diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-09-28 17:01:59 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-09-28 17:01:59 -0600 |
commit | fce80f2dc24ef45142ff92f74ebf809a39e06768 (patch) | |
tree | 01c591ca26460e7ca30597a85adff173d1ffc9a0 /libhw_cr | |
parent | bbf836c886a348bab4fad92d7ba448d1bbc9c910 (diff) | |
parent | 35c0f64218f4aa562f2dcde8eace87962bcab4e4 (diff) |
Diffstat (limited to 'libhw_cr')
-rw-r--r-- | libhw_cr/rp2040_include/libhw/w5500.h | 24 | ||||
-rw-r--r-- | libhw_cr/w5500.c | 93 |
2 files changed, 94 insertions, 23 deletions
diff --git a/libhw_cr/rp2040_include/libhw/w5500.h b/libhw_cr/rp2040_include/libhw/w5500.h index 43c58a3..d4ca4de 100644 --- a/libhw_cr/rp2040_include/libhw/w5500.h +++ b/libhw_cr/rp2040_include/libhw/w5500.h @@ -12,6 +12,7 @@ #include <libcr_ipc/chan.h> #include <libcr_ipc/mutex.h> #include <libcr_ipc/sema.h> +#include <libmisc/linkedlist.h> #include <libmisc/private.h> #include <libhw/generic/net.h> @@ -41,19 +42,23 @@ struct _w5500_socket { END_PRIVATE(LIBHW_W5500_H); }; +SLIST_DECLARE(_w5500_link_waitlist); + struct w5500 { BEGIN_PRIVATE(LIBHW_W5500_H); /* const-after-init */ lo_interface spi spidev; uint pin_intr; uint pin_reset; + uint pin_linkled; struct net_eth_addr hwaddr; /* mutable */ uint16_t next_local_port; struct _w5500_socket sockets[8]; struct _w5500_socket *free; - cr_sema_t intr; + cr_sema_t intr_sema; + struct _w5500_link_waitlist linkup_waiters; cr_mutex_t mu; END_PRIVATE(LIBHW_W5500_H); }; @@ -72,14 +77,21 @@ LO_IMPLEMENTATION_H(net_iface, struct w5500, w5500_if); * the MCU know that it should do an SPI RPC "get" to see what * happened.) * - A reset pin that the MCU can pull low to reset the W5500. + * + * Additionally (even though it isn't supposed to be a line of + * communication with the MCU) we watch the link-LED output of the + * W5500 (low=has link, high=no link) to know when to do an SPI RPC + * "get" on the PHYCFG register for link status, since that isn't one + * of the things that triggers the interrupt pin. */ -#define w5500_init(self, name, spi, pin_intr, pin_reset, eth_addr) do { \ - bi_decl(bi_2pins_with_names(pin_intr, name" interrupt", \ - pin_reset, name" reset")); \ - _w5500_init(self, spi, pin_intr, pin_reset, eth_addr); \ +#define w5500_init(self, name, spi, pin_intr, pin_reset, pin_linkled, eth_addr) do { \ + bi_decl(bi_3pins_with_names(pin_intr, name" interrupt", \ + pin_reset, name" reset", \ + pin_linkled, name" link-LED")); \ + _w5500_init(self, spi, pin_intr, pin_reset, pin_linkled, eth_addr); \ } while (0) void _w5500_init(struct w5500 *self, - lo_interface spi spi, uint pin_intr, uint pin_reset, + lo_interface spi spi, uint pin_intr, uint pin_reset, uint pin_linkled, struct net_eth_addr addr); /** diff --git a/libhw_cr/w5500.c b/libhw_cr/w5500.c index d757ca1..c519fc2 100644 --- a/libhw_cr/w5500.c +++ b/libhw_cr/w5500.c @@ -179,6 +179,8 @@ static void w5500_tcp_maybe_free(struct w5500 *chip, struct _w5500_socket *sock) w5500_free_socket(chip, sock); } +SLIST_DECLARE_NODE(_w5500_link_waitlist, cid_t); + static COROUTINE w5500_irq_cr(void *_chip) { struct w5500 *chip = _chip; cr_begin(); @@ -187,11 +189,13 @@ static COROUTINE w5500_irq_cr(void *_chip) { cr_mutex_lock(&chip->mu); bool had_intr = false; + bool linkdown = (w5500ll_read_common_reg(chip->spidev, phy_cfg) & PHYCFG_STAT_LINK) == 0; + uint8_t chipintr = w5500ll_read_common_reg(chip->spidev, chip_interrupt); log_n_debugln(W5500_LL, "w5500_irq_cr(): chipintr=", (base2_u8_, chipintr)); had_intr = had_intr || (chipintr != 0); if (chipintr) - w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF); + w5500ll_write_common_reg(chip->spidev, chip_interrupt, chipintr); for (uint8_t socknum = 0; socknum < 8; socknum++) { struct _w5500_socket *socket = &chip->sockets[socknum]; @@ -212,11 +216,11 @@ static COROUTINE w5500_irq_cr(void *_chip) { log_debugln("w5500_irq_cr(): signal sock[", socknum, "]->listen_sema"); cr_sema_signal(&socket->listen_sema); } - if (recv_bits) { + if (recv_bits || linkdown) { log_debugln("w5500_irq_cr(): signal sock[", socknum, "]->read_sema"); cr_sema_signal(&socket->read_sema); } - if (send_bits) { + if (send_bits || (linkdown && cr_chan_can_send(&socket->write_ch))) { log_debugln("w5500_irq_cr(): signal sock[", socknum, "]->write_ch"); cr_chan_send(&socket->write_ch, send_bits); } @@ -226,11 +230,16 @@ static COROUTINE w5500_irq_cr(void *_chip) { w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); } + if (chip->linkup_waiters.front && !linkdown) { + log_debugln("w5500_irq_cr(): signal linkup_waiters"); + cr_unpause(slist_pop_from_front(&chip->linkup_waiters)->val); + } + cr_mutex_unlock(&chip->mu); if (!had_intr && gpio_get(chip->pin_intr)) { log_debugln("w5500_irq_cr(): looks like all interrupts have been processed, sleeping..."); - cr_sema_wait(&chip->intr); + cr_sema_wait(&chip->intr_sema); } else cr_yield(); } @@ -282,21 +291,22 @@ static inline void w5500_socket_close(struct _w5500_socket *socket) { /* init() *********************************************************************/ -static void w5500_intrhandler(void *_chip, uint LM_UNUSED(gpio), enum gpio_irq_level LM_UNUSED(event)) { +static void w5500_intrhandler(void *_chip, uint gpio, enum gpio_irq_level LM_UNUSED(event)) { struct w5500 *chip = _chip; - log_debugln("w5500_intrhandler()"); - cr_sema_signal_from_intrhandler(&chip->intr); + log_debugln("w5500_intrhandler(gpio=", gpio, ")"); + cr_sema_signal_from_intrhandler(&chip->intr_sema); } void _w5500_init(struct w5500 *chip, - lo_interface spi spi, uint pin_intr, uint pin_reset, + lo_interface spi spi, uint pin_intr, uint pin_reset, uint pin_linkled, struct net_eth_addr addr) { assert(chip); assert(!LO_IS_NULL(spi)); /* Initialize the pads. */ - gpiopad_configure(pin_intr, GPIOPAD_CONF(IN(SCHMITT), NOOUT, PULL_DOWN)); - gpiopad_configure(pin_reset, GPIOPAD_CONF(NOIN, OUT(4MA, SLOW), PULL_DOWN)); + gpiopad_configure(pin_intr, GPIOPAD_CONF(IN(SCHMITT), NOOUT, PULL_DOWN)); + gpiopad_configure(pin_reset, GPIOPAD_CONF(NOIN, OUT(4MA, SLOW), PULL_DOWN)); + gpiopad_configure(pin_linkled, GPIOPAD_CONF(IN(SCHMITT), NOOUT, NO_PULL)); /* pulled up externally */ /* Initialize the data structures. */ *chip = (struct w5500){ @@ -304,6 +314,7 @@ void _w5500_init(struct w5500 *chip, .spidev = spi, .pin_intr = pin_intr, .pin_reset = pin_reset, + .pin_linkled = pin_linkled, .hwaddr = addr, /* mutable */ .next_local_port = CONFIG_W5500_LOCAL_PORT_MIN, @@ -337,6 +348,8 @@ void _w5500_init(struct w5500 *chip, /* Initialize the hardware. */ gpioirq_set_and_enable_exclusive_handler(pin_intr, GPIO_IRQ_EDGE_FALL, w5500_intrhandler, chip); + gpioirq_set_and_enable_exclusive_handler(pin_linkled, GPIO_IRQ_EDGE_FALL, w5500_intrhandler, chip); + gpioirq_set_and_enable_exclusive_handler(pin_linkled, GPIO_IRQ_EDGE_RISE, w5500_intrhandler, chip); gpio_set_dir(chip->pin_reset, GPIO_OUT); w5500_hard_reset(chip); @@ -349,8 +362,9 @@ void _w5500_init(struct w5500 *chip, static void w5500_ifdown(struct w5500 *chip) { w5500ll_write_common_reg(chip->spidev, phy_cfg, PHYCFG_CONF(normal, powerdown)); - /* Wait for the PHYCFG_STAT_LINK bit to clear. */ - while ((w5500ll_read_common_reg(chip->spidev, phy_cfg) & (PHYCFG_CONF_MASK|PHYCFG_STAT_LINK)) != PHYCFG_CONF(normal, powerdown)) + /* Wait for the PHYCFG_STAT_LINK bit to clear and the link-LED to turn off. */ + while ((w5500ll_read_common_reg(chip->spidev, phy_cfg) & (PHYCFG_CONF_MASK|PHYCFG_STAT_LINK)) != PHYCFG_CONF(normal, powerdown) || + !gpio_get(chip->pin_linkled)) cr_yield(); } @@ -505,6 +519,24 @@ void w5500_if_ifup(struct w5500 *chip) { cr_mutex_unlock(&chip->mu); } +bool w5500_if_is_link_up(struct w5500 *chip) { + cr_mutex_lock(&chip->mu); + bool linkup = (w5500ll_read_common_reg(chip->spidev, phy_cfg) & PHYCFG_STAT_LINK) != 0; + cr_mutex_unlock(&chip->mu); + return linkup; +} + +void w5500_if_wait_for_link_up(struct w5500 *chip) { + struct _w5500_link_waitlist_node self = { + .val = cr_getcid(), + }; + slist_push_to_rear(&chip->linkup_waiters, &self); + cr_sema_signal(&chip->intr_sema); + cr_pause_and_yield(); + if (chip->linkup_waiters.front) + cr_unpause(slist_pop_from_front(&chip->linkup_waiters)->val); +} + lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, uint16_t local_port) { assert(chip); @@ -721,6 +753,11 @@ static size_t_and_error w5500_tcp_writev(struct _w5500_socket *socket, const str return ERROR_AND(size_t, done, error_new(E_NET_ECLOSED)); } cr_mutex_lock(&chip->mu); + if ((w5500ll_read_common_reg(chip->spidev, phy_cfg) & PHYCFG_STAT_LINK) == 0) { + cr_mutex_unlock(&chip->mu); + log_debugln(" => network down"); + return ERROR_AND(size_t, done, error_new(E_POSIX_ENETDOWN)); + } 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); @@ -746,8 +783,11 @@ static size_t_and_error w5500_tcp_writev(struct _w5500_socket *socket, const str /* Submit the queue. */ w5500_socket_cmd(socket, CMD_SEND); - cr_mutex_unlock(&chip->mu); + cr_mutex_unlock(&chip->mu); /* NB: cr_mutex_unlock() never yields */ switch (cr_chan_recv(&socket->write_ch)) { + case 0: + log_debugln(" => network down"); + return ERROR_AND(size_t, done, error_new(E_POSIX_ENETDOWN)); case SOCKINTR_SEND_OK: log_debugln(" => sent ", freesize); done += freesize; @@ -807,6 +847,12 @@ static size_t_or_error w5500_tcp_readv(struct _w5500_socket *socket, const struc return ERROR_NEW_ERR(size_t, error_new(E_NET_ERECV_TIMEOUT)); } cr_mutex_lock(&chip->mu); + if ((w5500ll_read_common_reg(chip->spidev, phy_cfg) & PHYCFG_STAT_LINK) == 0) { + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); + log_debugln(" => network down"); + return ERROR_NEW_ERR(size_t, error_new(E_POSIX_ENETDOWN)); + } uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_CLOSE_WAIT: @@ -902,8 +948,12 @@ static error w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t co for (;;) { cr_mutex_lock(&chip->mu); - uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - if (state != STATE_UDP) { + if ((w5500ll_read_common_reg(chip->spidev, phy_cfg) & PHYCFG_STAT_LINK) == 0) { + cr_mutex_unlock(&chip->mu); + log_debugln(" => network down"); + return error_new(E_POSIX_ENETDOWN); + } + if (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_UDP) { cr_mutex_unlock(&chip->mu); log_debugln(" => closed"); return error_new(E_NET_ECLOSED); @@ -934,6 +984,9 @@ static error w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t co cr_mutex_unlock(&chip->mu); switch (cr_chan_recv(&socket->write_ch)) { + case 0: + log_debugln(" => network down"); + return error_new(E_POSIX_ENETDOWN); case SOCKINTR_SEND_OK: log_debugln(" => sent"); return ERROR_NULL; @@ -981,9 +1034,15 @@ static size_t_or_error w5500_udp_recvfrom(struct _w5500_socket *socket, void *bu return ERROR_NEW_ERR(size_t, error_new(E_NET_ERECV_TIMEOUT)); } cr_mutex_lock(&chip->mu); - uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - if (state != STATE_UDP) { + if ((w5500ll_read_common_reg(chip->spidev, phy_cfg) & PHYCFG_STAT_LINK) == 0) { + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); + log_debugln(" => network down"); + return ERROR_NEW_ERR(size_t, error_new(E_POSIX_ENETDOWN)); + } + if (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_UDP) { LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); log_debugln(" => hard closed"); return ERROR_NEW_ERR(size_t, error_new(E_NET_ECLOSED)); } |