summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flashimg/cpu/main.c3
-rw-r--r--libdhcp/dhcp_client.c60
-rw-r--r--libdhcp/tests/test_client.c6
-rw-r--r--libhw_cr/rp2040_include/libhw/w5500.h24
-rw-r--r--libhw_cr/w5500.c93
-rw-r--r--libhw_generic/include/libhw/generic/net.h3
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) \