From 0b57b2395105dd079c09afbbb8a61dc17de2fff7 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Wed, 12 Feb 2025 15:31:08 -0300 Subject: [PATCH 1/3] Add support for Wi-Fi chip drivers From e37a0b905662be936cdde769997162b2bda12db5 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Wed, 12 Feb 2025 15:32:23 -0300 Subject: [PATCH 2/3] Add CYW driver --- mongoose.c | 1198 ++++++++++++++++++++++++++++++++++++++++++++- mongoose.h | 53 ++ src/drivers/cyw.c | 1186 ++++++++++++++++++++++++++++++++++++++++++++ src/drivers/cyw.h | 51 ++ src/net_builtin.h | 1 + src/util.c | 5 + src/util.h | 1 + src/wifi_dummy.c | 3 +- 8 files changed, 2496 insertions(+), 2 deletions(-) create mode 100644 src/drivers/cyw.c create mode 100644 src/drivers/cyw.h diff --git a/mongoose.c b/mongoose.c index c5c38179..bd371e35 100644 --- a/mongoose.c +++ b/mongoose.c @@ -19321,12 +19321,18 @@ uint32_t mg_ntohl(uint32_t net) { return MG_LOAD_BE32(&net); } +void mg_delayms(unsigned int ms) { + uint64_t to = mg_millis() + ms + 1; + while (mg_millis() < to) (void) 0; +} + #ifdef MG_ENABLE_LINES #line 1 "src/wifi_dummy.c" #endif -#if (!defined(MG_ENABLE_DRIVER_PICO_W) || !MG_ENABLE_DRIVER_PICO_W) +#if (!defined(MG_ENABLE_DRIVER_PICO_W) || !MG_ENABLE_DRIVER_PICO_W) && \ + (!defined(MG_ENABLE_DRIVER_CYW) || !MG_ENABLE_DRIVER_CYW) bool mg_wifi_scan(void) { MG_ERROR(("No Wi-Fi driver enabled")); @@ -19776,6 +19782,1196 @@ static size_t cmsis_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) { #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/cyw.c" +#endif + + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW + + +static struct mg_tcpip_if *s_ifp; +static bool s_link, s_auth, s_join; + +static bool cyw_init(struct mg_tcpip_driver_cyw_firmware *fw, uint8_t *mac); +static void cyw_poll(void); + +static bool mg_tcpip_driver_cyw_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + if (MG_BIG_ENDIAN) { + MG_ERROR(("Big-endian host")); + return false; + } + s_ifp = ifp; + s_link = s_auth = s_join = false; + if (!cyw_init(d->fw, ifp->mac)) return false; + + if (d->apmode) { + MG_DEBUG(("Starting AP '%s' (%u)", d->apssid, d->apchannel)); + return mg_wifi_ap_start(d->apssid, d->appass, d->apchannel); + } else if (d->ssid != NULL && d->pass != NULL) { + MG_DEBUG(("Connecting to '%s'", d->ssid)); + return mg_wifi_connect(d->ssid, d->pass); + } + return true; +} + +static size_t mg_cyw_tx(unsigned int ifc, void *data, size_t len); +size_t mg_tcpip_driver_cyw_output(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + return mg_cyw_tx(d->apmode ? 1 : 0, (void *) buf, len) >= len ? len : 0; +} + +static bool mg_tcpip_driver_cyw_poll(struct mg_tcpip_if *ifp, bool s1) { + cyw_poll(); + if (!s1) return false; + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + return d->apmode ? s_link : s_link && s_auth && s_join; +} + +struct mg_tcpip_driver mg_tcpip_driver_cyw = {mg_tcpip_driver_cyw_init, + mg_tcpip_driver_cyw_output, NULL, + mg_tcpip_driver_cyw_poll}; + +// - DS: +// https://www.mouser.com/datasheet/2/196/Infineon_CYW43439_DataSheet_v03_00_EN-3074791.pdf +// - WHD: https://github.com/Infineon/wifi-host-driver +// +// | e <-- event data +// |----- +// net | vnd <-- network (TCP/IP) | vendor header (Broadcom (bcm)) +// -----|----- +// IOCTL | ETH | ETH <-- IOCTL/IOVAR: chip control | ETH: Ethernet header +// -------|-----|----- +// CDC | BDC | BDC +// ------- ----- ----- +// SDPCM <-- includes SDIO bus arbitration, not used in SPI +// ------------------- +// SPI | SDIO <-- padded to 32-bit | 64-bytes +// +// - SDPCM has 3 channels (control, data, and asynchronous data) +// - SPI (and SDIO) has 4 "functions", F0 to F3, to access different +// blocks in the chip, like the SPI/SDIO controller, chip backplane, and 2 DMA +// I/Os; these are usually handled by SDPCM but we need to explicitly access +// the I/O controller and chip backplane during initialization + +// Processor core firmware is loaded to TCM RAM, along with module-dependent +// (hardware design) NVRAM data, via the chip backplane access through the bus +// Once the chip has been initialized, information regarding regulatory +// constraints (CLM blob, “Country Locale Matrix”), is loaded as an IOVAR. This +// is tied to the module being certified, hence it is also module-dependent. +// - Result: chip firmware + module NVRAM data + module CLM blob + +#pragma pack(push, 1) +// all little endian + +struct cdc_hdr { + uint32_t cmd; // ioctl command value + uint16_t olen; // output buflen + uint16_t ilen; // input buflen (excludes header) + uint32_t flags; + uint32_t status; +}; + +struct bdc_hdr { + uint8_t flags; // Flags + uint8_t priority; // 802.1d Priority (low 3 bits) + uint8_t flags2; + uint8_t data_offset; // Offset from end of BDC header to packet data, in + // 4-uint8_t words. Leaves room for optional headers. +}; + +struct sdpcm_sw_hdr { + uint8_t sequence; // Sequence number of pkt + uint8_t channel_and_flags; // IOCTL/IOVAR or User Data or Event + uint8_t next_length; + uint8_t header_length; // Offset to BDC or CDC header + uint8_t wireless_flow_control; + uint8_t bus_data_credit; // Credit from WLAN Chip + uint8_t _reserved[2]; +}; + +struct sdpcm_hdr { + uint16_t len; + uint16_t _len; // ~len + struct sdpcm_sw_hdr sw_hdr; +}; + +struct data_hdr { + struct sdpcm_hdr sdpcm; + uint8_t pad[2]; + struct bdc_hdr bdc; +}; + +// gSPI, DS 4.2.1 Fig.12, 2-bit field +#define CYW_SD_FUNC_BUS 0 // F0 +#define CYW_SD_FUNC_CHIP 1 // F1 +#define CYW_SD_FUNC_WLAN 2 // F2 + +#define CYW_SDPCM_CTRL_HDR 0 +#define CYW_SDPCM_ASYNC_HDR 1 +#define CYW_SDPCM_DATA_HDR 2 + +#pragma pack(pop) + +static uint8_t s_tx_seqno; +static uint32_t txdata[2048 / 4], resp[2048 / 4]; + +static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len); +static void cyw_handle_bdc(struct bdc_hdr *bdc, size_t len); +static void cyw_handle_bdc_evnt(struct bdc_hdr *bdc, size_t len); + +static size_t cyw_spi_poll(uint8_t *dest); + +// High-level comm stuff + +static void cyw_poll(void) { + struct sdpcm_hdr *sdpcm = (struct sdpcm_hdr *) resp; + unsigned int channel; + if (cyw_spi_poll((uint8_t *) resp) == 0) return; // BUS DEPENDENCY + if ((sdpcm->len ^ sdpcm->_len) != 0xffff || sdpcm->len < sizeof(*sdpcm) || + sdpcm->len > 2048 - sizeof(*sdpcm)) + return; + channel = sdpcm->sw_hdr.channel_and_flags & 0x0F; + if (channel == CYW_SDPCM_CTRL_HDR) { + if (sdpcm->len >= sizeof(*sdpcm) + sizeof(struct cdc_hdr)) { + struct cdc_hdr *cdc = + (struct cdc_hdr *) ((size_t) sdpcm + sdpcm->sw_hdr.header_length); + size_t len = sdpcm->len - sdpcm->sw_hdr.header_length; + cyw_handle_cdc(cdc, len); + } + } else if (channel == CYW_SDPCM_DATA_HDR) { + if (sdpcm->len >= sizeof(*sdpcm) + sizeof(struct bdc_hdr)) { + struct bdc_hdr *bdc = + (struct bdc_hdr *) ((size_t) sdpcm + sdpcm->sw_hdr.header_length); + size_t len = sdpcm->len - sdpcm->sw_hdr.header_length; + cyw_handle_bdc(bdc, len); + } + } else if (channel == CYW_SDPCM_ASYNC_HDR) { + struct bdc_hdr *bdc = + (struct bdc_hdr *) ((size_t) sdpcm + sdpcm->sw_hdr.header_length); + size_t len_ = sdpcm->len - sdpcm->sw_hdr.header_length; + cyw_handle_bdc_evnt(bdc, len_); + } // else silently discard +} + +// WLAN frame reception +static void cyw_handle_bdc(struct bdc_hdr *bdc, size_t len) { + uint8_t *payload = (uint8_t *) &bdc[bdc->data_offset + 1]; + mg_tcpip_qwrite(payload, len - (payload - (uint8_t *) bdc), s_ifp); +} + +static size_t cyw_bus_tx(uint32_t *data, uint16_t len); + +// WLAN frame transmission +static size_t mg_cyw_tx(unsigned int ifc, void *data, size_t len) { + struct data_hdr *hdr = (struct data_hdr *) txdata; + uint16_t txlen = (uint16_t) (len + sizeof(*hdr)); + memset(txdata, 0, sizeof(*hdr)); + memcpy((uint8_t *) txdata + sizeof(*hdr), data, len); + // TODO(): hdr->bdc.priority = map IP to TOS if supporting QoS/ToS + hdr->bdc.flags = 2 << 4; // BDC version 2 + hdr->bdc.flags2 = ifc; // 0 -> STA, 1 -> AP + // hdr->bdc.data_offset = 0; // actually zeroed above + hdr->sdpcm.len = txlen; + hdr->sdpcm._len = (uint16_t) ~txlen; + hdr->sdpcm.sw_hdr.sequence = ++s_tx_seqno; + hdr->sdpcm.sw_hdr.channel_and_flags = CYW_SDPCM_DATA_HDR, + hdr->sdpcm.sw_hdr.header_length = offsetof(struct data_hdr, bdc); + return cyw_bus_tx(txdata, txlen); +} + +// WLAN event handling +#pragma pack(push, 1) +// all in network order + +struct eth_hdr { // TODO(scaprile) reuse 'eth' in net_builtin.c + uint8_t dest[6]; + uint8_t src[6]; + uint16_t type; +}; + +struct bcm_vendor_hdr { + uint16_t subtype; // vendor specific: 0x8001 + uint16_t length; // bytes following this field + uint8_t version; // 0 + uint8_t oui[3]; // vendor specific: 0x00 0x10 0x18 + uint16_t usr_subtype; +}; + +struct bcm_evnt_hdr { + uint16_t version; // 1: fields up to ifname; 2: as shown + uint16_t flags; + uint32_t event_type; + uint32_t status; + uint32_t reason; + uint32_t auth_type; + uint32_t datalen; + uint8_t addr[6]; // Station address (if applicable) + char ifname[16]; + uint8_t ifidx; + uint8_t bss_cfg_idx; +}; + +struct evnt_msg { + struct eth_hdr eth; + // struct vendor_hdr; but we only handle Broadcom (Wi-Fi processor) events + struct bcm_vendor_hdr bcm; + struct bcm_evnt_hdr event; +}; + +#pragma pack(pop) + +struct scan_result; +static void cyw_handle_scan_result(uint32_t status, struct scan_result *data, + size_t len); + +static void cyw_handle_bdc_evnt(struct bdc_hdr *bdc, size_t len) { + struct evnt_msg *msg = (struct evnt_msg *) &bdc[bdc->data_offset + 1]; + MG_VERBOSE(("%u bytes event", len)); + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump((void *) bdc, len); + if (mg_ntohs(msg->eth.type) != 0x886C || msg->bcm.oui[0] != 0x00 || + msg->bcm.oui[1] != 0x10 || msg->bcm.oui[2] != 0x18) + return; // discard if not Broadcom + if (mg_ntohl(msg->event.datalen) <= + len - ((uint8_t *) msg - (uint8_t *) bdc)) { + uint32_t event_type = mg_ntohl(msg->event.event_type); + uint32_t status = mg_ntohl(msg->event.status); + uint32_t reason = mg_ntohl(msg->event.reason); + uint16_t flags = mg_ntohs(msg->event.flags); + MG_DEBUG(("BCM event %lu %lu %lu %p", event_type, status, reason, flags)); + if (event_type == 16 && status == 0) { // Link + s_link = flags & 1; + } else if (event_type == 46 && s_link) { // PSK sup with link up + if (status == 6) { // Keyed + } else if ((status == 4 || status == 8 || status == 10) && + reason == 15) { // Wait M1/M3/G1 + MG_ERROR(("AUTH TIMEOUT")); + s_auth = false; + } else { + MG_ERROR(("AUTH FAILED")); + s_auth = false; + } + } else if (event_type == 3 && status != 6) { // Auth (not unsolicited) + if (status == 0) { // Success + s_auth = true; + } else { + MG_ERROR(("AUTH TIMEOUT")); + s_auth = false; + } + } else if (event_type == 1) { // Join + if (status == 0) { // Success + s_join = true; + } else { + MG_ERROR(("%s", status == 3 /* No networks */ ? "SSID NOT FOUND" + : "JOIN FAILED")); + s_join = false; + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_CONNECT_ERR, &status); + } + } else if (event_type == 12 || event_type == 5) { // Disassoc, Deauth + s_auth = false; + } else if (event_type == 69) { // Scan result + struct scan_result *data = (struct scan_result *) (&msg->event + 1); + size_t dlen = mg_ntohl(msg->event.datalen); + if (dlen > len - ((uint8_t *) data - (uint8_t *) bdc)) return; + cyw_handle_scan_result(status, data, dlen); + } + } // else silently discard +} + +static bool cyw_ioctl_get_(unsigned int ifc, unsigned int cmd, void *data, + size_t len); +static bool cyw_ioctl_set_(unsigned int ifc, unsigned int cmd, void *data, + size_t len); +static bool cyw_ioctl_iovar_get_(unsigned int ifc, char *var, void *data, + size_t len); +static bool cyw_ioctl_iovar_set_(unsigned int ifc, char *var, void *data, + size_t len); +// clang-format off +// convenience: ioctl funcs on default ifc (0), as only AP needs ifc 1 +static bool cyw_ioctl_get(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_get_(0, cmd, data, len); } +static bool cyw_ioctl_set(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_set_(0, cmd, data, len); } +static bool cyw_ioctl_iovar_get(char *var, void *data, size_t len) { return cyw_ioctl_iovar_get_(0, var, data, len); } +static bool cyw_ioctl_iovar_set(char *var, void *data, size_t len) { return cyw_ioctl_iovar_set_(0, var, data, len); } +// clang-format on + +// Wi-Fi network stuff + +// clang-format off +static bool cyw_wifi_connect(char *ssid, char *pass) { + uint32_t sup_wpa[2] = {0, 1}; // bss index 0 = STA, not open + static const uint32_t const eapver[2] = {0, (uint32_t) -1}, // accept AP version + tmo[2] = {0, 2500}; + uint32_t data[64/4 + 1]; // max pass length: 64 for WPA, 128 for WPA3 SAE + unsigned int len; + uint32_t val; + val = 4; // security type: 0 for none, 2 for WPA, 4 for WPA2/WPA3, 6 for mixed WPA/WPA2 + // sup_wpa[1] = 0 if not using security + if (!(cyw_ioctl_set(134 /* SET_WSEC */, (uint8_t *)&val, sizeof(val)) + && cyw_ioctl_iovar_set("bsscfg:sup_wpa", (void *)sup_wpa, sizeof(sup_wpa)) + && cyw_ioctl_iovar_set("bsscfg:sup_wpa2_eapver", (void *)eapver, sizeof(eapver)) + && cyw_ioctl_iovar_set("bsscfg:sup_wpa_tmo", (void *)tmo, sizeof(tmo))) + ) return false; + mg_delayms(2); // allow radio firmware to be ready + // skip if not using auth + memset(data, 0, sizeof(data)); + len = strlen(pass); + ((uint16_t *)data)[0] = (uint16_t) len; + ((uint16_t *)data)[1] = 1; // indicates wireless security key, skip for WPA3 SAE + memcpy((uint8_t *)data + 2 * sizeof(uint16_t), pass, len); // skip for WPA3 SAE + if (!cyw_ioctl_set(268 /* SET_WSEC_PMK */, data, sizeof(data))) return false; // skip for WPA3 SAE, sizeof/2 if supporting SAE but using WPA + // for WPA3 SAE: memcpy((uint8_t *)data + sizeof(uint16_t), pass, len); cyw_ioctl_iovar_set("sae_password", data, sizeof(data)); + // resume if not using auth + val = 1; if (!cyw_ioctl_set(20 /* SET_INFRA */, (uint8_t *)&val, sizeof(val))) return false; + val = 0; // auth type: 0 for open, 3 for SAE + if (!cyw_ioctl_set(22 /* SET_AUTH */, (uint8_t *)&val, sizeof(val))) return false; + val = 1; // MFP capable: 1 for yes, 0 for no; recommended to be set for WPA2+ (2 for 'required', WPA3) + cyw_ioctl_iovar_set("mfp", (uint8_t *)&val, sizeof(val)); // Old chipsets do not support MFP + val = 0x80; // auth type: 0 for none, 4 for WPA PSK, 0x80 for WPA2 PSK, 0x40000 for WPA3 SAE PSK + if (!cyw_ioctl_set(165 /* SET_WPA_AUTH */, (uint8_t *)&val, sizeof(val))) return false; + len = strlen(ssid); + data[0] = (uint32_t) len; + memcpy((uint8_t *)&data[1], ssid, len); + if (!cyw_ioctl_set(26 /* SET_SSID */, data, len + sizeof(uint32_t))) return false; + return true; +} + +static bool cyw_wifi_disconnect(void) { + return cyw_ioctl_set(52 /* DISASSOC */, NULL, 0); +} + +// For AP functions, we use explicit ifc selection; both for clarity and maintenance, as some actions are performed on ifc 0, with or without a bss_index, and others are performed on ifc 1 + +static bool cyw_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { + uint32_t data[64/4 + 2]; // max pass length: 64 for WPA, 128 for WPA3 SAE + unsigned int len; + uint32_t val; + // CHIP DEPENDENCY + // RPi set the AMPDU parameter for AP (window size = 2) ***************** + // val = 2 ; cyw_ioctl_iovar_set_(0, "ampdu_ba_wsize", (uint8_t *)&val, sizeof(val)); + // some chips might require to turn APSTA off and issue a SET_AP IOCTL + len = strlen(ssid); + data[0] = 1; // bss index 1 = AP + data[1] = (uint32_t) len; + memcpy((uint8_t *)&data[2], ssid, len); + // TODO(scaprile): this takes some time to process, or requires a delay before doing it + if (!cyw_ioctl_iovar_set_(0, "bsscfg:ssid", (uint8_t *)&data, len + 2 * sizeof(uint32_t))) return false; + // TODO(scaprile): but sometimes this one takes some time to process + val = (uint32_t) channel; if (!cyw_ioctl_set_(0, 30 /* SET_CHANNEL */, (uint8_t *)&val, sizeof(val))) return false; + data[0] = 1; // bss index 1 = AP + data[1] = 0x00400004; // security type: 0 for none, 0x00200002 for WPA, 0x00400004 for WPA2, 0x01000004 for WPA3, 0x01400004 for mixed WPA2/WPA3, 0x00400006 for mixed WPA/WPA2 + // NOTE(): WHD writes & 0xFF if WPS is not enabled (?) + if (!cyw_ioctl_iovar_set_(0, "bsscfg:wsec", (uint8_t *)&data, 2 * sizeof(uint32_t))) return false; + val = 1; // MFP capable: 1 for yes, 0 for no; recommended to be set for WPA2+ (2 for 'required', WPA3) + cyw_ioctl_iovar_set_(1, "mfp", (uint8_t *)&val, sizeof(val)); // Old chipsets do not support MFP + mg_delayms(2); // allow radio firmware to be ready + // skip if not using auth + // WPA, WPA2, mixed WPA/WPA2, mixed WPA2/WPA3 + // NOTE(): WHD does not set SAE password for shared WPA2/WPA3, same do we + memset(data, 0, sizeof(data)); + len = strlen(pass); + ((uint16_t *)data)[0] = (uint16_t) len; // skip for WPA3 SAE + ((uint16_t *)data)[1] = 1; // indicates wireless security key, skip for WPA3 SAE + memcpy((uint8_t *)data + 2 * sizeof(uint16_t), pass, len); // skip for WPA3 SAE + if (!cyw_ioctl_set_(1, 268 /* SET_WSEC_PMK */, data, sizeof(data))) return false; // skip for WPA3 SAE, sizeof/2 if supporting SAE but using WPA + /* for WPA3 SAE: + memcpy((uint8_t *)data + sizeof(uint16_t), pass, len); + cyw_ioctl_iovar_set_(1, "sae_password", data, sizeof(data)); */ + /* for WPA3 or mixed WPA2/WPA3: + val = 5 ; cyw_ioctl_iovar_set_(1, "sae_max_pwe_loop", (uint8_t *)&val, sizeof(val)); // Some chipsets do not support this */ + // resume if not using auth + + data[0] = 1; // bss index 1 = AP + data[1] = 0x80; // auth type: 0 for none, 4 for WPA PSK, 0x80 for WPA2 PSK, 0x40000 for WPA3 SAE PSK; ored if mixed + if (!cyw_ioctl_iovar_set_(0, "bsscfg:wpa_auth", (uint8_t *)&data, 2 * sizeof(uint32_t))) return false; + + val = 1 /* auto */; if (!cyw_ioctl_set_(1, 110 /* SET_GMODE */, (uint8_t *)&val, sizeof(val))) return false; + // Set multicast tx rate to 11Mbps, may fail in some chipsets, we are enforcing it + val = 11000000 / 500000; if (!cyw_ioctl_iovar_set_(1, "2g_mrate", (uint8_t *)&val, sizeof(val))) return false; + val = 1; if (!cyw_ioctl_set_(1, 78 /* SET_DTIMPRD */, (uint8_t *)&val, sizeof(val))) return false; + data[0] = 1; // bss index 1 = AP + data[1] = 1; // UP + // TODO(scaprile): this takes a long time to process + if (!cyw_ioctl_iovar_set_(0, "bss", (uint8_t *)&data, 2 * sizeof(uint32_t))) return false; + return true; +} + +static bool cyw_wifi_ap_stop(void) { + uint32_t data[2]; + data[0] = 1; // bss index 1 = AP + data[1] = 0; // DOWN + if (!cyw_ioctl_iovar_set_(0, "bss", (uint8_t *)&data, 2 * sizeof(uint32_t))) return false; + // DO WE NEED TO CLEAR CHANNEL ??? + // CHIP DEPENDENCY + //val = 8 ; cyw_ioctl_iovar_set_(0, "ampdu_ba_wsize", (uint8_t *)&val, sizeof(val)); + return true; +} + +// WLAN scan handling + +#pragma pack(push, 1) +// in little endian + +struct wifi_scan_opt { + uint32_t version; + uint16_t action; + uint16_t _; + uint32_t ssid_len; + uint8_t ssid[32]; + uint8_t bssid[6]; + int8_t bss_type; + int8_t scan_type; + int32_t nprobes; + int32_t active_time; + int32_t passive_time; + int32_t home_time; + int32_t channel_num; + uint16_t channel_list[1]; +}; +#pragma pack(pop) + +static bool cyw_wifi_scan(void) { + struct wifi_scan_opt opts; + memset(&opts, 0, sizeof(opts)); + opts.version = 1; + opts.action = 1; // start + opts._ = 0; + memset(opts.bssid, 0xff, sizeof(opts.bssid)); + opts.bss_type = 2; // any + opts.nprobes = -1; + opts.active_time = -1; + opts.passive_time = -1; + opts.home_time = -1; + opts.channel_num = 0; + opts.channel_list[0] = 0; + return cyw_ioctl_iovar_set("escan", (uint8_t *)&opts, sizeof(opts)); +} + + +#pragma pack(push, 1) +// in little endian + +struct scan_bss { + uint32_t version; // version field + uint32_t length; // byte length of data in this record, starting at version and including IEs + uint8_t BSSID[6]; // Unique 6-byte MAC address + uint16_t beacon_period; // Interval between two consecutive beacon frames. Units are Kusec + uint16_t capability; // Capability information + uint8_t SSID_len; // SSID length + uint8_t SSID[32]; // Array to store SSID + uint8_t reserved1[1]; // Reserved(padding) + uint32_t rateset_count; // Count of rates in this set + uint8_t rateset_rates[16]; // rates in 500kbps units, higher bit set if basic + uint16_t chanspec; // Channel specification for basic service set + uint16_t atim_window; // Announcement traffic indication message window size. Units are Kusec + uint8_t dtim_period; // Delivery traffic indication message period + uint8_t reserved2[1]; // Reserved(padding) + int16_t RSSI; // receive signal strength (in dBm) + int8_t phy_noise; // noise (in dBm) + uint8_t n_cap; // BSS is 802.11n Capable + uint8_t reserved3[2]; // Reserved(padding) + uint32_t nbss_cap; // 802.11n BSS Capabilities (based on HT_CAP_*) + uint8_t ctl_ch; // 802.11n BSS control channel number + uint8_t reserved4[3]; // Reserved(padding) + uint32_t reserved32[1]; // Reserved for expansion of BSS properties + uint8_t flags; // flags + uint8_t vht_cap; // BSS is vht capable + uint8_t reserved5[2]; // Reserved(padding) + uint8_t basic_mcs[16]; // 802.11N BSS required MCS set + uint16_t ie_offset; // offset at which IEs start, from beginning + uint16_t reserved16[1]; // Reserved(padding) + uint32_t ie_length; // byte length of Information Elements + int16_t SNR; // Average SNR during frame reception +}; + +struct scan_result { + uint32_t buflen; + uint32_t version; + uint16_t sync_id; + uint16_t bss_count; + struct scan_bss bss[1]; +}; + +#pragma pack(pop) + +// CHIP DEPENDENCY +#define CYW_BSS_BANDMASK 0xc000 +#define CYW_BSS_BAND2G 0 +// + +static void cyw_handle_scan_result(uint32_t status, struct scan_result *data, size_t len) { + MG_VERBOSE(("scan event, status: %ld", status)); + if (status == 0) { // SUCCESS + MG_VERBOSE(("scan complete")); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_END, NULL); + } else if (status == 8) { // PARTIAL + struct mg_wifi_scan_bss_data bss; + struct scan_bss *sbss = data->bss; + unsigned int band = sbss->chanspec & CYW_BSS_BANDMASK; + if (data->version != 109 || data->bss_count != 1) { + MG_ERROR(("Unsupported: %lu %u", data->version, data->bss_count)); + return; + } + if (sbss->length > len - offsetof(struct scan_result, bss) || sbss->SSID_len > sizeof(sbss->SSID) || sbss->ie_offset < sizeof(*sbss) || sbss->ie_offset > (sizeof(*sbss) + sbss->ie_length) || sbss->ie_offset + sbss->ie_length > sbss->length) + return; // silently discard malformed data + if (!(sbss->flags & MG_BIT(2))) return; // RSSI_ONCHANNEL, ignore off-channel results + bss.SSID = mg_str_n(sbss->SSID, sbss->SSID_len); + bss.BSSID = sbss->BSSID; + bss.RSSI = (int8_t)sbss->RSSI; + bss.has_n = sbss->n_cap != 0; + bss.channel = bss.has_n ? sbss->ctl_ch : (uint8_t)(sbss->chanspec & 0xff); // n 40MHz vs a/b/g and 20MHz + bss.band = band & CYW_BSS_BAND2G ? MG_WIFI_BAND_2G : MG_WIFI_BAND_5G; + bss.security = (sbss->capability & MG_BIT(4) /* CAP_PRIVACY */) ? MG_WIFI_SECURITY_WEP : MG_WIFI_SECURITY_OPEN; + { // travel IEs (Information Elements) in search of security definitions + const uint8_t wot1[4] = {0x00, 0x50, 0xf2, 0x01}; // WPA_OUI_TYPE1 + uint8_t *ie = (uint8_t *)sbss + sbss->ie_offset; + int bytes = (int) sbss->ie_length; + while (bytes > 0 && ie[1] + 2 < bytes) { // ie[0] -> type, ie[1] -> bytes from ie[2] + if (ie[0] == 48 /* IE_ID_RSN */) bss.security |= MG_WIFI_SECURITY_WPA2; + if (ie[0] == 221 /* IE_ID_VENDOR_SPECIFIC */ && memcmp(&ie[2], wot1, 4) == 0) + bss.security |= MG_WIFI_SECURITY_WPA; + ie += ie[1] + 2; + bytes -= ie[1] + 2; + } + } + MG_VERBOSE(("BSS: %.*s (%u) (%M) %d dBm %u", bss.SSID.len, bss.SSID.buf, bss.channel, mg_print_mac, bss.BSSID, (int) bss.RSSI, bss.security)); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_RESULT, &bss); + } else { + MG_ERROR(("scan error")); + } +} +// clang-format on + +// IOCTL stuff. All values read and written are in little endian format + +static uint16_t s_ioctl_reqid; + +// CDC handler for waiting loop +static uint8_t *s_ioctl_resp; +static bool s_ioctl_err; + +static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len) { + uint8_t *resp = (uint8_t *) cdc + sizeof(*cdc); + MG_VERBOSE(("%u bytes CDC frame", len)); + if ((cdc->flags >> 16) != s_ioctl_reqid) return; + if (cdc->flags & 1) { + MG_ERROR(("IOCTL error: %ld", -cdc->status)); + s_ioctl_err = true; + return; + } + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump((void *) cdc, len); + MG_DEBUG(("IOCTL result: %02x %02x %02x %02x ...", resp[0], resp[1], resp[2], + resp[3])); + s_ioctl_resp = resp; +} +// NOTE(): alt no loop handler dispatching IOCTL response to current handler: +// static void *s_ioctl_hnd; *s_ioctl_hnd(ioctl, len); +// app is a state machine calling get/sets and advancing via these callbacks + +#pragma pack(push, 1) +// all little endian + +struct ctrl_hdr { + struct sdpcm_hdr sdpcm; + struct cdc_hdr cdc; +}; + +#pragma pack(pop) + +// IOCTL command send +static void cyw_ioctl_send_cmd(unsigned int ifc, unsigned int cmd, bool set, + size_t len) { + struct ctrl_hdr *hdr = (struct ctrl_hdr *) txdata; + uint16_t txlen = (uint16_t) (len + sizeof(*hdr)); + memset(txdata, 0, sizeof(*hdr)); + hdr->cdc.cmd = cmd; + hdr->cdc.olen = (uint16_t) len; + // hdr->cdc.ilen = 0; // actually zeroed above + hdr->cdc.flags = ((uint32_t) ++s_ioctl_reqid << 16) | ((ifc & 0xf) << 12) | + (set ? MG_BIT(1) : 0); + hdr->sdpcm.len = txlen; + hdr->sdpcm._len = (uint16_t) ~txlen; + hdr->sdpcm.sw_hdr.sequence = ++s_tx_seqno; + hdr->sdpcm.sw_hdr.channel_and_flags = CYW_SDPCM_CTRL_HDR; + hdr->sdpcm.sw_hdr.header_length = offsetof(struct ctrl_hdr, cdc); + cyw_bus_tx(txdata, txlen); +} + +// just send respective commands, response handled via CDC handler +static void cyw_ioctl_send_get(unsigned int ifc, unsigned int cmd) { + cyw_ioctl_send_cmd(ifc, cmd, false, 0); +} + +static void cyw_ioctl_send_set(unsigned int ifc, unsigned int cmd, void *data, + size_t len) { + if (data != NULL && len > 0) + memcpy((uint8_t *) txdata + sizeof(struct ctrl_hdr), data, len); + cyw_ioctl_send_cmd(ifc, cmd, true, (uint16_t) len); +} + +static void cyw_ioctl_send_iovar_get(unsigned int ifc, char *var, size_t len) { + unsigned int namelen = strlen(var) + 1; // include '\0' + // cmd = GET IOVAR, "set" the name... + cyw_ioctl_send_set(ifc, 262, var, len > namelen ? len : namelen); +} + +static void cyw_ioctl_send_iovar_set2(unsigned int ifc, char *var, void *data1, + size_t len1, void *data2, size_t len2) { + struct ctrl_hdr *hdr = (struct ctrl_hdr *) txdata; + unsigned int namelen = strlen(var) + 1; // include '\0' + uint16_t txlen, payload_len = (uint16_t) (namelen + len1 + len2); + memcpy((uint8_t *) txdata + sizeof(*hdr), var, namelen); + memcpy((uint8_t *) txdata + namelen + sizeof(*hdr), data1, len1); + if (data2 != NULL) + memcpy((uint8_t *) txdata + namelen + sizeof(*hdr) + len1, data2, len2); + txlen = (uint16_t) (payload_len + sizeof(*hdr)); + cyw_ioctl_send_cmd(ifc, 263, true, txlen); // cmd = SET IOVAR +} + +static void cyw_ioctl_send_iovar_set(unsigned int ifc, char *var, void *data, + size_t len) { + cyw_ioctl_send_iovar_set2(ifc, var, data, len, NULL, 0); +} + +// wait for a response, meanwhile delivering received frames and events +static bool cyw_ioctl_wait(void) { + unsigned int times = 6000; + s_ioctl_resp = NULL; + s_ioctl_err = false; + while (s_ioctl_resp == NULL && !s_ioctl_err && times-- > 0) + cyw_poll(); // TODO(scaprile): review wait/sleep strategy (this loop is executed only when initializing/acting on the chip) + MG_DEBUG(("resp: %lp, err: %c, times: %d", s_ioctl_resp, + s_ioctl_err ? '1' : '0', (int) times)); + return s_ioctl_resp != NULL; +} + +static bool cyw_ioctl_waitdata(void *data, size_t len) { + if (!cyw_ioctl_wait()) return false; + memcpy(data, s_ioctl_resp, len); + return true; +} + +// send respective commands, wait for a response or timeout +static bool cyw_ioctl_get_(unsigned int ifc, unsigned int cmd, void *data, + size_t len) { + cyw_ioctl_send_get(ifc, cmd); + return cyw_ioctl_waitdata(data, len); +} +static bool cyw_ioctl_set_(unsigned int ifc, unsigned int cmd, void *data, + size_t len) { + cyw_ioctl_send_set(ifc, cmd, data, len); + return cyw_ioctl_wait(); +} + +static bool cyw_ioctl_iovar_get_(unsigned int ifc, char *var, void *data, + size_t len) { + cyw_ioctl_send_iovar_get(ifc, var, len); + return cyw_ioctl_waitdata(data, len); +} +static bool cyw_ioctl_iovar_set2_(unsigned int ifc, char *var, void *data1, + size_t len1, void *data2, size_t len2) { + cyw_ioctl_send_iovar_set2(ifc, var, data1, len1, data2, len2); + return cyw_ioctl_wait(); +} +static bool cyw_ioctl_iovar_set_(unsigned int ifc, char *var, void *data, + size_t len) { + return cyw_ioctl_iovar_set2_(ifc, var, data, len, NULL, 0); +} + +// CYW43 chipset specifics. All values read and written are in little endian +// format + +#pragma pack(push, 1) +// all little endian + +struct cyw_country { + uint32_t a; + int32_t rev; + uint32_t c; +}; + +struct clm_hdr { + uint16_t flag; + uint16_t type; + uint32_t len; + uint32_t crc; +}; + +#pragma pack(pop) + +// worlwide rev0, try rev 17 for 4343W +static const uint32_t country_code = 'X' + ('X' << 8) + (0 << 16); + +static bool cyw_spi_init(); + +// clang-format off +static bool cyw_init(struct mg_tcpip_driver_cyw_firmware *fw, uint8_t *mac) { + uint32_t val = 0; + if (!cyw_spi_init()) return false; // BUS DEPENDENCY + // BT-ENABLED DEPENDENCY + // set Wi-Fi up + val = 0 /* disable */; cyw_ioctl_iovar_set("bus:txglom", (uint8_t *)&val, sizeof(val)); + val = 1 /* on */; cyw_ioctl_iovar_set("apsta", (uint8_t *)&val, sizeof(val)); + // CHIP DEPENDENCY + val = 8 ; cyw_ioctl_iovar_set("ampdu_ba_wsize", (uint8_t *)&val, sizeof(val)); + val = 4 ; cyw_ioctl_iovar_set("ampdu_mpdu", (uint8_t *)&val, sizeof(val)); + val = 0 /* 8K */; cyw_ioctl_iovar_set("ampdu_rx_factor", (uint8_t *)&val, sizeof(val)); + // + { + struct cyw_country c; + unsigned int rev = (unsigned int) (country_code >> 16) & 0xffff; + c.c = c.a = country_code & 0xffff; + c.rev = rev == 0 ? -1 : (int32_t) rev; // if rev is 0, set it to -1, the chip will use any NVRAM/OTP configured aggregate or default to rev 0 + cyw_ioctl_iovar_set("country", (void *)&c, sizeof(c)); + } // this takes some time to process + { // so do some retries while enabling events of interest + // we care for SET_SSID(0), JOIN(1), AUTH(3), DEAUTH(5), DISASSOC_IND(12), LINK(16), PSK_SUP(46), SCAN_RESULT(69); all < 128 + uint32_t data[128/8/4 + 1]; + data[0] = 0; // bss index: 0 = STA + memset(&data[1], 0, 128/8); // mark all as not desired + data[1] = MG_BIT(0) | MG_BIT(1) | MG_BIT(3) | MG_BIT(5) | MG_BIT(12) | MG_BIT(16); // events 0 to 31 + data[2] = MG_BIT(46 - 32); // events 32 to 63 + data[3] = MG_BIT(69 - 64); // events 64 to 95 + unsigned int times = 100; + while (times --) + if (cyw_ioctl_iovar_set("bsscfg:event_msgs", (uint8_t *)data, sizeof(data))) break; + if (times == ~0) return false; + } + val = 0; if (!cyw_ioctl_set(64 /* SET_ANTDIV */, (uint8_t *)&val, sizeof(val))) return false; + if (!cyw_ioctl_set(2 /* UP, interface up */, NULL, 0)) return false; + // use PM2 power saving for max throughput + val = 200 /* ms */; if (!cyw_ioctl_iovar_set("pm2_sleep_ret", (uint8_t *)&val, sizeof(val))) return false; + // set beacon intervals to reduce power consumption while associated to an AP but idle + val = 1; if (!cyw_ioctl_iovar_set("bcn_li_bcn", (uint8_t *)&val, sizeof(val))) return false; + val = 1; if (!cyw_ioctl_iovar_set("bcn_li_dtim", (uint8_t *)&val, sizeof(val))) return false; + val = 10; if (!cyw_ioctl_iovar_set("assoc_listen", (uint8_t *)&val, sizeof(val))) return false; + val = 1 /* auto */; if (!cyw_ioctl_set(110 /* SET_GMODE */, (uint8_t *)&val, sizeof(val))) return false; + val = 0 /* any */; if (!cyw_ioctl_set(142 /* SET_BAND */, (uint8_t *)&val, sizeof(val))) return false; + if (mg_log_level >= MG_LL_DEBUG) { + char text[256]; // this is huge, but we're just starting up + if (cyw_ioctl_iovar_get("ver", (uint8_t *)text, sizeof(text))) { + unsigned int len = strnlen(text, sizeof(text)); + MG_DEBUG(("Firmware:\n%.*s", len, text)); + } + text[0] = '\0'; + if (cyw_ioctl_iovar_get("clmver", (uint8_t *)text, sizeof(text)) && text[0] != '\0') { + unsigned int len = strnlen(text, sizeof(text)); + MG_DEBUG(("CLM:\n%.*s", len, text)); + } + } + { + if(cyw_ioctl_iovar_get("cur_etheraddr", mac, 6)) { + MG_DEBUG(("MAC: %M", mg_print_mac, mac)); + } else { + MG_ERROR(("read MAC failed")); + } + } + return true; +} +// clang-format on + +static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, + size_t nvramlen); + +static bool cyw_load_firmware(struct mg_tcpip_driver_cyw_firmware *fw) { + return cyw_load_fwll((void *) fw->code_addr, fw->code_len, + (void *) fw->nvram_addr, fw->nvram_len); +} + +// clang-format off +static bool cyw_load_clmll(void *data, size_t len) { + unsigned int sent = 0, offset = 0; + struct clm_hdr hdr = { + .flag = 1 << 12 /* DLOAD_HANDLER_VER */ | MG_BIT(1) /* DL_BEGIN */, + .type = 2, + .crc = 0}; + while (sent < len) { + unsigned int bytes = len - sent; + if (bytes > 1024) bytes = 1024; + if (sent + bytes >= len) hdr.flag |= MG_BIT(2); // DL_END; + hdr.len = bytes; + if (!cyw_ioctl_iovar_set2_(0, "clmload", (void *) &hdr, sizeof(hdr), (uint8_t *) data + offset, bytes)) + break; + sent += bytes; + offset += bytes; + hdr.flag &= ~MG_BIT(1); // DL_BEGIN + } + return sent >= len; +} +// clang-format on + +static bool cyw_load_clm(struct mg_tcpip_driver_cyw_firmware *fw) { + return cyw_load_clmll((void *) fw->clm_addr, fw->clm_len); +} + +// CYW43 chip backplane specifics. All values read and written are in little +// endian format + +// Access to chip backplane is done windowed in 32KB banks +// - addr = area base address + register offset +// - set the window address to addr & ~ADDRMSK +// - access addr & ADDRMSK for non-32-bit quantities +// - if accesing 32-bit quantities, do it on (addr & ADDRMSK) | ACCSS4B +#define CYW_CHIP_CHIPCOMMON 0x18000000 +#define CYW_CHIP_BCKPLN_WINSZ 0x8000 +#define CYW_CHIP_BCKPLN_ADDRMSK 0x7fff +#define CYW_CHIP_BCKPLN_ACCSS4B MG_BIT(15) +#define CYW_CHIP_BCKPLN_WRAPPOFF 0x100000 +#define CYW_CHIP_BCKPLN_SPIMAX 64 +#define CYW_CHIP_BCKPLN_SDIOMAX 1536 + +// CHIP DEPENDENCY +#define CYW_CHIP_ARMCORE_BASE (CYW_CHIP_CHIPCOMMON + 0x3000) +#define CYW_CHIP_SOCSRAM_BASE (CYW_CHIP_CHIPCOMMON + 0x4000) +#define CYW_CHIP_ARMCORE (CYW_CHIP_ARMCORE_BASE + CYW_CHIP_BCKPLN_WRAPPOFF) +#define CYW_CHIP_SOCSRAM (CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_BCKPLN_WRAPPOFF) +#define CYW_CHIP_ATCMRAM_BASE 0 +#define CYW_CHIP_RAM_SIZE 0x80000 +// + +#define CYW_CHIP_ADDRLOW 0x1000a +#define CYW_CHIP_ADDRMID 0x1000b +#define CYW_CHIP_ADDRHIGH 0x1000c +#define CYW_CHIP_SPIFRCTRL 0x1000d +#define CYW_CHIP_CLOCKCSR 0x1000e +#define CYW_CHIP_PULLUP 0x1000f +#define CYW_CHIP_WAKEUPCTL 0x1001e +#define CYW_CHIP_SLEEPCSR 0x1001f + +#define CYW_CHIP_SOCSRAM_BANKXIDX 0x010 +#define CYW_CHIP_SOCSRAM_BANKXPDA 0x044 +#define CYW_CHIP_AI_IOCTRL 0x408 +#define CYW_CHIP_AI_RESETCTRL 0x800 + +static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, + uint16_t len); +static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, + uint16_t len); + +// clang-format off +// set backplane window to requested area. +static void cyw_set_backplane_window(uint32_t addr) { + uint32_t val; + addr &= ~CYW_CHIP_BCKPLN_ADDRMSK; + val = (addr >> 24) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRHIGH, &val, 1); + val = (addr >> 16) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRMID, &val, 1); + val = (addr >> 8) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRLOW, &val, 1); +} + +static bool cyw_core_reset(uint32_t core_base, bool check) { + uint32_t val = 0; + // core disabled after chip reset + cyw_set_backplane_window(core_base); // set backplane window for requested area; we do know offsets fall within that window + // possible CHIP DEPENDENCY: AI_RESETSTATUS check and wait (instead of these cool reads) to ensure backplane operations end + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = MG_BIT(1) | MG_BIT(0) /* SICF_FGC | SICF_CLOCK_EN */; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // reset + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // release reset + mg_delayms(1); + val = MG_BIT(0) /* SICF_CLOCK_EN */; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + mg_delayms(1); + + if (check) { + // Verify only clock is enabled + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + if ((val & (MG_BIT(1) | MG_BIT(0)) /* SICF_FGC | SICF_CLOCK_EN) */) != MG_BIT(0)) return false; + // Verify it is not in reset state + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + if (val & MG_BIT(0)) return false; // AIRC_RESET + } + return true; +} + +static void cyw_socram_init(void) { + uint32_t val; + // CHIP DEPENDENCY: disable remap for SRAM_3 + cyw_set_backplane_window(CYW_CHIP_SOCSRAM_BASE); // set backplane window for requested area; we do know offsets fall within that window + val = 0x03; cyw_spi_write(CYW_SD_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXIDX) & CYW_CHIP_BCKPLN_ADDRMSK) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); + val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXPDA) & CYW_CHIP_BCKPLN_ADDRMSK) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); +} + +// transfer is fractioned in bus-to-backplane-size units within backplane windows +static void cyw_load_data(uint32_t dest, void *data, size_t len) { + size_t sent = 0, offset = 0; + uint32_t last_addr = ~0; + while (sent < len) { + size_t bytes = len - sent, avail; + uint32_t addr = dest + offset; + if (addr - last_addr >= CYW_CHIP_BCKPLN_WINSZ || last_addr == ~0) { + cyw_set_backplane_window(addr); // set backplane window for requested area + last_addr = addr & ~CYW_CHIP_BCKPLN_ADDRMSK; + } + addr &= CYW_CHIP_BCKPLN_ADDRMSK; + avail = CYW_CHIP_BCKPLN_WINSZ - (unsigned int) addr; + if (bytes > avail) bytes = avail; + // BUS DEPENDENCY: max bus to backplane transfer size + if (bytes > CYW_CHIP_BCKPLN_SPIMAX) bytes = CYW_CHIP_BCKPLN_SPIMAX; + cyw_spi_write(CYW_SD_FUNC_CHIP, addr | CYW_CHIP_BCKPLN_ACCSS4B, (uint8_t *)data + offset, bytes); + sent += bytes; + offset += bytes; + } +} + +// CHIP DEPENDENCY: no SOCSRAM base address; start address in fwdata image (Cortex-R4 chips) +static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, size_t nvramlen) { + uint32_t val = ((~(nvramlen / 4) & 0xffff) << 16) | (nvramlen / 4); // ~len len in 32-bit words + cyw_core_reset(CYW_CHIP_SOCSRAM, false); // cores were disabled at chip reset + cyw_socram_init(); + cyw_load_data(CYW_CHIP_ATCMRAM_BASE, fwdata, fwlen); + mg_delayms(5); // ************ CHECK IF THIS IS ACTUALLY NEEDED + // Load NVRAM and place 'length ~length' at the end; end of chip RAM + { + const uint32_t start = CYW_CHIP_RAM_SIZE - 4 - nvramlen; + cyw_load_data(start, nvramdata, nvramlen); // nvramlen must be a multiple of 4 + // RAM_SIZE is a multiple of WINSZ, so the place for len ~len will be at the end of the window + cyw_spi_write(CYW_SD_FUNC_CHIP, (CYW_CHIP_BCKPLN_WINSZ - 4) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); + } + // Reset ARM core and check it starts + if (!cyw_core_reset(CYW_CHIP_ARMCORE, true)) return false; + return true; +} +// clang-format on + +// CYW43 SPI bus specifics + +#define CYW_BUS_SPI_BUSCTRL 0x00 // 4 regs, 0 to 3 +#define CYW_BUS_SPI_INT 0x04 // 2 regs, 4 to 5 +#define CYW_BUS_SPI_INTEN 0x06 // 16-bit register +#define CYW_BUS_SPI_STATUS 0x08 // 32-bit register +#define CYW_BUS_SPI_TEST 0x14 // 32-bit register +#define CYW_BUS_SPI_RESPDLY_F1 0x1d // 8-bit register, F1: chip + +#define CYW_BUS_STS_LEN(x) ((x >> 9) & 0x7ff) + +// clang-format off +static size_t cyw_spi_poll(uint8_t *response) { + size_t len; + uint32_t res; + // SPI poll + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &res, sizeof(res)); + if (res == ~0 || !(res & MG_BIT(8) /* packet available */ )) return 0; + len = CYW_BUS_STS_LEN(res); + if (len == 0) { // just ack IRQ + uint16_t val = 1; + cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_SPIFRCTRL, &val, 1); + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); + cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); + return 0; + } + cyw_spi_read(CYW_SD_FUNC_WLAN, 0, response, len); + return len; +} + +// BUS DEPENDENCY: name is generic but function is bus dependent +static size_t cyw_bus_tx(uint32_t *data, uint16_t len) { + while (len & 3) data[len++] = 0; // SPI 32-bit padding (SDIO->64-byte) + return cyw_spi_write(CYW_SD_FUNC_WLAN, 0, data, len) ? len: 0; +} + +// this can be integrated in lowest level SPI read/write _driver_ functions +// (those calling hal SPI transaction functions), though is only used at start +uint32_t sw16_2(uint32_t data) { + return ((uint32_t)mg_htons((uint16_t)(data >> 16)) << 16) + mg_htons((uint16_t)data); +} + +// DS 4.2.2 Table 6: signal we're working in 16-bit mode +#define CYW_SD_16bMODE MG_BIT(2) // arbitrary bit out of the FUNC space + +static bool cyw_spi_init() { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + uint32_t val = 0; + // DS 4.2.3 Boot-Up Sequence; WHD: other chips might require more effort + unsigned int times = 51; + while (times--) { + cyw_spi_read(CYW_SD_FUNC_BUS | CYW_SD_16bMODE, CYW_BUS_SPI_TEST, &val, sizeof(val)); + if (sw16_2(val) == 0xFEEDBEAD) break; + mg_delayms(1); + } + if (times == ~0) return false; + // DS 4.2.3 Table 6. Chip starts in 16-bit little-endian mode. + // Configure SPI and switch to 32-bit big-endian mode: + // - High-speed mode + // - IRQ POLARITY high + // - SPI RESPONSE DELAY 4 bytes time [not in DS] TODO(scaprile): logic ana + // - Status not sent after command, IRQ with status + val = sw16_2(0x000204b3); // 4 reg content + cyw_spi_write(CYW_SD_FUNC_BUS | CYW_SD_16bMODE, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_TEST, &val, sizeof(val)); + if (val != 0xFEEDBEAD) return false; + val = 4; cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_RESPDLY_F1, &val, 1); + val = 0x99; // clear error bits DATA_UNAVAILABLE, COMMAND_ERROR, DATA_ERROR, F1_OVERFLOW + cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, 1); + val = 0x00be; // Enable IRQs F2_F3_FIFO_RD_UNDERFLOW, F2_F3_FIFO_WR_OVERFLOW, COMMAND_ERROR, DATA_ERROR, F2_PACKET_AVAILABLE, F1_OVERFLOW + // BT-ENABLED DEPENDENCY: add F1_INTR (bit 13) + cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + + // chip backplane is ready, initialize it + // request ALP (Active Low Power) clock + val = MG_BIT(3) /* ALP_REQ */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + // BT-ENABLED DEPENDENCY + times = 10; + while (times--) { + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + if (val & MG_BIT(6)) break; // ALP_AVAIL + mg_delayms(1); + } + if (times == ~0) return false; + // clear request + val = 0; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + cyw_set_backplane_window(CYW_CHIP_CHIPCOMMON); // set backplane window to start of CHIPCOMMON area + cyw_spi_read(CYW_SD_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); + MG_INFO(("WLAN chip is CYW%u", *((uint16_t *)&val))); + + // Load firmware (code and NVRAM) + if (!cyw_load_firmware(d->fw)) return false; + + // Wait for High Throughput (HT) clock ready + times = 50; + while (times--) { + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + if (val & MG_BIT(7)) break; // HT_AVAIL + mg_delayms(1); + } + if (times == ~0) return false; + // Wait for backplane ready + times = 1000; + while (times--) { + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); + if (val & MG_BIT(5)) break; // F2_RX_READY + mg_delayms(1); + } + if (times == ~0) return false; + + // CHIP DEPENDENCY + // Enable save / restore + // Configure WakeupCtrl, set HT_AVAIL in CLOCK_CSR + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); + val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); +#if 0 + // Set BRCM_CARDCAP to CMD_NODEC. NOTE(): This is probably only necessary for SDIO, not SPI + val = MG_BIT(3); cyw_spi_write(CYW_SD_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); +#endif + // Force HT request to chip backplane + val = MG_BIT(1) /* FORCE_HT */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + // Enable Keep SDIO On (KSO) + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + if (!(val & MG_BIT(0))) { + val |= MG_BIT(0); cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + } + // The SPI bus can be configured for sleep (KSO controls wlan block sleep) + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + val &= ~MG_BIT(7) /* WAKE_UP */; cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + // Set SPI bus sleep + val = 0x0f; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + + // Clear pullups. NOTE(): ? + val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + // Clear possible data unavailable error + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + if (val & MG_BIT(0)) cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + + // Load CLM blob + if (!cyw_load_clm(d->fw)) return false; + + return true; +} +// clang-format on + +// gSPI, DS 4.2.1 Fig.12 +#define CYW_SD_LEN(x) ((x) &0x7FF) // bits 0-10 +#define CYW_SD_ADDR(x) (((x) &0x1FFFF) << 11) // bits 11-27, +#define CYW_SD_FUNC(x) (((x) &3) << 28) // bits 28-29 +#define CYW_SD_INC MG_BIT(30) +#define CYW_SD_WR MG_BIT(31) + +static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; + uint32_t hdr = CYW_SD_WR | CYW_SD_INC | CYW_SD_FUNC(f) | CYW_SD_ADDR(addr) | + CYW_SD_LEN(len); // gSPI header + // TODO(scaprile): check spin in between and timeout values, return false + if (f == CYW_SD_FUNC_WLAN) { + uint32_t val = 0; + while ((val & MG_BIT(5)) != MG_BIT(5)) // F2 rx ready (FIFO ready) + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); + } + if (f & CYW_SD_16bMODE) + hdr = sw16_2(hdr); // swap half-words in 16-bit little-endian mode + + s->begin(NULL); + s->txn(NULL, (uint8_t *) &hdr, NULL, sizeof(hdr)); + if (len <= 4) { + uint32_t pad = 0; + memcpy(&pad, data, len); + s->txn(NULL, (uint8_t *) &pad, NULL, sizeof(pad)); + } else { + s->txn(NULL, (uint8_t *) data, NULL, len); + } + s->end(NULL); + return true; +} + +// will write 32-bit aligned quantities to data if f == CYW_SD_FUNC_WLAN +static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; + uint32_t padding = + f == CYW_SD_FUNC_CHIP + ? 4 + : 0; // add padding to chip backplane reads as a response delay + uint32_t hdr = CYW_SD_INC | CYW_SD_FUNC(f) | CYW_SD_ADDR(addr) | + CYW_SD_LEN(len + padding); // gSPI header + if (f == CYW_SD_FUNC_WLAN && (len & 3)) + len = (len + 4) & ~3; // align WLAN transfers to 32-bit + if (f & CYW_SD_16bMODE) + hdr = sw16_2(hdr); // swap half-words in 16-bit little-endian mode + + s->begin(NULL); + s->txn(NULL, (uint8_t *) &hdr, NULL, sizeof(hdr)); + if (f == CYW_SD_FUNC_CHIP) { + uint32_t pad; + s->txn(NULL, NULL, (uint8_t *) &pad, 4); // read padding back and discard + } + s->txn(NULL, NULL, (uint8_t *) data, len); + s->end(NULL); +} + +// Mongoose Wi-Fi API functions + +bool mg_wifi_scan(void) { + return cyw_wifi_scan(); +} + +bool mg_wifi_connect(char *ssid, char *pass) { + return cyw_wifi_connect(ssid, pass); +} + +bool mg_wifi_disconnect(void) { + return cyw_wifi_disconnect(); +} + +bool mg_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { + return cyw_wifi_ap_start(ssid, pass, channel); +} + +bool mg_wifi_ap_stop(void) { + return cyw_wifi_ap_stop(); +} + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/drivers/imxrt.c" #endif diff --git a/mongoose.h b/mongoose.h index b019dea8..79966047 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1107,6 +1107,7 @@ char *mg_random_str(char *buf, size_t len); uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); uint64_t mg_millis(void); // Return milliseconds since boot bool mg_path_is_sane(const struct mg_str path); +void mg_delayms(unsigned int ms); #define MG_U32(a, b, c, d) \ (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \ @@ -2869,6 +2870,7 @@ extern struct mg_tcpip_driver mg_tcpip_driver_xmc7; extern struct mg_tcpip_driver mg_tcpip_driver_ppp; extern struct mg_tcpip_driver mg_tcpip_driver_pico_w; extern struct mg_tcpip_driver mg_tcpip_driver_rw612; +extern struct mg_tcpip_driver mg_tcpip_driver_cyw; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { @@ -2961,6 +2963,57 @@ struct mg_profitem { #endif +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW + +struct mg_tcpip_spi_ { + void *spi; // Opaque SPI bus descriptor + void (*begin)(void *); // SPI begin: slave select low + void (*end)(void *); // SPI end: slave select high + void (*txn)(void *, uint8_t *, uint8_t *, + size_t len); // SPI transaction: write-read len bytes +}; + +struct mg_tcpip_driver_cyw_firmware { + const uint8_t * code_addr; + size_t code_len; + const uint8_t * nvram_addr; + size_t nvram_len; + const uint8_t * clm_addr; + size_t clm_len; +}; + +struct mg_tcpip_driver_cyw_data { + struct mg_tcpip_spi_ *spi; + struct mg_tcpip_driver_cyw_firmware *fw; + char *ssid; + char *pass; + char *apssid; + char *appass; + uint8_t security; // TBD + uint8_t apsecurity; // TBD + uint8_t apchannel; + bool apmode; // start in AP mode; 'false' starts connection to 'ssid' if not NULL +}; + +//#define MG_TCPIP_DRIVER_INIT(mgr) \ +// do { \ +// static struct mg_tcpip_driver_cyw_data driver_data_; \ +// static struct mg_tcpip_if mif_; \ +// MG_SET_WIFI_CONFIG(&driver_data_); \ +// mif_.ip = MG_TCPIP_IP; \ +// mif_.mask = MG_TCPIP_MASK; \ +// mif_.gw = MG_TCPIP_GW; \ +// mif_.driver = &mg_tcpip_driver_pico_w; \ +// mif_.driver_data = &driver_data_; \ +// mif_.recv_queue.size = 8192; \ +// mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ +// mg_tcpip_init(mgr, &mif_); \ +// MG_INFO(("Driver: cyw, MAC: %M", mg_print_mac, mif_.mac)); \ +// } while (0) + +#endif + + #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && MG_ENABLE_DRIVER_IMXRT struct mg_tcpip_driver_imxrt_data { diff --git a/src/drivers/cyw.c b/src/drivers/cyw.c new file mode 100644 index 00000000..908ed902 --- /dev/null +++ b/src/drivers/cyw.c @@ -0,0 +1,1186 @@ +#include "cyw.h" +#include "net.h" + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW + + +static struct mg_tcpip_if *s_ifp; +static bool s_link, s_auth, s_join; + +static bool cyw_init(struct mg_tcpip_driver_cyw_firmware *fw, uint8_t *mac); +static void cyw_poll(void); + +static bool mg_tcpip_driver_cyw_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + if (MG_BIG_ENDIAN) { + MG_ERROR(("Big-endian host")); + return false; + } + s_ifp = ifp; + s_link = s_auth = s_join = false; + if (!cyw_init(d->fw, ifp->mac)) return false; + + if (d->apmode) { + MG_DEBUG(("Starting AP '%s' (%u)", d->apssid, d->apchannel)); + return mg_wifi_ap_start(d->apssid, d->appass, d->apchannel); + } else if (d->ssid != NULL && d->pass != NULL) { + MG_DEBUG(("Connecting to '%s'", d->ssid)); + return mg_wifi_connect(d->ssid, d->pass); + } + return true; +} + +static size_t mg_cyw_tx(unsigned int ifc, void *data, size_t len); +size_t mg_tcpip_driver_cyw_output(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + return mg_cyw_tx(d->apmode ? 1 : 0, (void *) buf, len) >= len ? len : 0; +} + +static bool mg_tcpip_driver_cyw_poll(struct mg_tcpip_if *ifp, bool s1) { + cyw_poll(); + if (!s1) return false; + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + return d->apmode ? s_link : s_link && s_auth && s_join; +} + +struct mg_tcpip_driver mg_tcpip_driver_cyw = {mg_tcpip_driver_cyw_init, + mg_tcpip_driver_cyw_output, NULL, + mg_tcpip_driver_cyw_poll}; + +// - DS: +// https://www.mouser.com/datasheet/2/196/Infineon_CYW43439_DataSheet_v03_00_EN-3074791.pdf +// - WHD: https://github.com/Infineon/wifi-host-driver +// +// | e <-- event data +// |----- +// net | vnd <-- network (TCP/IP) | vendor header (Broadcom (bcm)) +// -----|----- +// IOCTL | ETH | ETH <-- IOCTL/IOVAR: chip control | ETH: Ethernet header +// -------|-----|----- +// CDC | BDC | BDC +// ------- ----- ----- +// SDPCM <-- includes SDIO bus arbitration, not used in SPI +// ------------------- +// SPI | SDIO <-- padded to 32-bit | 64-bytes +// +// - SDPCM has 3 channels (control, data, and asynchronous data) +// - SPI (and SDIO) has 4 "functions", F0 to F3, to access different +// blocks in the chip, like the SPI/SDIO controller, chip backplane, and 2 DMA +// I/Os; these are usually handled by SDPCM but we need to explicitly access +// the I/O controller and chip backplane during initialization + +// Processor core firmware is loaded to TCM RAM, along with module-dependent +// (hardware design) NVRAM data, via the chip backplane access through the bus +// Once the chip has been initialized, information regarding regulatory +// constraints (CLM blob, “Country Locale Matrix”), is loaded as an IOVAR. This +// is tied to the module being certified, hence it is also module-dependent. +// - Result: chip firmware + module NVRAM data + module CLM blob + +#pragma pack(push, 1) +// all little endian + +struct cdc_hdr { + uint32_t cmd; // ioctl command value + uint16_t olen; // output buflen + uint16_t ilen; // input buflen (excludes header) + uint32_t flags; + uint32_t status; +}; + +struct bdc_hdr { + uint8_t flags; // Flags + uint8_t priority; // 802.1d Priority (low 3 bits) + uint8_t flags2; + uint8_t data_offset; // Offset from end of BDC header to packet data, in + // 4-uint8_t words. Leaves room for optional headers. +}; + +struct sdpcm_sw_hdr { + uint8_t sequence; // Sequence number of pkt + uint8_t channel_and_flags; // IOCTL/IOVAR or User Data or Event + uint8_t next_length; + uint8_t header_length; // Offset to BDC or CDC header + uint8_t wireless_flow_control; + uint8_t bus_data_credit; // Credit from WLAN Chip + uint8_t _reserved[2]; +}; + +struct sdpcm_hdr { + uint16_t len; + uint16_t _len; // ~len + struct sdpcm_sw_hdr sw_hdr; +}; + +struct data_hdr { + struct sdpcm_hdr sdpcm; + uint8_t pad[2]; + struct bdc_hdr bdc; +}; + +// gSPI, DS 4.2.1 Fig.12, 2-bit field +#define CYW_SD_FUNC_BUS 0 // F0 +#define CYW_SD_FUNC_CHIP 1 // F1 +#define CYW_SD_FUNC_WLAN 2 // F2 + +#define CYW_SDPCM_CTRL_HDR 0 +#define CYW_SDPCM_ASYNC_HDR 1 +#define CYW_SDPCM_DATA_HDR 2 + +#pragma pack(pop) + +static uint8_t s_tx_seqno; +static uint32_t txdata[2048 / 4], resp[2048 / 4]; + +static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len); +static void cyw_handle_bdc(struct bdc_hdr *bdc, size_t len); +static void cyw_handle_bdc_evnt(struct bdc_hdr *bdc, size_t len); + +static size_t cyw_spi_poll(uint8_t *dest); + +// High-level comm stuff + +static void cyw_poll(void) { + struct sdpcm_hdr *sdpcm = (struct sdpcm_hdr *) resp; + unsigned int channel; + if (cyw_spi_poll((uint8_t *) resp) == 0) return; // BUS DEPENDENCY + if ((sdpcm->len ^ sdpcm->_len) != 0xffff || sdpcm->len < sizeof(*sdpcm) || + sdpcm->len > 2048 - sizeof(*sdpcm)) + return; + channel = sdpcm->sw_hdr.channel_and_flags & 0x0F; + if (channel == CYW_SDPCM_CTRL_HDR) { + if (sdpcm->len >= sizeof(*sdpcm) + sizeof(struct cdc_hdr)) { + struct cdc_hdr *cdc = + (struct cdc_hdr *) ((size_t) sdpcm + sdpcm->sw_hdr.header_length); + size_t len = sdpcm->len - sdpcm->sw_hdr.header_length; + cyw_handle_cdc(cdc, len); + } + } else if (channel == CYW_SDPCM_DATA_HDR) { + if (sdpcm->len >= sizeof(*sdpcm) + sizeof(struct bdc_hdr)) { + struct bdc_hdr *bdc = + (struct bdc_hdr *) ((size_t) sdpcm + sdpcm->sw_hdr.header_length); + size_t len = sdpcm->len - sdpcm->sw_hdr.header_length; + cyw_handle_bdc(bdc, len); + } + } else if (channel == CYW_SDPCM_ASYNC_HDR) { + struct bdc_hdr *bdc = + (struct bdc_hdr *) ((size_t) sdpcm + sdpcm->sw_hdr.header_length); + size_t len_ = sdpcm->len - sdpcm->sw_hdr.header_length; + cyw_handle_bdc_evnt(bdc, len_); + } // else silently discard +} + +// WLAN frame reception +static void cyw_handle_bdc(struct bdc_hdr *bdc, size_t len) { + uint8_t *payload = (uint8_t *) &bdc[bdc->data_offset + 1]; + mg_tcpip_qwrite(payload, len - (payload - (uint8_t *) bdc), s_ifp); +} + +static size_t cyw_bus_tx(uint32_t *data, uint16_t len); + +// WLAN frame transmission +static size_t mg_cyw_tx(unsigned int ifc, void *data, size_t len) { + struct data_hdr *hdr = (struct data_hdr *) txdata; + uint16_t txlen = (uint16_t) (len + sizeof(*hdr)); + memset(txdata, 0, sizeof(*hdr)); + memcpy((uint8_t *) txdata + sizeof(*hdr), data, len); + // TODO(): hdr->bdc.priority = map IP to TOS if supporting QoS/ToS + hdr->bdc.flags = 2 << 4; // BDC version 2 + hdr->bdc.flags2 = ifc; // 0 -> STA, 1 -> AP + // hdr->bdc.data_offset = 0; // actually zeroed above + hdr->sdpcm.len = txlen; + hdr->sdpcm._len = (uint16_t) ~txlen; + hdr->sdpcm.sw_hdr.sequence = ++s_tx_seqno; + hdr->sdpcm.sw_hdr.channel_and_flags = CYW_SDPCM_DATA_HDR, + hdr->sdpcm.sw_hdr.header_length = offsetof(struct data_hdr, bdc); + return cyw_bus_tx(txdata, txlen); +} + +// WLAN event handling +#pragma pack(push, 1) +// all in network order + +struct eth_hdr { // TODO(scaprile) reuse 'eth' in net_builtin.c + uint8_t dest[6]; + uint8_t src[6]; + uint16_t type; +}; + +struct bcm_vendor_hdr { + uint16_t subtype; // vendor specific: 0x8001 + uint16_t length; // bytes following this field + uint8_t version; // 0 + uint8_t oui[3]; // vendor specific: 0x00 0x10 0x18 + uint16_t usr_subtype; +}; + +struct bcm_evnt_hdr { + uint16_t version; // 1: fields up to ifname; 2: as shown + uint16_t flags; + uint32_t event_type; + uint32_t status; + uint32_t reason; + uint32_t auth_type; + uint32_t datalen; + uint8_t addr[6]; // Station address (if applicable) + char ifname[16]; + uint8_t ifidx; + uint8_t bss_cfg_idx; +}; + +struct evnt_msg { + struct eth_hdr eth; + // struct vendor_hdr; but we only handle Broadcom (Wi-Fi processor) events + struct bcm_vendor_hdr bcm; + struct bcm_evnt_hdr event; +}; + +#pragma pack(pop) + +struct scan_result; +static void cyw_handle_scan_result(uint32_t status, struct scan_result *data, + size_t len); + +static void cyw_handle_bdc_evnt(struct bdc_hdr *bdc, size_t len) { + struct evnt_msg *msg = (struct evnt_msg *) &bdc[bdc->data_offset + 1]; + MG_VERBOSE(("%u bytes event", len)); + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump((void *) bdc, len); + if (mg_ntohs(msg->eth.type) != 0x886C || msg->bcm.oui[0] != 0x00 || + msg->bcm.oui[1] != 0x10 || msg->bcm.oui[2] != 0x18) + return; // discard if not Broadcom + if (mg_ntohl(msg->event.datalen) <= + len - ((uint8_t *) msg - (uint8_t *) bdc)) { + uint32_t event_type = mg_ntohl(msg->event.event_type); + uint32_t status = mg_ntohl(msg->event.status); + uint32_t reason = mg_ntohl(msg->event.reason); + uint16_t flags = mg_ntohs(msg->event.flags); + MG_DEBUG(("BCM event %lu %lu %lu %p", event_type, status, reason, flags)); + if (event_type == 16 && status == 0) { // Link + s_link = flags & 1; + } else if (event_type == 46 && s_link) { // PSK sup with link up + if (status == 6) { // Keyed + } else if ((status == 4 || status == 8 || status == 10) && + reason == 15) { // Wait M1/M3/G1 + MG_ERROR(("AUTH TIMEOUT")); + s_auth = false; + } else { + MG_ERROR(("AUTH FAILED")); + s_auth = false; + } + } else if (event_type == 3 && status != 6) { // Auth (not unsolicited) + if (status == 0) { // Success + s_auth = true; + } else { + MG_ERROR(("AUTH TIMEOUT")); + s_auth = false; + } + } else if (event_type == 1) { // Join + if (status == 0) { // Success + s_join = true; + } else { + MG_ERROR(("%s", status == 3 /* No networks */ ? "SSID NOT FOUND" + : "JOIN FAILED")); + s_join = false; + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_CONNECT_ERR, &status); + } + } else if (event_type == 12 || event_type == 5) { // Disassoc, Deauth + s_auth = false; + } else if (event_type == 69) { // Scan result + struct scan_result *data = (struct scan_result *) (&msg->event + 1); + size_t dlen = mg_ntohl(msg->event.datalen); + if (dlen > len - ((uint8_t *) data - (uint8_t *) bdc)) return; + cyw_handle_scan_result(status, data, dlen); + } + } // else silently discard +} + +static bool cyw_ioctl_get_(unsigned int ifc, unsigned int cmd, void *data, + size_t len); +static bool cyw_ioctl_set_(unsigned int ifc, unsigned int cmd, void *data, + size_t len); +static bool cyw_ioctl_iovar_get_(unsigned int ifc, char *var, void *data, + size_t len); +static bool cyw_ioctl_iovar_set_(unsigned int ifc, char *var, void *data, + size_t len); +// clang-format off +// convenience: ioctl funcs on default ifc (0), as only AP needs ifc 1 +static bool cyw_ioctl_get(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_get_(0, cmd, data, len); } +static bool cyw_ioctl_set(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_set_(0, cmd, data, len); } +static bool cyw_ioctl_iovar_get(char *var, void *data, size_t len) { return cyw_ioctl_iovar_get_(0, var, data, len); } +static bool cyw_ioctl_iovar_set(char *var, void *data, size_t len) { return cyw_ioctl_iovar_set_(0, var, data, len); } +// clang-format on + +// Wi-Fi network stuff + +// clang-format off +static bool cyw_wifi_connect(char *ssid, char *pass) { + uint32_t sup_wpa[2] = {0, 1}; // bss index 0 = STA, not open + static const uint32_t const eapver[2] = {0, (uint32_t) -1}, // accept AP version + tmo[2] = {0, 2500}; + uint32_t data[64/4 + 1]; // max pass length: 64 for WPA, 128 for WPA3 SAE + unsigned int len; + uint32_t val; + val = 4; // security type: 0 for none, 2 for WPA, 4 for WPA2/WPA3, 6 for mixed WPA/WPA2 + // sup_wpa[1] = 0 if not using security + if (!(cyw_ioctl_set(134 /* SET_WSEC */, (uint8_t *)&val, sizeof(val)) + && cyw_ioctl_iovar_set("bsscfg:sup_wpa", (void *)sup_wpa, sizeof(sup_wpa)) + && cyw_ioctl_iovar_set("bsscfg:sup_wpa2_eapver", (void *)eapver, sizeof(eapver)) + && cyw_ioctl_iovar_set("bsscfg:sup_wpa_tmo", (void *)tmo, sizeof(tmo))) + ) return false; + mg_delayms(2); // allow radio firmware to be ready + // skip if not using auth + memset(data, 0, sizeof(data)); + len = strlen(pass); + ((uint16_t *)data)[0] = (uint16_t) len; + ((uint16_t *)data)[1] = 1; // indicates wireless security key, skip for WPA3 SAE + memcpy((uint8_t *)data + 2 * sizeof(uint16_t), pass, len); // skip for WPA3 SAE + if (!cyw_ioctl_set(268 /* SET_WSEC_PMK */, data, sizeof(data))) return false; // skip for WPA3 SAE, sizeof/2 if supporting SAE but using WPA + // for WPA3 SAE: memcpy((uint8_t *)data + sizeof(uint16_t), pass, len); cyw_ioctl_iovar_set("sae_password", data, sizeof(data)); + // resume if not using auth + val = 1; if (!cyw_ioctl_set(20 /* SET_INFRA */, (uint8_t *)&val, sizeof(val))) return false; + val = 0; // auth type: 0 for open, 3 for SAE + if (!cyw_ioctl_set(22 /* SET_AUTH */, (uint8_t *)&val, sizeof(val))) return false; + val = 1; // MFP capable: 1 for yes, 0 for no; recommended to be set for WPA2+ (2 for 'required', WPA3) + cyw_ioctl_iovar_set("mfp", (uint8_t *)&val, sizeof(val)); // Old chipsets do not support MFP + val = 0x80; // auth type: 0 for none, 4 for WPA PSK, 0x80 for WPA2 PSK, 0x40000 for WPA3 SAE PSK + if (!cyw_ioctl_set(165 /* SET_WPA_AUTH */, (uint8_t *)&val, sizeof(val))) return false; + len = strlen(ssid); + data[0] = (uint32_t) len; + memcpy((uint8_t *)&data[1], ssid, len); + if (!cyw_ioctl_set(26 /* SET_SSID */, data, len + sizeof(uint32_t))) return false; + return true; +} + +static bool cyw_wifi_disconnect(void) { + return cyw_ioctl_set(52 /* DISASSOC */, NULL, 0); +} + +// For AP functions, we use explicit ifc selection; both for clarity and maintenance, as some actions are performed on ifc 0, with or without a bss_index, and others are performed on ifc 1 + +static bool cyw_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { + uint32_t data[64/4 + 2]; // max pass length: 64 for WPA, 128 for WPA3 SAE + unsigned int len; + uint32_t val; + // CHIP DEPENDENCY + // RPi set the AMPDU parameter for AP (window size = 2) ***************** + // val = 2 ; cyw_ioctl_iovar_set_(0, "ampdu_ba_wsize", (uint8_t *)&val, sizeof(val)); + // some chips might require to turn APSTA off and issue a SET_AP IOCTL + len = strlen(ssid); + data[0] = 1; // bss index 1 = AP + data[1] = (uint32_t) len; + memcpy((uint8_t *)&data[2], ssid, len); + // TODO(scaprile): this takes some time to process, or requires a delay before doing it + if (!cyw_ioctl_iovar_set_(0, "bsscfg:ssid", (uint8_t *)&data, len + 2 * sizeof(uint32_t))) return false; + // TODO(scaprile): but sometimes this one takes some time to process + val = (uint32_t) channel; if (!cyw_ioctl_set_(0, 30 /* SET_CHANNEL */, (uint8_t *)&val, sizeof(val))) return false; + data[0] = 1; // bss index 1 = AP + data[1] = 0x00400004; // security type: 0 for none, 0x00200002 for WPA, 0x00400004 for WPA2, 0x01000004 for WPA3, 0x01400004 for mixed WPA2/WPA3, 0x00400006 for mixed WPA/WPA2 + // NOTE(): WHD writes & 0xFF if WPS is not enabled (?) + if (!cyw_ioctl_iovar_set_(0, "bsscfg:wsec", (uint8_t *)&data, 2 * sizeof(uint32_t))) return false; + val = 1; // MFP capable: 1 for yes, 0 for no; recommended to be set for WPA2+ (2 for 'required', WPA3) + cyw_ioctl_iovar_set_(1, "mfp", (uint8_t *)&val, sizeof(val)); // Old chipsets do not support MFP + mg_delayms(2); // allow radio firmware to be ready + // skip if not using auth + // WPA, WPA2, mixed WPA/WPA2, mixed WPA2/WPA3 + // NOTE(): WHD does not set SAE password for shared WPA2/WPA3, same do we + memset(data, 0, sizeof(data)); + len = strlen(pass); + ((uint16_t *)data)[0] = (uint16_t) len; // skip for WPA3 SAE + ((uint16_t *)data)[1] = 1; // indicates wireless security key, skip for WPA3 SAE + memcpy((uint8_t *)data + 2 * sizeof(uint16_t), pass, len); // skip for WPA3 SAE + if (!cyw_ioctl_set_(1, 268 /* SET_WSEC_PMK */, data, sizeof(data))) return false; // skip for WPA3 SAE, sizeof/2 if supporting SAE but using WPA + /* for WPA3 SAE: + memcpy((uint8_t *)data + sizeof(uint16_t), pass, len); + cyw_ioctl_iovar_set_(1, "sae_password", data, sizeof(data)); */ + /* for WPA3 or mixed WPA2/WPA3: + val = 5 ; cyw_ioctl_iovar_set_(1, "sae_max_pwe_loop", (uint8_t *)&val, sizeof(val)); // Some chipsets do not support this */ + // resume if not using auth + + data[0] = 1; // bss index 1 = AP + data[1] = 0x80; // auth type: 0 for none, 4 for WPA PSK, 0x80 for WPA2 PSK, 0x40000 for WPA3 SAE PSK; ored if mixed + if (!cyw_ioctl_iovar_set_(0, "bsscfg:wpa_auth", (uint8_t *)&data, 2 * sizeof(uint32_t))) return false; + + val = 1 /* auto */; if (!cyw_ioctl_set_(1, 110 /* SET_GMODE */, (uint8_t *)&val, sizeof(val))) return false; + // Set multicast tx rate to 11Mbps, may fail in some chipsets, we are enforcing it + val = 11000000 / 500000; if (!cyw_ioctl_iovar_set_(1, "2g_mrate", (uint8_t *)&val, sizeof(val))) return false; + val = 1; if (!cyw_ioctl_set_(1, 78 /* SET_DTIMPRD */, (uint8_t *)&val, sizeof(val))) return false; + data[0] = 1; // bss index 1 = AP + data[1] = 1; // UP + // TODO(scaprile): this takes a long time to process + if (!cyw_ioctl_iovar_set_(0, "bss", (uint8_t *)&data, 2 * sizeof(uint32_t))) return false; + return true; +} + +static bool cyw_wifi_ap_stop(void) { + uint32_t data[2]; + data[0] = 1; // bss index 1 = AP + data[1] = 0; // DOWN + if (!cyw_ioctl_iovar_set_(0, "bss", (uint8_t *)&data, 2 * sizeof(uint32_t))) return false; + // DO WE NEED TO CLEAR CHANNEL ??? + // CHIP DEPENDENCY + //val = 8 ; cyw_ioctl_iovar_set_(0, "ampdu_ba_wsize", (uint8_t *)&val, sizeof(val)); + return true; +} + +// WLAN scan handling + +#pragma pack(push, 1) +// in little endian + +struct wifi_scan_opt { + uint32_t version; + uint16_t action; + uint16_t _; + uint32_t ssid_len; + uint8_t ssid[32]; + uint8_t bssid[6]; + int8_t bss_type; + int8_t scan_type; + int32_t nprobes; + int32_t active_time; + int32_t passive_time; + int32_t home_time; + int32_t channel_num; + uint16_t channel_list[1]; +}; +#pragma pack(pop) + +static bool cyw_wifi_scan(void) { + struct wifi_scan_opt opts; + memset(&opts, 0, sizeof(opts)); + opts.version = 1; + opts.action = 1; // start + opts._ = 0; + memset(opts.bssid, 0xff, sizeof(opts.bssid)); + opts.bss_type = 2; // any + opts.nprobes = -1; + opts.active_time = -1; + opts.passive_time = -1; + opts.home_time = -1; + opts.channel_num = 0; + opts.channel_list[0] = 0; + return cyw_ioctl_iovar_set("escan", (uint8_t *)&opts, sizeof(opts)); +} + + +#pragma pack(push, 1) +// in little endian + +struct scan_bss { + uint32_t version; // version field + uint32_t length; // byte length of data in this record, starting at version and including IEs + uint8_t BSSID[6]; // Unique 6-byte MAC address + uint16_t beacon_period; // Interval between two consecutive beacon frames. Units are Kusec + uint16_t capability; // Capability information + uint8_t SSID_len; // SSID length + uint8_t SSID[32]; // Array to store SSID + uint8_t reserved1[1]; // Reserved(padding) + uint32_t rateset_count; // Count of rates in this set + uint8_t rateset_rates[16]; // rates in 500kbps units, higher bit set if basic + uint16_t chanspec; // Channel specification for basic service set + uint16_t atim_window; // Announcement traffic indication message window size. Units are Kusec + uint8_t dtim_period; // Delivery traffic indication message period + uint8_t reserved2[1]; // Reserved(padding) + int16_t RSSI; // receive signal strength (in dBm) + int8_t phy_noise; // noise (in dBm) + uint8_t n_cap; // BSS is 802.11n Capable + uint8_t reserved3[2]; // Reserved(padding) + uint32_t nbss_cap; // 802.11n BSS Capabilities (based on HT_CAP_*) + uint8_t ctl_ch; // 802.11n BSS control channel number + uint8_t reserved4[3]; // Reserved(padding) + uint32_t reserved32[1]; // Reserved for expansion of BSS properties + uint8_t flags; // flags + uint8_t vht_cap; // BSS is vht capable + uint8_t reserved5[2]; // Reserved(padding) + uint8_t basic_mcs[16]; // 802.11N BSS required MCS set + uint16_t ie_offset; // offset at which IEs start, from beginning + uint16_t reserved16[1]; // Reserved(padding) + uint32_t ie_length; // byte length of Information Elements + int16_t SNR; // Average SNR during frame reception +}; + +struct scan_result { + uint32_t buflen; + uint32_t version; + uint16_t sync_id; + uint16_t bss_count; + struct scan_bss bss[1]; +}; + +#pragma pack(pop) + +// CHIP DEPENDENCY +#define CYW_BSS_BANDMASK 0xc000 +#define CYW_BSS_BAND2G 0 +// + +static void cyw_handle_scan_result(uint32_t status, struct scan_result *data, size_t len) { + MG_VERBOSE(("scan event, status: %ld", status)); + if (status == 0) { // SUCCESS + MG_VERBOSE(("scan complete")); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_END, NULL); + } else if (status == 8) { // PARTIAL + struct mg_wifi_scan_bss_data bss; + struct scan_bss *sbss = data->bss; + unsigned int band = sbss->chanspec & CYW_BSS_BANDMASK; + if (data->version != 109 || data->bss_count != 1) { + MG_ERROR(("Unsupported: %lu %u", data->version, data->bss_count)); + return; + } + if (sbss->length > len - offsetof(struct scan_result, bss) || sbss->SSID_len > sizeof(sbss->SSID) || sbss->ie_offset < sizeof(*sbss) || sbss->ie_offset > (sizeof(*sbss) + sbss->ie_length) || sbss->ie_offset + sbss->ie_length > sbss->length) + return; // silently discard malformed data + if (!(sbss->flags & MG_BIT(2))) return; // RSSI_ONCHANNEL, ignore off-channel results + bss.SSID = mg_str_n(sbss->SSID, sbss->SSID_len); + bss.BSSID = sbss->BSSID; + bss.RSSI = (int8_t)sbss->RSSI; + bss.has_n = sbss->n_cap != 0; + bss.channel = bss.has_n ? sbss->ctl_ch : (uint8_t)(sbss->chanspec & 0xff); // n 40MHz vs a/b/g and 20MHz + bss.band = band & CYW_BSS_BAND2G ? MG_WIFI_BAND_2G : MG_WIFI_BAND_5G; + bss.security = (sbss->capability & MG_BIT(4) /* CAP_PRIVACY */) ? MG_WIFI_SECURITY_WEP : MG_WIFI_SECURITY_OPEN; + { // travel IEs (Information Elements) in search of security definitions + const uint8_t wot1[4] = {0x00, 0x50, 0xf2, 0x01}; // WPA_OUI_TYPE1 + uint8_t *ie = (uint8_t *)sbss + sbss->ie_offset; + int bytes = (int) sbss->ie_length; + while (bytes > 0 && ie[1] + 2 < bytes) { // ie[0] -> type, ie[1] -> bytes from ie[2] + if (ie[0] == 48 /* IE_ID_RSN */) bss.security |= MG_WIFI_SECURITY_WPA2; + if (ie[0] == 221 /* IE_ID_VENDOR_SPECIFIC */ && memcmp(&ie[2], wot1, 4) == 0) + bss.security |= MG_WIFI_SECURITY_WPA; + ie += ie[1] + 2; + bytes -= ie[1] + 2; + } + } + MG_VERBOSE(("BSS: %.*s (%u) (%M) %d dBm %u", bss.SSID.len, bss.SSID.buf, bss.channel, mg_print_mac, bss.BSSID, (int) bss.RSSI, bss.security)); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_RESULT, &bss); + } else { + MG_ERROR(("scan error")); + } +} +// clang-format on + +// IOCTL stuff. All values read and written are in little endian format + +static uint16_t s_ioctl_reqid; + +// CDC handler for waiting loop +static uint8_t *s_ioctl_resp; +static bool s_ioctl_err; + +static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len) { + uint8_t *resp = (uint8_t *) cdc + sizeof(*cdc); + MG_VERBOSE(("%u bytes CDC frame", len)); + if ((cdc->flags >> 16) != s_ioctl_reqid) return; + if (cdc->flags & 1) { + MG_ERROR(("IOCTL error: %ld", -cdc->status)); + s_ioctl_err = true; + return; + } + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump((void *) cdc, len); + MG_DEBUG(("IOCTL result: %02x %02x %02x %02x ...", resp[0], resp[1], resp[2], + resp[3])); + s_ioctl_resp = resp; +} +// NOTE(): alt no loop handler dispatching IOCTL response to current handler: +// static void *s_ioctl_hnd; *s_ioctl_hnd(ioctl, len); +// app is a state machine calling get/sets and advancing via these callbacks + +#pragma pack(push, 1) +// all little endian + +struct ctrl_hdr { + struct sdpcm_hdr sdpcm; + struct cdc_hdr cdc; +}; + +#pragma pack(pop) + +// IOCTL command send +static void cyw_ioctl_send_cmd(unsigned int ifc, unsigned int cmd, bool set, + size_t len) { + struct ctrl_hdr *hdr = (struct ctrl_hdr *) txdata; + uint16_t txlen = (uint16_t) (len + sizeof(*hdr)); + memset(txdata, 0, sizeof(*hdr)); + hdr->cdc.cmd = cmd; + hdr->cdc.olen = (uint16_t) len; + // hdr->cdc.ilen = 0; // actually zeroed above + hdr->cdc.flags = ((uint32_t) ++s_ioctl_reqid << 16) | ((ifc & 0xf) << 12) | + (set ? MG_BIT(1) : 0); + hdr->sdpcm.len = txlen; + hdr->sdpcm._len = (uint16_t) ~txlen; + hdr->sdpcm.sw_hdr.sequence = ++s_tx_seqno; + hdr->sdpcm.sw_hdr.channel_and_flags = CYW_SDPCM_CTRL_HDR; + hdr->sdpcm.sw_hdr.header_length = offsetof(struct ctrl_hdr, cdc); + cyw_bus_tx(txdata, txlen); +} + +// just send respective commands, response handled via CDC handler +static void cyw_ioctl_send_get(unsigned int ifc, unsigned int cmd) { + cyw_ioctl_send_cmd(ifc, cmd, false, 0); +} + +static void cyw_ioctl_send_set(unsigned int ifc, unsigned int cmd, void *data, + size_t len) { + if (data != NULL && len > 0) + memcpy((uint8_t *) txdata + sizeof(struct ctrl_hdr), data, len); + cyw_ioctl_send_cmd(ifc, cmd, true, (uint16_t) len); +} + +static void cyw_ioctl_send_iovar_get(unsigned int ifc, char *var, size_t len) { + unsigned int namelen = strlen(var) + 1; // include '\0' + // cmd = GET IOVAR, "set" the name... + cyw_ioctl_send_set(ifc, 262, var, len > namelen ? len : namelen); +} + +static void cyw_ioctl_send_iovar_set2(unsigned int ifc, char *var, void *data1, + size_t len1, void *data2, size_t len2) { + struct ctrl_hdr *hdr = (struct ctrl_hdr *) txdata; + unsigned int namelen = strlen(var) + 1; // include '\0' + uint16_t txlen, payload_len = (uint16_t) (namelen + len1 + len2); + memcpy((uint8_t *) txdata + sizeof(*hdr), var, namelen); + memcpy((uint8_t *) txdata + namelen + sizeof(*hdr), data1, len1); + if (data2 != NULL) + memcpy((uint8_t *) txdata + namelen + sizeof(*hdr) + len1, data2, len2); + txlen = (uint16_t) (payload_len + sizeof(*hdr)); + cyw_ioctl_send_cmd(ifc, 263, true, txlen); // cmd = SET IOVAR +} + +static void cyw_ioctl_send_iovar_set(unsigned int ifc, char *var, void *data, + size_t len) { + cyw_ioctl_send_iovar_set2(ifc, var, data, len, NULL, 0); +} + +// wait for a response, meanwhile delivering received frames and events +static bool cyw_ioctl_wait(void) { + unsigned int times = 6000; + s_ioctl_resp = NULL; + s_ioctl_err = false; + while (s_ioctl_resp == NULL && !s_ioctl_err && times-- > 0) + cyw_poll(); // TODO(scaprile): review wait/sleep strategy (this loop is executed only when initializing/acting on the chip) + MG_DEBUG(("resp: %lp, err: %c, times: %d", s_ioctl_resp, + s_ioctl_err ? '1' : '0', (int) times)); + return s_ioctl_resp != NULL; +} + +static bool cyw_ioctl_waitdata(void *data, size_t len) { + if (!cyw_ioctl_wait()) return false; + memcpy(data, s_ioctl_resp, len); + return true; +} + +// send respective commands, wait for a response or timeout +static bool cyw_ioctl_get_(unsigned int ifc, unsigned int cmd, void *data, + size_t len) { + cyw_ioctl_send_get(ifc, cmd); + return cyw_ioctl_waitdata(data, len); +} +static bool cyw_ioctl_set_(unsigned int ifc, unsigned int cmd, void *data, + size_t len) { + cyw_ioctl_send_set(ifc, cmd, data, len); + return cyw_ioctl_wait(); +} + +static bool cyw_ioctl_iovar_get_(unsigned int ifc, char *var, void *data, + size_t len) { + cyw_ioctl_send_iovar_get(ifc, var, len); + return cyw_ioctl_waitdata(data, len); +} +static bool cyw_ioctl_iovar_set2_(unsigned int ifc, char *var, void *data1, + size_t len1, void *data2, size_t len2) { + cyw_ioctl_send_iovar_set2(ifc, var, data1, len1, data2, len2); + return cyw_ioctl_wait(); +} +static bool cyw_ioctl_iovar_set_(unsigned int ifc, char *var, void *data, + size_t len) { + return cyw_ioctl_iovar_set2_(ifc, var, data, len, NULL, 0); +} + +// CYW43 chipset specifics. All values read and written are in little endian +// format + +#pragma pack(push, 1) +// all little endian + +struct cyw_country { + uint32_t a; + int32_t rev; + uint32_t c; +}; + +struct clm_hdr { + uint16_t flag; + uint16_t type; + uint32_t len; + uint32_t crc; +}; + +#pragma pack(pop) + +// worlwide rev0, try rev 17 for 4343W +static const uint32_t country_code = 'X' + ('X' << 8) + (0 << 16); + +static bool cyw_spi_init(); + +// clang-format off +static bool cyw_init(struct mg_tcpip_driver_cyw_firmware *fw, uint8_t *mac) { + uint32_t val = 0; + if (!cyw_spi_init()) return false; // BUS DEPENDENCY + // BT-ENABLED DEPENDENCY + // set Wi-Fi up + val = 0 /* disable */; cyw_ioctl_iovar_set("bus:txglom", (uint8_t *)&val, sizeof(val)); + val = 1 /* on */; cyw_ioctl_iovar_set("apsta", (uint8_t *)&val, sizeof(val)); + // CHIP DEPENDENCY + val = 8 ; cyw_ioctl_iovar_set("ampdu_ba_wsize", (uint8_t *)&val, sizeof(val)); + val = 4 ; cyw_ioctl_iovar_set("ampdu_mpdu", (uint8_t *)&val, sizeof(val)); + val = 0 /* 8K */; cyw_ioctl_iovar_set("ampdu_rx_factor", (uint8_t *)&val, sizeof(val)); + // + { + struct cyw_country c; + unsigned int rev = (unsigned int) (country_code >> 16) & 0xffff; + c.c = c.a = country_code & 0xffff; + c.rev = rev == 0 ? -1 : (int32_t) rev; // if rev is 0, set it to -1, the chip will use any NVRAM/OTP configured aggregate or default to rev 0 + cyw_ioctl_iovar_set("country", (void *)&c, sizeof(c)); + } // this takes some time to process + { // so do some retries while enabling events of interest + // we care for SET_SSID(0), JOIN(1), AUTH(3), DEAUTH(5), DISASSOC_IND(12), LINK(16), PSK_SUP(46), SCAN_RESULT(69); all < 128 + uint32_t data[128/8/4 + 1]; + data[0] = 0; // bss index: 0 = STA + memset(&data[1], 0, 128/8); // mark all as not desired + data[1] = MG_BIT(0) | MG_BIT(1) | MG_BIT(3) | MG_BIT(5) | MG_BIT(12) | MG_BIT(16); // events 0 to 31 + data[2] = MG_BIT(46 - 32); // events 32 to 63 + data[3] = MG_BIT(69 - 64); // events 64 to 95 + unsigned int times = 100; + while (times --) + if (cyw_ioctl_iovar_set("bsscfg:event_msgs", (uint8_t *)data, sizeof(data))) break; + if (times == ~0) return false; + } + val = 0; if (!cyw_ioctl_set(64 /* SET_ANTDIV */, (uint8_t *)&val, sizeof(val))) return false; + if (!cyw_ioctl_set(2 /* UP, interface up */, NULL, 0)) return false; + // use PM2 power saving for max throughput + val = 200 /* ms */; if (!cyw_ioctl_iovar_set("pm2_sleep_ret", (uint8_t *)&val, sizeof(val))) return false; + // set beacon intervals to reduce power consumption while associated to an AP but idle + val = 1; if (!cyw_ioctl_iovar_set("bcn_li_bcn", (uint8_t *)&val, sizeof(val))) return false; + val = 1; if (!cyw_ioctl_iovar_set("bcn_li_dtim", (uint8_t *)&val, sizeof(val))) return false; + val = 10; if (!cyw_ioctl_iovar_set("assoc_listen", (uint8_t *)&val, sizeof(val))) return false; + val = 1 /* auto */; if (!cyw_ioctl_set(110 /* SET_GMODE */, (uint8_t *)&val, sizeof(val))) return false; + val = 0 /* any */; if (!cyw_ioctl_set(142 /* SET_BAND */, (uint8_t *)&val, sizeof(val))) return false; + if (mg_log_level >= MG_LL_DEBUG) { + char text[256]; // this is huge, but we're just starting up + if (cyw_ioctl_iovar_get("ver", (uint8_t *)text, sizeof(text))) { + unsigned int len = strnlen(text, sizeof(text)); + MG_DEBUG(("Firmware:\n%.*s", len, text)); + } + text[0] = '\0'; + if (cyw_ioctl_iovar_get("clmver", (uint8_t *)text, sizeof(text)) && text[0] != '\0') { + unsigned int len = strnlen(text, sizeof(text)); + MG_DEBUG(("CLM:\n%.*s", len, text)); + } + } + { + if(cyw_ioctl_iovar_get("cur_etheraddr", mac, 6)) { + MG_DEBUG(("MAC: %M", mg_print_mac, mac)); + } else { + MG_ERROR(("read MAC failed")); + } + } + return true; +} +// clang-format on + +static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, + size_t nvramlen); + +static bool cyw_load_firmware(struct mg_tcpip_driver_cyw_firmware *fw) { + return cyw_load_fwll((void *) fw->code_addr, fw->code_len, + (void *) fw->nvram_addr, fw->nvram_len); +} + +// clang-format off +static bool cyw_load_clmll(void *data, size_t len) { + unsigned int sent = 0, offset = 0; + struct clm_hdr hdr = { + .flag = 1 << 12 /* DLOAD_HANDLER_VER */ | MG_BIT(1) /* DL_BEGIN */, + .type = 2, + .crc = 0}; + while (sent < len) { + unsigned int bytes = len - sent; + if (bytes > 1024) bytes = 1024; + if (sent + bytes >= len) hdr.flag |= MG_BIT(2); // DL_END; + hdr.len = bytes; + if (!cyw_ioctl_iovar_set2_(0, "clmload", (void *) &hdr, sizeof(hdr), (uint8_t *) data + offset, bytes)) + break; + sent += bytes; + offset += bytes; + hdr.flag &= ~MG_BIT(1); // DL_BEGIN + } + return sent >= len; +} +// clang-format on + +static bool cyw_load_clm(struct mg_tcpip_driver_cyw_firmware *fw) { + return cyw_load_clmll((void *) fw->clm_addr, fw->clm_len); +} + +// CYW43 chip backplane specifics. All values read and written are in little +// endian format + +// Access to chip backplane is done windowed in 32KB banks +// - addr = area base address + register offset +// - set the window address to addr & ~ADDRMSK +// - access addr & ADDRMSK for non-32-bit quantities +// - if accesing 32-bit quantities, do it on (addr & ADDRMSK) | ACCSS4B +#define CYW_CHIP_CHIPCOMMON 0x18000000 +#define CYW_CHIP_BCKPLN_WINSZ 0x8000 +#define CYW_CHIP_BCKPLN_ADDRMSK 0x7fff +#define CYW_CHIP_BCKPLN_ACCSS4B MG_BIT(15) +#define CYW_CHIP_BCKPLN_WRAPPOFF 0x100000 +#define CYW_CHIP_BCKPLN_SPIMAX 64 +#define CYW_CHIP_BCKPLN_SDIOMAX 1536 + +// CHIP DEPENDENCY +#define CYW_CHIP_ARMCORE_BASE (CYW_CHIP_CHIPCOMMON + 0x3000) +#define CYW_CHIP_SOCSRAM_BASE (CYW_CHIP_CHIPCOMMON + 0x4000) +#define CYW_CHIP_ARMCORE (CYW_CHIP_ARMCORE_BASE + CYW_CHIP_BCKPLN_WRAPPOFF) +#define CYW_CHIP_SOCSRAM (CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_BCKPLN_WRAPPOFF) +#define CYW_CHIP_ATCMRAM_BASE 0 +#define CYW_CHIP_RAM_SIZE 0x80000 +// + +#define CYW_CHIP_ADDRLOW 0x1000a +#define CYW_CHIP_ADDRMID 0x1000b +#define CYW_CHIP_ADDRHIGH 0x1000c +#define CYW_CHIP_SPIFRCTRL 0x1000d +#define CYW_CHIP_CLOCKCSR 0x1000e +#define CYW_CHIP_PULLUP 0x1000f +#define CYW_CHIP_WAKEUPCTL 0x1001e +#define CYW_CHIP_SLEEPCSR 0x1001f + +#define CYW_CHIP_SOCSRAM_BANKXIDX 0x010 +#define CYW_CHIP_SOCSRAM_BANKXPDA 0x044 +#define CYW_CHIP_AI_IOCTRL 0x408 +#define CYW_CHIP_AI_RESETCTRL 0x800 + +static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, + uint16_t len); +static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, + uint16_t len); + +// clang-format off +// set backplane window to requested area. +static void cyw_set_backplane_window(uint32_t addr) { + uint32_t val; + addr &= ~CYW_CHIP_BCKPLN_ADDRMSK; + val = (addr >> 24) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRHIGH, &val, 1); + val = (addr >> 16) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRMID, &val, 1); + val = (addr >> 8) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRLOW, &val, 1); +} + +static bool cyw_core_reset(uint32_t core_base, bool check) { + uint32_t val = 0; + // core disabled after chip reset + cyw_set_backplane_window(core_base); // set backplane window for requested area; we do know offsets fall within that window + // possible CHIP DEPENDENCY: AI_RESETSTATUS check and wait (instead of these cool reads) to ensure backplane operations end + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = MG_BIT(1) | MG_BIT(0) /* SICF_FGC | SICF_CLOCK_EN */; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // reset + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // release reset + mg_delayms(1); + val = MG_BIT(0) /* SICF_CLOCK_EN */; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + mg_delayms(1); + + if (check) { + // Verify only clock is enabled + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + if ((val & (MG_BIT(1) | MG_BIT(0)) /* SICF_FGC | SICF_CLOCK_EN) */) != MG_BIT(0)) return false; + // Verify it is not in reset state + cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + if (val & MG_BIT(0)) return false; // AIRC_RESET + } + return true; +} + +static void cyw_socram_init(void) { + uint32_t val; + // CHIP DEPENDENCY: disable remap for SRAM_3 + cyw_set_backplane_window(CYW_CHIP_SOCSRAM_BASE); // set backplane window for requested area; we do know offsets fall within that window + val = 0x03; cyw_spi_write(CYW_SD_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXIDX) & CYW_CHIP_BCKPLN_ADDRMSK) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); + val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXPDA) & CYW_CHIP_BCKPLN_ADDRMSK) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); +} + +// transfer is fractioned in bus-to-backplane-size units within backplane windows +static void cyw_load_data(uint32_t dest, void *data, size_t len) { + size_t sent = 0, offset = 0; + uint32_t last_addr = ~0; + while (sent < len) { + size_t bytes = len - sent, avail; + uint32_t addr = dest + offset; + if (addr - last_addr >= CYW_CHIP_BCKPLN_WINSZ || last_addr == ~0) { + cyw_set_backplane_window(addr); // set backplane window for requested area + last_addr = addr & ~CYW_CHIP_BCKPLN_ADDRMSK; + } + addr &= CYW_CHIP_BCKPLN_ADDRMSK; + avail = CYW_CHIP_BCKPLN_WINSZ - (unsigned int) addr; + if (bytes > avail) bytes = avail; + // BUS DEPENDENCY: max bus to backplane transfer size + if (bytes > CYW_CHIP_BCKPLN_SPIMAX) bytes = CYW_CHIP_BCKPLN_SPIMAX; + cyw_spi_write(CYW_SD_FUNC_CHIP, addr | CYW_CHIP_BCKPLN_ACCSS4B, (uint8_t *)data + offset, bytes); + sent += bytes; + offset += bytes; + } +} + +// CHIP DEPENDENCY: no SOCSRAM base address; start address in fwdata image (Cortex-R4 chips) +static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, size_t nvramlen) { + uint32_t val = ((~(nvramlen / 4) & 0xffff) << 16) | (nvramlen / 4); // ~len len in 32-bit words + cyw_core_reset(CYW_CHIP_SOCSRAM, false); // cores were disabled at chip reset + cyw_socram_init(); + cyw_load_data(CYW_CHIP_ATCMRAM_BASE, fwdata, fwlen); + mg_delayms(5); // ************ CHECK IF THIS IS ACTUALLY NEEDED + // Load NVRAM and place 'length ~length' at the end; end of chip RAM + { + const uint32_t start = CYW_CHIP_RAM_SIZE - 4 - nvramlen; + cyw_load_data(start, nvramdata, nvramlen); // nvramlen must be a multiple of 4 + // RAM_SIZE is a multiple of WINSZ, so the place for len ~len will be at the end of the window + cyw_spi_write(CYW_SD_FUNC_CHIP, (CYW_CHIP_BCKPLN_WINSZ - 4) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); + } + // Reset ARM core and check it starts + if (!cyw_core_reset(CYW_CHIP_ARMCORE, true)) return false; + return true; +} +// clang-format on + +// CYW43 SPI bus specifics + +#define CYW_BUS_SPI_BUSCTRL 0x00 // 4 regs, 0 to 3 +#define CYW_BUS_SPI_INT 0x04 // 2 regs, 4 to 5 +#define CYW_BUS_SPI_INTEN 0x06 // 16-bit register +#define CYW_BUS_SPI_STATUS 0x08 // 32-bit register +#define CYW_BUS_SPI_TEST 0x14 // 32-bit register +#define CYW_BUS_SPI_RESPDLY_F1 0x1d // 8-bit register, F1: chip + +#define CYW_BUS_STS_LEN(x) ((x >> 9) & 0x7ff) + +// clang-format off +static size_t cyw_spi_poll(uint8_t *response) { + size_t len; + uint32_t res; + // SPI poll + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &res, sizeof(res)); + if (res == ~0 || !(res & MG_BIT(8) /* packet available */ )) return 0; + len = CYW_BUS_STS_LEN(res); + if (len == 0) { // just ack IRQ + uint16_t val = 1; + cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_SPIFRCTRL, &val, 1); + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); + cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); + return 0; + } + cyw_spi_read(CYW_SD_FUNC_WLAN, 0, response, len); + return len; +} + +// BUS DEPENDENCY: name is generic but function is bus dependent +static size_t cyw_bus_tx(uint32_t *data, uint16_t len) { + while (len & 3) data[len++] = 0; // SPI 32-bit padding (SDIO->64-byte) + return cyw_spi_write(CYW_SD_FUNC_WLAN, 0, data, len) ? len: 0; +} + +// this can be integrated in lowest level SPI read/write _driver_ functions +// (those calling hal SPI transaction functions), though is only used at start +uint32_t sw16_2(uint32_t data) { + return ((uint32_t)mg_htons((uint16_t)(data >> 16)) << 16) + mg_htons((uint16_t)data); +} + +// DS 4.2.2 Table 6: signal we're working in 16-bit mode +#define CYW_SD_16bMODE MG_BIT(2) // arbitrary bit out of the FUNC space + +static bool cyw_spi_init() { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + uint32_t val = 0; + // DS 4.2.3 Boot-Up Sequence; WHD: other chips might require more effort + unsigned int times = 51; + while (times--) { + cyw_spi_read(CYW_SD_FUNC_BUS | CYW_SD_16bMODE, CYW_BUS_SPI_TEST, &val, sizeof(val)); + if (sw16_2(val) == 0xFEEDBEAD) break; + mg_delayms(1); + } + if (times == ~0) return false; + // DS 4.2.3 Table 6. Chip starts in 16-bit little-endian mode. + // Configure SPI and switch to 32-bit big-endian mode: + // - High-speed mode + // - IRQ POLARITY high + // - SPI RESPONSE DELAY 4 bytes time [not in DS] TODO(scaprile): logic ana + // - Status not sent after command, IRQ with status + val = sw16_2(0x000204b3); // 4 reg content + cyw_spi_write(CYW_SD_FUNC_BUS | CYW_SD_16bMODE, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_TEST, &val, sizeof(val)); + if (val != 0xFEEDBEAD) return false; + val = 4; cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_RESPDLY_F1, &val, 1); + val = 0x99; // clear error bits DATA_UNAVAILABLE, COMMAND_ERROR, DATA_ERROR, F1_OVERFLOW + cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, 1); + val = 0x00be; // Enable IRQs F2_F3_FIFO_RD_UNDERFLOW, F2_F3_FIFO_WR_OVERFLOW, COMMAND_ERROR, DATA_ERROR, F2_PACKET_AVAILABLE, F1_OVERFLOW + // BT-ENABLED DEPENDENCY: add F1_INTR (bit 13) + cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + + // chip backplane is ready, initialize it + // request ALP (Active Low Power) clock + val = MG_BIT(3) /* ALP_REQ */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + // BT-ENABLED DEPENDENCY + times = 10; + while (times--) { + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + if (val & MG_BIT(6)) break; // ALP_AVAIL + mg_delayms(1); + } + if (times == ~0) return false; + // clear request + val = 0; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + cyw_set_backplane_window(CYW_CHIP_CHIPCOMMON); // set backplane window to start of CHIPCOMMON area + cyw_spi_read(CYW_SD_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); + MG_INFO(("WLAN chip is CYW%u", *((uint16_t *)&val))); + + // Load firmware (code and NVRAM) + if (!cyw_load_firmware(d->fw)) return false; + + // Wait for High Throughput (HT) clock ready + times = 50; + while (times--) { + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + if (val & MG_BIT(7)) break; // HT_AVAIL + mg_delayms(1); + } + if (times == ~0) return false; + // Wait for backplane ready + times = 1000; + while (times--) { + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); + if (val & MG_BIT(5)) break; // F2_RX_READY + mg_delayms(1); + } + if (times == ~0) return false; + + // CHIP DEPENDENCY + // Enable save / restore + // Configure WakeupCtrl, set HT_AVAIL in CLOCK_CSR + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); + val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); +#if 0 + // Set BRCM_CARDCAP to CMD_NODEC. NOTE(): This is probably only necessary for SDIO, not SPI + val = MG_BIT(3); cyw_spi_write(CYW_SD_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); +#endif + // Force HT request to chip backplane + val = MG_BIT(1) /* FORCE_HT */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + // Enable Keep SDIO On (KSO) + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + if (!(val & MG_BIT(0))) { + val |= MG_BIT(0); cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + } + // The SPI bus can be configured for sleep (KSO controls wlan block sleep) + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + val &= ~MG_BIT(7) /* WAKE_UP */; cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + // Set SPI bus sleep + val = 0x0f; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + + // Clear pullups. NOTE(): ? + val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + // Clear possible data unavailable error + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + if (val & MG_BIT(0)) cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + + // Load CLM blob + if (!cyw_load_clm(d->fw)) return false; + + return true; +} +// clang-format on + +// gSPI, DS 4.2.1 Fig.12 +#define CYW_SD_LEN(x) ((x) &0x7FF) // bits 0-10 +#define CYW_SD_ADDR(x) (((x) &0x1FFFF) << 11) // bits 11-27, +#define CYW_SD_FUNC(x) (((x) &3) << 28) // bits 28-29 +#define CYW_SD_INC MG_BIT(30) +#define CYW_SD_WR MG_BIT(31) + +static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; + uint32_t hdr = CYW_SD_WR | CYW_SD_INC | CYW_SD_FUNC(f) | CYW_SD_ADDR(addr) | + CYW_SD_LEN(len); // gSPI header + // TODO(scaprile): check spin in between and timeout values, return false + if (f == CYW_SD_FUNC_WLAN) { + uint32_t val = 0; + while ((val & MG_BIT(5)) != MG_BIT(5)) // F2 rx ready (FIFO ready) + cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); + } + if (f & CYW_SD_16bMODE) + hdr = sw16_2(hdr); // swap half-words in 16-bit little-endian mode + + s->begin(NULL); + s->txn(NULL, (uint8_t *) &hdr, NULL, sizeof(hdr)); + if (len <= 4) { + uint32_t pad = 0; + memcpy(&pad, data, len); + s->txn(NULL, (uint8_t *) &pad, NULL, sizeof(pad)); + } else { + s->txn(NULL, (uint8_t *) data, NULL, len); + } + s->end(NULL); + return true; +} + +// will write 32-bit aligned quantities to data if f == CYW_SD_FUNC_WLAN +static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + struct mg_tcpip_driver_cyw_data *d = + (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; + uint32_t padding = + f == CYW_SD_FUNC_CHIP + ? 4 + : 0; // add padding to chip backplane reads as a response delay + uint32_t hdr = CYW_SD_INC | CYW_SD_FUNC(f) | CYW_SD_ADDR(addr) | + CYW_SD_LEN(len + padding); // gSPI header + if (f == CYW_SD_FUNC_WLAN && (len & 3)) + len = (len + 4) & ~3; // align WLAN transfers to 32-bit + if (f & CYW_SD_16bMODE) + hdr = sw16_2(hdr); // swap half-words in 16-bit little-endian mode + + s->begin(NULL); + s->txn(NULL, (uint8_t *) &hdr, NULL, sizeof(hdr)); + if (f == CYW_SD_FUNC_CHIP) { + uint32_t pad; + s->txn(NULL, NULL, (uint8_t *) &pad, 4); // read padding back and discard + } + s->txn(NULL, NULL, (uint8_t *) data, len); + s->end(NULL); +} + +// Mongoose Wi-Fi API functions + +bool mg_wifi_scan(void) { + return cyw_wifi_scan(); +} + +bool mg_wifi_connect(char *ssid, char *pass) { + return cyw_wifi_connect(ssid, pass); +} + +bool mg_wifi_disconnect(void) { + return cyw_wifi_disconnect(); +} + +bool mg_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { + return cyw_wifi_ap_start(ssid, pass, channel); +} + +bool mg_wifi_ap_stop(void) { + return cyw_wifi_ap_stop(); +} + +#endif diff --git a/src/drivers/cyw.h b/src/drivers/cyw.h new file mode 100644 index 00000000..91e33a60 --- /dev/null +++ b/src/drivers/cyw.h @@ -0,0 +1,51 @@ +#pragma once + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW + +struct mg_tcpip_spi_ { + void *spi; // Opaque SPI bus descriptor + void (*begin)(void *); // SPI begin: slave select low + void (*end)(void *); // SPI end: slave select high + void (*txn)(void *, uint8_t *, uint8_t *, + size_t len); // SPI transaction: write-read len bytes +}; + +struct mg_tcpip_driver_cyw_firmware { + const uint8_t * code_addr; + size_t code_len; + const uint8_t * nvram_addr; + size_t nvram_len; + const uint8_t * clm_addr; + size_t clm_len; +}; + +struct mg_tcpip_driver_cyw_data { + struct mg_tcpip_spi_ *spi; + struct mg_tcpip_driver_cyw_firmware *fw; + char *ssid; + char *pass; + char *apssid; + char *appass; + uint8_t security; // TBD + uint8_t apsecurity; // TBD + uint8_t apchannel; + bool apmode; // start in AP mode; 'false' starts connection to 'ssid' if not NULL +}; + +//#define MG_TCPIP_DRIVER_INIT(mgr) \ +// do { \ +// static struct mg_tcpip_driver_cyw_data driver_data_; \ +// static struct mg_tcpip_if mif_; \ +// MG_SET_WIFI_CONFIG(&driver_data_); \ +// mif_.ip = MG_TCPIP_IP; \ +// mif_.mask = MG_TCPIP_MASK; \ +// mif_.gw = MG_TCPIP_GW; \ +// mif_.driver = &mg_tcpip_driver_pico_w; \ +// mif_.driver_data = &driver_data_; \ +// mif_.recv_queue.size = 8192; \ +// mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ +// mg_tcpip_init(mgr, &mif_); \ +// MG_INFO(("Driver: cyw, MAC: %M", mg_print_mac, mif_.mac)); \ +// } while (0) + +#endif diff --git a/src/net_builtin.h b/src/net_builtin.h index 5b39479e..165b9d7b 100644 --- a/src/net_builtin.h +++ b/src/net_builtin.h @@ -89,6 +89,7 @@ extern struct mg_tcpip_driver mg_tcpip_driver_xmc7; extern struct mg_tcpip_driver mg_tcpip_driver_ppp; extern struct mg_tcpip_driver mg_tcpip_driver_pico_w; extern struct mg_tcpip_driver mg_tcpip_driver_rw612; +extern struct mg_tcpip_driver mg_tcpip_driver_cyw; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { diff --git a/src/util.c b/src/util.c index 9947f14a..1d2b43dc 100644 --- a/src/util.c +++ b/src/util.c @@ -193,3 +193,8 @@ uint16_t mg_ntohs(uint16_t net) { uint32_t mg_ntohl(uint32_t net) { return MG_LOAD_BE32(&net); } + +void mg_delayms(unsigned int ms) { + uint64_t to = mg_millis() + ms + 1; + while (mg_millis() < to) (void) 0; +} diff --git a/src/util.h b/src/util.h index 9c1399d0..7e14e703 100644 --- a/src/util.h +++ b/src/util.h @@ -17,6 +17,7 @@ char *mg_random_str(char *buf, size_t len); uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); uint64_t mg_millis(void); // Return milliseconds since boot bool mg_path_is_sane(const struct mg_str path); +void mg_delayms(unsigned int ms); #define MG_U32(a, b, c, d) \ (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \ diff --git a/src/wifi_dummy.c b/src/wifi_dummy.c index 8f0bda8f..3a56d627 100644 --- a/src/wifi_dummy.c +++ b/src/wifi_dummy.c @@ -1,6 +1,7 @@ #include "wifi.h" -#if (!defined(MG_ENABLE_DRIVER_PICO_W) || !MG_ENABLE_DRIVER_PICO_W) +#if (!defined(MG_ENABLE_DRIVER_PICO_W) || !MG_ENABLE_DRIVER_PICO_W) && \ + (!defined(MG_ENABLE_DRIVER_CYW) || !MG_ENABLE_DRIVER_CYW) bool mg_wifi_scan(void) { MG_ERROR(("No Wi-Fi driver enabled")); From 7d94f9f61b23cbec209cbef9b5fa54dde00b25a3 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Mon, 24 Mar 2025 16:15:32 -0300 Subject: [PATCH 3/3] Add Pico + RM2 Wi-Fi example --- .../CMakeLists.txt | 24 +++ .../Makefile | 20 +++ .../rm2-pico-picosdk-baremetal-builtin/hal.h | 155 ++++++++++++++++++ .../rm2-pico-picosdk-baremetal-builtin/main.c | 151 +++++++++++++++++ .../mongoose.c | 1 + .../mongoose.h | 1 + .../mongoose_config.h | 6 + .../pico_sdk_import.cmake | 84 ++++++++++ 8 files changed, 442 insertions(+) create mode 100644 tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/CMakeLists.txt create mode 100644 tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/Makefile create mode 100644 tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/hal.h create mode 100644 tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/main.c create mode 120000 tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose.c create mode 120000 tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose.h create mode 100644 tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose_config.h create mode 100644 tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/pico_sdk_import.cmake diff --git a/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/CMakeLists.txt b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/CMakeLists.txt new file mode 100644 index 00000000..e1c3597d --- /dev/null +++ b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.13) +include(pico-sdk/pico_sdk_init.cmake) + +project(firmware) +pico_sdk_init() + +add_executable(firmware + main.c + mongoose.c +) + +target_include_directories(firmware PUBLIC + . +) + +target_link_libraries(firmware hardware_pio pico_stdlib pico_rand) +pico_add_extra_outputs(firmware) # create map/bin/hex file etc. + +pico_enable_stdio_usb(firmware 1) # Route stdio +pico_enable_stdio_uart(firmware 0) # to USB + +# Mongoose build flags in mongoose_config.h + +# Example build options diff --git a/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/Makefile b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/Makefile new file mode 100644 index 00000000..369ccdef --- /dev/null +++ b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/Makefile @@ -0,0 +1,20 @@ +RM = rm -rf +MKBUILD = test -d build || mkdir build +ifeq ($(OS),Windows_NT) + RM = cmd /C del /Q /F /S + MKBUILD = if not exist build mkdir build +endif + +all example: + true + +build build/firmware.uf2: pico-sdk main.c + $(MKBUILD) + cd build && cmake -G "Unix Makefiles" .. && make + +pico-sdk: + git clone --depth 1 -b 2.1.0 https://github.com/raspberrypi/pico-sdk $@ + cd $@ && git submodule update --init + +clean: + $(RM) pico-sdk build diff --git a/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/hal.h b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/hal.h new file mode 100644 index 00000000..06dac6a6 --- /dev/null +++ b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/hal.h @@ -0,0 +1,155 @@ +// Copyright (c) 2025 Cesanta Software Limited +// All rights reserved + +#include "pico/stdlib.h" +#include "hardware/pio.h" + +extern void mg_delayms(unsigned int ms); + +#define SPIPIO pio0 +#define SPISM 0 +#define DATAPIN 2 +#define CLKPIN 3 +#define CSPIN 1 +#define PWRPIN 0 + +#define piospi_wrap_target 0 +#define piospi_wrap 6 +#define piospi_pio_version 0 + +#define piospi_offset_rx_ 7u + +static const uint16_t piospi_program_instructions[] = { + // .wrap_target + 0x80a0, // 0: pull block side 0 + 0xa027, // 1: mov x, osr side 0 + 0x6068, // 2: out null, 8 side 0 + 0x6001, // 3: out pins, 1 side 0 + 0x1043, // 4: jmp x--, 3 side 1 + 0xe000, // 5: set pins, 0 side 0 + 0xc020, // 6: irq wait 0 side 0 + // .wrap + 0xe080, // 7: set pindirs, 0 side 0 + 0x6020, // 8: out x, 32 side 0 + 0x4001, // 9: in pins, 1 side 0 + 0x1049, // 10: jmp x--, 9 side 1 + 0xe000, // 11: set pins, 0 side 0 + 0xe081, // 12: set pindirs, 1 side 0 + 0xc020, // 13: irq wait 0 side 0 + 0x0000, // 14: jmp 0 side 0 +}; + +static const struct pio_program piospi_program = { + .instructions = piospi_program_instructions, + .length = 15, + .origin = -1, + .pio_version = 0, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config piospi_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + piospi_wrap_target, offset + piospi_wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; +} + +static inline pio_sm_config piospi_init(PIO pio, uint sm, uint addr, uint data, uint clk) { + pio_gpio_init(pio, data); + pio_gpio_init(pio, clk); + pio_sm_set_pins_with_mask(pio, sm, 0, (1 << data) | (1 << clk)); + pio_sm_config c = piospi_program_get_default_config(addr); + sm_config_set_out_pins(&c, data, 1); + sm_config_set_in_pins(&c, data); + sm_config_set_set_pins(&c, data, 1); + sm_config_set_sideset_pins(&c, clk); + pio_sm_set_consecutive_pindirs(pio, sm, data, 1, true); + pio_sm_set_consecutive_pindirs(pio, sm, clk, 1, true); + pio_sm_set_pindirs_with_mask(pio, sm, (1 << data) | (1 << clk), (1 << data) | (1 << clk)); + sm_config_set_in_shift(&c, false, true, 8); // push bytes, MSB first, auto +#if PICO_RP2040 + sm_config_set_clkdiv(&c, 18); // Run at 133/1 = <50MHz (2x data rate) +#else + sm_config_set_clkdiv(&c, 20); // Run at 150/1 = <50MHz (2x data rate) +#endif + return c; +} +static inline void piospi_tx(PIO pio, uint sm, pio_sm_config *c, uint addr, uint data) { + sm_config_set_fifo_join(c, PIO_FIFO_JOIN_TX); + sm_config_set_out_shift(c, false, true, 8); // pull bytes, MSB first, auto + pio_sm_init(pio, sm, addr, c); +} +static inline void piospi_rx(PIO pio, uint sm, pio_sm_config *c, uint addr, uint data) { + sm_config_set_fifo_join(c, PIO_FIFO_JOIN_NONE); + sm_config_set_out_shift(c, false, true, 32); // pull words, MSB first, auto + pio_sm_init(pio, sm, addr + piospi_offset_rx_, c); +} +static inline void piospi_done(PIO pio, uint sm) { + while(!pio_interrupt_get(pio, 0)); + pio_sm_set_enabled(pio, sm, false); + pio_interrupt_clear(pio, 0); +} + +static pio_sm_config s_piospi_sm_config; +static uint s_piospi_sm_addr; + +void piospi_write(uint8_t *data, size_t len) { + size_t initial_len = len; + piospi_tx(SPIPIO, SPISM, &s_piospi_sm_config, s_piospi_sm_addr, DATAPIN); + __compiler_memory_barrier(); + pio_sm_put(SPIPIO, SPISM, (len * 8) - 1); // bits to transmit + pio_sm_set_enabled(SPIPIO, SPISM, true); + while (len--) pio_sm_put_blocking(SPIPIO, SPISM, (*data++) << 24); + piospi_done(SPIPIO, SPISM); +} + +// Read data block from SPI interface +void piospi_read(uint8_t *data, size_t len) { + piospi_rx(SPIPIO, SPISM, &s_piospi_sm_config, s_piospi_sm_addr, DATAPIN); + __compiler_memory_barrier(); + pio_sm_put(SPIPIO, SPISM, (len * 8) - 1); // bits to receive + pio_sm_set_enabled(SPIPIO, SPISM, true); + while (len--) *data++ = pio_sm_get_blocking(SPIPIO, SPISM); + piospi_done(SPIPIO, SPISM); +} + +void hwspecific_spi_init(void) { + gpio_init(PWRPIN); + gpio_set_dir(PWRPIN, GPIO_OUT); + gpio_put(PWRPIN, 0); + gpio_init(CSPIN); + gpio_set_dir(CSPIN, GPIO_OUT); + gpio_put(CSPIN, 0); + // init SPI pins so they are idle and stable during power up + gpio_init(DATAPIN); + gpio_set_dir(DATAPIN, GPIO_OUT); + gpio_put(DATAPIN, 0); + gpio_init(CLKPIN); + gpio_set_dir(CLKPIN, GPIO_OUT); + gpio_put(CLKPIN, 0); + mg_delayms(100); + gpio_put(CSPIN, 1); + gpio_put(PWRPIN, 1); + s_piospi_sm_addr = pio_add_program(SPIPIO, &piospi_program); + s_piospi_sm_config = piospi_init(SPIPIO, SPISM, s_piospi_sm_addr, DATAPIN, CLKPIN); + mg_delayms(50); +} + +void hwspecific_spi_begin(void *arg) { + gpio_put(CSPIN, 0); + (void) arg; +} + +// either write or read, not both +void hwspecific_spi_txn(void *arg, uint8_t *txdata, uint8_t *rxdata, size_t len) { + if (txdata != NULL) piospi_write(txdata, len); + if (rxdata != NULL) piospi_read(rxdata, len); + (void) arg; +} + +void hwspecific_spi_end(void *arg) { + gpio_put(CSPIN, 1); + (void) arg; +} diff --git a/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/main.c b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/main.c new file mode 100644 index 00000000..fefdf17d --- /dev/null +++ b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/main.c @@ -0,0 +1,151 @@ +// Copyright (c) 2025 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" +#include "hal.h" + + +#define WIFI_SSID "YOUR_WIFI_NETWORK_NAME" // SET THIS! +#define WIFI_PASS "YOUR_WIFI_PASSWORD" // SET THIS! + + +static const struct mg_tcpip_spi_ spi = {NULL, hwspecific_spi_begin, hwspecific_spi_end, hwspecific_spi_txn}; + +#ifndef CYW43_RESOURCE_ATTRIBUTE +#define CYW43_RESOURCE_ATTRIBUTE +#endif +#include "pico-sdk/lib/cyw43-driver/firmware/w43439A0_7_95_49_00_combined.h" +#include "pico-sdk/lib/cyw43-driver/firmware/wifi_nvram_43439.h" +static const struct mg_tcpip_driver_cyw_firmware fw = { + (const uint8_t *)w43439A0_7_95_49_00_combined, (size_t)CYW43_WIFI_FW_LEN, + (const uint8_t *)wifi_nvram_4343, (size_t)sizeof(wifi_nvram_4343), + (const uint8_t *)(w43439A0_7_95_49_00_combined + sizeof(w43439A0_7_95_49_00_combined) - CYW43_CLM_LEN), (size_t)CYW43_CLM_LEN}; + +// mif user states +enum {AP, SCANNING, STOPPING_AP, CONNECTING, READY}; +static unsigned int state; +static uint32_t s_ip, s_mask; + + +static void mif_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) { + // TODO(): should we include this inside ifp ? add an fn_data ? + if (ev == MG_TCPIP_EV_ST_CHG) { + MG_INFO(("State change: %u", *(uint8_t *) ev_data)); + } + switch(state) { + case AP: // we are in AP mode, wait for a user connection to trigger a scan or a connection to a network + if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_UP) { + MG_INFO(("Access Point started")); + s_ip = ifp->ip, ifp->ip = MG_IPV4(192, 168, 169, 1); + s_mask = ifp->mask, ifp->mask = MG_IPV4(255, 255, 255, 0); + ifp->enable_dhcp_client = false; + ifp->enable_dhcp_server = true; + } else if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_READY) { + MG_INFO(("Access Point READY !")); + + // simulate user request to scan for networks + bool res = mg_wifi_scan(); + MG_INFO(("Starting scan: %s", res ? "OK":"FAIL")); + if (res) state = SCANNING; + } + break; + case SCANNING: + if (ev == MG_TCPIP_EV_WIFI_SCAN_RESULT) { + struct mg_wifi_scan_bss_data *bss = (struct mg_wifi_scan_bss_data *) ev_data; + MG_INFO(("BSS: %.*s (%u) (%M) %d dBm %u", bss->SSID.len, bss->SSID.buf, bss->channel, mg_print_mac, bss->BSSID, (int) bss->RSSI, bss->security)); + } else if (ev == MG_TCPIP_EV_WIFI_SCAN_END) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + MG_INFO(("Wi-Fi scan finished")); + + // simulate user selection of a network (1/2: stop AP) + bool res = mg_wifi_ap_stop(); + MG_INFO(("Manually stopping AP: %s", res ? "OK":"FAIL")); + if (res) state = STOPPING_AP; + // else we have a hw/fw problem + } + break; + case STOPPING_AP: + if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_DOWN) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + d->apmode = false; + + // simulate user selection of a network (2/2: actual connect) + bool res = mg_wifi_connect(d->ssid, d->pass); + MG_INFO(("Manually connecting: %s", res ? "OK":"FAIL")); + if (res) { + state = CONNECTING; + ifp->ip = s_ip; + ifp->mask = s_mask; + if (ifp->ip == 0) ifp->enable_dhcp_client = true; + ifp->enable_dhcp_server = false; + } // else manually start AP as below + } + break; + case CONNECTING: + if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_READY) { + MG_INFO(("READY!")); + state = READY; + + // simulate user code disconnection and go back to AP mode (1/2: disconnect) + bool res = mg_wifi_disconnect(); + MG_INFO(("Manually disconnecting: %s", res ? "OK":"FAIL")); + } else if (ev == MG_TCPIP_EV_WIFI_CONNECT_ERR) { + MG_ERROR(("Wi-Fi connect failed")); + // manually start AP as below + } + break; + case READY: + // go back to AP mode after a disconnection (simulation 2/2), you could retry + if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_DOWN) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + bool res = mg_wifi_ap_start(d->apssid, d->appass, d->apchannel); + MG_INFO(("Disconnected")); + MG_INFO(("Manually starting AP: %s", res ? "OK":"FAIL")); + if (res) { + state = AP; + d->apmode = true; + } + } + break; + } +} + + +static struct mg_tcpip_driver_cyw_data d = { + (struct mg_tcpip_spi_ *)&spi, (struct mg_tcpip_driver_cyw_firmware *)&fw, WIFI_SSID, WIFI_PASS, "mongoose", "mongoose", 0, 0, 10, true}; + +int main(void) { + // initialize stdio + stdio_init_all(); + + hwspecific_spi_init(); + + state = d.apmode ? AP : CONNECTING; + + struct mg_mgr mgr; // Initialise Mongoose event manager + mg_mgr_init(&mgr); // and attach it to the interface + mg_log_set(MG_LL_DEBUG); // Set log level + + // Initialise Mongoose network stack + // Either set use_dhcp or enter a static config. + // For static configuration, specify IP/mask/GW in network byte order + struct mg_tcpip_if mif = { + .ip = 0, + .driver = (struct mg_tcpip_driver *)&mg_tcpip_driver_cyw, + .driver_data = (struct mg_tcpip_driver_cyw_data*)&d, + .fn = mif_fn, +// .recv_queue.size = 8192 + }; + + mg_tcpip_init(&mgr, &mif); + MG_INFO(("Init done, starting main loop")); + + MG_INFO(("Initialising application...")); + + MG_INFO(("Starting event loop")); + for (;;) { + mg_mgr_poll(&mgr, 0); + } + + return 0; +} diff --git a/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose.c b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose.c new file mode 120000 index 00000000..5e522bbc --- /dev/null +++ b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose.c @@ -0,0 +1 @@ +../../../mongoose.c \ No newline at end of file diff --git a/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose.h b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose.h new file mode 120000 index 00000000..ee4ac823 --- /dev/null +++ b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose.h @@ -0,0 +1 @@ +../../../mongoose.h \ No newline at end of file diff --git a/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose_config.h b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose_config.h new file mode 100644 index 00000000..fafc6aa2 --- /dev/null +++ b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/mongoose_config.h @@ -0,0 +1,6 @@ +#define MG_ARCH MG_ARCH_PICOSDK + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_DRIVER_CYW 1 +#define MG_ENABLE_TCPIP_DRIVER_INIT 0 +#define MG_ENABLE_PACKED_FS 1 diff --git a/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/pico_sdk_import.cmake b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/pico_sdk_import.cmake new file mode 100644 index 00000000..a0721d0d --- /dev/null +++ b/tutorials/pico-sdk/rm2-pico-picosdk-baremetal-builtin/pico_sdk_import.cmake @@ -0,0 +1,84 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE})