From 50f0529d2bf3a5084020e2364347257d9ada172b Mon Sep 17 00:00:00 2001 From: Elara6331 Date: Wed, 13 Dec 2023 17:19:46 -0800 Subject: [PATCH] Add gpiodetect and gpioinfo commands --- README.md | 6 ++++-- build.zig | 46 ++++++++++++++++++++++++++++++++++++++---- cmd/detect.zig | 25 +++++++++++++++++++++++ cmd/info.zig | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ gpio.zig | 10 ++++++++++ index.zig | 1 + uapi.zig | 11 ++++++++++ 7 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 cmd/detect.zig create mode 100644 cmd/info.zig diff --git a/README.md b/README.md index c1599e1..f68b817 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,11 @@ _There's a companion article available on my website: https://www.elara.ws/artic ## Compatibility -**zig-gpio** uses the v2 character device API, which means it will work on any Linux system running kernel 5.10 or above. All you need to do is find out which `gpiochip` device controls which pin and what the offsets are, which you can do by either finding documentation online, or using the `gpiodetect` and `gpioinfo` tools from `libgpiod`. +**zig-gpio** uses the v2 character device API, which means it will work on any Linux system running kernel 5.10 or above. All you need to do is find out which `gpiochip` device controls which pin and what the offsets are, which you can do by either finding documentation online, or using the `gpiodetect` and `gpioinfo` tools from this repo or from `libgpiod`. -I plan to eventually write a Zig replacement for `gpiodetect` and `gpioinfo`. +## Commands + +**zig-gpio** provides replacements for some of the `libgpiod` tools, such as `gpiodetect` and `gpioinfo`. You can build all of them using `zig build commands` or specific ones using `zig build ` (for example: `zig build gpiodetect`). ## Try it yourself! diff --git a/build.zig b/build.zig index 560fbfa..22e25c4 100644 --- a/build.zig +++ b/build.zig @@ -1,18 +1,35 @@ const std = @import("std"); +const Item = struct { + name: []const u8, + src: []const u8, +}; + +/// List of examples +const examples = [_]Item{ + .{ .name = "blinky", .src = "_examples/blinky.zig" }, + .{ .name = "multi", .src = "_examples/multi.zig" }, +}; + +/// List of commands +const commands = [_]Item{ + .{ .name = "gpiodetect", .src = "cmd/detect.zig" }, + .{ .name = "gpioinfo", .src = "cmd/info.zig" }, +}; + pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + // Add the gpio module so it can be used by the package manager var gpio_module = b.createModule(.{ .source_file = .{ .path = "index.zig" } }); try b.modules.put(b.dupe("gpio"), gpio_module); + // Create a step to build all the examples const examples_step = b.step("examples", "build all the examples"); - inline for ([_]struct { name: []const u8, src: []const u8 }{ - .{ .name = "blinky", .src = "_examples/blinky.zig" }, - .{ .name = "multi", .src = "_examples/multi.zig" }, - }) |cfg| { + // Add all the examples + inline for (examples) |cfg| { const desc = try std.fmt.allocPrint(b.allocator, "build the {s} example", .{cfg.name}); const step = b.step(cfg.name, desc); @@ -28,4 +45,25 @@ pub fn build(b: *std.Build) !void { step.dependOn(&build_step.step); examples_step.dependOn(&build_step.step); } + + // Create a step to build all the commands + const commands_step = b.step("commands", "build all the commands"); + + // Add all the commands + inline for (commands) |cfg| { + const desc = try std.fmt.allocPrint(b.allocator, "build the {s} command", .{cfg.name}); + const step = b.step(cfg.name, desc); + + const exe = b.addExecutable(.{ + .name = cfg.name, + .root_source_file = .{ .path = cfg.src }, + .target = target, + .optimize = optimize, + }); + exe.addModule("gpio", gpio_module); + + const build_step = b.addInstallArtifact(exe, .{}); + step.dependOn(&build_step.step); + commands_step.dependOn(&build_step.step); + } } diff --git a/cmd/detect.zig b/cmd/detect.zig new file mode 100644 index 0000000..c8de3a3 --- /dev/null +++ b/cmd/detect.zig @@ -0,0 +1,25 @@ +const std = @import("std"); +const gpio = @import("gpio"); + +pub fn main() !void { + var iter_dir = try std.fs.openIterableDirAbsolute("/dev", .{}); + defer iter_dir.close(); + + const stdout = std.io.getStdOut().writer(); + + var iter = iter_dir.iterate(); + while (try iter.next()) |entry| { + if (!hasPrefix(entry.name, "gpiochip")) continue; + + const fl = try iter_dir.dir.openFile(entry.name, .{}); + var chip = try gpio.getChipByFd(fl.handle); + defer chip.close(); // This will close the fd + + try stdout.print("{s} [{s}] ({d} lines)\n", .{ chip.nameSlice(), chip.labelSlice(), chip.lines }); + } +} + +fn hasPrefix(s: []const u8, prefix: []const u8) bool { + if (s.len < prefix.len) return false; + return (std.mem.eql(u8, s[0..prefix.len], prefix)); +} diff --git a/cmd/info.zig b/cmd/info.zig new file mode 100644 index 0000000..bb3c651 --- /dev/null +++ b/cmd/info.zig @@ -0,0 +1,54 @@ +const std = @import("std"); +const gpio = @import("gpio"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer std.debug.assert(gpa.deinit() == .ok); + const alloc = gpa.allocator(); + + var dir = try std.fs.openDirAbsolute("/dev", .{}); + defer dir.close(); + + var args = std.process.args(); + _ = args.skip(); // Skip the program name + + // Iterate over each argument + while (args.next()) |arg| { + const fl = try dir.openFileZ(arg, .{}); + var chip = try gpio.getChipByFd(fl.handle); + defer chip.close(); // This will close the fd + + const stdout = std.io.getStdOut().writer(); + try stdout.print("{s} - {d} lines:\n", .{ chip.nameSlice(), chip.lines }); + + var offset: u32 = 0; + while (offset < chip.lines) : (offset += 1) { + const lineInfo = try chip.getLineInfo(offset); + + // Create an arraylist to store all the flag strings + var flags = std.ArrayList([]const u8).init(alloc); + defer flags.deinit(); + + // Appand any relevant flag strings to the array list + if (lineInfo.flags.input) try flags.append("input"); + if (lineInfo.flags.output) try flags.append("output"); + if (lineInfo.flags.used) try flags.append("used"); + if (lineInfo.flags.active_low) try flags.append("active_low"); + if (lineInfo.flags.edge_rising) try flags.append("edge_rising"); + if (lineInfo.flags.edge_falling) try flags.append("edge_falling"); + if (lineInfo.flags.open_drain) try flags.append("open_drain"); + if (lineInfo.flags.open_source) try flags.append("open_source"); + if (lineInfo.flags.bias_pull_up) try flags.append("bias_pull_up"); + if (lineInfo.flags.bias_pull_down) try flags.append("bias_pull_down"); + + // Join the array list into a string + const flagStr = try std.mem.join(alloc, ", ", flags.items); + defer alloc.free(flagStr); + + const name = if (lineInfo.name[0] != 0) lineInfo.nameSlice() else ""; + const consumer = if (lineInfo.flags.used) lineInfo.consumerSlice() else ""; + + try stdout.print(" line {d}: {s} {s} [{s}]\n", .{ offset, name, consumer, flagStr }); + } + } +} diff --git a/gpio.zig b/gpio.zig index 10b6eb6..189d6a4 100644 --- a/gpio.zig +++ b/gpio.zig @@ -40,6 +40,16 @@ pub const Chip = struct { lines: u32, closed: bool = false, + /// Returns the chip's name as a slice without any null characters + pub fn nameSlice(self: *Chip) []const u8 { + return std.mem.sliceTo(&self.name, 0); + } + + /// Returns the chip's label as a slice without any null characters + pub fn labelSlice(self: *Chip) []const u8 { + return std.mem.sliceTo(&self.label, 0); + } + /// Sets the chip's consumer value to `consumer`. pub fn setConsumer(self: *Chip, consumer: []const u8) !void { if (consumer.len > gpio.uapi.MAX_NAME_SIZE) return error.ConsumerTooLong; diff --git a/index.zig b/index.zig index f78df3e..030e7a9 100644 --- a/index.zig +++ b/index.zig @@ -2,6 +2,7 @@ pub const uapi = @import("uapi.zig"); pub const getChip = @import("gpio.zig").getChip; pub const getChipZ = @import("gpio.zig").getChipZ; +pub const getChipByFd = @import("gpio.zig").getChipByFd; pub const Chip = @import("gpio.zig").Chip; pub const Lines = @import("gpio.zig").Lines; pub const Line = @import("gpio.zig").Line; diff --git a/uapi.zig b/uapi.zig index 4024b76..bd1d80e 100644 --- a/uapi.zig +++ b/uapi.zig @@ -38,6 +38,16 @@ pub const LineInfo = extern struct { attrs: [MAX_LINE_NUM_ATTRS]LineAttribute, /// Reserved for future use _padding: [4]u32, + + /// Returns the line's name as a slice without any null characters + pub fn nameSlice(self: *const LineInfo) []const u8 { + return std.mem.sliceTo(&self.name, 0); + } + + /// Returns the line's consumer as a slice without any null characters + pub fn consumerSlice(self: *const LineInfo) []const u8 { + return std.mem.sliceTo(&self.consumer, 0); + } }; /// LineAttribute ID values @@ -171,6 +181,7 @@ fn handleErrno(ret: usize) !void { .INVAL => error.InvalidArgument, .BADF => error.BadFileDescriptor, .NOTTY => error.InappropriateIOCTLForDevice, + .IO => error.IOError, .FAULT => unreachable, else => |err| return std.os.unexpectedErrno(err), };