zig-riscv-embedded

Experimental Zig-based CoAP node for the HiFive1 RISC-V board

git clone https://git.8pit.net/zig-riscv-embedded.git

  1// Copyright © 2021 Sören Tempel
  2//
  3// This program is free software: you can redistribute it and/or modify
  4// it under the terms of the GNU Affero General Public License as
  5// published by the Free Software Foundation, either version 3 of the
  6// License, or (at your option) any later version.
  7//
  8// This program is distributed in the hope that it will be useful, but
  9// WITHOUT ANY WARRANTY; without even the implied warranty of
 10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 11// Affero General Public License for more details.
 12//
 13// You should have received a copy of the GNU Affero General Public License
 14// along with this program. If not, see <https://www.gnu.org/licenses/>.
 15
 16const zoap = @import("zoap");
 17const crc = @import("crc.zig");
 18const std = @import("std");
 19const console = @import("console.zig");
 20
 21const Plic = @import("plic.zig").Plic;
 22const Uart = @import("uart.zig").Uart;
 23
 24const FrameHandler = fn (ctx: ?*anyopaque, buf: []const u8) void;
 25const CoapHandler = fn (req: *zoap.Request) void;
 26
 27pub const Slip = struct {
 28    uart: *const Uart,
 29    plic: *const Plic,
 30    handler: ?FrameHandler = null,
 31    context: ?*anyopaque = null,
 32    rcvbuf: [MTU]u8 = undefined,
 33    rcvpos: usize = 0,
 34    prev_esc: bool = false,
 35
 36    // SLIP control bytes from RFC 1055.
 37    const END: u8 = 0o300;
 38    const ESC: u8 = 0o333;
 39    const ESC_END: u8 = 0o334;
 40    const ESC_ESC: u8 = 0o335;
 41
 42    // SLIP (as defined in RFC 1055) doesn't specify an MTU.
 43    const MTU: u32 = 1500;
 44
 45    fn writeByte(self: *Slip, byte: u8) void {
 46        self.rcvbuf[self.rcvpos] = byte;
 47        self.rcvpos += 1;
 48    }
 49
 50    fn handleByte(self: *Slip, byte: u8) !void {
 51        if (self.rcvpos >= self.rcvbuf.len) {
 52            self.prev_esc = false;
 53            return error.FrameTooLarge;
 54        }
 55
 56        switch (byte) {
 57            ESC => {
 58                self.prev_esc = true;
 59                return;
 60            },
 61            END => {
 62                if (self.handler != null)
 63                    self.handler.?(self.context, self.rcvbuf[0..self.rcvpos]);
 64                self.rcvpos = 0;
 65            },
 66            ESC_END, ESC_ESC => {
 67                var c: u8 = undefined;
 68                if (self.prev_esc) {
 69                    switch (byte) {
 70                        ESC_END => c = END,
 71                        ESC_ESC => c = ESC,
 72                        else => return error.UnknownEscapeSequence,
 73                    }
 74                } else {
 75                    c = byte;
 76                }
 77
 78                self.writeByte(c);
 79            },
 80            else => {
 81                self.writeByte(byte);
 82            },
 83        }
 84
 85        self.prev_esc = false;
 86    }
 87
 88    fn rxIrqHandler(self: *Slip) !void {
 89        while (self.uart.readByte()) |byte| {
 90            try self.handleByte(byte);
 91        }
 92    }
 93
 94    fn irqHandler(ctx: ?*anyopaque) void {
 95        var self: *Slip = @ptrCast(*Slip, @alignCast(@alignOf(*Slip), ctx.?));
 96
 97        const ip = self.uart.readIp();
 98        if (ip.rxwm) {
 99            rxIrqHandler(self) catch {
100                @panic("rx handler failed");
101            };
102        }
103    }
104
105    pub fn registerHandler(self: *Slip, func: FrameHandler, ctx: ?*anyopaque) !void {
106        // Enable RX interrupt, dissable TX interrupt.
107        self.uart.writeIe(false, true);
108
109        self.handler = func;
110        self.context = ctx;
111
112        try self.plic.registerHandler(self.uart.irq, irqHandler, self);
113    }
114};
115
116pub const FrameType = enum(u8) {
117    diagnostic = 0x0a,
118    coap = 0xa9,
119};
120
121pub const Frame = struct {
122    slip: *const Slip,
123    ftype: FrameType,
124    csum: crc.Incremental,
125
126    const WriteError = error{};
127    const FrameWriter = std.io.Writer(*Frame, WriteError, write);
128
129    fn init(slip: *const Slip, ftype: FrameType) Frame {
130        var frame = Frame{
131            .slip = slip,
132            .ftype = ftype,
133            .csum = .{},
134        };
135
136        frame.pushByte(@enumToInt(ftype));
137        return frame;
138    }
139
140    fn pushByteRaw(self: *Frame, byte: u8) void {
141        const uart = self.slip.uart;
142
143        // Busy wait for TX fifo to empty.
144        while (uart.isTxFull()) {}
145        uart.writeByte(byte);
146    }
147
148    fn pushByte(self: *Frame, byte: u8) void {
149        self.pushByteRaw(byte);
150        if (self.ftype == FrameType.coap)
151            self.csum.add(byte);
152    }
153
154    fn write(self: *Frame, data: []const u8) WriteError!usize {
155        for (data) |c| {
156            switch (c) {
157                Slip.END => {
158                    self.pushByte(Slip.ESC);
159                    self.pushByte(Slip.ESC_END);
160                },
161                Slip.ESC => {
162                    self.pushByte(Slip.ESC);
163                    self.pushByte(Slip.ESC_ESC);
164                },
165                else => {
166                    self.pushByte(c);
167                },
168            }
169        }
170
171        return data.len;
172    }
173
174    pub fn close(self: *Frame) void {
175        if (self.ftype == FrameType.coap) {
176            var fcs16 = self.csum.csum();
177            fcs16 ^= 0xffff; // complement
178
179            // XXX: Use @truncate instead?
180            self.pushByteRaw(@intCast(u8, fcs16 & @as(u16, 0x00ff)));
181            self.pushByteRaw(@intCast(u8, fcs16 >> 8 & @as(u16, 0x00ff)));
182        }
183
184        self.pushByteRaw(Slip.END);
185    }
186
187    pub fn writer(self: *Frame) FrameWriter {
188        return .{ .context = self };
189    }
190};
191
192pub const SlipMux = struct {
193    slip: *Slip,
194    handler: ?CoapHandler = null,
195
196    fn handleCoAP(self: *SlipMux, buf: []const u8) !void {
197        // 1 byte (frame type) + 4 byte (coap message) + 2 byte CRC
198        if (buf.len <= 7)
199            return error.CoAPFrameTooShort;
200        if (!crc.validCsum(buf))
201            return error.InvalidChecksum;
202
203        // Strip frame identifier and 16-bit CRC FCS.
204        const msgBuf = buf[1..(buf.len - @sizeOf(u16))];
205
206        var req = try zoap.Request.init(msgBuf);
207        self.handler.?(&req);
208    }
209
210    fn dispatchFrame(self: *SlipMux, buf: []const u8) !void {
211        switch (buf[0]) {
212            @enumToInt(FrameType.diagnostic) => {
213                return error.NoDiagnosticSupport;
214            },
215            @enumToInt(FrameType.coap) => {
216                try self.handleCoAP(buf);
217            },
218            else => {
219                return error.UnsupportedFrameType;
220            },
221        }
222    }
223
224    fn handleFrame(ctx: ?*anyopaque, buf: []const u8) void {
225        var self: *SlipMux = @ptrCast(*SlipMux, @alignCast(@alignOf(*SlipMux), ctx.?));
226        if (buf.len == 0)
227            return;
228
229        self.dispatchFrame(buf) catch |err| {
230            console.print("handleFrame failed: {s}\n", .{@errorName(err)});
231        };
232    }
233
234    pub fn newFrame(self: *SlipMux, ftype: FrameType) Frame {
235        return Frame.init(self.slip, ftype);
236    }
237
238    pub fn registerHandler(self: *SlipMux, handler: CoapHandler) !void {
239        self.handler = handler;
240        try self.slip.registerHandler(handleFrame, self);
241    }
242};