diff --git a/drivers/ninaw10/nina_wifi_bsp.c b/drivers/ninaw10/nina_wifi_bsp.c index c5a9b9b10a..6d04f46efd 100644 --- a/drivers/ninaw10/nina_wifi_bsp.c +++ b/drivers/ninaw10/nina_wifi_bsp.c @@ -97,7 +97,7 @@ int nina_bsp_read_irq(void) { int nina_bsp_spi_slave_select(uint32_t timeout) { // Wait for ACK to go low. for (mp_uint_t start = mp_hal_ticks_ms(); mp_hal_pin_read(MICROPY_HW_NINA_ACK) == 1; mp_hal_delay_ms(1)) { - if ((mp_hal_ticks_ms() - start) >= timeout) { + if (timeout && ((mp_hal_ticks_ms() - start) >= timeout)) { return -1; } } diff --git a/drivers/ninaw10/nina_wifi_drv.c b/drivers/ninaw10/nina_wifi_drv.c index e811650b95..0131476a0a 100644 --- a/drivers/ninaw10/nina_wifi_drv.c +++ b/drivers/ninaw10/nina_wifi_drv.c @@ -60,8 +60,7 @@ #define NINA_ARGS(...) (nina_args_t []) {__VA_ARGS__} #define NINA_VALS(...) (nina_vals_t []) {__VA_ARGS__} -#define NINA_SSELECT_TIMEOUT (10000) -#define NINA_RESPONSE_TIMEOUT (1000) +#define NINA_SSELECT_TIMEOUT (1000) #define NINA_CONNECT_TIMEOUT (10000) #if NINA_DEBUG @@ -130,18 +129,24 @@ typedef enum { NINA_CMD_GET_MAC_ADDR = 0x22, // Sockets commands. - NINA_CMD_SOCKET_OPEN = 0x3F, - NINA_CMD_SOCKET_CLOSE = 0x2E, - NINA_CMD_SOCKET_CONNECT = 0x2D, - NINA_CMD_SOCKET_AVAIL = 0x2B, - NINA_CMD_SOCKET_BIND = 0x28, - NINA_CMD_SOCKET_STATE = 0x2F, NINA_CMD_SOCKET_REMOTE_ADDR = 0x3A, - // TCP commands - NINA_CMD_TCP_SEND = 0x44, - NINA_CMD_TCP_RECV = 0x45, - NINA_CMD_TCP_ACK = 0x2A, + NINA_CMD_SOCKET_SOCKET = 0x70, + NINA_CMD_SOCKET_CLOSE = 0x71, + NINA_CMD_SOCKET_ERRNO = 0x72, + NINA_CMD_SOCKET_BIND = 0x73, + NINA_CMD_SOCKET_LISTEN = 0x74, + NINA_CMD_SOCKET_ACCEPT = 0x75, + NINA_CMD_SOCKET_CONNECT = 0x76, + NINA_CMD_SOCKET_SEND = 0x77, + NINA_CMD_SOCKET_RECV = 0x78, + NINA_CMD_SOCKET_SENDTO = 0x79, + NINA_CMD_SOCKET_RECVFROM = 0x7A, + NINA_CMD_SOCKET_IOCTL = 0x7B, + NINA_CMD_SOCKET_POLL = 0x7C, + NINA_CMD_SOCKET_SETSOCKOPT = 0x7D, + NINA_CMD_SOCKET_GETSOCKOPT = 0x7E, + NINA_CMD_SOCKET_GETPEERNAME = 0x7F, // UDP commands. NINA_CMD_UDP_SEND = 0x46, @@ -201,28 +206,15 @@ static uint8_t nina_bsp_spi_read_byte(void) { return byte; } -static int nina_wait_for_cmd(uint8_t cmd, uint32_t timeout) { - uint8_t buf = 0; - for (mp_uint_t start = mp_hal_ticks_ms(); ;) { - buf = nina_bsp_spi_read_byte(); - if (buf == CMD_ERROR || buf == cmd - || ((mp_hal_ticks_ms() - start) >= timeout)) { - break; - } - mp_hal_delay_ms(1); - } - - return (buf == cmd) ? 0 : -1; -} - static int nina_send_command(uint32_t cmd, uint32_t nargs, uint32_t width, nina_args_t *args) { int ret = -1; uint32_t length = 4; // 3 bytes header + 1 end byte debug_printf("nina_send_command (cmd 0x%x nargs %d width %d): ", cmd, nargs, width); + nina_bsp_spi_slave_deselect(); if (nina_bsp_spi_slave_select(NINA_SSELECT_TIMEOUT) != 0) { - return -1; + goto error_out; } // Send command header. @@ -265,29 +257,31 @@ error_out: static int nina_read_response(uint32_t cmd, uint32_t nvals, uint32_t width, nina_vals_t *vals) { int ret = -1; + uint32_t length = 3; // 3 bytes response header + uint8_t header[3] = {0, 0, 0}; debug_printf("nina_read_response(cmd 0x%x nvals %d width %d): ", cmd, nvals, width); // Read reply - if (nina_bsp_spi_slave_select(NINA_SSELECT_TIMEOUT) != 0) { - return -1; - } - - // Wait for CMD_START - if (nina_wait_for_cmd(CMD_START, NINA_RESPONSE_TIMEOUT) != 0) { + nina_bsp_spi_slave_deselect(); + if (nina_bsp_spi_slave_select(0) != 0) { goto error_out; } - // Should return CMD + REPLY flag. - if (nina_bsp_spi_read_byte() != (cmd | CMD_REPLY)) { + if (nina_bsp_spi_transfer(NULL, header, sizeof(header)) != 0 + || header[1] != (cmd | CMD_REPLY)) { + // Read padding. + uint8_t header_padding = nina_bsp_spi_read_byte(); + (void)header_padding; + debug_printf("nina_read_response() hdr 0x%x 0x%x 0x%x 0x%x\n", + header[0], header[1], header[2], header_padding); goto error_out; } // Sanity check the number of returned values. // NOTE: This is to handle the special case for the scan command. - uint32_t rvals = nina_bsp_spi_read_byte(); - if (nvals > rvals) { - nvals = rvals; + if (nvals > header[2]) { + nvals = header[2]; } // Read return value(s). @@ -310,9 +304,12 @@ static int nina_read_response(uint32_t cmd, uint32_t nvals, uint32_t width, nina // Set the size. *(vals[i].size) = bytes; + length += bytes + width; } - if (nina_bsp_spi_read_byte() != CMD_END) { + // Read CMD_END and padding. + uint8_t rspbuf_end[4]; + if (nina_bsp_spi_transfer(NULL, rspbuf_end, ((length + 1) % 4) + 1) != 0 || rspbuf_end[0] != CMD_END) { goto error_out; } @@ -337,7 +334,6 @@ static int nina_send_command_read_ack(uint32_t cmd, uint32_t nargs, uint32_t wid static int nina_send_command_read_vals(uint32_t cmd, uint32_t nargs, uint32_t argsw, nina_args_t *args, uint32_t nvals, uint32_t valsw, nina_vals_t *vals) { - if (nina_send_command(cmd, nargs, argsw, args) != 0 || nina_read_response(cmd, nvals, valsw, vals) != 0) { return -1; @@ -367,16 +363,6 @@ static int nina_connection_status() { return nina_send_command_read_ack(NINA_CMD_CONN_STATUS, 0, ARG_8BITS, NULL); } -static int nina_socket_status(uint8_t fd) { - return nina_send_command_read_ack(NINA_CMD_SOCKET_STATE, - 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd))); -} - -static int nina_server_socket_status(uint8_t fd) { - return nina_send_command_read_ack(NINA_CMD_SOCKET_STATE & 0xF9, - 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd))); -} - int nina_connect(const char *ssid, uint8_t security, const char *key, uint16_t channel) { uint8_t status = NINA_STATUS_CONNECT_FAILED; @@ -476,10 +462,6 @@ int nina_connected_sta(uint32_t *sta_ip) { return -1; } -int nina_wait_for_sta(uint32_t *sta_ip, uint32_t timeout) { - return -MP_ETIMEDOUT; -} - int nina_ifconfig(nina_ifconfig_t *ifconfig, bool set) { uint16_t ip_len = NINA_IPV4_ADDR_LEN; uint16_t sub_len = NINA_IPV4_ADDR_LEN; @@ -724,257 +706,213 @@ int nina_ioctl(uint32_t cmd, size_t len, uint8_t *buf, uint32_t iface) { return 0; } -int nina_socket_socket(uint8_t type) { +int nina_socket_socket(uint8_t type, uint8_t proto) { uint16_t size = 1; uint8_t sock = 0; - if (nina_send_command_read_vals(NINA_CMD_SOCKET_OPEN, - 0, ARG_8BITS, NULL, + if (nina_send_command_read_vals(NINA_CMD_SOCKET_SOCKET, + 2, ARG_8BITS, NINA_ARGS(ARG_BYTE(type), ARG_BYTE(proto)), 1, ARG_8BITS, NINA_VALS({&size, &sock})) != 0) { return -1; } - return sock; + return (sock == NO_SOCKET_AVAIL) ? -1 : sock; } int nina_socket_close(int fd) { - if (fd > 0 && fd < 255) { - if (nina_send_command_read_ack(NINA_CMD_SOCKET_CLOSE, - 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd))) != SPI_ACK) { - return -1; - } - for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(10)) { - if (nina_socket_status(fd) == SOCKET_STATE_CLOSED) { - break; - } - if ((mp_hal_ticks_ms() - start) >= 5000) { - return -MP_ETIMEDOUT; - } - } + if (nina_send_command_read_ack(NINA_CMD_SOCKET_CLOSE, + 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd))) != SPI_ACK) { + return -1; } return 0; } -int nina_socket_bind(int fd, uint8_t *ip, uint16_t port, int type) { - if (nina_send_command_read_ack(NINA_CMD_SOCKET_BIND, - 3, ARG_8BITS, - NINA_ARGS( - ARG_SHORT(__REVSH(port)), - ARG_BYTE(fd), - ARG_BYTE(type))) != SPI_ACK) { +int nina_socket_errno(int *_errno) { + uint16_t size = 1; + *_errno = 0; + if (nina_send_command_read_vals(NINA_CMD_SOCKET_ERRNO, + 0, ARG_8BITS, NULL, + 1, ARG_8BITS, NINA_VALS({&size, _errno})) != 0) { return -1; } + return 0; +} - // Only TCP sockets' states should be checked. - if (type == NINA_SOCKET_TYPE_TCP && - nina_server_socket_status(fd) != SOCKET_STATE_LISTEN) { +int nina_socket_bind(int fd, uint8_t *ip, uint16_t port) { + if (nina_send_command_read_ack(NINA_CMD_SOCKET_BIND, + 2, ARG_8BITS, + NINA_ARGS( + ARG_BYTE(fd), + ARG_SHORT(__REVSH(port)))) != SPI_ACK) { return -1; } return 0; } int nina_socket_listen(int fd, uint32_t backlog) { - return 0; // No listen ? -} - -int nina_socket_avail(int fd, int type, uint16_t *data) { - uint16_t size = 2; - - if (nina_send_command_read_vals(NINA_CMD_SOCKET_AVAIL, - 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd)), - 1, ARG_8BITS, NINA_VALS({&size, data})) != 0) { + if (nina_send_command_read_ack(NINA_CMD_SOCKET_LISTEN, + 2, ARG_8BITS, + NINA_ARGS( + ARG_BYTE(fd), + ARG_BYTE(backlog))) != SPI_ACK) { return -1; } - - // For TCP sockets in listen state, return 0 if there's no accepted socket. - if (*data == NO_SOCKET_AVAIL && type == NINA_SOCKET_TYPE_TCP - && nina_server_socket_status(fd) == SOCKET_STATE_LISTEN) { - *data = 0; - } - return 0; } -int nina_socket_accept(int fd, uint8_t *ip, uint16_t *port, int *fd_out, int32_t timeout) { - uint16_t sock = 0; - - if (nina_server_socket_status(fd) != SOCKET_STATE_LISTEN) { - return -1; - } - - for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(10)) { - if (nina_socket_avail(fd, NINA_SOCKET_TYPE_TCP, &sock) != 0) { - return -1; - } - if (sock != 0) { - break; - } - if (timeout == 0 || (timeout > 0 && (mp_hal_ticks_ms() - start) >= timeout)) { - return -MP_ETIMEDOUT; - } - } - +int nina_socket_accept(int fd, uint8_t *ip, uint16_t *port, int *fd_out) { + uint16_t fd_len = 1; uint16_t port_len = 2; uint16_t ip_len = NINA_IPV4_ADDR_LEN; - if (nina_send_command_read_vals(NINA_CMD_SOCKET_REMOTE_ADDR, - 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(sock)), - 2, ARG_8BITS, NINA_VALS({&ip_len, ip}, {&port_len, port})) != 0) { + *fd_out = 0; + if (nina_send_command_read_vals(NINA_CMD_SOCKET_ACCEPT, + 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd)), + 3, ARG_8BITS, NINA_VALS({&fd_len, fd_out}, {&ip_len, ip}, {&port_len, port})) != 0) { return -1; } - *fd_out = sock; - *port = __REVSH(*port); - return 0; + return (*fd_out == NO_SOCKET_AVAIL) ? -1 : 0; } -int nina_socket_connect(int fd, uint8_t *ip, uint16_t port, int32_t timeout) { +int nina_socket_connect(int fd, uint8_t *ip, uint16_t port) { if (nina_send_command_read_ack(NINA_CMD_SOCKET_CONNECT, - 4, ARG_8BITS, + 3, ARG_8BITS, NINA_ARGS( - ARG_WORD((*(uint32_t *)ip)), - ARG_SHORT(__REVSH(port)), ARG_BYTE(fd), - ARG_BYTE(NINA_SOCKET_TYPE_TCP))) != SPI_ACK) { + ARG_WORD((*(uint32_t *)ip)), + ARG_SHORT(__REVSH(port)))) != SPI_ACK) { return -1; } - - for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(10)) { - int state = nina_socket_status(fd); - if (state == -1) { - return -1; - } - - if (state == SOCKET_STATE_ESTABLISHED) { - break; - } - - if (timeout == 0 || (timeout > 0 && (mp_hal_ticks_ms() - start) >= timeout)) { - return -MP_ETIMEDOUT; - } - } - return 0; } -int nina_socket_send(int fd, const uint8_t *buf, uint32_t len, int32_t timeout) { +int nina_socket_send(int fd, const uint8_t *buf, uint32_t len) { uint16_t size = 2; uint16_t bytes = 0; - if (nina_socket_status(fd) != SOCKET_STATE_ESTABLISHED) { - return -MP_ENOTCONN; - } - - if (nina_send_command_read_vals(NINA_CMD_TCP_SEND, + if (nina_send_command_read_vals(NINA_CMD_SOCKET_SEND, 2, ARG_16BITS, NINA_ARGS(ARG_BYTE(fd), {len, buf}), - 1, ARG_8BITS, NINA_VALS({&size, &bytes})) != 0 || bytes <= 0) { + 1, ARG_8BITS, NINA_VALS({&size, &bytes})) != 0) { return -1; } - for (mp_uint_t start = mp_hal_ticks_ms(); ;) { - int resp = nina_send_command_read_ack(NINA_CMD_TCP_ACK, - 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd))); + // Only args sizes (not args themselves) are reversed in read_response(). + bytes = __REVSH(bytes); - if (resp == -1) { + if (bytes == 0) { + int _errno = 0; + if (nina_socket_errno(&_errno) != 0 || _errno != 0) { return -1; } - - if (resp == SPI_ACK) { - break; - } - - if (timeout == 0 || (timeout > 0 && (mp_hal_ticks_ms() - start) >= timeout)) { - return -MP_ETIMEDOUT; - } - mp_hal_delay_ms(1); } return bytes; } -int nina_socket_recv(int fd, uint8_t *buf, uint32_t len, int32_t timeout) { - uint16_t bytes = 0; +int nina_socket_recv(int fd, uint8_t *buf, uint32_t len) { + uint16_t bytes = len; - if (nina_socket_status(fd) != SOCKET_STATE_ESTABLISHED) { - return -MP_ENOTCONN; + if (nina_send_command_read_vals(NINA_CMD_SOCKET_RECV, + 2, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd), ARG_SHORT(bytes)), + 1, ARG_16BITS, NINA_VALS({&bytes, buf})) != 0) { + return -1; } - for (mp_uint_t start = mp_hal_ticks_ms(); bytes == 0; mp_hal_delay_ms(1)) { - bytes = len; - if (nina_send_command_read_vals(NINA_CMD_TCP_RECV, - 2, ARG_16BITS, NINA_ARGS(ARG_BYTE(fd), ARG_SHORT(bytes)), - 1, ARG_16BITS, NINA_VALS({&bytes, buf})) != 0) { + if (bytes == 0) { + int _errno = 0; + if (nina_socket_errno(&_errno) != 0 || _errno != 0) { return -1; } - - if (bytes != 0) { - break; - } - - if (timeout == 0 || (timeout > 0 && (mp_hal_ticks_ms() - start) >= timeout)) { - return -MP_ETIMEDOUT; - } } + return bytes; } // Check from the upper layer if the socket is bound, if not then auto-bind it first. -int nina_socket_sendto(int fd, const uint8_t *buf, uint32_t len, uint8_t *ip, uint16_t port, int32_t timeout) { - // TODO do we need to split the packet somewhere? - if (nina_send_command_read_ack(NINA_CMD_SOCKET_CONNECT, - 4, ARG_8BITS, - NINA_ARGS( - ARG_WORD((*(uint32_t *)ip)), - ARG_SHORT(__REVSH(port)), - ARG_BYTE(fd), - ARG_BYTE(NINA_SOCKET_TYPE_UDP))) != SPI_ACK) { +int nina_socket_sendto(int fd, const uint8_t *buf, uint32_t len, uint8_t *ip, uint16_t port) { + uint16_t size = 2; + uint16_t bytes = 0; + + if (nina_send_command_read_vals(NINA_CMD_SOCKET_SENDTO, + 4, ARG_16BITS, NINA_ARGS(ARG_BYTE(fd), ARG_WORD((*(uint32_t *)ip)), ARG_SHORT(__REVSH(port)), {len, buf}), + 1, ARG_8BITS, NINA_VALS({&size, &bytes})) != 0) { return -1; } - // Buffer length and socket number are passed as 16bits. - if (nina_send_command_read_ack(NINA_CMD_UDP_SEND, - 2, ARG_16BITS, NINA_ARGS(ARG_BYTE(fd), {len, buf})) != SPI_ACK) { - return -1; + // Only args sizes (not args themselves) are reversed in read_response(). + bytes = __REVSH(bytes); + + if (bytes == 0) { + int _errno = 0; + if (nina_socket_errno(&_errno) != 0 || _errno != 0) { + return -1; + } } - if (nina_send_command_read_ack(NINA_CMD_UDP_ACK, - 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd))) != SPI_ACK) { - return -1; - } - - return 0; + return bytes; } // Check from the upper layer if the socket is bound, if not then auto-bind it first. -int nina_socket_recvfrom(int fd, uint8_t *buf, uint32_t len, uint8_t *ip, uint16_t *port, int32_t timeout) { - uint16_t bytes = 0; +int nina_socket_recvfrom(int fd, uint8_t *buf, uint32_t len, uint8_t *ip, uint16_t *port) { + uint16_t bytes = len; uint16_t port_len = 2; uint16_t ip_len = NINA_IPV4_ADDR_LEN; - for (mp_uint_t start = mp_hal_ticks_ms(); bytes == 0; mp_hal_delay_ms(1)) { - bytes = len; - if (nina_send_command_read_vals(NINA_CMD_UDP_RECV, - 2, ARG_16BITS, NINA_ARGS(ARG_BYTE(fd), ARG_SHORT(bytes)), - 1, ARG_16BITS, NINA_VALS({&bytes, buf})) != 0) { - return -1; - } - - if (bytes != 0) { - break; - } - - if (timeout == 0 || (timeout > 0 && (mp_hal_ticks_ms() - start) >= timeout)) { - return -MP_ETIMEDOUT; - } - } - if (nina_send_command_read_vals(NINA_CMD_SOCKET_REMOTE_ADDR, - 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd)), - 2, ARG_8BITS, NINA_VALS({&ip_len, ip}, {&port_len, port})) != 0) { + if (nina_send_command_read_vals(NINA_CMD_SOCKET_RECVFROM, + 2, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd), ARG_SHORT(bytes)), + 3, ARG_16BITS, NINA_VALS({&ip_len, ip}, {&port_len, port}, {&bytes, buf})) != 0) { return -1; } + if (bytes == 0) { + int _errno = 0; + if (nina_socket_errno(&_errno) != 0 || _errno != 0) { + return -1; + } + } return bytes; } -int nina_socket_setsockopt(int fd, uint32_t level, uint32_t opt, const void *optval, uint32_t optlen) { - return -1; +int nina_socket_ioctl(int fd, uint32_t cmd, void *argval, uint32_t arglen) { + uint16_t len = arglen; + if (nina_send_command_read_vals(NINA_CMD_SOCKET_IOCTL, + 3, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd), ARG_WORD(cmd), {len, argval}), + 1, ARG_8BITS, NINA_VALS({&len, argval})) != 0 || arglen == 0) { + return -1; + } + return 0; +} + +int nina_socket_poll(int fd, uint8_t *flags) { + uint16_t flags_len = 1; + if (nina_send_command_read_vals(NINA_CMD_SOCKET_POLL, + 1, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd)), + 1, ARG_8BITS, NINA_VALS({&flags_len, flags})) != 0) { + return -1; + } + if (*flags & 0x80) { + // lwip_select() failed. + return -1; + } + return 0; +} + +int nina_socket_setsockopt(int fd, uint32_t level, uint32_t optname, const void *optval, uint16_t optlen) { + if (nina_send_command_read_ack( + NINA_CMD_SOCKET_SETSOCKOPT, + 3, ARG_8BITS, + NINA_ARGS(ARG_BYTE(fd), ARG_WORD(optname), {optlen, optval})) != SPI_ACK) { + return -1; + } + return 0; +} + +int nina_socket_getsockopt(int fd, uint32_t level, uint32_t optname, void *optval, uint16_t optlen) { + if (nina_send_command_read_vals( + NINA_CMD_SOCKET_GETSOCKOPT, + 3, ARG_8BITS, NINA_ARGS(ARG_BYTE(fd), ARG_WORD(optname), ARG_BYTE(optlen)), + 1, ARG_8BITS, NINA_VALS({&optlen, optval})) != 0 || optlen == 0) { + return -1; + } + return 0; } #endif // MICROPY_PY_NINAW10 diff --git a/drivers/ninaw10/nina_wifi_drv.h b/drivers/ninaw10/nina_wifi_drv.h index 4fe701eba1..22163c7286 100644 --- a/drivers/ninaw10/nina_wifi_drv.h +++ b/drivers/ninaw10/nina_wifi_drv.h @@ -38,9 +38,9 @@ #define NINA_MAX_NETWORK_LIST (10) #define NINA_MAX_SOCKET (10) -#define NINA_FW_VER_MAJOR (1) -#define NINA_FW_VER_MINOR (4) -#define NINA_FW_VER_PATCH (8) +#define NINA_FW_VER_MIN_MAJOR (1) +#define NINA_FW_VER_MIN_MINOR (5) +#define NINA_FW_VER_MIN_PATCH (0) #define NINA_FW_VER_MAJOR_OFFS (0) #define NINA_FW_VER_MINOR_OFFS (2) @@ -54,11 +54,9 @@ typedef enum { } nina_security_t; typedef enum { - NINA_SOCKET_TYPE_TCP = 0, - NINA_SOCKET_TYPE_UDP, - NINA_SOCKET_TYPE_TLS, - NINA_SOCKET_TYPE_UDP_MULTICAST, - NINA_SOCKET_TYPE_TLS_BEARSSL + NINA_SOCKET_TYPE_TCP = 1, + NINA_SOCKET_TYPE_UDP = 2, + NINA_SOCKET_TYPE_RAW = 3, } nina_socket_type_t; typedef struct { @@ -92,7 +90,6 @@ int nina_start_ap(const char *ssid, uint8_t security, const char *key, uint16_t int nina_disconnect(void); int nina_isconnected(void); int nina_connected_sta(uint32_t *sta_ip); -int nina_wait_for_sta(uint32_t *sta_ip, uint32_t timeout); int nina_ifconfig(nina_ifconfig_t *ifconfig, bool set); int nina_netinfo(nina_netinfo_t *netinfo); int nina_scan(nina_scan_callback_t scan_callback, void *arg, uint32_t timeout); @@ -101,17 +98,20 @@ int nina_fw_version(uint8_t *fw_ver); int nina_set_hostname(const char *name); int nina_gethostbyname(const char *name, uint8_t *out_ip); int nina_ioctl(uint32_t cmd, size_t len, uint8_t *buf, uint32_t iface); -int nina_socket_socket(uint8_t type); +int nina_socket_socket(uint8_t type, uint8_t proto); int nina_socket_close(int fd); -int nina_socket_bind(int fd, uint8_t *ip, uint16_t port, int type); +int nina_socket_errno(int *_errno); +int nina_socket_bind(int fd, uint8_t *ip, uint16_t port); int nina_socket_listen(int fd, uint32_t backlog); -int nina_socket_avail(int fd, int type, uint16_t *data); -int nina_socket_accept(int fd, uint8_t *ip, uint16_t *port, int *fd_out, int32_t timeout); -int nina_socket_connect(int fd, uint8_t *ip, uint16_t port, int32_t timeout); -int nina_socket_send(int fd, const uint8_t *buf, uint32_t len, int32_t timeout); -int nina_socket_recv(int fd, uint8_t *buf, uint32_t len, int32_t timeout); -int nina_socket_sendto(int fd, const uint8_t *buf, uint32_t len, uint8_t *ip, uint16_t port, int32_t timeout); -int nina_socket_recvfrom(int fd, uint8_t *buf, uint32_t len, uint8_t *ip, uint16_t *port, int32_t timeout); -int nina_socket_setsockopt(int fd, uint32_t level, uint32_t opt, const void *optval, uint32_t optlen); - +int nina_socket_accept(int fd, uint8_t *ip, uint16_t *port, int *fd_out); +int nina_socket_connect(int fd, uint8_t *ip, uint16_t port); +int nina_socket_send(int fd, const uint8_t *buf, uint32_t len); +int nina_socket_recv(int fd, uint8_t *buf, uint32_t len); +int nina_socket_sendto(int fd, const uint8_t *buf, uint32_t len, uint8_t *ip, uint16_t port); +int nina_socket_recvfrom(int fd, uint8_t *buf, uint32_t len, uint8_t *ip, uint16_t *port); +int nina_socket_ioctl(int fd, uint32_t cmd, void *argval, uint32_t arglen); +int nina_socket_poll(int fd, uint8_t *flags); +int nina_socket_setsockopt(int fd, uint32_t level, uint32_t opt, const void *optval, uint16_t optlen); +int nina_socket_getsockopt(int fd, uint32_t level, uint32_t opt, void *optval, uint16_t optlen); +int nina_socket_getpeername(int fd, uint8_t *ip, uint16_t *port); #endif // MICROPY_INCLUDED_DRIVERS_NINAW10_NINA_WIFI_DRV_H diff --git a/extmod/network_ninaw10.c b/extmod/network_ninaw10.c index 0340763073..64b73d6084 100644 --- a/extmod/network_ninaw10.c +++ b/extmod/network_ninaw10.c @@ -53,8 +53,24 @@ typedef struct _nina_obj_t { } nina_obj_t; // For auto-binding UDP sockets -#define BIND_PORT_RANGE_MIN (65000) -#define BIND_PORT_RANGE_MAX (65535) +#define BIND_PORT_RANGE_MIN (65000) +#define BIND_PORT_RANGE_MAX (65535) + +#define SOCKET_IOCTL_FIONREAD (0x4004667F) +#define SOCKET_IOCTL_FIONBIO (0x8004667E) + +#define SOCKET_POLL_RD (0x01) +#define SOCKET_POLL_WR (0x02) +#define SOCKET_POLL_ERR (0x04) + +#define SO_ACCEPTCONN (0x0002) +#define SO_ERROR (0x1007) +#define SO_TYPE (0x1008) +#define SO_NO_CHECK (0x100a) + +#define is_nonblocking_error(errno) ((errno) == MP_EAGAIN || (errno) == MP_EWOULDBLOCK || (errno) == MP_EINPROGRESS) + +#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) static uint16_t bind_port = BIND_PORT_RANGE_MIN; const mod_network_nic_type_t mod_network_nic_type_nina; @@ -85,22 +101,26 @@ STATIC mp_obj_t network_ninaw10_active(size_t n_args, const mp_obj_t *args) { MP_ERROR_TEXT("Failed to initialize Nina-W10 module, error: %d\n"), error); } // check firmware version - uint8_t fw_ver[NINA_FW_VER_LEN]; - if (nina_fw_version(fw_ver) != 0) { + uint8_t semver[NINA_FW_VER_LEN]; + if (nina_fw_version(semver) != 0) { nina_deinit(); mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("Failed to read firmware version, error: %d\n"), error); } - // Check fw version matches the driver. - if ((fw_ver[NINA_FW_VER_MAJOR_OFFS] - 48) != NINA_FW_VER_MAJOR || - (fw_ver[NINA_FW_VER_MINOR_OFFS] - 48) != NINA_FW_VER_MINOR || - (fw_ver[NINA_FW_VER_PATCH_OFFS] - 48) != NINA_FW_VER_PATCH) { - mp_printf(&mp_plat_print, - "Warning: firmware version mismatch, expected %d.%d.%d found: %d.%d.%d\n", - NINA_FW_VER_MAJOR, NINA_FW_VER_MINOR, NINA_FW_VER_PATCH, - fw_ver[NINA_FW_VER_MAJOR_OFFS] - 48, - fw_ver[NINA_FW_VER_MINOR_OFFS] - 48, - fw_ver[NINA_FW_VER_PATCH_OFFS] - 48); + // Check the minimum supported firmware version. + uint32_t fwmin = (NINA_FW_VER_MIN_MAJOR * 100) + + (NINA_FW_VER_MIN_MINOR * 10) + + (NINA_FW_VER_MIN_PATCH * 1); + + uint32_t fwver = (semver[NINA_FW_VER_MAJOR_OFFS] - 48) * 100 + + (semver[NINA_FW_VER_MINOR_OFFS] - 48) * 10 + + (semver[NINA_FW_VER_PATCH_OFFS] - 48) * 1; + + if (fwver < fwmin) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("Firmware version mismatch. Minimum supported firmware is v%d.%d.%d found v%d.%d.%d\n"), + NINA_FW_VER_MIN_MAJOR, NINA_FW_VER_MIN_MINOR, NINA_FW_VER_MIN_PATCH, semver[NINA_FW_VER_MAJOR_OFFS] - 48, + semver[NINA_FW_VER_MINOR_OFFS] - 48, semver[NINA_FW_VER_PATCH_OFFS] - 48); } } else { nina_deinit(); @@ -318,6 +338,7 @@ STATIC mp_obj_t network_ninaw10_status(size_t n_args, const mp_obj_t *args) { STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ninaw10_status_obj, 1, 2, network_ninaw10_status); STATIC mp_obj_t network_ninaw10_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t buf_in) { + debug_printf("ioctl(%d)\n", mp_obj_get_int(cmd_in)); nina_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_buffer_info_t buf; mp_get_buffer_raise(buf_in, &buf, MP_BUFFER_READ | MP_BUFFER_WRITE); @@ -327,45 +348,79 @@ STATIC mp_obj_t network_ninaw10_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_ STATIC MP_DEFINE_CONST_FUN_OBJ_3(network_ninaw10_ioctl_obj, network_ninaw10_ioctl); STATIC int network_ninaw10_gethostbyname(mp_obj_t nic, const char *name, mp_uint_t len, uint8_t *out_ip) { + debug_printf("gethostbyname(%s)\n", name); return nina_gethostbyname(name, out_ip); } +STATIC int network_ninaw10_socket_poll(mod_network_socket_obj_t *socket, uint32_t rwf, int *_errno) { + uint8_t flags = 0; + debug_printf("socket_polling_rw(%d, %d)\n", socket->fileno, rwf); + if (socket->timeout == 0) { + // Non-blocking socket, next socket function will return EAGAIN + return 0; + } + mp_uint_t start = mp_hal_ticks_ms(); + while (!(flags & rwf)) { + if (nina_socket_poll(socket->fileno, &flags) < 0 || (flags & SOCKET_POLL_ERR)) { + nina_socket_errno(_errno); + debug_printf("socket_poll() -> errno %d\n", *_errno); + return -1; + } + if (!(flags & rwf) && socket->timeout != -1 && + mp_hal_ticks_ms() - start > socket->timeout) { + *_errno = MP_ETIMEDOUT; + return -1; + } + } + return 0; +} + +STATIC int network_ninaw10_socket_setblocking(mod_network_socket_obj_t *socket, bool blocking, int *_errno) { + uint32_t nonblocking = !blocking; + // set socket in non-blocking mode + if (nina_socket_ioctl(socket->fileno, SOCKET_IOCTL_FIONBIO, &nonblocking, sizeof(nonblocking)) < 0) { + nina_socket_errno(_errno); + nina_socket_close(socket->fileno); + return -1; + } + return 0; +} + +STATIC int network_ninaw10_socket_listening(mod_network_socket_obj_t *socket, int *_errno) { + int listening = 0; + if (nina_socket_getsockopt(socket->fileno, MOD_NETWORK_SOL_SOCKET, + SO_ACCEPTCONN, &listening, sizeof(listening)) < 0) { + nina_socket_errno(_errno); + debug_printf("socket_getsockopt() -> errno %d\n", *_errno); + return -1; + } + return listening; +} + STATIC int network_ninaw10_socket_socket(mod_network_socket_obj_t *socket, int *_errno) { - uint8_t type; + debug_printf("socket_socket(%d %d %d)\n", socket->domain, socket->type, socket->proto); if (socket->domain != MOD_NETWORK_AF_INET) { *_errno = MP_EAFNOSUPPORT; return -1; } - switch (socket->type) { - case MOD_NETWORK_SOCK_STREAM: - type = NINA_SOCKET_TYPE_TCP; - break; - - case MOD_NETWORK_SOCK_DGRAM: - type = NINA_SOCKET_TYPE_UDP; - break; - - default: - *_errno = MP_EINVAL; - return -1; - } - // open socket - int fd = nina_socket_socket(type); + int fd = nina_socket_socket(socket->type, socket->proto); if (fd < 0) { - *_errno = fd; + nina_socket_errno(_errno); + debug_printf("socket_socket() -> errno %d\n", *_errno); return -1; } // set socket state socket->fileno = fd; socket->bound = false; - return 0; + return network_ninaw10_socket_setblocking(socket, false, _errno); } STATIC void network_ninaw10_socket_close(mod_network_socket_obj_t *socket) { + debug_printf("socket_close(%d)\n", socket->fileno); if (socket->fileno >= 0) { nina_socket_close(socket->fileno); socket->fileno = -1; // Mark socket FD as invalid @@ -373,6 +428,7 @@ STATIC void network_ninaw10_socket_close(mod_network_socket_obj_t *socket) { } STATIC int network_ninaw10_socket_bind(mod_network_socket_obj_t *socket, byte *ip, mp_uint_t port, int *_errno) { + debug_printf("socket_bind(%d, %d)\n", socket->fileno, port); uint8_t type; switch (socket->type) { case MOD_NETWORK_SOCK_STREAM: @@ -388,10 +444,11 @@ STATIC int network_ninaw10_socket_bind(mod_network_socket_obj_t *socket, byte *i return -1; } - int ret = nina_socket_bind(socket->fileno, ip, port, type); + int ret = nina_socket_bind(socket->fileno, ip, port); if (ret < 0) { - *_errno = ret; + nina_socket_errno(_errno); network_ninaw10_socket_close(socket); + debug_printf("socket_bind(%d, %d) -> errno: %d\n", socket->fileno, port, *_errno); return -1; } @@ -401,10 +458,12 @@ STATIC int network_ninaw10_socket_bind(mod_network_socket_obj_t *socket, byte *i } STATIC int network_ninaw10_socket_listen(mod_network_socket_obj_t *socket, mp_int_t backlog, int *_errno) { + debug_printf("socket_listen(%d, %d)\n", socket->fileno, backlog); int ret = nina_socket_listen(socket->fileno, backlog); if (ret < 0) { - *_errno = ret; + nina_socket_errno(_errno); network_ninaw10_socket_close(socket); + debug_printf("socket_listen() -> errno %d\n", *_errno); return -1; } return 0; @@ -412,73 +471,109 @@ STATIC int network_ninaw10_socket_listen(mod_network_socket_obj_t *socket, mp_in STATIC int network_ninaw10_socket_accept(mod_network_socket_obj_t *socket, mod_network_socket_obj_t *socket2, byte *ip, mp_uint_t *port, int *_errno) { + debug_printf("socket_accept(%d)\n", socket->fileno); + + if (network_ninaw10_socket_poll(socket, SOCKET_POLL_RD, _errno) != 0) { + return -1; + } + + *port = 0; int fd = 0; - // Call accept. - int ret = nina_socket_accept(socket->fileno, ip, (uint16_t *)port, &fd, socket->timeout); + int ret = nina_socket_accept(socket->fileno, ip, (uint16_t *)port, &fd); if (ret < 0) { - *_errno = -ret; - // Close socket if not a timeout error. - if (*_errno != MP_ETIMEDOUT) { + nina_socket_errno(_errno); + // Close socket if not a nonblocking error. + if (!is_nonblocking_error(*_errno)) { network_ninaw10_socket_close(socket); } + debug_printf("socket_accept() -> errno %d\n", *_errno); return -1; } // set socket state socket2->fileno = fd; socket2->bound = false; - return 0; + return network_ninaw10_socket_setblocking(socket2, false, _errno); } STATIC int network_ninaw10_socket_connect(mod_network_socket_obj_t *socket, byte *ip, mp_uint_t port, int *_errno) { - int ret = nina_socket_connect(socket->fileno, ip, port, socket->timeout); + debug_printf("socket_connect(%d)\n", socket->fileno); + + int ret = nina_socket_connect(socket->fileno, ip, port); if (ret < 0) { - *_errno = -ret; - // Close socket if not a timeout error. - if (*_errno != MP_ETIMEDOUT) { + nina_socket_errno(_errno); + debug_printf("socket_connect() -> errno %d\n", *_errno); + + // Close socket if not a nonblocking error. + if (!is_nonblocking_error(*_errno)) { network_ninaw10_socket_close(socket); + return -1; + } + + // Poll for write. + if (socket->timeout == 0 || + network_ninaw10_socket_poll(socket, SOCKET_POLL_WR, _errno) != 0) { + return -1; } - return -1; } return 0; } STATIC mp_uint_t network_ninaw10_socket_send(mod_network_socket_obj_t *socket, const byte *buf, mp_uint_t len, int *_errno) { - int ret = nina_socket_send(socket->fileno, buf, len, socket->timeout); + debug_printf("socket_send(%d, %d)\n", socket->fileno, len); + + if (network_ninaw10_socket_poll(socket, SOCKET_POLL_WR, _errno) != 0) { + return -1; + } + + int ret = nina_socket_send(socket->fileno, buf, len); if (ret < 0) { - *_errno = -ret; - // Close socket if not a timeout error. - if (*_errno != MP_ETIMEDOUT) { + nina_socket_errno(_errno); + // Close socket if not a nonblocking error. + if (!is_nonblocking_error(*_errno)) { network_ninaw10_socket_close(socket); } + debug_printf("socket_send() -> errno %d\n", *_errno); return -1; } return ret; } STATIC mp_uint_t network_ninaw10_socket_recv(mod_network_socket_obj_t *socket, byte *buf, mp_uint_t len, int *_errno) { - int ret = 0; - if (socket->type == MOD_NETWORK_SOCK_DGRAM) { - byte ip[4]; - uint16_t port; - ret = nina_socket_recvfrom(socket->fileno, buf, len, ip, &port, socket->timeout); - } else { - ret = nina_socket_recv(socket->fileno, buf, len, socket->timeout); + debug_printf("socket_recv(%d)\n", socket->fileno); + // check if socket in listening state. + if (network_ninaw10_socket_listening(socket, _errno) == 1) { + *_errno = MP_ENOTCONN; + return -1; } + + if (network_ninaw10_socket_poll(socket, SOCKET_POLL_RD, _errno) != 0) { + return -1; + } + + int ret = nina_socket_recv(socket->fileno, buf, len); if (ret < 0) { - *_errno = -ret; - // Close socket if not a timeout error. - if (*_errno != MP_ETIMEDOUT) { + nina_socket_errno(_errno); + if (*_errno == MP_ENOTCONN) { + *_errno = 0; + return 0; + } + // Close socket if not a nonblocking error. + if (!is_nonblocking_error(*_errno)) { network_ninaw10_socket_close(socket); } + debug_printf("socket_recv() -> errno %d\n", *_errno); return -1; } return ret; } STATIC mp_uint_t network_ninaw10_socket_auto_bind(mod_network_socket_obj_t *socket, int *_errno) { - if (socket->bound == false) { + debug_printf("socket_autobind(%d)\n", socket->fileno); + if (socket->bound == false && socket->type != MOD_NETWORK_SOCK_RAW) { if (network_ninaw10_socket_bind(socket, NULL, bind_port, _errno) != 0) { + nina_socket_errno(_errno); + debug_printf("socket_bind() -> errno %d\n", *_errno); return -1; } bind_port++; @@ -489,16 +584,21 @@ STATIC mp_uint_t network_ninaw10_socket_auto_bind(mod_network_socket_obj_t *sock STATIC mp_uint_t network_ninaw10_socket_sendto(mod_network_socket_obj_t *socket, const byte *buf, mp_uint_t len, byte *ip, mp_uint_t port, int *_errno) { + debug_printf("socket_sendto(%d)\n", socket->fileno); // Auto-bind the socket first if the socket is unbound. if (network_ninaw10_socket_auto_bind(socket, _errno) != 0) { return -1; } - int ret = nina_socket_sendto(socket->fileno, buf, len, ip, port, socket->timeout); + if (network_ninaw10_socket_poll(socket, SOCKET_POLL_WR, _errno) != 0) { + return -1; + } + + int ret = nina_socket_sendto(socket->fileno, buf, len, ip, port); if (ret < 0) { - *_errno = -ret; - // Close socket if not a timeout error. - if (*_errno != MP_ETIMEDOUT) { + nina_socket_errno(_errno); + // Close socket if not a nonblocking error. + if (!is_nonblocking_error(*_errno)) { network_ninaw10_socket_close(socket); } return -1; @@ -508,24 +608,25 @@ STATIC mp_uint_t network_ninaw10_socket_sendto(mod_network_socket_obj_t *socket, STATIC mp_uint_t network_ninaw10_socket_recvfrom(mod_network_socket_obj_t *socket, byte *buf, mp_uint_t len, byte *ip, mp_uint_t *port, int *_errno) { - int ret = 0; - if (socket->type == MOD_NETWORK_SOCK_STREAM) { - *port = 0; - *((uint32_t *)ip) = 0; - ret = nina_socket_recv(socket->fileno, buf, len, socket->timeout); - } else { - // Auto-bind the socket first if the socket is unbound. - if (network_ninaw10_socket_auto_bind(socket, _errno) != 0) { - return -1; - } - ret = nina_socket_recvfrom(socket->fileno, buf, len, ip, (uint16_t *)port, socket->timeout); + debug_printf("socket_recvfrom(%d)\n", socket->fileno); + // Auto-bind the socket first if the socket is unbound. + if (network_ninaw10_socket_auto_bind(socket, _errno) != 0) { + return -1; } + + if (network_ninaw10_socket_poll(socket, SOCKET_POLL_RD, _errno) != 0) { + return -1; + } + + *port = 0; + int ret = nina_socket_recvfrom(socket->fileno, buf, len, ip, (uint16_t *)port); if (ret < 0) { - *_errno = -ret; - // Close socket if not a timeout error. - if (*_errno != MP_ETIMEDOUT) { + nina_socket_errno(_errno); + // Close socket if not a nonblocking error. + if (!is_nonblocking_error(*_errno)) { network_ninaw10_socket_close(socket); } + debug_printf("socket_recvfrom() -> errno %d\n", *_errno); return -1; } return ret; @@ -533,53 +634,60 @@ STATIC mp_uint_t network_ninaw10_socket_recvfrom(mod_network_socket_obj_t *socke STATIC int network_ninaw10_socket_setsockopt(mod_network_socket_obj_t *socket, mp_uint_t level, mp_uint_t opt, const void *optval, mp_uint_t optlen, int *_errno) { + debug_printf("socket_setsockopt(%d, %d)\n", socket->fileno, opt); int ret = nina_socket_setsockopt(socket->fileno, level, opt, optval, optlen); if (ret < 0) { - *_errno = ret; + nina_socket_errno(_errno); network_ninaw10_socket_close(socket); + debug_printf("socket_setsockopt() -> errno %d\n", *_errno); return -1; } return 0; } STATIC int network_ninaw10_socket_settimeout(mod_network_socket_obj_t *socket, mp_uint_t timeout_ms, int *_errno) { + debug_printf("socket_settimeout(%d, %d)\n", socket->fileno, timeout_ms); + #if 0 + if (timeout_ms == 0 || timeout_ms == UINT32_MAX) { + // blocking/nonblocking mode + uint32_t nonblocking = (timeout_ms == 0); + ret |= nina_socket_ioctl(socket->fileno, SOCKET_IOCTL_FIONBIO, &nonblocking, sizeof(nonblocking)); + } else { + // timeout provided + uint32_t tv[2] = { + (timeout_ms / 1000), + (timeout_ms % 1000) * 1000, + }; + ret |= nina_socket_setsockopt(socket->fileno, MOD_NETWORK_SOL_SOCKET, MOD_NETWORK_SO_SNDTIMEO, tv, sizeof(tv)); + ret |= nina_socket_setsockopt(socket->fileno, MOD_NETWORK_SOL_SOCKET, MOD_NETWORK_SO_RCVTIMEO, tv, sizeof(tv)); + } + if (ret < 0) { + nina_socket_errno(_errno); + debug_printf("socket_settimeout() -> errno %d\n", *_errno); + } + #endif socket->timeout = timeout_ms; return 0; } STATIC int network_ninaw10_socket_ioctl(mod_network_socket_obj_t *socket, mp_uint_t request, mp_uint_t arg, int *_errno) { mp_uint_t ret = 0; - uint8_t type; - - switch (socket->type) { - case MOD_NETWORK_SOCK_STREAM: - type = NINA_SOCKET_TYPE_TCP; - break; - - case MOD_NETWORK_SOCK_DGRAM: - type = NINA_SOCKET_TYPE_UDP; - break; - - default: - *_errno = MP_EINVAL; - return MP_STREAM_ERROR; - } - + debug_printf("socket_ioctl(%d, %d)\n", socket->fileno, request); if (request == MP_STREAM_POLL) { - if (arg & MP_STREAM_POLL_RD) { - uint16_t avail = 0; - if (nina_socket_avail(socket->fileno, type, &avail) != 0) { - *_errno = MP_EIO; - ret = MP_STREAM_ERROR; - } else if (avail) { - // Readable or accepted socket ready. - ret |= MP_STREAM_POLL_RD; - } + uint8_t flags = 0; + if (nina_socket_poll(socket->fileno, &flags) < 0) { + nina_socket_errno(_errno); + ret = MP_STREAM_ERROR; + debug_printf("socket_ioctl() -> errno %d\n", *_errno); } - if (arg & MP_STREAM_POLL_WR) { + if ((arg & MP_STREAM_POLL_RD) && (flags & SOCKET_POLL_RD)) { + ret |= MP_STREAM_POLL_RD; + } + if ((arg & MP_STREAM_POLL_WR) && (flags & SOCKET_POLL_WR)) { ret |= MP_STREAM_POLL_WR; } } else { + // NOTE: FIONREAD and FIONBIO are supported as well. *_errno = MP_EINVAL; ret = MP_STREAM_ERROR; }