1 // See: https://github.com/torvalds/linux/commit/b53911aa872db462be2e5f1dd611b25c4c2e663b 2 // and: https://github.com/torvalds/linux/blob/a409ed156a90093a03fe6a93721ddf4c591eac87/include/uapi/linux/gpio.h 3 // the kernel didn't keep the v1 abi for long, there's a v2 thing less than a year later. This v2 thing is new in kernel 5.10 4 /++ 5 PRERELEASE EXPERIMENTAL MODULE / SUBJECT TO CHANGE WITHOUT WARNING / LIKELY TO CONTAIN BUGS 6 7 Wrapper for gpio use on Linux. It uses the new kernel interface directly, and thus requires a Linux kernel version newer than 4.9. It also requires a Linux kernel newer than 4.9 (apt upgrade your raspian install if you don't already have that). 8 9 Note that the kernel documentation is very clear: do NOT use this for anything you plan to distribute to others. It is really just for tinkering, not production. And if the kernel people say that, I say it like 1000x more. 10 11 12 $(PITFALL This is a PRERELEASE EXPERIMENTAL MODULE SUBJECT TO CHANGE WITHOUT WARNING. It is LIKELY TO CONTAIN BUGS!) 13 14 GPIOHANDLE_REQUEST_BIAS_PULL_UP and friends were added to the kernel in early 2020, so bleeding edge feature that is unlikely to work if you aren't that new too. My rpis do NOT support it. (the python library sets similar values tho by poking memory registers. I'm not gonna do that here, you can also solve it with electric circuit design (6k-ish ohm pull up and/or pull down resistor) or just knowing your own setup... so meh.) 15 16 License: GPL-2.0 WITH Linux-syscall-note because it includes copy/pasted Linux kernel header code. 17 +/ 18 module arsd.gpio; 19 20 version(linux): 21 22 import core.sys.posix.unistd; 23 import core.sys.posix.fcntl; 24 import core.sys.posix.sys.ioctl; 25 26 /// 27 class CErrorException : Exception { 28 this(string operation, string file = __FILE__, size_t line = __LINE__) { 29 import core.stdc.errno; 30 import core.stdc.string; 31 auto err = strerror(errno); 32 super(operation ~ ": " ~ cast(string) err[0 .. strlen(err)], file, line); 33 } 34 } 35 36 private string c_dup(ref char[32] c) { 37 foreach(idx, ch; c) 38 if(ch == 0) 39 return c[0 .. idx].idup; 40 return null; 41 } 42 43 /// 44 struct GpioChip { 45 int fd = -1; 46 47 /// 48 string name; 49 /// 50 string label; 51 /// 52 int lines; 53 54 @disable this(this); 55 56 /// "/dev/gpiochip0". Note it MUST be zero terminated! 57 this(string name) { 58 gpiochip_info info; 59 60 fd = open(name.ptr, O_RDWR); 61 if(fd == -1) 62 throw new CErrorException("open " ~ name); 63 64 if(ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info) == -1) 65 throw new CErrorException("ioctl get chip info"); 66 67 name = info.name.c_dup; 68 label = info.label.c_dup; 69 lines = info.lines; 70 } 71 72 /// 73 void getLineInfo(int line, out gpioline_info info) { 74 info.line_offset = line; 75 76 if(ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &info) == -1) 77 throw new CErrorException("ioctl get line info"); 78 } 79 80 /// Returns a file descriptor you can pass to [pullLine] or [getLine] (pullLine for OUTPUT, getLine for INPUT). 81 int requestLine(string label, scope uint[] lines, int flags, scope ubyte[] defaults) { 82 assert(lines.length == defaults.length); 83 84 gpiohandle_request req; 85 86 req.lines = cast(uint) lines.length; 87 req.flags = flags; 88 req.lineoffsets[0 .. lines.length] = lines[]; 89 req.default_values[0 .. defaults.length] = defaults[]; 90 91 req.consumer_label[0 .. label.length] = label[]; 92 93 if(ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req) == -1) 94 throw new CErrorException("ioctl get line handle"); 95 96 if(req.fd <= 0) 97 throw new Exception("request line failed"); 98 99 return req.fd; 100 } 101 102 /// Returns a file descriptor you can poll and read for events. Read [gpioevent_data] from the fd. 103 int requestEvent(string label, int line, int handleFlags, int eventFlags) { 104 gpioevent_request req; 105 req.lineoffset = line; 106 req.handleflags = handleFlags; 107 req.eventflags = eventFlags; 108 req.consumer_label[0 .. label.length] = label[]; 109 110 111 if(ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req) == -1) 112 throw new CErrorException("get event handle"); 113 if(req.fd <= 0) 114 throw new Exception("request event failed"); 115 116 return req.fd; 117 } 118 119 /// named as in "pull it high"; it sets the status. 120 void pullLine(int handle, scope ubyte[] high) { 121 gpiohandle_data data; 122 123 data.values[0 .. high.length] = high[]; 124 125 if(ioctl(handle, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) == -1) 126 throw new CErrorException("ioctl pull line"); 127 } 128 129 /// 130 void getLine(int handle, scope ubyte[] status) { 131 gpiohandle_data data; 132 133 if(ioctl(handle, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) == -1) 134 throw new CErrorException("ioctl get line"); 135 136 status = data.values[0 .. status.length]; 137 } 138 139 ~this() { 140 if(fd != -1) 141 close(fd); 142 fd = -1; 143 } 144 } 145 146 version(gpio_demo) 147 void main() { 148 import std.stdio; 149 GpioChip g = GpioChip("/dev/gpiochip0"); 150 151 auto ledfd = g.requestLine("D test", [18], GPIOHANDLE_REQUEST_OUTPUT, [0]); 152 scope(exit) close(ledfd); 153 auto btnfd = g.requestEvent("D test", 15, GPIOHANDLE_REQUEST_INPUT, GPIOEVENT_REQUEST_BOTH_EDGES); 154 scope(exit) close(btnfd); 155 156 /* 157 gpioline_info info; 158 foreach(line; 0 .. g.lines) { 159 g.getLineInfo(line, info); 160 writeln(line, ": ", info.flags, " ", info.name, " ", info.consumer); 161 } 162 */ 163 164 import core.thread; 165 166 writeln("LED on"); 167 g.pullLine(ledfd, [1]); 168 169 foreach(i; 0 .. 3) { 170 gpioevent_data event; 171 read(btnfd, &event, event.sizeof); 172 173 writeln(event); 174 } 175 176 writeln("LED off"); 177 g.pullLine(ledfd, [0]); 178 } 179 180 181 182 183 // copy/paste port of linux/gpio.h from the kernel 184 // (this is why it inherited the GPL btw) 185 extern(C) { 186 187 import core.sys.posix.sys.ioctl; 188 189 /** 190 Information about a certain GPIO chip. ioctl [GPIO_GET_CHIPINFO_IOCTL] 191 */ 192 struct gpiochip_info { 193 /// the Linux kernel name of this GPIO chip 194 char[32] name = 0; 195 /// a functional name for this GPIO chip, such as a product number, may be null 196 char[32] label = 0; 197 /// number of GPIO lines on this chip 198 uint lines; 199 } 200 201 enum GPIOLINE_FLAG_KERNEL = (1 << 0); /// Informational flags 202 enum GPIOLINE_FLAG_IS_OUT = (1 << 1); /// ditto 203 enum GPIOLINE_FLAG_ACTIVE_LOW = (1 << 2); /// ditto 204 enum GPIOLINE_FLAG_OPEN_DRAIN = (1 << 3); /// ditto 205 enum GPIOLINE_FLAG_OPEN_SOURCE = (1 << 4); /// ditto 206 enum GPIOLINE_FLAG_BIAS_PULL_UP = (1 << 5); /// ditto 207 enum GPIOLINE_FLAG_BIAS_PULL_DOWN = (1 << 6); /// ditto 208 enum GPIOLINE_FLAG_BIAS_DISABLE = (1 << 7); /// ditto 209 210 /** 211 Information about a certain GPIO line 212 */ 213 struct gpioline_info { 214 /// the local offset on this GPIO device, fill this in when requesting the line information from the kernel 215 uint line_offset; 216 /// various flags for this line 217 uint flags; 218 /// the name of this GPIO line, such as the output pin of the line on the chip, a rail or a pin header name on a board, as specified by the gpio chip, may be null 219 char[32] c_name = 0; 220 /// a functional name for the consumer of this GPIO line as set by whatever is using it, will be null if there is no current user but may also be null if the consumer doesn't set this up 221 char[32] c_consumer = 0; 222 223 /// 224 string name() { return c_dup(c_name); } 225 /// 226 string consumer() { return c_dup(c_consumer); } 227 }; 228 229 /** Maximum number of requested handles */ 230 enum GPIOHANDLES_MAX = 64; 231 232 /// line status change events 233 enum { 234 GPIOLINE_CHANGED_REQUESTED = 1, 235 GPIOLINE_CHANGED_RELEASED, 236 GPIOLINE_CHANGED_CONFIG, 237 } 238 239 /** 240 Information about a change in status of a GPIO line 241 242 243 Note: struct gpioline_info embedded here has 32-bit alignment on its own, 244 but it works fine with 64-bit alignment too. With its 72 byte size, we can 245 guarantee there are no implicit holes between it and subsequent members. 246 The 20-byte padding at the end makes sure we don't add any implicit padding 247 at the end of the structure on 64-bit architectures. 248 */ 249 struct gpioline_info_changed { 250 gpioline_info info; /// updated line information 251 ulong timestamp; /// estimate of time of status change occurrence, in nanoseconds and GPIOLINE_CHANGED_CONFIG 252 uint event_type; /// one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED 253 uint[5] padding; /* for future use */ 254 } 255 256 enum GPIOHANDLE_REQUEST_INPUT = (1 << 0); /// Linerequest flags 257 enum GPIOHANDLE_REQUEST_OUTPUT = (1 << 1); /// ditto 258 enum GPIOHANDLE_REQUEST_ACTIVE_LOW = (1 << 2); /// ditto 259 enum GPIOHANDLE_REQUEST_OPEN_DRAIN = (1 << 3); /// ditto 260 enum GPIOHANDLE_REQUEST_OPEN_SOURCE = (1 << 4); /// ditto 261 enum GPIOHANDLE_REQUEST_BIAS_PULL_UP = (1 << 5); /// ditto 262 enum GPIOHANDLE_REQUEST_BIAS_PULL_DOWN = (1 << 6); /// ditto 263 enum GPIOHANDLE_REQUEST_BIAS_DISABLE = (1 << 7); /// ditto 264 265 266 /** 267 Information about a GPIO handle request 268 */ 269 struct gpiohandle_request { 270 /// an array desired lines, specified by offset index for the associated GPIO device 271 uint[GPIOHANDLES_MAX] lineoffsets; 272 273 /// desired flags for the desired GPIO lines, such as GPIOHANDLE_REQUEST_OUTPUT, GPIOHANDLE_REQUEST_ACTIVE_LOW etc, OR:ed together. Note that even if multiple lines are requested, the same flags must be applicable to all of them, if you want lines with individual flags set, request them one by one. It is possible to select a batch of input or output lines, but they must all have the same characteristics, i.e. all inputs or all outputs, all active low etc 274 uint flags; 275 /// if the GPIOHANDLE_REQUEST_OUTPUT is set for a requested line, this specifies the default output value, should be 0 (low) or 1 (high), anything else than 0 or 1 will be interpreted as 1 (high) 276 ubyte[GPIOHANDLES_MAX] default_values; 277 /// a desired consumer label for the selected GPIO line(s) such as "my-bitbanged-relay" 278 char[32] consumer_label = 0; 279 /// number of lines requested in this request, i.e. the number of valid fields in the above arrays, set to 1 to request a single line 280 uint lines; 281 /// if successful this field will contain a valid anonymous file handle after a GPIO_GET_LINEHANDLE_IOCTL operation, zero or negative value means error 282 int fd; 283 } 284 285 /// Configuration for a GPIO handle request 286 /// Note: only in kernel newer than early 2020 287 struct gpiohandle_config { 288 uint flags; /// updated flags for the requested GPIO lines, such as GPIOHANDLE_REQUEST_OUTPUT, GPIOHANDLE_REQUEST_ACTIVE_LOW etc, OR:ed together 289 ubyte[GPIOHANDLES_MAX] default_values; /// if the GPIOHANDLE_REQUEST_OUTPUT is set in flags, this specifies the default output value, should be 0 (low) or 1 (high), anything else than 0 or 1 will be interpreted as 1 (high) 290 uint[4] padding; /// must be 0 291 } 292 293 /// 294 enum GPIOHANDLE_SET_CONFIG_IOCTL = _IOWR!gpiohandle_config(0xB4, 0x0a); 295 296 297 /** 298 Information of values on a GPIO handle 299 */ 300 struct gpiohandle_data { 301 /// when getting the state of lines this contains the current state of a line, when setting the state of lines these should contain the desired target state 302 ubyte[GPIOHANDLES_MAX] values; 303 } 304 305 enum GPIOHANDLE_GET_LINE_VALUES_IOCTL = _IOWR!gpiohandle_data(0xB4, 0x08); /// . 306 enum GPIOHANDLE_SET_LINE_VALUES_IOCTL = _IOWR!gpiohandle_data(0xB4, 0x09); /// ditto 307 enum GPIOEVENT_REQUEST_RISING_EDGE = (1 << 0); /// Eventrequest flags 308 enum GPIOEVENT_REQUEST_FALLING_EDGE = (1 << 1); /// ditto 309 enum GPIOEVENT_REQUEST_BOTH_EDGES = ((1 << 0) | (1 << 1)); /// ditto 310 311 /** 312 Information about a GPIO event request 313 */ 314 struct gpioevent_request { 315 /// the desired line to subscribe to events from, specified by offset index for the associated GPIO device 316 uint lineoffset; 317 /// desired handle flags for the desired GPIO line, such as GPIOHANDLE_REQUEST_ACTIVE_LOW or GPIOHANDLE_REQUEST_OPEN_DRAIN 318 uint handleflags; 319 /// desired flags for the desired GPIO event line, such as GPIOEVENT_REQUEST_RISING_EDGE or GPIOEVENT_REQUEST_FALLING_EDGE 320 uint eventflags; 321 /// a desired consumer label for the selected GPIO line(s) such as "my-listener" 322 char[32] consumer_label = 0; 323 /// if successful this field will contain a valid anonymous file handle after a GPIO_GET_LINEEVENT_IOCTL operation, zero or negative value means error 324 int fd; 325 } 326 327 enum GPIOEVENT_EVENT_RISING_EDGE = 0x01; /// GPIO event types 328 enum GPIOEVENT_EVENT_FALLING_EDGE = 0x02; /// ditto 329 330 331 /** 332 The actual event being pushed to userspace 333 */ 334 struct gpioevent_data { 335 /// best estimate of time of event occurrence, in nanoseconds 336 ulong timestamp; 337 /// event identifier 338 uint id; 339 } 340 341 enum GPIO_GET_CHIPINFO_IOCTL = _IOR!gpiochip_info(0xB4, 0x01); /// . 342 enum GPIO_GET_LINEINFO_WATCH_IOCTL = _IOWR!gpioline_info(0xB4, 0x0b); /// ditto 343 enum GPIO_GET_LINEINFO_UNWATCH_IOCTL = _IOWR!uint(0xB4, 0x0c); /// ditto 344 enum GPIO_GET_LINEINFO_IOCTL = _IOWR!gpioline_info(0xB4, 0x02); /// ditto 345 enum GPIO_GET_LINEHANDLE_IOCTL = _IOWR!gpiohandle_request(0xB4, 0x03); /// ditto 346 enum GPIO_GET_LINEEVENT_IOCTL = _IOWR!gpioevent_request(0xB4, 0x04); /// ditto 347 }