diff options
-rw-r--r-- | flashimg/cpu/main.c | 3 | ||||
-rw-r--r-- | libdhcp/dhcp_client.c | 60 | ||||
-rw-r--r-- | libdhcp/tests/test_client.c | 6 | ||||
-rw-r--r-- | libhw_cr/rp2040_include/libhw/w5500.h | 24 | ||||
-rw-r--r-- | libhw_cr/w5500.c | 93 | ||||
-rw-r--r-- | libhw_generic/include/libhw/generic/net.h | 3 |
6 files changed, 154 insertions, 35 deletions
diff --git a/flashimg/cpu/main.c b/flashimg/cpu/main.c index ab72316..d830e48 100644 --- a/flashimg/cpu/main.c +++ b/flashimg/cpu/main.c @@ -181,7 +181,7 @@ static lib9p_srv_file_or_error get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), st #define GPIO_PIN_DUT_PWR 23 #define GPIO_PIN_CLK25MHz 24 #define GPIO_PIN_LED 25 -#define GPIO_PIN_UNUSED_C26 26 +#define GPIO_PIN_NET_LNK 26 #define GPIO_PIN_UNUSED_C27 27 #define GPIO_PIN_UNUSED_C28 28 #define GPIO_PIN_UNUSED_C29 29 @@ -304,6 +304,7 @@ COROUTINE init_cr(void *) { LO_BOX(spi, &dev_spi), GPIO_PIN_NET_INT, /* PIN_INTR */ GPIO_PIN_NET_RST, /* PIN_RESET */ + GPIO_PIN_NET_LNK, /* PIN_LINKLED */ ((struct net_eth_addr){{ /* vendor ID: "Wiznet" */ 0x00, 0x08, 0xDC, diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c index 359bd9f..208bc1c 100644 --- a/libdhcp/dhcp_client.c +++ b/libdhcp/dhcp_client.c @@ -461,10 +461,8 @@ static error dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const log_debugln("state ", state_strs[client->state], ": sending DHCP ", dhcp_msgtyp_str(msgtyp)); error err = LO_CALL(client->sock, sendto, scratch_msg, DHCP_MSG_BASE_SIZE + optlen, client_broadcasts ? net_ip4_addr_broadcast : client->lease_server_id, DHCP_PORT_SERVER); - if (!ERROR_IS_NULL(err)) { - log_debugln("error: sendto: ", (error, err)); + if (!ERROR_IS_NULL(err)) return err; - } client->last_sent_msgtyp = msgtyp; return ERROR_NULL; } @@ -573,7 +571,6 @@ static error dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg * ignore: size_t_or_error r = LO_CALL(client->sock, recvfrom, &ret->raw, sizeof(ret->raw), &srv_addr, &srv_port); if (r.is_err) - /* msg_len is -errno */ return r.err; msg_len = r.size_t; @@ -743,8 +740,15 @@ static void dhcp_client_setstate(struct dhcp_client *client, uint8_t send_msgtyp, const char *errstr, struct dhcp_recv_msg *scratch_msg) { if (send_msgtyp) { error err = dhcp_client_send(client, send_msgtyp, errstr, &scratch_msg->raw); + if (!ERROR_IS_NULL(err)) { + log_errorln("error: sendto: ", (error, err)); + if (err.num == E_POSIX_ENETDOWN) + newstate = STATE_INIT; + } error_cleanup(&err); } + if (newstate == STATE_INIT) + LO_CALL(client->iface, ifcfg, (struct net_iface_config){}); client->state = newstate; } @@ -757,6 +761,17 @@ static void dhcp_client_setstate(struct dhcp_client *client, log_debugln("loop: state=", state_strs[client->state]); switch (client->state) { case STATE_INIT: + log_debugln("state ", state_strs[client->state], ": wait for link up"); + uint64_t start = LO_CALL(bootclock, get_time_ns); + LO_CALL(client->iface, wait_for_link_up); + uint64_t end = LO_CALL(bootclock, get_time_ns); + log_debugln("state ", state_strs[client->state], ": link up after ", (duration, end-start)); + /* Just because we've processed link-up on our end + * doesn't mean the router has; 500ms now is better than + * the REQUEST packet getting dropped and us having to + * wait an entire CONFIG_DHCP_SELECTING_NS before + * realizing it. */ + sleep_for_ms(500); client->xid = rand_uint32(); client->time_ns_init = LO_CALL(bootclock, get_time_ns); dhcp_client_setstate(client, STATE_SELECTING, DHCP_MSGTYP_DISCOVER, NULL, scratch_msg); @@ -776,10 +791,15 @@ static void dhcp_client_setstate(struct dhcp_client *client, } break; case E_NET_ERECV_TIMEOUT: + log_errorln("error: recvfrom: ", (error, err), " (action: reset to state=INIT)"); + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); + break; + case E_POSIX_ENETDOWN: + log_errorln("error: recvfrom: ", (error, err), " (action: reset to state=INIT)"); dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); break; default: - log_debugln("error: recvfrom: ", (error, err)); + log_errorln("error: recvfrom: ", (error, err), " (action: ignore)"); } break; case STATE_REQUESTING: @@ -803,8 +823,12 @@ static void dhcp_client_setstate(struct dhcp_client *client, /* ignore */ } break; + case E_POSIX_ENETDOWN: + log_errorln("error: recvfrom: ", (error, err), " (action: reset to state=INIT)"); + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); + break; default: - log_debugln("error: recvfrom: ", (error, err)); + log_errorln("error: recvfrom: ", (error, err), " (action: ignore)"); } break; case STATE_BOUND: @@ -814,10 +838,15 @@ static void dhcp_client_setstate(struct dhcp_client *client, /* discard */ break; case E_NET_ERECV_TIMEOUT: + /* Not logging the error here is intentional. */ dhcp_client_setstate(client, STATE_RENEWING, DHCP_MSGTYP_REQUEST, NULL, scratch_msg); break; + case E_POSIX_ENETDOWN: + log_errorln("error: recvfrom: ", (error, err), " (action: reset to state=INIT)"); + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); + break; default: - log_debugln("error: recvfrom: ", (error, err)); + log_errorln("error: recvfrom: ", (error, err), " (action: ignore)"); } break; case STATE_RENEWING: @@ -841,11 +870,16 @@ static void dhcp_client_setstate(struct dhcp_client *client, } break; case E_NET_ERECV_TIMEOUT: + log_errorln("error: recvfrom: ", (error, err), " (action: forget server, set state=REBINDING)"); client->lease_server_id = net_ip4_addr_zero; dhcp_client_setstate(client, STATE_REBINDING, DHCP_MSGTYP_REQUEST, NULL, scratch_msg); break; + case E_POSIX_ENETDOWN: + log_errorln("error: recvfrom: ", (error, err), " (action: reset to state=INIT)"); + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); + break; default: - log_debugln("error: recvfrom: ", (error, err)); + log_errorln("error: recvfrom: ", (error, err), " (action: ignore)"); } break; case STATE_REBINDING: @@ -866,11 +900,15 @@ static void dhcp_client_setstate(struct dhcp_client *client, } break; case E_NET_ERECV_TIMEOUT: - LO_CALL(client->iface, ifcfg, (struct net_iface_config){}); - dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); + log_errorln("error: recvfrom: ", (error, err), " (action: reset to state=INIT)"); + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); + break; + case E_POSIX_ENETDOWN: + log_errorln("error: recvfrom: ", (error, err), " (action: reset to state=INIT)"); + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); break; default: - log_debugln("error: recvfrom: ", (error, err)); + log_errorln("error: recvfrom: ", (error, err), " (action: ignore)"); } break; case STATE_INIT_REBOOT: diff --git a/libdhcp/tests/test_client.c b/libdhcp/tests/test_client.c index 772d778..0112e76 100644 --- a/libdhcp/tests/test_client.c +++ b/libdhcp/tests/test_client.c @@ -99,6 +99,12 @@ static void test_ifcfg(struct test_iface *LM_UNUSED(self), struct net_iface_conf cr_exit(); } +static bool test_is_link_up(struct test_iface *LM_UNUSED(self)) { + return true; +} + +static void test_wait_for_link_up(struct test_iface *LM_UNUSED(self)) {} + static bool test_arp_ping(struct test_iface *LM_UNUSED(self), struct net_ip4_addr LM_UNUSED(addr)) { return false; } 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)); } diff --git a/libhw_generic/include/libhw/generic/net.h b/libhw_generic/include/libhw/generic/net.h index 90938cf..beab341 100644 --- a/libhw_generic/include/libhw/generic/net.h +++ b/libhw_generic/include/libhw/generic/net.h @@ -113,6 +113,9 @@ struct net_iface_config { LO_FUNC(void , ifdown ) \ LO_FUNC(void , ifcfg , struct net_iface_config) \ \ + LO_FUNC(bool , is_link_up) \ + LO_FUNC(void , wait_for_link_up) \ + \ LO_FUNC(lo_interface net_stream_listener, tcp_listen, uint16_t local_port) \ LO_FUNC(net_stream_conn_or_error , tcp_dial , struct net_ip4_addr remote_node, uint16_t remote_port) \ LO_FUNC(lo_interface net_packet_conn , udp_conn , uint16_t local_port) \ |