From 3dd4c7da2a1468cb19280184f6e0e2d048553d72 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka Date: Wed, 27 Nov 2024 22:00:30 +0000 Subject: [PATCH] Add PPP driver --- examples/arduino/sim800-mqtt/mongoose.c | 1 + examples/arduino/sim800-mqtt/mongoose.h | 1 + .../arduino/sim800-mqtt/mongoose_config.h | 21 ++ examples/arduino/sim800-mqtt/sim800-mqtt.ino | 103 ++++++ mongoose.c | 327 +++++++++++++++++- mongoose.h | 70 ++-- src/drivers/ppp.c | 321 +++++++++++++++++ src/drivers/ppp.h | 11 + src/net_builtin.c | 2 +- src/net_builtin.h | 2 + 10 files changed, 826 insertions(+), 33 deletions(-) create mode 120000 examples/arduino/sim800-mqtt/mongoose.c create mode 120000 examples/arduino/sim800-mqtt/mongoose.h create mode 100644 examples/arduino/sim800-mqtt/mongoose_config.h create mode 100644 examples/arduino/sim800-mqtt/sim800-mqtt.ino create mode 100644 src/drivers/ppp.c create mode 100644 src/drivers/ppp.h diff --git a/examples/arduino/sim800-mqtt/mongoose.c b/examples/arduino/sim800-mqtt/mongoose.c new file mode 120000 index 0000000000..5e522bbcd4 --- /dev/null +++ b/examples/arduino/sim800-mqtt/mongoose.c @@ -0,0 +1 @@ +../../../mongoose.c \ No newline at end of file diff --git a/examples/arduino/sim800-mqtt/mongoose.h b/examples/arduino/sim800-mqtt/mongoose.h new file mode 120000 index 0000000000..ee4ac82323 --- /dev/null +++ b/examples/arduino/sim800-mqtt/mongoose.h @@ -0,0 +1 @@ +../../../mongoose.h \ No newline at end of file diff --git a/examples/arduino/sim800-mqtt/mongoose_config.h b/examples/arduino/sim800-mqtt/mongoose_config.h new file mode 100644 index 0000000000..94c0a1ea03 --- /dev/null +++ b/examples/arduino/sim800-mqtt/mongoose_config.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Arduino.h" + +#include +#include +#include +#include + +#define MG_ARCH MG_ARCH_CUSTOM +#define MG_ENABLE_SOCKET 0 +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_DRIVER_PPP 1 +#define MG_ENABLE_TCPIP_DRIVER_INIT 0 +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_IO_SIZE 128 + +// Enable TLS +// #define MG_TLS MG_TLS_BUILTIN +// #define MG_ENABLE_CUSTOM_RANDOM 1 diff --git a/examples/arduino/sim800-mqtt/sim800-mqtt.ino b/examples/arduino/sim800-mqtt/sim800-mqtt.ino new file mode 100644 index 0000000000..355090da42 --- /dev/null +++ b/examples/arduino/sim800-mqtt/sim800-mqtt.ino @@ -0,0 +1,103 @@ +#include +#include "mongoose.h" + +#define MQTT_SERVER "mqtt://broker.hivemq.com:1883" +#define MQTT_SUB_TOPIC "mg/rx" // Subscribe to this topic +#define MQTT_PUB_TOPIC "mg/tx" // Publish to this topic + +static const char *script[] = { + "AT\r\n", "*OK\r\n", + "ATZ\r\n", "*OK\r\n", + "AT+CPIN?\r\n", "*OK\r\n", + "AT+CNMI=0,0,0,0,0\r\n", "*OK\r\n", + "AT+CGDCONT=1,\"IP\",\"iot.1nce.net\"\r\n", "*OK\r\n", + "AT+CGDATA=\"PPP\",1\r\n", "*CONNECT\r\n", + NULL +}; + +// We use software serial to communicate with the modem +#define LED_PIN LED_BUILTIN +#define RX_PIN 9 +#define TX_PIN 8 +SoftwareSerial SSerial(RX_PIN, TX_PIN); + +struct mg_connection *mqtt_connection; +struct mg_tcpip_driver_ppp_data driver_data; +struct mg_mgr mgr; // Mongoose event manager +struct mg_tcpip_if mif = {.mac = {2, 0, 1, 2, 3, 5}}; // Network interface + +uint64_t mg_millis(void) { + return millis(); +} + +void mqtt_publish(const char *message) { + struct mg_mqtt_opts opts = {}; + opts.topic = mg_str(MQTT_PUB_TOPIC); + opts.message = mg_str(message); + if (mqtt_connection) mg_mqtt_pub(mqtt_connection, &opts); +} + +void handle_command(struct mg_str msg) { + if (msg.len == 3 && memcmp(msg.buf, "off", 3) == 0) { + digitalWrite(LED_PIN, LOW); + mqtt_publish("done - off"); + } else if (msg.len == 2 && memcmp(msg.buf, "on", 2) == 0) { + digitalWrite(LED_PIN, HIGH); + mqtt_publish("done - on"); + } +} + +static void mqtt_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_MQTT_OPEN) { + MG_INFO(("%lu CONNECTED to %s", c->id, MQTT_SERVER)); + struct mg_mqtt_opts opts = {}; + opts.topic = mg_str(MQTT_SUB_TOPIC); + mg_mqtt_sub(c, &opts); + MG_INFO(("%lu SUBSCRIBED to %s", c->id, MQTT_SUB_TOPIC)); + } else if (ev == MG_EV_MQTT_MSG) { + // Received MQTT message + struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; + MG_INFO(("%lu RECEIVED %.*s <- %.*s", c->id, (int) mm->data.len, + mm->data.buf, (int) mm->topic.len, mm->topic.buf)); + handle_command(mm->data); + } else if (ev == MG_EV_CLOSE) { + MG_INFO(("%lu CLOSED", c->id)); + mqtt_connection = NULL; + } +} + +void reconnect_if_not_connected(void) { + if (mif.state == MG_TCPIP_STATE_READY && mqtt_connection == NULL) { + struct mg_mqtt_opts opts = {}; + opts.clean = true; + mqtt_connection = + mg_mqtt_connect(&mgr, MQTT_SERVER, &opts, mqtt_ev_handler, NULL); + } +} + +void setup() { + Serial.begin(115200); // Initialise serial + while (!Serial) delay(50); // for debug output + + pinMode(LED_PIN, OUTPUT); // Initialise LED + pinMode(RX_PIN, INPUT); + pinMode(TX_PIN, OUTPUT); + SSerial.begin(19200); + + mg_mgr_init(&mgr); // Initialise Mongoose event manager + mg_log_set(MG_LL_DEBUG); // Set debug log level + mg_log_set_fn([](char ch, void *) { Serial.print(ch); }, NULL); // Log serial + + mif.driver = &mg_tcpip_driver_ppp; // Initialise built-in TCP/IP stack + mif.driver_data = &driver_data; // with the cellular driver + driver_data.script = script; + driver_data.tx = [](void *, uint8_t c) { SSerial.write(c); }, + driver_data.rx = [](void *) { return SSerial.available() ? SSerial.read() : -1; }, + mg_tcpip_init(&mgr, &mif); + mif.enable_dhcp_client = false; +} + +void loop() { + mg_mgr_poll(&mgr, 1); // Process network events + reconnect_if_not_connected(); // Reconnect to MQTT server if needed +} diff --git a/mongoose.c b/mongoose.c index 91283fe356..8ce4515d31 100644 --- a/mongoose.c +++ b/mongoose.c @@ -4974,7 +4974,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { #endif // Handle gw ARP request timeout, order is important if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { - ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC + ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC onstatechange(ifp); } // Handle physical interface up/down status @@ -17789,6 +17789,331 @@ bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool *full_duplex, return up; } +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/ppp.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_PPP) && MG_ENABLE_DRIVER_PPP + +#define MG_PPP_FLAG 0x7e // PPP frame delimiter +#define MG_PPP_ESC 0x7d // PPP escape byte for special characters +#define MG_PPP_ADDR 0xff +#define MG_PPP_CTRL 0x03 + +#define MG_PPP_PROTO_IP 0x0021 +#define MG_PPP_PROTO_LCP 0xc021 +#define MG_PPP_PROTO_IPCP 0x8021 + +#define MG_PPP_IPCP_REQ 1 +#define MG_PPP_IPCP_ACK 2 +#define MG_PPP_IPCP_NACK 3 +#define MG_PPP_IPCP_IPADDR 3 + +#define MG_PPP_LCP_CFG_REQ 1 +#define MG_PPP_LCP_CFG_ACK 2 +#define MG_PPP_LCP_CFG_NACK 3 +#define MG_PPP_LCP_CFG_REJECT 4 +#define MG_PPP_LCP_CFG_TERM_REQ 5 +#define MG_PPP_LCP_CFG_TERM_ACK 6 + +#define MG_PPP_AT_TIMEOUT 2000 + +static size_t print_atcmd(void (*out)(char, void *), void *arg, va_list *ap) { + struct mg_str s = va_arg(*ap, struct mg_str); + for (size_t i = 0; i < s.len; i++) out(s.buf[i] < 0x20 ? '.' : s.buf[i], arg); + return s.len; +} + +static bool mg_ppp_atcmd_handle(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + if (dd->script == NULL || dd->script_index < 0) return true; + if (dd->deadline == 0) dd->deadline = mg_millis() + MG_PPP_AT_TIMEOUT; + for (;;) { + if (dd->script_index % 2 == 0) { // send AT command + const char *cmd = dd->script[dd->script_index]; + MG_DEBUG(("send AT[%d]: %M", dd->script_index, print_atcmd, mg_str(cmd))); + while (*cmd) dd->tx(dd->uart, *cmd++); + dd->script_index++; + ifp->recv_queue.head = 0; + } else { // check AT command response + const char *expect = dd->script[dd->script_index]; + struct mg_queue *q = &ifp->recv_queue; + for (;;) { + int c; + int is_timeout = dd->deadline > 0 && mg_millis() > dd->deadline; + int is_overflow = q->head >= q->size - 1; + if (is_timeout || is_overflow) { + MG_ERROR(("AT error: %s, retrying... %u [%.*s]", + is_timeout ? "timeout" : "overflow", q->head, q->head, q->buf)); + dd->script_index = 0; + dd->deadline = mg_millis() + MG_PPP_AT_TIMEOUT; + if (dd->reset) dd->reset(dd->uart); + return false; // FAIL: timeout + } + if ((c = dd->rx(dd->uart)) < 0) return false; // no data + q->buf[q->head++] = c; + if (mg_match(mg_str_n(q->buf, q->head), mg_str(expect), NULL)) { + MG_DEBUG(("recv AT[%d]: %M", dd->script_index, print_atcmd, + mg_str_n(q->buf, q->head))); + dd->script_index++; + q->head = 0; + break; + } + } + } + if (dd->script[dd->script_index] == NULL) { + MG_DEBUG(("finished AT script")); + dd->script_index = -1; + return true; + } + } +} + +static bool mg_ppp_init(struct mg_tcpip_if *ifp) { + ifp->recv_queue.size = 3000; // MTU=1500, worst case escaping = 2x + return true; +} + +// Calculate FCS/CRC for PPP frames. Could be implemented faster using lookup +// tables. +static uint32_t fcs_do(uint32_t fcs, uint8_t x) { + for (int i = 0; i < 8; i++) { + fcs = ((fcs ^ x) & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1; + x >>= 1; + } + return fcs; +} + +static bool mg_ppp_up(struct mg_tcpip_if *ifp) { + return ifp->driver_data != NULL; +} + +// Transmit a single byte as part of the PPP frame (escaped, if needed) +static void mg_ppp_tx_byte(struct mg_tcpip_driver_ppp_data *dd, uint8_t b) { + if ((b < 0x20) || (b == MG_PPP_ESC) || (b == MG_PPP_FLAG)) { + dd->tx(dd->uart, MG_PPP_ESC); + dd->tx(dd->uart, b ^ 0x20); + } else { + dd->tx(dd->uart, b); + } +} + +// Transmit a single PPP frame for the given protocol +static void mg_ppp_tx_frame(struct mg_tcpip_driver_ppp_data *dd, uint16_t proto, + uint8_t *data, size_t datasz) { + uint16_t crc; + uint32_t fcs = 0xffff; + + dd->tx(dd->uart, MG_PPP_FLAG); + mg_ppp_tx_byte(dd, MG_PPP_ADDR); + mg_ppp_tx_byte(dd, MG_PPP_CTRL); + mg_ppp_tx_byte(dd, proto >> 8); + mg_ppp_tx_byte(dd, proto & 0xff); + fcs = fcs_do(fcs, MG_PPP_ADDR); + fcs = fcs_do(fcs, MG_PPP_CTRL); + fcs = fcs_do(fcs, proto >> 8); + fcs = fcs_do(fcs, proto & 0xff); + for (unsigned int i = 0; i < datasz; i++) { + mg_ppp_tx_byte(dd, data[i]); + fcs = fcs_do(fcs, data[i]); + } + crc = fcs & 0xffff; + mg_ppp_tx_byte(dd, ~crc); // send CRC, note the byte order + mg_ppp_tx_byte(dd, ~crc >> 8); + dd->tx(dd->uart, MG_PPP_FLAG); // end of frame +} + +// Send Ethernet frame as PPP frame +static size_t mg_ppp_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + if (ifp->state != MG_TCPIP_STATE_READY) return 0; + // XXX: what if not an IP protocol? + mg_ppp_tx_frame(dd, MG_PPP_PROTO_IP, (uint8_t *) buf + 14, len - 14); + return len; +} + +// Given a full PPP frame, unescape it in place and verify FCS, returns actual +// data size on success or 0 on error. +static size_t mg_ppp_verify_frame(uint8_t *buf, size_t bufsz) { + int unpack = 0; + uint16_t crc; + size_t pktsz = 0; + uint32_t fcs = 0xffff; + for (unsigned int i = 0; i < bufsz; i++) { + if (unpack == 0) { + if (buf[i] == 0x7d) { + unpack = 1; + } else { + buf[pktsz] = buf[i]; + fcs = fcs_do(fcs, buf[pktsz]); + pktsz++; + } + } else { + unpack = 0; + buf[pktsz] = buf[i] ^ 0x20; + fcs = fcs_do(fcs, buf[pktsz]); + pktsz++; + } + } + crc = fcs & 0xffff; + if (crc != 0xf0b8) { + MG_DEBUG(("bad crc: %04x", crc)); + return 0; + } + if (pktsz < 6 || buf[0] != MG_PPP_ADDR || buf[1] != MG_PPP_CTRL) { + return 0; + } + return pktsz - 2; // strip FCS +} + +// fetch as much data as we can, until a single PPP frame is received +static size_t mg_ppp_rx_frame(struct mg_tcpip_driver_ppp_data *dd, + struct mg_queue *q) { + while (q->head < q->size) { + int c; + if ((c = dd->rx(dd->uart)) < 0) { + return 0; + } + if (c == MG_PPP_FLAG) { + if (q->head > 0) { + break; + } else { + continue; + } + } + q->buf[q->head++] = c; + } + + size_t n = mg_ppp_verify_frame((uint8_t *) q->buf, q->head); + if (n == 0) { + MG_DEBUG(("invalid PPP frame of %d bytes", q->head)); + q->head = 0; + return 0; + } + q->head = n; + return q->head; +} + +static void mg_ppp_handle_lcp(struct mg_tcpip_if *ifp, uint8_t *lcp, + size_t lcpsz) { + uint8_t id; + uint16_t len; + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + if (lcpsz < 4) return; + id = lcp[1]; + len = (((uint16_t) lcp[2]) << 8) | (lcp[3]); + switch (lcp[0]) { + case MG_PPP_LCP_CFG_REQ: { + if (len == 4) { + MG_DEBUG(("LCP config request of %d bytes, acknowledging...", len)); + lcp[0] = MG_PPP_LCP_CFG_ACK; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_LCP, lcp, len); + lcp[0] = MG_PPP_LCP_CFG_REQ; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_LCP, lcp, len); + } else { + MG_DEBUG(("LCP config request of %d bytes, rejecting...", len)); + lcp[0] = MG_PPP_LCP_CFG_REJECT; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_LCP, lcp, len); + } + } break; + case MG_PPP_LCP_CFG_TERM_REQ: { + uint8_t ack[4] = {MG_PPP_LCP_CFG_TERM_ACK, id, 0, 4}; + MG_DEBUG(("LCP termination request, acknowledging...")); + mg_ppp_tx_frame(dd, MG_PPP_PROTO_LCP, ack, sizeof(ack)); + ifp->state = MG_TCPIP_STATE_DOWN; + if (dd->reset) dd->reset(dd->uart); + } break; + } +} + +static void mg_ppp_handle_ipcp(struct mg_tcpip_if *ifp, uint8_t *ipcp, + size_t ipcpsz) { + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + uint16_t len; + uint8_t id; + uint8_t req[] = { + MG_PPP_IPCP_REQ, 0, 0, 10, MG_PPP_IPCP_IPADDR, 6, 0, 0, 0, 0}; + if (ipcpsz < 4) return; + id = ipcp[1]; + len = (((uint16_t) ipcp[2]) << 8) | (ipcp[3]); + switch (ipcp[0]) { + case MG_PPP_IPCP_REQ: + MG_DEBUG(("got IPCP config request, acknowledging...")); + if (len >= 10 && ipcp[4] == MG_PPP_IPCP_IPADDR) { + uint8_t *ip = ipcp + 6; + MG_INFO(("host ip: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3])); + } + ipcp[0] = MG_PPP_IPCP_ACK; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_IPCP, ipcp, len); + req[1] = id; + // Request IP address 0.0.0.0 + mg_ppp_tx_frame(dd, MG_PPP_PROTO_IPCP, req, sizeof(req)); + break; + case MG_PPP_IPCP_ACK: + // This usually does not happen, as our "preferred" IP address is invalid + MG_DEBUG(("got IPCP config ack, link is online now")); + ifp->state = MG_TCPIP_STATE_READY; + break; + case MG_PPP_IPCP_NACK: + MG_DEBUG(("got IPCP config nack")); + // NACK contains our "suggested" IP address, use it + if (len >= 10 && ipcp[4] == MG_PPP_IPCP_IPADDR) { + uint8_t *ip = ipcp + 6; + MG_INFO(("ipcp ack, ip: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])); + ipcp[0] = MG_PPP_IPCP_REQ; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_IPCP, ipcp, len); + ifp->ip = ifp->mask = MG_IPV4(ip[0], ip[1], ip[2], ip[3]); + } + break; + } +} + +static size_t mg_ppp_rx(void *ethbuf, size_t ethlen, struct mg_tcpip_if *ifp) { + uint8_t *eth = ethbuf; + size_t ethsz = 0; + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + uint8_t *buf = (uint8_t *) ifp->recv_queue.buf; + + if (!mg_ppp_atcmd_handle(ifp)) return 0; + + size_t bufsz = mg_ppp_rx_frame(dd, &ifp->recv_queue); + if (!bufsz) return 0; + uint16_t proto = (((uint16_t) buf[2]) << 8) | (uint16_t) buf[3]; + switch (proto) { + case MG_PPP_PROTO_LCP: mg_ppp_handle_lcp(ifp, buf + 4, bufsz - 4); break; + case MG_PPP_PROTO_IPCP: mg_ppp_handle_ipcp(ifp, buf + 4, bufsz - 4); break; + case MG_PPP_PROTO_IP: + MG_DEBUG(("got IP packet of %d bytes", bufsz - 4)); + memmove(eth + 14, buf + 4, bufsz - 4); + memmove(eth, ifp->mac, 6); + memmove(eth + 6, "\xff\xff\xff\xff\xff\xff", 6); + eth[12] = 0x08; + eth[13] = 0x00; + ethsz = bufsz - 4 + 14; + ifp->recv_queue.head = 0; + return ethsz; +#if 0 + default: + MG_DEBUG(("unknown PPP frame:")); + mg_hexdump(ppp->buf, ppp->bufsz); +#endif + } + ifp->recv_queue.head = 0; + return 0; + (void) ethlen; +} + +struct mg_tcpip_driver mg_tcpip_driver_ppp = {mg_ppp_init, mg_ppp_tx, mg_ppp_rx, + mg_ppp_up}; + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/drivers/ra.c" #endif diff --git a/mongoose.h b/mongoose.h index d4a31cc210..0a72b2e0d5 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2782,6 +2782,7 @@ extern struct mg_tcpip_driver mg_tcpip_driver_cmsis; extern struct mg_tcpip_driver mg_tcpip_driver_ra; extern struct mg_tcpip_driver mg_tcpip_driver_xmc; extern struct mg_tcpip_driver mg_tcpip_driver_xmc7; +extern struct mg_tcpip_driver mg_tcpip_driver_ppp; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { @@ -2790,6 +2791,7 @@ struct mg_tcpip_spi { void (*end)(void *); // SPI end: slave select high uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1 byte, read reply }; + #endif @@ -2943,6 +2945,17 @@ bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex, uint8_t *speed); +struct mg_tcpip_driver_ppp_data { + void *uart; // Opaque UART bus descriptor + void (*reset)(void *); // Modem hardware reset + void (*tx)(void *, uint8_t); // UART transmit single byte + int (*rx)(void *); // UART receive single byte + const char **script; // List of AT commands and expected replies + int script_index; // Index of the current AT command in the list + uint64_t deadline; // AT command deadline in ms +}; + + #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && MG_ENABLE_DRIVER_RA struct mg_tcpip_driver_ra_data { @@ -3146,14 +3159,22 @@ struct mg_tcpip_driver_tms570_data { -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && MG_ENABLE_DRIVER_W5500 - -#endif - - -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC -struct mg_tcpip_driver_xmc7_data { +struct mg_tcpip_driver_xmc_data { + // 13.2.8.1 Station Management Functions + // MDC clock divider (). MDC clock is derived from ETH MAC clock + // It must not exceed 2.5MHz + // ETH Clock range DIVIDER mdc_cr VALUE + // -------------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz ETH Clock/42 0 + // 100-150 MHz ETH Clock/62 1 + // 20-35 MHz ETH Clock/16 2 + // 35-60 MHz ETH Clock/26 3 + // 150-250 MHz ETH Clock/102 4 + // 250-300 MHz ETH Clock/124 5 + // 110, 111 Reserved int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 uint8_t phy_addr; }; @@ -3163,45 +3184,31 @@ struct mg_tcpip_driver_xmc7_data { #endif #ifndef MG_DRIVER_MDC_CR -#define MG_DRIVER_MDC_CR 3 +#define MG_DRIVER_MDC_CR 4 #endif #define MG_TCPIP_DRIVER_INIT(mgr) \ do { \ - static struct mg_tcpip_driver_xmc7_data driver_data_; \ + static struct mg_tcpip_driver_xmc_data driver_data_; \ static struct mg_tcpip_if mif_; \ driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ mif_.ip = MG_TCPIP_IP; \ mif_.mask = MG_TCPIP_MASK; \ mif_.gw = MG_TCPIP_GW; \ - mif_.driver = &mg_tcpip_driver_xmc7; \ + mif_.driver = &mg_tcpip_driver_xmc; \ mif_.driver_data = &driver_data_; \ MG_SET_MAC_ADDRESS(mif_.mac); \ mg_tcpip_init(mgr, &mif_); \ - MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \ + MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \ } while (0) #endif +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC - -struct mg_tcpip_driver_xmc_data { - // 13.2.8.1 Station Management Functions - // MDC clock divider (). MDC clock is derived from ETH MAC clock - // It must not exceed 2.5MHz - // ETH Clock range DIVIDER mdc_cr VALUE - // -------------------------------------------- - // -1 <-- tell driver to guess the value - // 60-100 MHz ETH Clock/42 0 - // 100-150 MHz ETH Clock/62 1 - // 20-35 MHz ETH Clock/16 2 - // 35-60 MHz ETH Clock/26 3 - // 150-250 MHz ETH Clock/102 4 - // 250-300 MHz ETH Clock/124 5 - // 110, 111 Reserved +struct mg_tcpip_driver_xmc7_data { int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 uint8_t phy_addr; }; @@ -3211,27 +3218,28 @@ struct mg_tcpip_driver_xmc_data { #endif #ifndef MG_DRIVER_MDC_CR -#define MG_DRIVER_MDC_CR 4 +#define MG_DRIVER_MDC_CR 3 #endif #define MG_TCPIP_DRIVER_INIT(mgr) \ do { \ - static struct mg_tcpip_driver_xmc_data driver_data_; \ + static struct mg_tcpip_driver_xmc7_data driver_data_; \ static struct mg_tcpip_if mif_; \ driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ mif_.ip = MG_TCPIP_IP; \ mif_.mask = MG_TCPIP_MASK; \ mif_.gw = MG_TCPIP_GW; \ - mif_.driver = &mg_tcpip_driver_xmc; \ + mif_.driver = &mg_tcpip_driver_xmc7; \ mif_.driver_data = &driver_data_; \ MG_SET_MAC_ADDRESS(mif_.mac); \ mg_tcpip_init(mgr, &mif_); \ - MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \ + MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \ } while (0) #endif + #ifdef __cplusplus } #endif diff --git a/src/drivers/ppp.c b/src/drivers/ppp.c new file mode 100644 index 0000000000..463a763936 --- /dev/null +++ b/src/drivers/ppp.c @@ -0,0 +1,321 @@ +#include "net_builtin.h" + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_PPP) && MG_ENABLE_DRIVER_PPP + +#define MG_PPP_FLAG 0x7e // PPP frame delimiter +#define MG_PPP_ESC 0x7d // PPP escape byte for special characters +#define MG_PPP_ADDR 0xff +#define MG_PPP_CTRL 0x03 + +#define MG_PPP_PROTO_IP 0x0021 +#define MG_PPP_PROTO_LCP 0xc021 +#define MG_PPP_PROTO_IPCP 0x8021 + +#define MG_PPP_IPCP_REQ 1 +#define MG_PPP_IPCP_ACK 2 +#define MG_PPP_IPCP_NACK 3 +#define MG_PPP_IPCP_IPADDR 3 + +#define MG_PPP_LCP_CFG_REQ 1 +#define MG_PPP_LCP_CFG_ACK 2 +#define MG_PPP_LCP_CFG_NACK 3 +#define MG_PPP_LCP_CFG_REJECT 4 +#define MG_PPP_LCP_CFG_TERM_REQ 5 +#define MG_PPP_LCP_CFG_TERM_ACK 6 + +#define MG_PPP_AT_TIMEOUT 2000 + +static size_t print_atcmd(void (*out)(char, void *), void *arg, va_list *ap) { + struct mg_str s = va_arg(*ap, struct mg_str); + for (size_t i = 0; i < s.len; i++) out(s.buf[i] < 0x20 ? '.' : s.buf[i], arg); + return s.len; +} + +static bool mg_ppp_atcmd_handle(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + if (dd->script == NULL || dd->script_index < 0) return true; + if (dd->deadline == 0) dd->deadline = mg_millis() + MG_PPP_AT_TIMEOUT; + for (;;) { + if (dd->script_index % 2 == 0) { // send AT command + const char *cmd = dd->script[dd->script_index]; + MG_DEBUG(("send AT[%d]: %M", dd->script_index, print_atcmd, mg_str(cmd))); + while (*cmd) dd->tx(dd->uart, *cmd++); + dd->script_index++; + ifp->recv_queue.head = 0; + } else { // check AT command response + const char *expect = dd->script[dd->script_index]; + struct mg_queue *q = &ifp->recv_queue; + for (;;) { + int c; + int is_timeout = dd->deadline > 0 && mg_millis() > dd->deadline; + int is_overflow = q->head >= q->size - 1; + if (is_timeout || is_overflow) { + MG_ERROR(("AT error: %s, retrying...", + is_timeout ? "timeout" : "overflow")); + dd->script_index = 0; + dd->deadline = mg_millis() + MG_PPP_AT_TIMEOUT; + if (dd->reset) dd->reset(dd->uart); + return false; // FAIL: timeout + } + if ((c = dd->rx(dd->uart)) < 0) return false; // no data + q->buf[q->head++] = c; + if (mg_match(mg_str_n(q->buf, q->head), mg_str(expect), NULL)) { + MG_DEBUG(("recv AT[%d]: %M", dd->script_index, print_atcmd, + mg_str_n(q->buf, q->head))); + dd->script_index++; + q->head = 0; + break; + } + } + } + if (dd->script[dd->script_index] == NULL) { + MG_DEBUG(("finished AT script")); + dd->script_index = -1; + return true; + } + } +} + +static bool mg_ppp_init(struct mg_tcpip_if *ifp) { + ifp->recv_queue.size = 3000; // MTU=1500, worst case escaping = 2x + return true; +} + +// Calculate FCS/CRC for PPP frames. Could be implemented faster using lookup +// tables. +static uint32_t fcs_do(uint32_t fcs, uint8_t x) { + for (int i = 0; i < 8; i++) { + fcs = ((fcs ^ x) & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1; + x >>= 1; + } + return fcs; +} + +static bool mg_ppp_up(struct mg_tcpip_if *ifp) { + return ifp->driver_data != NULL; +} + +// Transmit a single byte as part of the PPP frame (escaped, if needed) +static void mg_ppp_tx_byte(struct mg_tcpip_driver_ppp_data *dd, uint8_t b) { + if ((b < 0x20) || (b == MG_PPP_ESC) || (b == MG_PPP_FLAG)) { + dd->tx(dd->uart, MG_PPP_ESC); + dd->tx(dd->uart, b ^ 0x20); + } else { + dd->tx(dd->uart, b); + } +} + +// Transmit a single PPP frame for the given protocol +static void mg_ppp_tx_frame(struct mg_tcpip_driver_ppp_data *dd, uint16_t proto, + uint8_t *data, size_t datasz) { + uint16_t crc; + uint32_t fcs = 0xffff; + + dd->tx(dd->uart, MG_PPP_FLAG); + mg_ppp_tx_byte(dd, MG_PPP_ADDR); + mg_ppp_tx_byte(dd, MG_PPP_CTRL); + mg_ppp_tx_byte(dd, proto >> 8); + mg_ppp_tx_byte(dd, proto & 0xff); + fcs = fcs_do(fcs, MG_PPP_ADDR); + fcs = fcs_do(fcs, MG_PPP_CTRL); + fcs = fcs_do(fcs, proto >> 8); + fcs = fcs_do(fcs, proto & 0xff); + for (unsigned int i = 0; i < datasz; i++) { + mg_ppp_tx_byte(dd, data[i]); + fcs = fcs_do(fcs, data[i]); + } + crc = fcs & 0xffff; + mg_ppp_tx_byte(dd, ~crc); // send CRC, note the byte order + mg_ppp_tx_byte(dd, ~crc >> 8); + dd->tx(dd->uart, MG_PPP_FLAG); // end of frame +} + +// Send Ethernet frame as PPP frame +static size_t mg_ppp_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + if (ifp->state != MG_TCPIP_STATE_READY) return 0; + // XXX: what if not an IP protocol? + mg_ppp_tx_frame(dd, MG_PPP_PROTO_IP, (uint8_t *) buf + 14, len - 14); + return len; +} + +// Given a full PPP frame, unescape it in place and verify FCS, returns actual +// data size on success or 0 on error. +static size_t mg_ppp_verify_frame(uint8_t *buf, size_t bufsz) { + int unpack = 0; + uint16_t crc; + size_t pktsz = 0; + uint32_t fcs = 0xffff; + for (unsigned int i = 0; i < bufsz; i++) { + if (unpack == 0) { + if (buf[i] == 0x7d) { + unpack = 1; + } else { + buf[pktsz] = buf[i]; + fcs = fcs_do(fcs, buf[pktsz]); + pktsz++; + } + } else { + unpack = 0; + buf[pktsz] = buf[i] ^ 0x20; + fcs = fcs_do(fcs, buf[pktsz]); + pktsz++; + } + } + crc = fcs & 0xffff; + if (crc != 0xf0b8) { + MG_DEBUG(("bad crc: %04x", crc)); + return 0; + } + if (pktsz < 6 || buf[0] != MG_PPP_ADDR || buf[1] != MG_PPP_CTRL) { + return 0; + } + return pktsz - 2; // strip FCS +} + +// fetch as much data as we can, until a single PPP frame is received +static size_t mg_ppp_rx_frame(struct mg_tcpip_driver_ppp_data *dd, + struct mg_queue *q) { + while (q->head < q->size) { + int c; + if ((c = dd->rx(dd->uart)) < 0) { + return 0; + } + if (c == MG_PPP_FLAG) { + if (q->head > 0) { + break; + } else { + continue; + } + } + q->buf[q->head++] = c; + } + + size_t n = mg_ppp_verify_frame((uint8_t *) q->buf, q->head); + if (n == 0) { + MG_DEBUG(("invalid PPP frame of %d bytes", q->head)); + q->head = 0; + return 0; + } + q->head = n; + return q->head; +} + +static void mg_ppp_handle_lcp(struct mg_tcpip_if *ifp, uint8_t *lcp, + size_t lcpsz) { + uint8_t id; + uint16_t len; + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + if (lcpsz < 4) return; + id = lcp[1]; + len = (((uint16_t) lcp[2]) << 8) | (lcp[3]); + switch (lcp[0]) { + case MG_PPP_LCP_CFG_REQ: { + if (len == 4) { + MG_DEBUG(("LCP config request of %d bytes, acknowledging...", len)); + lcp[0] = MG_PPP_LCP_CFG_ACK; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_LCP, lcp, len); + lcp[0] = MG_PPP_LCP_CFG_REQ; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_LCP, lcp, len); + } else { + MG_DEBUG(("LCP config request of %d bytes, rejecting...", len)); + lcp[0] = MG_PPP_LCP_CFG_REJECT; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_LCP, lcp, len); + } + } break; + case MG_PPP_LCP_CFG_TERM_REQ: { + uint8_t ack[4] = {MG_PPP_LCP_CFG_TERM_ACK, id, 0, 4}; + MG_DEBUG(("LCP termination request, acknowledging...")); + mg_ppp_tx_frame(dd, MG_PPP_PROTO_LCP, ack, sizeof(ack)); + ifp->state = MG_TCPIP_STATE_DOWN; + if (dd->reset) dd->reset(dd->uart); + } break; + } +} + +static void mg_ppp_handle_ipcp(struct mg_tcpip_if *ifp, uint8_t *ipcp, + size_t ipcpsz) { + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + uint16_t len; + uint8_t id; + uint8_t req[] = { + MG_PPP_IPCP_REQ, 0, 0, 10, MG_PPP_IPCP_IPADDR, 6, 0, 0, 0, 0}; + if (ipcpsz < 4) return; + id = ipcp[1]; + len = (((uint16_t) ipcp[2]) << 8) | (ipcp[3]); + switch (ipcp[0]) { + case MG_PPP_IPCP_REQ: + MG_DEBUG(("got IPCP config request, acknowledging...")); + if (len >= 10 && ipcp[4] == MG_PPP_IPCP_IPADDR) { + uint8_t *ip = ipcp + 6; + MG_INFO(("host ip: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3])); + } + ipcp[0] = MG_PPP_IPCP_ACK; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_IPCP, ipcp, len); + req[1] = id; + // Request IP address 0.0.0.0 + mg_ppp_tx_frame(dd, MG_PPP_PROTO_IPCP, req, sizeof(req)); + break; + case MG_PPP_IPCP_ACK: + // This usually does not happen, as our "preferred" IP address is invalid + MG_DEBUG(("got IPCP config ack, link is online now")); + ifp->state = MG_TCPIP_STATE_READY; + break; + case MG_PPP_IPCP_NACK: + MG_DEBUG(("got IPCP config nack")); + // NACK contains our "suggested" IP address, use it + if (len >= 10 && ipcp[4] == MG_PPP_IPCP_IPADDR) { + uint8_t *ip = ipcp + 6; + MG_INFO(("ipcp ack, ip: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])); + ipcp[0] = MG_PPP_IPCP_REQ; + mg_ppp_tx_frame(dd, MG_PPP_PROTO_IPCP, ipcp, len); + ifp->ip = ifp->mask = MG_IPV4(ip[0], ip[1], ip[2], ip[3]); + } + break; + } +} + +static size_t mg_ppp_rx(void *ethbuf, size_t ethlen, struct mg_tcpip_if *ifp) { + uint8_t *eth = ethbuf; + size_t ethsz = 0; + struct mg_tcpip_driver_ppp_data *dd = + (struct mg_tcpip_driver_ppp_data *) ifp->driver_data; + uint8_t *buf = (uint8_t *) ifp->recv_queue.buf; + + if (!mg_ppp_atcmd_handle(ifp)) return 0; + + size_t bufsz = mg_ppp_rx_frame(dd, &ifp->recv_queue); + if (!bufsz) return 0; + uint16_t proto = (((uint16_t) buf[2]) << 8) | (uint16_t) buf[3]; + switch (proto) { + case MG_PPP_PROTO_LCP: mg_ppp_handle_lcp(ifp, buf + 4, bufsz - 4); break; + case MG_PPP_PROTO_IPCP: mg_ppp_handle_ipcp(ifp, buf + 4, bufsz - 4); break; + case MG_PPP_PROTO_IP: + MG_DEBUG(("got IP packet of %d bytes", bufsz - 4)); + memmove(eth + 14, buf + 4, bufsz - 4); + memmove(eth, ifp->mac, 6); + memmove(eth + 6, "\xff\xff\xff\xff\xff\xff", 6); + eth[12] = 0x08; + eth[13] = 0x00; + ethsz = bufsz - 4 + 14; + ifp->recv_queue.head = 0; + return ethsz; +#if 0 + default: + MG_DEBUG(("unknown PPP frame:")); + mg_hexdump(ppp->buf, ppp->bufsz); +#endif + } + ifp->recv_queue.head = 0; + return 0; + (void) ethlen; +} + +struct mg_tcpip_driver mg_tcpip_driver_ppp = {mg_ppp_init, mg_ppp_tx, mg_ppp_rx, + mg_ppp_up}; + +#endif diff --git a/src/drivers/ppp.h b/src/drivers/ppp.h new file mode 100644 index 0000000000..5ae2b31e50 --- /dev/null +++ b/src/drivers/ppp.h @@ -0,0 +1,11 @@ +#pragma once + +struct mg_tcpip_driver_ppp_data { + void *uart; // Opaque UART bus descriptor + void (*reset)(void *); // Modem hardware reset + void (*tx)(void *, uint8_t); // UART transmit single byte + int (*rx)(void *); // UART receive single byte + const char **script; // List of AT commands and expected replies + int script_index; // Index of the current AT command in the list + uint64_t deadline; // AT command deadline in ms +}; diff --git a/src/net_builtin.c b/src/net_builtin.c index e37ab9a25d..3bdd3401eb 100644 --- a/src/net_builtin.c +++ b/src/net_builtin.c @@ -910,7 +910,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { #endif // Handle gw ARP request timeout, order is important if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { - ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC + ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC onstatechange(ifp); } // Handle physical interface up/down status diff --git a/src/net_builtin.h b/src/net_builtin.h index 9da0f59ad9..2f1c899c76 100644 --- a/src/net_builtin.h +++ b/src/net_builtin.h @@ -81,6 +81,7 @@ extern struct mg_tcpip_driver mg_tcpip_driver_cmsis; extern struct mg_tcpip_driver mg_tcpip_driver_ra; extern struct mg_tcpip_driver mg_tcpip_driver_xmc; extern struct mg_tcpip_driver mg_tcpip_driver_xmc7; +extern struct mg_tcpip_driver mg_tcpip_driver_ppp; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { @@ -89,4 +90,5 @@ struct mg_tcpip_spi { void (*end)(void *); // SPI end: slave select high uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1 byte, read reply }; + #endif