summaryrefslogtreecommitdiff
path: root/libhw_cr
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-09-28 17:01:59 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-09-28 17:01:59 -0600
commitfce80f2dc24ef45142ff92f74ebf809a39e06768 (patch)
tree01c591ca26460e7ca30597a85adff173d1ffc9a0 /libhw_cr
parentbbf836c886a348bab4fad92d7ba448d1bbc9c910 (diff)
parent35c0f64218f4aa562f2dcde8eace87962bcab4e4 (diff)
Merge branch 'lukeshu/linkdown'HEADmain
Diffstat (limited to 'libhw_cr')
-rw-r--r--libhw_cr/rp2040_include/libhw/w5500.h24
-rw-r--r--libhw_cr/w5500.c93
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));
}