summaryrefslogtreecommitdiff
path: root/libhw
diff options
context:
space:
mode:
Diffstat (limited to 'libhw')
-rw-r--r--libhw/rp2040_include/libhw/w5500.h2
-rw-r--r--libhw/w5500.c136
-rw-r--r--libhw/w5500_ll.h67
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)