/* hw/w5500.c - libmisc/net.h implementation for the WIZnet W5500 chip
 *
 * Copyright (C) 2024  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-Licence-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/socket.c
 *
 * SPDX-Licence-Identifier: BSD-3-Clause
 *
 * 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.
 */

#include <pico/time.h>     /* for sleep_ms() */
#include <hardware/gpio.h> /* pico-sdk:hardware_gpio5 */

#include <libcr/coroutine.h>
#include <libmisc/vcall.h>

#include "hw/w5500.h"
#include "config.h"

/* These are the default values of the Linux kernel's
 * net.ipv4.ip_local_port_range, so I figure they're probably good
 * values to use.  */
#ifndef CONFIG_W5500_LOCAL_PORT_MIN
#	define CONFIG_W5500_LOCAL_PORT_MIN 32768
#endif
#ifndef CONFIG_W5500_LOCAL_PORT_MAX
#	define CONFIG_W5500_LOCAL_PORT_MAX 60999
#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_BLOCK_SOCK(n,part)  (((n)<<5)|(_CTL_BLOCK_##part))
#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES)

/* Part 2: R/W.  */
#define CTL_MASK_RW 0b100
#define CTL_R       0b000
#define CTL_W       0b100

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

/* The W5500 has 2 channels of communication with the MCU:
 *
 *  - An SPI-based RPC protocol:
 *    + mode: mode 0 or mode 3
 *    + bit-order: MSB-first
 *    + clock frequency: 33.3MHz - 80MHz
 *  - An interrupt pin that it pulls low when an event happens (to let
 *    the MCU know that it should do an SPI RPC "get" to see what
 *    happened.)
 *
 * Even though SPI is a full-duplex protocol, the W5500's RPC protocol
 * on top of it is only half-duplex.  Lame.
 */

void w5500_spiframe_write(struct 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);

	uint8_t header[3] = {
		(uint8_t)((addr >> 8) & 0xFF),
		(uint8_t)(addr & 0xFF),
		(block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM,
	};
	struct bidi_iovec iov[] = {
		{.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)},
		{.iov_read_dst = NULL, .iov_write_src = data,   .iov_len = data_len},
	};
	VCALL(spidev, readwritev, iov, 2);
}

void w5500_spiframe_read(struct 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);

	uint8_t header[3] = {
		(uint8_t)((addr >> 8) & 0xFF),
		(uint8_t)(addr & 0xFF),
		(block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM,
	};
	struct bidi_iovec iov[] = {
		{.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)},
		{.iov_read_dst = data, .iov_write_src = NULL,   .iov_len = data_len},
	};
	VCALL(spidev, readwritev, iov, 2);
}

/* Offsets and sizes for use with that protocol.  *****************************/

struct w5500_block_common_reg {
	uint8_t mode;                   /* MR */
	uint8_t ip_gateway_addr[4];     /* GAR0 ... GAR3 */
	uint8_t ip_subnet_mask[4];      /* SUBR0 ... SUBR3 */
	uint8_t eth_addr[6];            /* SHAR0 ... SHAR5 */
	uint8_t ip_addr[4];             /* SIPR0 ... SIPR3 */

	uint8_t intlevel_0;             /* INTLEVEL0 */
	uint8_t intlevel_1;             /* INTLEVEL1 */
	uint8_t interrupt;              /* IR */
	uint8_t interrupt_mask;         /* IMR */
	uint8_t sock_interrupt;         /* SIR */
	uint8_t sock_interrupt_mask;    /* SIMR */
	uint8_t retry_time_0;           /* RTR0 */
	uint8_t retry_time_1;           /* RTR0 */
	uint8_t retry_count;            /* RCR */

	uint8_t ppp_lcp_request_timer;  /* PTIMER */
	uint8_t ppp_lcp_magic_bumber;   /* PMAGIC */
	uint8_t ppp_dst_eth_addr[6];    /* PHAR0 ... PHAR5 */
	uint8_t ppp_sess_id[2];         /* PSID0 ... PSID1 */
	uint8_t ppp_max_seg_size[2];    /* PMRU0 ... PMRU1 */

	uint8_t unreachable_ip_addr[4]; /* UIPR0 ... UIPR3 */
	uint8_t unreachable_port[2];    /* UPORTR0, UPORTR1 */

	uint8_t phy_cfg;                /* PHYCFGR */

	uint8_t _reserved[10];

	uint8_t chip_version;           /* VERSIONR */
};
static_assert(sizeof(struct w5500_block_common_reg) == 0x3A);

struct w5500_block_sock_reg {
	uint8_t mode;                /* Sn_MR; see MODE_{x} below */
	uint8_t command;             /* Sn_CR; see CMD_{x} below */
	uint8_t interrupt;           /* Sn_IR */
	uint8_t state;               /* Sn_SR; see STATE_{x} below */
	uint8_t local_port[2];       /* Sn_PORT0, Sn_PORT1 */
	uint8_t remote_eth_addr[6];  /* Sn_DHAR0 ... SnDHAR5 */
	uint8_t remote_ip_addr[4];   /* Sn_DIPR0 ... Sn_DIP3 */
	uint8_t remote_port[2];      /* Sn_DPORT0 ... Sn_DPORT1 */

	uint8_t max_seg_size[2];     /* Sn_MSSR0, Sn_MSSR1 */
	uint8_t _reserved0[1];
	uint8_t ip_tos;              /* Sn_TOS */
	uint8_t ip_ttl;              /* Sn_TTL */
	uint8_t _reserved1[7];

	uint8_t rx_buf_size;         /* Sn_RXBUF_SIZE */
	uint8_t tx_buf_size;         /* Sn_TXBUF_SIZE */
	uint8_t tx_free_size[2];     /* Sn_TX_FSR0, Sn_TX_FSR1 */
	uint8_t tx_read_pointer[2];  /* Sn_TX_RD0, Sn_TX_RD1 */
	uint8_t tx_write_pointer[2]; /* Sn_TX_WR0, Sn_TX_WR1 */
	uint8_t rx_size[2];          /* Sn_RX_RSR0, Sn_RX_RSR1 */
	uint8_t rx_read_pointer[2];  /* Sn_RX_RD0, Sn_RX_RD1 */
	uint8_t rx_write_pointer[2]; /* Sn_RX_WR0, Sn_RX_WR1 */

	uint8_t interrupt_mask;      /* Sn_IMR */
	uint8_t fragment_offset[2];  /* Sn_FRAG0, Sn_FRAG1 */
	uint8_t keepalive_timer;     /* Sn_KPALVTR */
};
static_assert(sizeof(struct w5500_block_sock_reg) == 0x30);

/* low 4 bits are the main enum, high 4 bits are flags */
#define MODE_CLOSED ((uint8_t)0b0000)
#define MODE_TCP    ((uint8_t)0b0001)
#define MODE_UDP    ((uint8_t)0b0010)
#define MODE_MACRAW ((uint8_t)0b0100)

#define MODE_FLAG_TCP_NODELAY_ACK         ((uint8_t)(1<<5))

#define MODE_FLAG_UDP_ENABLE_MULTICAST    ((uint8_t)(1<<7))
#define MODE_FLAG_UDP_BLOCK_BROADCAST     ((uint8_t)(1<<6))
#define MODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5))
#define MODE_FLAG_UDP_BLOCK_UNICAST       ((uint8_t)(1<<4))

#define MODE_FLAG_MACRAW_MAC_FILTERING    ((uint8_t)(1<<7))
#define MODE_FLAG_MACRAW_BLOCK_BROADCAST  ((uint8_t)(1<<6))
#define MODE_FLAG_MACRAW_BLOCK_MULTICAST  ((uint8_t)(1<<5))
#define MODE_FLAG_MACRAW_BLOCK_V6         ((uint8_t)(1<<4))

#define CMD_OPEN      ((uint8_t)0x01)
#define CMD_LISTEN    ((uint8_t)0x02) /* TCP-only */
#define CMD_CONNECT   ((uint8_t)0x04) /* TCP-only: dial */
#define CMD_DISCON    ((uint8_t)0x08) /* TCP-only: send/reply FIN */
#define CMD_CLOSE     ((uint8_t)0x10)
#define CMD_SEND      ((uint8_t)0x20)
#define CMD_SEND_MAC  ((uint8_t)0x21) /* UDP-only: send to remote_eth_addr without doing ARP on remote_ip_addr */
#define CMD_SEND_KEEP ((uint8_t)0x22) /* TCP-only: send a keepalive without any data */
#define CMD_RECV      ((uint8_t)0x40)

#define STATE_CLOSED          ((uint8_t)0x00)

/**
 * The TCP state diagram is as follows.
 *  - Reading the W5500's "state" register does not distinguish between FIN_WAIT_1 and FIN_WAIT_2;
 *    it just has a single FIN_WAIT.
 *  - At any point the state can jump to "CLOSED" either by CMD_CLOSE or by a timeout.
 *  - Writing data is valid in ESTABLISHED and CLOSE_WAIT.
 *  - Reading data is valid in ESTABLISHED and FIN_WAIT.
 *
 *     TCP state diagram, showing the flow of   │ CLOSED │      ━━ role separator     ┌───────┐
 *     SYN, FIN, and their assocaited ACKs.     └────────┘      ══ state transition   │ state │
 *                                                   V	        ┈┈ packet flow        └───────┘
 *                                              (CMD_OPEN)                                ║
 *                                                   V                              (action/event)
 *                                              ┌────────┐
 *     ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━│  INIT  │━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
 *     ┃ server         ┃                       └────────┘                        ┃         client ┃
 *     ┣━━━━━━━━━━━━━━━━┛                         V ┃┃ V                          ┗━━━━━━━━━━━━━━━━┫
 *     ┃                              ╔═══════════╝ ┃┃ ╚═══════════╗                               ┃
 *     ┃                        (CMD_LISTEN)        ┃┃       (CMD_CONNECT)                         ┃
 *     ┃                              V            ┌┃┃┈┈┈┈┈┈┈┈<(send SYN)                          ┃
 *     ┃                         ┌────────┐        ┊┃┃             V                               ┃
 *     ┃                         │ LISTEN │        ┊┃┃        ┌─────────┐                          ┃
 *     ┃                         └────────┘        ┊┃┃        │ SYNSENT │                          ┃
 *     ┃                              V            ┊┃┃        └─────────┘                          ┃
 *     ┃                          (recv SYN)<┈┈┈┈┈┈┘┃┃             V                               ┃
 *     ┃                        (send SYN+ACK)>┈┈┈┈┐┃┃             ║                               ┃
 *     ┃                              V            └┃┃┈┈┈┈┈┈>(recv SYN+ACK)                        ┃
 *     ┃                        ┌─────────┐        ┌┃┃┈┈┈┈┈┈┈┈<(send ack)                          ┃
 *     ┃                        │ SYNRECV │        ┊┃┃             ║                               ┃
 *     ┃                        └─────────┘        ┊┃┃             ║                               ┃
 *     ┃                          V     V          ┊┃┃             ║                               ┃
 *     ┃                          ║  (recv ACK)<┈┈┈┘┃┃             ║                               ┃
 *     ┃                          ║     ╚═════════╗ ┃┃ ╔═══════════╝                               ┃
 *     ┃                          ╚═══╗           V ┃┃ V                                           ┃
 *     ┃                              ║       ┌─────────────┐                                      ┃
 *     ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━│ ESTABLISHED │━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 *     ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━└─────────────┘━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 *     ┃                              ║           V ┃┃ V                                           ┃
 *     ┃                              ╠═══════════╝ ┃┃ ╚═══════════╗                               ┃
 *     ┃                         (CMD_DISCON)       ┃┃             ║                               ┃
 *     ┃  Both sides sent    ┌┈┈┈<(send FIN)>┈┈┈┈┈┈┐┃┃             ║                               ┃
 *     ┃  FIN at the "same"  ┊        V            └┃┃┈┈┈┈┈┈┈┈>(recv FIN)                          ┃
 *     ┃   time; both are    ┊  ┌────────────┐     ┌┃┃┈┈┈┈┈┈┈┈<(send ACK)                          ┃
 *     ┃   active closers    ┊  │ FIN_WAIT_1 │     ┊┃┃             V                               ┃
 *     ┃        / \          ┊  └────────────┘     ┊┃┃      ┌────────────┐                         ┃
 *     ┃ ,-----'   '-----,   ┊     V     V         ┊┃┃      │ CLOSE_WAIT │                         ┃
 *     ┃          ╔════════════════╝     ║         ┊┃┃      └────────────┘                         ┃
 *     ┃      (recv FIN)<┈┈┈┈┤        ╔══╝         ┊┃┃             V                               ┃
 *     ┃  ┌┈┈<(send ACK)>┈┈┐ ┊        ║            ┊┃┃             ║                               ┃
 *     ┃  ┊       ║        └┈┈┈┈┈>(recv ACK)<┈┈┈┈┈┈┘┃┃             ║                               ┃
 *     ┃  ┊       V          ┊        V             ┃┃             ║                               ┃
 *     ┃  ┊  ┌─────────┐     ┊  ┌────────────┐      ┃┃             ║                               ┃
 *     ┃  ┊  │ CLOSING │     ┊  │ FIN_WAIT_2 │      ┃┃             ║                               ┃
 *     ┃  ┊  └─────────┘     ┊  └────────────┘      ┃┃        (CMD_DISCON)                         ┃
 *     ┃  ┊       V          ┊        V            ┌┃┃┈┈┈┈┈┈┈┈<(send FIN)                          ┃
 *     ┃  ┊       ║          └┈┈┈>(recv FIN)<┈┈┈┈┈┈┘┃┃             ║                               ┃
 *     ┃  ┊       ║        ┌┈┈┈┈┈<(send ACK)>┈┈┈┈┈┈┐┃┃             V                               ┃
 *     ┃  └┈┈>(recv ACK)<┈┈┘          ╚═╗          ┊┃┃        ┌──────────┐                         ┃
 *     ┃          ╚════════════════╗    ║          ┊┃┃        │ LAST_ACK │                         ┃
 *     ┃                           V    V          ┊┃┃        └──────────┘                         ┃
 *     ┃                        ┌───────────┐      ┊┃┃             V                               ┃
 *     ┃                        │ TIME_WAIT │      ┊┃┃             ║                               ┃
 *     ┃                        └───────────┘      └┃┃┈┈┈┈┈┈┈┈>(recv ACK)                          ┃
 *     ┃                              V             ┃┃             ║                               ┃
 *     ┣━━━━━━━━━━━━━━━━┓     (2*MSL has elapsed)   ┃┃             ║              ┏━━━━━━━━━━━━━━━━┫
 *     ┃ active closer  ┃             ╚═══════════╗ ┃┃ ╔═══════════╝              ┃ passive closer ┃
 *     ┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━V━┛┗━V━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┛
 *                                              ┌────────┐
 *                                              │ CLOSED │
 */
#define STATE_TCP_INIT        ((uint8_t)0x13)
#define STATE_TCP_LISTEN      ((uint8_t)0x14) /* server */
#define STATE_TCP_SYNSENT     ((uint8_t)0x15) /* client; during dial */
#define STATE_TCP_SYNRECV     ((uint8_t)0x16) /* server; during accept */
#define STATE_TCP_ESTABLISHED ((uint8_t)0x17)
#define STATE_TCP_FIN_WAIT    ((uint8_t)0x18) /* during active close */
#define STATE_TCP_CLOSING     ((uint8_t)0x1a) /* during active close */
#define STATE_TCP_TIME_WAIT   ((uint8_t)0x1b) /* during active close */
#define STATE_TCP_CLOSE_WAIT  ((uint8_t)0x1c) /* during passive close */
#define STATE_TCP_LAST_ACK    ((uint8_t)0x1d) /* during passive close */

#define STATE_UDP             ((uint8_t)0x22)

#define STATE_MACRAW          ((uint8_t)0x42)

#define REGWRITE_COMMON(spidev, field, val) do {                                          \
		assert(sizeof(val) == sizeof(((struct w5500_block_common_reg){}).field)); \
		typeof(val) lval = val;                                                   \
		w5500_spiframe_write(spidev,                                              \
		                     offsetof(struct w5500_block_common_reg, field),      \
		                     CTL_BLOCK_COMMON_REG,                                \
		                     &lval,                                               \
		                     sizeof(lval));                                       \
	} while (0)

#define REGREAD_COMMON(spidev, field, typ) ({                                                   \
			typ val;                                                                \
			assert(sizeof(val) == sizeof(((struct w5500_block_sock_reg){}).field)); \
			w5500_spiframe_read(spidev,                                             \
			                    offsetof(struct w5500_block_sock_reg, field),       \
			                    CTL_BLOCK_COMMON,                                   \
			                    &(val),                                             \
			                    sizeof(val));                                       \
			val;                                                                    \
		})

#define REGWRITE_SOCK(spidev, socknum, field, val) do {                                 \
		assert(sizeof(val) == sizeof(((struct w5500_block_sock_reg){}).field)); \
		typeof(val) lval = val;                                                 \
		w5500_spiframe_write(spidev,                                            \
		                     offsetof(struct w5500_block_sock_reg, field),      \
		                     CTL_BLOCK_SOCK(socknum, REG),                      \
		                     &lval,                                             \
		                     sizeof(lval));                                     \
	} while (0)

#define REGREAD_SOCK(spidev, socknum, field, typ) ({                                            \
			typ val;                                                                \
			assert(sizeof(val) == sizeof(((struct w5500_block_sock_reg){}).field)); \
			w5500_spiframe_read(spidev,                                             \
			                     offsetof(struct w5500_block_sock_reg, field),      \
			                     CTL_BLOCK_SOCK(socknum, REG),                      \
			                     &(val),                                            \
			                     sizeof(val));                                      \
			val;                                                                    \
		})

/* init() *********************************************************************/

static implements_net_conn *w5500_accept(implements_net_listener *_listener);
static ssize_t w5500_read(implements_net_conn *conn, void *buf, size_t count);
static ssize_t w5500_write(implements_net_conn *conn, void *buf, size_t count);
static int w5500_close(implements_net_conn *conn, bool rd, bool wr);

static struct net_listener_vtable w5500_listener_vtable = {
	.accept = w5500_accept,
};

static struct net_conn_vtable w5500_conn_vtable = {
	.read  = w5500_read,
	.write = w5500_write,
	.close = w5500_close,
};

uint16_t w5500_get_local_port(struct w5500 *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;
}

void _w5500_init(struct w5500 *chip,
                 struct spi* spi, uint pin_intr, uint pin_reset,
                 struct net_eth_addr addr) {
	chip->spidev = spi;
	chip->pin_reset = pin_reset;
	for (uint8_t i = 0; i < 8; i++) {
		chip->_listeners[i].vtable = &w5500_listener_vtable;
		chip->_listeners[i].chip = chip;
		chip->_listeners[i].socknum = i;
		chip->_listeners[i].active_conn.vtable = &w5500_conn_vtable;
		chip->_listeners[i].active_conn.parent_listener = &chip->_listeners[i];
	}
	chip->_next_local_port = CONFIG_W5500_LOCAL_PORT_MIN;

	gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, TODO_cbfn);
	gpio_set_dir(chip->pin_reset, GPIO_OUT);

	w5500_reset(chip);

	REGWRITE_COMMON(spi, eth_addr, addr);
}


void w5500_reset(struct w5500 *chip) {
	/* TODO: Replace blocking sleep_ms() with something libcr-friendly.  */
	gpio_put(chip->pin_reset, 0);
	sleep_ms(1); /* minimum of 500us */
	gpio_put(chip->pin_reset, 1);
	sleep_ms(2); /* minimum of 1ms */
}

void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) {
	REGWRITE_COMMON(chip->spidev, ip_gateway_addr, cfg.gateway_addr);
	REGWRITE_COMMON(chip->spidev, ip_subnet_mask, cfg.subnet_mask);
	REGWRITE_COMMON(chip->spidev, ip_addr, cfg.addr);
}

/* listen() *******************************************************************/

implements_net_listener *w5500_listen(struct w5500 *chip, uint8_t socknum, uint16_t port) {
	assert(chip);
	assert(socknum < 8);
	assert(port);

	w5500_close(&chip->_listeners[socknum].active_conn, true, true);
	REGWRITE_SOCK(chip->spidev, socknum, mode, MODE_TCP);
	REGWRITE_SOCK(chip->spidev, socknum, local_port, ((uint8_t[2]){port>>8, port}));
	REGWRITE_SOCK(chip->spidev, socknum, command, CMD_OPEN);
	while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
		cr_yield();
	while (REGREAD_SOCK(chip->spidev, socknum, state, uint8_t) != STATE_TCP_INIT)
		cr_yield();

	return &chip->_listeners[socknum];
}

/* accept() *******************************************************************/

#define ASSERT_LISTENER()                                               \
	struct _w5500_listener *self = VCALL_SELF(struct _w5500_listener, implements_net_listener, _self); \
	assert(self);                                                   \
	                                                                \
	struct w5500 *chip = self->chip;                                \
	uint8_t socknum = self->socknum;                                \
	assert(chip);                                                   \
	assert(socknum < 8)

static implements_net_conn *w5500_accept(implements_net_listener *_self) {
	ASSERT_LISTENER();

	REGWRITE_SOCK(chip->spidev, socknum, command, CMD_LISTEN);
	while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
		cr_yield();
	for (;;) {
		uint8_t mode = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t);
		switch (mode) {
		case STATE_TCP_LISTEN:
		case STATE_TCP_SYNRECV:
			cr_yield();
			break;
		default:
			return &self->active_conn;
		}
	}
}

/* write() ********************************************************************/

#define ASSERT_CONN() \
	struct _w5500_conn *self = VCALL_SELF(struct _w5500_conn, implements_net_conn, _self); \
	assert(self);                                           \
	                                                        \
	struct w5500 *chip = self->parent_listener->chip;       \
	uint8_t socknum = self->parent_listener->socknum;       \
	assert(chip);                                           \
	assert(socknum < 8)

static ssize_t w5500_write(implements_net_conn *_self, void *buf, size_t count) {
	ASSERT_CONN();
	assert(buf);
	assert(count);

	// TODO
}

static int w5500_close(implements_net_conn *_self, bool rd, bool wr) {
	ASSERT_CONN();

	REGWRITE_SOCK(chip->spidev, socknum, command, CMD_CLOSE);
	while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
		cr_yield();
	REGWRITE_SOCK(chip->spidev, socknum, interrupt, (uint8_t)0xff);
	while (REGREAD_SOCK(chip->spidev, socknum, state, uint8_t) != STATE_CLOSED)
		cr_yield();

	return 0;
}