diff options
Diffstat (limited to 'libhw')
-rw-r--r-- | libhw/rp2040_include/libhw/w5500.h | 2 | ||||
-rw-r--r-- | libhw/w5500.c | 136 | ||||
-rw-r--r-- | libhw/w5500_ll.h | 67 |
3 files changed, 140 insertions, 65 deletions
diff --git a/libhw/rp2040_include/libhw/w5500.h b/libhw/rp2040_include/libhw/w5500.h index 80366a0..3cae620 100644 --- a/libhw/rp2040_include/libhw/w5500.h +++ b/libhw/rp2040_include/libhw/w5500.h @@ -41,7 +41,6 @@ struct _w5500_socket { _w5500_sockintr_ch_t write_ch; /* MODE_{TCP,UDP} */ bool list_open, read_open, write_open; /* MODE_TCP */ - cr_mutex_t cmd_mu; END_PRIVATE(LIBHW_W5500_H) }; @@ -59,6 +58,7 @@ struct w5500 { struct _w5500_socket sockets[8]; struct _w5500_socket *free; cr_sema_t intr; + cr_mutex_t mu; END_PRIVATE(LIBHW_W5500_H) }; diff --git a/libhw/w5500.c b/libhw/w5500.c index e675ae9..f36b9cf 100644 --- a/libhw/w5500.c +++ b/libhw/w5500.c @@ -225,49 +225,55 @@ static COROUTINE w5500_irq_cr(void *_chip) { cr_begin(); for (;;) { - while (!gpio_get(chip->pin_intr)) { - debugf("w5500_irq_cr(): gpio low"); - - uint8_t chipintr = w5500ll_read_common_reg(chip->spidev, chip_interrupt); - if (chipintr) - w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF); - - uint8_t sockmask = w5500ll_read_common_reg(chip->spidev, sock_interrupt); - for (uint8_t socknum = 0; socknum < 8; socknum++) { - if (!(sockmask & (1<<socknum))) - continue; - struct _w5500_socket *socket = &chip->sockets[socknum]; - - uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); - - switch (socket->mode) { - case W5500_MODE_NONE: - break; - case W5500_MODE_TCP: case W5500_MODE_UDP: - uint8_t listen_bits = sockintr & SOCKINTR_CONN, - send_bits = sockintr & (SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT), - recv_bits = sockintr & (SOCKINTR_RECV_DAT|SOCKINTR_RECV_FIN); - - if (listen_bits) { - debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->listen_sema", socknum); - cr_sema_signal(&socket->listen_sema); - } - if (recv_bits) { - debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->read_sema", socknum); - cr_sema_signal(&socket->read_sema); - } - if (send_bits) { - debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->write_ch", socknum); - _w5500_sockintr_ch_send(&socket->write_ch, send_bits); - } - break; - } + cr_mutex_lock(&chip->mu); + bool had_intr = false; + + uint8_t chipintr = w5500ll_read_common_reg(chip->spidev, chip_interrupt); + n_debugf(W5500_LL, "w5500_irq_cr(): chipintr=%"PRIu8, chipintr); + had_intr = had_intr || (chipintr != 0); + if (chipintr) + w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF); + + for (uint8_t socknum = 0; socknum < 8; socknum++) { + struct _w5500_socket *socket = &chip->sockets[socknum]; - w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); + uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); + n_debugf(W5500_LL, "w5500_irq_cr(): sockintr[%"PRIu8"]=%"PRIu8, socknum, sockintr); + had_intr = had_intr || (sockintr != 0); + + switch (socket->mode) { + case W5500_MODE_NONE: + break; + case W5500_MODE_TCP: case W5500_MODE_UDP: + uint8_t listen_bits = sockintr & SOCKINTR_CONN, + send_bits = sockintr & (SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT), + recv_bits = sockintr & (SOCKINTR_RECV_DAT|SOCKINTR_RECV_FIN); + + if (listen_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->listen_sema", socknum); + cr_sema_signal(&socket->listen_sema); + } + if (recv_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->read_sema", socknum); + cr_sema_signal(&socket->read_sema); + } + if (send_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->write_ch", socknum); + _w5500_sockintr_ch_send(&socket->write_ch, send_bits); + } + break; } + + w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); } - cr_sema_wait(&chip->intr); - debugf("w5500_irq_cr(): sema signalled"); + + cr_mutex_unlock(&chip->mu); + + if (!had_intr && gpio_get(chip->pin_intr)) { + debugf("w5500_irq_cr(): looks like all interrupts have been processed, sleeping..."); + cr_sema_wait(&chip->intr); + } else + cr_yield(); } cr_end(); @@ -290,11 +296,9 @@ static inline void w5500_socket_cmd(struct _w5500_socket *socket, uint8_t cmd) { struct w5500 *chip = w5500_socket_chip(socket); uint8_t socknum = socket->socknum; - cr_mutex_lock(&socket->cmd_mu); w5500ll_write_sock_reg(chip->spidev, socknum, command, cmd); while (w5500ll_read_sock_reg(chip->spidev, socknum, command) != 0x00) cr_yield(); - cr_mutex_unlock(&socket->cmd_mu); } static inline void w5500_socket_close(struct _w5500_socket *socket) { @@ -363,15 +367,6 @@ void _w5500_init(struct w5500 *chip, }; } - /* Validate that SPI works correctly. */ - for (uint16_t a = 0; a < 0x100; a++) { - w5500ll_write_sock_reg(chip->spidev, 0, mode, a); - uint8_t b = w5500ll_read_sock_reg(chip->spidev, 0, mode); - if (b != a) - errorf("SPI to W5500 does not appear to be functional: wrote:%d != read:%d", a, b); - } - w5500ll_write_sock_reg(chip->spidev, 0, mode, 0); - /* Initialize the hardware. */ gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, w5500_intrhandler); gpio_set_dir(chip->pin_reset, GPIO_OUT); @@ -433,20 +428,28 @@ static void w5500_post_reset(struct w5500 *chip) { } void w5500_hard_reset(struct w5500 *chip) { + cr_mutex_lock(&chip->mu); + gpio_put(chip->pin_reset, 0); sleep_for_ms(1); /* minimum of 500us */ gpio_put(chip->pin_reset, 1); sleep_for_ms(2); /* minimum of 1ms */ w5500_post_reset(chip); + + cr_mutex_unlock(&chip->mu); } void w5500_soft_reset(struct w5500 *chip) { + cr_mutex_lock(&chip->mu); + w5500ll_write_common_reg(chip->spidev, mode, CHIPMODE_RST); while (w5500ll_read_common_reg(chip->spidev, mode) & CHIPMODE_RST) cr_yield(); w5500_post_reset(chip); + + cr_mutex_unlock(&chip->mu); } static struct net_eth_addr w5500_if_hwaddr(implements_net_iface *_chip) { @@ -460,9 +463,13 @@ static void _w5500_if_up(implements_net_iface *_chip, struct net_iface_config cf struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); assert(chip); + cr_mutex_lock(&chip->mu); + w5500ll_write_common_reg(chip->spidev, ip_gateway_addr, cfg.gateway_addr); w5500ll_write_common_reg(chip->spidev, ip_subnet_mask, cfg.subnet_mask); w5500ll_write_common_reg(chip->spidev, ip_addr, cfg.addr); + + cr_mutex_unlock(&chip->mu); } static void w5500_if_up(implements_net_iface *_chip, struct net_iface_config cfg) { @@ -526,6 +533,8 @@ static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip socket->read_open = socket->write_open = true; restart: + cr_mutex_lock(&chip->mu); + /* Mimics socket.c:socket(). */ w5500_socket_close(socket); w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); @@ -538,6 +547,7 @@ static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip w5500ll_write_sock_reg(chip->spidev, socknum, remote_ip_addr, node); w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port)); w5500_socket_cmd(socket, CMD_CONNECT); + cr_mutex_unlock(&chip->mu); for (;;) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); debugf("tcp_dial(): state=%s", w5500_state_str(state)); @@ -574,12 +584,14 @@ static implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip socket->read_deadline_ns = 0; /* Mimics socket.c:socket(). */ + cr_mutex_lock(&chip->mu); w5500_socket_close(socket); w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_UDP); w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(socket->port)); w5500_socket_cmd(socket, CMD_OPEN); while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_UDP) cr_yield(); + cr_mutex_unlock(&chip->mu); return &socket->implements_net_packet_conn; } @@ -595,6 +607,8 @@ static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_li return NULL; } + cr_mutex_lock(&chip->mu); + /* Mimics socket.c:socket(). */ w5500_socket_close(socket); w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); @@ -605,6 +619,7 @@ static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_li /* Mimics socket.c:listen(). */ w5500_socket_cmd(socket, CMD_LISTEN); + cr_mutex_unlock(&chip->mu); for (;;) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); debugf("tcp_listener.accept() => state=%s", w5500_state_str(state)); @@ -668,8 +683,10 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, s debugf(" => soft closed"); return -NET_ECLOSED; } + cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) { + cr_mutex_unlock(&chip->mu); debugf(" => hard closed"); return -NET_ECLOSED; } @@ -677,6 +694,7 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, s uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); if (freesize < count-done && freesize < min_free_space) { /* Wait for more buffer space. */ + cr_mutex_unlock(&chip->mu); cr_yield(); continue; } @@ -690,6 +708,8 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, s /* Submit the queue. */ w5500_socket_cmd(socket, CMD_SEND); + + cr_mutex_unlock(&chip->mu); switch (_w5500_sockintr_ch_recv(&socket->write_ch)) { case SOCKINTR_SEND_OK: debugf(" => sent %zu", freesize); @@ -745,6 +765,7 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si debugf(" => recv timeout"); return -NET_ERECV_TIMEOUT; } + cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_CLOSE_WAIT: @@ -753,6 +774,7 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si break; /* OK */ default: VCALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); debugf(" => hard closed"); return -NET_ECLOSED; } @@ -763,10 +785,12 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si break; if (state == STATE_TCP_CLOSE_WAIT) { VCALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); debugf(" => EOF"); return 0; } + cr_mutex_unlock(&chip->mu); cr_sema_wait(&socket->read_sema); } assert(avail); @@ -781,6 +805,7 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si w5500_socket_cmd(socket, CMD_RECV); /* Return. */ VCALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); return avail; } @@ -792,6 +817,7 @@ static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr socket->read_open = false; if (wr && socket->write_open) { + cr_mutex_lock(&chip->mu); w5500_socket_cmd(socket, CMD_DISCON); while (socket->write_open) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); @@ -807,6 +833,7 @@ static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr break; } } + cr_mutex_unlock(&chip->mu); } w5500_tcp_maybe_free(chip, socket); @@ -829,8 +856,10 @@ static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, } for (;;) { + cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_UDP) { + cr_mutex_unlock(&chip->mu); debugf(" => closed"); return -NET_ECLOSED; } @@ -841,6 +870,7 @@ static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, break; /* Wait for more buffer space. */ + cr_mutex_unlock(&chip->mu); cr_yield(); } @@ -854,6 +884,7 @@ static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, /* Submit the queue. */ w5500_socket_cmd(socket, CMD_SEND); + cr_mutex_unlock(&chip->mu); switch (_w5500_sockintr_ch_recv(&socket->write_ch)) { case SOCKINTR_SEND_OK: debugf(" => sent"); @@ -901,6 +932,7 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf debugf(" => recv timeout"); return -NET_ERECV_TIMEOUT; } + cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_UDP) { VCALL(bootclock, del_trigger, &trigger); @@ -913,6 +945,7 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf /* We have data to read. */ break; + cr_mutex_unlock(&chip->mu); cr_sema_wait(&socket->read_sema); } assert(avail >= 8); @@ -941,6 +974,7 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf w5500_socket_cmd(socket, CMD_RECV); /* Return. */ VCALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); return len; } diff --git a/libhw/w5500_ll.h b/libhw/w5500_ll.h index c70da0d..25aa6b5 100644 --- a/libhw/w5500_ll.h +++ b/libhw/w5500_ll.h @@ -20,18 +20,26 @@ #include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */ #include <libhw/generic/spi.h> /* for implements_spi */ +/* Config *********************************************************************/ + +#include "config.h" + +#ifndef CONFIG_W5500_LL_DEBUG + #error config.h must define CONFIG_W5500_LL_DEBUG +#endif + /* Low-level protocol built on SPI frames. ***********************************/ /* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */ /* Part 1: Block ID. */ -#define CTL_MASK_BLOCK 0b11111000 -#define _CTL_BLOCK_RES 0b00000 -#define _CTL_BLOCK_REG 0b01000 -#define _CTL_BLOCK_TX 0b10000 -#define _CTL_BLOCK_RX 0b11000 +#define CTL_MASK_BLOCK 0b11111000 +#define _CTL_BLOCK_RES 0b00000 /* chip-wide registers on socknum=0, REServed on socknum>=1 */ +#define _CTL_BLOCK_REG 0b01000 /* socknum-specific registers */ +#define _CTL_BLOCK_TX 0b10000 /* socknum-specific transmit buffer */ +#define _CTL_BLOCK_RX 0b11000 /* socknum-specific receive buffer */ #define CTL_BLOCK_SOCK(n,part) (((n)<<5)|(_CTL_BLOCK_##part)) -#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES) +#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES) /* Part 2: R/W. */ #define CTL_MASK_RW 0b100 @@ -40,20 +48,42 @@ /* Part 3: Operating mode. */ #define CTL_MASK_OM 0b11 -#define CTL_OM_VDM 0b00 -#define CTL_OM_FDM1 0b01 -#define CTL_OM_FDM2 0b10 -#define CTL_OM_FDM4 0b11 +#define CTL_OM_VDM 0b00 /* variable-length data mode */ +#define CTL_OM_FDM1 0b01 /* fixed-length data mode: 1 byte data length */ +#define CTL_OM_FDM2 0b10 /* fixed-length data mode: 2 byte data length */ +#define CTL_OM_FDM4 0b11 /* fixed-length data mode: 4 byte data length */ + +#if CONFIG_W5500_LL_DEBUG +static char *_ctl_block_part_strs[] = { + "RES", + "REG", + "TX", + "RX", +}; +#define PRI_ctl_block "CTL_BLOCK_SOCK(%d, %s)" +#define ARG_ctl_block(b) (((b)>>5) & 0b111), _ctl_block_part_strs[((b)>>3)&0b11] +#endif /* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex. * Lame. */ static inline void -w5500ll_write(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { +#if CONFIG_W5500_LL_DEBUG +#define w5500ll_write(...) _w5500ll_write(__func__, __VA_ARGS__) +_w5500ll_write(const char *func, +#else +w5500ll_write( +#endif + implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { assert(spidev); assert((block & ~CTL_MASK_BLOCK) == 0); assert(data); assert(data_len); +#if CONFIG_W5500_LL_DEBUG + n_debugf(W5500_LL, + "%s(): w5500ll_write(spidev, addr=%#04x, block="PRI_ctl_block", data, data_len=%zu)", + func, addr, ARG_ctl_block(block), data_len); +#endif uint8_t header[3] = { (uint8_t)((addr >> 8) & 0xFF), @@ -68,11 +98,22 @@ w5500ll_write(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, } static inline void -w5500ll_read(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { +#if CONFIG_W5500_LL_DEBUG +#define w5500ll_read(...) _w5500ll_read(__func__, __VA_ARGS__) +_w5500ll_read(const char *func, +#else +w5500ll_read( +#endif + implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { assert(spidev); assert((block & ~CTL_MASK_BLOCK) == 0); assert(data); assert(data_len); +#if CONFIG_W5500_LL_DEBUG + n_debugf(W5500_LL, + "%s(): w5500ll_read(spidev, addr=%#04x, block="PRI_ctl_block", data, data_len=%zu)", + func, addr, ARG_ctl_block(block), data_len); +#endif uint8_t header[3] = { (uint8_t)((addr >> 8) & 0xFF), @@ -222,7 +263,7 @@ static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); #define SOCKINTR_SEND_TIMEOUT ((uint8_t)1<<3) /* ARP or TCP */ #define SOCKINTR_RECV_DAT ((uint8_t)1<<2) /* received data */ #define SOCKINTR_RECV_FIN ((uint8_t)1<<1) /* received FIN */ -#define SOCKINTR_CONN ((uint8_t)1<<1) /* first for SYN, then when SOCKMODE_ESTABLISHED */ +#define SOCKINTR_CONN ((uint8_t)1<<0) /* first for SYN, then when SOCKMODE_ESTABLISHED */ #define STATE_CLOSED ((uint8_t)0x00) |