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