diff options
-rw-r--r-- | cmd/sbc_harness/hw/w5500.c | 372 | ||||
-rw-r--r-- | cmd/sbc_harness/hw/w5500.h | 3 |
2 files changed, 227 insertions, 148 deletions
diff --git a/cmd/sbc_harness/hw/w5500.c b/cmd/sbc_harness/hw/w5500.c index b6d2b33..5e418f1 100644 --- a/cmd/sbc_harness/hw/w5500.c +++ b/cmd/sbc_harness/hw/w5500.c @@ -59,6 +59,11 @@ #define UNUSED(name) +#define ASSERT_SAMETYPE(a, b) \ + static_assert(_Generic(a, typeof(b): 1, default: 0), \ + "typeof("#a") != typeof("#b")") + + /* Low-level protocol built on SPI frames. ***********************************/ /* A u8 control byte has 3 parts: block-ID, R/W, and @@ -137,88 +142,109 @@ void w5500_spiframe_read(struct spi *spidev, uint16_t addr, uint8_t block, void /* Offsets and sizes for use with that protocol. *****************************/ +struct uint16_be { + uint8_t octets[2]; +}; + struct w5500_block_common_reg { - uint8_t mode; /* MR */ - uint8_t ip_gateway_addr[4]; /* GAR0 ... GAR3 */ - uint8_t ip_subnet_mask[4]; /* SUBR0 ... SUBR3 */ - uint8_t eth_addr[6]; /* SHAR0 ... SHAR5 */ - uint8_t ip_addr[4]; /* SIPR0 ... SIPR3 */ - - uint8_t intlevel_0; /* INTLEVEL0 */ - uint8_t intlevel_1; /* INTLEVEL1 */ - uint8_t interrupt; /* IR */ - uint8_t interrupt_mask; /* IMR */ - uint8_t sock_interrupt; /* SIR */ - uint8_t sock_interrupt_mask; /* SIMR */ - uint8_t retry_time_0; /* RTR0 */ - uint8_t retry_time_1; /* RTR0 */ - uint8_t retry_count; /* RCR */ - - uint8_t ppp_lcp_request_timer; /* PTIMER */ - uint8_t ppp_lcp_magic_bumber; /* PMAGIC */ - uint8_t ppp_dst_eth_addr[6]; /* PHAR0 ... PHAR5 */ - uint8_t ppp_sess_id[2]; /* PSID0 ... PSID1 */ - uint8_t ppp_max_seg_size[2]; /* PMRU0 ... PMRU1 */ - - uint8_t unreachable_ip_addr[4]; /* UIPR0 ... UIPR3 */ - uint8_t unreachable_port[2]; /* UPORTR0, UPORTR1 */ - - uint8_t phy_cfg; /* PHYCFGR */ - - uint8_t _reserved[10]; - - uint8_t chip_version; /* VERSIONR */ + uint8_t mode; /* MR; bitfield, see CHIPMODE_{x} below */ + struct net_ip4_addr ip_gateway_addr; /* GAR0 ... GAR3 */ + struct net_ip4_addr ip_subnet_mask; /* SUBR0 ... SUBR3 */ + struct net_eth_addr eth_addr; /* SHAR0 ... SHAR5 */ + struct net_ip4_addr ip_addr; /* SIPR0 ... SIPR3 */ + + struct uint16_be intlevel; /* INTLEVEL0, INTLEVEL1; minimum time before pin_intr can be pulled low again (time=(intlevel+1)*4/PLL) */ + uint8_t chip_interrupt; /* IR; bitfield, see CHIPINTR_{x} below */ + uint8_t chip_interrupt_mask; /* IMR; bitfield, see CHIPINTR_{x} below, 0=disable, 1=enable */ + uint8_t sock_interrupt; /* SIR; bitfield of which sockets have their .interrupt set */ + uint8_t sock_interrupt_mask; /* SIMR; bitfield of sockets, 0=disable, 1=enable */ + struct uint16_be retry_time; /* RTR0, RTR0; configures re-transmission period, in units of 100µs */ + uint8_t retry_count; /* RCR; configures max re-transmission count */ + + uint8_t ppp_lcp_request_timer; /* PTIMER */ + uint8_t ppp_lcp_magic_bumber; /* PMAGIC */ + struct net_eth_addr ppp_dst_eth_addr; /* PHAR0 ... PHAR5 */ + struct uint16_be ppp_sess_id; /* PSID0 ... PSID1 */ + struct uint16_be ppp_max_seg_size; /* PMRU0 ... PMRU1 */ + + struct net_ip4_addr unreachable_ip_addr; /* UIPR0 ... UIPR3 */ + struct uint16_be unreachable_port; /* UPORTR0, UPORTR1 */ + + uint8_t phy_cfg; /* PHYCFGR */ + + uint8_t _reserved[10]; + + uint8_t chip_version; /* VERSIONR */ }; static_assert(sizeof(struct w5500_block_common_reg) == 0x3A); +/* bitfield */ +#define CHIPMODE_RST ((uint8_t)(1<<7)); /* software reset */ +#define _CHIPMODE_UNUSED6 ((uint8_t)(1<<6)); +#define CHIPMODE_WOL ((uint8_t)(1<<5)); /* wake-on-lan */ +#define CHIPMODE_BLOCK_PING ((uint8_t)(1<<4)); +#define CHIPMODE_PPP ((uint8_t)(1<<3)); +#define _CHIPMODE_UNUSED2 ((uint8_t)(1<<2)); +#define CHIPMODE_FORCE_ARP ((uint8_t)(1<<1)); +#define _CHIPMODE_UNUSED0 ((uint8_t)(1<<0)); + +#define CHIPINTR_CONFLICT ((uint8_t)(1<<7)); /* ARP says remote IP is self */ +#define CHIPINTR_UNREACH ((uint8_t)(1<<6)); +#define CHIPINTR_PPP_CLOSE ((uint8_t)(1<<6)); +#define CHIPINTR_WOL ((uint8_t)(1<<4)); +#define _CHIPINTR_UNUSED3 ((uint8_t)(1<<3)); +#define _CHIPINTR_UNUSED2 ((uint8_t)(1<<2)); +#define _CHIPINTR_UNUSED1 ((uint8_t)(1<<1)); +#define _CHIPINTR_UNUSED0 ((uint8_t)(1<<0)); + struct w5500_block_sock_reg { - uint8_t mode; /* Sn_MR; see MODE_{x} below */ - uint8_t command; /* Sn_CR; see CMD_{x} below */ - uint8_t interrupt; /* Sn_IR */ - uint8_t state; /* Sn_SR; see STATE_{x} below */ - uint8_t local_port[2]; /* Sn_PORT0, Sn_PORT1 */ - uint8_t remote_eth_addr[6]; /* Sn_DHAR0 ... SnDHAR5 */ - uint8_t remote_ip_addr[4]; /* Sn_DIPR0 ... Sn_DIP3 */ - uint8_t remote_port[2]; /* Sn_DPORT0 ... Sn_DPORT1 */ - - uint8_t max_seg_size[2]; /* Sn_MSSR0, Sn_MSSR1 */ - uint8_t _reserved0[1]; - uint8_t ip_tos; /* Sn_TOS */ - uint8_t ip_ttl; /* Sn_TTL */ - uint8_t _reserved1[7]; - - uint8_t rx_buf_size; /* Sn_RXBUF_SIZE */ - uint8_t tx_buf_size; /* Sn_TXBUF_SIZE */ - uint8_t tx_free_size[2]; /* Sn_TX_FSR0, Sn_TX_FSR1 */ - uint8_t tx_read_pointer[2]; /* Sn_TX_RD0, Sn_TX_RD1 */ - uint8_t tx_write_pointer[2]; /* Sn_TX_WR0, Sn_TX_WR1 */ - uint8_t rx_size[2]; /* Sn_RX_RSR0, Sn_RX_RSR1 */ - uint8_t rx_read_pointer[2]; /* Sn_RX_RD0, Sn_RX_RD1 */ - uint8_t rx_write_pointer[2]; /* Sn_RX_WR0, Sn_RX_WR1 */ - - uint8_t interrupt_mask; /* Sn_IMR */ - uint8_t fragment_offset[2]; /* Sn_FRAG0, Sn_FRAG1 */ - uint8_t keepalive_timer; /* Sn_KPALVTR */ + uint8_t mode; /* Sn_MR; see SOCKMODE_{x} below */ + uint8_t command; /* Sn_CR; see CMD_{x} below */ + uint8_t interrupt; /* Sn_IR; bitfield, see SOCKINTR_{x} below */ + uint8_t state; /* Sn_SR; see STATE_{x} below */ + struct uint16_be local_port; /* Sn_PORT0, Sn_PORT1 */ + struct net_eth_addr remote_eth_addr; /* Sn_DHAR0 ... SnDHAR5 */ + struct net_ip4_addr remote_ip_addr; /* Sn_DIPR0 ... Sn_DIP3 */ + struct uint16_be remote_port; /* Sn_DPORT0 ... Sn_DPORT1 */ + + struct uint16_be max_seg_size; /* Sn_MSSR0, Sn_MSSR1 */ + uint8_t _reserved0[1]; + uint8_t ip_tos; /* Sn_TOS */ + uint8_t ip_ttl; /* Sn_TTL */ + uint8_t _reserved1[7]; + + uint8_t rx_buf_size; /* Sn_RXBUF_SIZE */ + uint8_t tx_buf_size; /* Sn_TXBUF_SIZE */ + struct uint16_be tx_free_size; /* Sn_TX_FSR0, Sn_TX_FSR1 */ + struct uint16_be tx_read_pointer; /* Sn_TX_RD0, Sn_TX_RD1 */ + struct uint16_be tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */ + struct uint16_be rx_size; /* Sn_RX_RSR0, Sn_RX_RSR1 */ + struct uint16_be rx_read_pointer; /* Sn_RX_RD0, Sn_RX_RD1 */ + struct uint16_be rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */ + + uint8_t interrupt_mask; /* Sn_IMR */ + struct uint16_be fragment_offset; /* Sn_FRAG0, Sn_FRAG1 */ + uint8_t keepalive_timer; /* Sn_KPALVTR */ }; static_assert(sizeof(struct w5500_block_sock_reg) == 0x30); /* low 4 bits are the main enum, high 4 bits are flags */ -#define MODE_CLOSED ((uint8_t)0b0000) -#define MODE_TCP ((uint8_t)0b0001) -#define MODE_UDP ((uint8_t)0b0010) -#define MODE_MACRAW ((uint8_t)0b0100) +#define SOCKMODE_CLOSED ((uint8_t)0b0000) +#define SOCKMODE_TCP ((uint8_t)0b0001) +#define SOCKMODE_UDP ((uint8_t)0b0010) +#define SOCKMODE_MACRAW ((uint8_t)0b0100) -#define MODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5)) +#define SOCKMODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5)) -#define MODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7)) -#define MODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6)) -#define MODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5)) -#define MODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4)) +#define SOCKMODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7)) +#define SOCKMODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6)) +#define SOCKMODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5)) +#define SOCKMODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4)) -#define MODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7)) -#define MODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6)) -#define MODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5)) -#define MODE_FLAG_MACRAW_BLOCK_V6 ((uint8_t)(1<<4)) +#define SOCKMODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7)) +#define SOCKMODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6)) +#define SOCKMODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5)) +#define SOCKMODE_FLAG_MACRAW_BLOCK_V6 ((uint8_t)(1<<4)) #define CMD_OPEN ((uint8_t)0x01) #define CMD_LISTEN ((uint8_t)0x02) /* TCP-only */ @@ -230,6 +256,15 @@ static_assert(sizeof(struct w5500_block_sock_reg) == 0x30); #define CMD_SEND_KEEP ((uint8_t)0x22) /* TCP-only: send a keepalive without any data */ #define CMD_RECV ((uint8_t)0x40) +#define _SOCKINTR_SEND_UNUSED7 ((uint8_t)1<<7) +#define _SOCKINTR_SEND_UNUSED6 ((uint8_t)1<<6) +#define _SOCKINTR_SEND_UNUSED5 ((uint8_t)1<<5) +#define SOCKINTR_SEND_FINISHED ((uint8_t)1<<4) +#define SOCKINTR_TIMEOUT ((uint8_t)1<<3) +#define SOCKINTR_RECV ((uint8_t)1<<2) +#define SOCKINTR_FIN ((uint8_t)1<<1) +#define SOCKINTR_CON ((uint8_t)1<<1) /* once for SYN, then once when SOCKMODE_ESTABLISHED */ + #define STATE_CLOSED ((uint8_t)0x00) /** @@ -317,46 +352,47 @@ static_assert(sizeof(struct w5500_block_sock_reg) == 0x30); #define STATE_MACRAW ((uint8_t)0x42) -#define REGWRITE_COMMON(spidev, field, val) do { \ - assert(sizeof(val) == sizeof(((struct w5500_block_common_reg){}).field)); \ - typeof(val) lval = val; \ - w5500_spiframe_write(spidev, \ - offsetof(struct w5500_block_common_reg, field), \ - CTL_BLOCK_COMMON_REG, \ - &lval, \ - sizeof(lval)); \ +#define REGWRITE_COMMON(spidev, field, val) do { \ + ASSERT_SAMETYPE(val, (struct w5500_block_common_reg){}.field); \ + typeof(val) lval = val; \ + w5500_spiframe_write(spidev, \ + offsetof(struct w5500_block_common_reg, field), \ + CTL_BLOCK_COMMON_REG, \ + &lval, \ + sizeof(lval)); \ } while (0) -#define REGREAD_COMMON(spidev, field, typ) ({ \ - typ val; \ - assert(sizeof(val) == sizeof(((struct w5500_block_sock_reg){}).field)); \ - w5500_spiframe_read(spidev, \ - offsetof(struct w5500_block_sock_reg, field), \ - CTL_BLOCK_COMMON, \ - &(val), \ - sizeof(val)); \ - val; \ + +#define REGREAD_COMMON(spidev, field, typ) ({ \ + typ val; \ + ASSERT_SAMETYPE(val, (struct w5500_block_common_reg){}.field); \ + w5500_spiframe_read(spidev, \ + offsetof(struct w5500_block_sock_reg, field), \ + CTL_BLOCK_COMMON, \ + &(val), \ + sizeof(val)); \ + val; \ }) -#define REGWRITE_SOCK(spidev, socknum, field, val) do { \ - assert(sizeof(val) == sizeof(((struct w5500_block_sock_reg){}).field)); \ - typeof(val) lval = val; \ - w5500_spiframe_write(spidev, \ - offsetof(struct w5500_block_sock_reg, field), \ - CTL_BLOCK_SOCK(socknum, REG), \ - &lval, \ - sizeof(lval)); \ +#define REGWRITE_SOCK(spidev, socknum, field, val) do { \ + ASSERT_SAMETYPE(val, (struct w5500_block_sock_reg){}.field); \ + typeof(val) lval = val; \ + w5500_spiframe_write(spidev, \ + offsetof(struct w5500_block_sock_reg, field), \ + CTL_BLOCK_SOCK(socknum, REG), \ + &lval, \ + sizeof(lval)); \ } while (0) -#define REGREAD_SOCK(spidev, socknum, field, typ) ({ \ - typ val; \ - assert(sizeof(val) == sizeof(((struct w5500_block_sock_reg){}).field)); \ - w5500_spiframe_read(spidev, \ - offsetof(struct w5500_block_sock_reg, field), \ - CTL_BLOCK_SOCK(socknum, REG), \ - &(val), \ - sizeof(val)); \ - val; \ +#define REGREAD_SOCK(spidev, socknum, field, typ) ({ \ + typ val; \ + ASSERT_SAMETYPE(val, (struct w5500_block_sock_reg){}.field); \ + w5500_spiframe_read(spidev, \ + offsetof(struct w5500_block_sock_reg, field), \ + CTL_BLOCK_SOCK(socknum, REG), \ + &(val), \ + sizeof(val)); \ + val; \ }) /* init() *********************************************************************/ @@ -376,32 +412,14 @@ static struct net_conn_vtable w5500_conn_vtable = { .close = w5500_close, }; -static uint16_t w5500_get_local_port(struct w5500 *self) { - uint16_t ret = self->next_local_port++; - if (self->next_local_port > CONFIG_W5500_LOCAL_PORT_MAX) - self->next_local_port = CONFIG_W5500_LOCAL_PORT_MIN; - return ret; -} - -static struct w5500 *w5500_listener_chip(struct _w5500_listener *listener) { - assert(listener); - assert(listener->socknum < 8); - - struct _w5500_listener *sock0 = &listener[-listener->socknum]; - assert(sock0); - struct w5500 *chip = - ((void *)sock0) - offsetof(struct w5500, listeners); - assert(chip); - return chip; -} - -static struct _w5500_listener *w5500_conn_listener(struct _w5500_conn *conn) { - assert(conn); - - struct _w5500_listener *list = - ((void *)conn) - offsetof(struct _w5500_listener, active_conn); - return list; -} +/* + static uint16_t w5500_get_local_port(struct w5500 *self) { + uint16_t ret = self->next_local_port++; + if (self->next_local_port > CONFIG_W5500_LOCAL_PORT_MAX) + self->next_local_port = CONFIG_W5500_LOCAL_PORT_MIN; + return ret; + } +*/ static void w5500_intrhandler(uint UNUSED(gpio), uint32_t UNUSED(event_mask)) { /* TODO */ @@ -427,7 +445,11 @@ void _w5500_init(struct w5500 *chip, .vtable = &w5500_listener_vtable, .socknum = i, .active_conn = { + /* const-after-init */ .vtable = &w5500_conn_vtable, + /* mutable */ + .read_open = false, + .write_open = false, }, /* mutable */ .port = 0, @@ -440,6 +462,8 @@ void _w5500_init(struct w5500 *chip, w5500_reset(chip, addr); } +/* chip methods ***************************************************************/ + void w5500_reset(struct w5500 *chip, struct net_eth_addr addr) { /* TODO: Replace blocking sleep_ms() with something libcr-friendly. */ gpio_put(chip->pin_reset, 0); @@ -456,8 +480,6 @@ void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) { REGWRITE_COMMON(chip->spidev, ip_addr, cfg.addr); } -/* listen() *******************************************************************/ - implements_net_listener *w5500_listen(struct w5500 *chip, uint8_t socknum, uint16_t port) { assert(chip); @@ -470,13 +492,25 @@ implements_net_listener *w5500_listen(struct w5500 *chip, uint8_t socknum, return &chip->listeners[socknum]; } -/* accept() *******************************************************************/ +/* listener methods ***********************************************************/ -#define ASSERT_LISTENER() \ - struct _w5500_listener *self = \ - VCALL_SELF(struct _w5500_listener, \ +static struct w5500 *w5500_listener_chip(struct _w5500_listener *listener) { + assert(listener); + assert(listener->socknum < 8); + + struct _w5500_listener *sock0 = &listener[-listener->socknum]; + assert(sock0); + struct w5500 *chip = + ((void *)sock0) - offsetof(struct w5500, listeners); + assert(chip); + return chip; +} + +#define ASSERT_LISTENER() \ + struct _w5500_listener *self = \ + VCALL_SELF(struct _w5500_listener, \ implements_net_listener, _self); \ - struct w5500 *chip = w5500_listener_chip(self); \ + struct w5500 *chip = w5500_listener_chip(self); \ uint8_t socknum = self->socknum; static void w5500_cmd_close(struct _w5500_listener *listener) { @@ -496,9 +530,9 @@ static implements_net_conn *w5500_accept(implements_net_listener *_self) { /* Mimics socket.c:socket(). */ w5500_cmd_close(self); - REGWRITE_SOCK(chip->spidev, socknum, mode, MODE_TCP); - struct { uint8_t octets[2]; } port_be = {{self->port>>8, self->port}}; - REGWRITE_SOCK(chip->spidev, socknum, local_port, port_be); + REGWRITE_SOCK(chip->spidev, socknum, mode, SOCKMODE_TCP); + struct uint16_be port = {{self->port>>8, self->port}}; + REGWRITE_SOCK(chip->spidev, socknum, local_port, port); REGWRITE_SOCK(chip->spidev, socknum, command, CMD_OPEN); while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00) cr_yield(); @@ -510,25 +544,35 @@ static implements_net_conn *w5500_accept(implements_net_listener *_self) { while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00) cr_yield(); for (;;) { - uint8_t mode = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t); - switch (mode) { + uint8_t state = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t); + switch (state) { case STATE_TCP_LISTEN: case STATE_TCP_SYNRECV: cr_yield(); break; default: + self->active_conn.read_open = true; + self->active_conn.write_open = true; return &self->active_conn; } } } -/* write() ********************************************************************/ +/* conn methods ***************************************************************/ + +static struct _w5500_listener *w5500_conn_listener(struct _w5500_conn *conn) { + assert(conn); + + struct _w5500_listener *list = + ((void *)conn) - offsetof(struct _w5500_listener, active_conn); + return list; +} -#define ASSERT_CONN() \ - struct _w5500_conn *self = \ +#define ASSERT_CONN() \ + struct _w5500_conn *self = \ VCALL_SELF(struct _w5500_conn, implements_net_conn, _self); \ - struct _w5500_listener *listener = w5500_conn_listener(self); \ - struct w5500 *chip = w5500_listener_chip(listener); \ + struct _w5500_listener *listener = w5500_conn_listener(self); \ + struct w5500 *chip = w5500_listener_chip(listener); \ uint8_t socknum = listener->socknum; static ssize_t w5500_write(implements_net_conn *_self, void *buf, size_t count) { @@ -536,7 +580,10 @@ static ssize_t w5500_write(implements_net_conn *_self, void *buf, size_t count) assert(buf); assert(count); - // TODO + uint8_t state = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t); + if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) + return -1; + } static ssize_t w5500_read(implements_net_conn *_self, void *buf, size_t count) { @@ -546,3 +593,32 @@ static ssize_t w5500_read(implements_net_conn *_self, void *buf, size_t count) { // TODO } + +static int w5500_close(implements_net_conn *_self, bool rd, bool wr) { + ASSERT_CONN(); + + if (rd) + self->read_open = false; + + if (wr && self->write_open) { + REGWRITE_SOCK(chip->spidev, socknum, command, CMD_DISCON); + while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00) + cr_yield(); + while (self->write_open) { + uint8_t state = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t); + switch (state) { + case STATE_TCP_FIN_WAIT: + self->write_open = false; + /* Can still read */ + if (!self->read_open) + w5500_cmd_close(listener); + break; + case STATE_CLOSED: + self->write_open = false; + break; + } + } + } + + return 0; +} diff --git a/cmd/sbc_harness/hw/w5500.h b/cmd/sbc_harness/hw/w5500.h index f8a7034..4f02152 100644 --- a/cmd/sbc_harness/hw/w5500.h +++ b/cmd/sbc_harness/hw/w5500.h @@ -16,6 +16,9 @@ struct _w5500_listener; struct _w5500_conn { /* const-after-init */ implements_net_conn; + /* mutable */ + bool read_open; + bool write_open; }; struct _w5500_listener { |