From 70650fedcfc508ac70c033ab3877cee26802d03f Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Fri, 13 Mar 2026 08:53:36 -0300 Subject: [PATCH] Add L2 PPP and PPPoE --- mongoose.c | 1165 ++++++++++------- mongoose.h | 38 +- src/drivers/at_cmd.c | 153 +++ src/drivers/at_cmd.h | 14 + src/drivers/ppp.c | 328 ----- src/drivers/ppp.h | 11 - src/l2.c | 60 +- src/l2.h | 2 +- src/l2_eth.c | 23 +- src/l2_ppp.c | 482 ++++++- src/net_builtin.c | 111 +- src/net_builtin.h | 11 +- .../arduino/sim800-mqtt/sim800-mqtt.ino | 6 +- .../Makefile | 37 + .../README.md | 87 ++ .../hal.h | 175 +++ .../link.ld | 29 + .../main.c | 63 + .../mongoose.c | 1 + .../mongoose.h | 1 + .../mongoose_config.h | 13 + .../syscalls.c | 88 ++ .../sysinit.c | 30 + 23 files changed, 2019 insertions(+), 909 deletions(-) create mode 100644 src/drivers/at_cmd.c create mode 100644 src/drivers/at_cmd.h delete mode 100644 src/drivers/ppp.c delete mode 100644 src/drivers/ppp.h create mode 100644 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/Makefile create mode 100644 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/README.md create mode 100644 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/hal.h create mode 100644 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/link.ld create mode 100644 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/main.c create mode 120000 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose.c create mode 120000 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose.h create mode 100644 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose_config.h create mode 100644 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/syscalls.c create mode 100644 tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/sysinit.c diff --git a/mongoose.c b/mongoose.c index d784d1fa..34733661 100644 --- a/mongoose.c +++ b/mongoose.c @@ -3334,11 +3334,11 @@ long mg_json_get_long(struct mg_str json, const char *path, long dflt) { #if MG_ENABLE_TCPIP // L2 API -void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, - uint16_t *framesize); +void mg_l2_init(struct mg_tcpip_if *ifp); +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms); uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, uint8_t *dst, uint8_t *frame); -size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *frame); +size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *cur); bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, struct mg_str *pay, struct mg_str *raw); // TODO(): ? bool mg_l2_rx(enum mg_l2type type, struct mg_l2opts *opts, uint8_t @@ -3346,24 +3346,31 @@ bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, uint8_t *mg_l2_getaddr(enum mg_l2type type, uint8_t *frame); uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype, struct mg_addr *ip); +#if MG_ENABLE_IPV6 bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len, uint8_t *addr); bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, uint8_t len); uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts); +#endif +size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len); // clang-format off -extern void mg_l2_eth_init(struct mg_l2addr *, uint16_t *, uint16_t *); +extern void mg_l2_eth_init(struct mg_tcpip_if *); +extern bool mg_l2_eth_poll(struct mg_tcpip_if *, bool); extern uint8_t *mg_l2_eth_header(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); extern size_t mg_l2_eth_footer(size_t, uint8_t *); extern bool mg_l2_eth_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); extern struct mg_l2addr *mg_l2_eth_getaddr(uint8_t *); extern struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype, struct mg_addr *); +#if MG_ENABLE_IPV6 extern bool mg_l2_eth_genip6(uint64_t *, uint8_t, struct mg_l2addr *); extern bool mg_l2_eth_ip6get(struct mg_l2addr *, uint8_t *, uint8_t); extern uint8_t mg_l2_eth_ip6put(struct mg_l2addr *, uint8_t *); +#endif -extern void mg_l2_ppp_init(struct mg_l2addr *, uint16_t *, uint16_t *); +extern void mg_l2_ppp_init(struct mg_tcpip_if *); +extern bool mg_l2_ppp_poll(struct mg_tcpip_if *, bool); extern uint8_t *mg_l2_ppp_header(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); extern size_t mg_l2_ppp_footer(size_t, uint8_t *); extern bool mg_l2_ppp_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); @@ -3374,8 +3381,14 @@ extern bool mg_l2_ppp_genip6(uint64_t *, uint8_t, struct mg_l2addr *); extern bool mg_l2_ppp_ip6get(struct mg_l2addr *, uint8_t *, uint8_t); extern uint8_t mg_l2_ppp_ip6put(struct mg_l2addr *, uint8_t *); #endif +extern void mg_l2_pppoe_init(struct mg_tcpip_if *); +extern bool mg_l2_pppoe_poll(struct mg_tcpip_if *, bool); +extern uint8_t *mg_l2_pppoe_header(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); +extern size_t mg_l2_pppoe_footer(size_t, uint8_t *); +extern bool mg_l2_pppoe_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); -typedef void (*l2_init_fn)(struct mg_l2addr *, uint16_t *, uint16_t *); +typedef void (*l2_init_fn)(struct mg_tcpip_if *); +typedef bool (*l2_poll_fn)(struct mg_tcpip_if *, bool); typedef uint8_t *((*l2_header_fn)(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *)); typedef size_t (*l2_footer_fn)(size_t, uint8_t *); typedef bool (*l2_rx_fn)(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); @@ -3388,22 +3401,25 @@ typedef uint8_t (*l2_ip6put_fn)(struct mg_l2addr *, uint8_t *); #endif // clang-format on -static const l2_init_fn l2_init[] = {mg_l2_eth_init, mg_l2_ppp_init}; -static const l2_header_fn l2_header[] = {mg_l2_eth_header, mg_l2_ppp_header}; -static const l2_footer_fn l2_footer[] = {mg_l2_eth_footer, mg_l2_ppp_footer}; -static const l2_rx_fn l2_rx[] = {mg_l2_eth_rx, mg_l2_ppp_rx}; -static const l2_getaddr_fn l2_getaddr[] = {mg_l2_eth_getaddr, - mg_l2_ppp_getaddr}; -static const l2_mapip_fn l2_mapip[] = {mg_l2_eth_mapip, mg_l2_ppp_mapip}; +static const l2_init_fn l2_init[] = {mg_l2_eth_init, mg_l2_ppp_init, mg_l2_pppoe_init}; +static const l2_poll_fn l2_poll[] = {mg_l2_eth_poll, mg_l2_ppp_poll, mg_l2_pppoe_poll}; +static const l2_header_fn l2_header[] = {mg_l2_eth_header, mg_l2_ppp_header, mg_l2_pppoe_header}; +static const l2_footer_fn l2_footer[] = {mg_l2_eth_footer, mg_l2_ppp_footer, mg_l2_pppoe_footer}; +static const l2_rx_fn l2_rx[] = {mg_l2_eth_rx, mg_l2_ppp_rx, mg_l2_pppoe_rx}; +static const l2_getaddr_fn l2_getaddr[] = {mg_l2_eth_getaddr, mg_l2_ppp_getaddr, mg_l2_ppp_getaddr}; +static const l2_mapip_fn l2_mapip[] = {mg_l2_eth_mapip, mg_l2_ppp_mapip, mg_l2_ppp_mapip}; #if MG_ENABLE_IPV6 -static const l2_genip6_fn l2_genip6[] = {mg_l2_eth_genip6, mg_l2_ppp_genip6}; -static const l2_ip6get_fn l2_ip6get[] = {mg_l2_eth_ip6get, mg_l2_ppp_ip6get}; -static const l2_ip6put_fn l2_ip6put[] = {mg_l2_eth_ip6put, mg_l2_ppp_ip6put}; +static const l2_genip6_fn l2_genip6[] = {mg_l2_eth_genip6, mg_l2_ppp_genip6, mg_l2_ppp_genip6}; +static const l2_ip6get_fn l2_ip6get[] = {mg_l2_eth_ip6get, mg_l2_ppp_ip6get, mg_l2_ppp_ip6get}; +static const l2_ip6put_fn l2_ip6put[] = {mg_l2_eth_ip6put, mg_l2_ppp_ip6put, mg_l2_ppp_ip6put}; #endif -void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, - uint16_t *framesize) { - l2_init[type]((struct mg_l2addr *) addr, mtu, framesize); +void mg_l2_init(struct mg_tcpip_if *ifp) { + l2_init[ifp->l2type](ifp); +} + +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + return l2_poll[ifp->l2type](ifp, expired_1000ms); } uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, @@ -3447,6 +3463,12 @@ uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts) { } #endif +size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len) { + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp); + if (n == len) ifp->nsent++; + return n; +} + #endif #ifdef MG_ENABLE_LINES @@ -3488,8 +3510,8 @@ static const uint16_t eth_types[] = { 0x8864 // PPPoE Session Stage }; -void mg_l2_eth_init(struct mg_l2addr *l2addr, uint16_t *mtu, - uint16_t *framesize) { +void mg_l2_eth_init(struct mg_tcpip_if *ifp) { + struct mg_l2addr *l2addr = (struct mg_l2addr *)ifp->mac; // If MAC is not set, make a random one if (l2addr->addr.mac[0] == 0 && l2addr->addr.mac[1] == 0 && l2addr->addr.mac[2] == 0 && l2addr->addr.mac[3] == 0 && @@ -3499,8 +3521,14 @@ void mg_l2_eth_init(struct mg_l2addr *l2addr, uint16_t *mtu, MG_INFO( ("MAC not set. Generated random: %M", mg_print_mac, l2addr->addr.mac)); } - *mtu = 1500; - *framesize = 1540; + ifp->mtu = 1500; + ifp->framesize = 1540; +} + +bool mg_l2_eth_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + (void) ifp; + (void) expired_1000ms; + return true; } uint8_t *mg_l2_eth_header(enum mg_l2proto proto, struct mg_l2addr *src, @@ -3512,10 +3540,11 @@ uint8_t *mg_l2_eth_header(enum mg_l2proto proto, struct mg_l2addr *src, return (uint8_t *) (eth + 1); } -size_t mg_l2_eth_footer(size_t len, uint8_t *frame) { - struct eth *eth = (struct eth *) frame; - // nothing to do; there is no len field in Ethernet, CRC is hw-calculated - return len + sizeof(*eth); +size_t mg_l2_eth_footer(size_t len, uint8_t *cur) { + // there is no len field in Ethernet, CRC is hw-calculated; pad to 64 - CRC + size_t ethlen = len + sizeof(struct eth); + (void) cur; + return ethlen >= 60 ? ethlen : 60; } struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype addrtype, @@ -3641,22 +3670,35 @@ uint8_t mg_l2_eth_ip6put(struct mg_l2addr *l2addr, uint8_t *opts) { #else #pragma pack(push, 1) #endif +// all in network order -struct ppp { // RFC-1662 - uint8_t addr, ctrl; +struct ppp { // RFC-1661 uint16_t proto; }; struct lcp { // RFC-1661 - uint8_t code, id, len[2]; + uint8_t code, id; + uint16_t len; }; -struct ipcp { // RFC-1332 - uint8_t code; +struct ipcp { // RFC-1332 + uint8_t code, id; + uint16_t len; }; -struct ipv6cp { // RFC-5072 - uint8_t code; +struct ipv6cp { // RFC-5072 + uint8_t code, id; + uint16_t len; +}; + +struct hdlc_ { // RFC-1662, "PPP in HDLC-like Framing" + uint8_t addr, ctrl; +}; + +struct pppoe { // RFC-2516, "A Method for Transmitting PPP Over Ethernet + // (PPPoE)" + uint8_t vertype, code; + uint16_t id, len; }; #if defined(__DCC__) @@ -3665,52 +3707,361 @@ struct ipv6cp { // RFC-5072 #pragma pack(pop) #endif +#define MG_PPP_ADDR 0xff +#define MG_PPP_CTRL 0x03 -void mg_l2_ppp_init(struct mg_l2addr *addr, uint16_t *mtu, - uint16_t *framesize) { - (void) addr; - *mtu = 1500; // 1492 for PPPoE - *framesize = 1540; // *** TODO(scaprile): actual value, check for PPPoE too +#define MG_PPP_PROTO_IP 0x0021 +#define MG_PPP_PROTO_IPV6 0x0057 +#define MG_PPP_PROTO_LCP 0xc021 +#define MG_PPP_PROTO_IPCP 0x8021 +#define MG_PPP_PROTO_IPV6CP 0x8057 +#define MG_PPP_PROTO_PAP 0xc023 +#define MG_PPP_PROTO_CHAP 0xc223 + +#define MG_PPP_IPCP_REQ 1 +#define MG_PPP_IPCP_ACK 2 +#define MG_PPP_IPCP_NACK 3 +#define MG_PPP_IPCP_REJECT 4 +#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_LCP_REJECT 8 +#define MG_PPP_LCP_ECHO_REQ 9 +#define MG_PPP_LCP_ECHO_REPLY 10 + +#define MG_PPPoE_PADI 0x09 +#define MG_PPPoE_PADO 0x07 +#define MG_PPPoE_PADR 0x19 +#define MG_PPPoE_PADS 0x65 +#define MG_PPPoE_PADT 0xa7 + +#define MG_PPPoE_ST_DISC 0 // Discovery phase, see what servers are out there +#define MG_PPPoE_ST_REQ 1 // Chose a server, request a session and wait +#define MG_PPPoE_ST_SESS 2 // Session established, PPP traffic is exchanged + +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) + +static bool s_link = false; // ******************************************* +static uint8_t s_state = MG_PPPoE_ST_DISC; +static uint16_t s_id; + +void mg_l2_ppp_init(struct mg_tcpip_if *ifp) { + ifp->mtu = 1500; + ifp->framesize = 1500 + sizeof(struct ppp) + sizeof(struct hdlc_); +} + +extern void mg_l2_eth_init(struct mg_tcpip_if *); + +void mg_l2_pppoe_init(struct mg_tcpip_if *ifp) { + mg_l2_eth_init(ifp); + ifp->mtu = ifp->mtu - (uint16_t) (sizeof(struct pppoe) + + sizeof(struct ppp)); // 1500 --> 1492 +} + +bool mg_l2_ppp_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_DOWN) s_link = false; + return s_link; +} + +static uint8_t *hdlc_header(uint8_t *p) { + struct hdlc_ *hdlc = (struct hdlc_ *) p; + hdlc->addr = MG_PPP_ADDR; + hdlc->ctrl = MG_PPP_CTRL; + return (uint8_t *) (hdlc + 1); +} + +static uint8_t *ppp_header(uint16_t proto, uint8_t *p) { + struct ppp *ppp = (struct ppp *) p; + ppp->proto = mg_htons(proto); + return (uint8_t *) (ppp + 1); +} + +static uint8_t *l2_ppp_header(enum mg_l2proto proto, uint8_t *p) { + uint16_t ppp_proto = proto == MG_TCPIP_L2PROTO_IPV4 ? MG_PPP_PROTO_IP + : proto == MG_TCPIP_L2PROTO_IPV4 ? MG_PPP_PROTO_IPV6 + : 0; + return ppp_header(ppp_proto, p); } uint8_t *mg_l2_ppp_header(enum mg_l2proto proto, struct mg_l2addr *src, struct mg_l2addr *dst, uint8_t *frame) { (void) src; (void) dst; - (void) proto; - return frame; + return l2_ppp_header(proto, hdlc_header(frame)); } -size_t mg_l2_ppp_footer(size_t len, uint8_t *frame) { - (void) frame; - return len; +extern uint8_t *mg_l2_eth_header(enum mg_l2proto proto, struct mg_l2addr *src, + struct mg_l2addr *dst, uint8_t *frame); + +static uint8_t *pppoe_header(enum mg_l2proto proto, uint8_t code, uint16_t id, + struct mg_l2addr *src, struct mg_l2addr *dst, + uint8_t *frame) { + struct pppoe *pppoe = + (struct pppoe *) mg_l2_eth_header(proto, src, dst, frame); + pppoe->vertype = 0x11; + pppoe->code = code; + pppoe->id = id; + return (uint8_t *) (pppoe + 1); +} + +uint8_t *mg_l2_pppoe_header(enum mg_l2proto proto, struct mg_l2addr *src, + struct mg_l2addr *dst, uint8_t *frame) { + (void) dst; + return l2_ppp_header(proto, pppoe_header(MG_TCPIP_L2PROTO_PPPoE_SESS, 0, s_id, + src, dst, frame)); +} + +// Calculate FCS/CRC for PPP frames. Could be implemented faster using lookup +// tables. +static uint16_t fcs_do(uint8_t *frame, size_t len) { + uint32_t fcs = 0xffff; + unsigned int j; + for (j = 0; j < len; j++) { + unsigned int i; + uint8_t x = frame[j]; + for (i = 0; i < 8; i++) { + fcs = ((fcs ^ x) & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1; + x >>= 1; + } + } + return (uint16_t) (fcs & 0xffff); +} + +size_t mg_l2_ppp_footer(size_t len, uint8_t *cur) { + uint16_t crc; + uint8_t *frame; + len += sizeof(struct ppp) + sizeof(struct hdlc_); + frame = cur - len; + crc = fcs_do(frame, len); + *cur++ = (uint8_t) ~crc; // add CRC, note the byte order + *cur++ = (uint8_t) (~crc >> 8); + // there is no len field in PPP + return len + 2; +} + +extern size_t mg_l2_eth_footer(size_t len, uint8_t *cur); + +static size_t pppoe_footer(size_t len, uint8_t *cur) { + struct pppoe *pppoe = (struct pppoe *) (cur - len - sizeof(struct pppoe)); + pppoe->len = mg_htons((uint16_t) len); + return mg_l2_eth_footer(PDIFF(pppoe, cur), cur); +} + +size_t mg_l2_pppoe_footer(size_t len, uint8_t *cur) { + return pppoe_footer(len + sizeof(struct ppp), cur); +} + +size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len); + +static uint8_t *ppp_tx_frame_header(struct mg_tcpip_if *ifp, uint16_t proto) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + if (ifp->l2type == MG_TCPIP_L2_PPP) + return ppp_header(proto, hdlc_header(l2p)); + return ppp_header(proto, pppoe_header(MG_TCPIP_L2PROTO_PPPoE_SESS, 0, s_id, + (struct mg_l2addr *) ifp->mac, + (struct mg_l2addr *) ifp->gwmac, l2p)); +} +static size_t ppp_tx_frame_footer(struct mg_tcpip_if *ifp, size_t len, + uint8_t *cur) { + return mg_l2_driver_output(ifp, ifp->l2type == MG_TCPIP_L2_PPPoE + ? mg_l2_pppoe_footer(len, cur) + : mg_l2_ppp_footer(len, cur)); +} + +// Transmit a single PPP frame for the given protocol +static size_t ppp_tx_frame(struct mg_tcpip_if *ifp, uint16_t proto, + uint8_t *data, size_t datasz) { + uint8_t *pay = ppp_tx_frame_header(ifp, proto); + memcpy(pay, data, datasz); + return ppp_tx_frame_footer(ifp, datasz, pay + datasz); +} + +static void ppp_handle_lcp(struct mg_tcpip_if *ifp, uint8_t *lcpp, + size_t lcpsz) { + uint8_t id; + uint16_t len; + struct lcp *lcp = (struct lcp *) lcpp; + if (lcpsz < sizeof(*lcp)) return; + id = lcp->id; + len = mg_ntohs(lcp->len); + switch (lcp->code) { + case MG_PPP_LCP_CFG_REQ: { + if (len == sizeof(*lcp)) { + MG_DEBUG(("LCP config request of %d bytes, acknowledging...", len)); + lcp->code = MG_PPP_LCP_CFG_ACK; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + lcp->code = MG_PPP_LCP_CFG_REQ; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + } else { + MG_DEBUG(("LCP config request of %d bytes, rejecting...", len)); + lcp->code = MG_PPP_LCP_CFG_REJECT; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + } + } break; + case MG_PPP_LCP_CFG_ACK: + s_link = true; + 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...")); + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, ack, sizeof(ack)); + s_link = false; + } break; + case MG_PPP_LCP_ECHO_REQ: // RFC-1661 5.8: must respond + MG_DEBUG(("LCP echo request of %d bytes, replying...", len)); + lcp->code = MG_PPP_LCP_ECHO_REPLY; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + break; + } +} + +static bool find_opt(const uint8_t opt, const uint8_t optlen, + const uint8_t *opts, size_t optslen, uint8_t *dest) { + uint8_t *p = (uint8_t *) opts; + while (optslen >= 2) { // parse options for requested one + if (p[1] > optslen) return false; // truncated / malformed + if (p[0] == opt && p[1] == optlen) { + memcpy(dest, p + 2, optlen - 2); + return true; + } + optslen -= p[1]; + p += p[1]; + } + return false; +} + +static void ppp_handle_ipcp(struct mg_tcpip_if *ifp, uint8_t *ipcpp, + size_t ipcpsz) { + uint16_t len; + uint8_t id; + struct ipcp *ipcp = (struct ipcp *) ipcpp; + uint8_t req[] = { + MG_PPP_IPCP_REQ, 0, 0, 10, MG_PPP_IPCP_IPADDR, 6, 0, 0, 0, 0}; + if (ipcpsz < sizeof(*ipcp)) return; + id = ipcp->id; + len = mg_ntohs(ipcp->len); + switch (ipcp->code) { + case MG_PPP_IPCP_REQ: + MG_VERBOSE(("got IPCP config request, acknowledging...")); + if (len >= 10 && + find_opt(MG_PPP_IPCP_IPADDR, 6, (const uint8_t *) (ipcp + 1), + len - sizeof(*ipcp), (uint8_t *) &ifp->gw)) + MG_DEBUG(("IPCP ack, GW IP: %M", mg_print_ip4, &ifp->gw)); + ipcp->code = MG_PPP_IPCP_ACK; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, ipcpp, len); + req[1] = id; + memcpy(req + 6, &ifp->ip, 4); // Request config IP address or 0.0.0.0 + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, req, sizeof(req)); + break; + case MG_PPP_IPCP_ACK: + // Our peer accepted our static IP address (unlikely if 0.0.0.0) + MG_VERBOSE(("got IPCP config ack")); + ifp->mask = 0xffffffff; // send to gw + ifp->state = MG_TCPIP_STATE_IP; + ifp->gw_ready = true; + break; + case MG_PPP_IPCP_NACK: + MG_VERBOSE(("got IPCP config nack")); + // NACK contains our "suggested" IP address, use it + if (len >= 10 && + find_opt(MG_PPP_IPCP_IPADDR, 6, (const uint8_t *) (ipcp + 1), + len - sizeof(*ipcp), (uint8_t *) &ifp->ip)) { + ifp->mask = 0xffffffff; // send to gw + ifp->state = MG_TCPIP_STATE_IP; + ifp->gw_ready = true; + MG_DEBUG(("IPCP ack, IP: %M", mg_print_ip4, &ifp->ip)); + ipcp->code = MG_PPP_IPCP_REQ; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, ipcpp, len); + } + break; + case MG_PPP_IPCP_REJECT: + MG_ERROR(("Peer rejected our IP address, need to properly set ifp->ip")); + break; + } +} + +#if MG_ENABLE_IPV6 +#endif + +static bool ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + struct ppp *ppp = (struct ppp *) pay->buf; + if (pay->len < sizeof(*ppp)) return false; // Truncated + pay->buf = (char *) (ppp + 1); + pay->len = pay->len - sizeof(*ppp); + switch (mg_ntohs(ppp->proto)) { + case MG_PPP_PROTO_LCP: + ppp_handle_lcp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IPCP: + if (s_link) ppp_handle_ipcp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IP: + if (!s_link) return false; + MG_VERBOSE(("got IP packet of %d bytes", pay->len)); + *proto = MG_TCPIP_L2PROTO_IPV4; + break; +#if 0 && MG_ENABLE_IPV6 + case MG_PPP_PROTO_IPV6CP: + if (s_link) ppp_handle_ipv6cp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IPV6: + if (!s_link) return false; + MG_VERBOSE(("got IPv6 packet of %d bytes", pay->len)); + *proto = MG_TCPIP_L2PROTO_IPV6; + break; +#endif + default: { + struct lcp rej; + uint8_t *p; + size_t msglen; + MG_DEBUG(("unknown %u-byte PPP frame with proto 0x%04x:", + pay->len + sizeof(*ppp), mg_ntohs(ppp->proto))); + if (mg_log_level >= MG_LL_DEBUG) mg_hexdump(ppp, sizeof(*ppp) + 20); + if (!s_link) return false; // RFC-1661 5.7: must reject on link up + if (pay->len > (size_t) (ifp->mtu - 20)) + pay->len = (size_t) (ifp->mtu - 20); // truncate to some safe limit + rej.code = MG_PPP_LCP_REJECT; + mg_random(&rej.id, sizeof(rej.id)); + msglen = pay->len + sizeof(*ppp); + rej.len = mg_htons((uint16_t) (msglen + sizeof(rej))); + p = ppp_tx_frame_header(ifp, MG_PPP_PROTO_LCP); + memmove(p, &rej, sizeof(rej)); // LCP reject + memmove(p + sizeof(rej), ppp, msglen); // rejected PPP message + ppp_tx_frame_footer(ifp, msglen + sizeof(rej), p + msglen + sizeof(rej)); + } + return false; + } + (void) raw; + return true; } bool mg_l2_ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, struct mg_str *pay, struct mg_str *raw) { -#if 0 -if (ppp->addr == MG_PPP_ADDR && ppp->ctrl == MG_PPP_CTRL) { - code = ntohs(ppp->proto); - payload = (uint8_t *) (ppp + 1); -} else { // Address-and-Control-Field-Compressed PPP header - uint16_t *cppp = (uint16_t *) ppp; - code = ntohs(*cppp); - payload = (uint8_t *) (cppp + 1); -} -#endif - *pay = *raw; - *proto = MG_TCPIP_L2PROTO_IPV4; - (void) ifp; - return true; + struct hdlc_ *hdlc = (struct hdlc_ *) raw->buf; + if (raw->len < sizeof(*hdlc) + 2) return false; // Truncated + if (hdlc->addr == MG_PPP_ADDR && hdlc->ctrl == MG_PPP_CTRL) { + pay->buf = (char *) (hdlc + 1); + pay->len = raw->len - sizeof(*hdlc) - 2; + } else { // Address-and-Control-Field-Compressed PPP header + pay->buf = (char *) raw->buf; + pay->len = raw->len - 2; + } + return ppp_rx(ifp, proto, pay, raw); } +extern struct mg_l2addr s_mapip; + struct mg_l2addr *mg_l2_ppp_getaddr(uint8_t *frame) { (void) frame; return &s_mapip; // bogus } -extern struct mg_l2addr s_mapip; - struct mg_l2addr *mg_l2_ppp_mapip(enum mg_l2addrtype addrtype, struct mg_addr *addr) { (void) addrtype; @@ -3741,6 +4092,100 @@ uint8_t mg_l2_ppp_ip6put(struct mg_l2addr *addr, uint8_t *opts) { } #endif +// Transmit a single PPPoE frame for the discovery phase +static size_t pppoe_tx_frame(struct mg_tcpip_if *ifp, uint8_t code, uint16_t id, + uint8_t *data, size_t datasz, + struct mg_l2addr *dst) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + uint8_t *p = pppoe_header(MG_TCPIP_L2PROTO_PPPoE_DISC, code, id, + (struct mg_l2addr *) ifp->mac, dst, l2p); + memmove(p, data, datasz); + return mg_l2_driver_output(ifp, pppoe_footer(datasz, p + datasz)); +} + +extern struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype, struct mg_addr *); + +bool mg_l2_pppoe_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + if (expired_1000ms && s_state == MG_PPPoE_ST_DISC && + ifp->state == MG_TCPIP_STATE_LINK_UP) { + uint16_t tags[2]; + tags[0] = mg_htons(0x0101); // Service Request + tags[1] = mg_htons(0x0000); // Any + pppoe_tx_frame(ifp, MG_PPPoE_PADI, 0, (uint8_t *) tags, sizeof(tags), + mg_l2_eth_mapip(MG_TCPIP_L2ADDR_BCAST, NULL)); + MG_DEBUG(("Sent PADI")); + } else if (expired_1000ms && (s_state != MG_PPPoE_ST_SESS || + ifp->state == MG_TCPIP_STATE_DOWN)) { + s_state = MG_PPPoE_ST_DISC; + } + return mg_l2_ppp_poll(ifp, expired_1000ms); +} + +extern bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw); +extern struct mg_l2addr *mg_l2_eth_getaddr(uint8_t *); + +bool mg_l2_pppoe_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + enum mg_l2proto eth_proto; // raw is handled by eth + struct pppoe *pppoe; + if (!mg_l2_eth_rx(ifp, ð_proto, pay, raw)) return false; + pppoe = (struct pppoe *) pay->buf; // here we handle pay, not raw + if (pay->len < sizeof(*pppoe)) return false; // Truncated + if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_DISC) { + MG_VERBOSE(("PPPoE_DISC")); + if (s_state == MG_PPPoE_ST_DISC && pppoe->code == MG_PPPoE_PADO && + pppoe->id == 0) { + uint16_t tags[2]; + bool has_cookie = false; + size_t len = pay->len - sizeof(*pppoe); + uint8_t *p = (uint8_t *) (pppoe + 1); + uint16_t taglen; + while (len >= 4) { // parse tags for a possible AC-Cookie + uint16_t curtag = *((uint16_t *) p); + taglen = mg_ntohs(*(((uint16_t *) p) + 1)); + if (taglen > len - 4) return false; // truncated / malformed + if (curtag == mg_htons(0x0104)) { + has_cookie = true; + break; + } + len -= 4 + taglen; + p += 4 + taglen; + } + tags[0] = mg_htons(0x0101); // Service Request + tags[1] = mg_htons(0x0000); // Any + if (has_cookie) { // copy tags to before AC-Cookie tag in rx buffer + memcpy(p -= sizeof(tags), tags, sizeof(tags)); // point to all tags + taglen += 4 + sizeof(tags); // account for tags + } else { + p = (uint8_t *) tags; + taglen = sizeof(tags); + } + pppoe_tx_frame(ifp, MG_PPPoE_PADR, 0, p, taglen, + mg_l2_eth_getaddr((uint8_t *) raw->buf)); + MG_DEBUG(("Sent PADR")); + s_state = MG_PPPoE_ST_REQ; + } else if (s_state == MG_PPPoE_ST_REQ && pppoe->code == MG_PPPoE_PADS) { + s_id = pppoe->id; + memcpy(&ifp->gwmac, mg_l2_eth_getaddr((uint8_t *) raw->buf)->addr.mac, 6); + MG_DEBUG(("PPPoE session 0x%04x started", mg_ntohs(s_id))); + s_state = MG_PPPoE_ST_SESS; + } else if (s_state == MG_PPPoE_ST_SESS && pppoe->code == MG_PPPoE_PADT && + pppoe->id == s_id) { + MG_ERROR(("Got PADT")); + s_id = 0; + s_link = false; + s_state = MG_PPPoE_ST_DISC; + } + } else if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_SESS && + s_state == MG_PPPoE_ST_SESS) { + pay->buf = (char *) (pppoe + 1); + pay->len = pay->len - sizeof(*pppoe); + return ppp_rx(ifp, proto, pay, raw); + } + return false; +} + #endif #ifdef MG_ENABLE_LINES @@ -5157,11 +5602,10 @@ struct pkt { }; // L2 API -void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, - uint16_t *framesize); +void mg_l2_init(struct mg_tcpip_if *ifp); uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, uint8_t *dst, uint8_t *frame); -size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *frame); +size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *cur); bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, struct mg_str *pay, struct mg_str *raw); uint8_t *mg_l2_getaddr(enum mg_l2type type, uint8_t *frame); @@ -5174,6 +5618,7 @@ bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, uint8_t len); uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts); #endif +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms); static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) { #if MG_ENABLE_PROFILE @@ -5229,9 +5674,9 @@ static uint16_t pcsum(void *d, void *p, size_t plen) { uint32_t sum; struct ip *ip = (struct ip *) d; #if defined(__DCC__) - volatile /* Makes PPC & Diab4.3 happy */ + volatile /* Makes PPC & Diab4.3 happy */ #endif - struct pseudoip pip; + struct pseudoip pip; pip.src = ip->src; pip.dst = ip->dst; pip.zero = 0; @@ -5260,9 +5705,9 @@ static uint16_t p6csum(void *d, void *p, size_t plen) { uint32_t sum; struct ip6 *ip6 = (struct ip6 *) d; #if defined(__DCC__) - volatile /* Makes PPC & Diab4.3 happy */ + volatile /* Makes PPC & Diab4.3 happy */ #endif - struct pseudoip6 pip6; + struct pseudoip6 pip6; pip6.src[0] = ip6->src[0], pip6.src[1] = ip6->src[1]; pip6.dst[0] = ip6->dst[0], pip6.dst[1] = ip6->dst[1]; pip6.zero[0] = 0, pip6.zero[1] = 0, pip6.zero[2] = 0; @@ -5342,17 +5787,22 @@ void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) { arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip; memcpy(arp->sha, ifp->mac, sizeof(arp->sha)); if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha)); - driver_output(ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, arp + 1), l2p)); + driver_output(ifp, + mg_l2_footer(ifp->l2type, sizeof(*arp), (uint8_t *) (arp + 1))); } static void onstatechange(struct mg_tcpip_if *ifp) { + if (ifp->state == MG_TCPIP_STATE_IP && + (ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE)) + ifp->state = MG_TCPIP_STATE_READY; if (ifp->state == MG_TCPIP_STATE_READY) { MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip)); MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw)); - if (ifp->l2type == MG_TCPIP_L2_ETH) // TODO(): print other l2 + if (ifp->l2type == MG_TCPIP_L2_ETH || + ifp->l2type == MG_TCPIP_L2_PPPoE) // TODO(): print other l2 MG_INFO((" MAC: %M", mg_print_mac, ifp->mac)); } else if (ifp->state == MG_TCPIP_STATE_IP) { - if (ifp->gw != 0) + if (ifp->gw != 0 && ifp->l2type == MG_TCPIP_L2_ETH) mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request } else if (ifp->state == MG_TCPIP_STATE_UP) { srand((unsigned int) mg_millis()); @@ -5389,7 +5839,7 @@ static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, struct mg_addr *ip_src, struct mg_addr *ip_dst, const void *buf, size_t len) { - uint8_t *l2p = (uint8_t *) ifp->tx.buf; + uint8_t *l3p; size_t l2_len; struct ip *ip = NULL; struct udp *udp; @@ -5400,6 +5850,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, len + sizeof(struct udp)); udp = (struct udp *) (ip6 + 1); l2_len = sizeof(*ip6) + sizeof(*udp) + len; + l3p = (uint8_t *) ip6; } else #endif { @@ -5407,6 +5858,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, len + sizeof(struct udp)); udp = (struct udp *) (ip + 1); l2_len = sizeof(*ip) + sizeof(*udp) + len; + l3p = (uint8_t *) ip; } udp->sport = ip_src->port; udp->dport = ip_dst->port; @@ -5421,7 +5873,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, { udp->csum = pcsum(ip, udp, sizeof(*udp) + len); } - l2_len = mg_l2_footer(ifp->l2type, l2_len, l2p); + l2_len = mg_l2_footer(ifp->l2type, l2_len, l3p + l2_len); return (driver_output(ifp, l2_len) == l2_len); } @@ -5550,7 +6002,8 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { arp->spa = ifp->ip; MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac, ifp->mac)); - driver_output(ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, arp + 1), l2p)); + driver_output( + ifp, mg_l2_footer(ifp->l2type, sizeof(*arp), (uint8_t *) (arp + 1))); } else if (pkt->arp->op == mg_htons(2)) { if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; // MG_VERBOSE(("ARP resp from %M", mg_print_ip4, &pkt->arp->spa)); @@ -5577,7 +6030,6 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { } static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - uint8_t *l2p = (uint8_t *) ifp->tx.buf; size_t plen = pkt->pay.len; if (!icmpcsum_ok(pkt->icmp, sizeof(struct icmp) + plen)) return; if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { @@ -5598,7 +6050,8 @@ static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { memset(icmp, 0, sizeof(*icmp)); // Set csum, type, code to 0 memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); - driver_output(ifp, mg_l2_footer(ifp->l2type, hlen + plen, l2p)); + driver_output(ifp, mg_l2_footer(ifp->l2type, hlen + plen, + (uint8_t *) ip + hlen + plen)); } } @@ -5738,7 +6191,6 @@ static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src, uint64_t *ip_dst, uint8_t type, uint8_t code, const void *buf, size_t len) { - uint8_t *l2p = (uint8_t *) ifp->tx.buf; struct ip6 *ip6; struct icmp6 *icmp6; ip6 = tx_ip6(ifp, l2_dst, 58, ip_src, ip_dst, sizeof(*icmp6) + len); @@ -5749,8 +6201,9 @@ static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src, memcpy(icmp6 + 1, buf, len); // Copy payload icmp6->csum = 0; // RFC-4443 2.3, RFC-8200 8.1 icmp6->csum = p6csum(ip6, icmp6, sizeof(*icmp6) + len); - driver_output( - ifp, mg_l2_footer(ifp->l2type, sizeof(*ip6) + sizeof(*icmp6) + len, l2p)); + driver_output(ifp, + mg_l2_footer(ifp->l2type, sizeof(*ip6) + sizeof(*icmp6) + len, + (uint8_t *) (ip6 + 1) + sizeof(*icmp6) + len)); } // Neighbor Discovery Protocol, RFC-4861 @@ -6013,7 +6466,7 @@ static void onstate6change(struct mg_tcpip_if *ifp) { } else if (ifp->state6 == MG_TCPIP_STATE_UP) { MG_INFO(("IP: %M", mg_print_ip6, &ifp->ip6ll)); } - if (ifp->state6 != MG_TCPIP_STATE_UP && ifp->state6 != MG_TCPIP_STATE_DOWN) + if (ifp->state6 > MG_TCPIP_STATE_UP) mg_tcpip_call(ifp, MG_TCPIP_EV_ST6_CHG, &ifp->state6); } #endif @@ -6100,7 +6553,7 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, struct mg_addr *ip_src, struct mg_addr *ip_dst, uint8_t flags, uint32_t seq, uint32_t ack, const void *buf, size_t len) { - uint8_t *l2p = (uint8_t *) ifp->tx.buf; + uint8_t *l3p; struct ip *ip = NULL; struct tcp *tcp; uint16_t opts[4 / 2]; @@ -6127,11 +6580,13 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, ip6 = tx_ip6(ifp, l2_dst, 6, ip_src->addr.ip6, ip_dst->addr.ip6, hlen + len); tcp = (struct tcp *) (ip6 + 1); + l3p = (uint8_t *) ip6; } else #endif { ip = tx_ip(ifp, l2_dst, 6, ip_src->addr.ip4, ip_dst->addr.ip4, hlen + len); tcp = (struct tcp *) (ip + 1); + l3p = (uint8_t *) ip; } memset(tcp, 0, sizeof(*tcp)); memmove(tcp + 1, opts, hlen - sizeof(*tcp)); // copy opts if any @@ -6154,7 +6609,8 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, MG_VERBOSE(("TCP %M -> %M fl %x len %u", mg_print_ip_port, ip_src, mg_print_ip_port, ip_dst, tcp->flags, len)); return driver_output( - ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, tcp) + hlen + len, l2p)); + ifp, mg_l2_footer(ifp->l2type, PDIFF(l3p, (uint8_t *) tcp + hlen + len), + (uint8_t *) tcp + hlen + len)); } static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt, @@ -6737,6 +7193,7 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { pkt.raw.len = len; pkt.l2 = (uint8_t *) pkt.raw.buf; if (!mg_l2_rx(ifp, &proto, &pkt.pay, &pkt.raw)) return; + if (ifp->state < MG_TCPIP_STATE_UP) return; // discard while L2 is not up if (proto == MG_TCPIP_L2PROTO_ARP) { pkt.arp = (struct arp *) (pkt.pay.buf); if (pkt.pay.len < sizeof(*pkt.arp)) return; // Truncated @@ -6752,7 +7209,7 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { } static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) { - if (ifp->state == MG_TCPIP_STATE_DOWN) return; + if (ifp->state < MG_TCPIP_STATE_UP) return; // DHCP RFC-2131 (4.4) if (ifp->enable_dhcp_client && s1) { if (ifp->state == MG_TCPIP_STATE_UP) { @@ -6772,11 +7229,13 @@ static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) { } } } -static void mg_ip_link(struct mg_tcpip_if *ifp, bool up) { - bool current = ifp->state != MG_TCPIP_STATE_DOWN; - if (!up && ifp->enable_dhcp_client) ifp->ip = 0; - if (up != current) { // link state has changed - ifp->state = up == false ? MG_TCPIP_STATE_DOWN +static void mg_ip_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) { + bool cur_drv = (ifp->state != MG_TCPIP_STATE_DOWN); + bool cur_l2 = (ifp->state >= MG_TCPIP_STATE_UP); + if (!l2_up && ifp->enable_dhcp_client) ifp->ip = 0; + if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed + ifp->state = !drv_up ? MG_TCPIP_STATE_DOWN + : !l2_up ? MG_TCPIP_STATE_LINK_UP : ifp->enable_dhcp_client || ifp->ip == 0 ? MG_TCPIP_STATE_UP : MG_TCPIP_STATE_IP; onstatechange(ifp); @@ -6789,15 +7248,17 @@ static void mg_ip_link(struct mg_tcpip_if *ifp, bool up) { #if MG_ENABLE_IPV6 static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) { - if (ifp->state6 == MG_TCPIP_STATE_DOWN) return; + if (ifp->state6 < MG_TCPIP_STATE_UP) return; if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP) tx_ndp_rs(ifp); } -static void mg_ip6_link(struct mg_tcpip_if *ifp, bool up) { - bool current = ifp->state6 != MG_TCPIP_STATE_DOWN; +static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) { + bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN); + bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP); if (!up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0; - if (up != current) { // link state has changed - ifp->state6 = !up ? MG_TCPIP_STATE_DOWN + if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed + ifp->state6 = !drv_up ? MG_TCPIP_STATE_DOWN + : !l2_up ? MG_TCPIP_STATE_LINK_UP : ifp->enable_slaac || ifp->ip6[0] == 0 ? MG_TCPIP_STATE_UP : MG_TCPIP_STATE_IP; onstate6change(ifp); @@ -6809,7 +7270,7 @@ static void mg_ip6_link(struct mg_tcpip_if *ifp, bool up) { } #else #define mg_ip6_poll(x, y) -#define mg_ip6_link(x, y) +#define mg_ip6_link(x, y, z) #endif static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { @@ -6819,7 +7280,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { if (expired_1000ms) { #if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS - const char *names[] = {"down", "up", "req", "ip", "ready"}; + const char *names[] = {"down", "lnk up", "lnk rdy", "req", "ip", "ready"}; size_t max = sizeof(names) / sizeof(char *); unsigned int state = ifp->state >= max ? max - 1 : ifp->state; MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", names[state], @@ -6852,14 +7313,15 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { tx_ndp_ns(ifp, ifp->gw6, NULL); // retry GW hwaddr resolution #endif - // poll driver - if (ifp->driver->poll) { - bool up = ifp->driver->poll(ifp, expired_1000ms); - // Handle physical interface up/down status, ifp->state rules over state6 - if (expired_1000ms) { - mg_ip_link(ifp, up); // Handle IPv4 - mg_ip6_link(ifp, up); // Handle IPv6 - if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down")); + { // poll driver and let L2 do its work, if any + // Handle physical interface up/down status + bool drv_up, l2_up; + drv_up = ifp->driver->poll ? ifp->driver->poll(ifp, expired_1000ms) : true; + l2_up = mg_l2_poll(ifp, expired_1000ms); // Handle L2 up/down link status; + if (expired_1000ms) { // ifp->state rules over state6 + mg_ip_link(ifp, drv_up, l2_up); // Handle IPv4 + mg_ip6_link(ifp, drv_up, l2_up); // Handle IPv6 + if (ifp->state < MG_TCPIP_STATE_UP) MG_ERROR(("Network is down")); mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL); } } @@ -6867,8 +7329,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { mg_ip_poll(ifp, expired_1000ms); // Handle IPv4 mg_ip6_poll(ifp, expired_1000ms); // Handle IPv6 - if (ifp->state == MG_TCPIP_STATE_DOWN) return; - // Read data from the network + // Read data from the network (mg_tcpip_rx() will discard for us) if (ifp->driver->rx != NULL) { // Simple polling driver, returns one frame size_t len = ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp); @@ -6884,6 +7345,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { mg_queue_del(&ifp->recv_queue, len); } } + if (ifp->state < MG_TCPIP_STATE_UP) return; // need to let L2 do its job // Process timeouts for (c = ifp->mgr->conns; c != NULL; c = c->next) { @@ -6936,7 +7398,7 @@ void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) { void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { // If L2 address is not set, make a random one; fill MTU - mg_l2_init(ifp->l2type, ifp->mac, &ifp->mtu, &ifp->framesize); + mg_l2_init(ifp); if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip" memcpy(ifp->dhcp_name, "mip", 4); @@ -6954,7 +7416,9 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { mgr->ifp = ifp; ifp->mgr = mgr; mgr->extraconnsize = sizeof(struct connstate); - if (ifp->ip == 0) ifp->enable_dhcp_client = true; + if (ifp->ip == 0 && ifp->l2type != MG_TCPIP_L2_PPP && + ifp->l2type != MG_TCPIP_L2_PPPoE) + ifp->enable_dhcp_client = true; mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535 ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from // MG_EPHEMERAL_PORT_BASE to 65535 @@ -23103,6 +23567,163 @@ size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op) { return c->send.len; // so far recoverable, let the caller decide } +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/at_cmd.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ATCMD) && MG_ENABLE_DRIVER_ATCMD + +#define MG_HDLC_FLAG 0x7e // HDLC frame delimiter +#define MG_HDLC_ESC 0x7d // HDLC escape byte for byte stuffing + +#define MG_ATCMD_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 void mg_atcmd_reset(struct mg_tcpip_driver_atcmd_data *dd) { + dd->script_index = 0; + dd->deadline = 0; + if (dd->reset) dd->reset(dd->usart); +} + +static bool mg_atcmd_handle(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_atcmd_data *dd = (struct mg_tcpip_driver_atcmd_data *) ifp->driver_data; + if (dd->script == NULL || dd->script_index < 0) return true; + if (dd->deadline == 0) dd->deadline = mg_millis() + MG_ATCMD_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->usart, *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; + bool is_timeout = dd->deadline > 0 && mg_millis() > dd->deadline; + bool is_overflow = q->head >= q->size - 1; + if (is_timeout || is_overflow) { + MG_ERROR(("AT error: %s, retrying...", is_timeout ? "timeout" : "overflow")); + mg_atcmd_reset(dd); + return false; // FAIL + } + if ((c = dd->rx(dd->usart)) < 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 void byte_stuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t b); +static bool byte_unstuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t *p); + +static size_t mg_atcmd_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_atcmd_data *dd = (struct mg_tcpip_driver_atcmd_data *) ifp->driver_data; + struct mg_queue *q = &ifp->recv_queue; + + if (!dd->link) return 0; + + while (q->head < q->size) { // read as many bytes as possible + uint8_t b; + int c = dd->rx(dd->usart); + if (c < 0) return 0; // no more bytes, exit + b = (uint8_t) c; + if (b == MG_HDLC_FLAG) { + if (q->head == 0) { // first flag: skip + dd->unstuffing = false; + continue; + } else { // last flag: end of frame, exit loop + break; + } + } + if (!dd->no_byte_stuff && !byte_unstuff(dd, &b)) continue; + q->buf[q->head++] = b; + } + len = (q->head <= len) ? q->head : 0; + memmove(buf, q->buf, len); + q->head = 0; + return len; +} + +static size_t mg_atcmd_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_atcmd_data *dd = (struct mg_tcpip_driver_atcmd_data *) ifp->driver_data; + uint8_t *p = (uint8_t *) buf; + size_t n = len; + + if (!dd->link) return 0; + + dd->tx(dd->usart, MG_HDLC_FLAG); + while (n--) { + if (dd->no_byte_stuff) { + dd->tx(dd->usart, *p++); + } else { + byte_stuff(dd, *p++); + } + } + dd->tx(dd->usart, MG_HDLC_FLAG); + return len; +} + +static bool mg_atcmd_init(struct mg_tcpip_if *ifp) { + ifp->recv_queue.size = 1506; // PPP MTU=1500 + header + HDLC w/ no flags + return true; +} + +static bool mg_atcmd_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_driver_atcmd_data *dd = (struct mg_tcpip_driver_atcmd_data *) ifp->driver_data; + dd->link = mg_atcmd_handle(ifp); + return s1 ? dd->link :false; +} + + +struct mg_tcpip_driver mg_tcpip_driver_atcmd = {mg_atcmd_init, mg_atcmd_tx, mg_atcmd_rx, mg_atcmd_poll}; + + + +static void byte_stuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t b) { + if ((b < 0x20) || (b == MG_HDLC_ESC) || (b == MG_HDLC_FLAG)) { + dd->tx(dd->usart, MG_HDLC_ESC); + dd->tx(dd->usart, b ^ 0x20); + } else { + dd->tx(dd->usart, b); + } +} + +static bool byte_unstuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t *p) { + if (!dd->unstuffing) { + if (*p == MG_HDLC_ESC) { + dd->unstuffing = true; + return false; + } else { + return true; + } + } + dd->unstuffing = false; + *p ^= 0x20; + return true; +} + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/drivers/cmsis.c" #endif @@ -25344,338 +25965,6 @@ bool mg_wifi_ap_stop(void) { #endif -#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 void mg_ppp_reset(struct mg_tcpip_driver_ppp_data *dd) { - dd->script_index = 0; - dd->deadline = 0; - if (dd->reset) dd->reset(dd->uart); -} - -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")); - mg_ppp_reset(dd); - 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_poll(struct mg_tcpip_if *ifp, bool s1) { - (void) s1; - 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)); - mg_ppp_reset(dd); - ifp->state = MG_TCPIP_STATE_UP; - 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_DEBUG(("host ip: %d.%d.%d.%d", 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_DEBUG(("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]); - ifp->state = MG_TCPIP_STATE_READY; - } - 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_VERBOSE(("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_poll}; - -#endif - #ifdef MG_ENABLE_LINES #line 1 "src/drivers/ra.c" #endif diff --git a/mongoose.h b/mongoose.h index 4210a75a..7cbda7c1 100644 --- a/mongoose.h +++ b/mongoose.h @@ -3209,7 +3209,7 @@ bool mg_wifi_ap_stop(void); #if MG_ENABLE_TCPIP // no config defaults to 0 => Ethernet -enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP }; // MG_TCPIP_L2_PPPoE +enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE}; #if defined(__DCC__) #pragma pack(1) @@ -3340,10 +3340,11 @@ struct mg_tcpip_if { volatile uint32_t nerr; // Number of driver errors uint8_t state; // Current link and IPv4 state #define MG_TCPIP_STATE_DOWN 0 // Interface is down -#define MG_TCPIP_STATE_UP 1 // Interface is up -#define MG_TCPIP_STATE_REQ 2 // Interface is up, DHCP REQUESTING state -#define MG_TCPIP_STATE_IP 3 // Interface is up and has an IP assigned -#define MG_TCPIP_STATE_READY 4 // Interface has fully come up, ready to work +#define MG_TCPIP_STATE_LINK_UP 1 // Driver reports link state is up +#define MG_TCPIP_STATE_UP 2 // Interface is up --> LINK_READY +#define MG_TCPIP_STATE_REQ 3 // Interface is up, DHCP REQUESTING state +#define MG_TCPIP_STATE_IP 4 // Interface is up and has an IP assigned +#define MG_TCPIP_STATE_READY 5 // Interface has fully come up, ready to work bool gw_ready; // We've got a hw address for the router #if MG_ENABLE_IPV6 uint8_t gw6mac[sizeof(struct mg_l2addr)]; // IPV6 Router's hw address @@ -3369,12 +3370,12 @@ 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; 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; +extern struct mg_tcpip_driver mg_tcpip_driver_atcmd; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { @@ -3424,6 +3425,20 @@ struct mg_tcpip_spi { #endif +struct mg_tcpip_driver_atcmd_data { + void *usart; // Opaque UART/SPI descriptor + void (*reset)(void *); // Modem hardware reset + void (*tx)(void *, uint8_t); // USART transmit single byte + int (*rx)(void *); // USART 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 + bool no_byte_stuff; // Do not perform byte stuffing/unstuffing + bool link; // internal use: done with AT processing + bool unstuffing; // internal use: unstuffing state +}; + + #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && MG_ENABLE_DRIVER_CMSIS #include "Driver_ETH_MAC.h" // keep this include @@ -3608,17 +3623,6 @@ struct mg_tcpip_driver_pico_w_data { #endif -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_RA6) && MG_ENABLE_DRIVER_RA6) || \ (defined(MG_ENABLE_DRIVER_RA8) && MG_ENABLE_DRIVER_RA8) diff --git a/src/drivers/at_cmd.c b/src/drivers/at_cmd.c new file mode 100644 index 00000000..1f413472 --- /dev/null +++ b/src/drivers/at_cmd.c @@ -0,0 +1,153 @@ +#include "net_builtin.h" + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ATCMD) && MG_ENABLE_DRIVER_ATCMD + +#define MG_HDLC_FLAG 0x7e // HDLC frame delimiter +#define MG_HDLC_ESC 0x7d // HDLC escape byte for byte stuffing + +#define MG_ATCMD_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 void mg_atcmd_reset(struct mg_tcpip_driver_atcmd_data *dd) { + dd->script_index = 0; + dd->deadline = 0; + if (dd->reset) dd->reset(dd->usart); +} + +static bool mg_atcmd_handle(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_atcmd_data *dd = (struct mg_tcpip_driver_atcmd_data *) ifp->driver_data; + if (dd->script == NULL || dd->script_index < 0) return true; + if (dd->deadline == 0) dd->deadline = mg_millis() + MG_ATCMD_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->usart, *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; + bool is_timeout = dd->deadline > 0 && mg_millis() > dd->deadline; + bool is_overflow = q->head >= q->size - 1; + if (is_timeout || is_overflow) { + MG_ERROR(("AT error: %s, retrying...", is_timeout ? "timeout" : "overflow")); + mg_atcmd_reset(dd); + return false; // FAIL + } + if ((c = dd->rx(dd->usart)) < 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 void byte_stuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t b); +static bool byte_unstuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t *p); + +static size_t mg_atcmd_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_atcmd_data *dd = (struct mg_tcpip_driver_atcmd_data *) ifp->driver_data; + struct mg_queue *q = &ifp->recv_queue; + + if (!dd->link) return 0; + + while (q->head < q->size) { // read as many bytes as possible + uint8_t b; + int c = dd->rx(dd->usart); + if (c < 0) return 0; // no more bytes, exit + b = (uint8_t) c; + if (b == MG_HDLC_FLAG) { + if (q->head == 0) { // first flag: skip + dd->unstuffing = false; + continue; + } else { // last flag: end of frame, exit loop + break; + } + } + if (!dd->no_byte_stuff && !byte_unstuff(dd, &b)) continue; + q->buf[q->head++] = b; + } + len = (q->head <= len) ? q->head : 0; + memmove(buf, q->buf, len); + q->head = 0; + return len; +} + +static size_t mg_atcmd_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_atcmd_data *dd = (struct mg_tcpip_driver_atcmd_data *) ifp->driver_data; + uint8_t *p = (uint8_t *) buf; + size_t n = len; + + if (!dd->link) return 0; + + dd->tx(dd->usart, MG_HDLC_FLAG); + while (n--) { + if (dd->no_byte_stuff) { + dd->tx(dd->usart, *p++); + } else { + byte_stuff(dd, *p++); + } + } + dd->tx(dd->usart, MG_HDLC_FLAG); + return len; +} + +static bool mg_atcmd_init(struct mg_tcpip_if *ifp) { + ifp->recv_queue.size = 1506; // PPP MTU=1500 + header + HDLC w/ no flags + return true; +} + +static bool mg_atcmd_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_driver_atcmd_data *dd = (struct mg_tcpip_driver_atcmd_data *) ifp->driver_data; + dd->link = mg_atcmd_handle(ifp); + return s1 ? dd->link :false; +} + + +struct mg_tcpip_driver mg_tcpip_driver_atcmd = {mg_atcmd_init, mg_atcmd_tx, mg_atcmd_rx, mg_atcmd_poll}; + + + +static void byte_stuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t b) { + if ((b < 0x20) || (b == MG_HDLC_ESC) || (b == MG_HDLC_FLAG)) { + dd->tx(dd->usart, MG_HDLC_ESC); + dd->tx(dd->usart, b ^ 0x20); + } else { + dd->tx(dd->usart, b); + } +} + +static bool byte_unstuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t *p) { + if (!dd->unstuffing) { + if (*p == MG_HDLC_ESC) { + dd->unstuffing = true; + return false; + } else { + return true; + } + } + dd->unstuffing = false; + *p ^= 0x20; + return true; +} + +#endif diff --git a/src/drivers/at_cmd.h b/src/drivers/at_cmd.h new file mode 100644 index 00000000..ea49cf8e --- /dev/null +++ b/src/drivers/at_cmd.h @@ -0,0 +1,14 @@ +#pragma once + +struct mg_tcpip_driver_atcmd_data { + void *usart; // Opaque UART/SPI descriptor + void (*reset)(void *); // Modem hardware reset + void (*tx)(void *, uint8_t); // USART transmit single byte + int (*rx)(void *); // USART 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 + bool no_byte_stuff; // Do not perform byte stuffing/unstuffing + bool link; // internal use: done with AT processing + bool unstuffing; // internal use: unstuffing state +}; diff --git a/src/drivers/ppp.c b/src/drivers/ppp.c deleted file mode 100644 index c12bb0e4..00000000 --- a/src/drivers/ppp.c +++ /dev/null @@ -1,328 +0,0 @@ -#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 void mg_ppp_reset(struct mg_tcpip_driver_ppp_data *dd) { - dd->script_index = 0; - dd->deadline = 0; - if (dd->reset) dd->reset(dd->uart); -} - -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")); - mg_ppp_reset(dd); - 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_poll(struct mg_tcpip_if *ifp, bool s1) { - (void) s1; - 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)); - mg_ppp_reset(dd); - ifp->state = MG_TCPIP_STATE_UP; - 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_DEBUG(("host ip: %d.%d.%d.%d", 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_DEBUG(("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]); - ifp->state = MG_TCPIP_STATE_READY; - } - 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_VERBOSE(("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_poll}; - -#endif diff --git a/src/drivers/ppp.h b/src/drivers/ppp.h deleted file mode 100644 index 5ae2b31e..00000000 --- a/src/drivers/ppp.h +++ /dev/null @@ -1,11 +0,0 @@ -#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/l2.c b/src/l2.c index 2ff0ab3a..88adcfac 100644 --- a/src/l2.c +++ b/src/l2.c @@ -5,11 +5,11 @@ #if MG_ENABLE_TCPIP // L2 API -void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, - uint16_t *framesize); +void mg_l2_init(struct mg_tcpip_if *ifp); +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms); uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, uint8_t *dst, uint8_t *frame); -size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *frame); +size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *cur); bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, struct mg_str *pay, struct mg_str *raw); // TODO(): ? bool mg_l2_rx(enum mg_l2type type, struct mg_l2opts *opts, uint8_t @@ -17,24 +17,31 @@ bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, uint8_t *mg_l2_getaddr(enum mg_l2type type, uint8_t *frame); uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype, struct mg_addr *ip); +#if MG_ENABLE_IPV6 bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len, uint8_t *addr); bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, uint8_t len); uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts); +#endif +size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len); // clang-format off -extern void mg_l2_eth_init(struct mg_l2addr *, uint16_t *, uint16_t *); +extern void mg_l2_eth_init(struct mg_tcpip_if *); +extern bool mg_l2_eth_poll(struct mg_tcpip_if *, bool); extern uint8_t *mg_l2_eth_header(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); extern size_t mg_l2_eth_footer(size_t, uint8_t *); extern bool mg_l2_eth_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); extern struct mg_l2addr *mg_l2_eth_getaddr(uint8_t *); extern struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype, struct mg_addr *); +#if MG_ENABLE_IPV6 extern bool mg_l2_eth_genip6(uint64_t *, uint8_t, struct mg_l2addr *); extern bool mg_l2_eth_ip6get(struct mg_l2addr *, uint8_t *, uint8_t); extern uint8_t mg_l2_eth_ip6put(struct mg_l2addr *, uint8_t *); +#endif -extern void mg_l2_ppp_init(struct mg_l2addr *, uint16_t *, uint16_t *); +extern void mg_l2_ppp_init(struct mg_tcpip_if *); +extern bool mg_l2_ppp_poll(struct mg_tcpip_if *, bool); extern uint8_t *mg_l2_ppp_header(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); extern size_t mg_l2_ppp_footer(size_t, uint8_t *); extern bool mg_l2_ppp_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); @@ -45,8 +52,14 @@ extern bool mg_l2_ppp_genip6(uint64_t *, uint8_t, struct mg_l2addr *); extern bool mg_l2_ppp_ip6get(struct mg_l2addr *, uint8_t *, uint8_t); extern uint8_t mg_l2_ppp_ip6put(struct mg_l2addr *, uint8_t *); #endif +extern void mg_l2_pppoe_init(struct mg_tcpip_if *); +extern bool mg_l2_pppoe_poll(struct mg_tcpip_if *, bool); +extern uint8_t *mg_l2_pppoe_header(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); +extern size_t mg_l2_pppoe_footer(size_t, uint8_t *); +extern bool mg_l2_pppoe_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); -typedef void (*l2_init_fn)(struct mg_l2addr *, uint16_t *, uint16_t *); +typedef void (*l2_init_fn)(struct mg_tcpip_if *); +typedef bool (*l2_poll_fn)(struct mg_tcpip_if *, bool); typedef uint8_t *((*l2_header_fn)(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *)); typedef size_t (*l2_footer_fn)(size_t, uint8_t *); typedef bool (*l2_rx_fn)(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); @@ -59,22 +72,25 @@ typedef uint8_t (*l2_ip6put_fn)(struct mg_l2addr *, uint8_t *); #endif // clang-format on -static const l2_init_fn l2_init[] = {mg_l2_eth_init, mg_l2_ppp_init}; -static const l2_header_fn l2_header[] = {mg_l2_eth_header, mg_l2_ppp_header}; -static const l2_footer_fn l2_footer[] = {mg_l2_eth_footer, mg_l2_ppp_footer}; -static const l2_rx_fn l2_rx[] = {mg_l2_eth_rx, mg_l2_ppp_rx}; -static const l2_getaddr_fn l2_getaddr[] = {mg_l2_eth_getaddr, - mg_l2_ppp_getaddr}; -static const l2_mapip_fn l2_mapip[] = {mg_l2_eth_mapip, mg_l2_ppp_mapip}; +static const l2_init_fn l2_init[] = {mg_l2_eth_init, mg_l2_ppp_init, mg_l2_pppoe_init}; +static const l2_poll_fn l2_poll[] = {mg_l2_eth_poll, mg_l2_ppp_poll, mg_l2_pppoe_poll}; +static const l2_header_fn l2_header[] = {mg_l2_eth_header, mg_l2_ppp_header, mg_l2_pppoe_header}; +static const l2_footer_fn l2_footer[] = {mg_l2_eth_footer, mg_l2_ppp_footer, mg_l2_pppoe_footer}; +static const l2_rx_fn l2_rx[] = {mg_l2_eth_rx, mg_l2_ppp_rx, mg_l2_pppoe_rx}; +static const l2_getaddr_fn l2_getaddr[] = {mg_l2_eth_getaddr, mg_l2_ppp_getaddr, mg_l2_ppp_getaddr}; +static const l2_mapip_fn l2_mapip[] = {mg_l2_eth_mapip, mg_l2_ppp_mapip, mg_l2_ppp_mapip}; #if MG_ENABLE_IPV6 -static const l2_genip6_fn l2_genip6[] = {mg_l2_eth_genip6, mg_l2_ppp_genip6}; -static const l2_ip6get_fn l2_ip6get[] = {mg_l2_eth_ip6get, mg_l2_ppp_ip6get}; -static const l2_ip6put_fn l2_ip6put[] = {mg_l2_eth_ip6put, mg_l2_ppp_ip6put}; +static const l2_genip6_fn l2_genip6[] = {mg_l2_eth_genip6, mg_l2_ppp_genip6, mg_l2_ppp_genip6}; +static const l2_ip6get_fn l2_ip6get[] = {mg_l2_eth_ip6get, mg_l2_ppp_ip6get, mg_l2_ppp_ip6get}; +static const l2_ip6put_fn l2_ip6put[] = {mg_l2_eth_ip6put, mg_l2_ppp_ip6put, mg_l2_ppp_ip6put}; #endif -void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, - uint16_t *framesize) { - l2_init[type]((struct mg_l2addr *) addr, mtu, framesize); +void mg_l2_init(struct mg_tcpip_if *ifp) { + l2_init[ifp->l2type](ifp); +} + +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + return l2_poll[ifp->l2type](ifp, expired_1000ms); } uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, @@ -118,4 +134,10 @@ uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts) { } #endif +size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len) { + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp); + if (n == len) ifp->nsent++; + return n; +} + #endif diff --git a/src/l2.h b/src/l2.h index 6be8dcd3..c961443d 100644 --- a/src/l2.h +++ b/src/l2.h @@ -6,7 +6,7 @@ #if MG_ENABLE_TCPIP // no config defaults to 0 => Ethernet -enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP }; // MG_TCPIP_L2_PPPoE +enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE}; #if defined(__DCC__) #pragma pack(1) diff --git a/src/l2_eth.c b/src/l2_eth.c index 590dd93c..1f3a29c2 100644 --- a/src/l2_eth.c +++ b/src/l2_eth.c @@ -34,8 +34,8 @@ static const uint16_t eth_types[] = { 0x8864 // PPPoE Session Stage }; -void mg_l2_eth_init(struct mg_l2addr *l2addr, uint16_t *mtu, - uint16_t *framesize) { +void mg_l2_eth_init(struct mg_tcpip_if *ifp) { + struct mg_l2addr *l2addr = (struct mg_l2addr *)ifp->mac; // If MAC is not set, make a random one if (l2addr->addr.mac[0] == 0 && l2addr->addr.mac[1] == 0 && l2addr->addr.mac[2] == 0 && l2addr->addr.mac[3] == 0 && @@ -45,8 +45,14 @@ void mg_l2_eth_init(struct mg_l2addr *l2addr, uint16_t *mtu, MG_INFO( ("MAC not set. Generated random: %M", mg_print_mac, l2addr->addr.mac)); } - *mtu = 1500; - *framesize = 1540; + ifp->mtu = 1500; + ifp->framesize = 1540; +} + +bool mg_l2_eth_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + (void) ifp; + (void) expired_1000ms; + return true; } uint8_t *mg_l2_eth_header(enum mg_l2proto proto, struct mg_l2addr *src, @@ -58,10 +64,11 @@ uint8_t *mg_l2_eth_header(enum mg_l2proto proto, struct mg_l2addr *src, return (uint8_t *) (eth + 1); } -size_t mg_l2_eth_footer(size_t len, uint8_t *frame) { - struct eth *eth = (struct eth *) frame; - // nothing to do; there is no len field in Ethernet, CRC is hw-calculated - return len + sizeof(*eth); +size_t mg_l2_eth_footer(size_t len, uint8_t *cur) { + // there is no len field in Ethernet, CRC is hw-calculated; pad to 64 - CRC + size_t ethlen = len + sizeof(struct eth); + (void) cur; + return ethlen >= 60 ? ethlen : 60; } struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype addrtype, diff --git a/src/l2_ppp.c b/src/l2_ppp.c index 2238add1..0f744c94 100644 --- a/src/l2_ppp.c +++ b/src/l2_ppp.c @@ -12,22 +12,35 @@ #else #pragma pack(push, 1) #endif +// all in network order -struct ppp { // RFC-1662 - uint8_t addr, ctrl; +struct ppp { // RFC-1661 uint16_t proto; }; struct lcp { // RFC-1661 - uint8_t code, id, len[2]; + uint8_t code, id; + uint16_t len; }; -struct ipcp { // RFC-1332 - uint8_t code; +struct ipcp { // RFC-1332 + uint8_t code, id; + uint16_t len; }; -struct ipv6cp { // RFC-5072 - uint8_t code; +struct ipv6cp { // RFC-5072 + uint8_t code, id; + uint16_t len; +}; + +struct hdlc_ { // RFC-1662, "PPP in HDLC-like Framing" + uint8_t addr, ctrl; +}; + +struct pppoe { // RFC-2516, "A Method for Transmitting PPP Over Ethernet + // (PPPoE)" + uint8_t vertype, code; + uint16_t id, len; }; #if defined(__DCC__) @@ -36,52 +49,361 @@ struct ipv6cp { // RFC-5072 #pragma pack(pop) #endif +#define MG_PPP_ADDR 0xff +#define MG_PPP_CTRL 0x03 -void mg_l2_ppp_init(struct mg_l2addr *addr, uint16_t *mtu, - uint16_t *framesize) { - (void) addr; - *mtu = 1500; // 1492 for PPPoE - *framesize = 1540; // *** TODO(scaprile): actual value, check for PPPoE too +#define MG_PPP_PROTO_IP 0x0021 +#define MG_PPP_PROTO_IPV6 0x0057 +#define MG_PPP_PROTO_LCP 0xc021 +#define MG_PPP_PROTO_IPCP 0x8021 +#define MG_PPP_PROTO_IPV6CP 0x8057 +#define MG_PPP_PROTO_PAP 0xc023 +#define MG_PPP_PROTO_CHAP 0xc223 + +#define MG_PPP_IPCP_REQ 1 +#define MG_PPP_IPCP_ACK 2 +#define MG_PPP_IPCP_NACK 3 +#define MG_PPP_IPCP_REJECT 4 +#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_LCP_REJECT 8 +#define MG_PPP_LCP_ECHO_REQ 9 +#define MG_PPP_LCP_ECHO_REPLY 10 + +#define MG_PPPoE_PADI 0x09 +#define MG_PPPoE_PADO 0x07 +#define MG_PPPoE_PADR 0x19 +#define MG_PPPoE_PADS 0x65 +#define MG_PPPoE_PADT 0xa7 + +#define MG_PPPoE_ST_DISC 0 // Discovery phase, see what servers are out there +#define MG_PPPoE_ST_REQ 1 // Chose a server, request a session and wait +#define MG_PPPoE_ST_SESS 2 // Session established, PPP traffic is exchanged + +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) + +static bool s_link = false; // ******************************************* +static uint8_t s_state = MG_PPPoE_ST_DISC; +static uint16_t s_id; + +void mg_l2_ppp_init(struct mg_tcpip_if *ifp) { + ifp->mtu = 1500; + ifp->framesize = 1500 + sizeof(struct ppp) + sizeof(struct hdlc_); +} + +extern void mg_l2_eth_init(struct mg_tcpip_if *); + +void mg_l2_pppoe_init(struct mg_tcpip_if *ifp) { + mg_l2_eth_init(ifp); + ifp->mtu = ifp->mtu - (uint16_t) (sizeof(struct pppoe) + + sizeof(struct ppp)); // 1500 --> 1492 +} + +bool mg_l2_ppp_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_DOWN) s_link = false; + return s_link; +} + +static uint8_t *hdlc_header(uint8_t *p) { + struct hdlc_ *hdlc = (struct hdlc_ *) p; + hdlc->addr = MG_PPP_ADDR; + hdlc->ctrl = MG_PPP_CTRL; + return (uint8_t *) (hdlc + 1); +} + +static uint8_t *ppp_header(uint16_t proto, uint8_t *p) { + struct ppp *ppp = (struct ppp *) p; + ppp->proto = mg_htons(proto); + return (uint8_t *) (ppp + 1); +} + +static uint8_t *l2_ppp_header(enum mg_l2proto proto, uint8_t *p) { + uint16_t ppp_proto = proto == MG_TCPIP_L2PROTO_IPV4 ? MG_PPP_PROTO_IP + : proto == MG_TCPIP_L2PROTO_IPV4 ? MG_PPP_PROTO_IPV6 + : 0; + return ppp_header(ppp_proto, p); } uint8_t *mg_l2_ppp_header(enum mg_l2proto proto, struct mg_l2addr *src, struct mg_l2addr *dst, uint8_t *frame) { (void) src; (void) dst; - (void) proto; - return frame; + return l2_ppp_header(proto, hdlc_header(frame)); } -size_t mg_l2_ppp_footer(size_t len, uint8_t *frame) { - (void) frame; - return len; +extern uint8_t *mg_l2_eth_header(enum mg_l2proto proto, struct mg_l2addr *src, + struct mg_l2addr *dst, uint8_t *frame); + +static uint8_t *pppoe_header(enum mg_l2proto proto, uint8_t code, uint16_t id, + struct mg_l2addr *src, struct mg_l2addr *dst, + uint8_t *frame) { + struct pppoe *pppoe = + (struct pppoe *) mg_l2_eth_header(proto, src, dst, frame); + pppoe->vertype = 0x11; + pppoe->code = code; + pppoe->id = id; + return (uint8_t *) (pppoe + 1); +} + +uint8_t *mg_l2_pppoe_header(enum mg_l2proto proto, struct mg_l2addr *src, + struct mg_l2addr *dst, uint8_t *frame) { + (void) dst; + return l2_ppp_header(proto, pppoe_header(MG_TCPIP_L2PROTO_PPPoE_SESS, 0, s_id, + src, dst, frame)); +} + +// Calculate FCS/CRC for PPP frames. Could be implemented faster using lookup +// tables. +static uint16_t fcs_do(uint8_t *frame, size_t len) { + uint32_t fcs = 0xffff; + unsigned int j; + for (j = 0; j < len; j++) { + unsigned int i; + uint8_t x = frame[j]; + for (i = 0; i < 8; i++) { + fcs = ((fcs ^ x) & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1; + x >>= 1; + } + } + return (uint16_t) (fcs & 0xffff); +} + +size_t mg_l2_ppp_footer(size_t len, uint8_t *cur) { + uint16_t crc; + uint8_t *frame; + len += sizeof(struct ppp) + sizeof(struct hdlc_); + frame = cur - len; + crc = fcs_do(frame, len); + *cur++ = (uint8_t) ~crc; // add CRC, note the byte order + *cur++ = (uint8_t) (~crc >> 8); + // there is no len field in PPP + return len + 2; +} + +extern size_t mg_l2_eth_footer(size_t len, uint8_t *cur); + +static size_t pppoe_footer(size_t len, uint8_t *cur) { + struct pppoe *pppoe = (struct pppoe *) (cur - len - sizeof(struct pppoe)); + pppoe->len = mg_htons((uint16_t) len); + return mg_l2_eth_footer(PDIFF(pppoe, cur), cur); +} + +size_t mg_l2_pppoe_footer(size_t len, uint8_t *cur) { + return pppoe_footer(len + sizeof(struct ppp), cur); +} + +size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len); + +static uint8_t *ppp_tx_frame_header(struct mg_tcpip_if *ifp, uint16_t proto) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + if (ifp->l2type == MG_TCPIP_L2_PPP) + return ppp_header(proto, hdlc_header(l2p)); + return ppp_header(proto, pppoe_header(MG_TCPIP_L2PROTO_PPPoE_SESS, 0, s_id, + (struct mg_l2addr *) ifp->mac, + (struct mg_l2addr *) ifp->gwmac, l2p)); +} +static size_t ppp_tx_frame_footer(struct mg_tcpip_if *ifp, size_t len, + uint8_t *cur) { + return mg_l2_driver_output(ifp, ifp->l2type == MG_TCPIP_L2_PPPoE + ? mg_l2_pppoe_footer(len, cur) + : mg_l2_ppp_footer(len, cur)); +} + +// Transmit a single PPP frame for the given protocol +static size_t ppp_tx_frame(struct mg_tcpip_if *ifp, uint16_t proto, + uint8_t *data, size_t datasz) { + uint8_t *pay = ppp_tx_frame_header(ifp, proto); + memcpy(pay, data, datasz); + return ppp_tx_frame_footer(ifp, datasz, pay + datasz); +} + +static void ppp_handle_lcp(struct mg_tcpip_if *ifp, uint8_t *lcpp, + size_t lcpsz) { + uint8_t id; + uint16_t len; + struct lcp *lcp = (struct lcp *) lcpp; + if (lcpsz < sizeof(*lcp)) return; + id = lcp->id; + len = mg_ntohs(lcp->len); + switch (lcp->code) { + case MG_PPP_LCP_CFG_REQ: { + if (len == sizeof(*lcp)) { + MG_DEBUG(("LCP config request of %d bytes, acknowledging...", len)); + lcp->code = MG_PPP_LCP_CFG_ACK; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + lcp->code = MG_PPP_LCP_CFG_REQ; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + } else { + MG_DEBUG(("LCP config request of %d bytes, rejecting...", len)); + lcp->code = MG_PPP_LCP_CFG_REJECT; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + } + } break; + case MG_PPP_LCP_CFG_ACK: + s_link = true; + 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...")); + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, ack, sizeof(ack)); + s_link = false; + } break; + case MG_PPP_LCP_ECHO_REQ: // RFC-1661 5.8: must respond + MG_DEBUG(("LCP echo request of %d bytes, replying...", len)); + lcp->code = MG_PPP_LCP_ECHO_REPLY; + ppp_tx_frame(ifp, MG_PPP_PROTO_LCP, lcpp, len); + break; + } +} + +static bool find_opt(const uint8_t opt, const uint8_t optlen, + const uint8_t *opts, size_t optslen, uint8_t *dest) { + uint8_t *p = (uint8_t *) opts; + while (optslen >= 2) { // parse options for requested one + if (p[1] > optslen) return false; // truncated / malformed + if (p[0] == opt && p[1] == optlen) { + memcpy(dest, p + 2, optlen - 2); + return true; + } + optslen -= p[1]; + p += p[1]; + } + return false; +} + +static void ppp_handle_ipcp(struct mg_tcpip_if *ifp, uint8_t *ipcpp, + size_t ipcpsz) { + uint16_t len; + uint8_t id; + struct ipcp *ipcp = (struct ipcp *) ipcpp; + uint8_t req[] = { + MG_PPP_IPCP_REQ, 0, 0, 10, MG_PPP_IPCP_IPADDR, 6, 0, 0, 0, 0}; + if (ipcpsz < sizeof(*ipcp)) return; + id = ipcp->id; + len = mg_ntohs(ipcp->len); + switch (ipcp->code) { + case MG_PPP_IPCP_REQ: + MG_VERBOSE(("got IPCP config request, acknowledging...")); + if (len >= 10 && + find_opt(MG_PPP_IPCP_IPADDR, 6, (const uint8_t *) (ipcp + 1), + len - sizeof(*ipcp), (uint8_t *) &ifp->gw)) + MG_DEBUG(("IPCP ack, GW IP: %M", mg_print_ip4, &ifp->gw)); + ipcp->code = MG_PPP_IPCP_ACK; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, ipcpp, len); + req[1] = id; + memcpy(req + 6, &ifp->ip, 4); // Request config IP address or 0.0.0.0 + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, req, sizeof(req)); + break; + case MG_PPP_IPCP_ACK: + // Our peer accepted our static IP address (unlikely if 0.0.0.0) + MG_VERBOSE(("got IPCP config ack")); + ifp->mask = 0xffffffff; // send to gw + ifp->state = MG_TCPIP_STATE_IP; + ifp->gw_ready = true; + break; + case MG_PPP_IPCP_NACK: + MG_VERBOSE(("got IPCP config nack")); + // NACK contains our "suggested" IP address, use it + if (len >= 10 && + find_opt(MG_PPP_IPCP_IPADDR, 6, (const uint8_t *) (ipcp + 1), + len - sizeof(*ipcp), (uint8_t *) &ifp->ip)) { + ifp->mask = 0xffffffff; // send to gw + ifp->state = MG_TCPIP_STATE_IP; + ifp->gw_ready = true; + MG_DEBUG(("IPCP ack, IP: %M", mg_print_ip4, &ifp->ip)); + ipcp->code = MG_PPP_IPCP_REQ; + ppp_tx_frame(ifp, MG_PPP_PROTO_IPCP, ipcpp, len); + } + break; + case MG_PPP_IPCP_REJECT: + MG_ERROR(("Peer rejected our IP address, need to properly set ifp->ip")); + break; + } +} + +#if MG_ENABLE_IPV6 +#endif + +static bool ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + struct ppp *ppp = (struct ppp *) pay->buf; + if (pay->len < sizeof(*ppp)) return false; // Truncated + pay->buf = (char *) (ppp + 1); + pay->len = pay->len - sizeof(*ppp); + switch (mg_ntohs(ppp->proto)) { + case MG_PPP_PROTO_LCP: + ppp_handle_lcp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IPCP: + if (s_link) ppp_handle_ipcp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IP: + if (!s_link) return false; + MG_VERBOSE(("got IP packet of %d bytes", pay->len)); + *proto = MG_TCPIP_L2PROTO_IPV4; + break; +#if 0 && MG_ENABLE_IPV6 + case MG_PPP_PROTO_IPV6CP: + if (s_link) ppp_handle_ipv6cp(ifp, (uint8_t *) pay->buf, pay->len); + return false; + case MG_PPP_PROTO_IPV6: + if (!s_link) return false; + MG_VERBOSE(("got IPv6 packet of %d bytes", pay->len)); + *proto = MG_TCPIP_L2PROTO_IPV6; + break; +#endif + default: { + struct lcp rej; + uint8_t *p; + size_t msglen; + MG_DEBUG(("unknown %u-byte PPP frame with proto 0x%04x:", + pay->len + sizeof(*ppp), mg_ntohs(ppp->proto))); + if (mg_log_level >= MG_LL_DEBUG) mg_hexdump(ppp, sizeof(*ppp) + 20); + if (!s_link) return false; // RFC-1661 5.7: must reject on link up + if (pay->len > (size_t) (ifp->mtu - 20)) + pay->len = (size_t) (ifp->mtu - 20); // truncate to some safe limit + rej.code = MG_PPP_LCP_REJECT; + mg_random(&rej.id, sizeof(rej.id)); + msglen = pay->len + sizeof(*ppp); + rej.len = mg_htons((uint16_t) (msglen + sizeof(rej))); + p = ppp_tx_frame_header(ifp, MG_PPP_PROTO_LCP); + memmove(p, &rej, sizeof(rej)); // LCP reject + memmove(p + sizeof(rej), ppp, msglen); // rejected PPP message + ppp_tx_frame_footer(ifp, msglen + sizeof(rej), p + msglen + sizeof(rej)); + } + return false; + } + (void) raw; + return true; } bool mg_l2_ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, struct mg_str *pay, struct mg_str *raw) { -#if 0 -if (ppp->addr == MG_PPP_ADDR && ppp->ctrl == MG_PPP_CTRL) { - code = ntohs(ppp->proto); - payload = (uint8_t *) (ppp + 1); -} else { // Address-and-Control-Field-Compressed PPP header - uint16_t *cppp = (uint16_t *) ppp; - code = ntohs(*cppp); - payload = (uint8_t *) (cppp + 1); -} -#endif - *pay = *raw; - *proto = MG_TCPIP_L2PROTO_IPV4; - (void) ifp; - return true; + struct hdlc_ *hdlc = (struct hdlc_ *) raw->buf; + if (raw->len < sizeof(*hdlc) + 2) return false; // Truncated + if (hdlc->addr == MG_PPP_ADDR && hdlc->ctrl == MG_PPP_CTRL) { + pay->buf = (char *) (hdlc + 1); + pay->len = raw->len - sizeof(*hdlc) - 2; + } else { // Address-and-Control-Field-Compressed PPP header + pay->buf = (char *) raw->buf; + pay->len = raw->len - 2; + } + return ppp_rx(ifp, proto, pay, raw); } +extern struct mg_l2addr s_mapip; + struct mg_l2addr *mg_l2_ppp_getaddr(uint8_t *frame) { (void) frame; return &s_mapip; // bogus } -extern struct mg_l2addr s_mapip; - struct mg_l2addr *mg_l2_ppp_mapip(enum mg_l2addrtype addrtype, struct mg_addr *addr) { (void) addrtype; @@ -112,4 +434,98 @@ uint8_t mg_l2_ppp_ip6put(struct mg_l2addr *addr, uint8_t *opts) { } #endif +// Transmit a single PPPoE frame for the discovery phase +static size_t pppoe_tx_frame(struct mg_tcpip_if *ifp, uint8_t code, uint16_t id, + uint8_t *data, size_t datasz, + struct mg_l2addr *dst) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + uint8_t *p = pppoe_header(MG_TCPIP_L2PROTO_PPPoE_DISC, code, id, + (struct mg_l2addr *) ifp->mac, dst, l2p); + memmove(p, data, datasz); + return mg_l2_driver_output(ifp, pppoe_footer(datasz, p + datasz)); +} + +extern struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype, struct mg_addr *); + +bool mg_l2_pppoe_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) { + if (expired_1000ms && s_state == MG_PPPoE_ST_DISC && + ifp->state == MG_TCPIP_STATE_LINK_UP) { + uint16_t tags[2]; + tags[0] = mg_htons(0x0101); // Service Request + tags[1] = mg_htons(0x0000); // Any + pppoe_tx_frame(ifp, MG_PPPoE_PADI, 0, (uint8_t *) tags, sizeof(tags), + mg_l2_eth_mapip(MG_TCPIP_L2ADDR_BCAST, NULL)); + MG_DEBUG(("Sent PADI")); + } else if (expired_1000ms && (s_state != MG_PPPoE_ST_SESS || + ifp->state == MG_TCPIP_STATE_DOWN)) { + s_state = MG_PPPoE_ST_DISC; + } + return mg_l2_ppp_poll(ifp, expired_1000ms); +} + +extern bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw); +extern struct mg_l2addr *mg_l2_eth_getaddr(uint8_t *); + +bool mg_l2_pppoe_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + enum mg_l2proto eth_proto; // raw is handled by eth + struct pppoe *pppoe; + if (!mg_l2_eth_rx(ifp, ð_proto, pay, raw)) return false; + pppoe = (struct pppoe *) pay->buf; // here we handle pay, not raw + if (pay->len < sizeof(*pppoe)) return false; // Truncated + if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_DISC) { + MG_VERBOSE(("PPPoE_DISC")); + if (s_state == MG_PPPoE_ST_DISC && pppoe->code == MG_PPPoE_PADO && + pppoe->id == 0) { + uint16_t tags[2]; + bool has_cookie = false; + size_t len = pay->len - sizeof(*pppoe); + uint8_t *p = (uint8_t *) (pppoe + 1); + uint16_t taglen; + while (len >= 4) { // parse tags for a possible AC-Cookie + uint16_t curtag = *((uint16_t *) p); + taglen = mg_ntohs(*(((uint16_t *) p) + 1)); + if (taglen > len - 4) return false; // truncated / malformed + if (curtag == mg_htons(0x0104)) { + has_cookie = true; + break; + } + len -= 4 + taglen; + p += 4 + taglen; + } + tags[0] = mg_htons(0x0101); // Service Request + tags[1] = mg_htons(0x0000); // Any + if (has_cookie) { // copy tags to before AC-Cookie tag in rx buffer + memcpy(p -= sizeof(tags), tags, sizeof(tags)); // point to all tags + taglen += 4 + sizeof(tags); // account for tags + } else { + p = (uint8_t *) tags; + taglen = sizeof(tags); + } + pppoe_tx_frame(ifp, MG_PPPoE_PADR, 0, p, taglen, + mg_l2_eth_getaddr((uint8_t *) raw->buf)); + MG_DEBUG(("Sent PADR")); + s_state = MG_PPPoE_ST_REQ; + } else if (s_state == MG_PPPoE_ST_REQ && pppoe->code == MG_PPPoE_PADS) { + s_id = pppoe->id; + memcpy(&ifp->gwmac, mg_l2_eth_getaddr((uint8_t *) raw->buf)->addr.mac, 6); + MG_DEBUG(("PPPoE session 0x%04x started", mg_ntohs(s_id))); + s_state = MG_PPPoE_ST_SESS; + } else if (s_state == MG_PPPoE_ST_SESS && pppoe->code == MG_PPPoE_PADT && + pppoe->id == s_id) { + MG_ERROR(("Got PADT")); + s_id = 0; + s_link = false; + s_state = MG_PPPoE_ST_DISC; + } + } else if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_SESS && + s_state == MG_PPPoE_ST_SESS) { + pay->buf = (char *) (pppoe + 1); + pay->len = pay->len - sizeof(*pppoe); + return ppp_rx(ifp, proto, pay, raw); + } + return false; +} + #endif diff --git a/src/net_builtin.c b/src/net_builtin.c index be8349cd..26529d05 100644 --- a/src/net_builtin.c +++ b/src/net_builtin.c @@ -191,11 +191,10 @@ struct pkt { }; // L2 API -void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, - uint16_t *framesize); +void mg_l2_init(struct mg_tcpip_if *ifp); uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, uint8_t *dst, uint8_t *frame); -size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *frame); +size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *cur); bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, struct mg_str *pay, struct mg_str *raw); uint8_t *mg_l2_getaddr(enum mg_l2type type, uint8_t *frame); @@ -208,6 +207,7 @@ bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, uint8_t len); uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts); #endif +bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms); static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) { #if MG_ENABLE_PROFILE @@ -263,9 +263,9 @@ static uint16_t pcsum(void *d, void *p, size_t plen) { uint32_t sum; struct ip *ip = (struct ip *) d; #if defined(__DCC__) - volatile /* Makes PPC & Diab4.3 happy */ + volatile /* Makes PPC & Diab4.3 happy */ #endif - struct pseudoip pip; + struct pseudoip pip; pip.src = ip->src; pip.dst = ip->dst; pip.zero = 0; @@ -294,9 +294,9 @@ static uint16_t p6csum(void *d, void *p, size_t plen) { uint32_t sum; struct ip6 *ip6 = (struct ip6 *) d; #if defined(__DCC__) - volatile /* Makes PPC & Diab4.3 happy */ + volatile /* Makes PPC & Diab4.3 happy */ #endif - struct pseudoip6 pip6; + struct pseudoip6 pip6; pip6.src[0] = ip6->src[0], pip6.src[1] = ip6->src[1]; pip6.dst[0] = ip6->dst[0], pip6.dst[1] = ip6->dst[1]; pip6.zero[0] = 0, pip6.zero[1] = 0, pip6.zero[2] = 0; @@ -376,17 +376,22 @@ void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) { arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip; memcpy(arp->sha, ifp->mac, sizeof(arp->sha)); if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha)); - driver_output(ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, arp + 1), l2p)); + driver_output(ifp, + mg_l2_footer(ifp->l2type, sizeof(*arp), (uint8_t *) (arp + 1))); } static void onstatechange(struct mg_tcpip_if *ifp) { + if (ifp->state == MG_TCPIP_STATE_IP && + (ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE)) + ifp->state = MG_TCPIP_STATE_READY; if (ifp->state == MG_TCPIP_STATE_READY) { MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip)); MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw)); - if (ifp->l2type == MG_TCPIP_L2_ETH) // TODO(): print other l2 + if (ifp->l2type == MG_TCPIP_L2_ETH || + ifp->l2type == MG_TCPIP_L2_PPPoE) // TODO(): print other l2 MG_INFO((" MAC: %M", mg_print_mac, ifp->mac)); } else if (ifp->state == MG_TCPIP_STATE_IP) { - if (ifp->gw != 0) + if (ifp->gw != 0 && ifp->l2type == MG_TCPIP_L2_ETH) mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request } else if (ifp->state == MG_TCPIP_STATE_UP) { srand((unsigned int) mg_millis()); @@ -423,7 +428,7 @@ static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, struct mg_addr *ip_src, struct mg_addr *ip_dst, const void *buf, size_t len) { - uint8_t *l2p = (uint8_t *) ifp->tx.buf; + uint8_t *l3p; size_t l2_len; struct ip *ip = NULL; struct udp *udp; @@ -434,6 +439,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, len + sizeof(struct udp)); udp = (struct udp *) (ip6 + 1); l2_len = sizeof(*ip6) + sizeof(*udp) + len; + l3p = (uint8_t *) ip6; } else #endif { @@ -441,6 +447,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, len + sizeof(struct udp)); udp = (struct udp *) (ip + 1); l2_len = sizeof(*ip) + sizeof(*udp) + len; + l3p = (uint8_t *) ip; } udp->sport = ip_src->port; udp->dport = ip_dst->port; @@ -455,7 +462,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, { udp->csum = pcsum(ip, udp, sizeof(*udp) + len); } - l2_len = mg_l2_footer(ifp->l2type, l2_len, l2p); + l2_len = mg_l2_footer(ifp->l2type, l2_len, l3p + l2_len); return (driver_output(ifp, l2_len) == l2_len); } @@ -584,7 +591,8 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { arp->spa = ifp->ip; MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac, ifp->mac)); - driver_output(ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, arp + 1), l2p)); + driver_output( + ifp, mg_l2_footer(ifp->l2type, sizeof(*arp), (uint8_t *) (arp + 1))); } else if (pkt->arp->op == mg_htons(2)) { if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; // MG_VERBOSE(("ARP resp from %M", mg_print_ip4, &pkt->arp->spa)); @@ -611,7 +619,6 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { } static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - uint8_t *l2p = (uint8_t *) ifp->tx.buf; size_t plen = pkt->pay.len; if (!icmpcsum_ok(pkt->icmp, sizeof(struct icmp) + plen)) return; if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { @@ -632,7 +639,8 @@ static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { memset(icmp, 0, sizeof(*icmp)); // Set csum, type, code to 0 memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); - driver_output(ifp, mg_l2_footer(ifp->l2type, hlen + plen, l2p)); + driver_output(ifp, mg_l2_footer(ifp->l2type, hlen + plen, + (uint8_t *) ip + hlen + plen)); } } @@ -772,7 +780,6 @@ static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src, uint64_t *ip_dst, uint8_t type, uint8_t code, const void *buf, size_t len) { - uint8_t *l2p = (uint8_t *) ifp->tx.buf; struct ip6 *ip6; struct icmp6 *icmp6; ip6 = tx_ip6(ifp, l2_dst, 58, ip_src, ip_dst, sizeof(*icmp6) + len); @@ -783,8 +790,9 @@ static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src, memcpy(icmp6 + 1, buf, len); // Copy payload icmp6->csum = 0; // RFC-4443 2.3, RFC-8200 8.1 icmp6->csum = p6csum(ip6, icmp6, sizeof(*icmp6) + len); - driver_output( - ifp, mg_l2_footer(ifp->l2type, sizeof(*ip6) + sizeof(*icmp6) + len, l2p)); + driver_output(ifp, + mg_l2_footer(ifp->l2type, sizeof(*ip6) + sizeof(*icmp6) + len, + (uint8_t *) (ip6 + 1) + sizeof(*icmp6) + len)); } // Neighbor Discovery Protocol, RFC-4861 @@ -1047,7 +1055,7 @@ static void onstate6change(struct mg_tcpip_if *ifp) { } else if (ifp->state6 == MG_TCPIP_STATE_UP) { MG_INFO(("IP: %M", mg_print_ip6, &ifp->ip6ll)); } - if (ifp->state6 != MG_TCPIP_STATE_UP && ifp->state6 != MG_TCPIP_STATE_DOWN) + if (ifp->state6 > MG_TCPIP_STATE_UP) mg_tcpip_call(ifp, MG_TCPIP_EV_ST6_CHG, &ifp->state6); } #endif @@ -1134,7 +1142,7 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, struct mg_addr *ip_src, struct mg_addr *ip_dst, uint8_t flags, uint32_t seq, uint32_t ack, const void *buf, size_t len) { - uint8_t *l2p = (uint8_t *) ifp->tx.buf; + uint8_t *l3p; struct ip *ip = NULL; struct tcp *tcp; uint16_t opts[4 / 2]; @@ -1161,11 +1169,13 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, ip6 = tx_ip6(ifp, l2_dst, 6, ip_src->addr.ip6, ip_dst->addr.ip6, hlen + len); tcp = (struct tcp *) (ip6 + 1); + l3p = (uint8_t *) ip6; } else #endif { ip = tx_ip(ifp, l2_dst, 6, ip_src->addr.ip4, ip_dst->addr.ip4, hlen + len); tcp = (struct tcp *) (ip + 1); + l3p = (uint8_t *) ip; } memset(tcp, 0, sizeof(*tcp)); memmove(tcp + 1, opts, hlen - sizeof(*tcp)); // copy opts if any @@ -1188,7 +1198,8 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, MG_VERBOSE(("TCP %M -> %M fl %x len %u", mg_print_ip_port, ip_src, mg_print_ip_port, ip_dst, tcp->flags, len)); return driver_output( - ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, tcp) + hlen + len, l2p)); + ifp, mg_l2_footer(ifp->l2type, PDIFF(l3p, (uint8_t *) tcp + hlen + len), + (uint8_t *) tcp + hlen + len)); } static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt, @@ -1771,6 +1782,7 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { pkt.raw.len = len; pkt.l2 = (uint8_t *) pkt.raw.buf; if (!mg_l2_rx(ifp, &proto, &pkt.pay, &pkt.raw)) return; + if (ifp->state < MG_TCPIP_STATE_UP) return; // discard while L2 is not up if (proto == MG_TCPIP_L2PROTO_ARP) { pkt.arp = (struct arp *) (pkt.pay.buf); if (pkt.pay.len < sizeof(*pkt.arp)) return; // Truncated @@ -1786,7 +1798,7 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { } static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) { - if (ifp->state == MG_TCPIP_STATE_DOWN) return; + if (ifp->state < MG_TCPIP_STATE_UP) return; // DHCP RFC-2131 (4.4) if (ifp->enable_dhcp_client && s1) { if (ifp->state == MG_TCPIP_STATE_UP) { @@ -1806,11 +1818,13 @@ static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) { } } } -static void mg_ip_link(struct mg_tcpip_if *ifp, bool up) { - bool current = ifp->state != MG_TCPIP_STATE_DOWN; - if (!up && ifp->enable_dhcp_client) ifp->ip = 0; - if (up != current) { // link state has changed - ifp->state = up == false ? MG_TCPIP_STATE_DOWN +static void mg_ip_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) { + bool cur_drv = (ifp->state != MG_TCPIP_STATE_DOWN); + bool cur_l2 = (ifp->state >= MG_TCPIP_STATE_UP); + if (!l2_up && ifp->enable_dhcp_client) ifp->ip = 0; + if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed + ifp->state = !drv_up ? MG_TCPIP_STATE_DOWN + : !l2_up ? MG_TCPIP_STATE_LINK_UP : ifp->enable_dhcp_client || ifp->ip == 0 ? MG_TCPIP_STATE_UP : MG_TCPIP_STATE_IP; onstatechange(ifp); @@ -1823,15 +1837,17 @@ static void mg_ip_link(struct mg_tcpip_if *ifp, bool up) { #if MG_ENABLE_IPV6 static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) { - if (ifp->state6 == MG_TCPIP_STATE_DOWN) return; + if (ifp->state6 < MG_TCPIP_STATE_UP) return; if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP) tx_ndp_rs(ifp); } -static void mg_ip6_link(struct mg_tcpip_if *ifp, bool up) { - bool current = ifp->state6 != MG_TCPIP_STATE_DOWN; +static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) { + bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN); + bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP); if (!up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0; - if (up != current) { // link state has changed - ifp->state6 = !up ? MG_TCPIP_STATE_DOWN + if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed + ifp->state6 = !drv_up ? MG_TCPIP_STATE_DOWN + : !l2_up ? MG_TCPIP_STATE_LINK_UP : ifp->enable_slaac || ifp->ip6[0] == 0 ? MG_TCPIP_STATE_UP : MG_TCPIP_STATE_IP; onstate6change(ifp); @@ -1843,7 +1859,7 @@ static void mg_ip6_link(struct mg_tcpip_if *ifp, bool up) { } #else #define mg_ip6_poll(x, y) -#define mg_ip6_link(x, y) +#define mg_ip6_link(x, y, z) #endif static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { @@ -1853,7 +1869,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { if (expired_1000ms) { #if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS - const char *names[] = {"down", "up", "req", "ip", "ready"}; + const char *names[] = {"down", "lnk up", "lnk rdy", "req", "ip", "ready"}; size_t max = sizeof(names) / sizeof(char *); unsigned int state = ifp->state >= max ? max - 1 : ifp->state; MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", names[state], @@ -1886,14 +1902,15 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { tx_ndp_ns(ifp, ifp->gw6, NULL); // retry GW hwaddr resolution #endif - // poll driver - if (ifp->driver->poll) { - bool up = ifp->driver->poll(ifp, expired_1000ms); - // Handle physical interface up/down status, ifp->state rules over state6 - if (expired_1000ms) { - mg_ip_link(ifp, up); // Handle IPv4 - mg_ip6_link(ifp, up); // Handle IPv6 - if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down")); + { // poll driver and let L2 do its work, if any + // Handle physical interface up/down status + bool drv_up, l2_up; + drv_up = ifp->driver->poll ? ifp->driver->poll(ifp, expired_1000ms) : true; + l2_up = mg_l2_poll(ifp, expired_1000ms); // Handle L2 up/down link status; + if (expired_1000ms) { // ifp->state rules over state6 + mg_ip_link(ifp, drv_up, l2_up); // Handle IPv4 + mg_ip6_link(ifp, drv_up, l2_up); // Handle IPv6 + if (ifp->state < MG_TCPIP_STATE_UP) MG_ERROR(("Network is down")); mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL); } } @@ -1901,8 +1918,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { mg_ip_poll(ifp, expired_1000ms); // Handle IPv4 mg_ip6_poll(ifp, expired_1000ms); // Handle IPv6 - if (ifp->state == MG_TCPIP_STATE_DOWN) return; - // Read data from the network + // Read data from the network (mg_tcpip_rx() will discard for us) if (ifp->driver->rx != NULL) { // Simple polling driver, returns one frame size_t len = ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp); @@ -1918,6 +1934,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { mg_queue_del(&ifp->recv_queue, len); } } + if (ifp->state < MG_TCPIP_STATE_UP) return; // need to let L2 do its job // Process timeouts for (c = ifp->mgr->conns; c != NULL; c = c->next) { @@ -1970,7 +1987,7 @@ void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) { void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { // If L2 address is not set, make a random one; fill MTU - mg_l2_init(ifp->l2type, ifp->mac, &ifp->mtu, &ifp->framesize); + mg_l2_init(ifp); if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip" memcpy(ifp->dhcp_name, "mip", 4); @@ -1988,7 +2005,9 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { mgr->ifp = ifp; ifp->mgr = mgr; mgr->extraconnsize = sizeof(struct connstate); - if (ifp->ip == 0) ifp->enable_dhcp_client = true; + if (ifp->ip == 0 && ifp->l2type != MG_TCPIP_L2_PPP && + ifp->l2type != MG_TCPIP_L2_PPPoE) + ifp->enable_dhcp_client = true; mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535 ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from // MG_EPHEMERAL_PORT_BASE to 65535 diff --git a/src/net_builtin.h b/src/net_builtin.h index d04b150c..00f8770d 100644 --- a/src/net_builtin.h +++ b/src/net_builtin.h @@ -82,10 +82,11 @@ struct mg_tcpip_if { volatile uint32_t nerr; // Number of driver errors uint8_t state; // Current link and IPv4 state #define MG_TCPIP_STATE_DOWN 0 // Interface is down -#define MG_TCPIP_STATE_UP 1 // Interface is up -#define MG_TCPIP_STATE_REQ 2 // Interface is up, DHCP REQUESTING state -#define MG_TCPIP_STATE_IP 3 // Interface is up and has an IP assigned -#define MG_TCPIP_STATE_READY 4 // Interface has fully come up, ready to work +#define MG_TCPIP_STATE_LINK_UP 1 // Driver reports link state is up +#define MG_TCPIP_STATE_UP 2 // Interface is up --> LINK_READY +#define MG_TCPIP_STATE_REQ 3 // Interface is up, DHCP REQUESTING state +#define MG_TCPIP_STATE_IP 4 // Interface is up and has an IP assigned +#define MG_TCPIP_STATE_READY 5 // Interface has fully come up, ready to work bool gw_ready; // We've got a hw address for the router #if MG_ENABLE_IPV6 uint8_t gw6mac[sizeof(struct mg_l2addr)]; // IPV6 Router's hw address @@ -111,12 +112,12 @@ 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; 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; +extern struct mg_tcpip_driver mg_tcpip_driver_atcmd; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { diff --git a/tutorials/mqtt/mqtt-client/arduino/sim800-mqtt/sim800-mqtt.ino b/tutorials/mqtt/mqtt-client/arduino/sim800-mqtt/sim800-mqtt.ino index 425a5b4e..5d913671 100644 --- a/tutorials/mqtt/mqtt-client/arduino/sim800-mqtt/sim800-mqtt.ino +++ b/tutorials/mqtt/mqtt-client/arduino/sim800-mqtt/sim800-mqtt.ino @@ -106,14 +106,14 @@ void setup() { mg_log_set(MG_LL_DEBUG); // Set debug log level mg_log_set_fn([](char c, void *) { Serial.print(c); }, NULL); // Log serial - mif.driver = &mg_tcpip_driver_ppp; // Initialise built-in TCP/IP stack - mif.driver_data = &driver_data; // with the cellular driver + mif.driver = &mg_tcpip_driver_atcmd; // Initialise built-in TCP/IP stack + mif.l2type = MG_TCPIP_L2_PPP; // with the cellular driver + mif.driver_data = &driver_data; driver_data.script = script; driver_data.reset = [](void *) { digitalWrite(RST_PIN, LOW); delay(1); digitalWrite(RST_PIN, HIGH); }, driver_data.tx = [](void *, uint8_t c) { ModemSerial.write(c); }, driver_data.rx = [](void *) { return ModemSerial.available() ? ModemSerial.read() : -1; }, mg_tcpip_init(&mgr, &mif); - mif.enable_dhcp_client = false; } void loop() { diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/Makefile b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/Makefile new file mode 100644 index 00000000..33d0c965 --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/Makefile @@ -0,0 +1,37 @@ +CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion +CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion +CFLAGS += -g3 -Os -ffunction-sections -fdata-sections +CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_f7/Include +CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_EXTRA) +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nano.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map + +SOURCES = main.c syscalls.c sysinit.c +SOURCES += cmsis_f7/Source/Templates/gcc/startup_stm32f746xx.s # ST startup file. Compiler-dependent! + +# Mongoose options are defined in mongoose_config.h +SOURCES += mongoose.c + +ifeq ($(OS),Windows_NT) + RM = cmd /C del /Q /F /S +else + RM = rm -rf +endif + +all build example: firmware.bin + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + +firmware.elf: cmsis_core cmsis_f7 $(SOURCES) hal.h link.ld + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@ + +flash: firmware.bin + st-flash --reset write $< 0x8000000 + +cmsis_core: # ARM CMSIS core headers + git clone --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ +cmsis_f7: # ST CMSIS headers for STM32F7 series + git clone --depth 1 -b v1.2.8 https://github.com/STMicroelectronics/cmsis_device_f7 $@ + +clean: + $(RM) firmware.* cmsis_core cmsis_f7 diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/README.md b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/README.md new file mode 100644 index 00000000..9628d8ed --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/README.md @@ -0,0 +1,87 @@ +# PPPoE on NUCLEO-F746ZG + +This example shows a device with a very simple web server (the NUCLEO board) that connects to a PPPoE server. The device web server can then be accessed from the computer running the web server. For more complete scenarios, proper server setting and routing would be needed, thing a network admin will do. + +## Configure and start your PPPoE server + +This is just a test example in Linux, we use _pppoe-server_ for this, as it is easy to configure and serves our purpose. For production scenarios, ask your network admin. + +```sh +$ cat /etc/ppp/pppoe-server-options +# PPP options for the PPPoE server +# LIC: GPL +#require-pap +#login +lcp-echo-interval 10 +lcp-echo-failure 2 +$ sudo pppoe-server -F -I yourethernetinterface +``` + +## Flash your NUCLEO + +```sh +$ make +$ make flash +``` + +## Check and test + +```sh +$ ifconfig ppp0 +ppp0: flags=4305 mtu 1492 + inet 10.0.0.1 netmask 255.255.255.255 destination 10.67.15.1 + ppp txqueuelen 3 (Point-to-Point Protocol) + RX packets 3 bytes 36 (36.0 B) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 28 bytes 7003 (6.8 KiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + +$ route +Kernel IP routing table +Destination Gateway Genmask Flags Metric Ref Use Iface +default _gateway 0.0.0.0 UG 0 0 0 virbr0 +10.67.15.1 0.0.0.0 255.255.255.255 UH 0 0 0 ppp0 + +$ curl http://10.67.15.1/ +hi +$ +``` + +```sh +$ minicom -D /dev/ttyACM0 +``` + +```text +0 3 mongoose.c:5306:mg_mgr_init MG_IO_SIZE: 512, TLS: builtin +6 2 main.c:48:main Init TCP/IP stack ... +b 3 mongoose.c:27645:guess_mdc_cr HCLK: 216000000, CR: 4 +11 2 mongoose.c:25494:mg_phy_init PHY ID: 0x07 0xc131 (LAN87x) +18 3 mongoose.c:5223:mg_listen 1 0 tcp://0.0.0.0:80 +1d 2 main.c:57:main Init done, starting main loop ... +7e7 3 mongoose.c:27757:mg_tcpip_driv Link is 100M full-duplex +7ec 3 mongoose.c:4035:mg_l2_pppoe_po Sent PADI +7f1 1 mongoose.c:7220:mg_tcpip_poll Network is down +7f6 3 mongoose.c:4083:mg_l2_pppoe_rx Sent PADR +7fa 3 mongoose.c:4088:mg_l2_pppoe_rx PPPoE session 0x0001 started +bcf 3 mongoose.c:27757:mg_tcpip_driv Link is 100M full-duplex +bd4 1 mongoose.c:7220:mg_tcpip_poll Network is down +c0d 3 mongoose.c:3821:ppp_handle_lcp LCP config request of 14 bytes, rejecting... +c15 3 mongoose.c:3815:ppp_handle_lcp LCP config request of 4 bytes, acknowledging... +c1e 3 mongoose.c:3836:ppp_handle_lcp LCP echo request of 8 bytes, replying... +c26 3 mongoose.c:3943:ppp_rx unknown 40-byte PPP frame with proto 0x80fd: +0000 80 fd 01 01 00 0f 1a 04 78 00 18 04 78 00 15 03 ........x...x... +0010 2f 00 00 00 00 00 /..... +c3a 3 mongoose.c:3874:ppp_handle_ipc IPCP ack, GW IP: 10.0.0.1 +c40 3 mongoose.c:3897:ppp_handle_ipc IPCP ack, IP: 10.67.15.1 +fb7 2 mongoose.c:5712:onstatechange READY, IP: 10.67.15.1 +fbc 2 mongoose.c:5713:onstatechange GW: 10.0.0.1 +fc1 2 mongoose.c:5715:onstatechange MAC: 02:35:42:56:22:2e +33d3 3 mongoose.c:3836:ppp_handle_lcp LCP echo request of 8 bytes, replying... +5b8b 3 mongoose.c:3836:ppp_handle_lcp LCP echo request of 8 bytes, replying... +8348 3 mongoose.c:3836:ppp_handle_lcp LCP echo request of 8 bytes, replying... +aafa 3 mongoose.c:3836:ppp_handle_lcp LCP echo request of 8 bytes, replying... +d2b7 3 mongoose.c:3836:ppp_handle_lcp LCP echo request of 8 bytes, replying... +fa74 3 mongoose.c:3836:ppp_handle_lcp LCP echo request of 8 bytes, replying... +12196 3 mongoose.c:6583:accept_conn 2 accepted 10.0.0.1:50846 +1219d 3 mongoose.c:5159:mg_close_conn 2 0 closed +``` diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/hal.h b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/hal.h new file mode 100644 index 00000000..26c44af6 --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/hal.h @@ -0,0 +1,175 @@ +// Copyright (c) 2022 Cesanta Software Limited +// All rights reserved +// https://www.st.com/resource/en/reference_manual/dm00124865-stm32f75xxx-and-stm32f74xxx-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf + +#pragma once + +#include + +#include +#include +#include +#include + +#define BIT(x) (1UL << (x)) +#define SETBITS(R, CLEARMASK, SETMASK) (R) = ((R) & ~(CLEARMASK)) | (SETMASK) +#define PIN(bank, num) ((((bank) - 'A') << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +#define LED1 PIN('B', 0) // On-board LED pin (green) +#define LED2 PIN('B', 7) // On-board LED pin (blue) +#define LED3 PIN('B', 14) // On-board LED pin (red) + +#define LED LED2 // Use blue LED for blinking + +/* System clock +5.3.3: APB1 clock <= 54MHz; APB2 clock <= 108MHz +3.3.2, Table 5: configure flash latency (WS) in accordance to clock freq +38.4: The AHB clock frequency must be at least 25 MHz when the Ethernet +controller is used */ +enum { APB1_PRE = 5 /* AHB clock / 4 */, APB2_PRE = 4 /* AHB clock / 2 */ }; +enum { PLL_HSI = 16, PLL_M = 8, PLL_N = 216, PLL_P = 2, PLL_Q = 9 }; +#define FLASH_LATENCY 7 +#define SYS_FREQUENCY ((PLL_HSI * PLL_N / PLL_M / PLL_P) * 1000000) // Core 216 MHz, USB 48 MHz +#define APB2_FREQUENCY (SYS_FREQUENCY / (BIT(APB2_PRE - 3))) +#define APB1_FREQUENCY (SYS_FREQUENCY / (BIT(APB1_PRE - 3))) + +static inline void spin(volatile uint32_t count) { + while (count--) (void) 0; +} + +enum { GPIO_MODE_INPUT, GPIO_MODE_OUTPUT, GPIO_MODE_AF, GPIO_MODE_ANALOG }; +enum { GPIO_OTYPE_PUSH_PULL, GPIO_OTYPE_OPEN_DRAIN }; +enum { GPIO_SPEED_LOW, GPIO_SPEED_MEDIUM, GPIO_SPEED_HIGH, GPIO_SPEED_INSANE }; +enum { GPIO_PULL_NONE, GPIO_PULL_UP, GPIO_PULL_DOWN }; +#define GPIO(N) ((GPIO_TypeDef *) (0x40020000 + 0x400 * (N))) + +static GPIO_TypeDef *gpio_bank(uint16_t pin) { return GPIO(PINBANK(pin)); } +static inline void gpio_toggle(uint16_t pin) { + GPIO_TypeDef *gpio = gpio_bank(pin); + uint32_t mask = BIT(PINNO(pin)); + gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0); +} +static inline int gpio_read(uint16_t pin) { + return gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0; +} +static inline void gpio_write(uint16_t pin, bool val) { + GPIO_TypeDef *gpio = gpio_bank(pin); + gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16); +} +static inline void gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + uint8_t speed, uint8_t pull, uint8_t af) { + GPIO_TypeDef *gpio = gpio_bank(pin); + uint8_t n = (uint8_t) (PINNO(pin)); + RCC->AHB1ENR |= BIT(PINBANK(pin)); // Enable GPIO clock + SETBITS(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n); + SETBITS(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2)); + SETBITS(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2)); + SETBITS(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4), + ((uint32_t) af) << ((n & 7) * 4)); + SETBITS(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2)); +} +static inline void gpio_input(uint16_t pin) { + gpio_init(pin, GPIO_MODE_INPUT, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_HIGH, + GPIO_PULL_NONE, 0); +} +static inline void gpio_output(uint16_t pin) { + gpio_init(pin, GPIO_MODE_OUTPUT, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_HIGH, + GPIO_PULL_NONE, 0); +} + +static inline void irq_exti_attach(uint16_t pin) { + uint8_t bank = (uint8_t) (PINBANK(pin)), n = (uint8_t) (PINNO(pin)); + SYSCFG->EXTICR[n / 4] &= ~(15UL << ((n % 4) * 4)); + SYSCFG->EXTICR[n / 4] |= (uint32_t) (bank << ((n % 4) * 4)); + EXTI->IMR |= BIT(n); + EXTI->RTSR |= BIT(n); + EXTI->FTSR |= BIT(n); + int irqvec = n < 5 ? 6 + n : n < 10 ? 23 : 40; // IRQ vector index, 10.1.2 + NVIC_SetPriority(irqvec, 3); + NVIC_EnableIRQ(irqvec); +} + +#ifndef UART_DEBUG +#define UART_DEBUG USART3 +#endif + +static inline void uart_init(USART_TypeDef *uart, unsigned long baud) { + // https://www.st.com/resource/en/datasheet/stm32f746zg.pdf + uint8_t af = 7; // Alternate function + uint16_t rx = 0, tx = 0; // pins + uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1 + + if (uart == USART1) freq = APB2_FREQUENCY, RCC->APB2ENR |= BIT(4); + if (uart == USART2) freq = APB1_FREQUENCY, RCC->APB1ENR |= BIT(17); + if (uart == USART3) freq = APB1_FREQUENCY, RCC->APB1ENR |= BIT(18); + + if (uart == USART1) tx = PIN('A', 9), rx = PIN('A', 10); + if (uart == USART2) tx = PIN('A', 2), rx = PIN('A', 3); + if (uart == USART3) tx = PIN('D', 8), rx = PIN('D', 9); + + gpio_init(tx, GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_HIGH, 0, af); + gpio_init(rx, GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_HIGH, 0, af); + uart->CR1 = 0; // Disable this UART + uart->BRR = freq / baud; // Set baud rate + uart->CR1 |= BIT(0) | BIT(2) | BIT(3); // Set UE, RE, TE +} +static inline void uart_write_byte(USART_TypeDef *uart, uint8_t byte) { + uart->TDR = byte; + while ((uart->ISR & BIT(7)) == 0) spin(1); +} +static inline void uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) { + while (len-- > 0) uart_write_byte(uart, *(uint8_t *) buf++); +} +static inline int uart_read_ready(USART_TypeDef *uart) { + return uart->ISR & BIT(5); // If RXNE bit is set, data is ready +} +static inline uint8_t uart_read_byte(USART_TypeDef *uart) { + return (uint8_t) (uart->RDR & 255); +} + +static inline void rng_init(void) { + RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; + RNG->CR |= RNG_CR_RNGEN; +} +static inline uint32_t rng_read(void) { + while ((RNG->SR & RNG_SR_DRDY) == 0) (void) 0; + return RNG->DR; +} + +// Hw pull-ups on PHY RXD0,1,DV to enable autonegotiation +static inline void ethernet_init(void) { + // Initialise Ethernet. Enable MAC GPIO pins, see + // https://www.farnell.com/datasheets/2014265.pdf section 6.10 + uint16_t pins[] = { + PIN('A', 1), /* ETH_REF_CLK */ \ + PIN('A', 2), /* ETH_MDIO */ \ + PIN('A', 7), /* ETH_CRS_DV */ \ + PIN('B', 13), /* ETH_TXD1 */ \ + PIN('C', 1), /* ETH_MDC */ \ + PIN('C', 4), /* ETH_RXD0 */ \ + PIN('C', 5), /* ETH_RXD1 */ \ + PIN('G', 11), /* ETH_TX_EN */ \ + PIN('G', 13) /* ETH_TXD0 */ + }; + for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + gpio_init(pins[i], GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_INSANE, + GPIO_PULL_NONE, 11); // 11 is the Ethernet function + } + NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler + SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; // Use RMII. Goes first! + RCC->AHB1ENR |= + RCC_AHB1ENR_ETHMACEN | RCC_AHB1ENR_ETHMACTXEN | RCC_AHB1ENR_ETHMACRXEN; +} + + + +#define UUID ((uint8_t *) UID_BASE) // Unique 96-bit chip ID. TRM 41.1 + +// Helper macro for MAC generation +#define GENERATE_LOCALLY_ADMINISTERED_MAC() \ + { \ + 2, UUID[0] ^ UUID[1], UUID[2] ^ UUID[3], UUID[4] ^ UUID[5], \ + UUID[6] ^ UUID[7] ^ UUID[8], UUID[9] ^ UUID[10] ^ UUID[11] \ + } diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/link.ld b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/link.ld new file mode 100644 index 00000000..330baddd --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/link.ld @@ -0,0 +1,29 @@ +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08000000, LENGTH = 1024k + sram(rwx) : ORIGIN = 0x20000000, LENGTH = 320k +} +_estack = ORIGIN(sram) + LENGTH(sram); /* stack points to end of SRAM */ + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) } > flash + + .data : { + _sdata = .; /* for init_ram() */ + *(.first_data) + *(.data SORT(.data.*)) + _edata = .; /* for init_ram() */ + } > sram AT > flash + _sidata = LOADADDR(.data); + + .bss : { + _sbss = .; /* for init_ram() */ + *(.bss SORT(.bss.*) COMMON) + _ebss = .; /* for init_ram() */ + } > sram + + . = ALIGN(8); + _end = .; /* for cmsis_gcc.h and init_ram() */ +} diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/main.c b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/main.c new file mode 100644 index 00000000..c0dae9f8 --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/main.c @@ -0,0 +1,63 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" +#include "mongoose.h" + +static void blink_cb(void *arg) { // Blink periodically + gpio_toggle(LED); + (void) arg; +} + +static volatile uint64_t s_ticks; // Milliseconds since boot +void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms + s_ticks++; +} + +uint64_t mg_millis(void) { // Let Mongoose use our uptime function + return s_ticks; // Return number of milliseconds since boot +} + +bool mg_random(void *buf, size_t len) { // Use on-board RNG + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; +} + + +static void fn(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + (void) hm; + mg_http_reply(c, 200, "", "hi\n"); + } +} + +int main(void) { + gpio_output(LED); // Setup blue LED + uart_init(UART_DEBUG, 115200); // Initialise debug printf + ethernet_init(); + + struct mg_mgr mgr; // Initialise + mg_mgr_init(&mgr); // Mongoose event manager + mg_log_set(MG_LL_DEBUG); // Set log level + + + MG_INFO(("Init TCP/IP stack ...")); + struct mg_tcpip_if mif = {.mac = GENERATE_LOCALLY_ADMINISTERED_MAC(), + .driver = &mg_tcpip_driver_stm32f, + .l2type = MG_TCPIP_L2_PPPoE, + .recv_queue.size = 4096}; + mg_tcpip_init(&mgr, &mif); + mg_timer_add(&mgr, 500, MG_TIMER_REPEAT, blink_cb, &mgr); + mg_http_listen(&mgr, "tcp://0.0.0.0:80", fn, &mgr); + + MG_INFO(("Init done, starting main loop ...")); + for (;;) { + mg_mgr_poll(&mgr, 0); + } + + return 0; +} diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose.c b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose.c new file mode 120000 index 00000000..5e522bbc --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose.c @@ -0,0 +1 @@ +../../../mongoose.c \ No newline at end of file diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose.h b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose.h new file mode 120000 index 00000000..ee4ac823 --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose.h @@ -0,0 +1 @@ +../../../mongoose.h \ No newline at end of file diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose_config.h b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose_config.h new file mode 100644 index 00000000..3a1d52ef --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/mongoose_config.h @@ -0,0 +1,13 @@ +#pragma once + +// See https://mongoose.ws/documentation/#build-options +#define MG_ARCH MG_ARCH_ARMGCC +#define MG_TLS MG_TLS_BUILTIN + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_DRIVER_STM32F 1 +#define MG_ENABLE_TCPIP_DRIVER_INIT 0 + + diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/syscalls.c b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/syscalls.c new file mode 100644 index 00000000..ac27f093 --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/syscalls.c @@ -0,0 +1,88 @@ +#include + +#include "hal.h" + +int _fstat(int fd, struct stat *st) { + if (fd < 0) return -1; + st->st_mode = S_IFCHR; + return 0; +} + +void *_sbrk(int incr) { + extern char _end; + static unsigned char *heap = NULL; + unsigned char *prev_heap; + unsigned char x = 0, *heap_end = (unsigned char *)((size_t) &x - 512); + (void) x; + if (heap == NULL) heap = (unsigned char *) &_end; + prev_heap = heap; + if (heap + incr > heap_end) return (void *) -1; + heap += incr; + return prev_heap; +} + +int _open(const char *path) { + (void) path; + return -1; +} + +int _close(int fd) { + (void) fd; + return -1; +} + +int _isatty(int fd) { + (void) fd; + return 1; +} + +int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +int _getpid(void) { + return -1; +} + +int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + if (fd == 1) uart_write_buf(UART_DEBUG, ptr, (size_t) len); + return -1; +} + +int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +int _unlink(const char *a) { + (void) a; + return -1; +} + +int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +int mkdir(const char *path, mode_t mode) { + (void) path, (void) mode; + return -1; +} + +void _init(void) {} diff --git a/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/sysinit.c b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/sysinit.c new file mode 100644 index 00000000..0cbc3a45 --- /dev/null +++ b/tutorials/stm32/nucleo-f746zg-make-baremetal-builtin-pppoe/sysinit.c @@ -0,0 +1,30 @@ +// Copyright (c) 2023 Cesanta Software Limited +// All rights reserved +// +// This file contains essentials required by the CMSIS: +// uint32_t SystemCoreClock - holds the system core clock value +// SystemInit() - initialises the system, e.g. sets up clocks + +#include "hal.h" + +uint32_t SystemCoreClock = SYS_FREQUENCY; + +void SystemInit(void) { // Called automatically by startup code + SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU + asm("DSB"); + asm("ISB"); + FLASH->ACR |= FLASH_LATENCY | BIT(8) | BIT(9); // Flash latency, prefetch + RCC->PLLCFGR &= ~((BIT(17) - 1) | (0xF << 24)); // Clear PLL multipliers + RCC->PLLCFGR |= (((PLL_P - 2) / 2) & 3) << 16; // Set PLL_P + RCC->PLLCFGR |= PLL_M | (PLL_N << 6) | (PLL_Q << 24); // Set PLL_M _N _Q + RCC->CR |= BIT(24); // Enable PLL + while ((RCC->CR & BIT(25)) == 0) spin(1); // Wait until done + RCC->CFGR = (APB1_PRE << 10) | (APB2_PRE << 13); // Set prescalers + RCC->CFGR |= 2; // Set clock source to PLL + while ((RCC->CFGR & 12) == 0) spin(1); // Wait until done + + RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // Enable SYSCFG + rng_init(); // Initialise random number generator + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms + +}