diff --git a/ports/w60x/modules/modsocket.c b/ports/w60x/modules/modsocket.c index 98e945e9e45bb..323048386ecb9 100644 --- a/ports/w60x/modules/modsocket.c +++ b/ports/w60x/modules/modsocket.c @@ -65,10 +65,79 @@ typedef struct _socket_obj_t { uint8_t proto; bool peer_closed; unsigned int retries; + #if MICROPY_PY_SOCKET_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_SOCKET_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 socket_events_divisor; +STATIC socket_obj_t *socket_events_head; + +void socket_events_deinit(void) { + socket_events_head = NULL; +} + +// Assumes the socket is not already in the linked list, and adds it +STATIC void socket_events_add(socket_obj_t *sock) { + sock->events_next = socket_events_head; + socket_events_head = sock; +} + +// Assumes the socket is already in the linked list, and removes it +STATIC void socket_events_remove(socket_obj_t *sock) { + for (socket_obj_t **s = &socket_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 socket_events_handler(void) { + if (socket_events_head == NULL) { + return; + } + if (--socket_events_divisor) { + return; + } + socket_events_divisor = USOCKET_EVENTS_DIVISOR; + + fd_set rfds; + FD_ZERO(&rfds); + int max_fd = 0; + + for (socket_obj_t *s = socket_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 = socket_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_SOCKET_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) { @@ -220,6 +289,25 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { break; } + #if MICROPY_PY_SOCKET_EVENTS + // level: SOL_SOCKET + // special "register callback" option + case 20: { + if (args[3] == mp_const_none) { + if (self->events_callback != MP_OBJ_NULL) { + socket_events_remove(self); + self->events_callback = MP_OBJ_NULL; + } + } else { + if (self->events_callback == MP_OBJ_NULL) { + socket_events_add(self); + } + self->events_callback = args[3]; + } + break; + } + #endif + // level: IPPROTO_IP case IP_ADD_MEMBERSHIP: { mp_buffer_info_t bufinfo; @@ -507,6 +595,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_SOCKET_EVENTS + if (socket->events_callback != MP_OBJ_NULL) { + socket_events_remove(socket); + socket->events_callback = MP_OBJ_NULL; + } + #endif int ret = lwip_close(socket->fd); if (ret != 0) { *errcode = errno; diff --git a/ports/w60x/mpconfigport.h b/ports/w60x/mpconfigport.h index 8e47c6000dd13..ace181d56d14e 100644 --- a/ports/w60x/mpconfigport.h +++ b/ports/w60x/mpconfigport.h @@ -76,8 +76,9 @@ #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (mp_hal_get_cpu_freq() / 200) // roughly #define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) -#define MICROPY_PY_WEBREPL (0) +#define MICROPY_PY_WEBREPL (1) #define MICROPY_PY_WEBSOCKET (1) +#define MICROPY_PY_SOCKET_EVENTS (1) #define MICROPY_PY_ONEWIRE (1) #define MICROPY_PY_MACHINE_BITSTREAM (1) #define FFCONF_H "lib/oofatfs/ffconf.h" @@ -108,11 +109,18 @@ #define MICROPY_BEGIN_ATOMIC_SECTION() tls_os_set_critical() #define MICROPY_END_ATOMIC_SECTION(state) tls_os_release_critical(state) +#if MICROPY_PY_SOCKET_EVENTS +#define MICROPY_PY_SOCKET_EVENTS_HANDLER extern void socket_events_handler(void); socket_events_handler(); +#else +#define MICROPY_PY_SOCKET_EVENTS_HANDLER +#endif + #if MICROPY_PY_THREAD #define MICROPY_EVENT_POLL_HOOK \ do { \ extern void mp_handle_pending(bool raise_exc); \ mp_handle_pending(true); \ + MICROPY_PY_SOCKET_EVENTS_HANDLER \ MP_THREAD_GIL_EXIT(); \ tls_os_time_delay(1); \ MP_THREAD_GIL_ENTER(); \ @@ -121,6 +129,7 @@ #define MICROPY_EVENT_POLL_HOOK \ do { \ extern void mp_handle_pending(bool raise_exc); \ + MICROPY_PY_SOCKET_EVENTS_HANDLER \ mp_handle_pending(true); \ tls_os_time_delay(1); \ } while (0);