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.
This commit is contained in:
Damien George 2018-04-27 23:53:45 +10:00
parent 98b05e3614
commit 999c8b9711
4 changed files with 107 additions and 0 deletions

View File

@ -47,6 +47,7 @@
#include "lib/utils/pyexec.h" #include "lib/utils/pyexec.h"
#include "uart.h" #include "uart.h"
#include "modmachine.h" #include "modmachine.h"
#include "modnetwork.h"
#include "mpthreadport.h" #include "mpthreadport.h"
// MicroPython runs as a task under FreeRTOS // MicroPython runs as a task under FreeRTOS
@ -110,6 +111,7 @@ soft_reset:
// deinitialise peripherals // deinitialise peripherals
machine_pins_deinit(); machine_pins_deinit();
usocket_events_deinit();
mp_deinit(); mp_deinit();
fflush(stdout); fflush(stdout);

View File

@ -31,4 +31,6 @@ enum { PHY_LAN8720, PHY_TLK110 };
MP_DECLARE_CONST_FUN_OBJ_KW(get_lan_obj); MP_DECLARE_CONST_FUN_OBJ_KW(get_lan_obj);
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_ifconfig_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_ifconfig_obj);
void usocket_events_deinit(void);
#endif #endif

View File

@ -47,6 +47,7 @@
#include "py/mperrno.h" #include "py/mperrno.h"
#include "lib/netutils/netutils.h" #include "lib/netutils/netutils.h"
#include "tcpip_adapter.h" #include "tcpip_adapter.h"
#include "modnetwork.h"
#include "lwip/sockets.h" #include "lwip/sockets.h"
#include "lwip/netdb.h" #include "lwip/netdb.h"
@ -63,10 +64,79 @@ typedef struct _socket_obj_t {
uint8_t type; uint8_t type;
uint8_t proto; uint8_t proto;
unsigned int retries; unsigned int retries;
#if MICROPY_PY_USOCKET_EVENTS
mp_obj_t events_callback;
struct _socket_obj_t *events_next;
#endif
} socket_obj_t; } socket_obj_t;
void _socket_settimeout(socket_obj_t *sock, uint64_t timeout_ms); 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) { NORETURN static void exception_from_errno(int _errno) {
// Here we need to convert from lwip errno values to MicroPython's standard ones // Here we need to convert from lwip errno values to MicroPython's standard ones
if (_errno == EINPROGRESS) { if (_errno == EINPROGRESS) {
@ -209,6 +279,25 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) {
break; 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 // level: IPPROTO_IP
case IP_ADD_MEMBERSHIP: { case IP_ADD_MEMBERSHIP: {
mp_buffer_info_t bufinfo; 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; return ret;
} else if (request == MP_STREAM_CLOSE) { } else if (request == MP_STREAM_CLOSE) {
if (socket->fd >= 0) { 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); int ret = lwip_close_r(socket->fd);
if (ret != 0) { if (ret != 0) {
*errcode = errno; *errcode = errno;

View File

@ -221,11 +221,18 @@ extern const struct _mp_obj_module_t mp_module_onewire;
#define MICROPY_BEGIN_ATOMIC_SECTION() portENTER_CRITICAL_NESTED() #define MICROPY_BEGIN_ATOMIC_SECTION() portENTER_CRITICAL_NESTED()
#define MICROPY_END_ATOMIC_SECTION(state) portEXIT_CRITICAL_NESTED(state) #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 #if MICROPY_PY_THREAD
#define MICROPY_EVENT_POLL_HOOK \ #define MICROPY_EVENT_POLL_HOOK \
do { \ do { \
extern void mp_handle_pending(void); \ extern void mp_handle_pending(void); \
mp_handle_pending(); \ mp_handle_pending(); \
MICROPY_PY_USOCKET_EVENTS_HANDLER \
MP_THREAD_GIL_EXIT(); \ MP_THREAD_GIL_EXIT(); \
MP_THREAD_GIL_ENTER(); \ MP_THREAD_GIL_ENTER(); \
} while (0); } while (0);
@ -234,6 +241,7 @@ extern const struct _mp_obj_module_t mp_module_onewire;
do { \ do { \
extern void mp_handle_pending(void); \ extern void mp_handle_pending(void); \
mp_handle_pending(); \ mp_handle_pending(); \
MICROPY_PY_USOCKET_EVENTS_HANDLER \
asm("waiti 0"); \ asm("waiti 0"); \
} while (0); } while (0);
#endif #endif