Files
mongoose/src/drivers/cyw.c
T

1416 lines
58 KiB
C

#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