aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/dpaa/fman_keygen.c
blob: d261d8c70f8170517e5b91737bbe71d7d07e9406 (plain) (blame)
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);
}