From 191c5ae0cde0b753eaa6d4e9074ddab465933700 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Wed, 30 Oct 2024 01:44:34 -0600 Subject: Finish the W5500 driver? --- libhw/w5500.c | 466 +++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 316 insertions(+), 150 deletions(-) (limited to 'libhw/w5500.c') diff --git a/libhw/w5500.c b/libhw/w5500.c index 603863a..1923a17 100644 --- a/libhw/w5500.c +++ b/libhw/w5500.c @@ -105,6 +105,40 @@ #define UNUSED(name) #define ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0])) +/* vtables ********************************************************************/ + +static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *); + +static void w5500_tcp_set_read_deadline(implements_net_stream_conn *, uint64_t ns); +static ssize_t w5500_tcp_read( implements_net_stream_conn *, void *, size_t); +static ssize_t w5500_tcp_write( implements_net_stream_conn *, void *, size_t); +static int w5500_tcp_close( implements_net_stream_conn *, bool rd, bool wr); + +static void w5500_udp_set_read_deadline(implements_net_packet_conn *, uint64_t ns); +static ssize_t w5500_udp_recvfrom( implements_net_packet_conn *, void *, size_t, + struct net_ip4_addr *, uint16_t *); +static ssize_t w5500_udp_sendto( implements_net_packet_conn *, void *, size_t, + struct net_ip4_addr, uint16_t); +static int w5500_udp_close( implements_net_packet_conn *); + +static struct net_stream_listener_vtable w5500_tcp_listener_vtable = { + .accept = w5500_tcp_accept, +}; + +static struct net_stream_conn_vtable w5500_tcp_conn_vtable = { + .set_read_deadline = w5500_tcp_set_read_deadline, + .read = w5500_tcp_read, + .write = w5500_tcp_write, + .close = w5500_tcp_close, +}; + +static struct net_packet_conn_vtable w5500_udp_conn_vtable = { + .set_read_deadline = w5500_udp_set_read_deadline, + .recvfrom = w5500_udp_recvfrom, + .sendto = w5500_udp_sendto, + .close = w5500_udp_close, +}; + /* mid-level utilities ********************************************************/ #if 0 @@ -129,18 +163,26 @@ static COROUTINE w5500_irq_cr(void *_chip) { for (uint8_t socknum = 0; socknum < 8; socknum++) { if (!(sockmask & (1<listeners[socknum]; + struct _w5500_socket *socket = &chip->sockets[socknum]; uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); - /* SOCKINTR_SEND_OK is useless. */ - uint8_t listen_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_CONN), - read_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_RECV|SOCKINTR_FIN); - - if (listen_bits) - cr_sema_signal(&listener->listen_sema); - if (read_bits) - cr_sema_signal(&listener->read_sema); + switch (socket->mode) { + case W5500_MODE_NONE: + break; + case W5500_MODE_TCP: + /* SOCKINTR_SEND_OK is useless; just count on the write methods to + * poll instead of waiting on notification from here. */ + uint8_t listen_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_CONN), + read_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_RECV|SOCKINTR_FIN); + + if (listen_bits) + cr_sema_signal(&socket->listen_sema); + /* fallthrough */ + case W5500_MODE_UDP: + if (read_bits) + cr_sema_signal(&socket->read_sema); + } w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); } @@ -149,22 +191,60 @@ static COROUTINE w5500_irq_cr(void *_chip) { cr_end(); } -/* init() *********************************************************************/ +static struct w5500 *w5500_socket_chip(struct _w5500_socket *socket) { + assert(socket); + assert(socket->socknum < 8); -static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *listener); -static ssize_t w5500_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count); -static ssize_t w5500_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count); -static int w5500_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr); + struct _w5500_socket *sock0 = &socket[-(socket->socknum)]; + assert(sock0); + struct w5500 *chip = + ((void *)sock0) - offsetof(struct w5500, sockets); + assert(chip); + return chip; +} -static struct net_stream_listener_vtable w5500_tcp_listener_vtable = { - .accept = w5500_tcp_accept, -}; +static inline void w5500_socket_cmd(struct _w5500_socket *socket, uint8_t cmd) { + assert(socket); + struct w5500 *chip = w5500_socket_chip(socket); + uint8_t socknum = socket->socknum; -static struct net_stream_conn_vtable w5500_tcp_conn_vtable = { - .read = w5500_tcp_read, - .write = w5500_tcp_write, - .close = w5500_tcp_close, -}; + 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) { + assert(socket); + struct w5500 *chip = w5500_socket_chip(socket); + uint8_t socknum = socket->socknum; + + /* Send CMD_CLOSE. */ + w5500_socket_cmd(socket, CMD_CLOSE); + /* Wait for it to transition to STATE_CLOSED. */ + while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_CLOSED) + cr_yield(); + + /* Update our MCU-side bookkeeping. */ + socket->mode = W5500_MODE_NONE; + socket->port = 0; + socket->read_deadline_ns = 0; + socket->read_open = socket->write_open = false; +} + +#define ASSERT_SELF(_iface, _mode) \ + struct _w5500_socket *socket = \ + VCALL_SELF(struct _w5500_socket, \ + implements_net_##_iface, _socket); \ + assert(socket); \ + uint8_t socknum = socket->socknum; \ + assert(socknum < 8); \ + assert(socket->mode == W5500_MODE_##_mode); \ + struct w5500 *chip = w5500_socket_chip(socket); \ + assert(chip); + +/* init() *********************************************************************/ static struct w5500 *w5500_chips[CONFIG_W5500_NUM] = {0}; @@ -191,19 +271,14 @@ void _w5500_init(struct w5500 *chip, .next_local_port = CONFIG_W5500_LOCAL_PORT_MIN, }; for (uint8_t i = 0; i < 8; i++) { - chip->listeners[i] = (struct _w5500_tcp_listener){ + chip->sockets[i] = (struct _w5500_socket){ /* const-after-init */ - .vtable = &w5500_tcp_listener_vtable, + .implements_net_stream_listener = { .vtable = &w5500_tcp_listener_vtable }, + .implements_net_stream_conn = { .vtable = &w5500_tcp_conn_vtable }, + .implements_net_packet_conn = { .vtable = &w5500_udp_conn_vtable }, .socknum = i, - .active_conn = { - /* const-after-init */ - .vtable = &w5500_tcp_conn_vtable, - /* mutable */ - .read_open = false, - .write_open = false, - }, /* mutable */ - .port = 0, + /* these all get initialized to the zero values */ }; } @@ -296,99 +371,57 @@ implements_net_stream_listener *w5500_tcp_listen(struct w5500 *chip, uint8_t soc assert(socknum < 8); assert(port); - assert(chip->listeners[socknum].port == 0); - chip->listeners[socknum].port = port; + assert(chip->sockets[socknum].mode == W5500_MODE_NONE); + chip->sockets[socknum].mode = W5500_MODE_TCP; + chip->sockets[socknum].port = port; + chip->sockets[socknum].read_deadline_ns = 0; - return &chip->listeners[socknum]; + return &chip->sockets[socknum].implements_net_stream_listener; } -/* implements_net_packet_conn *w5500_udp_conn(struct w5500 *chip, uint8_t socknum, uint16_t port) { assert(chip); assert(socknum < 8); assert(port); - assert(chip->listeners[socknum].port == 0); - chip->listeners[socknum].port = port; + assert(chip->sockets[socknum].mode == W5500_MODE_NONE); + chip->sockets[socknum].mode = W5500_MODE_UDP; + chip->sockets[socknum].port = port; + chip->sockets[socknum].read_deadline_ns = 0; - return &chip->listeners[socknum]; + return &chip->sockets[socknum].implements_net_packet_conn; } -*/ /* tcp_listener methods *******************************************************/ -static struct w5500 *w5500_tcp_listener_chip(struct _w5500_tcp_listener *listener) { - assert(listener); - assert(listener->socknum < 8); - - struct _w5500_tcp_listener *sock0 = &listener[-listener->socknum]; - assert(sock0); - struct w5500 *chip = - ((void *)sock0) - offsetof(struct w5500, listeners); - assert(chip); - return chip; -} - -static inline void w5500_tcp_listener_cmd(struct _w5500_tcp_listener *listener, uint8_t cmd) { - assert(listener); - struct w5500 *chip = w5500_tcp_listener_chip(listener); - uint8_t socknum = listener->socknum; - - cr_mutex_lock(&listener->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(&listener->cmd_mu); -} - -static inline void w5500_tcp_listener_cmd_close(struct _w5500_tcp_listener *listener) { - assert(listener); - struct w5500 *chip = w5500_tcp_listener_chip(listener); - uint8_t socknum = listener->socknum; - - w5500_tcp_listener_cmd(listener, CMD_CLOSE); - w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, 0xFF); - while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_CLOSED) - cr_yield(); - - listener->active_conn.read_open = listener->active_conn.write_open = false; -} - -#define ASSERT_LISTENER() \ - struct _w5500_tcp_listener *self = \ - VCALL_SELF(struct _w5500_tcp_listener, \ - implements_net_stream_listener, _self); \ - struct w5500 *chip = w5500_tcp_listener_chip(self); \ - uint8_t socknum = self->socknum; - -static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *_self) { - ASSERT_LISTENER(); +static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *_socket) { + ASSERT_SELF(stream_listener, TCP); restart: /* Mimics socket.c:socket(). */ - w5500_tcp_listener_cmd_close(self); + w5500_socket_close(socket); w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); - w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(self->port)); - w5500_tcp_listener_cmd(self, CMD_OPEN); + 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_TCP_INIT) cr_yield(); /* Mimics socket.c:listen(). */ - w5500_tcp_listener_cmd(self, CMD_LISTEN); + w5500_socket_cmd(socket, CMD_LISTEN); for (;;) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_LISTEN: case STATE_TCP_SYNRECV: - cr_sema_wait(&self->listen_sema); + cr_sema_wait(&socket->listen_sema); break; case STATE_TCP_ESTABLISHED: - self->active_conn.read_open = true; + socket->read_open = true; /* fall-through */ case STATE_TCP_CLOSE_WAIT: - self->active_conn.write_open = true; - return &self->active_conn; + socket->write_open = true; + return &socket->implements_net_stream_conn; default: goto restart; } @@ -397,32 +430,17 @@ static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listen /* tcp_conn methods ***********************************************************/ -static struct _w5500_tcp_listener *w5500_tcp_conn_listener(struct _w5500_tcp_conn *conn) { - assert(conn); - - struct _w5500_tcp_listener *list = - ((void *)conn) - offsetof(struct _w5500_tcp_listener, active_conn); - return list; -} - -#define ASSERT_CONN() \ - struct _w5500_tcp_conn *self = \ - VCALL_SELF(struct _w5500_tcp_conn, implements_net_stream_conn, _self); \ - struct _w5500_tcp_listener *listener = w5500_tcp_conn_listener(self); \ - struct w5500 *chip = w5500_tcp_listener_chip(listener); \ - uint8_t socknum = listener->socknum; - -static ssize_t w5500_tcp_write(implements_net_stream_conn *_self, void *buf, size_t count) { - ASSERT_CONN(); +static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, size_t count) { + ASSERT_SELF(stream_conn, TCP); assert(buf); assert(count); /* What we really want is to pause until we receive an ACK for * some data we just queued, so that we can line up some new * data to keep the buffer full. But that's not what - * SEND_FINIAIUI, the SEND_FINISHED interrupt doesn't fire - * until we receive the *last* ACK for the data, when the - * buffer is entirely empty. + * SEND_FINISHED does AIUI, the SEND_FINISHED interrupt + * doesn't fire until we receive the *last* ACK for the data, + * when the buffer is entirely empty. * * Which means we basically have to busy-poll for space in the * buffer becoming available. @@ -439,11 +457,11 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_self, void *buf, siz size_t done = 0; while (done < count) { - if (!self->write_open) - return -1; + if (!socket->write_open) + return -NET_ECLOSED; uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) - return -1; + return -NET_ECLOSED; uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); if (freesize < count-done && freesize < min_free_space) { @@ -453,72 +471,107 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_self, void *buf, siz } /* Queue data to be sent. */ + if ((size_t)freesize > count-done) + freesize = count-done; uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer)); w5500ll_write(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((char *)buf)[done], freesize); w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+freesize)); /* Submit the queue. */ - w5500_tcp_listener_cmd(listener, CMD_SEND); + w5500_socket_cmd(socket, CMD_SEND); done += freesize; } return done; } -static ssize_t w5500_tcp_read(implements_net_stream_conn *_self, void *buf, size_t count) { - ASSERT_CONN(); +static void w5500_tcp_set_read_deadline(implements_net_stream_conn *_socket, uint64_t ns) { + ASSERT_SELF(stream_conn, TCP); + socket->read_deadline_ns = ns; +} + +static void w5500_tcp_alarm_handler(void *_arg) { + struct _w5500_socket *socket = _arg; + cr_sema_signal(&socket->read_sema); +} + +static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, size_t count) { + ASSERT_SELF(stream_conn, TCP); assert(buf); assert(count); - size_t done = 0; - while (!done) { - if (!self->read_open) - return -1; + struct alarmclock_trigger trigger = {0}; + if (socket->read_deadline_ns) + VCALL(bootclock, add_trigger, &trigger, + socket->read_deadline_ns, + w5500_tcp_alarm_handler, + socket); + + /* Wait until there is data to read. */ + uint16_t avail = 0; + for (;;) { + if (!socket->read_open) { + VCALL(bootclock, del_trigger, &trigger); + return -NET_ECLOSED; + } + if (socket->read_deadline_ns && socket->read_deadline_ns >= VCALL(bootclock, get_time_ns)) { + VCALL(bootclock, del_trigger, &trigger); + return -NET_ETIMEDOUT; + } uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_CLOSE_WAIT: - return 0; /* EOF */ - case STATE_TCP_ESTABLISHED: case STATE_TCP_FIN_WAIT: + case STATE_TCP_ESTABLISHED: + case STATE_TCP_FIN_WAIT: break; /* OK */ default: - return -1; + VCALL(bootclock, del_trigger, &trigger); + return -NET_ECLOSED; } - uint16_t avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); - if (!avail) { - cr_sema_wait(&listener->read_sema); - continue; + avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); + if (avail) + /* We have data to read. */ + break; + if (state == STATE_TCP_CLOSE_WAIT) { + VCALL(bootclock, del_trigger, &trigger); + return 0; /* EOF */ } - if ((size_t)avail > count) - avail = count; - uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); - w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((char *)buf)[done], avail); - w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail)); - - w5500_tcp_listener_cmd(listener, CMD_RECV); - done += avail; + + cr_sema_wait(&socket->read_sema); } - return done; + assert(avail); + uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); + /* Read the data. */ + if ((size_t)avail > count) + avail = count; + w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), buf, avail); + /* Tell the chip that we read the data. */ + w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail)); + w5500_socket_cmd(socket, CMD_RECV); + /* Return. */ + VCALL(bootclock, del_trigger, &trigger); + return avail; } -static int w5500_tcp_close(implements_net_stream_conn *_self, bool rd, bool wr) { - ASSERT_CONN(); +static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr) { + ASSERT_SELF(stream_conn, TCP); if (rd) - self->read_open = false; + socket->read_open = false; - if (wr && self->write_open) { - w5500_tcp_listener_cmd(listener, CMD_DISCON); - while (self->write_open) { + if (wr && socket->write_open) { + w5500_socket_cmd(socket, CMD_DISCON); + while (socket->write_open) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_FIN_WAIT: - self->write_open = false; + socket->write_open = false; /* Can still read */ - if (!self->read_open) - w5500_tcp_listener_cmd_close(listener); + if (!socket->read_open) + w5500_socket_close(socket); break; case STATE_CLOSED: - self->write_open = false; + socket->write_open = false; break; } } @@ -528,3 +581,116 @@ static int w5500_tcp_close(implements_net_stream_conn *_self, bool rd, bool wr) } /* udp_conn methods ***********************************************************/ + +static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, size_t count, + struct net_ip4_addr node, uint16_t port) { + ASSERT_SELF(packet_conn, UDP); + assert(buf); + assert(count); + + uint16_t bufsize = ((uint16_t)w5500ll_read_sock_reg(chip->spidev, socknum, tx_buf_size))*1024; + if (count < bufsize) + return -NET_EMSGSIZE; + + for (;;) { + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + if (state != STATE_UDP) + return -NET_ECLOSED; + + uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); + if (freesize >= count) + /* We can send. */ + break; + + /* Wait for more buffer space. */ + cr_yield(); + } + + /* Where we're sending it. */ + w5500ll_write_sock_reg(chip->spidev, socknum, remote_ip_addr, node); + w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port)); + /* Queue data to be sent. */ + uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer)); + w5500ll_write(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), buf, count); + w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+count)); + /* Submit the queue. */ + w5500_socket_cmd(socket, CMD_SEND); + + return count; +} + +static void w5500_udp_set_read_deadline(implements_net_packet_conn *_socket, uint64_t ns) { + ASSERT_SELF(packet_conn, UDP); + socket->read_deadline_ns = ns; +} + +static void w5500_udp_alarm_handler(void *_arg) { + struct _w5500_socket *socket = _arg; + cr_sema_signal(&socket->read_sema); +} + +static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf, size_t count, + struct net_ip4_addr *node, uint16_t *port) { + ASSERT_SELF(packet_conn, UDP); + assert(buf); + assert(count); + + struct alarmclock_trigger trigger = {0}; + if (socket->read_deadline_ns) + VCALL(bootclock, add_trigger, &trigger, + socket->read_deadline_ns, + w5500_udp_alarm_handler, + socket); + + /* Wait until there is data to read. */ + uint16_t avail = 0; + for (;;) { + if (socket->read_deadline_ns && socket->read_deadline_ns >= VCALL(bootclock, get_time_ns)) { + VCALL(bootclock, del_trigger, &trigger); + return -NET_ETIMEDOUT; + } + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + if (state != STATE_UDP) { + VCALL(bootclock, del_trigger, &trigger); + return -NET_ECLOSED; + } + + uint16_t avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); + if (avail) + /* We have data to read. */ + break; + + cr_sema_wait(&socket->read_sema); + } + assert(avail >= 8); + uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); + /* Read a munged form of the UDP packet header. I + * can't find in the datasheet where it describes + * this; this is based off of socket.c:recvfrom(). */ + uint8_t hdr[8]; + w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), hdr, sizeof(hdr)); + node->octets[0] = hdr[0]; + node->octets[1] = hdr[1]; + node->octets[2] = hdr[2]; + node->octets[3] = hdr[3]; + *port = uint16be_decode(&hdr[4]); + uint16_t len = uint16be_decode(&hdr[6]); + /* Now read the actual data. */ + if (count > len) + count = len; + w5500ll_read(chip->spidev, ptr+8, CTL_BLOCK_SOCK(socknum, RX), buf, len); + /* Tell the chip that we read the data. */ + w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+8+len)); + w5500_socket_cmd(socket, CMD_RECV); + /* Return. */ + VCALL(bootclock, del_trigger, &trigger); + return len; +} + +static int w5500_udp_close(implements_net_packet_conn *_socket) { + ASSERT_SELF(packet_conn, UDP); + + w5500_socket_close(socket); + + return 0; +} -- cgit v1.2.3-2-g168b