Files
mongoose/src/drivers/st67w6.c
T

526 lines
18 KiB
C

#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_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 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 : (uint8_t) MG_WIFI_SECURITY_WEP;
if (val == 2 || val == 4) bss.security |= MG_WIFI_SECURITY_WPA;
if (val == 3 || val == 4 || val == 7) bss.security |= MG_WIFI_SECURITY_WPA2;
if (val == 6 || val == 7) bss.security |= MG_WIFI_SECURITY_WPA3;
if (val == 5) bss.security |= MG_WIFI_SECURITY_WPA_ENTERPRISE;
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