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 }