diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index 350efea75..5db0b5cec 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -4,6 +4,7 @@ const assert = std.debug.assert; const Bus = @import("cyw43439/bus.zig"); const WiFi = @import("cyw43439/wifi.zig"); +pub const JoinOptions = WiFi.JoinOptions; const log = std.log.scoped(.cyw43); @@ -27,7 +28,7 @@ pub fn init( self.mac = try self.read_mac(); } -pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: WiFi.JoinOptions) !void { +pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { try self.wifi.join(ssid, pwd, opt); } @@ -80,14 +81,6 @@ pub const Pin = struct { pin: u2, wifi: *WiFi, - pub fn get(self: *Pin) bool { - return self.wifi.gpio_get(self.pin); - } - - pub fn put(self: *Pin, value: u1) void { - self.wifi.gpio_set(self.pin, value); - } - pub fn toggle(self: *Pin) void { self.wifi.gpio_toggle(self.pin); } diff --git a/drivers/wireless/cyw43439/ioctl.zig b/drivers/wireless/cyw43439/ioctl.zig index 3cc867a22..e338d860c 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -192,7 +192,8 @@ pub const Response = struct { zero.msg.event_type = .none; return zero; } - var evt: EventPacket = @bitCast(buf[0..@sizeOf(EventPacket)].*); + var evt: EventPacket = undefined; + @memcpy(std.mem.asBytes(&evt), buf[0..@sizeOf(EventPacket)]); std.mem.byteSwapAllFields(EventPacket, &evt); return evt; } diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index c8351511a..11808979b 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -418,7 +418,7 @@ fn join_wait(self: *Self, wait_ms: u32, security: JoinOptions.Security) !void { var bytes: [512]u8 align(4) = undefined; while (delay < wait_ms) { - // self.log_read(); + // self.log_read(); // show chip logs const rsp = try self.read(&bytes) orelse { self.sleep_ms(ioctl.response_poll_interval); delay += ioctl.response_poll_interval; @@ -427,10 +427,6 @@ fn join_wait(self: *Self, wait_ms: u32, security: JoinOptions.Security) !void { switch (rsp.sdp.channel()) { .event => { const evt = rsp.event().msg; - // log.debug( - // " event type: {s:<15}, status: {s} flags: {x}", - // .{ @tagName(evt.event_type), @tagName(evt.status), evt.flags }, - // ); switch (evt.event_type) { .link => { if (evt.flags & 1 == 0) return error.Cyw43JoinLinkDown; @@ -456,18 +452,16 @@ fn join_wait(self: *Self, wait_ms: u32, security: JoinOptions.Security) !void { else => {}, } if (set_ssid and link_up and link_auth) { - //log.debug("join OK", .{}); return; } }, - else => self.log_response(rsp), + else => {}, } } return error.Cyw43JoinTimeout; } // show unexpected command response -// can be assert also fn log_response(self: *Self, rsp: ioctl.Response) void { _ = self; switch (rsp.sdp.channel()) { @@ -475,7 +469,7 @@ fn log_response(self: *Self, rsp: ioctl.Response) void { const evt = rsp.event().msg; if (evt.event_type == .none and evt.status == .success) return; - log.info( + log.debug( "unhandled event type: {}, status: {} ", .{ evt.event_type, evt.status }, ); diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 8d411c86f..d76f1a782 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -84,7 +84,10 @@ pub fn build(b: *std.Build) void { .{ .name = "ssd1306", .file = "src/ssd1306_oled.zig", .imports = &.{ .{ .name = "font8x8", .module = font8x8_dep.module("font8x8") }, } }, - .{ .name = "net-dhcp", .file = "src/net/dhcp.zig" }, + .{ .name = "net-pong", .file = "src/net/pong.zig" }, + .{ .name = "net-udp", .file = "src/net/udp.zig" }, + .{ .name = "net-tcp_client", .file = "src/net/tcp_client.zig" }, + .{ .name = "net-tcp_server", .file = "src/net/tcp_server.zig" }, }; var available_examples: std.array_list.Managed(Example) = .init(b.allocator); diff --git a/examples/raspberrypi/rp2xxx/src/net/lwip/include/lwipopts.h b/examples/raspberrypi/rp2xxx/src/net/lwip/include/lwipopts.h index 1934d40aa..9c2e2eaaf 100644 --- a/examples/raspberrypi/rp2xxx/src/net/lwip/include/lwipopts.h +++ b/examples/raspberrypi/rp2xxx/src/net/lwip/include/lwipopts.h @@ -1,3 +1,6 @@ +// all available options: +// https://github.com/lwip-tcpip/lwip/blob/STABLE-2_1_3_RELEASE/src/include/lwip/opt.h +// #ifndef lwipopts_h #define lwipopts_h @@ -37,9 +40,9 @@ #define MEMP_NUM_SYS_TIMEOUT 16 // callbacks -#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 0 #define LWIP_NETIF_STATUS_CALLBACK 1 -#define LWIP_NETIF_EXT_STATUS_CALLBACK 1 +#define LWIP_NETIF_EXT_STATUS_CALLBACK 0 // stats #define LWIP_STATS 1 diff --git a/examples/raspberrypi/rp2xxx/src/net/lwip/net.zig b/examples/raspberrypi/rp2xxx/src/net/lwip/net.zig index 8ce89eff5..2dce6479e 100644 --- a/examples/raspberrypi/rp2xxx/src/net/lwip/net.zig +++ b/examples/raspberrypi/rp2xxx/src/net/lwip/net.zig @@ -2,10 +2,12 @@ /// link interface. const std = @import("std"); -const log = std.log.scoped(.lwip); const assert = std.debug.assert; +fn assert_panic(ok: bool, msg: []const u8) void { + if (!ok) @panic(msg); +} -const c = @cImport({ +pub const lwip = @cImport({ @cInclude("lwip/init.h"); @cInclude("lwip/tcpip.h"); @cInclude("lwip/netif.h"); @@ -17,183 +19,478 @@ const c = @cImport({ @cInclude("lwip/timeouts.h"); }); -const Self = @This(); - -netif: c.netif = .{}, -dhcp: c.dhcp = .{}, - -mac: [6]u8, -link: struct { - ptr: *anyopaque, - recv: *const fn (*anyopaque, []u8) anyerror!?struct { usize, usize }, - send: *const fn (*anyopaque, []u8) anyerror!void, - ready: *const fn (*anyopaque) bool, -}, - -pub fn init(self: *Self) !void { - c.lwip_init(); - const netif: *c.netif = &self.netif; - - _ = c.netif_add( - netif, - @as(*c.ip4_addr_t, @ptrCast(@constCast(c.IP4_ADDR_ANY))), // ipaddr - @as(*c.ip4_addr_t, @ptrCast(@constCast(c.IP4_ADDR_ANY))), // netmask - @as(*c.ip4_addr_t, @ptrCast(@constCast(c.IP4_ADDR_ANY))), // gw - null, - netif_init, - c.netif_input, - ) orelse return error.OutOfMemory; - - c.netif_create_ip6_linklocal_address(netif, 1); - netif.ip6_autoconfig_enabled = 1; - c.netif_set_status_callback(netif, netif_status_callback); - c.netif_set_default(netif); - - c.dhcp_set_struct(netif, &self.dhcp); - c.netif_set_up(netif); - try c_err(c.dhcp_start(netif)); - c.netif_set_link_up(netif); -} +const log = std.log.scoped(.lwip); -fn netif_init(netif_c: [*c]c.netif) callconv(.c) c.err_t { - const netif: *c.netif = netif_c; - const self: *Self = @fieldParentPtr("netif", netif); - - netif.linkoutput = netif_linkoutput; - netif.output = c.etharp_output; - netif.output_ip6 = c.ethip6_output; - netif.mtu = sz.mtu; - netif.flags = c.NETIF_FLAG_BROADCAST | c.NETIF_FLAG_ETHARP | c.NETIF_FLAG_ETHERNET | c.NETIF_FLAG_IGMP | c.NETIF_FLAG_MLD6; - std.mem.copyForwards(u8, &netif.hwaddr, &self.mac); - netif.hwaddr_len = c.ETH_HWADDR_LEN; - return c.ERR_OK; -} +pub const Interface = struct { + const Self = @This(); + + netif: lwip.netif = .{}, + dhcp: lwip.dhcp = .{}, + + link: struct { + ptr: *anyopaque, + recv: *const fn (*anyopaque, []u8) anyerror!?struct { usize, usize }, + send: *const fn (*anyopaque, []u8) anyerror!void, + ready: *const fn (*anyopaque) bool, + }, + + pub const Options = struct { + fixed: ?Fixed = null, + + pub const Fixed = struct { + ip: lwip.ip4_addr, + netmask: lwip.ip4_addr, + gw: lwip.ip4_addr, + + pub fn init(ip: []const u8, netmask: []const u8, gw: []const u8) !Fixed { + var f: Fixed = undefined; + if (lwip.ip4addr_aton(ip.ptr, &f.ip) != 1 or + lwip.ip4addr_aton(netmask.ptr, &f.netmask) != 1 or + lwip.ip4addr_aton(gw.ptr, &f.gw) != 1) + return error.IpAddrParse; + return f; + } + }; + }; -fn netif_status_callback(netif_c: [*c]c.netif) callconv(.c) void { - const netif: *c.netif = netif_c; - const self: *Self = @fieldParentPtr("netif", netif); - log.debug("netif status callback is_link_up: {}, is_up: {}, ready: {}, ip: {f}", .{ - netif.flags & c.NETIF_FLAG_LINK_UP > 0, - netif.flags & c.NETIF_FLAG_UP > 0, - self.ready(), - IPFormatter.new(netif.ip_addr), - }); -} + pub fn init(self: *Self, mac: [6]u8, opt: Options) !void { + lwip.lwip_init(); + const netif: *lwip.netif = &self.netif; + + if (opt.fixed) |fixed| { + _ = lwip.netif_add( + netif, + &fixed.ip, + &fixed.netmask, + &fixed.gw, + null, + c_netif_init, + lwip.netif_input, + ) orelse return error.OutOfMemory; + } else { + _ = lwip.netif_add_noaddr( + netif, + null, + c_netif_init, + lwip.netif_input, + ) orelse return error.OutOfMemory; + } + + std.mem.copyForwards(u8, &netif.hwaddr, &mac); + lwip.netif_create_ip6_linklocal_address(netif, 1); + netif.ip6_autoconfig_enabled = 1; + lwip.netif_set_status_callback(netif, c_on_netif_status); + lwip.netif_set_default(netif); + lwip.netif_set_up(netif); + if (opt.fixed == null) { + lwip.dhcp_set_struct(netif, &self.dhcp); + try c_err(lwip.dhcp_start(netif)); + } + lwip.netif_set_link_up(netif); + } -/// Called by lwip when there is packet to send. -/// pbuf chain total_len is <= netif.mtu + ethernet header -fn netif_linkoutput(netif_c: [*c]c.netif, pbuf_c: [*c]c.pbuf) callconv(.c) c.err_t { - const netif: *c.netif = netif_c; - var pbuf: *c.pbuf = pbuf_c; - const self: *Self = @fieldParentPtr("netif", netif); + fn c_netif_init(netif_c: [*c]lwip.netif) callconv(.c) lwip.err_t { + const netif: *lwip.netif = netif_c; + netif.linkoutput = c_netif_linkoutput; + netif.output = lwip.etharp_output; + netif.output_ip6 = lwip.ethip6_output; + netif.mtu = sz.mtu; + netif.flags = lwip.NETIF_FLAG_BROADCAST | lwip.NETIF_FLAG_ETHARP | + lwip.NETIF_FLAG_ETHERNET | lwip.NETIF_FLAG_IGMP | lwip.NETIF_FLAG_MLD6; + netif.hwaddr_len = lwip.ETH_HWADDR_LEN; + return lwip.ERR_OK; + } - if (!self.link.ready(self.link.ptr)) { - log.err("linkouput link not ready", .{}); - return c.ERR_MEM; // lwip will try later + fn c_on_netif_status(netif_c: [*c]lwip.netif) callconv(.c) void { + const netif: *lwip.netif = netif_c; + const self: *Self = @fieldParentPtr("netif", netif); + log.debug("netif status callback is_link_up: {}, is_up: {}, ready: {}, ip: {f}", .{ + netif.flags & lwip.NETIF_FLAG_LINK_UP > 0, + netif.flags & lwip.NETIF_FLAG_UP > 0, + self.ready(), + IPFormatter.new(netif.ip_addr), + }); } - if (c.pbuf_header(pbuf, sz.link_head) != 0) { - log.err("can't get pbuf headroom len: {}, tot_len: {} ", .{ pbuf.len, pbuf.tot_len }); - return c.ERR_ARG; + /// Called by lwip when there is a packet to send. + /// pbuf chain total_len is <= netif.mtu + ethernet header + fn c_netif_linkoutput(netif_c: [*c]lwip.netif, pbuf_c: [*c]lwip.pbuf) callconv(.c) lwip.err_t { + const netif: *lwip.netif = netif_c; + var pbuf: *lwip.pbuf = pbuf_c; + const self: *Self = @fieldParentPtr("netif", netif); + + if (!self.link.ready(self.link.ptr)) { + log.err("linkouput link not ready", .{}); + return lwip.ERR_MEM; // lwip will try later + } + + if (lwip.pbuf_header(pbuf, sz.link_head) != 0) { + log.err("can't get pbuf headroom len: {}, tot_len: {} ", .{ pbuf.len, pbuf.tot_len }); + return lwip.ERR_ARG; + } + + if (pbuf.next != null) { + // clone chain into single packet buffer + pbuf = lwip.pbuf_clone(lwip.PBUF_RAW, lwip.PBUF_POOL, pbuf) orelse return lwip.ERR_MEM; + } + defer { + // free local pbuf clone (if clone was made) + if (pbuf_c.*.next != null) _ = lwip.pbuf_free(pbuf); + } + + self.link.send(self.link.ptr, payload_bytes(pbuf)) catch |err| { + log.err("link send {}", .{err}); + return lwip.ERR_ARG; + }; + return lwip.ERR_OK; } - if (pbuf.next != null) { - // clone chain into single packet buffer - pbuf = c.pbuf_clone(c.PBUF_RAW, c.PBUF_POOL, pbuf) orelse return c.ERR_MEM; + pub fn ready(self: *Self) bool { + const netif = &self.netif; + return (netif.flags & lwip.NETIF_FLAG_UP > 0) and + (netif.flags & lwip.NETIF_FLAG_LINK_UP > 0) and + (netif.ip_addr.u_addr.ip4.addr != 0 or netif.ip_addr.u_addr.ip6.addr[0] != 0); } - defer { - // free local clone is clone was made - if (pbuf_c.*.next != null) _ = c.pbuf_free(pbuf); + + pub fn poll(self: *Self) !void { + lwip.sys_check_timeouts(); + var packets: usize = 0; + while (true) : (packets += 1) { + // get packet buffer of the max size + const pbuf: *lwip.pbuf = lwip.pbuf_alloc( + lwip.PBUF_RAW, + sz.pbuf_pool, + lwip.PBUF_POOL, + ) orelse return error.OutOfMemory; + assert_panic( + pbuf.next == null and pbuf.len == pbuf.tot_len and pbuf.len == sz.pbuf_pool, + "net.Interface.pool invalid pbuf allocation", + ); + // receive into that buffer + const head, const len = try self.link.recv(self.link.ptr, payload_bytes(pbuf)) orelse { + // no data release packet buffer and exit loop + _ = lwip.pbuf_free(pbuf); + break; + }; + errdefer _ = lwip.pbuf_free(pbuf); // netif.input: takes ownership of pbuf on success + // set payload header and len + if (lwip.pbuf_header(pbuf, -@as(lwip.s16_t, @intCast(head))) != 0) { + return error.InvalidPbufHead; + } + pbuf.len = @intCast(len); + pbuf.tot_len = @intCast(len); + // pass data to the lwip input function + try c_err(self.netif.input.?(pbuf, &self.netif)); + } + if (packets > 0) { + lwip.sys_check_timeouts(); + } } - self.link.send(self.link.ptr, payload_bytes(pbuf)) catch |err| { - log.err("link send {}", .{err}); - return c.ERR_ARG; - }; - return c.ERR_OK; -} + pub fn log_stats(self: *Self) void { + _ = self; + const stats = lwip.lwip_stats; + log.debug("stats ip_frag: {}", .{stats.ip_frag}); + log.debug("stats icpmp: {}", .{stats.icmp}); + log.debug("stats mem: {} ", .{stats.mem}); + for (stats.memp, 0..) |s, i| { + log.debug("stats memp {}: {}", .{ i, s.* }); + } + } +}; -fn payload_bytes(pbuf: *c.pbuf) []u8 { +fn payload_bytes(pbuf: *lwip.pbuf) []u8 { return @as([*]u8, @ptrCast(pbuf.payload.?))[0..pbuf.len]; } -pub fn ready(self: *Self) bool { - const netif = &self.netif; - return (netif.flags & c.NETIF_FLAG_UP > 0) and - (netif.flags & c.NETIF_FLAG_LINK_UP > 0) and - (netif.ip_addr.u_addr.ip4.addr != 0 or netif.ip_addr.u_addr.ip6.addr[0] != 0); -} +pub const Udp = struct { + const Self = @This(); -pub fn poll(self: *Self) !void { - var mem_err_count: usize = 0; - while (true) { - // get packet buffer of the max size - const pbuf: *c.pbuf = c.pbuf_alloc(c.PBUF_RAW, sz.pbuf_pool, c.PBUF_POOL) orelse { - if (mem_err_count > 2) { - self.log_stats(); - return error.OutOfMemory; - } - mem_err_count += 1; - c.sys_check_timeouts(); - continue; - }; - mem_err_count = 0; - assert(pbuf.next == null); - assert(pbuf.len == pbuf.tot_len and pbuf.len == sz.pbuf_pool); - - // receive into that buffer - const head, const len = try self.link.recv(self.link.ptr, payload_bytes(pbuf)) orelse { - // no data release packet buffer and exit loop - _ = c.pbuf_free(pbuf); - break; + pub const RecvOptions = struct { + src: Endpoint, + /// If udp packet is fragmented into multiple lwip packet buffers (pbuf) + /// this will be false for all except the last fragment. + last_fragment: bool, + }; + const OnRecv = *const fn (*Self, []u8, RecvOptions) void; + + pcb: *lwip.udp_pcb, + on_recv: ?OnRecv = null, + + pub fn init(nic: *Interface) !Self { + const pcb: *lwip.udp_pcb = lwip.udp_new() orelse return error.OutOfMemory; + lwip.udp_bind_netif(pcb, &nic.netif); + pcb.ttl = 64; + return .{ + .pcb = pcb, }; - errdefer _ = c.pbuf_free(pbuf); // netif.input: transfers ownership of pbuf on success - // set payload header and len - if (c.pbuf_header(pbuf, -@as(c.s16_t, @intCast(head))) != 0) return error.InvalidPbufHead; - pbuf.len = @intCast(len); - pbuf.tot_len = @intCast(len); - // pass data to the lwip input function - try c_err(self.netif.input.?(pbuf, &self.netif)); } - c.sys_check_timeouts(); -} -pub fn log_stats(self: *Self) void { - _ = self; - const stats = c.lwip_stats; - log.debug("stats ip_frag: {}", .{stats.ip_frag}); - log.debug("stats icpmp: {}", .{stats.icmp}); - log.debug("stats mem: {} ", .{stats.mem}); - for (stats.memp, 0..) |s, i| { - log.debug("stats memp {}: {}", .{ i, s.* }); + pub fn send(self: *Self, data: []const u8, target: Endpoint) !void { + const pbuf: *lwip.pbuf = lwip.pbuf_alloc( + lwip.PBUF_TRANSPORT, + @intCast(data.len), + lwip.PBUF_POOL, + ) orelse return error.OutOfMemory; + defer _ = lwip.pbuf_free(pbuf); + try c_err(lwip.pbuf_take(pbuf, data.ptr, @intCast(data.len))); + try c_err(lwip.udp_sendto(self.pcb, pbuf, &target.addr, target.port)); } -} -pub const Udp = struct { - pcb: c.udp_pcb = .{}, - addr: c.ip_addr_t = .{}, - port: u16 = 0, + pub fn bind(self: *Self, port: u16, on_recv: OnRecv) !void { + assert_panic( + self.on_recv == null, + "net.Udp.bind already bound", + ); + self.on_recv = on_recv; + try c_err(lwip.udp_bind(self.pcb, lwip.IP_ADDR_ANY, port)); + lwip.udp_recv(self.pcb, Self.c_on_recv, self); + } - fn init(udp: *Udp, net: *Self, target: []const u8, port: u16) !void { - if (c.ipaddr_aton(target.ptr, &udp.addr) != 1) return error.IpAddrParse; - c.udp_bind_netif(&udp.pcb, &net.netif); - udp.port = port; - udp.pcb.ttl = 64; + fn c_on_recv( + ptr: ?*anyopaque, + _: [*c]lwip.udp_pcb, + pbuf_c: [*c]lwip.pbuf, + addr_c: [*c]const lwip.ip_addr, + port: u16, + ) callconv(.c) void { + var pbuf: *lwip.pbuf = pbuf_c; + const addr: lwip.ip_addr = addr_c[0]; + const self: *Self = @ptrCast(@alignCast(ptr.?)); + defer _ = lwip.pbuf_free(pbuf_c); + + while (true) { + const last_fragment = pbuf.next == null; + self.on_recv.?(self, payload_bytes(pbuf), .{ + .last_fragment = last_fragment, + .src = .{ .addr = addr, .port = port }, + }); + if (last_fragment) break; + pbuf = pbuf.next; + } } +}; - pub fn send(udp: *Udp, data: []const u8) !void { - const pbuf: *c.pbuf = c.pbuf_alloc(c.PBUF_TRANSPORT, @intCast(data.len), c.PBUF_POOL) orelse return error.OutOfMemory; - defer _ = c.pbuf_free(pbuf); - try c_err(c.pbuf_take(pbuf, data.ptr, @intCast(data.len))); - try c_err(c.udp_sendto(&udp.pcb, pbuf, &udp.addr, udp.port)); +pub const tcp = struct { + pub fn connect(nic: *Interface, conn: *Connection, target: *Endpoint) !void { + if (conn.pcb != null or conn.state == .open) return Error.AlreadyConnected; + if (conn.state != .closed) return Error.AlreadyConnecting; + conn.nic = nic; + + const pcb: *lwip.tcp_pcb = lwip.tcp_new() orelse return Error.OutOfMemory; + lwip.tcp_arg(pcb, conn); + lwip.tcp_err(pcb, Connection.c_on_err); + + errdefer _ = lwip.tcp_close(pcb); + try c_err(lwip.tcp_connect(pcb, &target.addr, target.port, Connection.c_on_connect)); + conn.state = .connecting; } + + pub const Connection = struct { + const Self = @This(); + + const State = enum { + closed, + connecting, + open, + }; + + const OnClose = *const fn (*Self, Error) void; + const OnRecv = *const fn (*Self, []u8) void; + const OnSent = *const fn (*Self, u16) void; + const OnConnect = *const fn (*Self) void; + + nic: ?*Interface = null, + pcb: ?*lwip.tcp_pcb = null, + state: State = .closed, + on_close: ?OnClose = null, + on_recv: ?OnRecv = null, + on_sent: ?OnSent = null, + on_connect: ?OnConnect = null, + + fn open(self: *Self, pcb: *lwip.tcp_pcb) void { + assert(self.nic != null); + lwip.tcp_bind_netif(pcb, &self.nic.?.netif); + lwip.tcp_arg(pcb, self); + lwip.tcp_recv(pcb, c_on_recv); + lwip.tcp_sent(pcb, c_on_sent); + lwip.tcp_err(pcb, c_on_err); + self.pcb = pcb; + self.state = .open; + if (self.on_connect) |cb| cb(self); + } + + fn c_on_err(ptr: ?*anyopaque, ce: lwip.err_t) callconv(.c) void { + const self: *Self = @ptrCast(@alignCast(ptr.?)); + // The corresponding pcb is already freed when this callback is called! + // https://www.nongnu.org/lwip/2_1_x/group__tcp__raw.html#gae1346c4e34d3bc7c01e1b47142ab3121 + self.pcb = null; + if (self.state != .closed) { + self.state = .closed; + if (self.on_close) |cb| cb(self, to_error(ce) orelse return); + } + } + + fn c_on_connect( + ptr: ?*anyopaque, + c_pcb: [*c]lwip.tcp_pcb, + ce: lwip.err_t, + ) callconv(.c) lwip.err_t { + if (ce != lwip.ERR_OK) return ce; // it is always 0 + const self: *Self = @ptrCast(@alignCast(ptr.?)); + if (self.pcb != null) { + lwip.tcp_abort(c_pcb); + log.debug("c_on_connect already connected, aborting pcb", .{}); + return lwip.ERR_ABRT; + } + assert_panic( + self.pcb == null and self.state == .connecting, + "net.tcp.Connection.c_on_connect invalid state", + ); + self.open(c_pcb); + return lwip.ERR_OK; + } + + fn c_on_recv( + ptr: ?*anyopaque, + _: [*c]lwip.tcp_pcb, + c_pbuf: [*c]lwip.pbuf, + ce: lwip.err_t, + ) callconv(.c) lwip.err_t { + const self: *Self = @ptrCast(@alignCast(ptr.?)); + if (c_pbuf == null) { + // peer clean close received + var ret: lwip.err_t = lwip.ERR_OK; + if (self.pcb != null) { + self.close() catch |err| { + log.err("c_on_recv close unexpected {}", .{err}); + ret = lwip.ERR_ABRT; + }; + if (self.on_close) |cb| cb(self, Error.EndOfStream); + } + return ret; + } + defer _ = lwip.pbuf_free(c_pbuf); + + if (to_error(ce)) |err| { + if (self.on_close) |cb| cb(self, err); + return lwip.ERR_OK; + } + + var pbuf: *lwip.pbuf = c_pbuf; + while (true) { + if (self.on_recv) |cb| cb(self, payload_bytes(pbuf)); + if (pbuf.next == null) break; + pbuf = pbuf.next; + } + if (self.pcb) |pcb| { + lwip.tcp_recved(pcb, c_pbuf.*.tot_len); + } + return lwip.ERR_OK; + } + + fn c_on_sent(ptr: ?*anyopaque, _: [*c]lwip.tcp_pcb, n: u16) callconv(.c) lwip.err_t { + const self: *Self = @ptrCast(@alignCast(ptr.?)); + if (self.on_sent) |cb| cb(self, n); + return lwip.ERR_OK; + } + + pub fn send(self: *Self, bytes: []const u8) !void { + const pcb = self.pcb orelse return Error.NotConnected; + try c_err(lwip.tcp_write(pcb, bytes.ptr, @intCast(bytes.len), lwip.TCP_WRITE_FLAG_COPY)); + try c_err(lwip.tcp_output(pcb)); + } + + pub fn close(self: *Self) !void { + const pcb = self.pcb orelse return Error.NotConnected; + self.pcb = null; + self.state = .closed; + c_err(lwip.tcp_close(pcb)) catch |err| { + lwip.tcp_abort(pcb); + return err; + }; + } + + /// Number of bytes currently available in the TCP send buffer for a + /// given TCP connection. + pub fn send_buffer(self: *Self) u16 { + const pcb = self.pcb orelse return lwip.TCP_SND_BUF; + return lwip.tcp_sndbuf(pcb); + } + + pub fn limits(self: *Self) void { + log.debug( + "tcp limits current snd_buf: {}, max snd_buf: {}, mss: {}, wnd: {}, snd_queuelen: {}", + .{ + self.send_buffer(), + lwip.TCP_SND_BUF, + lwip.TCP_MSS, + lwip.TCP_WND, + lwip.TCP_SND_QUEUELEN, + }, + ); + } + }; + + pub const Server = struct { + const Self = @This(); + + const OnAccept = *const fn () ?*Connection; + + nic: *Interface, + on_accept: OnAccept, + pcb: ?*lwip.tcp_pcb = null, + + pub fn bind(self: *Self, port: u16) !void { + const pcb: *lwip.tcp_pcb = lwip.tcp_new() orelse return Error.OutOfMemory; + + errdefer _ = lwip.tcp_close(pcb); + try c_err(lwip.tcp_bind(pcb, lwip.IP_ADDR_ANY, port)); + self.pcb = lwip.tcp_listen(pcb); + lwip.tcp_arg(self.pcb, self); + lwip.tcp_accept(self.pcb, c_on_accept); + } + + fn c_on_accept( + ptr: ?*anyopaque, + pcb: [*c]lwip.tcp_pcb, + ce: lwip.err_t, + ) callconv(.c) lwip.err_t { + const self: *Self = @ptrCast(@alignCast(ptr.?)); + if (to_error(ce)) |err| { + log.err("c_on_accept {}", .{err}); + return lwip.ERR_OK; + } + var conn = self.on_accept() orelse { + lwip.tcp_abort(pcb); + return lwip.ERR_ABRT; + }; + conn.nic = self.nic; + conn.open(pcb); + return lwip.ERR_OK; + } + }; }; -pub fn udp_init(self: *Self, udp: *Udp, target: []const u8, port: u16) !void { - try udp.init(self, target, port); -} +pub const Endpoint = struct { + addr: lwip.ip_addr = .{}, + port: u16 = 0, + + pub fn format(self: Endpoint, writer: anytype) !void { + const ip4_addr: *const lwip.ip4_addr_t = @ptrCast(&self.addr); + try writer.writeAll(std.mem.sliceTo(lwip.ip4addr_ntoa(ip4_addr), 0)); + var buf: [16]u8 = undefined; + try writer.writeAll(std.fmt.bufPrint(&buf, ":{}", .{self.port}) catch ""); + } -const Error = error{ + pub fn parse(ip: []const u8, port: u16) !Endpoint { + var target: Endpoint = .{ .port = port }; + if (lwip.ipaddr_aton(ip.ptr, &target.addr) != 1) return error.IpAddrParse; + return target; + } +}; + +pub const Error = error{ /// Out of memory error. OutOfMemory, /// Buffer error. @@ -226,63 +523,65 @@ const Error = error{ ConnectionClosed, /// Illegal argument. IllegalArgument, - /// + /// All other errors. Unknown, + /// Clean tcp close. + EndOfStream, }; +fn to_error(e: lwip.err_t) ?Error { + return switch (e) { + lwip.ERR_OK => null, + lwip.ERR_MEM => Error.OutOfMemory, + lwip.ERR_BUF => Error.BufferError, + lwip.ERR_TIMEOUT => Error.Timeout, + lwip.ERR_RTE => Error.Routing, + lwip.ERR_INPROGRESS => Error.InProgress, + lwip.ERR_VAL => Error.IllegalValue, + lwip.ERR_WOULDBLOCK => Error.WouldBlock, + lwip.ERR_USE => Error.AddressInUse, + lwip.ERR_ALREADY => Error.AlreadyConnecting, + lwip.ERR_ISCONN => Error.AlreadyConnected, + lwip.ERR_CONN => Error.NotConnected, + lwip.ERR_IF => Error.LowlevelInterfaceError, + lwip.ERR_ABRT => Error.ConnectionAborted, + lwip.ERR_RST => Error.ConnectionReset, + lwip.ERR_CLSD => Error.ConnectionClosed, + lwip.ERR_ARG => Error.IllegalArgument, + else => Error.Unknown, + }; +} + fn c_err(res: anytype) Error!void { switch (@TypeOf(res)) { - c.err_t => return switch (res) { - c.ERR_OK => {}, - c.ERR_MEM => Error.OutOfMemory, - c.ERR_BUF => Error.BufferError, - c.ERR_TIMEOUT => Error.Timeout, - c.ERR_RTE => Error.Routing, - c.ERR_INPROGRESS => Error.InProgress, - c.ERR_VAL => Error.IllegalValue, - c.ERR_WOULDBLOCK => Error.WouldBlock, - c.ERR_USE => Error.AddressInUse, - c.ERR_ALREADY => Error.AlreadyConnecting, - c.ERR_ISCONN => Error.AlreadyConnected, - c.ERR_CONN => Error.NotConnected, - c.ERR_IF => Error.LowlevelInterfaceError, - c.ERR_ABRT => Error.ConnectionAborted, - c.ERR_RST => Error.ConnectionReset, - c.ERR_CLSD => Error.ConnectionClosed, - c.ERR_ARG => Error.IllegalArgument, - else => { - log.err("error code: {}", .{res}); - @panic("unexpected lwip error code!"); - }, - }, - c.u8_t => { - if (res != 0) { - return Error.Unknown; - } + lwip.err_t => return to_error(res) orelse return, + lwip.u8_t => if (res != 0) { + return Error.Unknown; }, else => @compileError("unknown type"), } } const IPFormatter = struct { - addr: c.ip_addr_t, + addr: lwip.ip_addr_t, - pub fn new(addr: c.ip_addr_t) IPFormatter { + pub fn new(addr: lwip.ip_addr_t) IPFormatter { return IPFormatter{ .addr = addr }; } pub fn format( - addr: IPFormatter, + self: IPFormatter, writer: anytype, ) !void { - try writer.writeAll(std.mem.sliceTo(c.ip4addr_ntoa(@as(*const c.ip4_addr_t, @ptrCast(&addr.addr))), 0)); + const ip4_addr: *const lwip.ip4_addr_t = @ptrCast(&self.addr); + try writer.writeAll(std.mem.sliceTo(lwip.ip4addr_ntoa(ip4_addr), 0)); } }; // required buffer sizes const sz = struct { - const pbuf_pool = c.PBUF_POOL_BUFSIZE; // 1540 = 1500 mtu + ethernet + link head/tail - const link_head = c.PBUF_LINK_ENCAPSULATION_HLEN; // 22 + const pbuf_pool = lwip.PBUF_POOL_BUFSIZE; // 1540 = 1500 mtu + ethernet + link head/tail + const link_head = lwip.PBUF_LINK_ENCAPSULATION_HLEN; // 22 const link_tail = 4; // reserved for the status in recv buffer const ethernet = 14; // layer 2 ethernet header size diff --git a/examples/raspberrypi/rp2xxx/src/net/dhcp.zig b/examples/raspberrypi/rp2xxx/src/net/pong.zig similarity index 67% rename from examples/raspberrypi/rp2xxx/src/net/dhcp.zig rename to examples/raspberrypi/rp2xxx/src/net/pong.zig index ae7006280..2099c1bbc 100644 --- a/examples/raspberrypi/rp2xxx/src/net/dhcp.zig +++ b/examples/raspberrypi/rp2xxx/src/net/pong.zig @@ -14,7 +14,7 @@ pub const microzig_options = microzig.Options{ }; const log = std.log.scoped(.main); -const Net = @import("lwip/net.zig"); +const net = @import("lwip/net.zig"); comptime { _ = @import("lwip/exports.zig"); } @@ -35,12 +35,11 @@ pub fn main() !void { log.debug("mac address: {x}", .{wifi.mac}); // join network - try wifi.join(secrets.ssid, secrets.pwd, .{}); + try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); log.debug("wifi joined", .{}); - // init lwip - var net: Net = .{ - .mac = wifi.mac, + // init lwip network interface + var nic: net.Interface = .{ .link = .{ .ptr = wifi, .recv = drivers.WiFi.recv, @@ -48,12 +47,12 @@ pub fn main() !void { .ready = drivers.WiFi.ready, }, }; - try net.init(); + try nic.init(wifi.mac, try secrets.nic_options()); var ts = time.get_time_since_boot(); while (true) { // run lwip poller - try net.poll(); + try nic.poll(); // blink const now = time.get_time_since_boot(); @@ -63,13 +62,3 @@ pub fn main() !void { } } } - -// This will log dhcp assigned ip address, something like: -// -// ================ STARTING NEW LOGGER ================ -// [0.700113] debug (main): mac address: 2ccf67f3b7ea -// [4.382145] debug (main): wifi joined -// [5.095823] debug (lwip): netif status callback is_link_up: false, is_up: true, ready: false, ip: 0.0.0.0 -// [7.479941] debug (lwip): netif status callback is_link_up: true, is_up: true, ready: true, ip: 192.168.190.206 -// -// you can than ping that ip. diff --git a/examples/raspberrypi/rp2xxx/src/net/readme.md b/examples/raspberrypi/rp2xxx/src/net/readme.md new file mode 100644 index 000000000..3d7dfb6c6 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/net/readme.md @@ -0,0 +1,80 @@ +# Examples for the pico 2w and lwip network + +Currently tested only on pico 2w, should also work on pico w. + +## secrets.zig + +Enter your ssid, wifi password and wifi security type into `secrets.zig` file. All examples will used those credentials to connect to the WiFi. There is the option to use DHCP or fixed IP address. + +`tcp_client` example also uses `host_ip` when connecting to the host, enter your desktop host IP address there. + +## pong.zig + +This only sets up cyw43 (pico WiFi chip), joins the WiFi network, and initializes lwip stack. +When connected it will display its IP address in the log: + +``` +================ STARTING NEW LOGGER ================ +debug (main): mac address: 2ccf67f3b7ea +debug (main): wifi joined +debug (lwip): netif status callback is_link_up: false, is_up: true, ready: false, ip: 0.0.0.0 +debug (lwip): netif status callback is_link_up: true, is_up: true, ready: true, ip: 192.168.190.206 +``` + +Than you can ping that address from the your host computer and see responses. + + +## udp.zig + +Listens for UDP packets on port 9999. Each received packet is echoed to the source IP address and port 9999. + +Get pico IP address from log, if you are using DHCP: +``` +debug (lwip): netif status callback is_link_up: true, is_up: true, ready: true, ip: 192.168.190.206 +``` + +Then on host computer send something to that IP and port 9999: +```sh +echo "hello from host" | ncat -u 192.168.190.50 9999 + +ncat -u 192.168.190.50 9999 < LICENSE +``` + +To receive echoed packets start listening on the host: +```sh +ncat -ulp 9999 +``` + +Then send something to the pico again. + + +## tcp_server.zig + +Listens for TCP connections on the port 9998 and logs received data. + +Once you find pico IP in the log you can send something to that IP, for example to send file content: + +```sh +ncat 192.168.190.206 9998 < build.zig +``` + +Examples allocates space for two connections when both are active it will not receive any new connections. + + +## tcp_client.zig + +Connects to the host computer, sends TCP payload of various sizes. From time to time closes connection and reconnects. Receives TCP data and logs it. + +Host computer should listen on the port 9998 for TCP connections. This examples uses `host_ip` from secrets. + +On the host listen for TCP connections on port 9998: +```sh +ncat -lkv -p 9998 +``` + +To echo same packet back to the pico: +```sh +ncat -lkv -p 9998 --exec /bin/cat +``` + +This example will send various TCP payload sizes. When the payload is too big for the TCP send buffer out of memory error will be raised on send. diff --git a/examples/raspberrypi/rp2xxx/src/net/secrets.zig b/examples/raspberrypi/rp2xxx/src/net/secrets.zig index 5689acaa4..623a36f35 100644 --- a/examples/raspberrypi/rp2xxx/src/net/secrets.zig +++ b/examples/raspberrypi/rp2xxx/src/net/secrets.zig @@ -1,2 +1,26 @@ +const net = @import("lwip/net.zig"); +const microzig = @import("microzig"); +const JoinOptions = microzig.hal.drivers.WiFi.Chip.JoinOptions; + +// wifi credentials pub const ssid = "..."; pub const pwd = "..."; +pub const join_opt: JoinOptions = .{ .security = .wpa2_psk }; + +pub fn nic_options() !net.Interface.Options { + // to use dhcp assigned ip addres + return .{}; + + // // to use fixed ip change to something like: + // return .{ + // .fixed = try .init( + // "192.168.190.50", // fixed ip + // "255.255.255.0", // subnet mask + // "192.168.190.1", // gateway + // ), + // }; +} + +// IP address of the desktop host computer. +// Some examples will send to that host. +pub const host_ip = "192.168.190.235"; diff --git a/examples/raspberrypi/rp2xxx/src/net/tcp_client.zig b/examples/raspberrypi/rp2xxx/src/net/tcp_client.zig new file mode 100644 index 000000000..dfb3c52a9 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/net/tcp_client.zig @@ -0,0 +1,158 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const rp2xxx = microzig.hal; +const time = rp2xxx.time; +const gpio = rp2xxx.gpio; +const pio = rp2xxx.pio; +const drivers = rp2xxx.drivers; + +const uart = rp2xxx.uart.instance.num(0); +const uart_tx_pin = gpio.num(0); +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = rp2xxx.uart.log, +}; +const log = std.log.scoped(.main); + +const net = @import("lwip/net.zig"); +comptime { + _ = @import("lwip/exports.zig"); +} +const secrets = @import("secrets.zig"); + +pub fn main() !void { + // init uart logging + uart_tx_pin.set_function(.uart); + uart.apply(.{ + .clock_config = rp2xxx.clock_config, + }); + rp2xxx.uart.init_logger(uart); + + // init cyw43 + var wifi_driver: drivers.WiFi = .{}; + var wifi = try wifi_driver.init(.{}); + var led = wifi.gpio(0); + // join network + try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); + + // init lwip network interface + var nic: net.Interface = .{ + .link = .{ + .ptr = wifi, + .recv = drivers.WiFi.recv, + .send = drivers.WiFi.send, + .ready = drivers.WiFi.ready, + }, + }; + try nic.init(wifi.mac, try secrets.nic_options()); + + // init handler + const target = try net.Endpoint.parse(secrets.host_ip, 9998); + var cli: Client = .{ + .target = target, + .nic = &nic, + .conn = .{ + .on_recv = Client.on_recv, + .on_sent = Client.on_sent, + .on_close = Client.on_close, + .on_connect = Client.on_connect, + }, + }; + + var ts = time.get_time_since_boot(); + while (true) { + // run lwip poller + nic.poll() catch |err| { + log.err("net pool {}", .{err}); + }; + + const now = time.get_time_since_boot(); + if (now.diff(ts).to_us() > 500_000) { + // blink + ts = now; + led.toggle(); + + if (nic.ready()) { + cli.tick(); + } + } + } +} + +const Client = struct { + const Self = @This(); + + nic: *net.Interface, + target: net.Endpoint, + conn: net.tcp.Connection, + bytes_sent: usize = 0, + bytes_received: usize = 0, + send_count: usize = 0, + + fn on_connect(_: *net.tcp.Connection) void { + log.debug("connection open", .{}); + } + + fn on_close(_: *net.tcp.Connection, err: net.Error) void { + log.debug("connection closed {}", .{err}); + } + + fn on_recv(tcp: *net.tcp.Connection, bytes: []u8) void { + const self: *Self = @fieldParentPtr("conn", tcp); + self.bytes_received += bytes.len; + log.debug( + "recv {} bytes, total {} data: {s}", + .{ bytes.len, self.bytes_received, bytes[0..@min(64, bytes.len)] }, + ); + } + + fn on_sent(tcp: *net.tcp.Connection, n: u16) void { + const self: *Self = @fieldParentPtr("conn", tcp); + self.bytes_sent += n; + log.debug("sent {} bytes, total {}", .{ n, self.bytes_sent }); + } + + fn tick(self: *Self) void { + switch (self.conn.state) { + .closed => { + net.tcp.connect(self.nic, &self.conn, &self.target) catch |err| { + log.err("tcp connect {}", .{err}); + return; + }; + }, + .open => { + self.send_count += 1; + // close + if (self.send_count % 16 == 0) { + self.conn.close() catch |err| { + log.err("tcp close {}", .{err}); + }; + return; + } + // TCP_SND_BUF is maximum send buffer size, default is 536 * 2 = 1072 bytes + // ref: https://github.com/lwip-tcpip/lwip/blob/6ca936f6b588cee702c638eee75c2436e6cf75de/src/include/lwip/opt.h#L1310 + var buf: [net.lwip.TCP_SND_BUF + 128]u8 = @splat('-'); + // add some header to the buf + _ = std.fmt.bufPrint( + &buf, + "hello from rpi pi pico {}", + .{self.send_count}, + ) catch unreachable; + // change len on each send + const chunk_len = (self.send_count * 64) % buf.len; + // Try to send. If buf.len is greater than + // self.tcp.send_buffer() it will fail with OutOfMemory while + // trying to copy to tcp send buffer. + self.conn.send(buf[0..chunk_len]) catch |err| { + log.err("send {} bytes {}", .{ chunk_len, err }); + if (err == error.OutOfMemory) { + self.conn.limits(); + } + return; + }; + log.debug("sent {} bytes", .{chunk_len}); + }, + .connecting => {}, + } + } +}; diff --git a/examples/raspberrypi/rp2xxx/src/net/tcp_server.zig b/examples/raspberrypi/rp2xxx/src/net/tcp_server.zig new file mode 100644 index 000000000..79f51feb6 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/net/tcp_server.zig @@ -0,0 +1,112 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const rp2xxx = microzig.hal; +const time = rp2xxx.time; +const gpio = rp2xxx.gpio; +const pio = rp2xxx.pio; +const drivers = rp2xxx.drivers; + +const uart = rp2xxx.uart.instance.num(0); +const uart_tx_pin = gpio.num(0); +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = rp2xxx.uart.log, +}; +const log = std.log.scoped(.main); + +const net = @import("lwip/net.zig"); +comptime { + _ = @import("lwip/exports.zig"); +} +const secrets = @import("secrets.zig"); + +pub fn main() !void { + // init uart logging + uart_tx_pin.set_function(.uart); + uart.apply(.{ + .clock_config = rp2xxx.clock_config, + }); + rp2xxx.uart.init_logger(uart); + + // init cyw43 + var wifi_driver: drivers.WiFi = .{}; + var wifi = try wifi_driver.init(.{}); + var led = wifi.gpio(0); + // join network + try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); + + // init lwip network interface + var nic: net.Interface = .{ + .link = .{ + .ptr = wifi, + .recv = drivers.WiFi.recv, + .send = drivers.WiFi.send, + .ready = drivers.WiFi.ready, + }, + }; + try nic.init(wifi.mac, try secrets.nic_options()); + + // init server + var srv: net.tcp.Server = .{ + .nic = &nic, + .on_accept = on_accept, + }; + try srv.bind(9998); + + var ts = time.get_time_since_boot(); + while (true) { + // run lwip poller + nic.poll() catch |err| { + log.err("net pool {}", .{err}); + }; + + const now = time.get_time_since_boot(); + if (now.diff(ts).to_us() > 500_000) { + // blink + ts = now; + led.toggle(); + } + } +} + +var pool: [2]Session = @splat(.{}); + +fn on_accept() ?*net.tcp.Connection { + for (pool[0..]) |*handler| { + if (handler.conn.state != .closed) continue; + handler.* = .{ + .recv_bytes = 0, + .conn = .{ + .on_recv = Session.on_recv, + .on_connect = Session.on_connect, + .on_close = Session.on_close, + }, + }; + return &handler.conn; + } + return null; +} + +const Session = struct { + const Self = @This(); + + conn: net.tcp.Connection = .{}, + recv_bytes: usize = 0, + + fn on_recv(conn: *net.tcp.Connection, bytes: []u8) void { + const self: *Self = @fieldParentPtr("conn", conn); + self.recv_bytes += bytes.len; + log.debug( + "{x} recv {} bytes, total: {} {s}", + .{ @intFromPtr(conn), bytes.len, self.recv_bytes, @import("udp.zig").data_head(bytes, 64) }, + ); + } + + fn on_connect(conn: *net.tcp.Connection) void { + log.debug("{x} connected", .{@intFromPtr(conn)}); + } + + fn on_close(conn: *net.tcp.Connection, err: net.Error) void { + log.debug("{x} closed {}", .{ @intFromPtr(conn), err }); + } +}; diff --git a/examples/raspberrypi/rp2xxx/src/net/udp.zig b/examples/raspberrypi/rp2xxx/src/net/udp.zig new file mode 100644 index 000000000..a08fcbc49 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/net/udp.zig @@ -0,0 +1,86 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const rp2xxx = microzig.hal; +const time = rp2xxx.time; +const gpio = rp2xxx.gpio; +const pio = rp2xxx.pio; +const drivers = rp2xxx.drivers; + +const uart = rp2xxx.uart.instance.num(0); +const uart_tx_pin = gpio.num(0); +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = rp2xxx.uart.log, +}; +const log = std.log.scoped(.main); + +const net = @import("lwip/net.zig"); +comptime { + _ = @import("lwip/exports.zig"); +} +const secrets = @import("secrets.zig"); + +pub fn main() !void { + // init uart logging + uart_tx_pin.set_function(.uart); + uart.apply(.{ + .clock_config = rp2xxx.clock_config, + }); + rp2xxx.uart.init_logger(uart); + + // init cyw43 + var wifi_driver: drivers.WiFi = .{}; + var wifi = try wifi_driver.init(.{}); + var led = wifi.gpio(0); + // join network + try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); + + // init lwip network interface + var nic: net.Interface = .{ + .link = .{ + .ptr = wifi, + .recv = drivers.WiFi.recv, + .send = drivers.WiFi.send, + .ready = drivers.WiFi.ready, + }, + }; + try nic.init(wifi.mac, try secrets.nic_options()); + // udp init + var udp: net.Udp = try .init(&nic); + // listen for udp packets on port 9999 and call on_recv for each received packet + try udp.bind(9999, on_recv); + + var ts = time.get_time_since_boot(); + while (true) { + // run lwip poller + nic.poll() catch |err| { + log.err("net pool {}", .{err}); + }; + + // blink + const now = time.get_time_since_boot(); + if (now.diff(ts).to_us() > 500_000) { + ts = now; + led.toggle(); + } + } +} + +fn on_recv(udp: *net.Udp, bytes: []u8, opt: net.Udp.RecvOptions) void { + // show received packet + log.debug( + "received {} bytes, from: {f}, last: {}, data: {s}", + .{ bytes.len, opt.src, opt.last_fragment, data_head(bytes, 32) }, + ); + // echo same data to the source address and port 9999 + udp.send(bytes, .{ .addr = opt.src.addr, .port = 9999 }) catch |err| { + log.err("udp send {}", .{err}); + }; +} + +// log helper +pub fn data_head(bytes: []u8, max: usize) []u8 { + const head: []u8 = bytes[0..@min(max, bytes.len)]; + std.mem.replaceScalar(u8, head, '\n', ' '); + return head; +}