From 999c8b9711bf31ac375c09f35672e59b21d0aed4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 27 Apr 2018 23:53:45 +1000 Subject: [PATCH] esp32/modsocket: Add support for registering socket event callbacks. The esp8266 uses modlwip.c for its usocket implementation, which allows to easily support callbacks on socket events (like when a socket becomes ready for reading). This is not as easy to do for the esp32 which uses the ESP-IDF-provided lwIP POSIX socket API. Socket events are needed to get WebREPL working, and this patch provides a way for such events to work by explicitly polling registered sockets for readability, and then calling the associated callback if the socket is readable. --- ports/esp32/main.c | 2 + ports/esp32/modnetwork.h | 2 + ports/esp32/modsocket.c | 95 ++++++++++++++++++++++++++++++++++++++ ports/esp32/mpconfigport.h | 8 ++++ 4 files changed, 107 insertions(+) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index eebf183cd6..2fa86fd3c9 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -47,6 +47,7 @@ #include "lib/utils/pyexec.h" #include "uart.h" #include "modmachine.h" +#include "modnetwork.h" #include "mpthreadport.h" // MicroPython runs as a task under FreeRTOS @@ -110,6 +111,7 @@ soft_reset: // deinitialise peripherals machine_pins_deinit(); + usocket_events_deinit(); mp_deinit(); fflush(stdout); diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index d9f56591e2..b8dc1b8528 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -31,4 +31,6 @@ enum { PHY_LAN8720, PHY_TLK110 }; MP_DECLARE_CONST_FUN_OBJ_KW(get_lan_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_ifconfig_obj); +void usocket_events_deinit(void); + #endif diff --git a/ports/esp32/modsocket.c b/ports/esp32/modsocket.c index 8b3c280635..94cf3cc57c 100644 --- a/ports/esp32/modsocket.c +++ b/ports/esp32/modsocket.c @@ -47,6 +47,7 @@ #include "py/mperrno.h" #include "lib/netutils/netutils.h" #include "tcpip_adapter.h" +#include "modnetwork.h" #include "lwip/sockets.h" #include "lwip/netdb.h" @@ -63,10 +64,79 @@ typedef struct _socket_obj_t { uint8_t type; uint8_t proto; unsigned int retries; + #if MICROPY_PY_USOCKET_EVENTS + mp_obj_t events_callback; + struct _socket_obj_t *events_next; + #endif } socket_obj_t; void _socket_settimeout(socket_obj_t *sock, uint64_t timeout_ms); +#if MICROPY_PY_USOCKET_EVENTS +// Support for callbacks on asynchronous socket events (when socket becomes readable) + +// This divisor is used to reduce the load on the system, so it doesn't poll sockets too often +#define USOCKET_EVENTS_DIVISOR (8) + +STATIC uint8_t usocket_events_divisor; +STATIC socket_obj_t *usocket_events_head; + +void usocket_events_deinit(void) { + usocket_events_head = NULL; +} + +// Assumes the socket is not already in the linked list, and adds it +STATIC void usocket_events_add(socket_obj_t *sock) { + sock->events_next = usocket_events_head; + usocket_events_head = sock; +} + +// Assumes the socket is already in the linked list, and removes it +STATIC void usocket_events_remove(socket_obj_t *sock) { + for (socket_obj_t **s = &usocket_events_head;; s = &(*s)->events_next) { + if (*s == sock) { + *s = (*s)->events_next; + return; + } + } +} + +// Polls all registered sockets for readability and calls their callback if they are readable +void usocket_events_handler(void) { + if (usocket_events_head == NULL) { + return; + } + if (--usocket_events_divisor) { + return; + } + usocket_events_divisor = USOCKET_EVENTS_DIVISOR; + + fd_set rfds; + FD_ZERO(&rfds); + int max_fd = 0; + + for (socket_obj_t *s = usocket_events_head; s != NULL; s = s->events_next) { + FD_SET(s->fd, &rfds); + max_fd = MAX(max_fd, s->fd); + } + + // Poll the sockets + struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 }; + int r = select(max_fd + 1, &rfds, NULL, NULL, &timeout); + if (r <= 0) { + return; + } + + // Call the callbacks + for (socket_obj_t *s = usocket_events_head; s != NULL; s = s->events_next) { + if (FD_ISSET(s->fd, &rfds)) { + mp_call_function_1_protected(s->events_callback, s); + } + } +} + +#endif // MICROPY_PY_USOCKET_EVENTS + NORETURN static void exception_from_errno(int _errno) { // Here we need to convert from lwip errno values to MicroPython's standard ones if (_errno == EINPROGRESS) { @@ -209,6 +279,25 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { break; } + #if MICROPY_PY_USOCKET_EVENTS + // level: SOL_SOCKET + // special "register callback" option + case 20: { + if (args[3] == mp_const_none) { + if (self->events_callback != MP_OBJ_NULL) { + usocket_events_remove(self); + self->events_callback = MP_OBJ_NULL; + } + } else { + if (self->events_callback == MP_OBJ_NULL) { + usocket_events_add(self); + } + self->events_callback = args[3]; + } + break; + } + #endif + // level: IPPROTO_IP case IP_ADD_MEMBERSHIP: { mp_buffer_info_t bufinfo; @@ -439,6 +528,12 @@ STATIC mp_uint_t socket_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintpt return ret; } else if (request == MP_STREAM_CLOSE) { if (socket->fd >= 0) { + #if MICROPY_PY_USOCKET_EVENTS + if (socket->events_callback != MP_OBJ_NULL) { + usocket_events_remove(socket); + socket->events_callback = MP_OBJ_NULL; + } + #endif int ret = lwip_close_r(socket->fd); if (ret != 0) { *errcode = errno; diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 2f4acaf129..0239de4080 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -221,11 +221,18 @@ extern const struct _mp_obj_module_t mp_module_onewire; #define MICROPY_BEGIN_ATOMIC_SECTION() portENTER_CRITICAL_NESTED() #define MICROPY_END_ATOMIC_SECTION(state) portEXIT_CRITICAL_NESTED(state) +#if MICROPY_PY_USOCKET_EVENTS +#define MICROPY_PY_USOCKET_EVENTS_HANDLER extern void usocket_events_handler(void); usocket_events_handler(); +#else +#define MICROPY_PY_USOCKET_EVENTS_HANDLER +#endif + #if MICROPY_PY_THREAD #define MICROPY_EVENT_POLL_HOOK \ do { \ extern void mp_handle_pending(void); \ mp_handle_pending(); \ + MICROPY_PY_USOCKET_EVENTS_HANDLER \ MP_THREAD_GIL_EXIT(); \ MP_THREAD_GIL_ENTER(); \ } while (0); @@ -234,6 +241,7 @@ extern const struct _mp_obj_module_t mp_module_onewire; do { \ extern void mp_handle_pending(void); \ mp_handle_pending(); \ + MICROPY_PY_USOCKET_EVENTS_HANDLER \ asm("waiti 0"); \ } while (0); #endif