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