#include "cyw.h" #include "net.h" #if MG_ENABLE_TCPIP && \ ((defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW) || \ (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO)) #ifndef MG_ENABLE_DRIVER_CYW #define MG_ENABLE_DRIVER_CYW 0 #endif #ifndef MG_ENABLE_DRIVER_CYW_SDIO #define MG_ENABLE_DRIVER_CYW_SDIO 0 #endif static struct mg_tcpip_if *s_ifp; static uint32_t s_ip, s_mask; static bool s_link, s_auth, s_join; static void wifi_cb(struct mg_tcpip_if *ifp, int ev, void *ev_data) { struct mg_wifi_data *wifi = &((struct mg_tcpip_driver_cyw_data *) ifp->driver_data)->wifi; if (wifi->apmode && ev == MG_TCPIP_EV_STATE_CHANGE && *(uint8_t *) ev_data == MG_TCPIP_STATE_UP) { MG_DEBUG(("Access Point started")); s_ip = ifp->ip, ifp->ip = wifi->apip; s_mask = ifp->mask, ifp->mask = wifi->apmask; ifp->enable_dhcp_client = false; ifp->enable_dhcp_server = true; } } static bool cyw_init(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; struct mg_wifi_data *wifi = &d->wifi; if (MG_BIG_ENDIAN) { MG_ERROR(("Big-endian host")); return false; } s_ifp = ifp; s_ip = ifp->ip; s_mask = ifp->mask; s_link = s_auth = s_join = false; ifp->pfn = wifi_cb; if (!cyw_init(ifp->mac)) return false; if (wifi->apmode) { return mg_wifi_ap_start(wifi); } else if (wifi->ssid != NULL && wifi->pass != NULL) { return mg_wifi_connect(wifi); } 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->wifi.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->wifi.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 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 // - SDIO has 3 functions (proper SDIO terminology), F0 to F2, coincident with // those for SPI, accessed through standard SDIO practices. There is no F3. // 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, CYW43439 DS 4.2.1 Fig.12, 2-bit field #define CYW_SPID_FUNC_BUS 0 // F0 #define CYW_SPID_FUNC_CHIP 1 // F1 #define CYW_SPID_FUNC_WLAN 2 // F2 // SDIO functions, 3-bit field; CYW4343W and CYW43439 DS 4.1 #define CYW_SDIO_FUNC_BUS 0 // F0 #define CYW_SDIO_FUNC_CHIP 1 // F1 #define CYW_SDIO_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_bus_specific_poll(uint32_t *dest); static void cyw_update_hash_table(void); // High-level comm stuff static void cyw_poll(void) { struct sdpcm_hdr *sdpcm = (struct sdpcm_hdr *) resp; unsigned int channel; if (s_ifp->update_mac_hash_table) { // first call to _poll() is after _init(), so this is safe cyw_update_hash_table(); s_ifp->update_mac_hash_table = false; } if (cyw_bus_specific_poll(resp) == 0) return; 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_specific_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 = (uint8_t) 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_specific_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); // Do not call any IOCTL functions here, otherwise revise cyw_ioctl_wait() 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_VERBOSE(("BCM evt %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 __attribute__((unused)) 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 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 uint16_t *da = (uint16_t *) data; 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); da[0] = (uint16_t) len; da[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 uint16_t *da = (uint16_t *) data; 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); da[0] = (uint16_t) len; // skip for WPA3 SAE (43430 does NOT support WPA3 in AP) da[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((char *)sbss->SSID, sbss->SSID_len); bss.BSSID = (char *)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 *r = (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_VERBOSE(("IOCTL result: %02x %02x %02x %02x ..", r[0], r[1], r[2], r[3])); s_ioctl_resp = r; } // 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_specific_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 } __attribute__((unused)) 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); } static inline bool delayms(unsigned int ms) { mg_delayms(ms); return true; } // wait for a response, meanwhile delivering received frames and events static bool cyw_ioctl_wait(void) { unsigned int times = 100; s_ioctl_resp = NULL; s_ioctl_err = false; do { // IOCTL response processing does not call any other IOCTL function cyw_poll(); // otherwise we can't allow them to pile up here // network frames will be pushed to the queue so that is safe } while (s_ioctl_resp == NULL && !s_ioctl_err && times-- > 0 && delayms(1)); MG_VERBOSE(("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, TODO(): try rev 17 for 4343W static const uint32_t country_code = 'X' + ('X' << 8) + (0 << 16); static bool cyw_bus_specific_init(); static bool cyw_load_clmll(void *data, size_t len); static bool cyw_load_clm(struct mg_tcpip_driver_cyw_firmware *fw) { return cyw_load_clmll((void *) fw->clm_addr, fw->clm_len); } // clang-format off static bool cyw_init(uint8_t *mac) { struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; uint32_t val = 0; if (!cyw_bus_specific_init()) return false; if (!cyw_load_clm(d->fw)) return false; // Load CLM blob // 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 == (unsigned int) ~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 &= (uint16_t)~MG_BIT(1); // DL_BEGIN } return sent >= len; } // clang-format on static void cyw_update_hash_table(void) { // TODO(): read database, rebuild hash table uint32_t val = 0; val = 1; cyw_ioctl_iovar_set2_(0, "mcast_list", (uint8_t *) &val, sizeof(val), (uint8_t *) mcast_addr, sizeof(mcast_addr)); mg_delayms(50); } // 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 // BUS DEPENDENCY: max bus to backplane transfer size, bus function id #define CYW_CHIP_BCKPLN_SPIMAX 64 #define CYW_CHIP_BCKPLN_SDIOMAX 1536 #if MG_ENABLE_DRIVER_CYW_SDIO #define CYW_CHIP_BCKPLN_BUSMAX CYW_CHIP_BCKPLN_SDIOMAX #define CYW_BUS_FUNC_CHIP CYW_SDIO_FUNC_CHIP #else #define CYW_CHIP_BCKPLN_BUSMAX CYW_CHIP_BCKPLN_SPIMAX #define CYW_BUS_FUNC_CHIP CYW_SPID_FUNC_CHIP #endif // 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_bus_write(unsigned int f, uint32_t addr, void *data, uint16_t len); static bool cyw_bus_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_bus_write(CYW_BUS_FUNC_CHIP, CYW_CHIP_ADDRHIGH, &val, 1); val = (addr >> 16) & 0xff; cyw_bus_write(CYW_BUS_FUNC_CHIP, CYW_CHIP_ADDRMID, &val, 1); val = (addr >> 8) & 0xff; cyw_bus_write(CYW_BUS_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_bus_read(CYW_BUS_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_bus_write(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // reset cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end val = 0x00; cyw_bus_write(CYW_BUS_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_bus_write(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); cyw_bus_read(CYW_BUS_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_bus_read(CYW_BUS_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_bus_read(CYW_BUS_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: 43430 and 43439 only 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_bus_write(CYW_BUS_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXIDX) & CYW_CHIP_BCKPLN_ADDRMSK), &val, sizeof(val)); val = 0x00; cyw_bus_write(CYW_BUS_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXPDA) & CYW_CHIP_BCKPLN_ADDRMSK), &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 = (uint32_t) ~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 == (uint32_t) ~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; // internal backplane limit if (bytes > avail) bytes = avail; if (bytes > CYW_CHIP_BCKPLN_BUSMAX) bytes = CYW_CHIP_BCKPLN_BUSMAX; // bus to backplane transfer limit cyw_bus_write(CYW_BUS_FUNC_CHIP, addr, (uint8_t *)data + offset, (uint16_t) 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); // TODO(scaprile): 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_bus_write(CYW_BUS_FUNC_CHIP, (CYW_CHIP_BCKPLN_WINSZ - 4), &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 #if !MG_ENABLE_DRIVER_CYW_SDIO // 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) 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 static size_t cyw_spi_poll(uint8_t *response) { size_t len; uint32_t res; // SPI poll cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &res, sizeof(res)); if (res == (uint32_t) ~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_SPID_FUNC_CHIP, CYW_CHIP_SPIFRCTRL, &val, 1); cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); return 0; } cyw_spi_read(CYW_SPID_FUNC_WLAN, 0, response, (uint16_t)len); return len; } static size_t cyw_spi_tx(uint32_t *data, uint16_t len) { while (len & 3) data[len++] = 0; // SPI 32-bit padding return cyw_spi_write(CYW_SPID_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_SPI_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_SPID_FUNC_BUS | CYW_SPI_16bMODE, CYW_BUS_SPI_TEST, &val, sizeof(val)); if (sw16_2(val) == 0xFEEDBEAD) break; mg_delayms(1); } if (times == (unsigned int) ~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: d->hs true // - 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(0x000204a3 | (d->hs ? MG_BIT(4) : 0)); // 4 reg content cyw_spi_write(CYW_SPID_FUNC_BUS | CYW_SPI_16bMODE, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); mg_tcpip_call(s_ifp, MG_TCPIP_EV_DRIVER, NULL); cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_TEST, &val, sizeof(val)); if (val != 0xFEEDBEAD) return false; val = 4; cyw_spi_write(CYW_SPID_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_SPID_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_SPID_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_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); // BT-ENABLED DEPENDENCY times = 10; while (times--) { cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); if (val & MG_BIT(6)) break; // ALP_AVAIL mg_delayms(1); } if (times == (unsigned int) ~0) return false; // clear request val = 0; cyw_spi_write(CYW_SPID_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_SPID_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); if (val == 43430) val = 4343; MG_INFO(("WLAN chip is CYW%u%c", val, val == 4343 ? 'W' : ' ')); // 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_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); if (val & MG_BIT(7)) break; // HT_AVAIL mg_delayms(1); } if (times == (unsigned int) ~0) return false; // Wait for backplane ready times = 1000; while (times--) { cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); if (val & MG_BIT(5)) break; // F2_RX_READY mg_delayms(1); } if (times == (unsigned int) ~0) return false; // CHIP DEPENDENCY // Enable save / restore // Configure WakeupCtrl, set HT_AVAIL in CLOCK_CSR cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_spi_write(CYW_SPID_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_SPID_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_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); // Enable Keep SDIO On (KSO) cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); if (!(val & MG_BIT(0))) { val |= MG_BIT(0); cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); } // The SPI bus can be configured for sleep (KSO controls wlan block sleep) cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); val &= ~MG_BIT(7) /* WAKE_UP */; cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); // Set SPI bus sleep val = 0x0f; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); // Clear pullups. NOTE(): ? val = 0x00; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); // Clear possible data unavailable error cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); if (val & MG_BIT(0)) cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); return true; } // clang-format on // gSPI, CYW43439 DS 4.2.1 Fig.12 #define CYW_SPID_LEN(x) ((x) &0x7FF) // bits 0-10 #define CYW_SPID_ADDR(x) (((x) &0x1FFFF) << 11) // bits 11-27, #define CYW_SPID_FUNC(x) (((x) &3) << 28) // bits 28-29 #define CYW_SPID_INC MG_BIT(30) #define CYW_SPID_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->bus; uint32_t hdr = CYW_SPID_WR | CYW_SPID_INC | CYW_SPID_FUNC(f) | CYW_SPID_ADDR(addr) | CYW_SPID_LEN(len); // gSPI header // TODO(scaprile): check spin in between and timeout values, return false if (f == CYW_SPID_FUNC_WLAN) { uint32_t val = 0; while ((val & MG_BIT(5)) != MG_BIT(5)) // F2 rx ready (FIFO ready) cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); } if (f & CYW_SPI_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_SPID_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->bus; uint32_t padding = f == CYW_SPID_FUNC_CHIP ? 4 : 0; // add padding to chip backplane reads as a response delay uint32_t hdr = CYW_SPID_INC | CYW_SPID_FUNC(f) | CYW_SPID_ADDR(addr) | CYW_SPID_LEN(len + padding); // gSPI header if (f == CYW_SPID_FUNC_WLAN && (len & 3)) len = (len + 4) & ~3; // align WLAN transfers to 32-bit if (f & CYW_SPI_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_SPID_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); } static bool cyw_bus_specific_init(void) { return cyw_spi_init(); } static size_t cyw_bus_specific_poll(uint32_t *response) { return cyw_spi_poll((uint8_t *) response); } static size_t cyw_bus_specific_tx(uint32_t *data, uint16_t len) { return cyw_spi_tx(data, len); } static bool cyw_bus_write(unsigned int f, uint32_t addr, void *data, uint16_t len) { if (f == CYW_SPID_FUNC_CHIP && len >= 4) addr |= CYW_CHIP_BCKPLN_ACCSS4B; return cyw_spi_write(f, addr, data, len); } static bool cyw_bus_read(unsigned int f, uint32_t addr, void *data, uint16_t len) { cyw_spi_read(f, addr, data, len); return true; } #else // MG_ENABLE_DRIVER_CYW_SDIO #include "sdio.h" // CYW43 SDIO bus specifics // CYW4343W and CYW43439 DS 4.1 SDIO v2.0: //- F0: max block size is 32 bytes //- F1: max block size is 64 bytes //- F2: max block size is 512 bytes // clang-format off static bool cyw_sdio_transfer(bool write, unsigned int f, uint32_t addr, void *data, uint32_t len) { struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; struct mg_tcpip_sdio *s = (struct mg_tcpip_sdio *) d->bus; uint32_t *ptr = (uint32_t *) data; // assume 32-bit aligned data (all except firmware) if (write && (size_t) data & 3) { // missed, source data is not 32-bit aligned memcpy(txdata, data, len); // copy to an aligned buffer, we know it fits ptr = txdata; } // all possible read destinations are 32-bit aligned // mg_sdio_transfer requires 32-bit alignment for > 1 byte transfers return mg_sdio_transfer(s, write, f, addr, ptr, len); } static size_t cyw_sdio_poll(uint32_t *response) { uint32_t res; uint16_t *len = (uint16_t *)&res; // WHD: internal docs, "tag" hinting a possible packet. // This is actually the len / ~len field of a possible struct sdpcm_hdr, if there is a packet available, or 0 if there is none. cyw_sdio_transfer(false, CYW_SDIO_FUNC_WLAN, 0, &res, sizeof(res)); // read "the tag" if ((len[0] | len[1]) == 0 || (len[0] ^ len[1]) != 0xffff || *len <= 4) return 0; response[0] = res; // copy what we already read, then read the rest cyw_sdio_transfer(false, CYW_SDIO_FUNC_WLAN, 0, response + 1, *len - sizeof(res)); return (size_t)*len; } static size_t cyw_sdio_tx(uint32_t *data, uint16_t len) { return cyw_sdio_transfer(true, CYW_SDIO_FUNC_WLAN, 0, data, len) ? len: 0; } static bool cyw_sdio_init() { struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; struct mg_tcpip_sdio *s = (struct mg_tcpip_sdio *) d->bus; uint32_t val = 0; if (!mg_sdio_init(s)) return false; // no block transfers on F0. if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_BUS, 32)) return false; if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_CHIP, 64)) return false; if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_WLAN, 64)) return false; // TODO(scaprile): we don't handle SDIO interrupts, study CCCR INTEN and SDIO support (SDIO 6.3, 8) // Enable chip backplane (F1) if (!mg_sdio_enable_f(s, CYW_SDIO_FUNC_CHIP)) return false; // Wait for F1 to be ready if (!mg_sdio_waitready_f(s, CYW_SDIO_FUNC_CHIP)) return false; // chip backplane is ready, initialize it // request ALP (Active Low Power) clock val = MG_BIT(5) | MG_BIT(3) | MG_BIT(0); // HW_CLKREQ_OFF ALP_REQ FORCE_ALP cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); // BT-ENABLED DEPENDENCY unsigned int times = 10; while (times--) { if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; if (val & MG_BIT(6)) break; // ALP_AVAIL mg_delayms(1); } if (times == (unsigned int) ~0) return false; // clear request val = 0; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); // Enable WLAN (F2) if (!mg_sdio_enable_f(s, CYW_SDIO_FUNC_WLAN)) return false; // Clear pullups. NOTE(): ? val = 0x00; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); // we don't handle wake nor OOB interrupts; SEP_INT_CTL is a vendor specific SDIO register // SDIO interrupts: enable F2 interrupt only cyw_set_backplane_window(CYW_CHIP_CHIPCOMMON); // set backplane window to start of CHIPCOMMON area cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); if (val == 43430) val = 4343; MG_INFO(("WLAN chip is CYW%u%c", val, val == 4343 ? 'W' : ' ')); // Load firmware (code and NVRAM) if (!cyw_load_firmware(d->fw)) return false; // Wait for High Throughput (HT) clock ready times = 50; while (times--) { if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; if (val & MG_BIT(7)) break; // HT_AVAIL mg_delayms(1); } if (times == (unsigned int) ~0) return false; // Wait for WLAN ready if (!mg_sdio_waitready_f(s, CYW_SDIO_FUNC_WLAN)) return false; // CHIP DEPENDENCY // Enable save / restore // Configure WakeupCtrl, set HT_AVAIL in CLOCK_CSR if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1)) return false; val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); #if 0 // TODO(scaprile): Check if this is actually necessary // Set BRCM_CARDCAP to CMD_NODEC. This is a vendor specific SDIO register val = MG_BIT(3); cyw_sdio_transfer(true, CYW_SDIO_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); #endif // Force HT request to chip backplane val = MG_BIT(1) /* FORCE_HT */; if(!cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; // Enable Keep SDIO On (KSO) cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); if (!(val & MG_BIT(0))) { val |= MG_BIT(0); cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); } return true; } // clang-format on static bool cyw_bus_specific_init(void) { return cyw_sdio_init(); } static size_t cyw_bus_specific_poll(uint32_t *response) { return cyw_sdio_poll(response); } static size_t cyw_bus_specific_tx(uint32_t *data, uint16_t len) { return cyw_sdio_tx(data, len); } static bool cyw_bus_write(unsigned int f, uint32_t addr, void *data, uint16_t len) { if (f == CYW_SDIO_FUNC_CHIP && len == 4) addr |= CYW_CHIP_BCKPLN_ACCSS4B; return cyw_sdio_transfer(true, f, addr, data, (uint32_t) len); } static bool cyw_bus_read(unsigned int f, uint32_t addr, void *data, uint16_t len) { return cyw_sdio_transfer(false, f, addr, data, (uint32_t) len); } #endif // Mongoose Wi-Fi API functions bool mg_wifi_scan(void) { return cyw_wifi_scan(); } bool mg_wifi_connect(struct mg_wifi_data *wifi) { s_ifp->ip = s_ip; s_ifp->mask = s_mask; if (s_ifp->ip == 0) s_ifp->enable_dhcp_client = true; s_ifp->enable_dhcp_server = false; MG_DEBUG(("Connecting to '%s'", wifi->ssid)); return cyw_wifi_connect(wifi->ssid, wifi->pass); } bool mg_wifi_disconnect(void) { return cyw_wifi_disconnect(); } bool mg_wifi_ap_start(struct mg_wifi_data *wifi) { MG_DEBUG(("Starting AP '%s' (%u)", wifi->apssid, wifi->apchannel)); return cyw_wifi_ap_start(wifi->apssid, wifi->appass, wifi->apchannel); } bool mg_wifi_ap_stop(void) { return cyw_wifi_ap_stop(); } #endif