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 }