summaryrefslogtreecommitdiff
path: root/libhw/w5500_ll.h
blob: 57bfccd1bc51ff76d77a490fec40992c039f2876 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/* libhw/w5500_ll.h - Low-level header library for the WIZnet W5500 chip
 *
 * Based entirely on the W5500 datasheet, v1.1.0.
 * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf
 *
 * Copyright (C) 2024  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-Licence-Identifier: AGPL-3.0-or-later
 */

#ifndef _LIBHW_W5500_LL_H_
#define _LIBHW_W5500_LL_H_

#include <assert.h> /* for assert(), static_assert() */
#include <stdint.h> /* for uint{n}_t */
#include <string.h> /* for memcmp() */

#include <libmisc/vcall.h>  /* for VCALL() */
#include <libmisc/endian.h> /* for uint16be_t */

#include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */
#include <libhw/generic/spi.h> /* for implements_spi */


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

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

static inline void
w5500ll_read(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);

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

/* Common chip-wide registers.  ***********************************************/

struct w5500ll_block_common_reg {
	uint8_t                 mode;                  /* MR; bitfield, see CHIPMODE_{x} below */
	struct net_ip4_addr     ip_gateway_addr;       /* GAR0 ... GAR3 */
	struct net_ip4_addr     ip_subnet_mask;        /* SUBR0 ... SUBR3 */
	struct net_eth_addr     eth_addr;              /* SHAR0 ... SHAR5 */
	struct net_ip4_addr     ip_addr;               /* SIPR0 ... SIPR3 */

	uint16be_t              intlevel;              /* INTLEVEL0, INTLEVEL1; if non-zero,
	                                                * hysteresis between pin_intr being pulled
	                                                * low (hysteresis=(intlevel+1)*4/(150MHz)) */
	uint8_t                 chip_interrupt;        /* IR; bitfield, see CHIPINTR_{x} below */
	uint8_t                 chip_interrupt_mask;   /* IMR; bitfield, see CHIPINTR_{x} below, 0=disable, 1=enable */
	uint8_t                 sock_interrupt;        /* SIR; bitfield of which sockets have their .interrupt set  */
	uint8_t                 sock_interrupt_mask;   /* SIMR; bitfield of sockets, 0=disable, 1=enable */
	uint16be_t              retry_time;            /* RTR0, RTR0; configures re-transmission period, in units of 100µs */
	uint8_t                 retry_count;           /* RCR; configures max re-transmission count */

	uint8_t                 ppp_lcp_request_timer; /* PTIMER */
	uint8_t                 ppp_lcp_magic_bumber;  /* PMAGIC */
	struct net_eth_addr     ppp_dst_eth_addr;      /* PHAR0 ... PHAR5 */
	uint16be_t              ppp_sess_id;           /* PSID0 ... PSID1 */
	uint16be_t              ppp_max_seg_size;      /* PMRU0 ... PMRU1 */

	struct net_ip4_addr     unreachable_ip_addr;   /* UIPR0 ... UIPR3 */
	uint16be_t              unreachable_port;      /* UPORTR0, UPORTR1 */

	uint8_t                 phy_cfg;               /* PHYCFGR */

	uint8_t                 _reserved[10];

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

/* bitfield */
#define CHIPMODE_RST        ((uint8_t)(1<<7)) /* software reset */
#define _CHIPMODE_UNUSED6   ((uint8_t)(1<<6))
#define CHIPMODE_WOL        ((uint8_t)(1<<5)) /* wake-on-lan */
#define CHIPMODE_BLOCK_PING ((uint8_t)(1<<4))
#define CHIPMODE_PPP        ((uint8_t)(1<<3))
#define _CHIPMODE_UNUSED2   ((uint8_t)(1<<2))
#define CHIPMODE_FORCE_ARP  ((uint8_t)(1<<1))
#define _CHIPMODE_UNUSED0   ((uint8_t)(1<<0))

#define CHIPINTR_CONFLICT  ((uint8_t)(1<<7)) /* ARP says remote IP is self */
#define CHIPINTR_UNREACH   ((uint8_t)(1<<6))
#define CHIPINTR_PPP_CLOSE ((uint8_t)(1<<6))
#define CHIPINTR_WOL       ((uint8_t)(1<<4)) /* wake-on-LAN */
#define _CHIPINTR_UNUSED3  ((uint8_t)(1<<3))
#define _CHIPINTR_UNUSED2  ((uint8_t)(1<<2))
#define _CHIPINTR_UNUSED1  ((uint8_t)(1<<1))
#define _CHIPINTR_UNUSED0  ((uint8_t)(1<<0))

#define w5500ll_write_common_reg(spidev, field, val)       \
	w5500ll_write_reg(spidev,                          \
	                  CTL_BLOCK_COMMON_REG,            \
	                  struct w5500ll_block_common_reg, \
	                  field, val)


#define w5500ll_read_common_reg(spidev, field)            \
	w5500ll_read_reg(spidev,                          \
	                 CTL_BLOCK_COMMON_REG,            \
	                 struct w5500ll_block_common_reg, \
	                 field)

/* Per-socket registers.  *****************************************************/

struct w5500ll_block_sock_reg {
	uint8_t                 mode;             /* Sn_MR; see SOCKMODE_{x} below */
	uint8_t                 command;          /* Sn_CR; see CMD_{x} below */
	uint8_t                 interrupt;        /* Sn_IR; bitfield, see SOCKINTR_{x} below */
	uint8_t                 state;            /* Sn_SR; see STATE_{x} below */
	uint16be_t              local_port;       /* Sn_PORT0, Sn_PORT1 */
	struct net_eth_addr     remote_eth_addr;  /* Sn_DHAR0 ... SnDHAR5 */
	struct net_ip4_addr     remote_ip_addr;   /* Sn_DIPR0 ... Sn_DIP3 */
	uint16be_t              remote_port;      /* Sn_DPORT0 ... Sn_DPORT1 */

	uint16be_t              max_seg_size;     /* 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; in KiB, power of 2, <= 16 */
	uint8_t                 tx_buf_size;      /* Sn_TXBUF_SIZE; in KiB, power of 2, <= 16 */
	uint16be_t              tx_free_size;     /* Sn_TX_FSR0, Sn_TX_FSR1 */
	uint16be_t              tx_read_pointer;  /* Sn_TX_RD0, Sn_TX_RD1 */
	uint16be_t              tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */
	uint16be_t              rx_size;          /* Sn_RX_RSR0, Sn_RX_RSR1 */
	uint16be_t              rx_read_pointer;  /* Sn_RX_RD0, Sn_RX_RD1 */
	uint16be_t              rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */

	uint8_t                 interrupt_mask;   /* Sn_IMR */
	uint16be_t              fragment_offset;  /* Sn_FRAG0, Sn_FRAG1 */
	uint8_t                 keepalive_timer;  /* Sn_KPALVTR */
};
static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30);

/* low 4 bits are the main enum, high 4 bits are flags */
#define SOCKMODE_CLOSED ((uint8_t)0b0000)
#define SOCKMODE_TCP    ((uint8_t)0b0001)
#define SOCKMODE_UDP    ((uint8_t)0b0010)
#define SOCKMODE_MACRAW ((uint8_t)0b0100)

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

#define SOCKMODE_FLAG_UDP_ENABLE_MULTICAST    ((uint8_t)(1<<7))
#define SOCKMODE_FLAG_UDP_BLOCK_BROADCAST     ((uint8_t)(1<<6))
#define SOCKMODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5))
#define SOCKMODE_FLAG_UDP_BLOCK_UNICAST       ((uint8_t)(1<<4))

#define SOCKMODE_FLAG_MACRAW_MAC_FILTERING    ((uint8_t)(1<<7))
#define SOCKMODE_FLAG_MACRAW_BLOCK_BROADCAST  ((uint8_t)(1<<6))
#define SOCKMODE_FLAG_MACRAW_BLOCK_MULTICAST  ((uint8_t)(1<<5))
#define SOCKMODE_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 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 _SOCKINTR_SEND_UNUSED7 ((uint8_t)1<<7)
#define _SOCKINTR_SEND_UNUSED6 ((uint8_t)1<<6)
#define _SOCKINTR_SEND_UNUSED5 ((uint8_t)1<<5)
#define SOCKINTR_SEND_OK       ((uint8_t)1<<4)
#define SOCKINTR_TIMEOUT       ((uint8_t)1<<3)
#define SOCKINTR_RECV          ((uint8_t)1<<2)
#define SOCKINTR_FIN           ((uint8_t)1<<1)
#define SOCKINTR_CONN          ((uint8_t)1<<1) /* first for SYN, then when SOCKMODE_ESTABLISHED */

#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 w5500ll_write_sock_reg(spidev, socknum, field, val) \
	w5500ll_write_reg(spidev,                           \
	                  CTL_BLOCK_SOCK(socknum, REG),     \
	                  struct w5500ll_block_sock_reg,    \
	                  field, val)

#define w5500ll_read_sock_reg(spidev, socknum, field)   \
	w5500ll_read_reg(spidev,                        \
	                 CTL_BLOCK_SOCK(socknum, REG),  \
	                 struct w5500ll_block_sock_reg, \
	                 field)

/******************************************************************************/

#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \
		typeof((blocktyp){}.field) lval = val;                \
		w5500ll_write(spidev,                                 \
		                     offsetof(blocktyp, field),       \
		                     blockid,                         \
		                     &lval,                           \
		                     sizeof(lval));                   \
	} while (0)

/* The datasheet tells us that multi-byte reads are non-atomic and
 * that "it is recommended that you read all 16-bits twice or more
 * until getting the same value".  */
#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({              \
		typeof((blocktyp){}.field) val;                            \
		w5500ll_read(spidev,                                       \
		             offsetof(blocktyp, field),                    \
		             blockid,                                      \
		             &val,                                         \
		             sizeof(val));                                 \
		if (sizeof(val) > 1)                                       \
			for (;;) {                                         \
				typeof(val) val2;                          \
				w5500ll_read(spidev,                       \
				             offsetof(blocktyp, field),    \
				             blockid,                      \
				             &val2,                        \
				             sizeof(val));                 \
				if (memcmp(&val2, &val, sizeof(val)) == 0) \
					break;                             \
				val = val2;                                \
			}                                                  \
		val;                                                       \
	})

#endif /* _LIBHW_W5500_LL_H_ */