diff --git a/mongoose.c b/mongoose.c index d06865ae..8ff57852 100644 --- a/mongoose.c +++ b/mongoose.c @@ -116,6 +116,544 @@ fail: return 0; } +#ifdef MG_ENABLE_LINES +#line 1 "src/bsd.c" +#endif + + +#if MG_ENABLE_BSD_SOCKETS + +struct mg_bsd_sock { + void *t; // opaque transport handle + int fd; + int domain, type, proto; + bool nonblock; + struct sockaddr_in addr; // bind address + struct sockaddr_in peer; // peer address (after accept/connect) + struct mg_bsd_sock *next; +}; + +#define MG_BSD_FD_BASE 17777 + +// static struct mg_mgr *s_mgr; +static struct mg_bsd_sock *s_socks; + +static struct mg_bsd_sock *get(int fd) { + if (fd < MG_BSD_FD_BASE) return NULL; + for (struct mg_bsd_sock *s = s_socks; s; s = s->next) + if (s->fd == fd) return s; + return NULL; +} + +static int alloc_sock(struct mg_bsd_sock *s) { + for (int fd = MG_BSD_FD_BASE; ; fd++) { + if (get(fd) == NULL) { s->fd = fd; break; } + } + s->next = s_socks; + s_socks = s; + return s->fd; +} + +static void release_sock(int fd) { + struct mg_bsd_sock **p = &s_socks; + while (*p && (*p)->fd != fd) p = &(*p)->next; + if (*p) *p = (*p)->next; +} + + +int socket(int domain, int type, int proto) { + struct mg_bsd_sock *s = (struct mg_bsd_sock *) calloc(1, sizeof(*s)); + if (!s) { errno = ENOMEM; return -1; } + s->t = mg_bsd_transport_new(domain, type, proto); + if (!s->t || alloc_sock(s) < 0) { free(s); errno = ENOMEM; return -1; } + s->domain = domain; s->type = type; s->proto = proto; + return s->fd; +} + +int bind(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + memcpy(&s->addr, addr, len < sizeof(s->addr) ? len : sizeof(s->addr)); + return 0; +} + +int listen(int fd, int backlog) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + (void) backlog; + return mg_bsd_transport_listen(s->t, &s->addr); +} + +int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *ls = get(fd); + if (!ls) return -1; + struct sockaddr_in peer = {0}; + void *t = mg_bsd_transport_accept(ls->t, &peer, ls->nonblock); + if (!t) { if (ls->nonblock) errno = EAGAIN; return -1; } + struct mg_bsd_sock *ns = (struct mg_bsd_sock *) calloc(1, sizeof(*ns)); + if (!ns || alloc_sock(ns) < 0) { mg_bsd_transport_free(t); free(ns); errno = ENOMEM; return -1; } + ns->t = t; ns->domain = ls->domain; ns->type = ls->type; ns->peer = peer; + if (addr && addrlen) { + size_t sz = sizeof(peer) < *addrlen ? sizeof(peer) : *addrlen; + memcpy(addr, &peer, sz); + *addrlen = (socklen_t) sizeof(peer); + } + return ns->fd; +} + +int connect(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + (void) len; + return mg_bsd_transport_connect(s->t, (const struct sockaddr_in *) addr, s->nonblock); +} + +ssize_t send(int fd, const void *buf, size_t len, int flags) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + return mg_bsd_transport_send(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); +} + +ssize_t recv(int fd, void *buf, size_t len, int flags) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + return mg_bsd_transport_recv(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); +} + +ssize_t sendto(int fd, const void *buf, size_t len, int flags, + const struct sockaddr *dest, socklen_t addrlen) { + (void) dest; (void) addrlen; + return send(fd, buf, len, flags); +} + +ssize_t recvfrom(int fd, void *buf, size_t len, int flags, + struct sockaddr *src, socklen_t *addrlen) { + ssize_t n = recv(fd, buf, len, flags); + if (n > 0 && src && addrlen) { + struct mg_bsd_sock *s = get(fd); + if (s) { + size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen; + memcpy(src, &s->peer, sz); + *addrlen = (socklen_t) sizeof(s->peer); + } + } + return n; +} + +ssize_t write(int fd, const void *buf, size_t len) { return send(fd, buf, len, 0); } +ssize_t read(int fd, void *buf, size_t len) { return recv(fd, buf, len, 0); } + +int close(int fd) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + mg_bsd_transport_close(s->t); + release_sock(fd); + free(s); + return 0; +} + +int shutdown(int fd, int how) { (void) how; return close(fd); } + +int fcntl(int fd, int cmd, int arg) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + if (cmd == F_GETFL) return s->nonblock ? O_NONBLOCK : 0; + if (cmd == F_SETFL) { s->nonblock = (arg & O_NONBLOCK) != 0; return 0; } + return -1; +} + +int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { + (void) fd; (void) level; (void) optname; (void) optval; (void) optlen; + return 0; +} + +int getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen) { + (void) fd; (void) level; (void) optname; + if (optval && optlen && *optlen >= sizeof(int)) { *(int *) optval = 0; *optlen = sizeof(int); } + return 0; +} + +int getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + size_t sz = sizeof(s->addr) < *addrlen ? sizeof(s->addr) : *addrlen; + memcpy(addr, &s->addr, sz); + *addrlen = (socklen_t) sz; + return 0; +} + +int getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *s = get(fd); + if (!s) { errno = ENOTCONN; return -1; } + size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen; + memcpy(addr, &s->peer, sz); + *addrlen = (socklen_t) sz; + return 0; +} + +// select/poll: not implemented for queue-based backend +int select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *tv) { + (void) nfds; (void) r; (void) w; (void) e; (void) tv; + return 0; +} + +int poll(struct pollfd *fds, unsigned int nfds, int timeout) { + (void) fds; (void) nfds; (void) timeout; + return 0; +} + +// DNS stubs (overridden in the FreeRTOS backend below) +#if !MG_ENABLE_FREERTOS +struct hostent *gethostbyname(const char *name) { (void) name; return NULL; } +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + (void) node; (void) service; (void) hints; (void) res; + return -1; +} +void freeaddrinfo(struct addrinfo *res) { (void) res; } +#endif + +int inet_pton(int af, const char *src, void *dst) { + struct mg_addr a = {0}; + if (af == AF_INET && mg_aton(mg_str_s(src), &a)) { memcpy(dst, &a.addr.ip4, 4); return 1; } + return 0; +} + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { + if (af == AF_INET && size >= 16) { + const uint8_t *ip = (const uint8_t *) src; + snprintf(dst, size, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return dst; + } + return NULL; +} + +in_addr_t inet_addr(const char *cp) { + struct mg_addr a = {0}; + return mg_aton(mg_str_s(cp), &a) ? a.addr.ip4 : (in_addr_t) -1; +} + +static char s_ntoa_buf[16]; +char *inet_ntoa(struct in_addr in) { + const uint8_t *ip = (const uint8_t *) &in.s_addr; + snprintf(s_ntoa_buf, sizeof(s_ntoa_buf), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return s_ntoa_buf; +} + +#ifdef MG_ENABLE_BSD_PROTOTYPES +uint16_t htons(uint16_t n) { return mg_htons(n); } +uint16_t ntohs(uint16_t n) { return mg_htons(n); } +uint32_t htonl(uint32_t n) { return mg_htonl(n); } +uint32_t ntohl(uint32_t n) { return mg_htonl(n); } +#endif + +// ============================================================ +// FreeRTOS + Mongoose transport backend +// ============================================================ +#if MG_ENABLE_FREERTOS + +#include + +#ifndef MG_BSD_CHUNK_SIZE +#define MG_BSD_CHUNK_SIZE 256 +#endif +#ifndef MG_BSD_Q_DEPTH +#define MG_BSD_Q_DEPTH 4 +#endif + +struct mg_bsd_chunk { uint8_t data[MG_BSD_CHUNK_SIZE]; uint16_t len; }; + +struct mg_xport { + struct mg_connection *c; // Mongoose connection, task1-only + QueueHandle_t recv_q; // task1 writes on MG_EV_READ, task2 reads in recv() + QueueHandle_t send_q; // task2 writes in send(), task1 drains on MG_EV_POLL + QueueHandle_t accept_q; // task1 writes on MG_EV_ACCEPT, task2 reads in accept() + struct sockaddr_in peer; + bool closed; + TaskHandle_t connect_waiter; // task blocked in connect(), woken by MG_EV_CONNECT + int *connect_result; // where to store 0/−1 connect outcome +}; + +enum mg_bsd_cmd_op { BSD_CMD_LISTEN, BSD_CMD_CLOSE, BSD_CMD_CONNECT, BSD_CMD_RESOLVE }; +struct mg_bsd_cmd { + enum mg_bsd_cmd_op type; + struct mg_xport *x; + char url[64]; + TaskHandle_t caller; + int *result; +}; + +static QueueHandle_t s_cmd_q; + +// Single-slot DNS resolve state (not reentrant, sufficient for demos) +static struct { struct mg_addr addr; bool done, error; TaskHandle_t caller; } s_resolve; + +static void resolve_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_RESOLVE) { s_resolve.addr = c->rem; s_resolve.done = true; c->is_closing = 1; } + else if ((ev == MG_EV_ERROR || ev == MG_EV_CLOSE) && !s_resolve.done) s_resolve.error = true; + if ((s_resolve.done || s_resolve.error) && s_resolve.caller) { + TaskHandle_t h = s_resolve.caller; + s_resolve.caller = NULL; // prevent double-notify on subsequent MG_EV_CLOSE + xTaskNotifyGive(h); + } + (void) ev_data; +} + +// Allocate transport for an accepted connection (recv+send queues only) +static struct mg_xport *xport_alloc(void) { + struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); + if (!x) return NULL; + x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->recv_q || !x->send_q) { mg_bsd_transport_free(x); return NULL; } + return x; +} + +static void xport_ev(struct mg_connection *c, int ev, void *ev_data) { + struct mg_xport *x = (struct mg_xport *) c->fn_data; + if (!x) return; + + if (ev == MG_EV_ACCEPT) { + // c is the new accepted connection; x is the listening transport + struct mg_xport *nx = xport_alloc(); + if (!nx) { c->is_closing = 1; return; } + nx->c = c; + nx->peer.sin_family = AF_INET; + nx->peer.sin_port = c->rem.port; + memcpy(&nx->peer.sin_addr, &c->rem.addr.ip4, 4); + c->fn_data = nx; + xQueueSend(x->accept_q, &nx, 0); + } else if (ev == MG_EV_READ && x->recv_q) { + // Drain c->recv into recv_q in fixed-size chunks; task1 owns c->recv + size_t off = 0; + while (off < c->recv.len) { + struct mg_bsd_chunk chunk; + size_t n = c->recv.len - off; + if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; + memcpy(chunk.data, c->recv.buf + off, n); + chunk.len = (uint16_t) n; + xQueueSend(x->recv_q, &chunk, portMAX_DELAY); + off += n; + } + mg_iobuf_del(&c->recv, 0, c->recv.len); + } else if (ev == MG_EV_POLL && x->send_q) { + // Drain send_q → mg_send(); task1 owns c + struct mg_bsd_chunk chunk; + while (xQueueReceive(x->send_q, &chunk, 0) == pdTRUE) + mg_send(c, chunk.data, chunk.len); + } else if (ev == MG_EV_CONNECT) { + // Outgoing connection established: wake the task blocked in connect() + if (x->connect_waiter) { + if (x->connect_result) *x->connect_result = 0; + TaskHandle_t h = x->connect_waiter; + x->connect_waiter = NULL; x->connect_result = NULL; + xTaskNotifyGive(h); + } + } else if (ev == MG_EV_CLOSE) { + x->c = NULL; x->closed = true; c->fn_data = NULL; + // If connect() is still waiting, signal failure + if (x->connect_waiter) { + if (x->connect_result) *x->connect_result = -1; + TaskHandle_t h = x->connect_waiter; + x->connect_waiter = NULL; x->connect_result = NULL; + xTaskNotifyGive(h); + } + if (x->recv_q) { struct mg_bsd_chunk eof = {.len = 0}; xQueueSend(x->recv_q, &eof, 0); } + if (x->accept_q) { struct mg_xport *nil = NULL; xQueueSend(x->accept_q, &nil, 0); } + } + (void) ev_data; +} + +void mg_bsd_init(void) { + s_cmd_q = xQueueCreate(8, sizeof(struct mg_bsd_cmd)); +} + +void mg_bsd_poll(struct mg_mgr *mgr) { + struct mg_bsd_cmd cmd; + if (s_cmd_q == NULL) return; + while (xQueueReceive(s_cmd_q, &cmd, 0) == pdTRUE) { + bool notify = true; + if (cmd.type == BSD_CMD_LISTEN) { + struct mg_connection *c = mg_listen(mgr, cmd.url, xport_ev, cmd.x); + cmd.x->c = c; + *cmd.result = c ? 0 : -1; + } else if (cmd.type == BSD_CMD_CLOSE) { + if (cmd.x->c) { + cmd.x->c->fn_data = NULL; + cmd.x->c->is_draining = 1; + } + *cmd.result = 0; + } else if (cmd.type == BSD_CMD_CONNECT) { + cmd.x->connect_waiter = cmd.caller; + cmd.x->connect_result = cmd.result; + struct mg_connection *c = mg_connect(mgr, cmd.url, xport_ev, cmd.x); + cmd.x->c = c; + if (!c) { *cmd.result = -1; cmd.x->connect_waiter = NULL; cmd.x->connect_result = NULL; } + else notify = false; // xport_ev notifies when connected or on error + } else if (cmd.type == BSD_CMD_RESOLVE) { + s_resolve.done = s_resolve.error = false; + s_resolve.caller = cmd.caller; + char url[80]; + snprintf(url, sizeof(url), "tcp://%s:0", cmd.url); + if (!mg_connect(mgr, url, resolve_cb, NULL)) s_resolve.error = true; + else notify = false; // resolve_cb notifies when done + } + if (notify) xTaskNotifyGive(cmd.caller); + } +} + +void *mg_bsd_transport_new(int domain, int type, int proto) { + (void) domain; (void) type; (void) proto; + // For socket() calls: allocate accept_q only; recv/send added when needed + struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); + if (!x) return NULL; + x->accept_q = xQueueCreate(MG_BSD_BACKLOG, sizeof(struct mg_xport *)); + if (!x->accept_q) { free(x); return NULL; } + return x; +} + +void mg_bsd_transport_free(void *t) { + struct mg_xport *x = (struct mg_xport *) t; + if (!x) return; + if (x->recv_q) vQueueDelete(x->recv_q); + if (x->send_q) vQueueDelete(x->send_q); + if (x->accept_q) vQueueDelete(x->accept_q); + free(x); +} + +int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr) { + struct mg_xport *x = (struct mg_xport *) t; + int result = -1; + struct mg_bsd_cmd cmd = {BSD_CMD_LISTEN, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + snprintf(cmd.url, sizeof(cmd.url), "tcp://0.0.0.0:%d", mg_ntohs(addr->sin_port)); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + return result; +} + +void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + struct mg_xport *nx = NULL; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + if (xQueueReceive(x->accept_q, &nx, ticks) != pdTRUE || !nx) return NULL; + if (peer) *peer = nx->peer; + return nx; +} + +ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + struct mg_bsd_chunk chunk; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + if (xQueueReceive(x->recv_q, &chunk, ticks) != pdTRUE) { + errno = EAGAIN; + return -1; + } + if (chunk.len == 0) return 0; // EOF + size_t n = chunk.len < len ? chunk.len : len; + memcpy(buf, chunk.data, n); + return (ssize_t) n; +} + +ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + if (x->closed) return -1; + size_t sent = 0; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + while (sent < len) { + struct mg_bsd_chunk chunk; + size_t n = len - sent; + if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; + memcpy(chunk.data, (const uint8_t *) buf + sent, n); + chunk.len = (uint16_t) n; + if (xQueueSend(x->send_q, &chunk, ticks) != pdTRUE) break; + sent += n; + } + return sent > 0 ? (ssize_t) sent : (errno = EAGAIN, -1); +} + +int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + (void) nonblock; + if (!x->recv_q) x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->send_q) x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->recv_q || !x->send_q) { errno = ENOMEM; return -1; } + int result = -1; + struct mg_bsd_cmd cmd = {BSD_CMD_CONNECT, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + uint8_t *ip = (uint8_t *) &addr->sin_addr.s_addr; + snprintf(cmd.url, sizeof(cmd.url), "tcp://%d.%d.%d.%d:%d", + ip[0], ip[1], ip[2], ip[3], mg_ntohs(addr->sin_port)); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + return result; +} + +// gethostbyname: resolve via Mongoose DNS (not reentrant) +static struct hostent s_hostent; +static char *s_h_aliases[1]; +static char *s_h_addr_list[2]; +static uint32_t s_h_addr; +static char s_h_name[64]; + +struct hostent *gethostbyname(const char *name) { + struct mg_bsd_cmd cmd = {BSD_CMD_RESOLVE, NULL, {0}, xTaskGetCurrentTaskHandle(), NULL}; + snprintf(cmd.url, sizeof(cmd.url), "%s", name); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (s_resolve.error) return NULL; + s_h_addr = s_resolve.addr.addr.ip4; + s_h_addr_list[0] = (char *) &s_h_addr; + s_h_addr_list[1] = NULL; + s_h_aliases[0] = NULL; + snprintf(s_h_name, sizeof(s_h_name), "%s", name); + s_hostent.h_name = s_h_name; + s_hostent.h_aliases = s_h_aliases; + s_hostent.h_addrtype = AF_INET; + s_hostent.h_length = 4; + s_hostent.h_addr_list = s_h_addr_list; + return &s_hostent; +} + +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + struct hostent *h = gethostbyname(node); + if (!h) return -1; + struct addrinfo *ai = (struct addrinfo *) calloc(1, sizeof(*ai)); + struct sockaddr_in *sa = (struct sockaddr_in *) calloc(1, sizeof(*sa)); + if (!ai || !sa) { free(ai); free(sa); return -1; } + sa->sin_family = AF_INET; + memcpy(&sa->sin_addr, h->h_addr, 4); + if (service) sa->sin_port = htons((uint16_t) atoi(service)); + ai->ai_family = AF_INET; + ai->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; + ai->ai_addrlen = sizeof(*sa); + ai->ai_addr = (struct sockaddr *) sa; + *res = ai; + return 0; +} + +void freeaddrinfo(struct addrinfo *res) { + while (res) { + struct addrinfo *next = res->ai_next; + free(res->ai_addr); + free(res); + res = next; + } +} + +void mg_bsd_transport_close(void *t) { + struct mg_xport *x = (struct mg_xport *) t; + if (!x->closed && x->c) { + int result = 0; + struct mg_bsd_cmd cmd = {BSD_CMD_CLOSE, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + mg_bsd_transport_free(x); +} + +#endif // MG_ENABLE_FREERTOS +#endif // MG_ENABLE_BSD_SOCKETS + #ifdef MG_ENABLE_LINES #line 1 "src/dash.c" #endif @@ -8187,9 +8725,10 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { } s->twclosure = true; } else { - flags |= TH_FIN; + // Peer closed first: send ACK only, enter CLOSE_WAIT. + // The connection loop will call init_closure after pending send data + // is flushed, then send our FIN. c->is_draining = 1; - settmout(c, MIP_TTYPE_FIN); } tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, flags, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); diff --git a/mongoose.h b/mongoose.h index efb8d370..a5099c5c 100644 --- a/mongoose.h +++ b/mongoose.h @@ -905,6 +905,14 @@ struct timeval { #define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack #endif +#ifndef MG_ENABLE_BSD_SOCKETS +#define MG_ENABLE_BSD_SOCKETS 0 // BSD API support for built-in stack +#endif + +#ifndef MG_ENABLE_BSD_PROTOTYPES +#define MG_ENABLE_BSD_PROTOTYPES 1 +#endif + #ifndef MG_ENABLE_LWIP #define MG_ENABLE_LWIP 0 // lwIP network stack #endif @@ -3589,6 +3597,186 @@ struct mg_tcpip_spi { #endif + + + +#if MG_ENABLE_BSD_SOCKETS + +#ifndef MG_ENABLE_BSD_PROTOTYPES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +typedef unsigned int socklen_t; +typedef int ssize_t; +typedef uint32_t in_addr_t; +struct in_addr { in_addr_t s_addr; }; +struct in6_addr { uint8_t s6_addr[16]; }; +struct sockaddr { uint16_t sa_family; char sa_data[14]; }; +struct sockaddr_in { + uint16_t sin_family; + uint16_t sin_port; + struct in_addr sin_addr; + char sin_zero[8]; +}; +struct sockaddr_in6 { + uint16_t sin6_family; + uint16_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + uint32_t sin6_scope_id; +}; +struct sockaddr_storage { uint16_t ss_family; char __ss_pad[126]; }; +struct hostent { + char *h_name; + char **h_aliases; + int h_addrtype; + int h_length; + char **h_addr_list; +}; +#define h_addr h_addr_list[0] +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; +struct pollfd { int fd; short events; short revents; }; +#define AF_INET 2 +#define AF_INET6 10 +#define AF_UNSPEC 0 +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 +#define PF_UNSPEC AF_UNSPEC +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define IPPROTO_TCP 6 +#define IPPROTO_UDP 17 +#define INADDR_ANY 0 +#define INADDR_LOOPBACK 0x7f000001 +#define SOL_SOCKET 0xffff +#define SO_REUSEADDR 2 +#define SO_REUSEPORT 15 +#define SO_KEEPALIVE 9 +#define SO_ERROR 4 +#define SO_BROADCAST 6 +#define SO_RCVBUF 8 +#define SO_SNDBUF 7 +#define TCP_NODELAY 1 +#define MSG_DONTWAIT 0x40 +#define MSG_NOSIGNAL 0x4000 +#define MSG_PEEK 0x02 +#define F_GETFL 3 +#define F_SETFL 4 +#define O_NONBLOCK 0x0004 +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 +#define AI_PASSIVE 0x0001 +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 +#ifndef EAGAIN +#define EAGAIN 11 +#endif +#ifndef EWOULDBLOCK +#define EWOULDBLOCK EAGAIN +#endif +#ifndef EINPROGRESS +#define EINPROGRESS 36 +#endif +#ifndef ENOTCONN +#define ENOTCONN 107 +#endif +#ifndef ECONNREFUSED +#define ECONNREFUSED 111 +#endif +#ifndef ECONNRESET +#define ECONNRESET 104 +#endif +#ifndef EADDRINUSE +#define EADDRINUSE 98 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 110 +#endif +#define POLLIN 0x001 +#define POLLOUT 0x004 +#define POLLERR 0x008 +#define POLLHUP 0x010 +#define POLLNVAL 0x020 +uint16_t htons(uint16_t); +uint16_t ntohs(uint16_t); +uint32_t htonl(uint32_t); +uint32_t ntohl(uint32_t); +#endif // MG_ENABLE_BSD_PROTOTYPES + +#define closesocket(a) close(a) + +#ifndef MG_BSD_BACKLOG +#define MG_BSD_BACKLOG 5 +#endif + +#ifndef MG_BSD_CONNECT_TIMEOUT_MS +#define MG_BSD_CONNECT_TIMEOUT_MS 10000 +#endif + +// Mongoose-specific API +void mg_bsd_init(void); // must be called before socket() +void mg_bsd_poll(struct mg_mgr *); // process pending BSD commands + +// Transport backend: implement these OR define MG_ENABLE_FREERTOS for the built-in backend +void *mg_bsd_transport_new(int domain, int type, int proto); +void mg_bsd_transport_free(void *t); +int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr); +void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock); +ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock); +ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock); +int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock); +void mg_bsd_transport_close(void *t); + +// Standard BSD socket API +int socket(int, int, int); +int bind(int, const struct sockaddr *, socklen_t); +int listen(int, int); +int accept(int, struct sockaddr *, socklen_t *); +int connect(int, const struct sockaddr *, socklen_t); +ssize_t send(int, const void *, size_t, int); +ssize_t recv(int, void *, size_t, int); +ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); +ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *); +int close(int); +int shutdown(int, int); +int fcntl(int, int, int); +int setsockopt(int, int, int, const void *, socklen_t); +int getsockopt(int, int, int, void *, socklen_t *); +int getsockname(int, struct sockaddr *, socklen_t *); +int getpeername(int, struct sockaddr *, socklen_t *); +int select(int, fd_set *, fd_set *, fd_set *, struct timeval *); +int poll(struct pollfd *, unsigned int, int); +struct hostent *gethostbyname(const char *); +int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **); +void freeaddrinfo(struct addrinfo *); +int inet_pton(int, const char *, void *); +const char *inet_ntop(int, const void *, char *, socklen_t); +in_addr_t inet_addr(const char *); +char *inet_ntoa(struct in_addr); + +#endif // MG_ENABLE_BSD_SOCKETS + + struct mg_tcpip_driver_atcmd_data { void *usart; // Opaque UART/SPI descriptor void (*reset)(void *); // Modem hardware reset diff --git a/src/bsd.c b/src/bsd.c new file mode 100644 index 00000000..d975204f --- /dev/null +++ b/src/bsd.c @@ -0,0 +1,534 @@ +#include "bsd.h" + +#if MG_ENABLE_BSD_SOCKETS + +struct mg_bsd_sock { + void *t; // opaque transport handle + int fd; + int domain, type, proto; + bool nonblock; + struct sockaddr_in addr; // bind address + struct sockaddr_in peer; // peer address (after accept/connect) + struct mg_bsd_sock *next; +}; + +#define MG_BSD_FD_BASE 17777 + +// static struct mg_mgr *s_mgr; +static struct mg_bsd_sock *s_socks; + +static struct mg_bsd_sock *get(int fd) { + if (fd < MG_BSD_FD_BASE) return NULL; + for (struct mg_bsd_sock *s = s_socks; s; s = s->next) + if (s->fd == fd) return s; + return NULL; +} + +static int alloc_sock(struct mg_bsd_sock *s) { + for (int fd = MG_BSD_FD_BASE; ; fd++) { + if (get(fd) == NULL) { s->fd = fd; break; } + } + s->next = s_socks; + s_socks = s; + return s->fd; +} + +static void release_sock(int fd) { + struct mg_bsd_sock **p = &s_socks; + while (*p && (*p)->fd != fd) p = &(*p)->next; + if (*p) *p = (*p)->next; +} + + +int socket(int domain, int type, int proto) { + struct mg_bsd_sock *s = (struct mg_bsd_sock *) calloc(1, sizeof(*s)); + if (!s) { errno = ENOMEM; return -1; } + s->t = mg_bsd_transport_new(domain, type, proto); + if (!s->t || alloc_sock(s) < 0) { free(s); errno = ENOMEM; return -1; } + s->domain = domain; s->type = type; s->proto = proto; + return s->fd; +} + +int bind(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + memcpy(&s->addr, addr, len < sizeof(s->addr) ? len : sizeof(s->addr)); + return 0; +} + +int listen(int fd, int backlog) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + (void) backlog; + return mg_bsd_transport_listen(s->t, &s->addr); +} + +int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *ls = get(fd); + if (!ls) return -1; + struct sockaddr_in peer = {0}; + void *t = mg_bsd_transport_accept(ls->t, &peer, ls->nonblock); + if (!t) { if (ls->nonblock) errno = EAGAIN; return -1; } + struct mg_bsd_sock *ns = (struct mg_bsd_sock *) calloc(1, sizeof(*ns)); + if (!ns || alloc_sock(ns) < 0) { mg_bsd_transport_free(t); free(ns); errno = ENOMEM; return -1; } + ns->t = t; ns->domain = ls->domain; ns->type = ls->type; ns->peer = peer; + if (addr && addrlen) { + size_t sz = sizeof(peer) < *addrlen ? sizeof(peer) : *addrlen; + memcpy(addr, &peer, sz); + *addrlen = (socklen_t) sizeof(peer); + } + return ns->fd; +} + +int connect(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + (void) len; + return mg_bsd_transport_connect(s->t, (const struct sockaddr_in *) addr, s->nonblock); +} + +ssize_t send(int fd, const void *buf, size_t len, int flags) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + return mg_bsd_transport_send(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); +} + +ssize_t recv(int fd, void *buf, size_t len, int flags) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + return mg_bsd_transport_recv(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); +} + +ssize_t sendto(int fd, const void *buf, size_t len, int flags, + const struct sockaddr *dest, socklen_t addrlen) { + (void) dest; (void) addrlen; + return send(fd, buf, len, flags); +} + +ssize_t recvfrom(int fd, void *buf, size_t len, int flags, + struct sockaddr *src, socklen_t *addrlen) { + ssize_t n = recv(fd, buf, len, flags); + if (n > 0 && src && addrlen) { + struct mg_bsd_sock *s = get(fd); + if (s) { + size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen; + memcpy(src, &s->peer, sz); + *addrlen = (socklen_t) sizeof(s->peer); + } + } + return n; +} + +ssize_t write(int fd, const void *buf, size_t len) { return send(fd, buf, len, 0); } +ssize_t read(int fd, void *buf, size_t len) { return recv(fd, buf, len, 0); } + +int close(int fd) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + mg_bsd_transport_close(s->t); + release_sock(fd); + free(s); + return 0; +} + +int shutdown(int fd, int how) { (void) how; return close(fd); } + +int fcntl(int fd, int cmd, int arg) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + if (cmd == F_GETFL) return s->nonblock ? O_NONBLOCK : 0; + if (cmd == F_SETFL) { s->nonblock = (arg & O_NONBLOCK) != 0; return 0; } + return -1; +} + +int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { + (void) fd; (void) level; (void) optname; (void) optval; (void) optlen; + return 0; +} + +int getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen) { + (void) fd; (void) level; (void) optname; + if (optval && optlen && *optlen >= sizeof(int)) { *(int *) optval = 0; *optlen = sizeof(int); } + return 0; +} + +int getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *s = get(fd); + if (!s) return -1; + size_t sz = sizeof(s->addr) < *addrlen ? sizeof(s->addr) : *addrlen; + memcpy(addr, &s->addr, sz); + *addrlen = (socklen_t) sz; + return 0; +} + +int getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_sock *s = get(fd); + if (!s) { errno = ENOTCONN; return -1; } + size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen; + memcpy(addr, &s->peer, sz); + *addrlen = (socklen_t) sz; + return 0; +} + +// select/poll: not implemented for queue-based backend +int select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *tv) { + (void) nfds; (void) r; (void) w; (void) e; (void) tv; + return 0; +} + +int poll(struct pollfd *fds, unsigned int nfds, int timeout) { + (void) fds; (void) nfds; (void) timeout; + return 0; +} + +// DNS stubs (overridden in the FreeRTOS backend below) +#if !MG_ENABLE_FREERTOS +struct hostent *gethostbyname(const char *name) { (void) name; return NULL; } +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + (void) node; (void) service; (void) hints; (void) res; + return -1; +} +void freeaddrinfo(struct addrinfo *res) { (void) res; } +#endif + +int inet_pton(int af, const char *src, void *dst) { + struct mg_addr a = {0}; + if (af == AF_INET && mg_aton(mg_str_s(src), &a)) { memcpy(dst, &a.addr.ip4, 4); return 1; } + return 0; +} + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { + if (af == AF_INET && size >= 16) { + const uint8_t *ip = (const uint8_t *) src; + snprintf(dst, size, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return dst; + } + return NULL; +} + +in_addr_t inet_addr(const char *cp) { + struct mg_addr a = {0}; + return mg_aton(mg_str_s(cp), &a) ? a.addr.ip4 : (in_addr_t) -1; +} + +static char s_ntoa_buf[16]; +char *inet_ntoa(struct in_addr in) { + const uint8_t *ip = (const uint8_t *) &in.s_addr; + snprintf(s_ntoa_buf, sizeof(s_ntoa_buf), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return s_ntoa_buf; +} + +#ifdef MG_ENABLE_BSD_PROTOTYPES +uint16_t htons(uint16_t n) { return mg_htons(n); } +uint16_t ntohs(uint16_t n) { return mg_htons(n); } +uint32_t htonl(uint32_t n) { return mg_htonl(n); } +uint32_t ntohl(uint32_t n) { return mg_htonl(n); } +#endif + +// ============================================================ +// FreeRTOS + Mongoose transport backend +// ============================================================ +#if MG_ENABLE_FREERTOS + +#include + +#ifndef MG_BSD_CHUNK_SIZE +#define MG_BSD_CHUNK_SIZE 256 +#endif +#ifndef MG_BSD_Q_DEPTH +#define MG_BSD_Q_DEPTH 4 +#endif + +struct mg_bsd_chunk { uint8_t data[MG_BSD_CHUNK_SIZE]; uint16_t len; }; + +struct mg_xport { + struct mg_connection *c; // Mongoose connection, task1-only + QueueHandle_t recv_q; // task1 writes on MG_EV_READ, task2 reads in recv() + QueueHandle_t send_q; // task2 writes in send(), task1 drains on MG_EV_POLL + QueueHandle_t accept_q; // task1 writes on MG_EV_ACCEPT, task2 reads in accept() + struct sockaddr_in peer; + bool closed; + TaskHandle_t connect_waiter; // task blocked in connect(), woken by MG_EV_CONNECT + int *connect_result; // where to store 0/−1 connect outcome +}; + +enum mg_bsd_cmd_op { BSD_CMD_LISTEN, BSD_CMD_CLOSE, BSD_CMD_CONNECT, BSD_CMD_RESOLVE }; +struct mg_bsd_cmd { + enum mg_bsd_cmd_op type; + struct mg_xport *x; + char url[64]; + TaskHandle_t caller; + int *result; +}; + +static QueueHandle_t s_cmd_q; + +// Single-slot DNS resolve state (not reentrant, sufficient for demos) +static struct { struct mg_addr addr; bool done, error; TaskHandle_t caller; } s_resolve; + +static void resolve_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_RESOLVE) { s_resolve.addr = c->rem; s_resolve.done = true; c->is_closing = 1; } + else if ((ev == MG_EV_ERROR || ev == MG_EV_CLOSE) && !s_resolve.done) s_resolve.error = true; + if ((s_resolve.done || s_resolve.error) && s_resolve.caller) { + TaskHandle_t h = s_resolve.caller; + s_resolve.caller = NULL; // prevent double-notify on subsequent MG_EV_CLOSE + xTaskNotifyGive(h); + } + (void) ev_data; +} + +// Allocate transport for an accepted connection (recv+send queues only) +static struct mg_xport *xport_alloc(void) { + struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); + if (!x) return NULL; + x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->recv_q || !x->send_q) { mg_bsd_transport_free(x); return NULL; } + return x; +} + +static void xport_ev(struct mg_connection *c, int ev, void *ev_data) { + struct mg_xport *x = (struct mg_xport *) c->fn_data; + if (!x) return; + + if (ev == MG_EV_ACCEPT) { + // c is the new accepted connection; x is the listening transport + struct mg_xport *nx = xport_alloc(); + if (!nx) { c->is_closing = 1; return; } + nx->c = c; + nx->peer.sin_family = AF_INET; + nx->peer.sin_port = c->rem.port; + memcpy(&nx->peer.sin_addr, &c->rem.addr.ip4, 4); + c->fn_data = nx; + xQueueSend(x->accept_q, &nx, 0); + } else if (ev == MG_EV_READ && x->recv_q) { + // Drain c->recv into recv_q in fixed-size chunks; task1 owns c->recv + size_t off = 0; + while (off < c->recv.len) { + struct mg_bsd_chunk chunk; + size_t n = c->recv.len - off; + if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; + memcpy(chunk.data, c->recv.buf + off, n); + chunk.len = (uint16_t) n; + xQueueSend(x->recv_q, &chunk, portMAX_DELAY); + off += n; + } + mg_iobuf_del(&c->recv, 0, c->recv.len); + } else if (ev == MG_EV_POLL && x->send_q) { + // Drain send_q → mg_send(); task1 owns c + struct mg_bsd_chunk chunk; + while (xQueueReceive(x->send_q, &chunk, 0) == pdTRUE) + mg_send(c, chunk.data, chunk.len); + } else if (ev == MG_EV_CONNECT) { + // Outgoing connection established: wake the task blocked in connect() + if (x->connect_waiter) { + if (x->connect_result) *x->connect_result = 0; + TaskHandle_t h = x->connect_waiter; + x->connect_waiter = NULL; x->connect_result = NULL; + xTaskNotifyGive(h); + } + } else if (ev == MG_EV_CLOSE) { + x->c = NULL; x->closed = true; c->fn_data = NULL; + // If connect() is still waiting, signal failure + if (x->connect_waiter) { + if (x->connect_result) *x->connect_result = -1; + TaskHandle_t h = x->connect_waiter; + x->connect_waiter = NULL; x->connect_result = NULL; + xTaskNotifyGive(h); + } + if (x->recv_q) { struct mg_bsd_chunk eof = {.len = 0}; xQueueSend(x->recv_q, &eof, 0); } + if (x->accept_q) { struct mg_xport *nil = NULL; xQueueSend(x->accept_q, &nil, 0); } + } + (void) ev_data; +} + +void mg_bsd_init(void) { + s_cmd_q = xQueueCreate(8, sizeof(struct mg_bsd_cmd)); +} + +void mg_bsd_poll(struct mg_mgr *mgr) { + struct mg_bsd_cmd cmd; + if (s_cmd_q == NULL) return; + while (xQueueReceive(s_cmd_q, &cmd, 0) == pdTRUE) { + bool notify = true; + if (cmd.type == BSD_CMD_LISTEN) { + struct mg_connection *c = mg_listen(mgr, cmd.url, xport_ev, cmd.x); + cmd.x->c = c; + *cmd.result = c ? 0 : -1; + } else if (cmd.type == BSD_CMD_CLOSE) { + if (cmd.x->c) { + cmd.x->c->fn_data = NULL; + cmd.x->c->is_draining = 1; + } + *cmd.result = 0; + } else if (cmd.type == BSD_CMD_CONNECT) { + cmd.x->connect_waiter = cmd.caller; + cmd.x->connect_result = cmd.result; + struct mg_connection *c = mg_connect(mgr, cmd.url, xport_ev, cmd.x); + cmd.x->c = c; + if (!c) { *cmd.result = -1; cmd.x->connect_waiter = NULL; cmd.x->connect_result = NULL; } + else notify = false; // xport_ev notifies when connected or on error + } else if (cmd.type == BSD_CMD_RESOLVE) { + s_resolve.done = s_resolve.error = false; + s_resolve.caller = cmd.caller; + char url[80]; + snprintf(url, sizeof(url), "tcp://%s:0", cmd.url); + if (!mg_connect(mgr, url, resolve_cb, NULL)) s_resolve.error = true; + else notify = false; // resolve_cb notifies when done + } + if (notify) xTaskNotifyGive(cmd.caller); + } +} + +void *mg_bsd_transport_new(int domain, int type, int proto) { + (void) domain; (void) type; (void) proto; + // For socket() calls: allocate accept_q only; recv/send added when needed + struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); + if (!x) return NULL; + x->accept_q = xQueueCreate(MG_BSD_BACKLOG, sizeof(struct mg_xport *)); + if (!x->accept_q) { free(x); return NULL; } + return x; +} + +void mg_bsd_transport_free(void *t) { + struct mg_xport *x = (struct mg_xport *) t; + if (!x) return; + if (x->recv_q) vQueueDelete(x->recv_q); + if (x->send_q) vQueueDelete(x->send_q); + if (x->accept_q) vQueueDelete(x->accept_q); + free(x); +} + +int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr) { + struct mg_xport *x = (struct mg_xport *) t; + int result = -1; + struct mg_bsd_cmd cmd = {BSD_CMD_LISTEN, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + snprintf(cmd.url, sizeof(cmd.url), "tcp://0.0.0.0:%d", mg_ntohs(addr->sin_port)); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + return result; +} + +void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + struct mg_xport *nx = NULL; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + if (xQueueReceive(x->accept_q, &nx, ticks) != pdTRUE || !nx) return NULL; + if (peer) *peer = nx->peer; + return nx; +} + +ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + struct mg_bsd_chunk chunk; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + if (xQueueReceive(x->recv_q, &chunk, ticks) != pdTRUE) { + errno = EAGAIN; + return -1; + } + if (chunk.len == 0) return 0; // EOF + size_t n = chunk.len < len ? chunk.len : len; + memcpy(buf, chunk.data, n); + return (ssize_t) n; +} + +ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + if (x->closed) return -1; + size_t sent = 0; + TickType_t ticks = nonblock ? 0 : portMAX_DELAY; + while (sent < len) { + struct mg_bsd_chunk chunk; + size_t n = len - sent; + if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; + memcpy(chunk.data, (const uint8_t *) buf + sent, n); + chunk.len = (uint16_t) n; + if (xQueueSend(x->send_q, &chunk, ticks) != pdTRUE) break; + sent += n; + } + return sent > 0 ? (ssize_t) sent : (errno = EAGAIN, -1); +} + +int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock) { + struct mg_xport *x = (struct mg_xport *) t; + (void) nonblock; + if (!x->recv_q) x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->send_q) x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); + if (!x->recv_q || !x->send_q) { errno = ENOMEM; return -1; } + int result = -1; + struct mg_bsd_cmd cmd = {BSD_CMD_CONNECT, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + uint8_t *ip = (uint8_t *) &addr->sin_addr.s_addr; + snprintf(cmd.url, sizeof(cmd.url), "tcp://%d.%d.%d.%d:%d", + ip[0], ip[1], ip[2], ip[3], mg_ntohs(addr->sin_port)); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + return result; +} + +// gethostbyname: resolve via Mongoose DNS (not reentrant) +static struct hostent s_hostent; +static char *s_h_aliases[1]; +static char *s_h_addr_list[2]; +static uint32_t s_h_addr; +static char s_h_name[64]; + +struct hostent *gethostbyname(const char *name) { + struct mg_bsd_cmd cmd = {BSD_CMD_RESOLVE, NULL, {0}, xTaskGetCurrentTaskHandle(), NULL}; + snprintf(cmd.url, sizeof(cmd.url), "%s", name); + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (s_resolve.error) return NULL; + s_h_addr = s_resolve.addr.addr.ip4; + s_h_addr_list[0] = (char *) &s_h_addr; + s_h_addr_list[1] = NULL; + s_h_aliases[0] = NULL; + snprintf(s_h_name, sizeof(s_h_name), "%s", name); + s_hostent.h_name = s_h_name; + s_hostent.h_aliases = s_h_aliases; + s_hostent.h_addrtype = AF_INET; + s_hostent.h_length = 4; + s_hostent.h_addr_list = s_h_addr_list; + return &s_hostent; +} + +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + struct hostent *h = gethostbyname(node); + if (!h) return -1; + struct addrinfo *ai = (struct addrinfo *) calloc(1, sizeof(*ai)); + struct sockaddr_in *sa = (struct sockaddr_in *) calloc(1, sizeof(*sa)); + if (!ai || !sa) { free(ai); free(sa); return -1; } + sa->sin_family = AF_INET; + memcpy(&sa->sin_addr, h->h_addr, 4); + if (service) sa->sin_port = htons((uint16_t) atoi(service)); + ai->ai_family = AF_INET; + ai->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; + ai->ai_addrlen = sizeof(*sa); + ai->ai_addr = (struct sockaddr *) sa; + *res = ai; + return 0; +} + +void freeaddrinfo(struct addrinfo *res) { + while (res) { + struct addrinfo *next = res->ai_next; + free(res->ai_addr); + free(res); + res = next; + } +} + +void mg_bsd_transport_close(void *t) { + struct mg_xport *x = (struct mg_xport *) t; + if (!x->closed && x->c) { + int result = 0; + struct mg_bsd_cmd cmd = {BSD_CMD_CLOSE, x, {0}, xTaskGetCurrentTaskHandle(), &result}; + xQueueSend(s_cmd_q, &cmd, portMAX_DELAY); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + mg_bsd_transport_free(x); +} + +#endif // MG_ENABLE_FREERTOS +#endif // MG_ENABLE_BSD_SOCKETS diff --git a/src/bsd.h b/src/bsd.h new file mode 100644 index 00000000..6ace82c4 --- /dev/null +++ b/src/bsd.h @@ -0,0 +1,180 @@ +#pragma once + +#include "config.h" +#include "net_builtin.h" + +#if MG_ENABLE_BSD_SOCKETS + +#ifndef MG_ENABLE_BSD_PROTOTYPES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +typedef unsigned int socklen_t; +typedef int ssize_t; +typedef uint32_t in_addr_t; +struct in_addr { in_addr_t s_addr; }; +struct in6_addr { uint8_t s6_addr[16]; }; +struct sockaddr { uint16_t sa_family; char sa_data[14]; }; +struct sockaddr_in { + uint16_t sin_family; + uint16_t sin_port; + struct in_addr sin_addr; + char sin_zero[8]; +}; +struct sockaddr_in6 { + uint16_t sin6_family; + uint16_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + uint32_t sin6_scope_id; +}; +struct sockaddr_storage { uint16_t ss_family; char __ss_pad[126]; }; +struct hostent { + char *h_name; + char **h_aliases; + int h_addrtype; + int h_length; + char **h_addr_list; +}; +#define h_addr h_addr_list[0] +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; +struct pollfd { int fd; short events; short revents; }; +#define AF_INET 2 +#define AF_INET6 10 +#define AF_UNSPEC 0 +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 +#define PF_UNSPEC AF_UNSPEC +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define IPPROTO_TCP 6 +#define IPPROTO_UDP 17 +#define INADDR_ANY 0 +#define INADDR_LOOPBACK 0x7f000001 +#define SOL_SOCKET 0xffff +#define SO_REUSEADDR 2 +#define SO_REUSEPORT 15 +#define SO_KEEPALIVE 9 +#define SO_ERROR 4 +#define SO_BROADCAST 6 +#define SO_RCVBUF 8 +#define SO_SNDBUF 7 +#define TCP_NODELAY 1 +#define MSG_DONTWAIT 0x40 +#define MSG_NOSIGNAL 0x4000 +#define MSG_PEEK 0x02 +#define F_GETFL 3 +#define F_SETFL 4 +#define O_NONBLOCK 0x0004 +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 +#define AI_PASSIVE 0x0001 +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 +#ifndef EAGAIN +#define EAGAIN 11 +#endif +#ifndef EWOULDBLOCK +#define EWOULDBLOCK EAGAIN +#endif +#ifndef EINPROGRESS +#define EINPROGRESS 36 +#endif +#ifndef ENOTCONN +#define ENOTCONN 107 +#endif +#ifndef ECONNREFUSED +#define ECONNREFUSED 111 +#endif +#ifndef ECONNRESET +#define ECONNRESET 104 +#endif +#ifndef EADDRINUSE +#define EADDRINUSE 98 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 110 +#endif +#define POLLIN 0x001 +#define POLLOUT 0x004 +#define POLLERR 0x008 +#define POLLHUP 0x010 +#define POLLNVAL 0x020 +uint16_t htons(uint16_t); +uint16_t ntohs(uint16_t); +uint32_t htonl(uint32_t); +uint32_t ntohl(uint32_t); +#endif // MG_ENABLE_BSD_PROTOTYPES + +#define closesocket(a) close(a) + +#ifndef MG_BSD_BACKLOG +#define MG_BSD_BACKLOG 5 +#endif + +#ifndef MG_BSD_CONNECT_TIMEOUT_MS +#define MG_BSD_CONNECT_TIMEOUT_MS 10000 +#endif + +// Mongoose-specific API +void mg_bsd_init(void); // must be called before socket() +void mg_bsd_poll(struct mg_mgr *); // process pending BSD commands + +// Transport backend: implement these OR define MG_ENABLE_FREERTOS for the built-in backend +void *mg_bsd_transport_new(int domain, int type, int proto); +void mg_bsd_transport_free(void *t); +int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr); +void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock); +ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock); +ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock); +int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock); +void mg_bsd_transport_close(void *t); + +// Standard BSD socket API +int socket(int, int, int); +int bind(int, const struct sockaddr *, socklen_t); +int listen(int, int); +int accept(int, struct sockaddr *, socklen_t *); +int connect(int, const struct sockaddr *, socklen_t); +ssize_t send(int, const void *, size_t, int); +ssize_t recv(int, void *, size_t, int); +ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); +ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *); +int close(int); +int shutdown(int, int); +int fcntl(int, int, int); +int setsockopt(int, int, int, const void *, socklen_t); +int getsockopt(int, int, int, void *, socklen_t *); +int getsockname(int, struct sockaddr *, socklen_t *); +int getpeername(int, struct sockaddr *, socklen_t *); +int select(int, fd_set *, fd_set *, fd_set *, struct timeval *); +int poll(struct pollfd *, unsigned int, int); +struct hostent *gethostbyname(const char *); +int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **); +void freeaddrinfo(struct addrinfo *); +int inet_pton(int, const char *, void *); +const char *inet_ntop(int, const void *, char *, socklen_t); +in_addr_t inet_addr(const char *); +char *inet_ntoa(struct in_addr); + +#endif // MG_ENABLE_BSD_SOCKETS diff --git a/src/config.h b/src/config.h index b38a4b70..49250e9c 100644 --- a/src/config.h +++ b/src/config.h @@ -20,6 +20,14 @@ #define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack #endif +#ifndef MG_ENABLE_BSD_SOCKETS +#define MG_ENABLE_BSD_SOCKETS 0 // BSD API support for built-in stack +#endif + +#ifndef MG_ENABLE_BSD_PROTOTYPES +#define MG_ENABLE_BSD_PROTOTYPES 1 +#endif + #ifndef MG_ENABLE_LWIP #define MG_ENABLE_LWIP 0 // lwIP network stack #endif diff --git a/src/net_builtin.c b/src/net_builtin.c index a4d9ff92..4da3c4d7 100644 --- a/src/net_builtin.c +++ b/src/net_builtin.c @@ -1423,9 +1423,10 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { } s->twclosure = true; } else { - flags |= TH_FIN; + // Peer closed first: send ACK only, enter CLOSE_WAIT. + // The connection loop will call init_closure after pending send data + // is flushed, then send our FIN. c->is_draining = 1; - settmout(c, MIP_TTYPE_FIN); } tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, flags, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); diff --git a/test/Makefile b/test/Makefile index c87626e1..36faa3cd 100644 --- a/test/Makefile +++ b/test/Makefile @@ -251,7 +251,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c) cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - cd .. && (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/rpc.h src/dash.h src/ota.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ + cd .. && (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/rpc.h src/dash.h src/ota.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ clean: clean_tutorials clean_tutorials_embedded diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/FreeRTOSConfig.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd/FreeRTOSConfig.h new file mode 100644 index 00000000..1fb1d8af --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd/FreeRTOSConfig.h @@ -0,0 +1,39 @@ +#pragma once + +#include "hal.h" + +#define configUSE_PREEMPTION 1 +#define configCPU_CLOCK_HZ SYS_FREQUENCY +#define configTICK_RATE_HZ 1000 +#define configMAX_PRIORITIES 5 +#define configUSE_16_BIT_TICKS 0 +#define configUSE_TICK_HOOK 0 +#define configUSE_IDLE_HOOK 0 +#define configUSE_TIMERS 0 +#define configUSE_CO_ROUTINES 0 +#define configUSE_MALLOC_FAILED_HOOK 0 +#define configMINIMAL_STACK_SIZE 128 +#define configTOTAL_HEAP_SIZE (1024 * 64) +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_xTaskGetSchedulerState 1 + +#ifdef __NVIC_PRIO_BITS +#define configPRIO_BITS __NVIC_PRIO_BITS +#else +#define configPRIO_BITS 4 +#endif +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 +#define configKERNEL_INTERRUPT_PRIORITY \ + (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) + +#define configMAX_SYSCALL_INTERRUPT_PRIORITY \ + (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) + +#define configASSERT(expr) \ + if (!(expr)) printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr) + +#define vPortSVCHandler SVC_Handler +#define xPortPendSVHandler PendSV_Handler +//#define xPortSysTickHandler SysTick_Handler diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/Makefile b/tutorials/stm32/nucleo-h723zg/minimal-bsd/Makefile new file mode 100644 index 00000000..df1d01f2 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd/Makefile @@ -0,0 +1,59 @@ +# Copyright (c) 2026 Cesanta Software Limited +# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/ + +CFLAGS = -W -Wall -Wextra -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_h7/Include -Imongoose +CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA) +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map + +SOURCES = main.c hal.c +SOURCES += cmsis_h7/Source/Templates/gcc/startup_stm32h723xx.s +SOURCES += mongoose/mongoose.c + +# FreeRTOS. H7 has a Cortex-M7 r1p0 core, FreeRTOS recommends using CM4F port for non-r0p1 CM7 micros +SOURCES += FreeRTOS-Kernel/tasks.c FreeRTOS-Kernel/list.c FreeRTOS-Kernel/queue.c +SOURCES += FreeRTOS-Kernel/portable/MemMang/heap_4.c +SOURCES += FreeRTOS-Kernel/portable/GCC/ARM_CM4F/port.c +CFLAGS += -IFreeRTOS-Kernel/include +CFLAGS += -IFreeRTOS-Kernel/portable/GCC/ARM_CM4F -Wno-conversion + +all build example: firmware.bin + +mongoose/mongoose.c mongoose/mongoose.h: + cp ../../../../mongoose.[ch] mongoose/ + +firmware.elf: cmsis_core cmsis_h7 FreeRTOS-Kernel hal.h link.ld Makefile $(SOURCES) mongoose/mongoose.h + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@ + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + @echo + @echo "To flash, run 'make flash', or use STM32CubeProgrammer" + +flash: firmware.bin + STM32_Programmer_CLI -c port=swd -w firmware.elf -hardRst + +cmsis_core: + git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ + +cmsis_h7: + git clone -q -c advice.detachedHead=false --depth 1 -b v1.10.6 https://github.com/STMicroelectronics/cmsis_device_h7 $@ + +FreeRTOS-Kernel: + git clone --depth 1 -b V11.3.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@ + +clean: + rm -rf firmware.* cmsis_* mongoose/mongoose.* + + +# Automated remote test. Requires env variable VCON_API_KEY set. See https://vcon.io/automated-firmware-tests/ +DEVICE_URL ?= https://dash.vcon.io/api/v3/devices/10 +update: firmware.bin + curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/ota --data-binary @$< + +test update: CFLAGS_EXTRA = -DUART_DEBUG=USART1 +test: update + curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/tx?t=15 | tee /tmp/output.txt + grep 'READY, IP:' /tmp/output.txt # Check for network init diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.c new file mode 100644 index 00000000..e519bff3 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.c @@ -0,0 +1,125 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" + +bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) { + uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry + if (period == 0) return false; // Avoid division by zero + if (diff < period) return false; // Period has not elapsed yet + *t += (diff / period) * period; // Preserve cadence, skip missed periods + return true; +} + +static volatile uint64_t s_ticks; // Milliseconds since boot +void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms + s_ticks++; +} + +uint64_t hal_get_tick(void) { + return s_ticks; +}; + +uint32_t SystemCoreClock = 160000000; +void SystemInit(void) { // Called automatically by startup code + hal_system_init(); // Enable FPU + hal_clock_init(); + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +void ExitRun0Mode(void) { +} + +struct stat; +__attribute__((weak)) int _fstat(int fd, struct stat *st) { + (void) fd, (void) st; + return -1; +} + +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +static unsigned char *s_current_heap_end = _end; + +size_t hal_ram_used(void) { + return (size_t) (s_current_heap_end - _end); +} + +size_t hal_ram_free(void) { + unsigned char endofstack; + return (size_t) (&endofstack - s_current_heap_end); +} + +void *_sbrk(int incr) { + unsigned char *prev_heap; + unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256); + prev_heap = s_current_heap_end; + // Check how much space we got from the heap end to the stack end + if (s_current_heap_end + incr > heap_end) return (void *) -1; + s_current_heap_end += incr; + return prev_heap; +} + +__attribute__((weak)) int _open(const char *path) { + (void) path; + return -1; +} + +__attribute__((weak)) int _close(int fd) { + (void) fd; + return -1; +} + +__attribute__((weak)) int _isatty(int fd) { + (void) fd; + return 1; +} + +__attribute__((weak)) int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +__attribute__((weak)) void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +__attribute__((weak)) void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +__attribute__((weak)) int _getpid(void) { + return -1; +} + +__attribute__((weak)) int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +__attribute__((weak)) int _unlink(const char *a) { + (void) a; + return -1; +} + +__attribute__((weak)) int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +__attribute__((weak)) int mkdir(const char *path, int mode) { + (void) path, (void) mode; + return -1; +} + +__attribute__((weak)) void _init(void) { +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.h new file mode 100644 index 00000000..1043c454 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.h @@ -0,0 +1,219 @@ +// Copyright (c) 2022-2026 Cesanta Software Limited +// Datasheet: RM0468, devboard manual: UM2407 +// https://www.st.com/resource/en/reference_manual/rm0468-stm32h723733-stm32h725735-and-stm32h730-value-line-advanced-armbased-32bit-mcus-stmicroelectronics.pdf +// https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf +// Alternate functions: https://www.st.com/resource/en/datasheet/stm32h723.pdf + +#pragma once + +#include + +#include +#include +#include +#include + +extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now); +extern uint64_t hal_get_tick(void); +extern size_t hal_ram_free(void); +extern size_t hal_ram_used(void); +extern uint32_t SystemCoreClock; + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) +#define PIN(bank, num) ((((bank) - 'A') << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +#define HAL_ETH_PINS PIN('A', 1), PIN('A', 2), PIN('A', 7), \ + PIN('B', 13), PIN('C', 1), PIN('C', 4), \ + PIN('C', 5), PIN('G', 11), PIN('G', 13) + +// System clock (2.1, Figure 1; 8.5, Figure 45; 8.5.5, Figure 47; 8.5.6, Figure +// 49; 8.5.8 Table 56; datasheet) CPU_FREQUENCY <= 550 MHz; hclk = CPU_FREQUENCY +// / HAL_HPRE ; hclk <= 275 MHz; APB clocks <= 137.5 MHz. D1 domain bus matrix (and +// so flash) runs at hclk frequency. Configure flash latency (WS) in accordance +// to hclk freq (4.3.8, Table 16) The Ethernet controller is in D2 domain and +// runs at hclk frequency +enum { + HAL_D1CPRE = 1, // actual divisor value + HAL_HPRE = 2, // actual divisor value + HAL_D1PPRE = 4, // register values, divisor value = BIT(value - 3) = / 2 + HAL_D2PPRE1 = 4, + HAL_D2PPRE2 = 4, + HAL_D3PPRE = 4 +}; +// PLL1_P: odd division factors are not allowed (8.7.12) (except for '1') +enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 225, PLL1_P = 1 }; +#define HAL_FLASH_LATENCY 0x33 // WRHIGHFREQ LATENCY +#define SYS_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / HAL_D1CPRE) * 1000000) +#define AHB_FREQUENCY (SYS_FREQUENCY / HAL_HPRE) +#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE2 - 3))) +#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE1 - 3))) + +static inline void hal_spin(volatile uint32_t n) { + while (n--) (void) 0; +} + +enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_MODE_AF, HAL_GPIO_MODE_ANALOG }; +enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN }; +enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_MEDIUM, HAL_GPIO_SPEED_HIGH, HAL_GPIO_SPEED_INSANE }; +enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN }; + +#define HAL_GPIO(N) ((GPIO_TypeDef *) (0x40000000 + 0x18020000UL + 0x400 * (N))) + +static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) { + return HAL_GPIO(PINBANK(pin)); +} +static inline void hal_gpio_toggle(uint16_t pin) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint32_t mask = BIT(PINNO(pin)); + gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0); +} +static inline int hal_gpio_read(uint16_t pin) { + return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0; +} +static inline void hal_gpio_write(uint16_t pin, bool val) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16); +} +static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + uint8_t speed, uint8_t pull, uint8_t af) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint8_t n = (uint8_t) (PINNO(pin)); + RCC->AHB4ENR |= BIT(PINBANK(pin)); // Enable GPIO clock + CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n); + CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2)); + CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2)); + CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4), + ((uint32_t) af) << ((n & 7) * 4)); + CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2)); +} +static inline void hal_gpio_input(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, + HAL_GPIO_PULL_NONE, 0); +} +static inline void hal_gpio_output(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, + HAL_GPIO_PULL_NONE, 0); +} + +// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3 +// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks +// must be enabled for CPU access, as the kernel clock drives the BRR, not the +// APB bus interface +static inline void hal_uart_init(USART_TypeDef *uart, uint16_t tx, + uint16_t rx, unsigned long baud) { + uint8_t af = 7; // Alternate function + 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->APB1LENR |= BIT(17); + if (uart == USART3) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(18); + +#if 0 // CONSTANT BAUD RATE FOR REMOTE DEBUGGING WHILE SETTING THE PLL + CLRSET(RCC->D2CCIP2R, 7 << 3, 3 << 3); // use HSI for UART1 + freq = 64000000; +#endif + + hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af); + hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_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 hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) { + uart->TDR = byte; + while ((uart->ISR & BIT(7)) == 0) hal_spin(1); +} +static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) { + while (len-- > 0) hal_uart_write_byte(uart, *(uint8_t *) buf++); +} +static inline int hal_uart_read_ready(USART_TypeDef *uart) { + return uart->ISR & BIT(5); // If RXNE bit is set, data is ready +} +static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) { + return (uint8_t) (uart->RDR & 255); +} + +// RNG clock (rng_clk) defaults to HSI48 and is characterized at this frequency. +// Enable at SystemInit. (STM32CubeMX) It won't work with a 275MHz (AHB) +static inline void hal_rng_init(void) { + RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // Enable RNG AHB clock (hal_rng_hclk) + // 39.6.2 Table 320 init + RNG->CR = RNG_CR_CONDRST | (0x0F << RNG_CR_RNG_CONFIG1_Pos) | + (0 << RNG_CR_RNG_CONFIG2_Pos) | (0xD << RNG_CR_RNG_CONFIG3_Pos); + RNG->HTCR = 0x17590abc; + RNG->HTCR = 0xaa74; + RNG->CR &= ~RNG_CR_CONDRST ; + while(RNG->CR & RNG_CR_CONDRST) hal_spin(1); // 39.7.1 + RNG->CR |= RNG_CR_RNGEN; // Enable RNG +} + +static inline uint32_t hal_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 hal_ethernet_init(void) { + // Initialise Ethernet. Enable MAC GPIO pins, see + // https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf + uint16_t pins[] = {HAL_ETH_PINS}; + for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + hal_gpio_init(pins[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_INSANE, + HAL_GPIO_PULL_NONE, 11); // 11 is the Ethernet function + } + NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler + CLRSET(SYSCFG->PMCR, 7 << 21, 4 << 21); // Use RMII (12.3.1) + RCC->AHB1ENR |= BIT(15) | BIT(16) | BIT(17); // Enable Ethernet clocks +} + +static inline unsigned int hal_div2prescval(unsigned int div) { + // 0 --> /1; 8 --> /2 ... 11 --> /16; 12 --> /64 ... 15 --> /512 + if (div == 1) return 0; + if (div > 16) div /= 2; + unsigned int val = 7; + while (div >>= 1) ++val; + return val; +} + +static inline unsigned int hal_pllrge(unsigned int f) { + unsigned int val = 0; + while (f >>= 1) ++val; + return val - 1; +} + +static inline void hal_clock_init(void) { + PWR->CR3 |= BIT(1); // select LDO (reset value) + while ((PWR->CSR1 & BIT(13)) == 0) hal_spin(1); // ACTVOSRDY + PWR->D3CR &= ~(BIT(15) | BIT(14)); // Select VOS0 + while ((PWR->D3CR & BIT(13)) == 0) hal_spin(1); // VOSRDY + CLRSET( + RCC->D1CFGR, (0x0F << 8) | (7 << 4) | (0x0F << 0), + (hal_div2prescval(HAL_D1CPRE) << 8) | (HAL_D1PPRE << 4) | (hal_div2prescval(HAL_HPRE) << 0)); + RCC->D2CFGR = (HAL_D2PPRE2 << 8) | (HAL_D2PPRE1 << 4); + RCC->D3CFGR = (HAL_D3PPRE << 4); + CLRSET(RCC->PLLCFGR, 3 << 2, + hal_pllrge(PLL1_HSI / PLL1_M) + << 2); // keep reset config (DIVP1EN, !PLL1VCOSEL), PLL1RGE + CLRSET(RCC->PLL1DIVR, (0x7F << 9) | (0x1FF << 0), + ((PLL1_P - 1) << 9) | ((PLL1_N - 1) << 0)); // Set PLL1_P PLL1_N + CLRSET(RCC->PLLCKSELR, 0x3F << 4, + PLL1_M << 4); // Set PLL1_M (source defaults to HSI) + RCC->CR |= BIT(24) | BIT(12); // Enable PLL1 and HSI48 (for RNG) + while ((RCC->CR & BIT(25)) == 0) hal_spin(1); // Wait until done + RCC->CFGR |= (3 << 0); // Set clock source to PLL1 + while ((RCC->CFGR & (7 << 3)) != (3 << 3)) hal_spin(1); // Wait until done + FLASH->ACR = HAL_FLASH_LATENCY; // default is larger + RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; // Enable SYSCFG + while ((RCC->CR & BIT(13)) == 0) hal_spin(1); // Make sure HSI48 is ready + SystemCoreClock = SYS_FREQUENCY; // Update SystemCoreClock global var + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +static inline void hal_system_init(void) { + SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU + __DSB(); + __ISB(); +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/link.ld b/tutorials/stm32/nucleo-h723zg/minimal-bsd/link.ld new file mode 100644 index 00000000..a62749dc --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd/link.ld @@ -0,0 +1,28 @@ +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08000000, LENGTH = 1024k + + dtcmram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K + ram_d1 (rwx) : ORIGIN = 0x24000000, LENGTH = 320K + ram_d2 (rwx) : ORIGIN = 0x30000000, LENGTH = 32K + ram_d3 (rwx) : ORIGIN = 0x38000000, LENGTH = 16K + itcmram (rwx) : ORIGIN = 0x00000000, LENGTH = 64K +} +_estack = ORIGIN(ram_d1) + LENGTH(ram_d1); + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) } > flash + + /* Note the .iram section, for functions copied to RAM. Required for MG_IRAM OTA support */ + .data : { _sdata = .; *(.first_data) *(.iram .iram* .iram.*) *(.data SORT(.data.*)) _edata = .; } > ram_d1 AT > flash + _sidata = LOADADDR(.data); + + .bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > ram_d1 + + .eth_ram : { *(.eth_ram .eth_ram*) } > ram_d1 AT > flash + + . = ALIGN(8); + _end = .; +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/main.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd/main.c new file mode 100644 index 00000000..3d834258 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd/main.c @@ -0,0 +1,142 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved +// +// Two FreeRTOS tasks sharing one Mongoose event manager via the BSD socket +// shim (MG_ENABLE_BSD_SOCKETS). task1 owns the manager and drives the +// network; task2 accepts connections and spawns per-connection echo_tasks +// that speak standard BSD sockets. + +#include "hal.h" +#include "mongoose.h" + +#ifndef UART_DEBUG +#define UART_DEBUG USART3 +#define UART_DEBUG_TX_PIN PIN('D', 8) +#define UART_DEBUG_RX_PIN PIN('D', 9) +#else +#define UART_DEBUG_TX_PIN PIN('A', 9) +#define UART_DEBUG_RX_PIN PIN('A', 10) +#endif + +#define LED1 PIN('B', 0) +#define LED2 PIN('E', 1) +#define LED3 PIN('B', 14) + +int _write(int fd, char *ptr, int len) { + if (fd == 1) hal_uart_write_buf(UART_DEBUG, ptr, (size_t) len); + return len; +} + +static void blink_task(void) { + static uint64_t t = 0; + if (hal_timer_expired(&t, 500, hal_get_tick())) hal_gpio_toggle(LED1); +} + +uint64_t mg_millis(void) { return hal_get_tick(); } + +bool mg_random(void *buf, size_t len) { + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = hal_rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; +} + +static void http_ev_handler(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; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else { + mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", hal_get_tick()); + } + } +} + +// task1: network owner. Runs the Mongoose event loop (mg_bsd_poll drives both +// the TCP/IP stack and the BSD command queue) and blinks the LED. +static void task1(void *args) { + static struct mg_mgr mgr; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + for (;;) { + mg_mgr_poll(&mgr, 0); + mg_bsd_poll(&mgr); + blink_task(); + } + (void) args; +} + +// client_task: connects to mongoose.ws:80, fetches "/", logs response length. +// Spawned when the echo server receives a message starting with "42". +static void client_task(void *args) { + struct hostent *h = gethostbyname("mongoose.ws"); + if (!h) { MG_ERROR(("DNS failed")); vTaskDelete(NULL); return; } + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in sa = {.sin_family = AF_INET, .sin_port = htons(80)}; + memcpy(&sa.sin_addr, h->h_addr, 4); + if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + MG_ERROR(("connect failed")); close(fd); vTaskDelete(NULL); return; + } + const char *req = "GET / HTTP/1.0\r\nHost: mongoose.ws\r\n\r\n"; + send(fd, req, strlen(req), 0); + size_t total = 0; + char buf[512]; + ssize_t n; + while ((n = recv(fd, buf, sizeof(buf), 0)) > 0) total += (size_t) n; + MG_INFO(("mongoose.ws response: %lu bytes", (unsigned long) total)); + close(fd); + vTaskDelete(NULL); + (void) args; +} + +// echo_task: one per accepted connection. Echoes data back. +// If a message starts with "42", also spawns a client_task to fetch mongoose.ws. +static void echo_task(void *args) { + int fd = (int) (uintptr_t) args; + char buf[512]; + ssize_t n; + while ((n = recv(fd, buf, sizeof(buf), 0)) > 0) { + if (n >= 2 && buf[0] == '4' && buf[1] == '2') + xTaskCreate(client_task, "client", 1024, NULL, configMAX_PRIORITIES - 1, NULL); + send(fd, buf, (size_t) n, 0); + } + close(fd); + vTaskDelete(NULL); +} + +// task2: accept loop. Waits for incoming connections on port 1234 and spawns +// an echo_task for each one, allowing concurrent clients. +static void task2(void *args) { + mg_bsd_init(); + int lfd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in sa = {.sin_family = AF_INET, .sin_port = htons(1234), .sin_addr = {INADDR_ANY}}; + bind(lfd, (struct sockaddr *) &sa, sizeof(sa)); + listen(lfd, 5); + MG_INFO(("BSD echo server on :1234")); + for (;;) { + int fd = accept(lfd, NULL, NULL); + if (fd < 0) continue; + xTaskCreate(echo_task, "echo", 512, (void *) (uintptr_t) fd, + configMAX_PRIORITIES - 1, NULL); + } + (void) args; +} + +int main(void) { + hal_clock_init(); + hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200); + hal_rng_init(); + hal_ethernet_init(); + hal_gpio_output(LED1); + hal_gpio_output(LED2); + hal_gpio_output(LED3); + MG_INFO(("CPU clock: %lu MHz", SystemCoreClock / 1000000)); + + // task2 at higher priority than task1 so that when task1 pushes data into + // recv_q, task2 preempts immediately, echoes, and yields before MG_EV_CLOSE. + xTaskCreate(task1, "task1", 2048, NULL, configMAX_PRIORITIES - 2, NULL); + xTaskCreate(task2, "task2", 256, NULL, configMAX_PRIORITIES - 1, NULL); + vTaskStartScheduler(); + return 0; +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/mongoose/mongoose_config.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd/mongoose/mongoose_config.h new file mode 100644 index 00000000..91896432 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd/mongoose/mongoose_config.h @@ -0,0 +1,37 @@ +#pragma once + +// See https://mongoose.ws/docs/getting-started/build-options/ +#define MG_ARCH MG_ARCH_ARMGCC +#define MG_TLS MG_TLS_BUILTIN +#define MG_OTA MG_OTA_STM32H7 + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_FREERTOS 1 +#define MG_ENABLE_BSD_SOCKETS 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_DRIVER_STM32H 1 +#define MG_ENABLE_PACKED_FS 1 +#define MG_ETH_RAM __attribute__((section(".eth_ram"))) + +// #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5 +// #define MG_TCPIP_PHY_ADDR 0 // PHY address + +// For static IP configuration, define MG_TCPIP_{IP,MASK,GW} +// By default, those are set to zero, meaning that DHCP is used +// +// #define MG_TCPIP_IP MG_IPV4(192, 168, 0, 10) // IP +// #define MG_TCPIP_GW MG_IPV4(192, 168, 0, 1) // Gateway +// #define MG_TCPIP_MASK MG_IPV4(255, 255, 255, 0) // Netmask + +// Construct MAC address from the MCU unique ID +#define MGUID ((uint32_t *) 0x1ff1e800) // Unique 96-bit chip ID +#define MG_SET_MAC_ADDRESS(mac) \ + do { \ + mac[0] = 2; \ + mac[1] = MGUID[0] & 255; \ + mac[2] = (MGUID[0] >> 10) & 255; \ + mac[3] = (MGUID[0] >> 19) & 255; \ + mac[4] = MGUID[1] & 255; \ + mac[5] = MGUID[2] & 255; \ + } while (0)