From 10ae4d25fe73de4961e7736ae76ee31480d10fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Tue, 13 Jan 2026 16:45:48 +0100 Subject: [PATCH 01/19] cyw43: add join poller Besides blocking wifi join we can now poll for the join status while still have option to do another work between polls. --- drivers/wireless/cyw43439.zig | 8 +++ drivers/wireless/cyw43439/wifi.zig | 81 +++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 25 deletions(-) diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index f539fd6b6..6ff4de756 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -33,6 +33,10 @@ pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !v try self.wifi.join(ssid, pwd, opt); } +pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !WiFi.JoinPoller { + return try self.wifi.join_poller(ssid, pwd, opt); +} + fn show_clm_ver(self: *Self) !void { var data: [128]u8 = @splat(0); const n = try self.wifi.get_var("clmver", &data); @@ -84,6 +88,10 @@ pub const Pin = struct { pub fn toggle(self: *Pin) void { self.wifi.gpio_toggle(self.pin); } + + pub fn put(self: *Pin, value: u1) void { + self.wifi.gpio_put(self.pin, value); + } }; pub fn link(self: *Self) Link { diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 6f86e3f11..a06ce98f7 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -276,7 +276,13 @@ pub const JoinOptions = struct { }; }; +/// Blocking wifi network join pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { + var jp = try self.join_poller(ssid, pwd, opt); + try jp.wait(30 * 1000); +} + +pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !JoinPoller { const bus = self.bus; // Clear pullups @@ -406,35 +412,43 @@ pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !v try self.set_cmd(.set_ssid, ioctl.encode_ssid(&buf, ssid)); } - - try self.join_wait(30 * 1000, opt.security); + return .{ + .wifi = self, + .state = .{ .link_auth = opt.security == .open }, + }; } -fn join_wait(self: *Self, wait_ms: u32, security: JoinOptions.Security) !void { - var delay: u32 = 0; - var link_up: bool = false; - var link_auth: bool = security == .open; - var set_ssid: bool = false; - var bytes: [512]u8 align(4) = undefined; +pub const JoinPoller = struct { + const State = packed struct { + link_up: bool = false, + link_auth: bool = false, + set_ssid: bool = false, - while (delay < wait_ms) { - // 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; - continue; + pub const connected: State = .{ + .link_up = true, + .link_auth = true, + .set_ssid = true, }; + }; + + wifi: *Self, + state: State = .{}, + + pub fn poll(jp: *JoinPoller) !void { + var bytes: [512]u8 align(4) = undefined; + const rsp = try jp.wifi.read(&bytes) orelse return; + switch (rsp.sdp.channel()) { .event => { const evt = rsp.event().msg; switch (evt.event_type) { .link => { if (evt.flags & 1 == 0) return error.Cyw43JoinLinkDown; - link_up = true; + jp.state.link_up = true; }, .psk_sup => { if (evt.status != .unsolicited) return error.Cyw43JoinWpaHandshake; - link_auth = true; + jp.state.link_auth = true; }, .assoc => { if (evt.status != .success) return error.Cyw43JoinAssocRequest; @@ -447,19 +461,30 @@ fn join_wait(self: *Self, wait_ms: u32, security: JoinOptions.Security) !void { }, .set_ssid => { if (evt.status != .success) return error.Cyw43JoinSetSsid; - set_ssid = true; + jp.state.set_ssid = true; }, else => {}, } - if (set_ssid and link_up and link_auth) { - return; - } }, else => {}, } } - return error.Cyw43JoinTimeout; -} + + pub fn is_connected(jp: *JoinPoller) bool { + return jp.state == State.connected; + } + + pub fn wait(jp: *JoinPoller, wait_ms: u32) !void { + var delay: u32 = 0; + try jp.poll(); + while (delay < wait_ms and !jp.is_connected()) { + jp.wifi.sleep_ms(ioctl.response_poll_interval); + delay += ioctl.response_poll_interval; + try jp.poll(); + } + return error.Cyw43JoinTimeout; + } +}; // show unexpected command response fn log_response(self: *Self, rsp: ioctl.Response) void { @@ -585,9 +610,15 @@ pub fn gpio_enable(self: *Self, pin: u2) void { } pub fn gpio_toggle(self: *Self, pin: u2) void { - var val = self.bus.read_int(u32, .backplane, chip.gpio.output); - val = val ^ @as(u32, 1) << pin; - self.bus.write_int(u32, .backplane, chip.gpio.output, val); + var reg = self.bus.read_int(u32, .backplane, chip.gpio.output); + reg = reg ^ @as(u32, 1) << pin; + self.bus.write_int(u32, .backplane, chip.gpio.output, reg); +} + +pub fn gpio_put(self: *Self, pin: u2, value: u1) void { + var reg = self.bus.read_int(u32, .backplane, chip.gpio.output); + reg = reg | @as(u32, value) << pin; + self.bus.write_int(u32, .backplane, chip.gpio.output, reg); } // to set gpio pin by sending command From 5abc9c2bf81e1333fb6c674d4940d498b55d6b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Wed, 14 Jan 2026 14:27:46 +0100 Subject: [PATCH 02/19] cyw43: add scan poller --- drivers/wireless/cyw43439.zig | 4 + drivers/wireless/cyw43439/ioctl.zig | 169 ++++++++++++++++++++-------- drivers/wireless/cyw43439/wifi.zig | 129 +++++++++++++++++++-- 3 files changed, 244 insertions(+), 58 deletions(-) diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index 6ff4de756..a6ff277d9 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -37,6 +37,10 @@ pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOpti return try self.wifi.join_poller(ssid, pwd, opt); } +pub fn scan_poller(self: *Self) !WiFi.ScanPoller { + return try self.wifi.scan_poller(); +} + fn show_clm_ver(self: *Self) !void { var data: [128]u8 = @splat(0); const n = try self.wifi.get_var("clmver", &data); diff --git a/drivers/wireless/cyw43439/ioctl.zig b/drivers/wireless/cyw43439/ioctl.zig index e338d860c..3746b5af9 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -96,6 +96,7 @@ pub const Cmd = enum(u32) { set_wsec = 134, set_band = 142, set_wpa_auth = 165, + set_scan_channel_time = 185, get_var = 262, set_var = 263, set_wsec_pmk = 268, @@ -197,6 +198,55 @@ pub const Response = struct { std.mem.byteSwapAllFields(EventPacket, &evt); return evt; } + + pub fn event_scan_result(self: Self, res: *EventScanResult) !Security { + assert(self.sdp.channel() == .event); + const buf = self.data(); + if (buf.len < @sizeOf(EventPacket) + @sizeOf(EventScanResult)) { + return error.Cyw43InsufficientData; + } + const res_buf = buf[@sizeOf(EventPacket)..]; + @memcpy(std.mem.asBytes(res), res_buf[0..@sizeOf(EventScanResult)]); + res.info.channel &= 0xff; + if (res_buf.len < res.info.ie_offset + res.info.ie_length) { + return error.Cyw43InsufficientData; + } + + // ref: https://github.com/georgerobotics/cyw43-driver/blob/13004039ffe127519f33824bf7d240e1f23fbdcd/src/cyw43_ll.c#L538 + var security: Security = .{}; + const is_open = res.info.capability & 0x0010 == 0; + if (!is_open) security.wep_psk = true; + + var ie_buf = res_buf[res.info.ie_offset..][0..res.info.ie_length]; + while (ie_buf.len >= 2) { + const typ = ie_buf[0]; + const len = ie_buf[1]; + if (typ == 48) { + security.wpa2 = true; + } else { + const wpa_oui_type1 = "\x00\x50\xF2\x01"; + if (typ == 221 and ie_buf.len >= 2 + wpa_oui_type1.len) { + if (std.mem.eql(u8, ie_buf[2..][0..4], wpa_oui_type1)) { + security.wpa = true; + } + } + } + if (ie_buf.len < 2 + len) break; + ie_buf = ie_buf[2 + len ..]; + } + + return security; + } +}; + +pub const Security = packed struct { + wep_psk: bool = false, + wpa: bool = false, + wpa2: bool = false, + + pub fn open(s: Security) bool { + return @as(u3, @bitCast(s)) == 0; + } }; pub fn response(buf: []const u8) !Response { @@ -350,11 +400,46 @@ const EventPacket = extern struct { }; // Escan result event (excluding 12-byte IOCTL header and BDC header) -const EventScanResult = extern struct { - eth: EthernetHeader, - hdr: EventHeader, - msg: EventMessage, - scan: ScanResultHeader, +pub const EventScanResult = extern struct { + // Scan result header (part of wl_escan_result_t) + const Header = extern struct { + buflen: u32, + version: u32, + sync_id: u16, + bss_count: u16, + }; + + // BSS info from EScan (part of wl_bss_info_t) + const BssInfo = extern struct { + version: u32, // version field + length: u32, // byte length of data in this record, starting at version and including IEs + bssid: [6]u8, // The MAC address of the Access Point (AP) + beacon_period: u16, // Interval between two consecutive beacon frames. Units are Kusec + capability: u16, // Capability information + ssid_len: u8, // SSID length + ssid: [32]u8, // Array to store SSID + nrates: u32, // Count of rates in this set + rates: [16]u8, // rates in 500kbps units, higher bit set if basic + channel: u16, // Channel specification for basic service set + atim_window: u16, // Announcement traffic indication message window size. Units are Kusec + dtim_period: u8, // Delivery traffic indication message period + rssi: u16, // receive signal strength (in dBm) + phy_noise: u8, // noise (in dBm) + // The following fields assume the 'version' field is 109 (0x6D) + n_cap: u8, // BSS is 802.11N Capable + nbss_cap: u32, // 802.11N BSS Capabilities (based on HT_CAP_*) + ctl_ch: u8, // 802.11N BSS control channel number + reserved1: u32, // Reserved for expansion of BSS properties + flags: u8, // flags + reserved2: [3]u8, // Reserved for expansion of BSS properties + basic_mcs: [16]u8, // 802.11N BSS required MCS set + ie_offset: u16, // offset at which IEs start, from beginning + ie_length: u32, // byte length of Information Elements + snr: u16, // Average SNR(signal to noise ratio) during frame reception + // Variable-length Information Elements follow, see cyw43_ll_wifi_parse_scan_result + }; + + hdr: Header, info: BssInfo, }; @@ -389,48 +474,9 @@ const EventMessage = extern struct { bsscfgidx: u8, }; -// Scan result header (part of wl_escan_result_t) -const ScanResultHeader = extern struct { - buflen: u32, - version: u32, - sync_id: u16, - bss_count: u16, -}; - -// BSS info from EScan (part of wl_bss_info_t) -const BssInfo = extern struct { - version: u32, // version field - length: u32, // byte length of data in this record, starting at version and including IEs - bssid: [6]u8, // Unique 6-byte MAC address - beacon_period: u16, // Interval between two consecutive beacon frames. Units are Kusec - capability: u16, // Capability information - ssid_len: u8, // SSID length - ssid: [32]u8, // Array to store SSID - nrates: u32, // Count of rates in this set - rates: [16]u8, // rates in 500kbps units, higher bit set if basic - channel: u16, // Channel specification for basic service set - atim_window: u16, // Announcement traffic indication message window size. Units are Kusec - dtim_period: u8, // Delivery traffic indication message period - rssi: u16, // receive signal strength (in dBm) - phy_noise: u8, // noise (in dBm) - // The following fields assume the 'version' field is 109 (0x6D) - n_cap: u8, // BSS is 802.11N Capable - nbss_cap: u32, // 802.11N BSS Capabilities (based on HT_CAP_*) - ctl_ch: u8, // 802.11N BSS control channel number - reserved1: u32, // Reserved for expansion of BSS properties - flags: u8, // flags - reserved2: [3]u8, // Reserved for expansion of BSS properties - basic_mcs: [16]u8, // 802.11N BSS required MCS set - ie_offset: u16, // offset at which IEs start, from beginning - ie_length: u32, // byte length of Information Elements - snr: u16, // Average SNR(signal to noise ratio) during frame reception - // Variable-length Information Elements follow, see cyw43_ll_wifi_parse_scan_result -}; - -// zig fmt: off - // Async events -const EventType = enum(u32) { +pub const EventType = enum(u32) { + // zig fmt: off none = 0xffffffff, set_ssid = 0, // indicates status of set ssid , join = 1, // differentiates join ibss from found (wlc_e_start) ibss @@ -582,9 +628,17 @@ const EventType = enum(u32) { ext_auth_frame_rx = 188, // authentication request received mgmt_frame_txstatus = 189, // mgmt frame Tx complete _, -}; + // zig fmt: on -// zig fmt: on + pub fn mask(events: []const EventType) [26]u8 { + var m: [26]u8 = @splat(0); + for (events) |event| { + const e: u32 = @intFromEnum(event); + m[4 + e / 8] |= @as(u8, 1) << @as(u3, @truncate(e & 7)); + } + return m; + } +}; pub const EventStatus = enum(u32) { /// operation was successful @@ -718,3 +772,22 @@ test "small data is padded in request to 4 bytes" { try testing.expectEqualSlices(u8, r1, r2); } + +test "events mask" { + const buf = hex_to_bytes("000000008B120102004000000000800100000000000000000000"); + try testing.expectEqual(26, buf.len); + const mask = EventType.mask(&.{ + .join, + .assoc, + .reassoc, + .assoc_req_ie, + .assoc_resp_ie, + .set_ssid, + .link, + .auth, + .psk_sup, + .eapol_msg, + .disassoc_ind, + }); + try testing.expectEqualSlices(u8, &buf, &mask); +} diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index a06ce98f7..08e7b6c44 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -337,16 +337,20 @@ pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOpti try self.set_var("ampdu_rx_factor", &.{0x00}); self.sleep_ms(150); } - // Enable events - { - // using events list from: https://github.com/jbentham/picowi/blob/bb33b1e7a15a685f06dda6764b79e429ce9b325e/lib/picowi_join.c#L38 - // ref: https://github.com/jbentham/picowi/blob/bb33b1e7a15a685f06dda6764b79e429ce9b325e/lib/picowi_join.c#L74 - // can be something like: - // ref: https://github.com/embassy-rs/embassy/blob/96a026c73bad2ebb8dfc78e88c9690611bf2cb97/cyw43/src/control.rs#L242 - const buf = ioctl.hex_to_bytes("000000008B120102004000000000800100000000000000000000"); - try self.set_var("bsscfg:event_msgs", &buf); - self.sleep_ms(50); - } + try self.enable_events(&.{ + .join, + .assoc, + .reassoc, + .assoc_req_ie, + .assoc_resp_ie, + .set_ssid, + .link, + .auth, + .psk_sup, + .eapol_msg, + .disassoc_ind, + }); + var buf: [64]u8 = @splat(0); // space for 10 addresses // Enable multicast { @@ -486,6 +490,111 @@ pub const JoinPoller = struct { } }; +const ScanParams = extern struct { + version: u32 = 1, + action: u16 = 1, + sync_id: u16 = 0x1, + + ssid_len: u32 = 0, + ssid: [32]u8 = @splat(0), + + bssid: [6]u8 = .{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + bss_type: u8 = 2, + scan_type: u8 = 1, // passive + + nprobes: u32 = 0xff_ff_ff_ff, + active_time: u32 = 0xff_ff_ff_ff, + passive_time: u32 = 0xff_ff_ff_ff, + home_time: u32 = 0xff_ff_ff_ff, + + nchans: u16 = 0, + nssids: u16 = 0, + + chans: [14][2]u8 = @splat(@splat(0)), + ssids: [1][32]u8 = @splat(@splat(0)), +}; + +fn enable_events(self: *Self, events: []const ioctl.EventType) !void { + const mask = ioctl.EventType.mask(events); + try self.set_var("bsscfg:event_msgs", &mask); + self.sleep_ms(50); +} + +/// Init scan and return poller +pub fn scan_poller(self: *Self) !ScanPoller { + try self.enable_events(&.{ .escan_result, .set_ssid }); + + try self.set_cmd(.set_scan_channel_time, &.{40}); + try self.set_var("pm2_sleep_ret", &.{0xc8}); + try self.set_var("bcn_li_bcn", &.{1}); + try self.set_var("bcn_li_dtim", &.{1}); + try self.set_var("assoc_listen", &.{0x0a}); + try self.set_cmd(.set_band, &.{0}); + try self.set_cmd(.up, &.{0}); + var scan_params: ScanParams = .{}; + try self.set_var("escan", mem.asBytes(&scan_params)); + + return .{ + .wifi = self, + }; +} + +pub const ScanPoller = struct { + wifi: *Self, + res: ioctl.EventScanResult = undefined, + done: bool = false, + + const Result = struct { + ssid: []const u8, + ap_mac: [6]u8, + security: ioctl.Security, + channel: u16, + }; + + /// Returns ssid of the found wifi network. Null is returned when scan is + /// completed. Zero length ssid when there is no event to read. + /// Same ssid can be returned mutliple times. + /// + /// var sp = try wifi.scan_poller(); + /// while (try sp.poll()) |ssid| { + /// if (ssid.len != 0) { + /// log.debug("ssid: {s}", .{ssid}); + /// } + /// hal.time.sleep_ms(10); + /// } + /// + pub fn poll(sp: *ScanPoller) !?Result { + if (sp.done) return null; + var bytes: [1280]u8 align(4) = undefined; + if (try sp.wifi.read(&bytes)) |rsp| { + switch (rsp.sdp.channel()) { + .event => { + const evt = rsp.event().msg; + switch (evt.event_type) { + .escan_result => { + if (evt.status == .success) { + sp.done = true; + return null; + } + const security = try rsp.event_scan_result(&sp.res); + if (sp.res.info.ssid_len == 0) return null; + return .{ + .ap_mac = sp.res.info.bssid, + .ssid = sp.res.info.ssid[0..sp.res.info.ssid_len], + .security = security, + .channel = sp.res.info.channel, + }; + }, + else => sp.wifi.log_response(rsp), + } + }, + else => sp.wifi.log_response(rsp), + } + } + return null; + } +}; + // show unexpected command response fn log_response(self: *Self, rsp: ioctl.Response) void { _ = self; From 6a84a353090fe30d3310b7ff2a47fe875bb51c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Thu, 15 Jan 2026 11:57:24 +0100 Subject: [PATCH 03/19] cyw43: use single event handler During join and later in recv. We can now detect disconnect, reconnect and set connected state. --- drivers/wireless/cyw43439.zig | 4 + drivers/wireless/cyw43439/ioctl.zig | 2 +- drivers/wireless/cyw43439/wifi.zig | 209 ++++++++++++++++++---------- 3 files changed, 142 insertions(+), 73 deletions(-) diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index a6ff277d9..6c8aacb8c 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -29,6 +29,10 @@ pub fn init( self.mac = try self.read_mac(); } +pub fn connected(self: *Self) bool { + return self.wifi.connected(); +} + pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { try self.wifi.join(ssid, pwd, opt); } diff --git a/drivers/wireless/cyw43439/ioctl.zig b/drivers/wireless/cyw43439/ioctl.zig index 3746b5af9..54a228d88 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -460,7 +460,7 @@ const EventHeader = extern struct { }; // Raw event header (sdpcm_raw_event_header_t) -const EventMessage = extern struct { +pub const EventMessage = extern struct { version: u16, flags: u16, event_type: EventType, diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 08e7b6c44..5c78518e1 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -15,6 +15,7 @@ credit: u8 = 0, tx_sequence: u8 = 0, status: ?Status = null, log_state: LogState = .{}, +events: Events = .{}, const ioctl_request_bytes_len = 1024; @@ -341,14 +342,14 @@ pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOpti .join, .assoc, .reassoc, - .assoc_req_ie, - .assoc_resp_ie, .set_ssid, .link, .auth, .psk_sup, - .eapol_msg, .disassoc_ind, + .disassoc, + .deauth, + .deauth_ind, }); var buf: [64]u8 = @splat(0); // space for 10 addresses @@ -416,66 +417,57 @@ pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOpti try self.set_cmd(.set_ssid, ioctl.encode_ssid(&buf, ssid)); } - return .{ - .wifi = self, - .state = .{ .link_auth = opt.security == .open }, - }; + if (opt.security == .open) { + self.events.psk_sup = .success; + } + + return .{ .wifi = self }; } -pub const JoinPoller = struct { - const State = packed struct { - link_up: bool = false, - link_auth: bool = false, - set_ssid: bool = false, - - pub const connected: State = .{ - .link_up = true, - .link_auth = true, - .set_ssid = true, - }; - }; +pub fn connected(self: Self) bool { + return self.events.connected(); +} +pub const JoinPoller = struct { wifi: *Self, - state: State = .{}, - pub fn poll(jp: *JoinPoller) !void { + // Join to open network events: + // [1.824251] type: .auth, status: .success + // [1.859136] type: .assoc, status: .success + // [1.876234] type: .link, status: .success + // [1.893244] type: .join, status: .success + // [1.910249] type: .set_ssid, status: .success + // Join to wpa2 network events: + // [4.039611] type: .auth, status: .success + // [4.073556] type: .assoc, status: .success + // [4.090268] type: .link, status: .success + // [4.106842] type: .psk_sup, status: .unsolicited + // [4.124026] type: .join, status: .success + // [4.140596] type: .set_ssid, status: .success + + // On disconnect: + // [10.410040] type: .deauth_ind, status: .success + // [11.456022] type: .auth, status: .fail + // [20.875620] type: .link, status: .success + // On reconnect + // [39.896166] type: .auth, status: .success + // [39.910309] type: .reassoc, status: .success + // [39.917245] type: .link, status: .success + // [40.202375] type: .join, status: .success + + pub fn poll(jp: *JoinPoller) !bool { + const events = &jp.wifi.events; + if (events.connected()) return false; + var bytes: [512]u8 align(4) = undefined; - const rsp = try jp.wifi.read(&bytes) orelse return; + const rsp = try jp.wifi.read(&bytes) orelse return true; switch (rsp.sdp.channel()) { - .event => { - const evt = rsp.event().msg; - switch (evt.event_type) { - .link => { - if (evt.flags & 1 == 0) return error.Cyw43JoinLinkDown; - jp.state.link_up = true; - }, - .psk_sup => { - if (evt.status != .unsolicited) return error.Cyw43JoinWpaHandshake; - jp.state.link_auth = true; - }, - .assoc => { - if (evt.status != .success) return error.Cyw43JoinAssocRequest; - }, - .auth => { - if (evt.status != .success) return error.Cyw43JoinAuthRequest; - }, - .disassoc_ind => { - return error.Cyw43JoinDisassocIndication; - }, - .set_ssid => { - if (evt.status != .success) return error.Cyw43JoinSetSsid; - jp.state.set_ssid = true; - }, - else => {}, - } - }, - else => {}, + .event => jp.wifi.handle_event(&rsp.event().msg), + else => jp.wifi.log_response(rsp), } - } - - pub fn is_connected(jp: *JoinPoller) bool { - return jp.state == State.connected; + try events.err(); + return !events.connected(); } pub fn wait(jp: *JoinPoller, wait_ms: u32) !void { @@ -490,6 +482,49 @@ pub const JoinPoller = struct { } }; +fn handle_event(self: *Self, evt: *const ioctl.EventMessage) void { + const events = &self.events; + const state = Events.State.from; + // log.debug( + // "poll event type: {}, status: {} ", + // .{ evt.event_type, evt.status }, + // ); + switch (evt.event_type) { + .link => { + events.link = state(evt.status == .success and evt.flags & 1 > 0); + }, + .psk_sup => { + events.psk_sup = + state(evt.status == .success or evt.status == .unsolicited); + }, + .assoc, .reassoc => { + events.assoc = state(evt.status == .success); + }, + .disassoc_ind, .disassoc => { + events.assoc = .fail; + }, + .auth => { + events.auth = state(evt.status == .success); + }, + .deauth_ind, .deauth => { + events.auth = .fail; + }, + .set_ssid => { + events.set_ssid = state(evt.status == .success); + }, + .join => { + events.join = state(evt.status == .success); + }, + .none => {}, + else => { + log.warn( + "unhandled event type: {}, status: {} ", + .{ evt.event_type, evt.status }, + ); + }, + } +} + const ScanParams = extern struct { version: u32 = 1, action: u16 = 1, @@ -545,24 +580,16 @@ pub const ScanPoller = struct { done: bool = false, const Result = struct { - ssid: []const u8, - ap_mac: [6]u8, - security: ioctl.Security, - channel: u16, + ssid: []const u8 = &.{}, + ap_mac: [6]u8 = @splat(0), + security: ioctl.Security = .{}, + channel: u16 = 0, + + pub fn empty(res: Result) bool { + return res.ssid.len == 0; + } }; - /// Returns ssid of the found wifi network. Null is returned when scan is - /// completed. Zero length ssid when there is no event to read. - /// Same ssid can be returned mutliple times. - /// - /// var sp = try wifi.scan_poller(); - /// while (try sp.poll()) |ssid| { - /// if (ssid.len != 0) { - /// log.debug("ssid: {s}", .{ssid}); - /// } - /// hal.time.sleep_ms(10); - /// } - /// pub fn poll(sp: *ScanPoller) !?Result { if (sp.done) return null; var bytes: [1280]u8 align(4) = undefined; @@ -577,7 +604,7 @@ pub const ScanPoller = struct { return null; } const security = try rsp.event_scan_result(&sp.res); - if (sp.res.info.ssid_len == 0) return null; + if (sp.res.info.ssid_len == 0) return .{}; return .{ .ap_mac = sp.res.info.bssid, .ssid = sp.res.info.ssid[0..sp.res.info.ssid_len], @@ -591,7 +618,7 @@ pub const ScanPoller = struct { else => sp.wifi.log_response(rsp), } } - return null; + return .{}; } }; @@ -646,6 +673,7 @@ pub fn recv_zc(self: *Self, buffer: []u8) !?struct { usize, usize } { const rsp = try self.read(buffer) orelse return null; switch (rsp.sdp.channel()) { .data => return rsp.data_pos(), + .event => self.handle_event(&rsp.event().msg), else => self.log_response(rsp), } } @@ -752,6 +780,43 @@ const Status = packed struct { _reserved3: u12, }; +const Events = struct { + pub const State = enum(u2) { + none, + success, + fail, + + fn from(ok: bool) State { + return if (ok) .success else .fail; + } + }; + + link: State = .none, + psk_sup: State = .none, + set_ssid: State = .none, + assoc: State = .none, + auth: State = .none, + join: State = .none, + + pub fn err(e: Events) !void { + if (e.link == .fail) return error.Cyw43LinkDown; + if (e.psk_sup == .fail) return error.Cyw43WpaHandshake; + if (e.set_ssid == .fail) return error.Cyw43SetSsid; + if (e.assoc == .fail) return error.Cyw43AssocRequest; + if (e.auth == .fail) return error.Cyw43AuthRequest; + if (e.join == .fail) return error.Cyw43Join; + } + + pub fn connected(e: Events) bool { + return e.link == .success and + e.psk_sup == .success and + e.set_ssid == .success and + e.assoc == .success and + e.auth == .success and + e.join == .success; + } +}; + // CYW43439 chip values const chip = struct { const wrapper_register_offset: u32 = 0x100000; From 07a430e140522d886ffeee4b82943a6776f23e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Fri, 16 Jan 2026 13:02:02 +0100 Subject: [PATCH 04/19] unify pollers interface Make them both return has more flag. --- drivers/wireless/cyw43439/ioctl.zig | 16 +++-- drivers/wireless/cyw43439/wifi.zig | 105 +++++++++++++++++++--------- 2 files changed, 82 insertions(+), 39 deletions(-) diff --git a/drivers/wireless/cyw43439/ioctl.zig b/drivers/wireless/cyw43439/ioctl.zig index 54a228d88..b60a407a4 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -199,35 +199,37 @@ pub const Response = struct { return evt; } - pub fn event_scan_result(self: Self, res: *EventScanResult) !Security { + pub fn event_scan_result(self: Self) !struct { EventScanResult, Security } { + var res: EventScanResult = undefined; + var sec: Security = .{}; + assert(self.sdp.channel() == .event); const buf = self.data(); if (buf.len < @sizeOf(EventPacket) + @sizeOf(EventScanResult)) { return error.Cyw43InsufficientData; } const res_buf = buf[@sizeOf(EventPacket)..]; - @memcpy(std.mem.asBytes(res), res_buf[0..@sizeOf(EventScanResult)]); + @memcpy(std.mem.asBytes(&res), res_buf[0..@sizeOf(EventScanResult)]); res.info.channel &= 0xff; if (res_buf.len < res.info.ie_offset + res.info.ie_length) { return error.Cyw43InsufficientData; } // ref: https://github.com/georgerobotics/cyw43-driver/blob/13004039ffe127519f33824bf7d240e1f23fbdcd/src/cyw43_ll.c#L538 - var security: Security = .{}; const is_open = res.info.capability & 0x0010 == 0; - if (!is_open) security.wep_psk = true; + if (!is_open) sec.wep_psk = true; var ie_buf = res_buf[res.info.ie_offset..][0..res.info.ie_length]; while (ie_buf.len >= 2) { const typ = ie_buf[0]; const len = ie_buf[1]; if (typ == 48) { - security.wpa2 = true; + sec.wpa2 = true; } else { const wpa_oui_type1 = "\x00\x50\xF2\x01"; if (typ == 221 and ie_buf.len >= 2 + wpa_oui_type1.len) { if (std.mem.eql(u8, ie_buf[2..][0..4], wpa_oui_type1)) { - security.wpa = true; + sec.wpa = true; } } } @@ -235,7 +237,7 @@ pub const Response = struct { ie_buf = ie_buf[2 + len ..]; } - return security; + return .{ res, sec }; } }; diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 5c78518e1..6d97a4aab 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -472,11 +472,10 @@ pub const JoinPoller = struct { pub fn wait(jp: *JoinPoller, wait_ms: u32) !void { var delay: u32 = 0; - try jp.poll(); - while (delay < wait_ms and !jp.is_connected()) { + while (delay < wait_ms) { + if (!try jp.poll()) return; jp.wifi.sleep_ms(ioctl.response_poll_interval); delay += ioctl.response_poll_interval; - try jp.poll(); } return error.Cyw43JoinTimeout; } @@ -557,7 +556,7 @@ fn enable_events(self: *Self, events: []const ioctl.EventType) !void { /// Init scan and return poller pub fn scan_poller(self: *Self) !ScanPoller { - try self.enable_events(&.{ .escan_result, .set_ssid }); + try self.enable_events(&.{.escan_result}); try self.set_cmd(.set_scan_channel_time, &.{40}); try self.set_var("pm2_sleep_ret", &.{0xc8}); @@ -576,8 +575,18 @@ pub fn scan_poller(self: *Self) !ScanPoller { pub const ScanPoller = struct { wifi: *Self, - res: ioctl.EventScanResult = undefined, - done: bool = false, + + /// Poller not done, client should poll for more results + more: bool = true, + /// Result of the last poll or null if it did't find new ssid + result: ?Result = null, + /// Stable buffer for the ssid in Result + ssid_buf: [32]u8 = @splat(0), + + /// De-duplication of returned results. + /// Remember x last access point mac addresses. + seen_buffer: [16][6]u8 = undefined, + seen_idx: usize = 0, const Result = struct { ssid: []const u8 = &.{}, @@ -590,35 +599,67 @@ pub const ScanPoller = struct { } }; - pub fn poll(sp: *ScanPoller) !?Result { - if (sp.done) return null; + /// Returns true if scan is not finished. + /// If poll finds new ssid result is not null. + /// + /// Intended usage: + /// while (try scan.poll()) { + /// if (scan.result) |res| { ... + pub fn poll(sp: *ScanPoller) !bool { + if (sp.more) { + sp.result = null; + try sp.poll_tick(); + } + return sp.more; + } + + fn poll_tick(sp: *ScanPoller) !void { var bytes: [1280]u8 align(4) = undefined; - if (try sp.wifi.read(&bytes)) |rsp| { - switch (rsp.sdp.channel()) { - .event => { - const evt = rsp.event().msg; - switch (evt.event_type) { - .escan_result => { - if (evt.status == .success) { - sp.done = true; - return null; - } - const security = try rsp.event_scan_result(&sp.res); - if (sp.res.info.ssid_len == 0) return .{}; - return .{ - .ap_mac = sp.res.info.bssid, - .ssid = sp.res.info.ssid[0..sp.res.info.ssid_len], - .security = security, - .channel = sp.res.info.channel, - }; - }, - else => sp.wifi.log_response(rsp), - } - }, - else => sp.wifi.log_response(rsp), + const rsp = try sp.wifi.read(&bytes) orelse return; + switch (rsp.sdp.channel()) { + .event => { + const evt = rsp.event().msg; + switch (evt.event_type) { + .escan_result => { + if (evt.status == .success) { + sp.more = false; + return; + } + try sp.handle_scan_result(rsp); + }, + else => sp.wifi.log_response(rsp), + } + }, + else => sp.wifi.log_response(rsp), + } + } + + fn handle_scan_result(sp: *ScanPoller, rsp: ioctl.Response) !void { + const res, const security = try rsp.event_scan_result(); + if (res.info.ssid_len == 0) return; // skip zero length ssid + const info = &res.info; + const ap_mac = info.bssid; + + // Check if access point mac is already seen + for (0..@min(sp.seen_idx, sp.seen_buffer.len)) |i| { + if (std.mem.eql(u8, &sp.seen_buffer[i], &ap_mac)) { + return; } } - return .{}; + // Store in seen list + sp.seen_buffer[sp.seen_idx % sp.seen_buffer.len] = ap_mac; + sp.seen_idx += 1; + + // Store ssid in stable buffer + sp.ssid_buf = info.ssid; + + // Prepare result + sp.result = .{ + .ap_mac = ap_mac, + .ssid = sp.ssid_buf[0..info.ssid_len], + .security = security, + .channel = info.channel, + }; } }; From b7464702193b2ee9608e94034fe36e77275ff3a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Fri, 16 Jan 2026 18:12:25 +0100 Subject: [PATCH 05/19] cyw43: fix integer overflow 2 + len when len is u8 can overflow --- drivers/wireless/cyw43439/ioctl.zig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/wireless/cyw43439/ioctl.zig b/drivers/wireless/cyw43439/ioctl.zig index b60a407a4..ac057a64d 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -223,18 +223,19 @@ pub const Response = struct { while (ie_buf.len >= 2) { const typ = ie_buf[0]; const len = ie_buf[1]; + ie_buf = ie_buf[2..]; if (typ == 48) { sec.wpa2 = true; } else { const wpa_oui_type1 = "\x00\x50\xF2\x01"; - if (typ == 221 and ie_buf.len >= 2 + wpa_oui_type1.len) { - if (std.mem.eql(u8, ie_buf[2..][0..4], wpa_oui_type1)) { + if (typ == 221 and ie_buf.len >= wpa_oui_type1.len) { + if (mem.eql(u8, ie_buf[0..wpa_oui_type1.len], wpa_oui_type1)) { sec.wpa = true; } } } - if (ie_buf.len < 2 + len) break; - ie_buf = ie_buf[2 + len ..]; + if (ie_buf.len <= len) break; + ie_buf = ie_buf[len..]; } return .{ res, sec }; From a53350eb6f77b9b0c53ca02a3e702e85b0d79626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Fri, 16 Jan 2026 19:32:45 +0100 Subject: [PATCH 06/19] move join/scan common part to init --- drivers/wireless/cyw43439/wifi.zig | 381 +++++++++++++++-------------- 1 file changed, 199 insertions(+), 182 deletions(-) diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 6d97a4aab..2be81a4db 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -22,8 +22,7 @@ const ioctl_request_bytes_len = 1024; pub fn init(self: *Self) !void { const bus = self.bus; - // Init ALP (Active Low Power) clock - { + { // Init ALP (Active Low Power) clock _ = bus.write_int(u8, .backplane, Bus.backplane.chip_clock_csr, Bus.backplane.alp_avail_req); _ = bus.write_int(u8, .backplane, Bus.backplane.function2_watermark, 0x10); const watermark = bus.read_int(u8, .backplane, Bus.backplane.function2_watermark); @@ -39,8 +38,8 @@ pub fn init(self: *Self) !void { // const chip_id = bus.read_int(u16, .backplane, chip.pmu_base_address); // log.debug("chip ID: 0x{X}", .{chip_id}); } - // Upload firmware - { + + { // Upload firmware self.core_disable(.wlan); self.core_reset(.socram); @@ -51,8 +50,8 @@ pub fn init(self: *Self) !void { const firmware = @embedFile("../cyw43/firmware/43439A0_7_95_61.bin"); bus.backplane_write(chip.atcm_ram_base_address, firmware); } - // Load nvram - { + + { // Load nvram const nvram_len = ((NVRAM.len + 3) >> 2) * 4; // Round up to 4 bytes. const addr_magic = chip.atcm_ram_base_address + chip.chip_ram_size - 4; const addr = addr_magic - nvram_len; @@ -62,43 +61,46 @@ pub fn init(self: *Self) !void { const nvram_len_magic = (~nvram_len_words << 16) | nvram_len_words; bus.write_int(u32, .backplane, addr_magic, nvram_len_magic); } - // starting up core... + + // Starting up core... self.core_reset(.wlan); try self.core_is_up(.wlan); - // wait until HT clock is available; takes about 29ms + // Wait until HT clock is available; takes about 29ms while (bus.read_int(u8, .backplane, Bus.backplane.chip_clock_csr) & 0x80 == 0) {} - // "Set up the interrupt mask and enable interrupts" - const sdio_int_host_mask: u32 = 0x24; - const i_hmb_sw_mask: u32 = 0x000000f0; - bus.write_int( - u32, - .backplane, - chip.sdiod_core_base_address + sdio_int_host_mask, - i_hmb_sw_mask, - ); - bus.write_int(u16, .bus, Bus.reg.interrupt_enable, @bitCast(Bus.Irq{ .f2_packet_available = true })); + { // Set up the interrupt mask and enable interrupts + const sdio_int_host_mask: u32 = 0x24; + const i_hmb_sw_mask: u32 = 0x000000f0; + bus.write_int( + u32, + .backplane, + chip.sdiod_core_base_address + sdio_int_host_mask, + i_hmb_sw_mask, + ); + bus.write_int(u16, .bus, Bus.reg.interrupt_enable, @bitCast(Bus.Irq{ .f2_packet_available = true })); + } // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." bus.write_int(u8, .backplane, Bus.backplane.function2_watermark, 0x20); - // waiting for F2 to be ready... + // Waiting for F2 to be ready... while (true) { - const status: Status = @bitCast(self.bus.read_int(u32, .bus, Bus.reg.status)); + const status: Status = @bitCast(bus.read_int(u32, .bus, Bus.reg.status)); if (status.f2_rx_ready) break; } - // clear pad pulls - bus.write_int(u8, .backplane, Bus.backplane.pull_up, 0); - _ = bus.read_int(u8, .backplane, Bus.backplane.pull_up); + { // Clear pad pulls + bus.write_int(u8, .backplane, Bus.backplane.pull_up, 0); + _ = bus.read_int(u8, .backplane, Bus.backplane.pull_up); + } - // start HT clock - bus.write_int(u8, .backplane, Bus.backplane.chip_clock_csr, 0x10); - while (bus.read_int(u8, .backplane, Bus.backplane.chip_clock_csr) & 0x80 == 0) {} + { // start HT clock + bus.write_int(u8, .backplane, Bus.backplane.chip_clock_csr, 0x10); + while (bus.read_int(u8, .backplane, Bus.backplane.chip_clock_csr) & 0x80 == 0) {} + } - // Load Country Locale Matrix (CLM) - { + { // Load Country Locale Matrix (CLM) const data = @embedFile("../cyw43/firmware/43439A0_clm.bin"); const ClmLoadControl = extern struct { @@ -122,9 +124,96 @@ pub fn init(self: *Self) !void { nbytes += n; } } + + { // Clear data unavail error + const val = bus.read_int(u16, .bus, Bus.reg.interrupt); + if (val & 1 > 0) + bus.write_int(u16, .bus, Bus.reg.interrupt, val); + } + + { // Set sleep KSO (keep SDIO on), cyw43_kso_set + bus.write_int(u8, .backplane, Bus.backplane.sleep_csr, 1); + bus.write_int(u8, .backplane, Bus.backplane.sleep_csr, 1); + _ = bus.read_int(u8, .backplane, Bus.backplane.sleep_csr); + } + + { // cyw43_ll_wifi_on + + { // Set country + // TODO: move to arguments + const opt: JoinOptions = .{}; + + const code = opt.country.code; + var val = extern struct { + abbrev: [4]u8, + revision: i32, + code: [4]u8, + }{ + .abbrev = .{ code[0], code[1], 0, 0 }, + .revision = opt.country.revision, + .code = .{ code[0], code[1], 0, 0 }, + }; + self.set_var("country", mem.asBytes(&val)) catch |err| switch (err) { + error.Cyw43InvalidCommandStatus => { + log.err( + "invalid country code: {s}, revision: {}", + .{ opt.country.code, opt.country.revision }, + ); + return error.Cyw43InvalidCountryCode; + }, + else => return err, + }; + } + + // Set antenna to chip antenna + try self.set_cmd(.set_antdiv, &.{0}); + + { // Set some WiFi config + try self.set_var("bus:txglom", &.{0x00}); + try self.set_var("apsta", &.{0x01}); + try self.set_var("ampdu_ba_wsize", &.{0x08}); + try self.set_var("ampdu_mpdu", &.{0x04}); + try self.set_var("ampdu_rx_factor", &.{0x00}); + self.sleep_ms(150); + } + + try self.enable_events(&.{ + .join, + .assoc, + .reassoc, + .set_ssid, + .link, + .auth, + .psk_sup, + .disassoc_ind, + .disassoc, + .deauth, + .deauth_ind, + .escan_result, + }); + + // Set the interface as "up" + try self.set_cmd(.up, &.{}); + } + + { // Set power mode parameters cyw43_ll_wifi_pm + try self.set_var("pm2_sleep_ret", &.{0xc8}); + try self.set_var("bcn_li_bcn", &.{1}); + try self.set_var("bcn_li_dtim", &.{1}); + try self.set_var("assoc_listen", &.{0x0a}); + try self.set_cmd(.set_gmode, &.{1}); // auto + try self.set_cmd(.set_band, &.{0}); // any + } + self.log_init(); } +fn enable_events(self: *Self, events: []const ioctl.EventType) !void { + const mask = ioctl.EventType.mask(events); + try self.set_var("bsscfg:event_msgs", &mask); + self.sleep_ms(50); +} + fn core_disable(self: *Self, core: Core) void { const base = core.base_addr(); // Dummy read? @@ -270,7 +359,7 @@ pub const JoinOptions = struct { wpa3_sae, }; - // ref: https://github.com/embassy-rs/embassy/blob/96a026c73bad2ebb8dfc78e88c9690611bf2cb97/cyw43/src/structs.rs#L371 + // https://github.com/georgerobotics/cyw43-driver/blob/13004039ffe127519f33824bf7d240e1f23fbdcd/src/cyw43_country.h#L59 pub const Country = struct { code: [2]u8 = "XX".*, // Worldwide revision: i32 = -1, @@ -284,74 +373,6 @@ pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !v } pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !JoinPoller { - const bus = self.bus; - - // Clear pullups - { - bus.write_int(u8, .backplane, Bus.backplane.pull_up, 0xf); - bus.write_int(u8, .backplane, Bus.backplane.pull_up, 0); - _ = self.bus.read_int(u8, .backplane, Bus.backplane.pull_up); - } - // Clear data unavail error - { - const val = self.bus.read_int(u16, .bus, Bus.reg.interrupt); - if (val & 1 > 0) - self.bus.write_int(u16, .bus, Bus.reg.interrupt, val); - } - // Set sleep KSO (should poll to check for success) - { - bus.write_int(u8, .backplane, Bus.backplane.sleep_csr, 1); - bus.write_int(u8, .backplane, Bus.backplane.sleep_csr, 1); - _ = self.bus.read_int(u8, .backplane, Bus.backplane.pull_up); - //log.debug("REG_BACKPLANE_SLEEP_CSR value: {}", .{val}); - } - // Set country - { - const code = opt.country.code; - var val = extern struct { - abbrev: [4]u8, - revision: i32, - code: [4]u8, - }{ - .abbrev = .{ code[0], code[1], 0, 0 }, - .revision = opt.country.revision, - .code = .{ code[0], code[1], 0, 0 }, - }; - self.set_var("country", mem.asBytes(&val)) catch |err| switch (err) { - error.Cyw43InvalidCommandStatus => { - log.err( - "invalid country code: {s}, revision: {}", - .{ opt.country.code, opt.country.revision }, - ); - return error.Cyw43InvalidCountryCode; - }, - else => return err, - }; - } - try self.set_cmd(.set_antdiv, &.{0}); - // Data aggregation - { - try self.set_var("bus:txglom", &.{0x00}); - try self.set_var("apsta", &.{0x01}); - try self.set_var("ampdu_ba_wsize", &.{0x08}); - try self.set_var("ampdu_mpdu", &.{0x04}); - try self.set_var("ampdu_rx_factor", &.{0x00}); - self.sleep_ms(150); - } - try self.enable_events(&.{ - .join, - .assoc, - .reassoc, - .set_ssid, - .link, - .auth, - .psk_sup, - .disassoc_ind, - .disassoc, - .deauth, - .deauth_ind, - }); - var buf: [64]u8 = @splat(0); // space for 10 addresses // Enable multicast { @@ -360,63 +381,54 @@ pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOpti try self.set_var("mcast_list", &buf); self.sleep_ms(50); } - // join_restart function - { - try self.set_cmd(.up, &.{}); - try self.set_cmd(.set_gmode, &.{1}); - try self.set_cmd(.set_band, &.{0}); - try self.set_var("pm2_sleep_ret", &.{0xc8}); - try self.set_var("bcn_li_bcn", &.{1}); - try self.set_var("bcn_li_dtim", &.{1}); - try self.set_var("assoc_listen", &.{0x0a}); - try self.set_cmd(.set_wsec, &.{switch (opt.security) { - .wpa_psk => 2, - .wpa2_psk, .wpa3_sae => 6, - .open => 0, - }}); - switch (opt.security) { - .open => { - try self.set_var("bsscfg:sup_wpa", &.{ 0, 0, 0, 0, 0, 0, 0, 0 }); - }, - else => { - try self.set_var("bsscfg:sup_wpa", &.{ 0, 0, 0, 0, 1, 0, 0, 0 }); - try self.set_var("bsscfg:sup_wpa2_eapver", &.{ 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff }); - try self.set_var("bsscfg:sup_wpa_tmo", &.{ 0, 0, 0, 0, 0xc4, 0x09, 0, 0 }); - }, - } - self.sleep_ms(2); - - switch (opt.security) { - .open => {}, - .wpa3_sae => { - try self.set_var("sae_password", ioctl.encode_sae_pwd(&buf, pwd)); - }, - else => { - try self.set_cmd(.set_wsec_pmk, ioctl.encode_pwd(&buf, pwd)); - }, - } - try self.set_cmd(.set_infra, &.{1}); - - try self.set_cmd(.set_auth, &.{switch (opt.security) { - .wpa3_sae => 3, - else => 0, - }}); - try self.set_var("mfp", &.{switch (opt.security) { - .wpa_psk => 0, - .wpa2_psk => 1, - .wpa3_sae => 2, - .open => 0, - }}); - try self.set_cmd(.set_wpa_auth, switch (opt.security) { - .wpa_psk => &.{ 0x04, 0, 0, 0 }, - .wpa2_psk => &.{ 0x80, 0, 0, 0 }, - .wpa3_sae => &.{ 0, 0, 0x04, 0 }, - .open => &.{ 0, 0, 0, 0 }, - }); + try self.set_cmd(.set_wsec, &.{switch (opt.security) { + .wpa_psk => 2, + .wpa2_psk, .wpa3_sae => 6, + .open => 0, + }}); + switch (opt.security) { + .open => { + try self.set_var("bsscfg:sup_wpa", &.{ 0, 0, 0, 0, 0, 0, 0, 0 }); + }, + else => { + try self.set_var("bsscfg:sup_wpa", &.{ 0, 0, 0, 0, 1, 0, 0, 0 }); + try self.set_var("bsscfg:sup_wpa2_eapver", &.{ 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff }); + try self.set_var("bsscfg:sup_wpa_tmo", &.{ 0, 0, 0, 0, 0xc4, 0x09, 0, 0 }); + }, + } + self.sleep_ms(2); - try self.set_cmd(.set_ssid, ioctl.encode_ssid(&buf, ssid)); + switch (opt.security) { + .open => {}, + .wpa3_sae => { + try self.set_var("sae_password", ioctl.encode_sae_pwd(&buf, pwd)); + }, + else => { + try self.set_cmd(.set_wsec_pmk, ioctl.encode_pwd(&buf, pwd)); + }, } + try self.set_cmd(.set_infra, &.{1}); + + try self.set_cmd(.set_auth, &.{switch (opt.security) { + .wpa3_sae => 3, + else => 0, + }}); + try self.set_var("mfp", &.{switch (opt.security) { + .wpa_psk => 0, + .wpa2_psk => 1, + .wpa3_sae => 2, + .open => 0, + }}); + try self.set_cmd(.set_wpa_auth, switch (opt.security) { + .wpa_psk => &.{ 0x04, 0, 0, 0 }, + .wpa2_psk => &.{ 0x80, 0, 0, 0 }, + .wpa3_sae => &.{ 0, 0, 0x04, 0 }, + .open => &.{ 0, 0, 0, 0 }, + }); + + try self.set_cmd(.set_ssid, ioctl.encode_ssid(&buf, ssid)); + if (opt.security == .open) { self.events.psk_sup = .success; } @@ -431,6 +443,8 @@ pub fn connected(self: Self) bool { pub const JoinPoller = struct { wifi: *Self, + // TODO: describe + // // Join to open network events: // [1.824251] type: .auth, status: .success // [1.859136] type: .assoc, status: .success @@ -454,6 +468,23 @@ pub const JoinPoller = struct { // [39.910309] type: .reassoc, status: .success // [39.917245] type: .link, status: .success // [40.202375] type: .join, status: .success + // + // wrong pwd events + // [3.759191] debug (cyw43_wifi): poll event type: .auth, status: .success + // [3.785954] debug (cyw43_wifi): poll event type: .assoc, status: .success + // [3.802674] debug (cyw43_wifi): poll event type: .link, status: .success + // [3.829335] debug (cyw43_wifi): poll event type: .join, status: .success + // [3.845951] debug (cyw43_wifi): poll event type: .set_ssid, status: .success + // [6.276576] debug (cyw43_wifi): poll event type: .psk_sup, status: .partial + // [6.283378] error: panic: main() returned error.Cyw43WpaHandshake + // + // wrong ssid + // [1.555841] debug (cyw43_wifi): poll event type: .set_ssid, status: .no_networks + // [1.563185] error: panic: main() returned error.Cyw43SetSsid + // + // wrong security open/wpa_psk/wpa3_sae instad of wpa2_psk + // [1.525449] debug (cyw43_wifi): poll event type: .set_ssid, status: .fail + // [1.532186] error: panic: main() returned error.Cyw43SetSsid pub fn poll(jp: *JoinPoller) !bool { const events = &jp.wifi.events; @@ -484,10 +515,11 @@ pub const JoinPoller = struct { fn handle_event(self: *Self, evt: *const ioctl.EventMessage) void { const events = &self.events; const state = Events.State.from; - // log.debug( - // "poll event type: {}, status: {} ", - // .{ evt.event_type, evt.status }, - // ); + // TODO remove + log.debug( + "poll event type: {}, status: {} ", + .{ evt.event_type, evt.status }, + ); switch (evt.event_type) { .link => { events.link = state(evt.status == .success and evt.flags & 1 > 0); @@ -529,44 +561,29 @@ const ScanParams = extern struct { action: u16 = 1, sync_id: u16 = 0x1, + // ssid to scan for, 0 - all ssid_len: u32 = 0, ssid: [32]u8 = @splat(0), - bssid: [6]u8 = .{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - bss_type: u8 = 2, - scan_type: u8 = 1, // passive + bssid: [6]u8 = @splat(0xff), + bss_type: u8 = 2, // 2 - bss type any + scan_type: u8 = 0, // 0=active, 1=passive nprobes: u32 = 0xff_ff_ff_ff, active_time: u32 = 0xff_ff_ff_ff, passive_time: u32 = 0xff_ff_ff_ff, home_time: u32 = 0xff_ff_ff_ff, - nchans: u16 = 0, - nssids: u16 = 0, - - chans: [14][2]u8 = @splat(@splat(0)), - ssids: [1][32]u8 = @splat(@splat(0)), + channel_num: u32 = 0, + channel_list: [1]u16 = @splat(0), }; -fn enable_events(self: *Self, events: []const ioctl.EventType) !void { - const mask = ioctl.EventType.mask(events); - try self.set_var("bsscfg:event_msgs", &mask); - self.sleep_ms(50); -} - /// Init scan and return poller pub fn scan_poller(self: *Self) !ScanPoller { - try self.enable_events(&.{.escan_result}); - - try self.set_cmd(.set_scan_channel_time, &.{40}); - try self.set_var("pm2_sleep_ret", &.{0xc8}); - try self.set_var("bcn_li_bcn", &.{1}); - try self.set_var("bcn_li_dtim", &.{1}); - try self.set_var("assoc_listen", &.{0x0a}); - try self.set_cmd(.set_band, &.{0}); - try self.set_cmd(.up, &.{0}); - var scan_params: ScanParams = .{}; - try self.set_var("escan", mem.asBytes(&scan_params)); + // Params can be used to choose active/passive scan type or to set specific + // ssid to scan for. + var params: ScanParams = .{}; + try self.set_var("escan", mem.asBytes(¶ms)); return .{ .wifi = self, @@ -585,7 +602,7 @@ pub const ScanPoller = struct { /// De-duplication of returned results. /// Remember x last access point mac addresses. - seen_buffer: [16][6]u8 = undefined, + seen_buffer: [8][6]u8 = undefined, seen_idx: usize = 0, const Result = struct { @@ -648,7 +665,7 @@ pub const ScanPoller = struct { } // Store in seen list sp.seen_buffer[sp.seen_idx % sp.seen_buffer.len] = ap_mac; - sp.seen_idx += 1; + sp.seen_idx +%= 1; // Store ssid in stable buffer sp.ssid_buf = info.ssid; From 8ca537bf27c2ef6a4af9009590e9be9320f3cbf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Sat, 17 Jan 2026 00:57:57 +0100 Subject: [PATCH 07/19] cyw43: check that event is valid --- drivers/wireless/cyw43439/ioctl.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/wireless/cyw43439/ioctl.zig b/drivers/wireless/cyw43439/ioctl.zig index ac057a64d..00b400864 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -185,7 +185,7 @@ pub const Response = struct { return .{ head, self.sdp.len - head }; } - pub fn event(self: Self) EventPacket { + pub fn event(self: Self) ?EventPacket { assert(self.sdp.channel() == .event); const buf = self.data(); if (buf.len < @sizeOf(EventPacket)) { @@ -196,6 +196,10 @@ pub const Response = struct { var evt: EventPacket = undefined; @memcpy(std.mem.asBytes(&evt), buf[0..@sizeOf(EventPacket)]); std.mem.byteSwapAllFields(EventPacket, &evt); + + if (evt.eth.ether_type != 0x886c) return null; + if (!mem.eql(u8, &evt.hdr.oui, &.{ 0x00, 0x10, 0x18 })) return null; + return evt; } From 54022f8744c7ec64033496c4e6dd78b65c3e75b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Sat, 17 Jan 2026 00:58:30 +0100 Subject: [PATCH 08/19] cyw43: use single poll function Handle scan/join in the same function. Add join_state and scan_result to the main struct. --- drivers/wireless/cyw43439.zig | 16 +- drivers/wireless/cyw43439/wifi.zig | 259 ++++++++++++++++------------- 2 files changed, 159 insertions(+), 116 deletions(-) diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index 6c8aacb8c..28e6c2f68 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -29,14 +29,22 @@ pub fn init( self.mac = try self.read_mac(); } -pub fn connected(self: *Self) bool { - return self.wifi.connected(); +pub fn joined(self: *Self) bool { + return self.wifi.join_state == .joined; +} + +pub fn join_state(self: *Self) WiFi.JoinState { + return self.wifi.join_state; } pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { try self.wifi.join(ssid, pwd, opt); } +pub fn poll(self: *Self) !void { + try self.wifi.poll(); +} + pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !WiFi.JoinPoller { return try self.wifi.join_poller(ssid, pwd, opt); } @@ -45,6 +53,10 @@ pub fn scan_poller(self: *Self) !WiFi.ScanPoller { return try self.wifi.scan_poller(); } +pub fn scan_result(self: *Self) ?WiFi.ScanResult { + return self.wifi.scan_result; +} + fn show_clm_ver(self: *Self) !void { var data: [128]u8 = @splat(0); const n = try self.wifi.get_var("clmver", &data); diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 2be81a4db..057bcfc40 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -16,6 +16,8 @@ tx_sequence: u8 = 0, status: ?Status = null, log_state: LogState = .{}, events: Events = .{}, +scan_result: ?ScanResult = null, +join_state: JoinState = .initial, const ioctl_request_bytes_len = 1024; @@ -433,15 +435,16 @@ pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOpti self.events.psk_sup = .success; } - return .{ .wifi = self }; + self.join_state = .joining; + return .{ .parent = self }; } pub fn connected(self: Self) bool { - return self.events.connected(); + return self.join_state == .joined; } pub const JoinPoller = struct { - wifi: *Self, + parent: *Self, // TODO: describe // @@ -487,39 +490,42 @@ pub const JoinPoller = struct { // [1.532186] error: panic: main() returned error.Cyw43SetSsid pub fn poll(jp: *JoinPoller) !bool { - const events = &jp.wifi.events; - if (events.connected()) return false; - - var bytes: [512]u8 align(4) = undefined; - const rsp = try jp.wifi.read(&bytes) orelse return true; - - switch (rsp.sdp.channel()) { - .event => jp.wifi.handle_event(&rsp.event().msg), - else => jp.wifi.log_response(rsp), + const events = &jp.parent.events; + if (jp.parent.join_state == .joining) { + try jp.parent.poll(); + try events.err(); } - try events.err(); - return !events.connected(); + return jp.parent.join_state == .joining; } pub fn wait(jp: *JoinPoller, wait_ms: u32) !void { var delay: u32 = 0; while (delay < wait_ms) { if (!try jp.poll()) return; - jp.wifi.sleep_ms(ioctl.response_poll_interval); + jp.parent.sleep_ms(ioctl.response_poll_interval); delay += ioctl.response_poll_interval; } return error.Cyw43JoinTimeout; } }; -fn handle_event(self: *Self, evt: *const ioctl.EventMessage) void { +pub fn poll(self: *Self) !void { + var bytes: [ioctl_request_bytes_len]u8 align(4) = undefined; + const rsp = try self.read(&bytes) orelse return; + switch (rsp.sdp.channel()) { + .event => self.handle_event(rsp), + else => self.log_response(rsp), + } +} + +fn handle_event(self: *Self, rsp: ioctl.Response) void { + const evt = (rsp.event() orelse return).msg; const events = &self.events; const state = Events.State.from; - // TODO remove - log.debug( - "poll event type: {}, status: {} ", - .{ evt.event_type, evt.status }, - ); + // log.debug( + // "handle event type: {}, status: {} ", + // .{ evt.event_type, evt.status }, + // ); switch (evt.event_type) { .link => { events.link = state(evt.status == .success and evt.flags & 1 > 0); @@ -546,14 +552,48 @@ fn handle_event(self: *Self, evt: *const ioctl.EventMessage) void { .join => { events.join = state(evt.status == .success); }, - .none => {}, + .escan_result => { + self.scan_result = null; + events.scan = switch (evt.status) { + .success => .success, + .partial => .running, + else => .fail, + }; + if (evt.status == .partial) { + self.handle_scan_event(rsp); + } + }, + .none => return, else => { log.warn( "unhandled event type: {}, status: {} ", .{ evt.event_type, evt.status }, ); + return; }, } + const new_state = events.join_state(); + // if (new_state != self.join_state) { + // log.debug("state changed old: {}, new: {}", .{ self.join_state, new_state }); + // log.debug("events: {}", .{self.events}); + // } + self.join_state = new_state; +} + +fn handle_scan_event(self: *Self, rsp: ioctl.Response) void { + const res, const security = rsp.event_scan_result() catch |err| { + log.err("fail to parse event scan result {}", .{err}); + return; + }; + if (res.info.ssid_len == 0) return; // skip zero length ssid + const info = &res.info; + self.scan_result = .{}; + const sr = &self.scan_result.?; + sr.ssid_buf = info.ssid; + sr.ap_mac = info.bssid; + sr.ssid = sr.ssid_buf[0..info.ssid_len]; + sr.security = security; + sr.channel = info.channel; } const ScanParams = extern struct { @@ -584,99 +624,49 @@ pub fn scan_poller(self: *Self) !ScanPoller { // ssid to scan for. var params: ScanParams = .{}; try self.set_var("escan", mem.asBytes(¶ms)); + self.events.scan = .running; return .{ - .wifi = self, + .parent = self, }; } pub const ScanPoller = struct { - wifi: *Self, + parent: *Self, - /// Poller not done, client should poll for more results - more: bool = true, - /// Result of the last poll or null if it did't find new ssid - result: ?Result = null, - /// Stable buffer for the ssid in Result - ssid_buf: [32]u8 = @splat(0), - - /// De-duplication of returned results. - /// Remember x last access point mac addresses. - seen_buffer: [8][6]u8 = undefined, + /// Buffer for last x seen ssid's. Used for de-duplication of scan results. + seen_buf: [8][6]u8 = undefined, seen_idx: usize = 0, - const Result = struct { - ssid: []const u8 = &.{}, - ap_mac: [6]u8 = @splat(0), - security: ioctl.Security = .{}, - channel: u16 = 0, - - pub fn empty(res: Result) bool { - return res.ssid.len == 0; - } - }; - /// Returns true if scan is not finished. /// If poll finds new ssid result is not null. /// /// Intended usage: /// while (try scan.poll()) { - /// if (scan.result) |res| { ... + /// if (scan.result()) |res| { ... pub fn poll(sp: *ScanPoller) !bool { - if (sp.more) { - sp.result = null; - try sp.poll_tick(); - } - return sp.more; - } - - fn poll_tick(sp: *ScanPoller) !void { - var bytes: [1280]u8 align(4) = undefined; - const rsp = try sp.wifi.read(&bytes) orelse return; - switch (rsp.sdp.channel()) { - .event => { - const evt = rsp.event().msg; - switch (evt.event_type) { - .escan_result => { - if (evt.status == .success) { - sp.more = false; - return; - } - try sp.handle_scan_result(rsp); - }, - else => sp.wifi.log_response(rsp), - } - }, - else => sp.wifi.log_response(rsp), + if (sp.status() == .running) { + try sp.parent.poll(); } + return sp.status() == .running; } - fn handle_scan_result(sp: *ScanPoller, rsp: ioctl.Response) !void { - const res, const security = try rsp.event_scan_result(); - if (res.info.ssid_len == 0) return; // skip zero length ssid - const info = &res.info; - const ap_mac = info.bssid; - + pub fn result(sp: *ScanPoller) ?ScanResult { + const res = sp.parent.scan_result orelse return null; // Check if access point mac is already seen - for (0..@min(sp.seen_idx, sp.seen_buffer.len)) |i| { - if (std.mem.eql(u8, &sp.seen_buffer[i], &ap_mac)) { - return; + for (0..@min(sp.seen_idx, sp.seen_buf.len)) |i| { + if (std.mem.eql(u8, &sp.seen_buf[i], &res.ap_mac)) { + return null; } } // Store in seen list - sp.seen_buffer[sp.seen_idx % sp.seen_buffer.len] = ap_mac; + sp.seen_buf[sp.seen_idx % sp.seen_buf.len] = res.ap_mac; sp.seen_idx +%= 1; + return res; + } - // Store ssid in stable buffer - sp.ssid_buf = info.ssid; - - // Prepare result - sp.result = .{ - .ap_mac = ap_mac, - .ssid = sp.ssid_buf[0..info.ssid_len], - .security = security, - .channel = info.channel, - }; + pub fn status(sp: ScanPoller) Events.State { + return sp.parent.events.scan; } }; @@ -685,7 +675,7 @@ fn log_response(self: *Self, rsp: ioctl.Response) void { _ = self; switch (rsp.sdp.channel()) { .event => { - const evt = rsp.event().msg; + const evt = (rsp.event() orelse return).msg; if (evt.event_type == .none and evt.status == .success) return; log.debug( @@ -731,7 +721,7 @@ pub fn recv_zc(self: *Self, buffer: []u8) !?struct { usize, usize } { const rsp = try self.read(buffer) orelse return null; switch (rsp.sdp.channel()) { .data => return rsp.data_pos(), - .event => self.handle_event(&rsp.event().msg), + .event => self.handle_event(rsp), else => self.log_response(rsp), } } @@ -838,43 +828,84 @@ const Status = packed struct { _reserved3: u12, }; -const Events = struct { +pub const ScanResult = struct { + ssid: []const u8 = &.{}, + ap_mac: [6]u8 = @splat(0), + security: ioctl.Security = .{}, + channel: u16 = 0, + ssid_buf: [32]u8 = @splat(0), +}; + +const Events = packed struct { pub const State = enum(u2) { - none, - success, - fail, + none = 0b00, + success = 0b01, + fail = 0b10, + running = 0b11, - fn from(ok: bool) State { + fn from(ok: bool) Events.State { return if (ok) .success else .fail; } }; - link: State = .none, - psk_sup: State = .none, - set_ssid: State = .none, - assoc: State = .none, - auth: State = .none, - join: State = .none, + // essential join events + auth: Events.State = .none, + link: Events.State = .none, + psk_sup: Events.State = .none, + set_ssid: Events.State = .none, + // other join events + assoc: Events.State = .none, + join: Events.State = .none, + // scan event + scan: Events.State = .none, pub fn err(e: Events) !void { + if (e.auth == .fail) return error.Cyw43Authentication; if (e.link == .fail) return error.Cyw43LinkDown; if (e.psk_sup == .fail) return error.Cyw43WpaHandshake; if (e.set_ssid == .fail) return error.Cyw43SetSsid; - if (e.assoc == .fail) return error.Cyw43AssocRequest; - if (e.auth == .fail) return error.Cyw43AuthRequest; - if (e.join == .fail) return error.Cyw43Join; } - pub fn connected(e: Events) bool { - return e.link == .success and - e.psk_sup == .success and - e.set_ssid == .success and - e.assoc == .success and - e.auth == .success and - e.join == .success; + pub fn join_state(e: Events) JoinState { + var i: u14 = @bitCast(e); + i &= 0b00_00_00_11_11_11_11; // filter essential join events + const fail_mask = 0b10_10_10_10; + const succ_mask = 0b01_01_01_01; + if (i == 0) return .initial; + if (i & fail_mask > 0) return .disjoined; // any failed + if (i & succ_mask == succ_mask) return .joined; // all success + return .joining; } }; +const testing = std.testing; + +test "events to join_state" { + var e: Events = .{}; + try testing.expectEqual(.initial, e.join_state()); + e.auth = .success; + try testing.expectEqual(.joining, e.join_state()); + e.link = .success; + e.psk_sup = .success; + try testing.expectEqual(.joining, e.join_state()); + try testing.expectEqual(0b00_01_01_01, @as(u14, @bitCast(e))); + e.set_ssid = .success; + try testing.expectEqual(.joined, e.join_state()); + e.auth = .fail; + try testing.expectEqual(.disjoined, e.join_state()); + e.auth = .none; + try testing.expectEqual(.joining, e.join_state()); + e.auth = .success; + try testing.expectEqual(.joined, e.join_state()); +} + +pub const JoinState = enum { + initial, + joining, + joined, + disjoined, +}; + // CYW43439 chip values const chip = struct { const wrapper_register_offset: u32 = 0x100000; From 34cf87f167716d45f6aa76460b99f4c20470f3ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Sat, 17 Jan 2026 12:35:23 +0100 Subject: [PATCH 09/19] cyw43: refactor join/scan status handling Fix todo: move country option to init. Rename methods, join and scan are no blocking returning poller. Blocking join is now join_wait. --- drivers/wireless/cyw43439.zig | 53 ++-- drivers/wireless/cyw43439/wifi.zig | 246 ++++++++++-------- examples/raspberrypi/rp2xxx/src/net/pong.zig | 2 +- .../raspberrypi/rp2xxx/src/net/tcp_client.zig | 2 +- .../raspberrypi/rp2xxx/src/net/tcp_server.zig | 2 +- examples/raspberrypi/rp2xxx/src/net/udp.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/drivers.zig | 7 +- 7 files changed, 160 insertions(+), 154 deletions(-) diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index 28e6c2f68..d3fe3be36 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -6,6 +6,7 @@ const Link = @import("link"); const Bus = @import("cyw43439/bus.zig"); const WiFi = @import("cyw43439/wifi.zig"); pub const JoinOptions = WiFi.JoinOptions; +pub const InitOptions = WiFi.InitOptions; const log = std.log.scoped(.cyw43); @@ -19,65 +20,43 @@ pub fn init( self: *Self, spi: Bus.Spi, sleep_ms: *const fn (delay: u32) void, + opt: InitOptions, ) !void { self.bus = .{ .spi = spi, .sleep_ms = sleep_ms }; try self.bus.init(); self.wifi = .{ .bus = &self.bus }; - try self.wifi.init(); + try self.wifi.init(opt); - self.mac = try self.read_mac(); + self.mac = try self.wifi.read_mac(); } -pub fn joined(self: *Self) bool { - return self.wifi.join_state == .joined; +pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !WiFi.JoinPoller { + return try self.wifi.join(ssid, pwd, opt); } -pub fn join_state(self: *Self) WiFi.JoinState { - return self.wifi.join_state; +/// Blocking wifi network join +pub fn join_wait(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { + var poller = try self.join(ssid, pwd, opt); + try poller.wait(opt.wait_ms); } -pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { - try self.wifi.join(ssid, pwd, opt); -} - -pub fn poll(self: *Self) !void { - try self.wifi.poll(); +pub fn join_state(self: *Self) WiFi.JoinState { + return self.wifi.join_state; } -pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !WiFi.JoinPoller { - return try self.wifi.join_poller(ssid, pwd, opt); +pub fn is_joined(self: *Self) bool { + return self.wifi.join_state == .joined; } -pub fn scan_poller(self: *Self) !WiFi.ScanPoller { - return try self.wifi.scan_poller(); +pub fn scan(self: *Self) !WiFi.ScanPoller { + return try self.wifi.scan(); } pub fn scan_result(self: *Self) ?WiFi.ScanResult { return self.wifi.scan_result; } -fn show_clm_ver(self: *Self) !void { - var data: [128]u8 = @splat(0); - const n = try self.wifi.get_var("clmver", &data); - var iter = mem.splitScalar(u8, data[0..n], 0x0a); - log.debug("clmver:", .{}); - while (iter.next()) |line| { - if (line.len == 0 or line[0] == 0x00) continue; - log.debug(" {s}", .{line}); - } -} - -fn read_mac(self: *Self) ![6]u8 { - var mac: [6]u8 = @splat(0); - const n = try self.wifi.get_var("cur_etheraddr", &mac); - if (n != mac.len) { - log.err("read_mac unexpected read bytes: {}", .{n}); - return error.ReadMacFailed; - } - return mac; -} - pub fn recv_zc(ptr: *anyopaque, bytes: []u8) anyerror!?struct { usize, usize } { const self: *Self = @ptrCast(@alignCast(ptr)); return self.wifi.recv_zc(bytes); diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 057bcfc40..1a4769782 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -13,15 +13,28 @@ bus: *Bus, request_id: u16 = 0, credit: u8 = 0, tx_sequence: u8 = 0, -status: ?Status = null, +bus_status: ?BusStatus = null, log_state: LogState = .{}, -events: Events = .{}, + +event_log: EventLog = .{}, +join_state: JoinState = .none, +scan_state: ScanState = .none, scan_result: ?ScanResult = null, -join_state: JoinState = .initial, const ioctl_request_bytes_len = 1024; -pub fn init(self: *Self) !void { +pub const InitOptions = struct { + country: Country = .{}, + + // List of available countries: + // https://github.com/georgerobotics/cyw43-driver/blob/13004039ffe127519f33824bf7d240e1f23fbdcd/src/cyw43_country.h#L59 + pub const Country = struct { + code: [2]u8 = "XX".*, // Worldwide + revision: i32 = -1, + }; +}; + +pub fn init(self: *Self, opt: InitOptions) !void { const bus = self.bus; { // Init ALP (Active Low Power) clock @@ -88,7 +101,7 @@ pub fn init(self: *Self) !void { // Waiting for F2 to be ready... while (true) { - const status: Status = @bitCast(bus.read_int(u32, .bus, Bus.reg.status)); + const status: BusStatus = @bitCast(bus.read_int(u32, .bus, Bus.reg.status)); if (status.f2_rx_ready) break; } @@ -142,9 +155,6 @@ pub fn init(self: *Self) !void { { // cyw43_ll_wifi_on { // Set country - // TODO: move to arguments - const opt: JoinOptions = .{}; - const code = opt.country.code; var val = extern struct { abbrev: [4]u8, @@ -189,7 +199,6 @@ pub fn init(self: *Self) !void { .psk_sup, .disassoc_ind, .disassoc, - .deauth, .deauth_ind, .escan_result, }); @@ -352,7 +361,7 @@ fn response_poll(self: *Self, cmd: ioctl.Cmd, data: ?[]u8) !usize { pub const JoinOptions = struct { security: Security = .wpa2_psk, - country: Country = .{}, + wait_ms: u32 = 30 * 1000, pub const Security = enum { open, @@ -360,21 +369,9 @@ pub const JoinOptions = struct { wpa2_psk, wpa3_sae, }; - - // https://github.com/georgerobotics/cyw43-driver/blob/13004039ffe127519f33824bf7d240e1f23fbdcd/src/cyw43_country.h#L59 - pub const Country = struct { - code: [2]u8 = "XX".*, // Worldwide - revision: i32 = -1, - }; }; -/// Blocking wifi network join -pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { - var jp = try self.join_poller(ssid, pwd, opt); - try jp.wait(30 * 1000); -} - -pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !JoinPoller { +pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !JoinPoller { var buf: [64]u8 = @splat(0); // space for 10 addresses // Enable multicast { @@ -432,7 +429,7 @@ pub fn join_poller(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOpti try self.set_cmd(.set_ssid, ioctl.encode_ssid(&buf, ssid)); if (opt.security == .open) { - self.events.psk_sup = .success; + self.event_log.psk_sup = .success; } self.join_state = .joining; @@ -446,7 +443,7 @@ pub fn connected(self: Self) bool { pub const JoinPoller = struct { parent: *Self, - // TODO: describe + // Typical events flow: // // Join to open network events: // [1.824251] type: .auth, status: .success @@ -454,6 +451,7 @@ pub const JoinPoller = struct { // [1.876234] type: .link, status: .success // [1.893244] type: .join, status: .success // [1.910249] type: .set_ssid, status: .success + // // Join to wpa2 network events: // [4.039611] type: .auth, status: .success // [4.073556] type: .assoc, status: .success @@ -461,39 +459,39 @@ pub const JoinPoller = struct { // [4.106842] type: .psk_sup, status: .unsolicited // [4.124026] type: .join, status: .success // [4.140596] type: .set_ssid, status: .success - + // // On disconnect: // [10.410040] type: .deauth_ind, status: .success // [11.456022] type: .auth, status: .fail // [20.875620] type: .link, status: .success - // On reconnect + // + // On reconnect: // [39.896166] type: .auth, status: .success // [39.910309] type: .reassoc, status: .success // [39.917245] type: .link, status: .success // [40.202375] type: .join, status: .success // - // wrong pwd events - // [3.759191] debug (cyw43_wifi): poll event type: .auth, status: .success - // [3.785954] debug (cyw43_wifi): poll event type: .assoc, status: .success - // [3.802674] debug (cyw43_wifi): poll event type: .link, status: .success - // [3.829335] debug (cyw43_wifi): poll event type: .join, status: .success - // [3.845951] debug (cyw43_wifi): poll event type: .set_ssid, status: .success - // [6.276576] debug (cyw43_wifi): poll event type: .psk_sup, status: .partial - // [6.283378] error: panic: main() returned error.Cyw43WpaHandshake + // Joining with wrong password: + // [3.759191] type: .auth, status: .success + // [3.785954] type: .assoc, status: .success + // [3.802674] type: .link, status: .success + // [3.829335] type: .join, status: .success + // [3.845951] type: .set_ssid, status: .success + // [6.276576] type: .psk_sup, status: .partial + // [6.283378] error: error.Cyw43WpaHandshake // - // wrong ssid - // [1.555841] debug (cyw43_wifi): poll event type: .set_ssid, status: .no_networks - // [1.563185] error: panic: main() returned error.Cyw43SetSsid + // Wrong network ssid + // [1.555841] type: .set_ssid, status: .no_networks + // [1.563185] error: error.Cyw43SetSsid + // + // Wrong security open/wpa_psk/wpa3_sae instad of wpa2_psk + // [1.525449] type: .set_ssid, status: .fail + // [1.532186] error: error.Cyw43SetSsid // - // wrong security open/wpa_psk/wpa3_sae instad of wpa2_psk - // [1.525449] debug (cyw43_wifi): poll event type: .set_ssid, status: .fail - // [1.532186] error: panic: main() returned error.Cyw43SetSsid - pub fn poll(jp: *JoinPoller) !bool { - const events = &jp.parent.events; if (jp.parent.join_state == .joining) { try jp.parent.poll(); - try events.err(); + try jp.parent.event_log.err(); } return jp.parent.join_state == .joining; } @@ -520,43 +518,45 @@ pub fn poll(self: *Self) !void { fn handle_event(self: *Self, rsp: ioctl.Response) void { const evt = (rsp.event() orelse return).msg; - const events = &self.events; - const state = Events.State.from; - // log.debug( - // "handle event type: {}, status: {} ", - // .{ evt.event_type, evt.status }, - // ); + const event_log = &self.event_log; + const state = EventLog.EventState.from; + const debug_log = false; + if (debug_log) + log.debug( + "handle event type: {}, status: {} ", + .{ evt.event_type, evt.status }, + ); switch (evt.event_type) { .link => { - events.link = state(evt.status == .success and evt.flags & 1 > 0); + event_log.link = state(evt.status == .success and evt.flags & 1 > 0); }, .psk_sup => { - events.psk_sup = + event_log.psk_sup = state(evt.status == .success or evt.status == .unsolicited); }, .assoc, .reassoc => { - events.assoc = state(evt.status == .success); + event_log.assoc = state(evt.status == .success); }, .disassoc_ind, .disassoc => { - events.assoc = .fail; + event_log.assoc = .fail; }, .auth => { - events.auth = state(evt.status == .success); + event_log.auth = state(evt.status == .success); }, - .deauth_ind, .deauth => { - events.auth = .fail; + .deauth_ind => { + event_log.auth = .fail; }, .set_ssid => { - events.set_ssid = state(evt.status == .success); + event_log.set_ssid = state(evt.status == .success); }, .join => { - events.join = state(evt.status == .success); + event_log.join = state(evt.status == .success); }, .escan_result => { self.scan_result = null; - events.scan = switch (evt.status) { - .success => .success, - .partial => .running, + self.scan_state = switch (evt.status) { + .success, .newassoc, .ccxfastrm => .success, + .partial, .newscan => .running, else => .fail, }; if (evt.status == .partial) { @@ -572,11 +572,11 @@ fn handle_event(self: *Self, rsp: ioctl.Response) void { return; }, } - const new_state = events.join_state(); - // if (new_state != self.join_state) { - // log.debug("state changed old: {}, new: {}", .{ self.join_state, new_state }); - // log.debug("events: {}", .{self.events}); - // } + const new_state = event_log.join_state(); + if (debug_log and new_state != self.join_state) { + log.debug("state changed old: {}, new: {}", .{ self.join_state, new_state }); + log.debug("event log: {}", .{self.event_log}); + } self.join_state = new_state; } @@ -619,12 +619,12 @@ const ScanParams = extern struct { }; /// Init scan and return poller -pub fn scan_poller(self: *Self) !ScanPoller { +pub fn scan(self: *Self) !ScanPoller { // Params can be used to choose active/passive scan type or to set specific // ssid to scan for. var params: ScanParams = .{}; try self.set_var("escan", mem.asBytes(¶ms)); - self.events.scan = .running; + self.scan_state = .running; return .{ .parent = self, @@ -645,10 +645,10 @@ pub const ScanPoller = struct { /// while (try scan.poll()) { /// if (scan.result()) |res| { ... pub fn poll(sp: *ScanPoller) !bool { - if (sp.status() == .running) { + if (sp.parent.scan_state == .running) { try sp.parent.poll(); } - return sp.status() == .running; + return sp.parent.scan_state == .running; } pub fn result(sp: *ScanPoller) ?ScanResult { @@ -665,14 +665,13 @@ pub const ScanPoller = struct { return res; } - pub fn status(sp: ScanPoller) Events.State { - return sp.parent.events.scan; + pub fn state(sp: ScanPoller) ScanState { + return sp.parent.scan_state; } }; // show unexpected command response -fn log_response(self: *Self, rsp: ioctl.Response) void { - _ = self; +fn log_response(self: Self, rsp: ioctl.Response) void { switch (rsp.sdp.channel()) { .event => { const evt = (rsp.event() orelse return).msg; @@ -690,6 +689,7 @@ fn log_response(self: *Self, rsp: ioctl.Response) void { log.err(" data: {x}", .{rsp.data()}); }, .data => { + if (self.join_state == .joining) return; log.err("unexpected data:", .{}); log.err(" bus: {}", .{rsp.sdp}); log.err(" bdc: {}", .{rsp.bdc()}); @@ -698,7 +698,7 @@ fn log_response(self: *Self, rsp: ioctl.Response) void { } } -fn sleep_ms(self: *Self, delay: u32) void { +fn sleep_ms(self: Self, delay: u32) void { self.bus.sleep_ms(delay); } @@ -760,16 +760,16 @@ pub fn has_credit(self: *Self) bool { // Read packet from the wifi chip. Assuming that this is used in the loop // until it returns null. That way we can cache status from previous read. fn read(self: *Self, buffer: []u8) !?ioctl.Response { - if (self.status == null) self.read_status(); - const status = self.status.?; - self.status = null; + if (self.bus_status == null) self.read_bus_status(); + const status = self.bus_status.?; + self.bus_status = null; if (status.f2_packet_available and status.packet_length > 0) { const words_len: usize = ((status.packet_length + 3) >> 2) + 1; // add one word for the status const words = as_words(buffer, words_len); self.bus.read(.wlan, 0, status.packet_length, words); // last word is status - self.status = @bitCast(words[words.len - 1]); + self.bus_status = @bitCast(words[words.len - 1]); // parse response const rsp = try ioctl.response(mem.sliceAsBytes(words)[0..status.packet_length]); // update credit @@ -786,8 +786,8 @@ fn as_words(bytes: []u8, len: usize) []u32 { return words; } -fn read_status(self: *Self) void { - self.status = @bitCast(self.bus.read_int(u32, .bus, Bus.reg.status)); +fn read_bus_status(self: *Self) void { + self.bus_status = @bitCast(self.bus.read_int(u32, .bus, Bus.reg.status)); } pub fn gpio_enable(self: *Self, pin: u2) void { @@ -814,8 +814,29 @@ pub fn gpio_out(self: *Self, pin: u2, on: bool) !void { try self.set_var("gpioout", &data); } +fn show_clm_ver(self: *Self) !void { + var data: [128]u8 = @splat(0); + const n = try self.get_var("clmver", &data); + var iter = mem.splitScalar(u8, data[0..n], 0x0a); + log.debug("clmver:", .{}); + while (iter.next()) |line| { + if (line.len == 0 or line[0] == 0x00) continue; + log.debug(" {s}", .{line}); + } +} + +pub fn read_mac(self: *Self) ![6]u8 { + var mac: [6]u8 = @splat(0); + const n = try self.get_var("cur_etheraddr", &mac); + if (n != mac.len) { + log.err("read_mac unexpected read bytes: {}", .{n}); + return error.ReadMacFailed; + } + return mac; +} + // ref: datasheet 'Table 5. gSPI Status Field Details' -const Status = packed struct { +const BusStatus = packed struct { data_not_available: bool, // The requested read data is not available. underflow: bool, // FIFO underflow occurred due to current (F2, F3) read command. overflow: bool, // FIFO overflow occurred due to current (F1, F2, F3) write command. @@ -836,59 +857,71 @@ pub const ScanResult = struct { ssid_buf: [32]u8 = @splat(0), }; -const Events = packed struct { - pub const State = enum(u2) { +pub const ScanState = enum { + none, + running, + success, + fail, +}; + +// Log of the join events +const EventLog = packed struct { + pub const EventState = enum(u2) { none = 0b00, success = 0b01, fail = 0b10, - running = 0b11, - fn from(ok: bool) Events.State { + fn from(ok: bool) EventState { return if (ok) .success else .fail; } }; // essential join events - auth: Events.State = .none, - link: Events.State = .none, - psk_sup: Events.State = .none, - set_ssid: Events.State = .none, + auth: EventState = .none, + link: EventState = .none, + psk_sup: EventState = .none, + set_ssid: EventState = .none, // other join events - assoc: Events.State = .none, - join: Events.State = .none, - // scan event - scan: Events.State = .none, + assoc: EventState = .none, + join: EventState = .none, - pub fn err(e: Events) !void { + pub fn err(e: EventLog) !void { if (e.auth == .fail) return error.Cyw43Authentication; if (e.link == .fail) return error.Cyw43LinkDown; if (e.psk_sup == .fail) return error.Cyw43WpaHandshake; if (e.set_ssid == .fail) return error.Cyw43SetSsid; } - pub fn join_state(e: Events) JoinState { - var i: u14 = @bitCast(e); - i &= 0b00_00_00_11_11_11_11; // filter essential join events + pub fn join_state(e: EventLog) JoinState { + var i: u12 = @bitCast(e); + i &= 0b00_00_11_11_11_11; // filter essential join events const fail_mask = 0b10_10_10_10; const succ_mask = 0b01_01_01_01; - if (i == 0) return .initial; + if (i == 0) return .none; if (i & fail_mask > 0) return .disjoined; // any failed if (i & succ_mask == succ_mask) return .joined; // all success return .joining; } }; +pub const JoinState = enum { + none, + joining, + joined, + disjoined, +}; + const testing = std.testing; test "events to join_state" { - var e: Events = .{}; - try testing.expectEqual(.initial, e.join_state()); + var e: EventLog = .{}; + try testing.expectEqual(.none, e.join_state()); e.auth = .success; try testing.expectEqual(.joining, e.join_state()); e.link = .success; e.psk_sup = .success; try testing.expectEqual(.joining, e.join_state()); - try testing.expectEqual(0b00_01_01_01, @as(u14, @bitCast(e))); + try testing.expectEqual(0b00_01_01_01, @as(u12, @bitCast(e))); e.set_ssid = .success; try testing.expectEqual(.joined, e.join_state()); e.auth = .fail; @@ -899,13 +932,6 @@ test "events to join_state" { try testing.expectEqual(.joined, e.join_state()); } -pub const JoinState = enum { - initial, - joining, - joined, - disjoined, -}; - // CYW43439 chip values const chip = struct { const wrapper_register_offset: u32 = 0x100000; diff --git a/examples/raspberrypi/rp2xxx/src/net/pong.zig b/examples/raspberrypi/rp2xxx/src/net/pong.zig index 01ae742b9..b360bd2df 100644 --- a/examples/raspberrypi/rp2xxx/src/net/pong.zig +++ b/examples/raspberrypi/rp2xxx/src/net/pong.zig @@ -35,7 +35,7 @@ pub fn main() !void { log.debug("mac address: {x}", .{wifi.mac}); // join network - try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); + try wifi.join_wait(secrets.ssid, secrets.pwd, secrets.join_opt); log.debug("wifi joined", .{}); // init lwip network interface diff --git a/examples/raspberrypi/rp2xxx/src/net/tcp_client.zig b/examples/raspberrypi/rp2xxx/src/net/tcp_client.zig index eaa47287d..eebc006e4 100644 --- a/examples/raspberrypi/rp2xxx/src/net/tcp_client.zig +++ b/examples/raspberrypi/rp2xxx/src/net/tcp_client.zig @@ -33,7 +33,7 @@ pub fn main() !void { var wifi = try wifi_driver.init(.{}); var led = wifi.gpio(0); // join network - try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); + try wifi.join_wait(secrets.ssid, secrets.pwd, secrets.join_opt); // init lwip network interface var nic: net.Interface = .{ .link = wifi.link() }; diff --git a/examples/raspberrypi/rp2xxx/src/net/tcp_server.zig b/examples/raspberrypi/rp2xxx/src/net/tcp_server.zig index c176c86f8..8fcfa396b 100644 --- a/examples/raspberrypi/rp2xxx/src/net/tcp_server.zig +++ b/examples/raspberrypi/rp2xxx/src/net/tcp_server.zig @@ -33,7 +33,7 @@ pub fn main() !void { var wifi = try wifi_driver.init(.{}); var led = wifi.gpio(0); // join network - try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); + try wifi.join_wait(secrets.ssid, secrets.pwd, secrets.join_opt); // init lwip network interface var nic: net.Interface = .{ .link = wifi.link() }; diff --git a/examples/raspberrypi/rp2xxx/src/net/udp.zig b/examples/raspberrypi/rp2xxx/src/net/udp.zig index 0e6b34782..25ff47736 100644 --- a/examples/raspberrypi/rp2xxx/src/net/udp.zig +++ b/examples/raspberrypi/rp2xxx/src/net/udp.zig @@ -33,7 +33,7 @@ pub fn main() !void { var wifi = try wifi_driver.init(.{}); var led = wifi.gpio(0); // join network - try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); + try wifi.join_wait(secrets.ssid, secrets.pwd, secrets.join_opt); // init lwip network interface var nic: net.Interface = .{ .link = wifi.link() }; diff --git a/port/raspberrypi/rp2xxx/src/hal/drivers.zig b/port/raspberrypi/rp2xxx/src/hal/drivers.zig index 913096b77..1a162e45c 100644 --- a/port/raspberrypi/rp2xxx/src/hal/drivers.zig +++ b/port/raspberrypi/rp2xxx/src/hal/drivers.zig @@ -515,13 +515,13 @@ pub const WiFi = struct { pub const ready = Chip.ready; const Spi = @import("cyw43439_pio_spi.zig"); - pub const Config = Spi.Config; + pub const Options = Chip.InitOptions; spi: Spi = undefined, chip: Chip = .{}, // cyw43 chip interface - pub fn init(self: *Self, config: Config) !*Chip { - self.spi = try Spi.init(config); + pub fn init(self: *Self, opt: Options) !*Chip { + self.spi = try Spi.init(.{}); try self.chip.init( .{ .ptr = &self.spi, @@ -531,6 +531,7 @@ pub const WiFi = struct { }, }, hal.time.sleep_ms, + opt, ); return &self.chip; } From 39b10ab87b91a18fc9c4c51457eca43069ebe5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Sat, 17 Jan 2026 17:09:31 +0100 Subject: [PATCH 10/19] net: de-duplicate logging Report status after init, not also during init. Remember last status and de-duplicate. Remove error log which is also passed to the application. --- modules/network/src/include/lwipopts.h | 2 +- modules/network/src/root.zig | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/network/src/include/lwipopts.h b/modules/network/src/include/lwipopts.h index c3b4ffac1..90521dea6 100644 --- a/modules/network/src/include/lwipopts.h +++ b/modules/network/src/include/lwipopts.h @@ -23,7 +23,7 @@ #define LWIP_MDNS_RESPONDER LWIP_UDP // callbacks -#define LWIP_NETIF_LINK_CALLBACK 0 +#define LWIP_NETIF_LINK_CALLBACK 1 #define LWIP_NETIF_STATUS_CALLBACK 1 #define LWIP_NETIF_EXT_STATUS_CALLBACK 0 diff --git a/modules/network/src/root.zig b/modules/network/src/root.zig index 3e0e73fc9..e4a663be2 100644 --- a/modules/network/src/root.zig +++ b/modules/network/src/root.zig @@ -29,6 +29,7 @@ pub const Interface = struct { netif: lwip.netif = .{}, dhcp: lwip.dhcp = .{}, link: Link, + status_flags: u16 = 0, pub const Options = struct { fixed: ?Fixed = null, @@ -75,7 +76,6 @@ pub const Interface = struct { 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) { @@ -83,6 +83,8 @@ pub const Interface = struct { try c_err(lwip.dhcp_start(netif)); } lwip.netif_set_link_up(netif); + lwip.netif_set_status_callback(netif, c_on_netif_status); + lwip.netif_set_link_callback(netif, c_on_netif_status); } fn c_netif_init(netif_c: [*c]lwip.netif) callconv(.c) lwip.err_t { @@ -100,6 +102,10 @@ pub const Interface = struct { 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); + + const new_flags: u16 = (if (self.ready()) @as(u16, 1) else 0) << 8 | netif.flags; + if (self.status_flags == new_flags) return; + self.status_flags = new_flags; log.debug("netif status callback is_link_up: {}, is_up: {}, ready: {}, ip: {s}", .{ netif.flags & lwip.NETIF_FLAG_LINK_UP > 0, netif.flags & lwip.NETIF_FLAG_UP > 0, @@ -130,7 +136,6 @@ pub const Interface = struct { } self.link.vtable.send(self.link.ptr, payload_bytes(pbuf)) catch |err| { - log.err("link send {}", .{err}); return switch (err) { error.OutOfMemory => lwip.ERR_MEM, error.LinkDown => lwip.ERR_IF, @@ -343,7 +348,6 @@ pub const tcp = struct { 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( From c5c4be2ea2f4b65581cb118e3f96c92ba887dbf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Sat, 17 Jan 2026 17:11:24 +0100 Subject: [PATCH 11/19] cyw43: report link down in send --- drivers/wireless/cyw43439.zig | 9 +++++---- drivers/wireless/cyw43439/wifi.zig | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index d3fe3be36..cc37085b7 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -64,10 +64,11 @@ pub fn recv_zc(ptr: *anyopaque, bytes: []u8) anyerror!?struct { usize, usize } { pub fn send_zc(ptr: *anyopaque, bytes: []u8) Link.Error!void { const self: *Self = @ptrCast(@alignCast(ptr)); - self.wifi.send_zc(bytes) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkDown => return error.LinkDown, - else => return error.InternalError, + if (self.wifi.join_state != .joined) return error.LinkDown; + if (!self.wifi.has_credit()) return error.OutOfMemory; + self.wifi.send_zc(bytes) catch |err| { + log.err("cyw43 send {}", .{err}); + return error.InternalError; }; } diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 1a4769782..d66821a15 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -739,8 +739,6 @@ pub fn recv_zc(self: *Self, buffer: []u8) !?struct { usize, usize } { /// Buffer has to be 4 bytes aligned and it will be extended in as_words to the /// word boundary! pub fn send_zc(self: *Self, buffer: []u8) anyerror!void { - if (!self.has_credit()) return error.OutOfMemory; - const eth_frame_len = buffer.len - 22; // add bus header self.tx_sequence +%= 1; From 48bae5f70d4f57f19c57d0b239ccd21329239bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Sat, 17 Jan 2026 17:17:52 +0100 Subject: [PATCH 12/19] rpi: add WiFi networks scan example --- examples/raspberrypi/rp2xxx/build.zig | 1 + examples/raspberrypi/rp2xxx/src/net/readme.md | 20 +++++ examples/raspberrypi/rp2xxx/src/net/scan.zig | 88 +++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 examples/raspberrypi/rp2xxx/src/net/scan.zig diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index f21d50379..1c76a4c86 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -96,6 +96,7 @@ pub fn build(b: *std.Build) void { .{ .name = "font8x8", .module = font8x8_dep.module("font8x8") }, } }, .{ .name = "net-pong", .file = "src/net/pong.zig" }, + .{ .name = "net-scan", .file = "src/net/scan.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" }, diff --git a/examples/raspberrypi/rp2xxx/src/net/readme.md b/examples/raspberrypi/rp2xxx/src/net/readme.md index 3d7dfb6c6..4a960289d 100644 --- a/examples/raspberrypi/rp2xxx/src/net/readme.md +++ b/examples/raspberrypi/rp2xxx/src/net/readme.md @@ -23,6 +23,26 @@ debug (lwip): netif status callback is_link_up: true, is_up: true, ready: true, Than you can ping that address from the your host computer and see responses. +## scan.zig + +Scans for WiFi networks and reports each found network. If open network is found it connects to that network. Reports WiFi chip join state. + +``` +================ STARTING NEW LOGGER ================ +[0.930822] debug (main): scaning wifi networks +[0.945470] debug (main): found ssid: SUN2000-NS235G001306, channel: 1, open: false, ap mac 3820283fbffa +[1.095437] debug (main): found ssid: my-net-1 , channel: 6, open: false, ap mac 18e829c4ec78 +[1.115025] debug (main): found ssid: my-net-2 , channel: 6, open: true , ap mac 1ae829c4ec78 +[1.185040] debug (main): found ssid: my-net-2 , channel: 6, open: true , ap mac 7283c23179d7 +[1.214764] debug (main): found ssid: my-net-1 , channel: 6, open: false, ap mac 7683c23179d7 +[1.625584] debug (main): found ssid: NVRE82242483 , channel: 13, open: false, ap mac e0baad364a3d +[1.665162] debug (main): joining to: my-net-2 +[2.543121] debug (main): join state .joined +[4.675729] debug (lwip): netif status callback is_link_up: true, is_up: true, ready: true, ip: 192.168.207.170 +[30.053127] debug (main): join state changed .disjoined +[38.535162] debug (main): join state changed .joined +``` +Here I turned off my-net-2 access point, and bring it back after few seconds. ## udp.zig diff --git a/examples/raspberrypi/rp2xxx/src/net/scan.zig b/examples/raspberrypi/rp2xxx/src/net/scan.zig new file mode 100644 index 000000000..9a6433310 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/net/scan.zig @@ -0,0 +1,88 @@ +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); + +comptime { + _ = @import("lwip_exports.zig"); +} +const net = @import("net"); + +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); + + var ssid_buf: [32]u8 = undefined; + var ssid: []const u8 = ssid_buf[0..0]; + + log.debug("scaning wifi networks", .{}); + var scan = try wifi.scan(); + while (try scan.poll()) { + if (scan.result()) |res| { + log.debug( + " found ssid: {s:<20}, channel: {}, open: {:<5}, ap mac {x}", + .{ res.ssid, res.channel, res.security.open(), res.ap_mac }, + ); + if (res.security.open() and ssid.len == 0) { + ssid_buf = res.ssid_buf; + ssid = ssid_buf[0..res.ssid.len]; + } + } + time.sleep_ms(10); + } + + if (ssid.len == 0) { + @panic("no open WiFi network found"); + } + + log.debug("joining to: {s}", .{ssid}); + var join = try wifi.join(ssid, "", .{ .security = .open }); + var ticks: u32 = 0; + while (try join.poll()) : (ticks +%= 1) { + if (ticks % 5 == 0) { + led.toggle(); + } + time.sleep_ms(10); + } + log.debug("join state {}", .{wifi.join_state()}); + + // init lwip network interface + var nic: net.Interface = .{ .link = wifi.link() }; + try nic.init(wifi.mac, .{}); + + var join_state = wifi.join_state(); + ticks = 0; + while (true) : (ticks +%= 1) { + // run lwip poller + try nic.poll(); + // blink + if (ticks % 50 == 0) { + led.toggle(); + } + if (join_state != wifi.join_state()) { + join_state = wifi.join_state(); + log.debug("join state changed {}", .{join_state}); + } + time.sleep_ms(10); + } +} From ef138d16c137cf6c66eae9f86b2a8f89ae749e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Sat, 17 Jan 2026 17:56:14 +0100 Subject: [PATCH 13/19] net: report link state from chip on each read Use that state to set lwip link up/down. --- drivers/wireless/cyw43439.zig | 22 +++++++++++++--- drivers/wireless/cyw43439/wifi.zig | 5 ++-- examples/raspberrypi/rp2xxx/src/net/readme.md | 8 +++--- modules/network/link/link.zig | 23 +++++++++++----- modules/network/src/root.zig | 26 ++++++++++++++----- 5 files changed, 61 insertions(+), 23 deletions(-) diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index cc37085b7..a861f6cf9 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -31,6 +31,8 @@ pub fn init( self.mac = try self.wifi.read_mac(); } +/// Non blocking join. Returns poller which should be pulled while poll method +/// returns true (has more). pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !WiFi.JoinPoller { return try self.wifi.join(ssid, pwd, opt); } @@ -49,6 +51,7 @@ pub fn is_joined(self: *Self) bool { return self.wifi.join_state == .joined; } +/// Non blocking scan. Returns poller. pub fn scan(self: *Self) !WiFi.ScanPoller { return try self.wifi.scan(); } @@ -57,16 +60,27 @@ pub fn scan_result(self: *Self) ?WiFi.ScanResult { return self.wifi.scan_result; } -pub fn recv_zc(ptr: *anyopaque, bytes: []u8) anyerror!?struct { usize, usize } { +/// Zero copy receive. This buffer is passed to the chip. Buffer has to be 4 +/// bytes aligned and at least 1540 bytes. `head` and `len` defines part of the +/// buffer where is received ethernet packet. If `len` is zero there is no packet. +pub fn recv_zc(ptr: *anyopaque, buffer: []u8) anyerror!Link.RecvResponse { const self: *Self = @ptrCast(@alignCast(ptr)); - return self.wifi.recv_zc(bytes); + const head, const len = try self.wifi.recv_zc(buffer); + return .{ + .head = head, + .len = len, + .link_state = if (self.is_joined()) .up else .down, + }; } -pub fn send_zc(ptr: *anyopaque, bytes: []u8) Link.Error!void { +/// Zero copy send. Buffer has to have 22 bytes of headrom for chip control +/// command. Ethernet packet has to start at byte 22. Buffer has to be 4 bytes +/// aligned. +pub fn send_zc(ptr: *anyopaque, buffer: []u8) Link.Error!void { const self: *Self = @ptrCast(@alignCast(ptr)); if (self.wifi.join_state != .joined) return error.LinkDown; if (!self.wifi.has_credit()) return error.OutOfMemory; - self.wifi.send_zc(bytes) catch |err| { + self.wifi.send_zc(buffer) catch |err| { log.err("cyw43 send {}", .{err}); return error.InternalError; }; diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index d66821a15..626e28d17 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -715,10 +715,9 @@ fn sleep_ms(self: Self, delay: u32) void { /// /// Layer 2 header+payload position is passed to the caller. First return /// argument is start of that data in the buffer second is length. -/// -pub fn recv_zc(self: *Self, buffer: []u8) !?struct { usize, usize } { +pub fn recv_zc(self: *Self, buffer: []u8) !struct { usize, usize } { while (true) { - const rsp = try self.read(buffer) orelse return null; + const rsp = try self.read(buffer) orelse return .{ 0, 0 }; switch (rsp.sdp.channel()) { .data => return rsp.data_pos(), .event => self.handle_event(rsp), diff --git a/examples/raspberrypi/rp2xxx/src/net/readme.md b/examples/raspberrypi/rp2xxx/src/net/readme.md index 4a960289d..64fc8de34 100644 --- a/examples/raspberrypi/rp2xxx/src/net/readme.md +++ b/examples/raspberrypi/rp2xxx/src/net/readme.md @@ -38,9 +38,11 @@ Scans for WiFi networks and reports each found network. If open network is found [1.625584] debug (main): found ssid: NVRE82242483 , channel: 13, open: false, ap mac e0baad364a3d [1.665162] debug (main): joining to: my-net-2 [2.543121] debug (main): join state .joined -[4.675729] debug (lwip): netif status callback is_link_up: true, is_up: true, ready: true, ip: 192.168.207.170 -[30.053127] debug (main): join state changed .disjoined -[38.535162] debug (main): join state changed .joined +[5.017620] debug (lwip): netif status callback is_link_up: true, is_up: true, ready: true, ip: 192.168.207.170 +[20.673675] debug (lwip): netif status callback is_link_up: false, is_up: true, ready: false, ip: 192.168.207.170 +[20.683811] debug (main): join state changed .disjoined +[30.386339] debug (lwip): netif status callback is_link_up: true, is_up: true, ready: true, ip: 192.168.207.170 +[30.396295] debug (main): join state changed .joined ``` Here I turned off my-net-2 access point, and bring it back after few seconds. diff --git a/modules/network/link/link.zig b/modules/network/link/link.zig index 29213364b..726b16e3b 100644 --- a/modules/network/link/link.zig +++ b/modules/network/link/link.zig @@ -7,19 +7,30 @@ vtable: struct { /// Receive data packet from the data link layer. /// /// Network module allocates and passes buffer to the recv function. If there - /// is no data waiting in the driver null is returned. + /// is no data waiting in the driver zero len is returned in the response. /// - /// If non null tuple is returned it contains start position of the Ethernet - /// header in that buffer and length of the data packet. - /// - /// Start position can be greater than zero if there is data link layer + /// Head and len definies part of the buffer where paket data is stored. + /// Head position can be greater than zero if there is data link layer /// specific header in the buffer after getting data from the driver. - recv: *const fn (*anyopaque, []u8) anyerror!?struct { usize, usize }, + /// + /// Each read also returns current link state; up or down. + recv: *const fn (*anyopaque, []u8) anyerror!RecvResponse, /// Send data packet to the data link layer. send: *const fn (*anyopaque, []u8) Error!void, }, +pub const RecvResponse = struct { + pub const LinkState = enum { + up, + down, + }; + + head: usize = 0, + len: usize = 0, + link_state: LinkState = .up, +}; + pub const Error = error{ /// Packet can't fit into link output buffer OutOfMemory, diff --git a/modules/network/src/root.zig b/modules/network/src/root.zig index e4a663be2..0a3723248 100644 --- a/modules/network/src/root.zig +++ b/modules/network/src/root.zig @@ -154,6 +154,7 @@ pub const Interface = struct { pub fn poll(self: *Self) !void { lwip.sys_check_timeouts(); + const netif = &self.netif; var packets: usize = 0; while (true) : (packets += 1) { // get packet buffer of the max size @@ -167,20 +168,31 @@ pub const Interface = struct { "net.Interface.pool invalid pbuf allocation", ); // receive into that buffer - const head, const len = try self.link.vtable.recv(self.link.ptr, payload_bytes(pbuf)) orelse { - // no data release packet buffer and exit loop + const rsp = try self.link.vtable.recv(self.link.ptr, payload_bytes(pbuf)); + // sync link state + const link_state: Link.RecvResponse.LinkState = + if (netif.flags & lwip.NETIF_FLAG_LINK_UP > 0) .up else .down; + if (rsp.link_state != link_state) { + switch (rsp.link_state) { + .up => lwip.netif_set_link_up(netif), + .down => lwip.netif_set_link_down(netif), + } + } + + if (rsp.len == 0) { // 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 (head > 0 and lwip.pbuf_header(pbuf, -@as(lwip.s16_t, @intCast(head))) != 0) { + if (rsp.head > 0 and lwip.pbuf_header(pbuf, -@as(lwip.s16_t, @intCast(rsp.head))) != 0) { return error.InvalidPbufHead; } - pbuf.len = @intCast(len); - pbuf.tot_len = @intCast(len); + pbuf.len = @intCast(rsp.len); + pbuf.tot_len = @intCast(rsp.len); // pass data to the lwip input function - try c_err(self.netif.input.?(pbuf, &self.netif)); + try c_err(netif.input.?(pbuf, netif)); } if (packets > 0) { lwip.sys_check_timeouts(); From 82f454f75b51d8cc6611e9ece905145e433e64f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Mon, 26 Jan 2026 23:39:10 +0100 Subject: [PATCH 14/19] rpi net: add interrupt mode Handling cyw43 data packet receive interrupts. --- drivers/wireless/cyw43439.zig | 4 +- drivers/wireless/cyw43439/bus.zig | 16 ++-- drivers/wireless/cyw43439/ioctl.zig | 1 + drivers/wireless/cyw43439/wifi.zig | 56 ++++++++----- examples/raspberrypi/rp2xxx/build.zig | 1 + examples/raspberrypi/rp2xxx/src/net/readme.md | 5 ++ modules/network/link/link.zig | 1 + modules/network/src/root.zig | 38 ++++++--- .../rp2xxx/src/hal/cyw43439_pio_spi.zig | 12 +-- port/raspberrypi/rp2xxx/src/hal/drivers.zig | 79 +++++++++++++++++-- 10 files changed, 163 insertions(+), 50 deletions(-) diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index a861f6cf9..555572a16 100644 --- a/drivers/wireless/cyw43439.zig +++ b/drivers/wireless/cyw43439.zig @@ -65,11 +65,13 @@ pub fn scan_result(self: *Self) ?WiFi.ScanResult { /// buffer where is received ethernet packet. If `len` is zero there is no packet. pub fn recv_zc(ptr: *anyopaque, buffer: []u8) anyerror!Link.RecvResponse { const self: *Self = @ptrCast(@alignCast(ptr)); - const head, const len = try self.wifi.recv_zc(buffer); + const head, const len, const next_packet_available = try self.wifi.recv_zc(buffer); + if (self.wifi.err) |err| return err; return .{ .head = head, .len = len, .link_state = if (self.is_joined()) .up else .down, + .next_packet_available = next_packet_available, }; } diff --git a/drivers/wireless/cyw43439/bus.zig b/drivers/wireless/cyw43439/bus.zig index 899b42ada..a9c0924ee 100644 --- a/drivers/wireless/cyw43439/bus.zig +++ b/drivers/wireless/cyw43439/bus.zig @@ -19,9 +19,16 @@ pub const Spi = struct { self.vtable.write(self.ptr, buffer); } + pub fn irq_cleared(self: *@This()) void { + if (self.vtable.irq_cleared) |handler| { + handler(self.ptr); + } + } + pub const VTable = struct { read: *const fn (*anyopaque, []u32) void, write: *const fn (*anyopaque, []u32) void, + irq_cleared: ?*const fn (*anyopaque) void = null, }; }; @@ -68,7 +75,7 @@ pub fn init(self: *Self) !void { .wake_up = true, }, .response_delay = .{ - .unknown = 0x4, // 32-bit response delay? + .unknown = 0, }, .status_enable = .{ .status_enable = true, @@ -354,11 +361,8 @@ pub const Irq = packed struct { f2_packet_available: bool = false, f3_packet_available: bool = false, f1_overflow: bool = false, // Due to last write. Bkplane has pending write requests - misc_intr0: bool = false, - misc_intr1: bool = false, - misc_intr2: bool = false, - misc_intr3: bool = false, - misc_intr4: bool = false, + + _reserved1: u5 = 0, f1_intr: bool = false, f2_intr: bool = false, f3_intr: bool = false, diff --git a/drivers/wireless/cyw43439/ioctl.zig b/drivers/wireless/cyw43439/ioctl.zig index 00b400864..782530609 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -92,6 +92,7 @@ pub const Cmd = enum(u32) { set_auth = 22, set_ssid = 26, set_antdiv = 64, + set_pm = 86, set_gmode = 110, set_wsec = 134, set_band = 142, diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 626e28d17..9188bdfa6 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -13,13 +13,13 @@ bus: *Bus, request_id: u16 = 0, credit: u8 = 0, tx_sequence: u8 = 0, -bus_status: ?BusStatus = null, log_state: LogState = .{}, event_log: EventLog = .{}, join_state: JoinState = .none, scan_state: ScanState = .none, scan_result: ?ScanResult = null, +err: ?anyerror = null, const ioctl_request_bytes_len = 1024; @@ -214,6 +214,7 @@ pub fn init(self: *Self, opt: InitOptions) !void { try self.set_var("assoc_listen", &.{0x0a}); try self.set_cmd(.set_gmode, &.{1}); // auto try self.set_cmd(.set_band, &.{0}); // any + // try self.set_cmd(.set_pm, &.{2});// power mode } self.log_init(); @@ -330,7 +331,7 @@ fn response_poll(self: *Self, cmd: ioctl.Cmd, data: ?[]u8) !usize { var bytes: [ioctl_request_bytes_len]u8 align(4) = undefined; var delay: usize = 0; while (delay < ioctl.response_wait) { - const rsp = try self.read(&bytes) orelse { + const rsp, _ = try self.read(&bytes) orelse { self.sleep_ms(ioctl.response_poll_interval); delay += ioctl.response_poll_interval; continue; @@ -372,6 +373,8 @@ pub const JoinOptions = struct { }; pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !JoinPoller { + self.err = null; + var buf: [64]u8 = @splat(0); // space for 10 addresses // Enable multicast { @@ -498,18 +501,18 @@ pub const JoinPoller = struct { pub fn wait(jp: *JoinPoller, wait_ms: u32) !void { var delay: u32 = 0; - while (delay < wait_ms) { - if (!try jp.poll()) return; + while (delay < wait_ms and try jp.poll()) { jp.parent.sleep_ms(ioctl.response_poll_interval); delay += ioctl.response_poll_interval; } + if (jp.parent.join_state == .joined) return; return error.Cyw43JoinTimeout; } }; pub fn poll(self: *Self) !void { var bytes: [ioctl_request_bytes_len]u8 align(4) = undefined; - const rsp = try self.read(&bytes) orelse return; + const rsp, _ = try self.read(&bytes) orelse return; switch (rsp.sdp.channel()) { .event => self.handle_event(rsp), else => self.log_response(rsp), @@ -577,6 +580,11 @@ fn handle_event(self: *Self, rsp: ioctl.Response) void { log.debug("state changed old: {}, new: {}", .{ self.join_state, new_state }); log.debug("event log: {}", .{self.event_log}); } + if (self.join_state == .joining and new_state == .disjoined) { + event_log.err() catch |err| { + self.err = err; + }; + } self.join_state = new_state; } @@ -620,6 +628,7 @@ const ScanParams = extern struct { /// Init scan and return poller pub fn scan(self: *Self) !ScanPoller { + self.err = null; // Params can be used to choose active/passive scan type or to set specific // ssid to scan for. var params: ScanParams = .{}; @@ -715,11 +724,14 @@ fn sleep_ms(self: Self, delay: u32) void { /// /// Layer 2 header+payload position is passed to the caller. First return /// argument is start of that data in the buffer second is length. -pub fn recv_zc(self: *Self, buffer: []u8) !struct { usize, usize } { +pub fn recv_zc(self: *Self, buffer: []u8) !struct { usize, usize, bool } { while (true) { - const rsp = try self.read(buffer) orelse return .{ 0, 0 }; + const rsp, const rx_ready = try self.read(buffer) orelse return .{ 0, 0, false }; switch (rsp.sdp.channel()) { - .data => return rsp.data_pos(), + .data => { + const head, const len = rsp.data_pos(); + return .{ head, len, rx_ready }; + }, .event => self.handle_event(rsp), else => self.log_response(rsp), } @@ -754,24 +766,28 @@ pub fn has_credit(self: *Self) bool { return self.tx_sequence != self.credit and (self.credit -% self.tx_sequence) & 0x80 == 0; } -// Read packet from the wifi chip. Assuming that this is used in the loop -// until it returns null. That way we can cache status from previous read. -fn read(self: *Self, buffer: []u8) !?ioctl.Response { - if (self.bus_status == null) self.read_bus_status(); - const status = self.bus_status.?; - self.bus_status = null; +// Read packet from the WiFi chip. Returns bus response and indication if there +// is more data packets available. Returns null if there are no packets ready. +fn read(self: *Self, buffer: []u8) !?struct { ioctl.Response, bool } { + var status = self.read_bus_status(); + defer { + if (status.f2_interrupt and !status.f2_packet_available and self.join_state != .none) { + // Reading all packets clears interrupt, notify upstream that it is + // cleared now. + self.bus.spi.irq_cleared(); + } + } if (status.f2_packet_available and status.packet_length > 0) { const words_len: usize = ((status.packet_length + 3) >> 2) + 1; // add one word for the status const words = as_words(buffer, words_len); - self.bus.read(.wlan, 0, status.packet_length, words); - // last word is status - self.bus_status = @bitCast(words[words.len - 1]); // parse response const rsp = try ioctl.response(mem.sliceAsBytes(words)[0..status.packet_length]); + // last word is status + status = @bitCast(words[words.len - 1]); // update credit self.credit = rsp.sdp.credit; - return rsp; + return .{ rsp, status.f2_packet_available }; } return null; } @@ -783,8 +799,8 @@ fn as_words(bytes: []u8, len: usize) []u32 { return words; } -fn read_bus_status(self: *Self) void { - self.bus_status = @bitCast(self.bus.read_int(u32, .bus, Bus.reg.status)); +fn read_bus_status(self: *Self) BusStatus { + return @bitCast(self.bus.read_int(u32, .bus, Bus.reg.status)); } pub fn gpio_enable(self: *Self, pin: u2) void { diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 1c76a4c86..b766509d5 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -96,6 +96,7 @@ pub fn build(b: *std.Build) void { .{ .name = "font8x8", .module = font8x8_dep.module("font8x8") }, } }, .{ .name = "net-pong", .file = "src/net/pong.zig" }, + .{ .name = "net-irq", .file = "src/net/irq.zig" }, .{ .name = "net-scan", .file = "src/net/scan.zig" }, .{ .name = "net-udp", .file = "src/net/udp.zig" }, .{ .name = "net-tcp_client", .file = "src/net/tcp_client.zig" }, diff --git a/examples/raspberrypi/rp2xxx/src/net/readme.md b/examples/raspberrypi/rp2xxx/src/net/readme.md index 64fc8de34..90b92c652 100644 --- a/examples/raspberrypi/rp2xxx/src/net/readme.md +++ b/examples/raspberrypi/rp2xxx/src/net/readme.md @@ -100,3 +100,8 @@ 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. + +## irq.zig + +Similar to simple pong example but in interrupt mode. Link layer (cyw43) interrupts when there is available data packet to read. +Now when `nic.poll` is called only when there is data packet available there is need to call `nic.tick` periodically to run lwip internal timers. diff --git a/modules/network/link/link.zig b/modules/network/link/link.zig index 726b16e3b..422ad05a1 100644 --- a/modules/network/link/link.zig +++ b/modules/network/link/link.zig @@ -29,6 +29,7 @@ pub const RecvResponse = struct { head: usize = 0, len: usize = 0, link_state: LinkState = .up, + next_packet_available: ?bool = null, }; pub const Error = error{ diff --git a/modules/network/src/root.zig b/modules/network/src/root.zig index 0a3723248..339b4c39f 100644 --- a/modules/network/src/root.zig +++ b/modules/network/src/root.zig @@ -31,6 +31,15 @@ pub const Interface = struct { link: Link, status_flags: u16 = 0, + stat: struct { + poll_count: usize = 0, + tick_count: usize = 0, + recv_packets: usize = 0, + recv_bytes: usize = 0, + send_packets: usize = 0, + send_bytes: usize = 0, + } = .{}, + pub const Options = struct { fixed: ?Fixed = null, @@ -78,15 +87,15 @@ pub const Interface = struct { netif.ip6_autoconfig_enabled = 1; lwip.netif_set_default(netif); lwip.netif_set_up(netif); + lwip.netif_set_status_callback(netif, c_on_netif_status); + lwip.netif_set_link_callback(netif, c_on_netif_status); if (opt.fixed == null) { lwip.dhcp_set_struct(netif, &self.dhcp); try c_err(lwip.dhcp_start(netif)); } - lwip.netif_set_link_up(netif); - lwip.netif_set_status_callback(netif, c_on_netif_status); - lwip.netif_set_link_callback(netif, c_on_netif_status); } + // synchronously called from init (during netif_add) 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; @@ -106,7 +115,7 @@ pub const Interface = struct { const new_flags: u16 = (if (self.ready()) @as(u16, 1) else 0) << 8 | netif.flags; if (self.status_flags == new_flags) return; self.status_flags = new_flags; - log.debug("netif status callback is_link_up: {}, is_up: {}, ready: {}, ip: {s}", .{ + log.debug("netif status callback is_link_up: {:<5} is_up: {:<5} ready: {:<5} ip: {s}", .{ netif.flags & lwip.NETIF_FLAG_LINK_UP > 0, netif.flags & lwip.NETIF_FLAG_UP > 0, self.ready(), @@ -142,6 +151,8 @@ pub const Interface = struct { else => lwip.ERR_ARG, }; }; + self.stat.send_packets +%= 1; + self.stat.send_bytes +%= pbuf.len; return lwip.ERR_OK; } @@ -152,11 +163,19 @@ pub const Interface = struct { (netif.ip_addr.u_addr.ip4.addr != 0 or netif.ip_addr.u_addr.ip6.addr[0] != 0); } + /// Must be called periodically if poll is not called (e.g when using rx + /// interrupt). Good rule of thumb seems to be every 10-50 ms. + pub fn tick(self: *Self) void { + self.stat.tick_count +%= 1; + lwip.sys_check_timeouts(); + } + + /// Poll underlying link layer for data packet. pub fn poll(self: *Self) !void { + self.stat.poll_count +%= 1; lwip.sys_check_timeouts(); const netif = &self.netif; - var packets: usize = 0; - while (true) : (packets += 1) { + while (true) { // get packet buffer of the max size const pbuf: *lwip.pbuf = lwip.pbuf_alloc( lwip.PBUF_RAW, @@ -193,9 +212,10 @@ pub const Interface = struct { pbuf.tot_len = @intCast(rsp.len); // pass data to the lwip input function try c_err(netif.input.?(pbuf, netif)); - } - if (packets > 0) { - lwip.sys_check_timeouts(); + self.stat.recv_packets +%= 1; + self.stat.recv_bytes +%= rsp.len; + + if (rsp.next_packet_available) |next_packet_available| if (!next_packet_available) break; } } diff --git a/port/raspberrypi/rp2xxx/src/hal/cyw43439_pio_spi.zig b/port/raspberrypi/rp2xxx/src/hal/cyw43439_pio_spi.zig index f3e99a47f..199a11a88 100644 --- a/port/raspberrypi/rp2xxx/src/hal/cyw43439_pio_spi.zig +++ b/port/raspberrypi/rp2xxx/src/hal/cyw43439_pio_spi.zig @@ -27,10 +27,6 @@ const cyw43spi_program = blk: { \\in pins, 1 side 1 \\jmp y-- lp2 side 0 \\ - \\; wait for event and irq host - \\wait 1 pin 0 side 0 - \\irq 0 side 0 - \\ \\.wrap , .{}).get_program_by_name("cyw43spi"); }; @@ -71,7 +67,7 @@ pub fn init(config: Config) !Self { // IO pin setup pio.gpio_init(pins.io); pins.io.set_output_disabled(false); - pins.io.set_pull(.disabled); + pins.io.set_pull(.down); pins.io.set_schmitt_trigger_enabled(true); try pio.set_input_sync_bypass(pins.io); pins.io.set_drive_strength(.@"12mA"); @@ -123,8 +119,7 @@ pub fn init(config: Config) !Self { }; } -pub fn read(ptr: *anyopaque, words: []u32) void { - const self: *Self = @ptrCast(@alignCast(ptr)); +pub fn read(self: *Self, words: []u32) void { self.pins.cs.put(0); defer self.pins.cs.put(1); @@ -138,8 +133,7 @@ pub fn read(ptr: *anyopaque, words: []u32) void { // By default it sends status after each read/write. // ref: datasheet 'Table 6. gSPI Registers' status enable has default 1 -pub fn write(ptr: *anyopaque, words: []u32) void { - const self: *Self = @ptrCast(@alignCast(ptr)); +pub fn write(self: *Self, words: []u32) void { self.pins.cs.put(0); defer self.pins.cs.put(1); diff --git a/port/raspberrypi/rp2xxx/src/hal/drivers.zig b/port/raspberrypi/rp2xxx/src/hal/drivers.zig index 1a162e45c..36ecfc895 100644 --- a/port/raspberrypi/rp2xxx/src/hal/drivers.zig +++ b/port/raspberrypi/rp2xxx/src/hal/drivers.zig @@ -515,7 +515,10 @@ pub const WiFi = struct { pub const ready = Chip.ready; const Spi = @import("cyw43439_pio_spi.zig"); - pub const Options = Chip.InitOptions; + pub const Options = struct { + enable_irq: bool = false, + country: Chip.InitOptions.Country = .{}, + }; spi: Spi = undefined, chip: Chip = .{}, // cyw43 chip interface @@ -524,15 +527,81 @@ pub const WiFi = struct { self.spi = try Spi.init(.{}); try self.chip.init( .{ - .ptr = &self.spi, + .ptr = self, .vtable = &.{ - .read = Spi.read, - .write = Spi.write, + .read = Self.read, + .write = Self.write, + .irq_cleared = if (opt.enable_irq) Self.irq_cleared else null, }, }, hal.time.sleep_ms, - opt, + .{ .country = opt.country }, ); + if (opt.enable_irq) { + self.set_irq_enabled(true); + } return &self.chip; } + + fn write(ptr: *anyopaque, words: []u32) void { + const self: *Self = @ptrCast(@alignCast(ptr)); + + // Disable interrupts during spi communication + const enabled = self.is_irq_enabled(); + if (enabled) self.set_irq_enabled(false); + defer if (enabled) self.set_irq_enabled(true); + + self.spi.write(words); + } + + fn read(ptr: *anyopaque, words: []u32) void { + const self: *Self = @ptrCast(@alignCast(ptr)); + + // Disable interrupts during spi communication + const enabled = self.is_irq_enabled(); + if (enabled) self.set_irq_enabled(false); + defer if (enabled) self.set_irq_enabled(true); + + self.spi.read(words); + } + + fn irq_cleared(ptr: *anyopaque) void { + const self: *Self = @ptrCast(@alignCast(ptr)); + self.set_irq_enabled(true); + } + + fn set_irq_enabled(self: *Self, enable: bool) void { + self.spi.pins.io.set_irq_enabled(.{ .high = 1 }, enable); + } + + // Returns true if this is source of interrupt + pub fn disable_irq_if(self: *Self, trg: hal.gpio.IrqTrigger) bool { + if (trg.pin == self.spi.pins.io and trg.events.high == 1) { + self.set_irq_enabled(false); + return true; + } + return false; + } + + pub fn disable_irq(self: *Self) void { + self.set_irq_enabled(false); + } + + fn is_irq_enabled(self: *Self) bool { + const events = irq_enabled_events(self.spi.pins.io); + return events.high == 1; + } + + fn irq_enabled_events(pin: hal.gpio.Pin) hal.gpio.IrqEvents { + const core_num = microzig.hal.get_cpu_id(); + const IO_BANK0 = microzig.chip.peripherals.IO_BANK0; + const ints_base: [*]volatile u32 = switch (core_num) { + 0 => @ptrCast(&IO_BANK0.PROC0_INTE0), + else => @ptrCast(&IO_BANK0.PROC1_INTE0), + }; + const pin_num = @intFromEnum(pin); + const bits: u4 = @truncate(ints_base[pin_num >> 3] & 0xF); + const events: hal.gpio.IrqEvents = @bitCast(bits); + return events; + } }; From d70cd6a0a90b536482e88dfaf14cfcfa2a3180c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Tue, 27 Jan 2026 20:04:54 +0100 Subject: [PATCH 15/19] fix returning pointer to local variable vtable is `*const Vtable` making it runtime known makes that local variable. Making it comptime known const. --- drivers/wireless/cyw43439/bus.zig | 6 ++---- port/raspberrypi/rp2xxx/src/hal/drivers.zig | 10 +++++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/wireless/cyw43439/bus.zig b/drivers/wireless/cyw43439/bus.zig index a9c0924ee..474448d20 100644 --- a/drivers/wireless/cyw43439/bus.zig +++ b/drivers/wireless/cyw43439/bus.zig @@ -20,15 +20,13 @@ pub const Spi = struct { } pub fn irq_cleared(self: *@This()) void { - if (self.vtable.irq_cleared) |handler| { - handler(self.ptr); - } + self.vtable.irq_cleared(self.ptr); } pub const VTable = struct { read: *const fn (*anyopaque, []u32) void, write: *const fn (*anyopaque, []u32) void, - irq_cleared: ?*const fn (*anyopaque) void = null, + irq_cleared: *const fn (*anyopaque) void, }; }; diff --git a/port/raspberrypi/rp2xxx/src/hal/drivers.zig b/port/raspberrypi/rp2xxx/src/hal/drivers.zig index 36ecfc895..4400af0d6 100644 --- a/port/raspberrypi/rp2xxx/src/hal/drivers.zig +++ b/port/raspberrypi/rp2xxx/src/hal/drivers.zig @@ -516,28 +516,30 @@ pub const WiFi = struct { const Spi = @import("cyw43439_pio_spi.zig"); pub const Options = struct { - enable_irq: bool = false, + handle_irq: bool = false, country: Chip.InitOptions.Country = .{}, }; spi: Spi = undefined, chip: Chip = .{}, // cyw43 chip interface + handle_irq: bool = false, pub fn init(self: *Self, opt: Options) !*Chip { self.spi = try Spi.init(.{}); + self.handle_irq = opt.handle_irq; try self.chip.init( .{ .ptr = self, .vtable = &.{ .read = Self.read, .write = Self.write, - .irq_cleared = if (opt.enable_irq) Self.irq_cleared else null, + .irq_cleared = Self.irq_cleared, }, }, hal.time.sleep_ms, .{ .country = opt.country }, ); - if (opt.enable_irq) { + if (opt.handle_irq) { self.set_irq_enabled(true); } return &self.chip; @@ -567,6 +569,7 @@ pub const WiFi = struct { fn irq_cleared(ptr: *anyopaque) void { const self: *Self = @ptrCast(@alignCast(ptr)); + if (!self.handle_irq) return; self.set_irq_enabled(true); } @@ -588,6 +591,7 @@ pub const WiFi = struct { } fn is_irq_enabled(self: *Self) bool { + if (!self.handle_irq) return false; const events = irq_enabled_events(self.spi.pins.io); return events.high == 1; } From c32e8acaab5ec885618a6a38ceb555fecb6d13be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Tue, 27 Jan 2026 20:13:14 +0100 Subject: [PATCH 16/19] fix network module build Partially reverting changes from #861. Macros set on net module are not visible when building lwip. For example setting `net_mod.addCMacro("MEM_ALIGNMENT", "4");` gets lwip build with default 1 of MEM_ALIGNMENT. Changed to setting that on lwip module instead of net module. Later I also copied that to the net module because it is needed when using cImport in net package. Without that it also gets default if I check `lwip.MEM_ALIGNMENT` somewhere in src/root.zig. --- modules/network/build.zig | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/network/build.zig b/modules/network/build.zig index 77e4594b6..4b1bf9100 100644 --- a/modules/network/build.zig +++ b/modules/network/build.zig @@ -64,15 +64,22 @@ pub fn build(b: *std.Build) void { lwip_lib.root_module.linkLibrary(foundation_dep.artifact("foundation")); net_mod.linkLibrary(lwip_lib); + const lwip_mod = lwip_lib.root_module; // MEM_ALIGNMENT of 4 bytes allows us to use packet buffers in u32 dma. - net_mod.addCMacro("MEM_ALIGNMENT", "4"); - net_mod.addCMacro("MEM_SIZE", to_s(&buf, mem_size)); - net_mod.addCMacro("PBUF_POOL_SIZE", to_s(&buf, pbuf_pool_size)); - net_mod.addCMacro("PBUF_LINK_HLEN", to_s(&buf, ethernet_header)); - net_mod.addCMacro("PBUF_POOL_BUFSIZE", to_s(&buf, pbuf_length)); - net_mod.addCMacro("PBUF_LINK_ENCAPSULATION_HLEN", to_s(&buf, pbuf_header_length)); + lwip_mod.addCMacro("MEM_ALIGNMENT", "4"); + lwip_mod.addCMacro("MEM_SIZE", to_s(&buf, mem_size)); + lwip_mod.addCMacro("PBUF_POOL_SIZE", to_s(&buf, pbuf_pool_size)); + lwip_mod.addCMacro("PBUF_LINK_HLEN", to_s(&buf, ethernet_header)); + lwip_mod.addCMacro("PBUF_POOL_BUFSIZE", to_s(&buf, pbuf_length)); + lwip_mod.addCMacro("PBUF_LINK_ENCAPSULATION_HLEN", to_s(&buf, pbuf_header_length)); // 40 bytes IPv6 header, 20 bytes TCP header - net_mod.addCMacro("TCP_MSS", to_s(&buf, mtu - 40 - 20)); + lwip_mod.addCMacro("TCP_MSS", to_s(&buf, mtu - 40 - 20)); + + // Copy macros from lwip to net, so we have same values when calling + // translate-c from cImport. + for (lwip_mod.c_macros.items) |m| { + net_mod.c_macros.append(b.allocator, m) catch @panic("out of memory"); + } const options = b.addOptions(); options.addOption(u16, "mtu", mtu); From f44e0c1c511ea1208528c22c6cb680f4ad909956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Tue, 27 Jan 2026 20:32:58 +0100 Subject: [PATCH 17/19] fix naming Suggested by lint results. --- drivers/wireless/cyw43439/ioctl.zig | 68 ++++++++++++++--------------- drivers/wireless/cyw43439/wifi.zig | 11 +++-- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/drivers/wireless/cyw43439/ioctl.zig b/drivers/wireless/cyw43439/ioctl.zig index 782530609..514aae773 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -215,16 +215,16 @@ pub const Response = struct { } const res_buf = buf[@sizeOf(EventPacket)..]; @memcpy(std.mem.asBytes(&res), res_buf[0..@sizeOf(EventScanResult)]); - res.info.channel &= 0xff; - if (res_buf.len < res.info.ie_offset + res.info.ie_length) { + res.channel &= 0xff; + if (res_buf.len < res.ie_offset + res.ie_length) { return error.Cyw43InsufficientData; } // ref: https://github.com/georgerobotics/cyw43-driver/blob/13004039ffe127519f33824bf7d240e1f23fbdcd/src/cyw43_ll.c#L538 - const is_open = res.info.capability & 0x0010 == 0; + const is_open = res.capability & 0x0010 == 0; if (!is_open) sec.wep_psk = true; - var ie_buf = res_buf[res.info.ie_offset..][0..res.info.ie_length]; + var ie_buf = res_buf[res.ie_offset..][0..res.ie_length]; while (ie_buf.len >= 2) { const typ = ie_buf[0]; const len = ie_buf[1]; @@ -409,7 +409,7 @@ const EventPacket = extern struct { // Escan result event (excluding 12-byte IOCTL header and BDC header) pub const EventScanResult = extern struct { - // Scan result header (part of wl_escan_result_t) + // Scan result header const Header = extern struct { buflen: u32, version: u32, @@ -417,38 +417,34 @@ pub const EventScanResult = extern struct { bss_count: u16, }; - // BSS info from EScan (part of wl_bss_info_t) - const BssInfo = extern struct { - version: u32, // version field - length: u32, // byte length of data in this record, starting at version and including IEs - bssid: [6]u8, // The MAC address of the Access Point (AP) - beacon_period: u16, // Interval between two consecutive beacon frames. Units are Kusec - capability: u16, // Capability information - ssid_len: u8, // SSID length - ssid: [32]u8, // Array to store SSID - nrates: u32, // Count of rates in this set - rates: [16]u8, // rates in 500kbps units, higher bit set if basic - channel: u16, // Channel specification for basic service set - atim_window: u16, // Announcement traffic indication message window size. Units are Kusec - dtim_period: u8, // Delivery traffic indication message period - rssi: u16, // receive signal strength (in dBm) - phy_noise: u8, // noise (in dBm) - // The following fields assume the 'version' field is 109 (0x6D) - n_cap: u8, // BSS is 802.11N Capable - nbss_cap: u32, // 802.11N BSS Capabilities (based on HT_CAP_*) - ctl_ch: u8, // 802.11N BSS control channel number - reserved1: u32, // Reserved for expansion of BSS properties - flags: u8, // flags - reserved2: [3]u8, // Reserved for expansion of BSS properties - basic_mcs: [16]u8, // 802.11N BSS required MCS set - ie_offset: u16, // offset at which IEs start, from beginning - ie_length: u32, // byte length of Information Elements - snr: u16, // Average SNR(signal to noise ratio) during frame reception - // Variable-length Information Elements follow, see cyw43_ll_wifi_parse_scan_result - }; - hdr: Header, - info: BssInfo, + + version: u32, // version field + length: u32, // byte length of data in this record, starting at version and including IEs + bssid: [6]u8, // The MAC address of the Access Point (AP) + beacon_period: u16, // Interval between two consecutive beacon frames. Units are Kusec + capability: u16, // Capability information + ssid_len: u8, // SSID length + ssid: [32]u8, // Array to store SSID + nrates: u32, // Count of rates in this set + rates: [16]u8, // rates in 500kbps units, higher bit set if basic + channel: u16, // Channel specification for basic service set + atim_window: u16, // Announcement traffic indication message window size. Units are Kusec + dtim_period: u8, // Delivery traffic indication message period + rssi: u16, // receive signal strength (in dBm) + phy_noise: u8, // noise (in dBm) + // The following fields assume the 'version' field is 109 (0x6D) + n_cap: u8, // BSS is 802.11N Capable + nbss_cap: u32, // 802.11N BSS Capabilities (based on HT_CAP_*) + ctl_ch: u8, // 802.11N BSS control channel number + reserved1: u32, // Reserved for expansion of BSS properties + flags: u8, // flags + reserved2: [3]u8, // Reserved for expansion of BSS properties + basic_mcs: [16]u8, // 802.11N BSS required MCS set + ie_offset: u16, // offset at which IEs start, from beginning + ie_length: u32, // byte length of Information Elements + snr: u16, // Average SNR(signal to noise ratio) during frame reception + // Variable-length Information Elements follow, see cyw43_ll_wifi_parse_scan_result }; // Ethernet header (sdpcm_ethernet_header_t) diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 9188bdfa6..73aeb0f08 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -593,15 +593,14 @@ fn handle_scan_event(self: *Self, rsp: ioctl.Response) void { log.err("fail to parse event scan result {}", .{err}); return; }; - if (res.info.ssid_len == 0) return; // skip zero length ssid - const info = &res.info; + if (res.ssid_len == 0) return; // skip zero length ssid self.scan_result = .{}; const sr = &self.scan_result.?; - sr.ssid_buf = info.ssid; - sr.ap_mac = info.bssid; - sr.ssid = sr.ssid_buf[0..info.ssid_len]; + sr.ssid_buf = res.ssid; + sr.ap_mac = res.bssid; + sr.ssid = sr.ssid_buf[0..res.ssid_len]; sr.security = security; - sr.channel = info.channel; + sr.channel = res.channel; } const ScanParams = extern struct { From 46203a4823722d5ff974156a0e78983d19eaa9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Wed, 28 Jan 2026 12:16:07 +0100 Subject: [PATCH 18/19] improve error log line This logged "unexpected command repose" when command failed. But failure is wrong configuration. Unexpected here means getting command response when no command is sent. --- drivers/wireless/cyw43439/wifi.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/wireless/cyw43439/wifi.zig b/drivers/wireless/cyw43439/wifi.zig index 73aeb0f08..2a029a137 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -349,7 +349,7 @@ fn response_poll(self: *Self, cmd: ioctl.Cmd, data: ?[]u8) !usize { } return 0; } - self.log_response(rsp); + log.err("command {} failed with status {}", .{ cdc.cmd, cdc.status }); return error.Cyw43InvalidCommandStatus; } }, From 8115df4461b5e6cfbde8e92dc16a894a34c5f9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Wed, 28 Jan 2026 12:40:05 +0100 Subject: [PATCH 19/19] add missing file --- examples/raspberrypi/rp2xxx/src/net/irq.zig | 126 ++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 examples/raspberrypi/rp2xxx/src/net/irq.zig diff --git a/examples/raspberrypi/rp2xxx/src/net/irq.zig b/examples/raspberrypi/rp2xxx/src/net/irq.zig new file mode 100644 index 000000000..ecc135764 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/net/irq.zig @@ -0,0 +1,126 @@ +/// Interrupt line is multiplexed with MOSI and MISO SPI lines, so it requires a +/// little special handling. +/// +/// Interrupt is disabled during SPI read/write. It should be disabled in the +/// callback handler to prevent storm of interrputs. That is done in +/// `is_packet_ready_interrupt`. Reading packet data will re-enable intterrupt +/// when the last packet is read. +/// +const std = @import("std"); +const microzig = @import("microzig"); +const cpu = microzig.cpu; +const rp2xxx = microzig.hal; +const time = rp2xxx.time; +const gpio = rp2xxx.gpio; +const pio = rp2xxx.pio; +const drivers = rp2xxx.drivers; +const system_timer = rp2xxx.system_timer; +const chip = rp2xxx.compatibility.chip; + +const uart = rp2xxx.uart.instance.num(0); +const uart_tx_pin = gpio.num(0); + +pub const rp2040_options: microzig.Options = .{ + .log_level = .debug, + .logFn = rp2xxx.uart.log, + .interrupts = .{ + .IO_IRQ_BANK0 = .{ .c = gpio_interrupt }, + .TIMER_IRQ_0 = .{ .c = timer_interrupt }, + }, +}; +pub const rp2350_options: microzig.Options = .{ + .log_level = .debug, + .logFn = rp2xxx.uart.log, + .interrupts = .{ + .IO_IRQ_BANK0 = .{ .c = gpio_interrupt }, + .TIMER0_IRQ_0 = .{ .c = timer_interrupt }, + }, +}; +const timer_irq = if (chip == .RP2040) .TIMER_IRQ_0 else .TIMER0_IRQ_0; +pub const microzig_options = if (chip == .RP2040) rp2040_options else rp2350_options; +const timer = system_timer.num(0); + +const log = std.log.scoped(.main); + +comptime { + _ = @import("lwip_exports.zig"); +} +const net = @import("net"); +const secrets = @import("secrets.zig"); + +var wifi_driver: drivers.WiFi = .{}; + +pub fn main() !void { + uart_tx_pin.set_function(.uart); + uart.apply(.{ + .clock_config = rp2xxx.clock_config, + }); + rp2xxx.uart.init_logger(uart); + + // Enable gpio interrupt callback + microzig.interrupt.enable(.IO_IRQ_BANK0); + // Enable timer interrupt callback + microzig.cpu.interrupt.enable(timer_irq); + timer.set_interrupt_enabled(.alarm0, true); + + // Init wifi_driver with interrupt handling enabled + var wifi = try wifi_driver.init(.{ .handle_irq = true }); + _ = try wifi.join(secrets.ssid, secrets.pwd, secrets.join_opt); + var led = wifi.gpio(0); + + var nic: net.Interface = .{ .link = wifi.link() }; + try nic.init(wifi.mac, try secrets.nic_options()); + + timer.schedule_alarm(.alarm0, timer.read_low() +% tick_interval_ms * 1000); + while (true) { + // get and reset wakeup source + const src = wakeup_source; + wakeup_source = .{}; + + if (src.wifi) { + // Interrupt will be enabled after last packet is read + try nic.poll(); + log.debug("{}", .{nic.stat}); + } + if (src.tick) { + nic.tick(); + if (nic.stat.tick_count % 5 == 0) { + led.toggle(); + } + } + cpu.wfe(); + } +} + +const tick_interval_ms = 50; + +var wakeup_source: packed struct { + wifi: bool = false, + tick: bool = false, +} = .{}; + +fn gpio_interrupt() linksection(".ram_text") callconv(.c) void { + // Disable interrupts storm, store source and wake up main loop. + wifi_driver.disable_irq(); + wakeup_source.wifi = true; + cpu.sev(); + + // // If there are multiple triggers use `disable_irq_if` in the triggers + // // iterator. It will disable interrupt wifi_driver is the source of + // // interrupt, returns true in that case otherwise false. + // var iter = gpio.IrqEventIter{}; + // while (iter.next()) |trg| { + // if (wifi_driver.disable_irq_if(trg)) { + // wakeup_source.packet_ready = true; + // cpu.sev(); + // return; + // } + // } +} + +fn timer_interrupt() linksection(".ram_text") callconv(.c) void { + wakeup_source.tick = true; + cpu.sev(); + timer.clear_interrupt(.alarm0); + timer.schedule_alarm(.alarm0, timer.read_low() +% tick_interval_ms * 1000); +}