diff --git a/drivers/wireless/cyw43439.zig b/drivers/wireless/cyw43439.zig index f539fd6b6..555572a16 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,52 +20,71 @@ 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 join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { - try self.wifi.join(ssid, pwd, opt); +/// 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); } -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}); - } +/// 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); } -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 join_state(self: *Self) WiFi.JoinState { + return self.wifi.join_state; +} + +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(); } -pub fn recv_zc(ptr: *anyopaque, bytes: []u8) anyerror!?struct { usize, usize } { +pub fn scan_result(self: *Self) ?WiFi.ScanResult { + return self.wifi.scan_result; +} + +/// 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, 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, + }; } -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)); - 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(buffer) catch |err| { + log.err("cyw43 send {}", .{err}); + return error.InternalError; }; } @@ -84,6 +104,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/bus.zig b/drivers/wireless/cyw43439/bus.zig index 899b42ada..474448d20 100644 --- a/drivers/wireless/cyw43439/bus.zig +++ b/drivers/wireless/cyw43439/bus.zig @@ -19,9 +19,14 @@ pub const Spi = struct { self.vtable.write(self.ptr, buffer); } + pub fn irq_cleared(self: *@This()) void { + 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, }; }; @@ -68,7 +73,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 +359,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 e338d860c..514aae773 100644 --- a/drivers/wireless/cyw43439/ioctl.zig +++ b/drivers/wireless/cyw43439/ioctl.zig @@ -92,10 +92,12 @@ 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, set_wpa_auth = 165, + set_scan_channel_time = 185, get_var = 262, set_var = 263, set_wsec_pmk = 268, @@ -184,7 +186,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)) { @@ -195,8 +197,64 @@ 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; } + + 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)]); + 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.capability & 0x0010 == 0; + if (!is_open) sec.wep_psk = true; + + 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]; + 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 >= 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 <= len) break; + ie_buf = ie_buf[len..]; + } + + return .{ res, sec }; + } +}; + +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,12 +408,43 @@ 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, - info: BssInfo, +pub const EventScanResult = extern struct { + // Scan result header + const Header = extern struct { + buflen: u32, + version: u32, + sync_id: u16, + bss_count: u16, + }; + + hdr: Header, + + 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) @@ -375,7 +464,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, @@ -389,48 +478,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 +632,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 +776,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 6f86e3f11..2a029a137 100644 --- a/drivers/wireless/cyw43439/wifi.zig +++ b/drivers/wireless/cyw43439/wifi.zig @@ -13,16 +13,31 @@ bus: *Bus, request_id: u16 = 0, credit: u8 = 0, tx_sequence: u8 = 0, -status: ?Status = 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; -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 - { + { // 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); @@ -38,8 +53,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); @@ -50,8 +65,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; @@ -61,43 +76,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: BusStatus = @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 { @@ -121,9 +139,93 @@ 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 + 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_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 + // try self.set_cmd(.set_pm, &.{2});// power mode + } + 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? @@ -229,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; @@ -247,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; } }, @@ -260,7 +362,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, @@ -268,79 +370,11 @@ pub const JoinOptions = struct { wpa2_psk, wpa3_sae, }; - - // ref: https://github.com/embassy-rs/embassy/blob/96a026c73bad2ebb8dfc78e88c9690611bf2cb97/cyw43/src/structs.rs#L371 - pub const Country = struct { - code: [2]u8 = "XX".*, // Worldwide - revision: i32 = -1, - }; }; -pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !void { - const bus = self.bus; +pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !JoinPoller { + self.err = null; - // 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); - } - // 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); - } var buf: [64]u8 = @splat(0); // space for 10 addresses // Enable multicast { @@ -349,124 +383,306 @@ pub fn join(self: *Self, ssid: []const u8, pwd: []const u8, opt: JoinOptions) !v 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 }); - }, + 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_ssid, ioctl.encode_ssid(&buf, ssid)); + + if (opt.security == .open) { + self.event_log.psk_sup = .success; + } + + self.join_state = .joining; + return .{ .parent = self }; +} + +pub fn connected(self: Self) bool { + return self.join_state == .joined; +} + +pub const JoinPoller = struct { + parent: *Self, + + // Typical events flow: + // + // 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 + // + // 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 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 + // + pub fn poll(jp: *JoinPoller) !bool { + if (jp.parent.join_state == .joining) { + try jp.parent.poll(); + try jp.parent.event_log.err(); } - self.sleep_ms(2); + return jp.parent.join_state == .joining; + } - 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)); - }, + pub fn wait(jp: *JoinPoller, wait_ms: u32) !void { + var delay: u32 = 0; + while (delay < wait_ms and try jp.poll()) { + jp.parent.sleep_ms(ioctl.response_poll_interval); + delay += ioctl.response_poll_interval; } - 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 }, - }); + if (jp.parent.join_state == .joined) return; + return error.Cyw43JoinTimeout; + } +}; - try self.set_cmd(.set_ssid, ioctl.encode_ssid(&buf, ssid)); +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 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 => { + event_log.link = state(evt.status == .success and evt.flags & 1 > 0); + }, + .psk_sup => { + event_log.psk_sup = + state(evt.status == .success or evt.status == .unsolicited); + }, + .assoc, .reassoc => { + event_log.assoc = state(evt.status == .success); + }, + .disassoc_ind, .disassoc => { + event_log.assoc = .fail; + }, + .auth => { + event_log.auth = state(evt.status == .success); + }, + .deauth_ind => { + event_log.auth = .fail; + }, + .set_ssid => { + event_log.set_ssid = state(evt.status == .success); + }, + .join => { + event_log.join = state(evt.status == .success); + }, + .escan_result => { + self.scan_result = null; + self.scan_state = switch (evt.status) { + .success, .newassoc, .ccxfastrm => .success, + .partial, .newscan => .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 = 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}); } + if (self.join_state == .joining and new_state == .disjoined) { + event_log.err() catch |err| { + self.err = err; + }; + } + self.join_state = new_state; +} - try self.join_wait(30 * 1000, opt.security); +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.ssid_len == 0) return; // skip zero length ssid + self.scan_result = .{}; + const sr = &self.scan_result.?; + sr.ssid_buf = res.ssid; + sr.ap_mac = res.bssid; + sr.ssid = sr.ssid_buf[0..res.ssid_len]; + sr.security = security; + sr.channel = res.channel; } -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; +const ScanParams = extern struct { + version: u32 = 1, + action: u16 = 1, + sync_id: u16 = 0x1, - 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; - }; - 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; - }, - .psk_sup => { - if (evt.status != .unsolicited) return error.Cyw43JoinWpaHandshake; - 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; - set_ssid = true; - }, - else => {}, - } - if (set_ssid and link_up and link_auth) { - return; - } - }, - else => {}, + // ssid to scan for, 0 - all + ssid_len: u32 = 0, + ssid: [32]u8 = @splat(0), + + 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, + + channel_num: u32 = 0, + channel_list: [1]u16 = @splat(0), +}; + +/// 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 = .{}; + try self.set_var("escan", mem.asBytes(¶ms)); + self.scan_state = .running; + + return .{ + .parent = self, + }; +} + +pub const ScanPoller = struct { + parent: *Self, + + /// Buffer for last x seen ssid's. Used for de-duplication of scan results. + seen_buf: [8][6]u8 = undefined, + seen_idx: usize = 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| { ... + pub fn poll(sp: *ScanPoller) !bool { + if (sp.parent.scan_state == .running) { + try sp.parent.poll(); } + return sp.parent.scan_state == .running; } - return error.Cyw43JoinTimeout; -} + + 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_buf.len)) |i| { + if (std.mem.eql(u8, &sp.seen_buf[i], &res.ap_mac)) { + return null; + } + } + // Store in seen list + sp.seen_buf[sp.seen_idx % sp.seen_buf.len] = res.ap_mac; + sp.seen_idx +%= 1; + return res; + } + + 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().msg; + const evt = (rsp.event() orelse return).msg; if (evt.event_type == .none and evt.status == .success) return; log.debug( @@ -481,6 +697,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()}); @@ -489,7 +706,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); } @@ -506,12 +723,15 @@ 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 null; + 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), } } @@ -529,8 +749,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; @@ -547,24 +765,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.status == null) self.read_status(); - const status = self.status.?; - self.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.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; } @@ -576,8 +798,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) BusStatus { + return @bitCast(self.bus.read_int(u32, .bus, Bus.reg.status)); } pub fn gpio_enable(self: *Self, pin: u2) void { @@ -585,9 +807,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 @@ -598,8 +826,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. @@ -612,6 +861,89 @@ const Status = packed struct { _reserved3: u12, }; +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), +}; + +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, + + fn from(ok: bool) EventState { + return if (ok) .success else .fail; + } + }; + + // essential join events + auth: EventState = .none, + link: EventState = .none, + psk_sup: EventState = .none, + set_ssid: EventState = .none, + // other join events + assoc: EventState = .none, + join: EventState = .none, + + 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: 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 .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: 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(u12, @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()); +} + // CYW43439 chip values const chip = struct { const wrapper_register_offset: u32 = 0x100000; diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index f21d50379..b766509d5 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -96,6 +96,8 @@ 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" }, .{ .name = "net-tcp_server", .file = "src/net/tcp_server.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); +} 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/readme.md b/examples/raspberrypi/rp2xxx/src/net/readme.md index 3d7dfb6c6..90b92c652 100644 --- a/examples/raspberrypi/rp2xxx/src/net/readme.md +++ b/examples/raspberrypi/rp2xxx/src/net/readme.md @@ -23,6 +23,28 @@ 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 +[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. ## udp.zig @@ -78,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/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); + } +} 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/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); diff --git a/modules/network/link/link.zig b/modules/network/link/link.zig index 29213364b..422ad05a1 100644 --- a/modules/network/link/link.zig +++ b/modules/network/link/link.zig @@ -7,19 +7,31 @@ 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, + next_packet_available: ?bool = null, +}; + pub const Error = error{ /// Packet can't fit into link output buffer OutOfMemory, 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..339b4c39f 100644 --- a/modules/network/src/root.zig +++ b/modules/network/src/root.zig @@ -29,6 +29,16 @@ pub const Interface = struct { netif: lwip.netif = .{}, dhcp: lwip.dhcp = .{}, 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, @@ -75,16 +85,17 @@ 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); + 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); } + // 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; @@ -100,7 +111,11 @@ 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); - log.debug("netif status callback is_link_up: {}, is_up: {}, ready: {}, ip: {s}", .{ + + 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: {:<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(), @@ -130,13 +145,14 @@ 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, else => lwip.ERR_ARG, }; }; + self.stat.send_packets +%= 1; + self.stat.send_bytes +%= pbuf.len; return lwip.ERR_OK; } @@ -147,10 +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(); - var packets: usize = 0; - while (true) : (packets += 1) { + const netif = &self.netif; + while (true) { // get packet buffer of the max size const pbuf: *lwip.pbuf = lwip.pbuf_alloc( lwip.PBUF_RAW, @@ -162,23 +187,35 @@ 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)); - } - if (packets > 0) { - lwip.sys_check_timeouts(); + try c_err(netif.input.?(pbuf, netif)); + self.stat.recv_packets +%= 1; + self.stat.recv_bytes +%= rsp.len; + + if (rsp.next_packet_available) |next_packet_available| if (!next_packet_available) break; } } @@ -343,7 +380,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( 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 913096b77..4400af0d6 100644 --- a/port/raspberrypi/rp2xxx/src/hal/drivers.zig +++ b/port/raspberrypi/rp2xxx/src/hal/drivers.zig @@ -515,23 +515,97 @@ pub const WiFi = struct { pub const ready = Chip.ready; const Spi = @import("cyw43439_pio_spi.zig"); - pub const Config = Spi.Config; + pub const Options = struct { + 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, config: Config) !*Chip { - self.spi = try Spi.init(config); + 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.spi, + .ptr = self, .vtable = &.{ - .read = Spi.read, - .write = Spi.write, + .read = Self.read, + .write = Self.write, + .irq_cleared = Self.irq_cleared, }, }, hal.time.sleep_ms, + .{ .country = opt.country }, ); + if (opt.handle_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)); + if (!self.handle_irq) return; + 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 { + if (!self.handle_irq) return false; + 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; + } };