summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/microsoft/mana/shm_channel.c
blob: 5553af9c8085a1b93735f40e3910e19f3e5daf29 (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
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2021, Microsoft Corporation. */

#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/mm.h>

#include <net/mana/shm_channel.h>

#define PAGE_FRAME_L48_WIDTH_BYTES 6
#define PAGE_FRAME_L48_WIDTH_BITS (PAGE_FRAME_L48_WIDTH_BYTES * 8)
#define PAGE_FRAME_L48_MASK 0x0000FFFFFFFFFFFF
#define PAGE_FRAME_H4_WIDTH_BITS 4
#define VECTOR_MASK 0xFFFF
#define SHMEM_VF_RESET_STATE ((u32)-1)

#define SMC_MSG_TYPE_ESTABLISH_HWC 1
#define SMC_MSG_TYPE_ESTABLISH_HWC_VERSION 0

#define SMC_MSG_TYPE_DESTROY_HWC 2
#define SMC_MSG_TYPE_DESTROY_HWC_VERSION 0

#define SMC_MSG_DIRECTION_REQUEST 0
#define SMC_MSG_DIRECTION_RESPONSE 1

/* Structures labeled with "HW DATA" are exchanged with the hardware. All of
 * them are naturally aligned and hence don't need __packed.
 */

/* Shared memory channel protocol header
 *
 * msg_type: set on request and response; response matches request.
 * msg_version: newer PF writes back older response (matching request)
 *  older PF acts on latest version known and sets that version in result
 *  (less than request).
 * direction: 0 for request, VF->PF; 1 for response, PF->VF.
 * status: 0 on request,
 *   operation result on response (success = 0, failure = 1 or greater).
 * reset_vf: If set on either establish or destroy request, indicates perform
 *  FLR before/after the operation.
 * owner_is_pf: 1 indicates PF owned, 0 indicates VF owned.
 */
union smc_proto_hdr {
	u32 as_uint32;

	struct {
		u8 msg_type	: 3;
		u8 msg_version	: 3;
		u8 reserved_1	: 1;
		u8 direction	: 1;

		u8 status;

		u8 reserved_2;

		u8 reset_vf	: 1;
		u8 reserved_3	: 6;
		u8 owner_is_pf	: 1;
	};
}; /* HW DATA */

#define SMC_APERTURE_BITS 256
#define SMC_BASIC_UNIT (sizeof(u32))
#define SMC_APERTURE_DWORDS (SMC_APERTURE_BITS / (SMC_BASIC_UNIT * 8))
#define SMC_LAST_DWORD (SMC_APERTURE_DWORDS - 1)

static int mana_smc_poll_register(void __iomem *base, bool reset)
{
	void __iomem *ptr = base + SMC_LAST_DWORD * SMC_BASIC_UNIT;
	u32 last_dword;
	int i;

	/* Poll the hardware for the ownership bit. This should be pretty fast,
	 * but let's do it in a loop just in case the hardware or the PF
	 * driver are temporarily busy.
	 */
	for (i = 0; i < 20 * 1000; i++)  {
		last_dword = readl(ptr);

		/* shmem reads as 0xFFFFFFFF in the reset case */
		if (reset && last_dword == SHMEM_VF_RESET_STATE)
			return 0;

		/* If bit_31 is set, the PF currently owns the SMC. */
		if (!(last_dword & BIT(31)))
			return 0;

		usleep_range(1000, 2000);
	}

	return -ETIMEDOUT;
}

static int mana_smc_read_response(struct shm_channel *sc, u32 msg_type,
				  u32 msg_version, bool reset_vf)
{
	void __iomem *base = sc->base;
	union smc_proto_hdr hdr;
	int err;

	/* Wait for PF to respond. */
	err = mana_smc_poll_register(base, reset_vf);
	if (err)
		return err;

	hdr.as_uint32 = readl(base + SMC_LAST_DWORD * SMC_BASIC_UNIT);

	if (reset_vf && hdr.as_uint32 == SHMEM_VF_RESET_STATE)
		return 0;

	/* Validate protocol fields from the PF driver */
	if (hdr.msg_type != msg_type || hdr.msg_version > msg_version ||
	    hdr.direction != SMC_MSG_DIRECTION_RESPONSE) {
		dev_err(sc->dev, "Wrong SMC response 0x%x, type=%d, ver=%d\n",
			hdr.as_uint32, msg_type, msg_version);
		return -EPROTO;
	}

	/* Validate the operation result */
	if (hdr.status != 0) {
		dev_err(sc->dev, "SMC operation failed: 0x%x\n", hdr.status);
		return -EPROTO;
	}

	return 0;
}

void mana_smc_init(struct shm_channel *sc, struct device *dev,
		   void __iomem *base)
{
	sc->dev = dev;
	sc->base = base;
}

int mana_smc_setup_hwc(struct shm_channel *sc, bool reset_vf, u64 eq_addr,
		       u64 cq_addr, u64 rq_addr, u64 sq_addr,
		       u32 eq_msix_index)
{
	union smc_proto_hdr *hdr;
	u16 all_addr_h4bits = 0;
	u16 frame_addr_seq = 0;
	u64 frame_addr = 0;
	u8 shm_buf[32];
	u64 *shmem;
	u32 *dword;
	u8 *ptr;
	int err;
	int i;

	/* Ensure VF already has possession of shared memory */
	err = mana_smc_poll_register(sc->base, false);
	if (err) {
		dev_err(sc->dev, "Timeout when setting up HWC: %d\n", err);
		return err;
	}

	if (!PAGE_ALIGNED(eq_addr) || !PAGE_ALIGNED(cq_addr) ||
	    !PAGE_ALIGNED(rq_addr) || !PAGE_ALIGNED(sq_addr))
		return -EINVAL;

	if ((eq_msix_index & VECTOR_MASK) != eq_msix_index)
		return -EINVAL;

	/* Scheme for packing four addresses and extra info into 256 bits.
	 *
	 * Addresses must be page frame aligned, so only frame address bits
	 * are transferred.
	 *
	 * 52-bit frame addresses are split into the lower 48 bits and upper
	 * 4 bits. Lower 48 bits of 4 address are written sequentially from
	 * the start of the 256-bit shared memory region followed by 16 bits
	 * containing the upper 4 bits of the 4 addresses in sequence.
	 *
	 * A 16 bit EQ vector number fills out the next-to-last 32-bit dword.
	 *
	 * The final 32-bit dword is used for protocol control information as
	 * defined in smc_proto_hdr.
	 */

	memset(shm_buf, 0, sizeof(shm_buf));
	ptr = shm_buf;

	/* EQ addr: low 48 bits of frame address */
	shmem = (u64 *)ptr;
	frame_addr = PHYS_PFN(eq_addr);
	*shmem = frame_addr & PAGE_FRAME_L48_MASK;
	all_addr_h4bits |= (frame_addr >> PAGE_FRAME_L48_WIDTH_BITS) <<
		(frame_addr_seq++ * PAGE_FRAME_H4_WIDTH_BITS);
	ptr += PAGE_FRAME_L48_WIDTH_BYTES;

	/* CQ addr: low 48 bits of frame address */
	shmem = (u64 *)ptr;
	frame_addr = PHYS_PFN(cq_addr);
	*shmem = frame_addr & PAGE_FRAME_L48_MASK;
	all_addr_h4bits |= (frame_addr >> PAGE_FRAME_L48_WIDTH_BITS) <<
		(frame_addr_seq++ * PAGE_FRAME_H4_WIDTH_BITS);
	ptr += PAGE_FRAME_L48_WIDTH_BYTES;

	/* RQ addr: low 48 bits of frame address */
	shmem = (u64 *)ptr;
	frame_addr = PHYS_PFN(rq_addr);
	*shmem = frame_addr & PAGE_FRAME_L48_MASK;
	all_addr_h4bits |= (frame_addr >> PAGE_FRAME_L48_WIDTH_BITS) <<
		(frame_addr_seq++ * PAGE_FRAME_H4_WIDTH_BITS);
	ptr += PAGE_FRAME_L48_WIDTH_BYTES;

	/* SQ addr: low 48 bits of frame address */
	shmem = (u64 *)ptr;
	frame_addr = PHYS_PFN(sq_addr);
	*shmem = frame_addr & PAGE_FRAME_L48_MASK;
	all_addr_h4bits |= (frame_addr >> PAGE_FRAME_L48_WIDTH_BITS) <<
		(frame_addr_seq++ * PAGE_FRAME_H4_WIDTH_BITS);
	ptr += PAGE_FRAME_L48_WIDTH_BYTES;

	/* High 4 bits of the four frame addresses */
	*((u16 *)ptr) = all_addr_h4bits;
	ptr += sizeof(u16);

	/* EQ MSIX vector number */
	*((u16 *)ptr) = (u16)eq_msix_index;
	ptr += sizeof(u16);

	/* 32-bit protocol header in final dword */
	*((u32 *)ptr) = 0;

	hdr = (union smc_proto_hdr *)ptr;
	hdr->msg_type = SMC_MSG_TYPE_ESTABLISH_HWC;
	hdr->msg_version = SMC_MSG_TYPE_ESTABLISH_HWC_VERSION;
	hdr->direction = SMC_MSG_DIRECTION_REQUEST;
	hdr->reset_vf = reset_vf;

	/* Write 256-message buffer to shared memory (final 32-bit write
	 * triggers HW to set possession bit to PF).
	 */
	dword = (u32 *)shm_buf;
	for (i = 0; i < SMC_APERTURE_DWORDS; i++)
		writel(*dword++, sc->base + i * SMC_BASIC_UNIT);

	/* Read shmem response (polling for VF possession) and validate.
	 * For setup, waiting for response on shared memory is not strictly
	 * necessary, since wait occurs later for results to appear in EQE's.
	 */
	err = mana_smc_read_response(sc, SMC_MSG_TYPE_ESTABLISH_HWC,
				     SMC_MSG_TYPE_ESTABLISH_HWC_VERSION,
				     reset_vf);
	if (err) {
		dev_err(sc->dev, "Error when setting up HWC: %d\n", err);
		return err;
	}

	return 0;
}

int mana_smc_teardown_hwc(struct shm_channel *sc, bool reset_vf)
{
	union smc_proto_hdr hdr = {};
	int err;

	/* Ensure already has possession of shared memory */
	err = mana_smc_poll_register(sc->base, false);
	if (err) {
		dev_err(sc->dev, "Timeout when tearing down HWC\n");
		return err;
	}

	/* Set up protocol header for HWC destroy message */
	hdr.msg_type = SMC_MSG_TYPE_DESTROY_HWC;
	hdr.msg_version = SMC_MSG_TYPE_DESTROY_HWC_VERSION;
	hdr.direction = SMC_MSG_DIRECTION_REQUEST;
	hdr.reset_vf = reset_vf;

	/* Write message in high 32 bits of 256-bit shared memory, causing HW
	 * to set possession bit to PF.
	 */
	writel(hdr.as_uint32, sc->base + SMC_LAST_DWORD * SMC_BASIC_UNIT);

	/* Read shmem response (polling for VF possession) and validate.
	 * For teardown, waiting for response is required to ensure hardware
	 * invalidates MST entries before software frees memory.
	 */
	err = mana_smc_read_response(sc, SMC_MSG_TYPE_DESTROY_HWC,
				     SMC_MSG_TYPE_DESTROY_HWC_VERSION,
				     reset_vf);
	if (err) {
		dev_err(sc->dev, "Error when tearing down HWC: %d\n", err);
		return err;
	}

	return 0;
}