diff --git a/mongoose.c b/mongoose.c index 32e03e0f..f4c6b2ad 100644 --- a/mongoose.c +++ b/mongoose.c @@ -22555,7 +22555,8 @@ void mg_free(void *ptr) { #if (!defined(MG_ENABLE_DRIVER_PICO_W) || !MG_ENABLE_DRIVER_PICO_W) && \ (!defined(MG_ENABLE_DRIVER_CYW) || !MG_ENABLE_DRIVER_CYW) && \ (!defined(MG_ENABLE_DRIVER_CYW_SDIO) || !MG_ENABLE_DRIVER_CYW_SDIO) && \ - (!defined(MG_ENABLE_DRIVER_NXP_WIFI) || !MG_ENABLE_DRIVER_NXP_WIFI) + (!defined(MG_ENABLE_DRIVER_NXP_WIFI) || !MG_ENABLE_DRIVER_NXP_WIFI) && \ + (!defined(MG_ENABLE_DRIVER_ST67W6) || !MG_ENABLE_DRIVER_ST67W6) bool mg_wifi_scan(void) { @@ -26356,6 +26357,538 @@ bool mg_sdio_init(struct mg_tcpip_sdio *sdio) { #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/st67w6.c" +#endif + + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ST67W6) && \ + MG_ENABLE_DRIVER_ST67W6 + +static struct mg_tcpip_if *s_ifp; +static uint32_t s_ip, s_mask; +static bool s_link = false, s_connecting = false; + +static void wifi_cb(struct mg_tcpip_if *ifp, int ev, void *ev_data) { + struct mg_wifi_data *wifi = + &((struct mg_tcpip_driver_st67w6_data *) ifp->driver_data)->wifi; + if (wifi->apmode && ev == MG_TCPIP_EV_ST_CHG && + *(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 st67w6_init(uint8_t *mac); +static void st67w6_poll(bool is_at); + +static bool mg_tcpip_driver_st67w6_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + struct mg_wifi_data *wifi = &d->wifi; + if (MG_BIG_ENDIAN) { + MG_ERROR(("Big-endian host")); + return false; + } + if (d->is_ready == NULL) return false; + s_ifp = ifp; + s_ip = ifp->ip; + s_mask = ifp->mask; + s_link = false; + ifp->pfn = wifi_cb; + if (!st67w6_init(ifp->mac)) return false; + + if (d->send_queue.size == 0) d->send_queue.size = 8192; + d->send_queue.buf = (char *) mg_calloc(1, d->send_queue.size); + if (d->send_queue.buf == NULL) { + MG_ERROR(("OOM")); + 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; +} + +// Decouple; module access depends on it being RDY. See st67w6_poll() +size_t mg_tcpip_driver_st67w6_output(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + char *p; + if (mg_queue_book(&d->send_queue, &p, len) < len) return 0; + memcpy(p, buf, len); + mg_queue_add(&d->send_queue, len); + return len; +} + +static bool mg_tcpip_driver_st67w6_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + if (d->is_ready == NULL) return false; + st67w6_poll(false); + if (!s1) return false; + return s_link; +} + +struct mg_tcpip_driver mg_tcpip_driver_st67w6 = { + mg_tcpip_driver_st67w6_init, mg_tcpip_driver_st67w6_output, NULL, + mg_tcpip_driver_st67w6_poll}; + +// AT | STA | AP | HCI | OT +// -------------------------- +// framing <-- includes rx stall indication +// -------------------------- +// SPI <-- padded to 32-bit +// +// Transactions take place when the module signals RDY, 'tx' and 'write' +// functions actually just write to memory +// - AT: handles configuration and events (unsolicited +foo:). Responses may +// come as: +// - 1 frame: just OK or ERROR +// - 2 frames: 1 text line each, a response and OK +// - 3 frames: 2 text lines: 1 frame with text and no CR/LF, 1 frame with +// binary and CR/LF, 1 text line with OK +// - STA and AP contain plain Ethernet frames to/from the STA and AP networks, +// respectively +// - HCI: BLE stuff +// - OT: ? + +#pragma pack(push, 1) +// little endian + +struct spi_hdr { + uint16_t magic; + uint16_t len; + uint8_t vflags; // version :2, rx_stall :1, flags :5 + uint8_t type; + uint16_t reserved; +}; + +#define ST67W6_SPI_TYPE_AT 0 +#define ST67W6_SPI_TYPE_STA 1 +#define ST67W6_SPI_TYPE_AP 2 +#define ST67W6_SPI_TYPE_HCI 3 +#define ST67W6_SPI_TYPE_OT 4 + +#pragma pack(pop) + +static uint32_t txdata[2048 / 4], rxdata[2048 / 4]; +static uint8_t at_resp[300]; + +static bool s_at_ok, s_at_err; +static size_t s_at_resp_len; +static void st67w6_handle_wifi_evnt(char *, size_t len); +static void st67w6_handle_scan_result(char *, size_t len); + +static size_t st67w6_spi_poll(uint8_t *write, uint8_t *read); +static void st67w6_write(unsigned int f, void *data, uint16_t len); +static void st67w6_update_hash_table(void); + +// High-level comm stuff + +static void st67w6_poll(bool is_at) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct spi_hdr *h = (struct spi_hdr *) rxdata; + unsigned int type; + uint8_t *txsource = (uint8_t *) txdata; + if (s_ifp->update_mac_hash_table) { + // first call to _poll() is after _init(), so this is safe + st67w6_update_hash_table(); + s_ifp->update_mac_hash_table = false; + } + if (!is_at) { // send outstanding WLAN frames in the queue + char *buf; + size_t len; + // NOTE(): have traffic-dependent queues or queue traffic type with data + if ((len = mg_queue_next(&d->send_queue, &buf)) > 0) { + st67w6_write(d->wifi.apmode ? ST67W6_SPI_TYPE_AP : ST67W6_SPI_TYPE_STA, + buf, (uint16_t) len); + mg_queue_del(&d->send_queue, len); + } else { // nothing to send + txsource = NULL; + } + } + if (st67w6_spi_poll(txsource, (uint8_t *) rxdata) == 0) return; + if (h->len == 0) return; + type = h->type; + if (type == ST67W6_SPI_TYPE_AT) { + char *p = (char *) (h + 1); + size_t len = h->len; + if (len > 7 && strncmp(p, "+CWLAP:", 7) == 0) { // scan result + st67w6_handle_scan_result(p + 7, len - 7); + } else if (len > 4 && strncmp(p, "+CW:", 4) == 0) { + st67w6_handle_wifi_evnt(p + 4, len - 4); + } else { + MG_VERBOSE(("AT partial: %.*s", (int) len, p)); + if (s_at_resp_len + len >= sizeof(at_resp)) + s_at_resp_len = 0; // truncate response, error will be caught later + memcpy(at_resp + s_at_resp_len, p, len); + s_at_resp_len += len; + if (mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*ERROR*", 7), NULL)) { + s_at_err = true; + } else if (mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*OK*", 4), NULL)) { + s_at_ok = true; + } + } + } else if (type == ST67W6_SPI_TYPE_STA || type == ST67W6_SPI_TYPE_AP) { + // WLAN frame reception + mg_tcpip_qwrite(h + 1, h->len, s_ifp); + } // else silently discard +} + +// WLAN event handling + +// - Do not call any AT functions here, otherwise revise st67w6_at_wait() +// - The module likes to send ERROR along with other events +static void st67w6_handle_wifi_evnt(char *p, size_t len) { + struct mg_str data[2]; + MG_VERBOSE(("event: %.*s", (int) len, p)); + if (len > 9 && strncmp(p, "CONNECTED", 9) == 0) { + s_link = true; + s_connecting = false; + } else if (len > 12 && strncmp(p, "DISCONNECTED", 12) == 0) { + s_link = false; + s_connecting = false; // should not be needed + } else if (s_connecting && len > 6 && + mg_match(mg_str_n(p, len), mg_str_n("ERROR,*\r\n*", 10), data)) { + size_t reason = 0; + bool ok = mg_to_size_t(data[0], &reason); + s_connecting = false; + MG_ERROR(("CONNECT FAILED")); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_CONNECT_ERR, ok ? &reason : NULL); + } else if (len > 9 && strncmp(p, "SCAN_DONE", 9) == 0) { + MG_VERBOSE(("scan complete")); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_END, NULL); + } // else silently discard: CONNECTING; STA_CONNECTED,"MAC"; + // STA_DISCONNECTED,"MAC"; DIST_STA_IP,"MAC","IP" +} + +static bool st67w6_at_cmd(char *cmd, size_t len); + +// Wi-Fi network stuff + +static bool st67w6_wifi_connect(char *ssid, char *pass) { + char cmd[90]; // ssid + pass + AT + size_t cmd_len; + if (!st67w6_at_cmd("AT+CWMODE=1,0\r\n", 15)) return false; + cmd_len = mg_snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\",,0\r\n", ssid, + pass); // takes >700ms + if (!st67w6_at_cmd(cmd, cmd_len)) return false; + st67w6_at_cmd("AT+CWRECONNCFG=0,0\r\n", 20); // disregard error, connecting + s_connecting = true; + return true; +} + +static bool st67w6_wifi_disconnect(void) { + s_connecting = false; + if (!st67w6_at_cmd("AT+CWQAP=0\r\n", 12)) return false; // takes >800ms + return st67w6_at_cmd("AT+CWMODE=0,0\r\n", 15); // takes >550ms +} + +static bool st67w6_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { + char cmd[90]; // ssid + pass + AT + size_t cmd_len; + if (!st67w6_at_cmd("AT+CWMODE=2,0\r\n", 15)) return false; // takes >800ms + cmd_len = mg_snprintf( + cmd, sizeof(cmd), "AT+CWSAP=\"%s\",\"%s\",%u,3,2,0\r\n", ssid, pass, + channel); // 3: WPA2_PSK; 2: max stations // takes >350ms + s_link = true; + return st67w6_at_cmd(cmd, cmd_len); +} + +static bool st67w6_wifi_ap_stop(void) { + s_link = false; + return st67w6_at_cmd("AT+CWMODE=0,0\r\n", 15); // takes >550ms +} + +// WLAN scan handling + +// +CWLAP:(security,"SSID",RSSI,"BSSID",channel,cipher,proto,wps)\r\n +// security: OPEN, WEP, WPA, WPA2, WPA-WPA2, WPA-EAP, WPA3-SAE, WPA2-WPA3-SAE +// cipher: NONE, WEP, AES/CCMP, TKIP, TKIP and AES/CCMP +// proto: 4-bit bitmap AX,N,G,B; all set from right to left + +static bool st67w6_wifi_scan(void) { + return st67w6_at_cmd("AT+CWLAPOPT=1,1695,-100,255,50\r\n", 32) && + st67w6_at_cmd("AT+CWLAP=0,,,0\r\n", 16); +} + +static void st67w6_handle_scan_result(char *data, size_t len) { + struct mg_wifi_scan_bss_data bss; + struct mg_str fields[2]; + char mac[6]; + uint8_t val; + unsigned int i; + MG_VERBOSE(("scan result event: %.*s", (int) len, data)); + ++data, --len; // skip '(' + if (!mg_span(mg_str_n(data, len), &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + bss.security = + (val == 0) + ? MG_WIFI_SECURITY_OPEN + : MG_WIFI_SECURITY_WEP | + ((val == 2 || val == 4 || val == 5) ? MG_WIFI_SECURITY_WPA + : 0) | + ((val == 3 || val == 4 || val == 7) ? MG_WIFI_SECURITY_WPA2 + : 0) | + ((val == 6 || val == 7) ? MG_WIFI_SECURITY_WPA3 : 0); + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + bss.SSID.buf = fields[0].buf + 1, bss.SSID.len = fields[0].len - 2; + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + while (fields[0].buf[0] == ' ') ++fields[0].buf, --fields[0].len; + if (fields[0].buf[0] != '-') return; // positive RSSI would be great + ++fields[0].buf, --fields[0].len; + if (!mg_str_to_num(fields[0], 10, &val, 1)) return; + bss.RSSI = (int8_t) - (int8_t) val; + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + if (fields[0].len < 19) return; + ++fields[0].buf, --fields[0].len; // skip '"' + for (i = 0; i < 6; i++) { + struct mg_str str; + str.buf = fields[0].buf + 3 * i; + str.len = 2; + if (!mg_str_to_num(str, 16, &mac[i], 1)) return; + } + bss.BSSID = mac; + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &bss.channel, 1)) + return; + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + // ignore cypher + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + bss.has_n = (val & 4) != 0; + // bss.has_ax = (val & 8) != 0; + bss.band = MG_WIFI_BAND_2G; // NOT INFORMED with default options, no docs + 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); +} + +// AT stuff + +static inline bool delayms(unsigned int ms) { + mg_delayms(ms); + return true; +} + +// send AT command, wait for a response or timeout, meanwhile delivering +// received frames and events + +static bool st67w6_at_cmd(char *cmd, size_t len) { + bool is_at = true; + unsigned int times = 1000; + s_at_resp_len = 0; + st67w6_write(ST67W6_SPI_TYPE_AT, cmd, (uint16_t) len); + s_at_ok = false, s_at_err = false; + do { // AT response processing does not call any other AT function + st67w6_poll(is_at); // otherwise we can't allow them to pile up here + is_at = false; // avoid repeating, allow queued WLAN frames to be sent + // network frames will be pushed to the queue so that is safe + } while (!s_at_ok && !s_at_err && times-- > 0 && delayms(1)); + MG_VERBOSE(("AT response:\n%.*s", s_at_resp_len, at_resp)); + MG_VERBOSE(("ok: %c, err: %c, times: %d", s_at_ok ? '1' : '0', + s_at_err ? '1' : '0', (int) times)); + return s_at_ok; +} + +static bool st67w6_spi_init(void); +bool mg_to_size_t(struct mg_str str, size_t *val); + +static bool st67w6_init(uint8_t *mac) { + // struct mg_tcpip_driver_st67w6_data *d = (struct + // mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct mg_str data[3]; + size_t val; + bool is_b = false; + if (!st67w6_spi_init()) return false; + if (!st67w6_at_cmd("AT\r\n", 4)) return false; + if (!st67w6_at_cmd("AT+CWNETMODE?\r\n", 15) || + !mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*:*\r\n*", 6), data) || + !mg_to_size_t(data[1], &val)) + return false; + if (val != 0) { + MG_ERROR(("Wrong firmware, T02 is needed")); + return false; + } + // set clock, who cares ??? + if (!st67w6_at_cmd("AT+GET_CLOCK\r\n", 14)) return false; + MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // TODO(scaprile): --> VERBOSE + // BT-ENABLED DEPENDENCY + + // MODULE DEPENDENCY + if (!st67w6_at_cmd("AT+EFUSE-R=24,\"0x100\"\r\n", 23) || + !mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*,*", 3), + data)) + return false; + if (data[1].buf[0] == 'C' && data[1].buf[1] == '6') is_b = true; + MG_DEBUG(("WLAN module is %sB type", is_b ? "" : "not")); // --> VERBOSE + if (is_b) { + // Disable the antenna diversity pin + if (!st67w6_at_cmd("AT+IORST=0\r\n", 12)) return false; + // Apparently they intend to disable some antenna ... + if (!st67w6_at_cmd("AT+CWANTENABLE?\r\n", 17)) return false; + MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // --> VERBOSE + } + + // Do not set wake-up pin (AT+SLWKIO) + // Disable power save mode + // NOTE(scaprile): (no response if in hibernate mode, though I guess we + // wouldn't have reached this point in that case either) + if (!st67w6_at_cmd("AT+PWR=0\r\n", 12)) return false; + // set Wi-Fi + // set country code + if (!st67w6_at_cmd("AT+CWCOUNTRY=0,\"00\"\r\n", 21)) return false; +#if 0 + // set DTIM + if (!st67w6_at_cmd("AT+SLWKDTIM=1\r\n", 15)) return false; +#endif + // Read only default MAC, ignore set bit count (data[6] & 0x3F). Custom MACs + // reside at @0x64 and 0x70 + if (st67w6_at_cmd("AT+EFUSE-R=7,\"0x014\"\r\n", 22) && + mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*,*", 3), + data)) { + int i; + for (i = 0; i < 6; i++) mac[i] = data[1].buf[5 - i]; + MG_DEBUG(("MAC: %M", mg_print_mac, mac)); + } else { + MG_ERROR(("read MAC failed")); + } + return true; +} + +static void st67w6_update_hash_table(void) { + // TODO(): read database, rebuild hash table + // uint32_t val = 0; + // val = 1; + // st67w6_at_iovar_set2_(0, "mcast_list", (uint8_t *) &val, sizeof(val), + // (uint8_t *) mcast_addr, sizeof(mcast_addr)); mg_delayms(50); +} + +// SPI specifics + +#define ST67W6_SPI_MAGIC 0x55AA +// #define ST67W6_SPI_VERSION(x), IS_STALL, FLAGS(x), ... + +static const uint8_t idlehdr[sizeof(struct spi_hdr)] = {0xaa, 0x55, 0, 0, + 0, 0, 0, 0}; + +static void st67w6_write(unsigned int f, void *data, uint16_t len) { + struct spi_hdr *h = (struct spi_hdr *) txdata; + h->magic = ST67W6_SPI_MAGIC; + h->type = (uint8_t) f; + h->vflags = 0; + h->len = len; + h->reserved = 0; + memmove(h + 1, data, len); +} + +static size_t st67w6_spi_poll(uint8_t *write, uint8_t *read) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct spi_hdr *th, *rh = (struct spi_hdr *) read; + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; + size_t padded; + unsigned int times; + + th = (write != NULL) ? (struct spi_hdr *) write : (struct spi_hdr *) idlehdr; + s->begin(s->spi); + times = 50; + while (times--) { + if (d->is_ready()) break; + if (times == 0) { + MG_ERROR(("RDY TIMEOUT")); + s->end(s->spi); + return 0; + } + mg_delayms(1); + } + + padded = (th->len + 3) & ~3; + s->txn(s->spi, (uint8_t *) th, (uint8_t *) rh, sizeof(*th) + padded); + if (rh->magic == ST67W6_SPI_MAGIC && rh->len > padded) { + size_t remaining_padded = (rh->len - padded + 3) & ~3; + if (remaining_padded > (2048 - sizeof(*rh) - padded)) + remaining_padded = 2048 - sizeof(*rh) - padded; + s->txn(s->spi, NULL, read + sizeof(*rh) + padded, remaining_padded); + } + times = 50; + while (times--) { + if (!d->is_ready()) break; + if (times == 0) { + MG_ERROR(("!RDY TIMEOUT")); + break; + } + mg_delayms(1); + } + s->end(s->spi); + return (size_t) rh->len; +} + +static bool st67w6_spi_init(void) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + size_t len; + unsigned int times = 1000; + while (times--) { + if (d->is_ready()) break; + if (times == 0) return false; + mg_delayms(1); + } + if (((len = st67w6_spi_poll(NULL, (uint8_t *) rxdata)) == 0) || + !mg_match(mg_str_n(((char *) rxdata) + sizeof(struct spi_hdr), len), + mg_str_n("*ready*", 7), NULL)) + return false; + return true; +} + +// Mongoose Wi-Fi API functions + +bool mg_wifi_scan(void) { + return st67w6_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 st67w6_wifi_connect(wifi->ssid, wifi->pass); +} + +bool mg_wifi_disconnect(void) { + return st67w6_wifi_disconnect(); +} + +bool mg_wifi_ap_start(struct mg_wifi_data *wifi) { + MG_DEBUG(("Starting AP '%s' (%u)", wifi->apssid, wifi->apchannel)); + return st67w6_wifi_ap_start(wifi->apssid, wifi->appass, wifi->apchannel); +} + +bool mg_wifi_ap_stop(void) { + return st67w6_wifi_ap_stop(); +} + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/drivers/stm32f.c" #endif diff --git a/mongoose.h b/mongoose.h index 0fa8353d..1baf4619 100644 --- a/mongoose.h +++ b/mongoose.h @@ -3336,6 +3336,7 @@ extern struct mg_tcpip_driver mg_tcpip_driver_pico_w; extern struct mg_tcpip_driver mg_tcpip_driver_rw612; extern struct mg_tcpip_driver mg_tcpip_driver_cyw; extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; +extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { @@ -3743,6 +3744,43 @@ bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, #endif +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ST67W6) && \ + MG_ENABLE_DRIVER_ST67W6 + +struct mg_tcpip_spi_ { + void *spi; // Opaque SPI bus descriptor + void (*begin)(void *); // SPI begin: slave select low + void (*end)(void *); // SPI end: slave select high + void (*txn)(void *, uint8_t *, uint8_t *, + size_t len); // SPI transaction: write-read len bytes +}; + +struct mg_tcpip_driver_st67w6_data { + struct mg_wifi_data wifi; + void *spi; + bool (*is_ready)(void); // return state of module RDY pin + struct mg_queue send_queue; // decouple tx calls from module polls +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_st67w6_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_st67w6; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: st67w6, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \ MG_ENABLE_DRIVER_STM32F diff --git a/src/drivers/st67w6.c b/src/drivers/st67w6.c new file mode 100644 index 00000000..da53b050 --- /dev/null +++ b/src/drivers/st67w6.c @@ -0,0 +1,528 @@ +#include "st67w6.h" +#include "net.h" + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ST67W6) && \ + MG_ENABLE_DRIVER_ST67W6 + +static struct mg_tcpip_if *s_ifp; +static uint32_t s_ip, s_mask; +static bool s_link = false, s_connecting = false; + +static void wifi_cb(struct mg_tcpip_if *ifp, int ev, void *ev_data) { + struct mg_wifi_data *wifi = + &((struct mg_tcpip_driver_st67w6_data *) ifp->driver_data)->wifi; + if (wifi->apmode && ev == MG_TCPIP_EV_ST_CHG && + *(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 st67w6_init(uint8_t *mac); +static void st67w6_poll(bool is_at); + +static bool mg_tcpip_driver_st67w6_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + struct mg_wifi_data *wifi = &d->wifi; + if (MG_BIG_ENDIAN) { + MG_ERROR(("Big-endian host")); + return false; + } + if (d->is_ready == NULL) return false; + s_ifp = ifp; + s_ip = ifp->ip; + s_mask = ifp->mask; + s_link = false; + ifp->pfn = wifi_cb; + if (!st67w6_init(ifp->mac)) return false; + + if (d->send_queue.size == 0) d->send_queue.size = 8192; + d->send_queue.buf = (char *) mg_calloc(1, d->send_queue.size); + if (d->send_queue.buf == NULL) { + MG_ERROR(("OOM")); + 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; +} + +// Decouple; module access depends on it being RDY. See st67w6_poll() +size_t mg_tcpip_driver_st67w6_output(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + char *p; + if (mg_queue_book(&d->send_queue, &p, len) < len) return 0; + memcpy(p, buf, len); + mg_queue_add(&d->send_queue, len); + return len; +} + +static bool mg_tcpip_driver_st67w6_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; + if (d->is_ready == NULL) return false; + st67w6_poll(false); + if (!s1) return false; + return s_link; +} + +struct mg_tcpip_driver mg_tcpip_driver_st67w6 = { + mg_tcpip_driver_st67w6_init, mg_tcpip_driver_st67w6_output, NULL, + mg_tcpip_driver_st67w6_poll}; + +// AT | STA | AP | HCI | OT +// -------------------------- +// framing <-- includes rx stall indication +// -------------------------- +// SPI <-- padded to 32-bit +// +// Transactions take place when the module signals RDY, 'tx' and 'write' +// functions actually just write to memory +// - AT: handles configuration and events (unsolicited +foo:). Responses may +// come as: +// - 1 frame: just OK or ERROR +// - 2 frames: 1 text line each, a response and OK +// - 3 frames: 2 text lines: 1 frame with text and no CR/LF, 1 frame with +// binary and CR/LF, 1 text line with OK +// - STA and AP contain plain Ethernet frames to/from the STA and AP networks, +// respectively +// - HCI: BLE stuff +// - OT: ? + +#pragma pack(push, 1) +// little endian + +struct spi_hdr { + uint16_t magic; + uint16_t len; + uint8_t vflags; // version :2, rx_stall :1, flags :5 + uint8_t type; + uint16_t reserved; +}; + +#define ST67W6_SPI_TYPE_AT 0 +#define ST67W6_SPI_TYPE_STA 1 +#define ST67W6_SPI_TYPE_AP 2 +#define ST67W6_SPI_TYPE_HCI 3 +#define ST67W6_SPI_TYPE_OT 4 + +#pragma pack(pop) + +static uint32_t txdata[2048 / 4], rxdata[2048 / 4]; +static uint8_t at_resp[300]; + +static bool s_at_ok, s_at_err; +static size_t s_at_resp_len; +static void st67w6_handle_wifi_evnt(char *, size_t len); +static void st67w6_handle_scan_result(char *, size_t len); + +static size_t st67w6_spi_poll(uint8_t *write, uint8_t *read); +static void st67w6_write(unsigned int f, void *data, uint16_t len); +static void st67w6_update_hash_table(void); + +// High-level comm stuff + +static void st67w6_poll(bool is_at) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct spi_hdr *h = (struct spi_hdr *) rxdata; + unsigned int type; + uint8_t *txsource = (uint8_t *) txdata; + if (s_ifp->update_mac_hash_table) { + // first call to _poll() is after _init(), so this is safe + st67w6_update_hash_table(); + s_ifp->update_mac_hash_table = false; + } + if (!is_at) { // send outstanding WLAN frames in the queue + char *buf; + size_t len; + // NOTE(): have traffic-dependent queues or queue traffic type with data + if ((len = mg_queue_next(&d->send_queue, &buf)) > 0) { + st67w6_write(d->wifi.apmode ? ST67W6_SPI_TYPE_AP : ST67W6_SPI_TYPE_STA, + buf, (uint16_t) len); + mg_queue_del(&d->send_queue, len); + } else { // nothing to send + txsource = NULL; + } + } + if (st67w6_spi_poll(txsource, (uint8_t *) rxdata) == 0) return; + if (h->len == 0) return; + type = h->type; + if (type == ST67W6_SPI_TYPE_AT) { + char *p = (char *) (h + 1); + size_t len = h->len; + if (len > 7 && strncmp(p, "+CWLAP:", 7) == 0) { // scan result + st67w6_handle_scan_result(p + 7, len - 7); + } else if (len > 4 && strncmp(p, "+CW:", 4) == 0) { + st67w6_handle_wifi_evnt(p + 4, len - 4); + } else { + MG_VERBOSE(("AT partial: %.*s", (int) len, p)); + if (s_at_resp_len + len >= sizeof(at_resp)) + s_at_resp_len = 0; // truncate response, error will be caught later + memcpy(at_resp + s_at_resp_len, p, len); + s_at_resp_len += len; + if (mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*ERROR*", 7), NULL)) { + s_at_err = true; + } else if (mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*OK*", 4), NULL)) { + s_at_ok = true; + } + } + } else if (type == ST67W6_SPI_TYPE_STA || type == ST67W6_SPI_TYPE_AP) { + // WLAN frame reception + mg_tcpip_qwrite(h + 1, h->len, s_ifp); + } // else silently discard +} + +// WLAN event handling + +// - Do not call any AT functions here, otherwise revise st67w6_at_wait() +// - The module likes to send ERROR along with other events +static void st67w6_handle_wifi_evnt(char *p, size_t len) { + struct mg_str data[2]; + MG_VERBOSE(("event: %.*s", (int) len, p)); + if (len > 9 && strncmp(p, "CONNECTED", 9) == 0) { + s_link = true; + s_connecting = false; + } else if (len > 12 && strncmp(p, "DISCONNECTED", 12) == 0) { + s_link = false; + s_connecting = false; // should not be needed + } else if (s_connecting && len > 6 && + mg_match(mg_str_n(p, len), mg_str_n("ERROR,*\r\n*", 10), data)) { + size_t reason = 0; + bool ok = mg_to_size_t(data[0], &reason); + s_connecting = false; + MG_ERROR(("CONNECT FAILED")); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_CONNECT_ERR, ok ? &reason : NULL); + } else if (len > 9 && strncmp(p, "SCAN_DONE", 9) == 0) { + MG_VERBOSE(("scan complete")); + mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_END, NULL); + } // else silently discard: CONNECTING; STA_CONNECTED,"MAC"; + // STA_DISCONNECTED,"MAC"; DIST_STA_IP,"MAC","IP" +} + +static bool st67w6_at_cmd(char *cmd, size_t len); + +// Wi-Fi network stuff + +static bool st67w6_wifi_connect(char *ssid, char *pass) { + char cmd[90]; // ssid + pass + AT + size_t cmd_len; + if (!st67w6_at_cmd("AT+CWMODE=1,0\r\n", 15)) return false; + cmd_len = mg_snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\",,0\r\n", ssid, + pass); // takes >700ms + if (!st67w6_at_cmd(cmd, cmd_len)) return false; + st67w6_at_cmd("AT+CWRECONNCFG=0,0\r\n", 20); // disregard error, connecting + s_connecting = true; + return true; +} + +static bool st67w6_wifi_disconnect(void) { + s_connecting = false; + if (!st67w6_at_cmd("AT+CWQAP=0\r\n", 12)) return false; // takes >800ms + return st67w6_at_cmd("AT+CWMODE=0,0\r\n", 15); // takes >550ms +} + +static bool st67w6_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { + char cmd[90]; // ssid + pass + AT + size_t cmd_len; + if (!st67w6_at_cmd("AT+CWMODE=2,0\r\n", 15)) return false; // takes >800ms + cmd_len = mg_snprintf( + cmd, sizeof(cmd), "AT+CWSAP=\"%s\",\"%s\",%u,3,2,0\r\n", ssid, pass, + channel); // 3: WPA2_PSK; 2: max stations // takes >350ms + s_link = true; + return st67w6_at_cmd(cmd, cmd_len); +} + +static bool st67w6_wifi_ap_stop(void) { + s_link = false; + return st67w6_at_cmd("AT+CWMODE=0,0\r\n", 15); // takes >550ms +} + +// WLAN scan handling + +// +CWLAP:(security,"SSID",RSSI,"BSSID",channel,cipher,proto,wps)\r\n +// security: OPEN, WEP, WPA, WPA2, WPA-WPA2, WPA-EAP, WPA3-SAE, WPA2-WPA3-SAE +// cipher: NONE, WEP, AES/CCMP, TKIP, TKIP and AES/CCMP +// proto: 4-bit bitmap AX,N,G,B; all set from right to left + +static bool st67w6_wifi_scan(void) { + return st67w6_at_cmd("AT+CWLAPOPT=1,1695,-100,255,50\r\n", 32) && + st67w6_at_cmd("AT+CWLAP=0,,,0\r\n", 16); +} + +static void st67w6_handle_scan_result(char *data, size_t len) { + struct mg_wifi_scan_bss_data bss; + struct mg_str fields[2]; + char mac[6]; + uint8_t val; + unsigned int i; + MG_VERBOSE(("scan result event: %.*s", (int) len, data)); + ++data, --len; // skip '(' + if (!mg_span(mg_str_n(data, len), &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + bss.security = + (val == 0) + ? MG_WIFI_SECURITY_OPEN + : MG_WIFI_SECURITY_WEP | + ((val == 2 || val == 4 || val == 5) ? MG_WIFI_SECURITY_WPA + : 0) | + ((val == 3 || val == 4 || val == 7) ? MG_WIFI_SECURITY_WPA2 + : 0) | + ((val == 6 || val == 7) ? MG_WIFI_SECURITY_WPA3 : 0); + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + bss.SSID.buf = fields[0].buf + 1, bss.SSID.len = fields[0].len - 2; + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + while (fields[0].buf[0] == ' ') ++fields[0].buf, --fields[0].len; + if (fields[0].buf[0] != '-') return; // positive RSSI would be great + ++fields[0].buf, --fields[0].len; + if (!mg_str_to_num(fields[0], 10, &val, 1)) return; + bss.RSSI = (int8_t) - (int8_t) val; + if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; + if (fields[0].len < 19) return; + ++fields[0].buf, --fields[0].len; // skip '"' + for (i = 0; i < 6; i++) { + struct mg_str str; + str.buf = fields[0].buf + 3 * i; + str.len = 2; + if (!mg_str_to_num(str, 16, &mac[i], 1)) return; + } + bss.BSSID = mac; + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &bss.channel, 1)) + return; + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + // ignore cypher + if (!mg_span(fields[1], &fields[0], &fields[1], ',') || + !mg_str_to_num(fields[0], 10, &val, 1)) + return; + bss.has_n = (val & 4) != 0; + // bss.has_ax = (val & 8) != 0; + bss.band = MG_WIFI_BAND_2G; // NOT INFORMED with default options, no docs + 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); +} + +// AT stuff + +static inline bool delayms(unsigned int ms) { + mg_delayms(ms); + return true; +} + +// send AT command, wait for a response or timeout, meanwhile delivering +// received frames and events + +static bool st67w6_at_cmd(char *cmd, size_t len) { + bool is_at = true; + unsigned int times = 1000; + s_at_resp_len = 0; + st67w6_write(ST67W6_SPI_TYPE_AT, cmd, (uint16_t) len); + s_at_ok = false, s_at_err = false; + do { // AT response processing does not call any other AT function + st67w6_poll(is_at); // otherwise we can't allow them to pile up here + is_at = false; // avoid repeating, allow queued WLAN frames to be sent + // network frames will be pushed to the queue so that is safe + } while (!s_at_ok && !s_at_err && times-- > 0 && delayms(1)); + MG_VERBOSE(("AT response:\n%.*s", s_at_resp_len, at_resp)); + MG_VERBOSE(("ok: %c, err: %c, times: %d", s_at_ok ? '1' : '0', + s_at_err ? '1' : '0', (int) times)); + return s_at_ok; +} + +static bool st67w6_spi_init(void); +bool mg_to_size_t(struct mg_str str, size_t *val); + +static bool st67w6_init(uint8_t *mac) { + // struct mg_tcpip_driver_st67w6_data *d = (struct + // mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct mg_str data[3]; + size_t val; + bool is_b = false; + if (!st67w6_spi_init()) return false; + if (!st67w6_at_cmd("AT\r\n", 4)) return false; + if (!st67w6_at_cmd("AT+CWNETMODE?\r\n", 15) || + !mg_match(mg_str_n((char *) at_resp, s_at_resp_len), + mg_str_n("*:*\r\n*", 6), data) || + !mg_to_size_t(data[1], &val)) + return false; + if (val != 0) { + MG_ERROR(("Wrong firmware, T02 is needed")); + return false; + } + // set clock, who cares ??? + if (!st67w6_at_cmd("AT+GET_CLOCK\r\n", 14)) return false; + MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // TODO(scaprile): --> VERBOSE + // BT-ENABLED DEPENDENCY + + // MODULE DEPENDENCY + if (!st67w6_at_cmd("AT+EFUSE-R=24,\"0x100\"\r\n", 23) || + !mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*,*", 3), + data)) + return false; + if (data[1].buf[0] == 'C' && data[1].buf[1] == '6') is_b = true; + MG_DEBUG(("WLAN module is %sB type", is_b ? "" : "not")); // --> VERBOSE + if (is_b) { + // Disable the antenna diversity pin + if (!st67w6_at_cmd("AT+IORST=0\r\n", 12)) return false; + // Apparently they intend to disable some antenna ... + if (!st67w6_at_cmd("AT+CWANTENABLE?\r\n", 17)) return false; + MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // --> VERBOSE + } + + // Do not set wake-up pin (AT+SLWKIO) + // Disable power save mode + // NOTE(scaprile): (no response if in hibernate mode, though I guess we + // wouldn't have reached this point in that case either) + if (!st67w6_at_cmd("AT+PWR=0\r\n", 12)) return false; + // set Wi-Fi + // set country code + if (!st67w6_at_cmd("AT+CWCOUNTRY=0,\"00\"\r\n", 21)) return false; +#if 0 + // set DTIM + if (!st67w6_at_cmd("AT+SLWKDTIM=1\r\n", 15)) return false; +#endif + // Read only default MAC, ignore set bit count (data[6] & 0x3F). Custom MACs + // reside at @0x64 and 0x70 + if (st67w6_at_cmd("AT+EFUSE-R=7,\"0x014\"\r\n", 22) && + mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*,*", 3), + data)) { + int i; + for (i = 0; i < 6; i++) mac[i] = data[1].buf[5 - i]; + MG_DEBUG(("MAC: %M", mg_print_mac, mac)); + } else { + MG_ERROR(("read MAC failed")); + } + return true; +} + +static void st67w6_update_hash_table(void) { + // TODO(): read database, rebuild hash table + // uint32_t val = 0; + // val = 1; + // st67w6_at_iovar_set2_(0, "mcast_list", (uint8_t *) &val, sizeof(val), + // (uint8_t *) mcast_addr, sizeof(mcast_addr)); mg_delayms(50); +} + +// SPI specifics + +#define ST67W6_SPI_MAGIC 0x55AA +// #define ST67W6_SPI_VERSION(x), IS_STALL, FLAGS(x), ... + +static const uint8_t idlehdr[sizeof(struct spi_hdr)] = {0xaa, 0x55, 0, 0, + 0, 0, 0, 0}; + +static void st67w6_write(unsigned int f, void *data, uint16_t len) { + struct spi_hdr *h = (struct spi_hdr *) txdata; + h->magic = ST67W6_SPI_MAGIC; + h->type = (uint8_t) f; + h->vflags = 0; + h->len = len; + h->reserved = 0; + memmove(h + 1, data, len); +} + +static size_t st67w6_spi_poll(uint8_t *write, uint8_t *read) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + struct spi_hdr *th, *rh = (struct spi_hdr *) read; + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; + size_t padded; + unsigned int times; + + th = (write != NULL) ? (struct spi_hdr *) write : (struct spi_hdr *) idlehdr; + s->begin(s->spi); + times = 50; + while (times--) { + if (d->is_ready()) break; + if (times == 0) { + MG_ERROR(("RDY TIMEOUT")); + s->end(s->spi); + return 0; + } + mg_delayms(1); + } + + padded = (th->len + 3) & ~3; + s->txn(s->spi, (uint8_t *) th, (uint8_t *) rh, sizeof(*th) + padded); + if (rh->magic == ST67W6_SPI_MAGIC && rh->len > padded) { + size_t remaining_padded = (rh->len - padded + 3) & ~3; + if (remaining_padded > (2048 - sizeof(*rh) - padded)) + remaining_padded = 2048 - sizeof(*rh) - padded; + s->txn(s->spi, NULL, read + sizeof(*rh) + padded, remaining_padded); + } + times = 50; + while (times--) { + if (!d->is_ready()) break; + if (times == 0) { + MG_ERROR(("!RDY TIMEOUT")); + break; + } + mg_delayms(1); + } + s->end(s->spi); + return (size_t) rh->len; +} + +static bool st67w6_spi_init(void) { + struct mg_tcpip_driver_st67w6_data *d = + (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; + size_t len; + unsigned int times = 1000; + while (times--) { + if (d->is_ready()) break; + if (times == 0) return false; + mg_delayms(1); + } + if (((len = st67w6_spi_poll(NULL, (uint8_t *) rxdata)) == 0) || + !mg_match(mg_str_n(((char *) rxdata) + sizeof(struct spi_hdr), len), + mg_str_n("*ready*", 7), NULL)) + return false; + return true; +} + +// Mongoose Wi-Fi API functions + +bool mg_wifi_scan(void) { + return st67w6_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 st67w6_wifi_connect(wifi->ssid, wifi->pass); +} + +bool mg_wifi_disconnect(void) { + return st67w6_wifi_disconnect(); +} + +bool mg_wifi_ap_start(struct mg_wifi_data *wifi) { + MG_DEBUG(("Starting AP '%s' (%u)", wifi->apssid, wifi->apchannel)); + return st67w6_wifi_ap_start(wifi->apssid, wifi->appass, wifi->apchannel); +} + +bool mg_wifi_ap_stop(void) { + return st67w6_wifi_ap_stop(); +} + +#endif diff --git a/src/drivers/st67w6.h b/src/drivers/st67w6.h new file mode 100644 index 00000000..d94c5474 --- /dev/null +++ b/src/drivers/st67w6.h @@ -0,0 +1,37 @@ +#pragma once + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ST67W6) && \ + MG_ENABLE_DRIVER_ST67W6 + +struct mg_tcpip_spi_ { + void *spi; // Opaque SPI bus descriptor + void (*begin)(void *); // SPI begin: slave select low + void (*end)(void *); // SPI end: slave select high + void (*txn)(void *, uint8_t *, uint8_t *, + size_t len); // SPI transaction: write-read len bytes +}; + +struct mg_tcpip_driver_st67w6_data { + struct mg_wifi_data wifi; + void *spi; + bool (*is_ready)(void); // return state of module RDY pin + struct mg_queue send_queue; // decouple tx calls from module polls +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_st67w6_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_st67w6; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: st67w6, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif diff --git a/src/net_builtin.h b/src/net_builtin.h index 42d6afdd..d04b150c 100644 --- a/src/net_builtin.h +++ b/src/net_builtin.h @@ -116,6 +116,7 @@ extern struct mg_tcpip_driver mg_tcpip_driver_pico_w; extern struct mg_tcpip_driver mg_tcpip_driver_rw612; extern struct mg_tcpip_driver mg_tcpip_driver_cyw; extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; +extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { diff --git a/src/wifi_dummy.c b/src/wifi_dummy.c index b1c73796..6a9fd3d5 100644 --- a/src/wifi_dummy.c +++ b/src/wifi_dummy.c @@ -3,7 +3,8 @@ #if (!defined(MG_ENABLE_DRIVER_PICO_W) || !MG_ENABLE_DRIVER_PICO_W) && \ (!defined(MG_ENABLE_DRIVER_CYW) || !MG_ENABLE_DRIVER_CYW) && \ (!defined(MG_ENABLE_DRIVER_CYW_SDIO) || !MG_ENABLE_DRIVER_CYW_SDIO) && \ - (!defined(MG_ENABLE_DRIVER_NXP_WIFI) || !MG_ENABLE_DRIVER_NXP_WIFI) + (!defined(MG_ENABLE_DRIVER_NXP_WIFI) || !MG_ENABLE_DRIVER_NXP_WIFI) && \ + (!defined(MG_ENABLE_DRIVER_ST67W6) || !MG_ENABLE_DRIVER_ST67W6) bool mg_wifi_scan(void) {