/* libhw/w5500.c - <libhw/generic/net.h> implementation for the WIZnet W5500 chip
 *
 * Copyright (C) 2024  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 *
 * -----------------------------------------------------------------------------
 * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/wizchip_conf.c
 * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.h
 * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.c
 * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/socket.c
 *
 * Copyright (c)  2013, WIZnet Co., LTD.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <ORGANIZATION> nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * -----------------------------------------------------------------------------
 * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt
 *
 * Copyright (c) 2014 WIZnet Co.,Ltd.
 * Copyright (c) WIZnet ioLibrary Project.
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * SPDX-License-Identifier: MIT
 */

#include <inttypes.h> /* for PRIu{n} */

/* TODO: Write a <libhw/generic/gpio.h> to avoid w5500.c being
 * pico-sdk-specific.  */
#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */

#include <libcr/coroutine.h> /* for cr_yield() */
#include <libmisc/vcall.h>   /* for VCALL_SELF() */

#include <libhw/generic/alarmclock.h> /* for sleep_*() */

#define LOG_NAME W5500
#include <libmisc/log.h> /* for errorf(), debugf(), const_byte_str() */

#define IMPLEMENTATION_FOR_LIBHW_W5500_H YES
#include <libhw/w5500.h>

#include "w5500_ll.h"

/* Config *********************************************************************/

#include "config.h"

#ifndef CONFIG_W5500_LOCAL_PORT_MIN
	#error config.h must define CONFIG_W5500_LOCAL_PORT_MIN
#endif
#ifndef CONFIG_W5500_LOCAL_PORT_MAX
	#error config.h must define CONFIG_W5500_LOCAL_PORT_MAX
#endif
#ifndef CONFIG_W5500_NUM
	#error config.h must define CONFIG_W5500_NUM
#endif
#ifndef CONFIG_W5500_DEBUG
	#error config.h must define CONFIG_W5500_DEBUG
#endif

/* C language *****************************************************************/

#define UNUSED(name)
#define ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0]))

static const char *w5500_state_str(uint8_t state) {
	switch (state) {
	case STATE_CLOSED:          return "STATE_CLOSED";
	case STATE_TCP_INIT:        return "STATE_TCP_INIT";
	case STATE_TCP_LISTEN:      return "STATE_TCP_LISTEN";
	case STATE_TCP_SYNSENT:     return "STATE_TCP_SYNSENT";
	case STATE_TCP_SYNRECV:     return "STATE_TCP_SYNRECV";
	case STATE_TCP_ESTABLISHED: return "STATE_TCP_ESTABLISHED";
	case STATE_TCP_FIN_WAIT:    return "STATE_TCP_FIN_WAIT";
	case STATE_TCP_CLOSING:     return "STATE_TCP_CLOSING";
	case STATE_TCP_TIME_WAIT:   return "STATE_TCP_TIME_WAIT";
	case STATE_TCP_CLOSE_WAIT:  return "STATE_TCP_CLOSE_WAIT";
	case STATE_TCP_LAST_ACK:    return "STATE_TCP_LAST_ACK";
	case STATE_UDP:             return "STATE_UDP";
	case STATE_MACRAW:          return "STATE_MACRAW";
	default:                    return const_byte_str(state);
	}
}

/* vtables ********************************************************************/

/* iface */
static struct net_eth_addr              w5500_if_hwaddr     (implements_net_iface *);
static void                             w5500_if_up         (implements_net_iface *, struct net_iface_config);
static void                             w5500_if_down       (implements_net_iface *);
static implements_net_stream_listener  *w5500_if_tcp_listen (implements_net_iface *, uint16_t local_port);
static implements_net_stream_conn      *w5500_if_tcp_dial   (implements_net_iface *, struct net_ip4_addr, uint16_t remote_port);
static implements_net_packet_conn      *w5500_if_udp_conn   (implements_net_iface *, uint16_t local_port);

/* stream_listener */
static implements_net_stream_conn      *w5500_tcplist_accept(implements_net_stream_listener *);
static int                              w5500_tcplist_close (implements_net_stream_listener *);

/* stream_conn */
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);

/* packet_conn */
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 *);

/* tables */

static struct net_iface_vtable w5500_iface_vtable = {
	.hwaddr            = w5500_if_hwaddr,
	.ifup              = w5500_if_up,
	.ifdown            = w5500_if_down,
	.tcp_listen        = w5500_if_tcp_listen,
	.tcp_dial          = w5500_if_tcp_dial,
	.udp_conn          = w5500_if_udp_conn,
};

static struct net_stream_listener_vtable w5500_tcp_listener_vtable = {
	.accept            = w5500_tcplist_accept,
	.close             = w5500_tcplist_close,
};

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 ********************************************************/

static uint16_t w5500_alloc_local_port(struct w5500 *self) {
	assert(self);
	uint16_t ret = self->next_local_port++;
	if (self->next_local_port > CONFIG_W5500_LOCAL_PORT_MAX)
		self->next_local_port = CONFIG_W5500_LOCAL_PORT_MIN;
	return ret;
}

static struct _w5500_socket *w5500_alloc_socket(struct w5500 *self) {
	assert(self);
	struct _w5500_socket *sock = self->free;
	if (!sock)
		return NULL;
	self->free = sock->next_free;
	sock->next_free = NULL;
	assert(sock->mode == W5500_MODE_NONE);
	return sock;
}

static void w5500_free_socket(struct w5500 *self, struct _w5500_socket *sock) {
	assert(self);
	assert(sock);
	sock->mode = W5500_MODE_NONE;
	sock->next_free = self->free;
	self->free = sock;
}

static void w5500_tcp_maybe_free(struct w5500 *chip, struct _w5500_socket *sock) {
	assert(chip);
	assert(sock);
	assert(sock->mode == W5500_MODE_TCP);
	if (!sock->list_open && !sock->read_open && !sock->write_open)
		w5500_free_socket(chip, sock);
}

static COROUTINE w5500_irq_cr(void *_chip) {
	struct w5500 *chip = _chip;
	cr_begin();

	for (;;) {
		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];

			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_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();
}

static struct w5500 *w5500_socket_chip(struct _w5500_socket *socket) {
	assert(socket);
	assert(socket->socknum < 8);

	struct _w5500_socket *sock0 = &socket[-(socket->socknum)];
	assert(sock0);
	struct w5500 *chip =
		((void *)sock0) - offsetof(struct w5500, sockets);
	assert(chip);
	return chip;
}

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;

	w5500ll_write_sock_reg(chip->spidev, socknum, command, cmd);
	while (w5500ll_read_sock_reg(chip->spidev, socknum, command) != 0x00)
		cr_yield();
}

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();
}

#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};

static void w5500_intrhandler(uint gpio, uint32_t UNUSED(event_mask)) {
	debugf("w5500_intrhandler(): interrupt on pin %u", gpio);
	for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++)
		if (w5500_chips[i] && w5500_chips[i]->pin_intr == gpio)
			cr_sema_signal_from_intrhandler(&w5500_chips[i]->intr);
}

void _w5500_init(struct w5500 *chip,
                 implements_spi *spi, uint pin_intr, uint pin_reset,
                 struct net_eth_addr addr) {
	assert(chip);
	assert(spi);

	/* Initialize the data structures.  */
	*chip = (struct w5500){
		/* const-after-init */
		.implements_net_iface = { .vtable = &w5500_iface_vtable },
		.spidev          = spi,
		.pin_intr        = pin_intr,
		.pin_reset       = pin_reset,
		.hwaddr          = addr,
		/* mutable */
		.next_local_port = CONFIG_W5500_LOCAL_PORT_MIN,
	};
	chip->free = &chip->sockets[0];
	for (uint8_t i = 0; i < 8; i++) {
		chip->sockets[i] = (struct _w5500_socket){
			/* const-after-init */
			.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,
			/* mutable */
			.next_free = (i + 1 < 8) ? &chip->sockets[i+1] : NULL,
			/* The rest of the mutable members get
			 * initialized to the zero values.  */
		};
	}

	/* 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);
	w5500_hard_reset(chip);

	/* Finally, wire in the interrupt handler.  */
	bool saved = cr_save_and_disable_interrupts();
	for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++) {
		if (w5500_chips[i] == NULL) {
			w5500_chips[i] = chip;
			break;
		}
	}
	cr_restore_interrupts(saved);
	coroutine_add("w5500_irq", w5500_irq_cr, chip);
}

/* chip methods ***************************************************************/

static void w5500_post_reset(struct w5500 *chip) {
	/* The W5500 does not have a built-in MAC address, we must
	 * provide one.  */
	w5500ll_write_common_reg(chip->spidev, eth_addr, chip->hwaddr);

	/* The RP2040 needs a 1/sys_clk hysteresis between interrupts
	 * for us to notice them.  At the maximum-rated clock-rate of
	 * 133MHz, that means 7.5ns (but the sbc-harness overclocks
	 * the RP2040, so we could get away with even shorter).
	 *
	 * If intlevel is non-zero, then the hysteresis is
	 * (intlevel+1)*4/(150MHz), or (intlevel+1)*26.7ns; so even
	 * the shortest-possible hysteresis much larger than necessary
	 * for us.  */
	w5500ll_write_common_reg(chip->spidev, intlevel, uint16be_marshal(1));

	/* This implementation does not care about any of the
	 * chip-level interrupts.  */
	w5500ll_write_common_reg(chip->spidev, chip_interrupt_mask, 0);

	/* This implementation cares about interrupts for each
	 * socket.  */
	w5500ll_write_common_reg(chip->spidev, sock_interrupt_mask, 0xFF);

	/* Configure retry/timeout.
	 *
	 *     timeout_arp = 0.1ms * retry_time * (retry_count+1)
	 *
	 *                                        retry_count
	 *     timeout_tcp = 0.1ms * retry_time * Σ 2^min(n, floor(1+log_2(65535/retry_time)))
	 *                                        n=0
	 *
	 * For retry_time=2000, retry_count=3, this means
	 *
	 *     timeout_arp = 0.8s
	 *     timeout_tcp = 3.0s
	 */
	w5500ll_write_common_reg(chip->spidev, retry_time, uint16be_marshal(2000));
	w5500ll_write_common_reg(chip->spidev, retry_count, 3);
}

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) {
	struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
	assert(chip);

	return chip->hwaddr;
}

static void _w5500_if_up(implements_net_iface *_chip, struct net_iface_config cfg) {
	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) {
	debugf("if_up()");
	debugf(":: addr         = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.addr));
	debugf(":: gateway_addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.gateway_addr));
	debugf(":: subnet_mask  = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.subnet_mask));
	_w5500_if_up(_chip, cfg);
}

static void w5500_if_down(implements_net_iface *_chip) {
	debugf("if_down()");
	_w5500_if_up(_chip, (struct net_iface_config){0});
}

static implements_net_stream_listener *w5500_if_tcp_listen(implements_net_iface *_chip, uint16_t local_port) {
	struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
	assert(chip);

	struct _w5500_socket *sock = w5500_alloc_socket(chip);
	if (!sock) {
		debugf("tcp_listen() => no sock");
		return NULL;
	}
	debugf("tcp_listen() => sock[%"PRIu8"]", sock->socknum);

	if (!local_port)
		local_port = w5500_alloc_local_port(chip);

	assert(sock->mode == W5500_MODE_NONE);
	sock->mode = W5500_MODE_TCP;
	sock->port = local_port;
	sock->read_deadline_ns = 0;
	sock->list_open = true;

	return &sock->implements_net_stream_listener;
}

static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip,
                                                     struct net_ip4_addr node, uint16_t port) {
	struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
	assert(chip);
	assert(memcmp(node.octets, net_ip4_addr_zero.octets, 4));
	assert(memcmp(node.octets, net_ip4_addr_broadcast.octets, 4));
	assert(port);

	struct _w5500_socket *socket = w5500_alloc_socket(chip);
	if (!socket) {
		debugf("tcp_dial() => no sock");
		return NULL;
	}
	uint8_t socknum = socket->socknum;
	debugf("tcp_dial() => sock[%"PRIu8"]", socknum);

	uint16_t local_port = w5500_alloc_local_port(chip);

	assert(socket->mode == W5500_MODE_NONE);
	socket->mode = W5500_MODE_TCP;
	socket->port = local_port;
	socket->read_deadline_ns = 0;
	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);
	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:connect().  */
	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));
		switch (state) {
		case STATE_TCP_SYNSENT:
			cr_yield();
			break;
		case STATE_TCP_ESTABLISHED:
			return &socket->implements_net_stream_conn;
		default:
			goto restart;
		}
	}
}

static implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip, uint16_t local_port) {
	struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
	assert(chip);

	struct _w5500_socket *socket = w5500_alloc_socket(chip);
	if (!socket) {
		debugf("udp_conn() => no sock");
		return NULL;
	}
	uint8_t socknum = socket->socknum;
	debugf("udp_conn() => sock[%"PRIu8"]", socknum);

	if (!local_port)
		local_port = w5500_alloc_local_port(chip);

	assert(socket->mode == W5500_MODE_NONE);
	socket->mode = W5500_MODE_UDP;
	socket->port = local_port;
	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;
}

/* tcp_listener methods *******************************************************/

static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *_socket) {
	ASSERT_SELF(stream_listener, TCP);

 restart:
	if (!socket->list_open) {
		debugf("tcp_listener.accept() => already closed");
		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);
	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_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));
		switch (state) {
		case STATE_TCP_LISTEN:
		case STATE_TCP_SYNRECV:
			cr_sema_wait(&socket->listen_sema);
			break;
		case STATE_TCP_ESTABLISHED:
			socket->read_open = true;
			/* fall-through */
		case STATE_TCP_CLOSE_WAIT:
			socket->write_open = true;
			return &socket->implements_net_stream_conn;
		default:
			goto restart;
		}
	}
}

static int w5500_tcplist_close(implements_net_stream_listener *_socket) {
	debugf("tcp_listener.close()");
	ASSERT_SELF(stream_listener, TCP);

	socket->list_open = false;
	w5500_tcp_maybe_free(chip, socket);
	return 0;
}

/* tcp_conn methods ***********************************************************/

static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, size_t count) {
	debugf("tcp_conn.write(%zu)", 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_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.
	 *
	 * We'll add more data to the buffer whenever there is
	 * `min_free_space` in the buffer (or the rest of the data
	 * fits in the buffer).
	 *
	 * This `min_free_space` can probably stand to be tuned; must
	 * be >0, <=bufsize.  `1500-58` is the 100BaseT MTU minus the
	 * Ethernet+IP+TCP overhead.  */
	uint16_t bufsize = ((uint16_t)w5500ll_read_sock_reg(chip->spidev, socknum, tx_buf_size))*1024;
	uint16_t min_free_space = MIN(1500-58, bufsize/4);

	size_t done = 0;
	while (done < count) {
		if (!socket->write_open) {
			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;
		}

		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;
		}

		/* 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_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);
			done += freesize;
			break;
		case SOCKINTR_SEND_TIMEOUT:
			debugf(" => ACK timeout");
			return -NET_EACK_TIMEOUT;
		case SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT:
			assert_notreached("send both OK and timed out?");
		default:
			assert_notreached("invalid write_ch bits");
		}
	}
	debugf(" => send finished");
	return done;
}

static void w5500_tcp_set_read_deadline(implements_net_stream_conn *_socket, uint64_t ns) {
	debugf("tcp_conn.set_read_deadline(%"PRIu64")", 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_from_intrhandler(&socket->read_sema);
}

static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, size_t count) {
	debugf("tcp_conn.read()");
	ASSERT_SELF(stream_conn, TCP);
	assert(buf);
	assert(count);

	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);
			debugf(" => soft closed");
			return -NET_ECLOSED;
		}
		if (socket->read_deadline_ns && socket->read_deadline_ns <= VCALL(bootclock, get_time_ns)) {
			VCALL(bootclock, del_trigger, &trigger);
			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:
		case STATE_TCP_ESTABLISHED:
		case STATE_TCP_FIN_WAIT:
			break; /* OK */
		default:
			VCALL(bootclock, del_trigger, &trigger);
			cr_mutex_unlock(&chip->mu);
			debugf(" => hard closed");
			return -NET_ECLOSED;
		}

		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);
			cr_mutex_unlock(&chip->mu);
			debugf(" => EOF");
			return 0;
		}

		cr_mutex_unlock(&chip->mu);
		cr_sema_wait(&socket->read_sema);
	}
	assert(avail);
	debugf(" => received %"PRIu16" bytes", 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);
	cr_mutex_unlock(&chip->mu);
	return avail;
}

static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr) {
	debugf("tcp_conn.close(rd=%s, wr=%s)", rd ? "true" : "false", wr ? "true" : "false");
	ASSERT_SELF(stream_conn, TCP);

	if (rd)
		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);
			switch (state) {
			case STATE_TCP_FIN_WAIT:
				socket->write_open = false;
				/* Can still read */
				if (!socket->read_open)
					w5500_socket_close(socket);
				break;
			case STATE_CLOSED:
				socket->write_open = false;
				break;
			}
		}
		cr_mutex_unlock(&chip->mu);
	}

	w5500_tcp_maybe_free(chip, socket);
	return 0;
}

/* 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) {
	debugf("udp_conn.sendto()");
	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) {
		debugf(" => msg too large");
		return -NET_EMSGSIZE;
	}

	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;
		}

		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_mutex_unlock(&chip->mu);
		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);

	cr_mutex_unlock(&chip->mu);
	switch (_w5500_sockintr_ch_recv(&socket->write_ch)) {
	case SOCKINTR_SEND_OK:
		debugf(" => sent");
		return count;
	case SOCKINTR_SEND_TIMEOUT:
		debugf(" => ARP timeout");
		return -NET_EARP_TIMEOUT;
	case SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT:
		assert_notreached("send both OK and timed out?");
	default:
		assert_notreached("invalid write_ch bits");
	}
}

static void w5500_udp_set_read_deadline(implements_net_packet_conn *_socket, uint64_t ns) {
	debugf("udp_conn.set_read_deadline(%"PRIu64")", 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_from_intrhandler(&socket->read_sema);
}

static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf, size_t count,
                                  struct net_ip4_addr *ret_node, uint16_t *ret_port) {
	debugf("udp_conn.recvfrom()");
	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);
			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);
			debugf(" => hard closed");
			return -NET_ECLOSED;
		}

		avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size));
		if (avail)
			/* We have data to read.  */
			break;

		cr_mutex_unlock(&chip->mu);
		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));
	if (ret_node) {
		ret_node->octets[0] = hdr[0];
		ret_node->octets[1] = hdr[1];
		ret_node->octets[2] = hdr[2];
		ret_node->octets[3] = hdr[3];
	}
	if (ret_port)
		*ret_port = uint16be_decode(&hdr[4]);
	uint16_t len = uint16be_decode(&hdr[6]);
	debugf(" => received %"PRIu16" bytes%s", len, len < avail-8 ? " (plus more messages)" : "");
	/* 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);
	cr_mutex_unlock(&chip->mu);
	return len;
}

static int w5500_udp_close(implements_net_packet_conn *_socket) {
	debugf("udp_conn.close()");
	ASSERT_SELF(packet_conn, UDP);

	w5500_socket_close(socket);
	w5500_free_socket(chip, socket);
	return 0;
}