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
364
365
366
367
368
369
370
371
|
/*-
* Copyright (c) 2026 Justin Hibbits
*
* SPDX-License-Identifier: BSD-2-Clause
*
* FMan KeyGen (KG) support. The KG block is the FMan sub-unit that
* classifies incoming frames into destination FQIDs via user-defined
* "schemes". Each scheme extracts a key from the frame's parse
* result, hashes it, and enqueues to FQID = base | (hash & mask).
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <machine/bus.h>
#include "fman.h"
#include "fman_keygen.h"
#define FMAN_KG_OFFSET 0xc1000
/*
* Register offsets, relative to FMAN_KG_OFFSET (0xc1000) inside the
* FMan register block.
*/
#define FMKG_GCR 0x000 /* general configuration */
#define FMKG_GCR_EN 0x80000000
#define FMKG_EER 0x00C /* error event */
#define FMKG_EER_DOUBLE_ECC 0x80000000
#define FMKG_EER_KEYSIZE_OVF 0x40000000
#define FMKG_EEER 0x010 /* error event enable */
#define FMKG_SEER 0x01C /* scheme error event */
#define FMKG_SEEER 0x020 /* scheme error event enable */
#define FMKG_GSR 0x024 /* global status */
#define FMKG_GSR_BSY 0x80000000
#define FMKG_TPC 0x028 /* total packet counter */
#define FMKG_SERC 0x02C /* soft error capture */
#define FMKG_FDOR 0x034 /* frame data offset */
#define FMKG_GDV0R 0x038 /* global default value 0 */
#define FMKG_GDV1R 0x03C /* global default value 1 */
#define FMKG_FEER 0x044 /* force error event */
#define FMKG_AR 0x1FC /* action register */
#define FMKG_AR_GO 0x80000000
#define FMKG_AR_READ 0x40000000 /* clear = write */
#define FMKG_AR_ERR 0x20000000
#define FMKG_AR_SEL_CLS_PLAN 0x01000000
#define FMKG_AR_SEL_PORT 0x02000000 /* clear = scheme entry */
#define FMKG_AR_PORT_WSEL_SP 0x00008000
#define FMKG_AR_PORT_WSEL_CPP 0x00004000
#define FMKG_AR_SCM_WSEL_UPDCNT 0x00008000
#define FMKG_AR_NUM_SHIFT 16 /* scheme id here */
/*
* Indirect access sub-region (union of "scheme entry" and "port
* entry" views, discriminated by the last AR selector written).
*/
#define FMKG_IND 0x100
/* Scheme-view offsets, relative to FMKG_IND. */
#define FMKG_SE_MODE 0x00 /* mode + NIA */
#define FMKG_SE_MODE_EN 0x80000000
#define FMKG_SE_EKFC 0x04 /* extract known fields command */
#define FMKG_EKFC_IPSRC1 0x00100000
#define FMKG_EKFC_IPDST1 0x00080000
#define FMKG_EKFC_L4PSRC 0x00000004
#define FMKG_EKFC_L4PDST 0x00000002
#define FMKG_EKFC_IPSEC_SPI 0x00000200
#define FMKG_SE_EKDV 0x08 /* extract known default value */
#define FMKG_EKDV_IP_ADDR_SHIFT 18
#define FMKG_EKDV_L4_PORT_SHIFT 8
#define FMKG_EKDV_USE_DV0 2
#define FMKG_EKDV_USE_DV1 3
#define FMKG_SE_BMCH 0x0C /* bit mask high */
#define FMKG_SE_BMCL 0x10 /* bit mask low */
#define FMKG_SE_FQB 0x14 /* frame queue base */
#define FMKG_SE_HC 0x18 /* hash command */
#define FMKG_SE_HC_SYM 0x40000000 /* symmetric hash */
#define FMKG_SE_HC_SHIFT_SHIFT 24
#define FMKG_SE_HC_MASK_SHIFT 0
#define FMKG_SE_PPC 0x1C /* policer profile command */
#define FMKG_SE_GEC(i) (0x20 + (i) * 4) /* generic extract command [0..7] */
#define FMKG_SE_SPC 0x40 /* statistic packet counter */
#define FMKG_SE_DV0 0x44 /* default value 0 */
#define FMKG_SE_DV1 0x48 /* default value 1 */
#define FMKG_SE_CCBS 0x4C /* coarse classification */
#define FMKG_SE_MV 0x50 /* match vector */
#define FMKG_SE_OM 0x54 /* operation mode */
#define FMKG_SE_VSP 0x58 /* virtual storage profile */
#define FMKG_SE_VSP_NO_KSP_EN 0x80000000
/* Port-view offsets, relative to FMKG_IND. */
#define FMKG_PE_SP 0x00 /* port scheme partition */
#define FMKG_PE_CPP 0x04 /* port classification-plan partition */
/*
* Default NIA for post-KeyGen
*/
#define FMKG_ENQ_KG_DFLT_NIA \
(NIA_ENG_BMI | NIA_BMI_AC_ENQ_FRAME)
/*
* Arbitrary fillers used for the "missing field" fallback slots.
* The exact values don't matter for correctness -- the hash still
* distributes uniformly across FQs -- but a non-zero pattern makes
* two frames missing the same field hash to a consistent bucket.
*/
#define FMKG_DFLT_IPv4_ADDR 0x0a0a0a0a
#define FMKG_DFLT_L4_PORT 0x0b0b0b0b
/* Sizing. */
#define FMKG_MAX_SCHEMES 32
#define FMKG_MAX_HW_PORTS 64
/*
* AR-write acknowledge wait. Linux busy-loops with no bound; we
* cap at ~1 ms of DELAY(1) to survive a wedged block without
* hanging the boot.
*/
#define FMKG_AR_WAIT_US 1000
static inline uint32_t
kg_read(struct fman_softc *sc, bus_size_t off)
{
return (bus_read_4(sc->mem_res, FMAN_KG_OFFSET + off));
}
static inline void
kg_write(struct fman_softc *sc, bus_size_t off, uint32_t val)
{
bus_write_4(sc->mem_res, FMAN_KG_OFFSET + off, val);
}
static int
kg_ar_write(struct fman_softc *sc, uint32_t ar)
{
int i;
kg_write(sc, FMKG_AR, ar);
for (i = 0; i < FMKG_AR_WAIT_US; i++) {
ar = kg_read(sc, FMKG_AR);
if ((ar & FMKG_AR_GO) == 0)
break;
DELAY(1);
}
if ((ar & FMKG_AR_GO) != 0) {
device_printf(sc->dev,
"fman_kg: AR stuck busy (0x%08x)\n", ar);
return (ETIMEDOUT);
}
if ((ar & FMKG_AR_ERR) != 0) {
device_printf(sc->dev,
"fman_kg: AR reported error (0x%08x)\n", ar);
return (EIO);
}
return (0);
}
/*
* Populate the indirect scheme registers for an RSS-style hash
* scheme and commit it via an AR-write.
*/
static int
kg_program_scheme(struct fman_softc *sc, uint8_t scheme_id,
uint32_t base_fqid, uint32_t nfqs)
{
uint32_t ekdv;
int error;
kg_write(sc, FMKG_IND + FMKG_SE_MODE,
FMKG_SE_MODE_EN | FMKG_ENQ_KG_DFLT_NIA);
kg_write(sc, FMKG_IND + FMKG_SE_EKFC,
FMKG_EKFC_IPSRC1 | FMKG_EKFC_IPDST1 |
FMKG_EKFC_L4PSRC | FMKG_EKFC_L4PDST |
FMKG_EKFC_IPSEC_SPI);
ekdv = (FMKG_EKDV_USE_DV0 << FMKG_EKDV_IP_ADDR_SHIFT) |
(FMKG_EKDV_USE_DV1 << FMKG_EKDV_L4_PORT_SHIFT);
kg_write(sc, FMKG_IND + FMKG_SE_EKDV, ekdv);
kg_write(sc, FMKG_IND + FMKG_SE_DV0, FMKG_DFLT_IPv4_ADDR);
kg_write(sc, FMKG_IND + FMKG_SE_DV1, FMKG_DFLT_L4_PORT);
kg_write(sc, FMKG_IND + FMKG_SE_FQB, base_fqid);
/*
* FQID mask width = (nfqs - 1), no hash right-shift. Symmetric
* hash is deliberately off: per NXP's own driver notes,
* spreading breaks with SYM set even though the extraction key
* is nominally symmetric.
*/
kg_write(sc, FMKG_IND + FMKG_SE_HC,
(nfqs - 1) << FMKG_SE_HC_MASK_SHIFT);
/*
* TODO: Revisit these registers later, if necessary.
*/
kg_write(sc, FMKG_IND + FMKG_SE_BMCH, 0);
kg_write(sc, FMKG_IND + FMKG_SE_BMCL, 0);
kg_write(sc, FMKG_IND + FMKG_SE_PPC, 0);
kg_write(sc, FMKG_IND + FMKG_SE_SPC, 0);
for (int i = 0; i < 8; i++)
kg_write(sc, FMKG_IND + FMKG_SE_GEC(i), 0);
kg_write(sc, FMKG_IND + FMKG_SE_CCBS, 0);
kg_write(sc, FMKG_IND + FMKG_SE_MV, 0); /* indirect scheme */
kg_write(sc, FMKG_IND + FMKG_SE_OM, 0);
/*
* Don't let the scheme override the port's Virtual Storage
* Profile.
*/
kg_write(sc, FMKG_IND + FMKG_SE_VSP, FMKG_SE_VSP_NO_KSP_EN);
error = kg_ar_write(sc, FMKG_AR_GO |
(scheme_id << FMKG_AR_NUM_SHIFT) | FMKG_AR_SCM_WSEL_UPDCNT);
if (error != 0)
device_printf(sc->dev,
"fman_kg: scheme %u program failed\n", scheme_id);
return (error);
}
static int
kg_disable_scheme(struct fman_softc *sc, uint8_t scheme_id)
{
kg_write(sc, FMKG_IND + FMKG_SE_MODE, 0);
return (kg_ar_write(sc, FMKG_AR_GO | (scheme_id << FMKG_AR_NUM_SHIFT)));
}
/*
* Read-modify-write the port's scheme-partition bitmap to add or
* remove @scheme_id.
*/
static int
kg_bind_scheme(struct fman_softc *sc, uint8_t hw_port_id, uint8_t scheme_id,
bool bind)
{
uint32_t sp;
int error;
error = kg_ar_write(sc,
FMKG_AR_GO | FMKG_AR_READ | FMKG_AR_SEL_PORT |
hw_port_id | FMKG_AR_PORT_WSEL_SP);
if (error != 0)
return (error);
sp = kg_read(sc, FMKG_IND + FMKG_PE_SP);
if (bind)
sp |= (1U << (31 - scheme_id));
else
sp &= ~(1U << (31 - scheme_id));
kg_write(sc, FMKG_IND + FMKG_PE_SP, sp);
return (kg_ar_write(sc,
FMKG_AR_GO | FMKG_AR_SEL_PORT | hw_port_id | FMKG_AR_PORT_WSEL_SP));
}
int
fman_kg_init(struct fman_softc *sc)
{
int error, i;
sc->sc_kg_schemes_used = 0;
for (i = 0; i < FMKG_MAX_HW_PORTS; i++)
sc->sc_kg_port_scheme[i] = -1;
kg_write(sc, FMKG_GCR, FMKG_ENQ_KG_DFLT_NIA);
kg_write(sc, FMKG_EER,
FMKG_EER_DOUBLE_ECC | FMKG_EER_KEYSIZE_OVF);
kg_write(sc, FMKG_FDOR, 0);
kg_write(sc, FMKG_GDV0R, 0);
kg_write(sc, FMKG_GDV1R, 0);
for (i = 0; i < FMKG_MAX_HW_PORTS; i++) {
kg_write(sc, FMKG_IND + FMKG_PE_SP, 0);
error = kg_ar_write(sc, FMKG_AR_GO | FMKG_AR_SEL_PORT |
i | FMKG_AR_PORT_WSEL_SP);
if (error != 0)
return (error);
kg_write(sc, FMKG_IND + FMKG_PE_CPP, 0);
error = kg_ar_write(sc, FMKG_AR_GO | FMKG_AR_SEL_PORT |
i | FMKG_AR_PORT_WSEL_CPP);
if (error != 0)
return (error);
}
/* Enable per-scheme error events. */
kg_write(sc, FMKG_SEER, 0xffffffff);
kg_write(sc, FMKG_SEEER, 0xffffffff);
/* TODO: Error handling interrupts -- register with FMan */
/* Enable the block. */
kg_write(sc, FMKG_GCR, kg_read(sc, FMKG_GCR) | FMKG_GCR_EN);
return (0);
}
void
fman_kg_fini(struct fman_softc *sc)
{
/* No need for a full teardown, just disable the module. */
kg_write(sc, FMKG_GCR, 0);
while ((kg_read(sc, FMKG_GSR) & FMKG_GSR_BSY) != 0)
DELAY(1);
}
int
fman_kg_alloc_hash_scheme(struct fman_softc *sc, int hw_port_id,
uint32_t base_fqid, uint32_t nfqs)
{
uint8_t scheme_id;
int error, i;
if (hw_port_id < 0 || hw_port_id >= FMKG_MAX_HW_PORTS)
return (EINVAL);
if (base_fqid == 0 || (base_fqid & ~0x00ffffff) != 0)
return (EINVAL);
if (nfqs == 0 || (nfqs & (nfqs - 1)) != 0)
return (EINVAL); /* not a power of two */
if ((base_fqid & (nfqs - 1)) != 0)
return (EINVAL); /* base not aligned to nfqs */
if (sc->sc_kg_port_scheme[hw_port_id] != -1)
return (EBUSY);
for (i = 0; i < FMKG_MAX_SCHEMES; i++) {
if ((sc->sc_kg_schemes_used & (1U << i)) == 0) {
scheme_id = i;
break;
}
}
if (i == FMKG_MAX_SCHEMES)
return (ENOSPC);
error = kg_program_scheme(sc, scheme_id, base_fqid, nfqs);
if (error != 0)
return (error);
error = kg_bind_scheme(sc, hw_port_id, scheme_id, true);
if (error != 0) {
(void)kg_disable_scheme(sc, scheme_id);
return (error);
}
sc->sc_kg_schemes_used |= (1U << scheme_id);
sc->sc_kg_port_scheme[hw_port_id] = scheme_id;
return (0);
}
int
fman_kg_free_hash_scheme(struct fman_softc *sc, int hw_port_id)
{
int8_t scheme_id;
int error;
if (hw_port_id < 0 || hw_port_id >= FMKG_MAX_HW_PORTS)
return (EINVAL);
scheme_id = sc->sc_kg_port_scheme[hw_port_id];
if (scheme_id == -1)
return (ENOENT);
error = kg_bind_scheme(sc, hw_port_id, scheme_id, false);
if (error != 0)
return (error);
error = kg_disable_scheme(sc, scheme_id);
if (error != 0)
return (error);
sc->sc_kg_schemes_used &= ~(1U << scheme_id);
sc->sc_kg_port_scheme[hw_port_id] = -1;
return (0);
}
|