xref: /spdk/module/accel/dsa/accel_dsa.c (revision aaba5d9c9e8fca9925d5812030ff3ec9ba869fa3)
1 /*   SPDX-License-Identifier: BSD-3-Clause
2  *   Copyright (C) 2022 Intel Corporation.
3  *   All rights reserved.
4  */
5 
6 #include "accel_dsa.h"
7 
8 #include "spdk/stdinc.h"
9 
10 #include "spdk_internal/accel_module.h"
11 #include "spdk/log.h"
12 #include "spdk_internal/idxd.h"
13 
14 #include "spdk/env.h"
15 #include "spdk/event.h"
16 #include "spdk/likely.h"
17 #include "spdk/thread.h"
18 #include "spdk/idxd.h"
19 #include "spdk/util.h"
20 #include "spdk/json.h"
21 #include "spdk/trace.h"
22 #include "spdk_internal/trace_defs.h"
23 
24 static bool g_dsa_enable = false;
25 static bool g_kernel_mode = false;
26 
27 enum channel_state {
28 	IDXD_CHANNEL_ACTIVE,
29 	IDXD_CHANNEL_ERROR,
30 };
31 
32 static bool g_dsa_initialized = false;
33 
34 struct idxd_device {
35 	struct				spdk_idxd_device *dsa;
36 	TAILQ_ENTRY(idxd_device)	tailq;
37 };
38 static TAILQ_HEAD(, idxd_device) g_dsa_devices = TAILQ_HEAD_INITIALIZER(g_dsa_devices);
39 static struct idxd_device *g_next_dev = NULL;
40 static uint32_t g_num_devices = 0;
41 static pthread_mutex_t g_dev_lock = PTHREAD_MUTEX_INITIALIZER;
42 
43 struct idxd_task {
44 	struct spdk_accel_task	task;
45 	struct idxd_io_channel	*chan;
46 };
47 
48 struct idxd_io_channel {
49 	struct spdk_idxd_io_channel	*chan;
50 	struct idxd_device		*dev;
51 	enum channel_state		state;
52 	struct spdk_poller		*poller;
53 	uint32_t			num_outstanding;
54 	TAILQ_HEAD(, spdk_accel_task)	queued_tasks;
55 };
56 
57 static struct spdk_io_channel *dsa_get_io_channel(void);
58 
59 static struct idxd_device *
60 idxd_select_device(struct idxd_io_channel *chan)
61 {
62 	uint32_t count = 0;
63 	struct idxd_device *dev;
64 	uint32_t socket_id = spdk_env_get_socket_id(spdk_env_get_current_core());
65 
66 	/*
67 	 * We allow channels to share underlying devices,
68 	 * selection is round-robin based with a limitation
69 	 * on how many channel can share one device.
70 	 */
71 	do {
72 		/* select next device */
73 		pthread_mutex_lock(&g_dev_lock);
74 		g_next_dev = TAILQ_NEXT(g_next_dev, tailq);
75 		if (g_next_dev == NULL) {
76 			g_next_dev = TAILQ_FIRST(&g_dsa_devices);
77 		}
78 		dev = g_next_dev;
79 		pthread_mutex_unlock(&g_dev_lock);
80 
81 		if (socket_id != spdk_idxd_get_socket(dev->dsa)) {
82 			continue;
83 		}
84 
85 		/*
86 		 * Now see if a channel is available on this one. We only
87 		 * allow a specific number of channels to share a device
88 		 * to limit outstanding IO for flow control purposes.
89 		 */
90 		chan->chan = spdk_idxd_get_channel(dev->dsa);
91 		if (chan->chan != NULL) {
92 			SPDK_DEBUGLOG(accel_dsa, "On socket %d using device on socket %d\n",
93 				      socket_id, spdk_idxd_get_socket(dev->dsa));
94 			return dev;
95 		}
96 	} while (count++ < g_num_devices);
97 
98 	/* We are out of available channels and/or devices for the local socket. We fix the number
99 	 * of channels that we allocate per device and only allocate devices on the same socket
100 	 * that the current thread is on. If on a 2 socket system it may be possible to avoid
101 	 * this situation by spreading threads across the sockets.
102 	 */
103 	SPDK_ERRLOG("No more DSA devices available on the local socket.\n");
104 	return NULL;
105 }
106 
107 static void
108 dsa_done(void *cb_arg, int status)
109 {
110 	struct idxd_task *idxd_task = cb_arg;
111 	struct idxd_io_channel *chan;
112 
113 	chan = idxd_task->chan;
114 
115 	assert(chan->num_outstanding > 0);
116 	spdk_trace_record(TRACE_ACCEL_DSA_OP_COMPLETE, 0, 0, 0, chan->num_outstanding - 1);
117 	chan->num_outstanding--;
118 
119 	spdk_accel_task_complete(&idxd_task->task, status);
120 }
121 
122 static int
123 idxd_submit_dualcast(struct idxd_io_channel *ch, struct idxd_task *idxd_task, int flags)
124 {
125 	struct spdk_accel_task *task = &idxd_task->task;
126 
127 	if (spdk_unlikely(task->d.iovcnt != 1 || task->d2.iovcnt != 1 || task->s.iovcnt != 1)) {
128 		return -EINVAL;
129 	}
130 
131 	if (spdk_unlikely(task->d.iovs[0].iov_len != task->s.iovs[0].iov_len ||
132 			  task->d.iovs[0].iov_len != task->d2.iovs[0].iov_len)) {
133 		return -EINVAL;
134 	}
135 
136 	return spdk_idxd_submit_dualcast(ch->chan, task->d.iovs[0].iov_base,
137 					 task->d2.iovs[0].iov_base, task->s.iovs[0].iov_base,
138 					 task->d.iovs[0].iov_len, flags, dsa_done, idxd_task);
139 }
140 
141 static int
142 _process_single_task(struct spdk_io_channel *ch, struct spdk_accel_task *task)
143 {
144 	struct idxd_io_channel *chan = spdk_io_channel_get_ctx(ch);
145 	struct idxd_task *idxd_task;
146 	int rc = 0, flags = 0;
147 
148 	idxd_task = SPDK_CONTAINEROF(task, struct idxd_task, task);
149 	idxd_task->chan = chan;
150 
151 	switch (task->op_code) {
152 	case ACCEL_OPC_COPY:
153 		rc = spdk_idxd_submit_copy(chan->chan, task->d.iovs, task->d.iovcnt,
154 					   task->s.iovs, task->s.iovcnt, flags, dsa_done, idxd_task);
155 		break;
156 	case ACCEL_OPC_DUALCAST:
157 		rc = idxd_submit_dualcast(chan, idxd_task, flags);
158 		break;
159 	case ACCEL_OPC_COMPARE:
160 		rc = spdk_idxd_submit_compare(chan->chan, task->s.iovs, task->s.iovcnt,
161 					      task->s2.iovs, task->s2.iovcnt, flags,
162 					      dsa_done, idxd_task);
163 		break;
164 	case ACCEL_OPC_FILL:
165 		rc = spdk_idxd_submit_fill(chan->chan, task->d.iovs, task->d.iovcnt,
166 					   task->fill_pattern, flags, dsa_done, idxd_task);
167 		break;
168 	case ACCEL_OPC_CRC32C:
169 		rc = spdk_idxd_submit_crc32c(chan->chan, task->s.iovs, task->s.iovcnt, task->seed,
170 					     task->crc_dst, flags, dsa_done, idxd_task);
171 		break;
172 	case ACCEL_OPC_COPY_CRC32C:
173 		rc = spdk_idxd_submit_copy_crc32c(chan->chan, task->d.iovs, task->d.iovcnt,
174 						  task->s.iovs, task->s.iovcnt,
175 						  task->seed, task->crc_dst, flags,
176 						  dsa_done, idxd_task);
177 		break;
178 	default:
179 		assert(false);
180 		rc = -EINVAL;
181 		break;
182 	}
183 
184 	if (rc == 0) {
185 		chan->num_outstanding++;
186 		spdk_trace_record(TRACE_ACCEL_DSA_OP_SUBMIT, 0, 0, 0, chan->num_outstanding);
187 	}
188 
189 	return rc;
190 }
191 
192 static int
193 dsa_submit_tasks(struct spdk_io_channel *ch, struct spdk_accel_task *first_task)
194 {
195 	struct idxd_io_channel *chan = spdk_io_channel_get_ctx(ch);
196 	struct spdk_accel_task *task, *tmp;
197 	int rc = 0;
198 
199 	task = first_task;
200 
201 	if (chan->state == IDXD_CHANNEL_ERROR) {
202 		while (task) {
203 			tmp = TAILQ_NEXT(task, link);
204 			spdk_accel_task_complete(task, -EINVAL);
205 			task = tmp;
206 		}
207 		return 0;
208 	}
209 
210 	if (!TAILQ_EMPTY(&chan->queued_tasks)) {
211 		goto queue_tasks;
212 	}
213 
214 	/* The caller will either submit a single task or a group of tasks that are
215 	 * linked together but they cannot be on a list. For example, see idxd_poll()
216 	 * where a list of queued tasks is being resubmitted, the list they are on
217 	 * is initialized after saving off the first task from the list which is then
218 	 * passed in here.  Similar thing is done in the accel framework.
219 	 */
220 	while (task) {
221 		tmp = TAILQ_NEXT(task, link);
222 		rc = _process_single_task(ch, task);
223 
224 		if (rc == -EBUSY) {
225 			goto queue_tasks;
226 		} else if (rc) {
227 			spdk_accel_task_complete(task, rc);
228 		}
229 		task = tmp;
230 	}
231 
232 	return 0;
233 
234 queue_tasks:
235 	while (task != NULL) {
236 		tmp = TAILQ_NEXT(task, link);
237 		TAILQ_INSERT_TAIL(&chan->queued_tasks, task, link);
238 		task = tmp;
239 	}
240 	return 0;
241 }
242 
243 static int
244 idxd_poll(void *arg)
245 {
246 	struct idxd_io_channel *chan = arg;
247 	struct spdk_accel_task *task = NULL;
248 	struct idxd_task *idxd_task;
249 	int count;
250 
251 	count = spdk_idxd_process_events(chan->chan);
252 
253 	/* Check if there are any pending ops to process if the channel is active */
254 	if (chan->state == IDXD_CHANNEL_ACTIVE) {
255 		/* Submit queued tasks */
256 		if (!TAILQ_EMPTY(&chan->queued_tasks)) {
257 			task = TAILQ_FIRST(&chan->queued_tasks);
258 			idxd_task = SPDK_CONTAINEROF(task, struct idxd_task, task);
259 
260 			TAILQ_INIT(&chan->queued_tasks);
261 
262 			dsa_submit_tasks(spdk_io_channel_from_ctx(idxd_task->chan), task);
263 		}
264 	}
265 
266 	return count > 0 ? SPDK_POLLER_BUSY : SPDK_POLLER_IDLE;
267 }
268 
269 static size_t
270 accel_dsa_get_ctx_size(void)
271 {
272 	return sizeof(struct idxd_task);
273 }
274 
275 static bool
276 dsa_supports_opcode(enum accel_opcode opc)
277 {
278 	if (!g_dsa_initialized) {
279 		return false;
280 	}
281 
282 	switch (opc) {
283 	case ACCEL_OPC_COPY:
284 	case ACCEL_OPC_FILL:
285 	case ACCEL_OPC_DUALCAST:
286 	case ACCEL_OPC_COMPARE:
287 	case ACCEL_OPC_CRC32C:
288 	case ACCEL_OPC_COPY_CRC32C:
289 		return true;
290 	default:
291 		return false;
292 	}
293 }
294 
295 static int accel_dsa_init(void);
296 static void accel_dsa_exit(void *ctx);
297 static void accel_dsa_write_config_json(struct spdk_json_write_ctx *w);
298 
299 static struct spdk_accel_module_if g_dsa_module = {
300 	.module_init = accel_dsa_init,
301 	.module_fini = accel_dsa_exit,
302 	.write_config_json = accel_dsa_write_config_json,
303 	.get_ctx_size = accel_dsa_get_ctx_size,
304 	.name			= "dsa",
305 	.supports_opcode	= dsa_supports_opcode,
306 	.get_io_channel		= dsa_get_io_channel,
307 	.submit_tasks		= dsa_submit_tasks
308 };
309 
310 SPDK_ACCEL_MODULE_REGISTER(dsa, &g_dsa_module)
311 
312 static int
313 dsa_create_cb(void *io_device, void *ctx_buf)
314 {
315 	struct idxd_io_channel *chan = ctx_buf;
316 	struct idxd_device *dsa;
317 
318 	dsa = idxd_select_device(chan);
319 	if (dsa == NULL) {
320 		SPDK_ERRLOG("Failed to get an idxd channel\n");
321 		return -EINVAL;
322 	}
323 
324 	chan->dev = dsa;
325 	chan->poller = SPDK_POLLER_REGISTER(idxd_poll, chan, 0);
326 	TAILQ_INIT(&chan->queued_tasks);
327 	chan->num_outstanding = 0;
328 	chan->state = IDXD_CHANNEL_ACTIVE;
329 
330 	return 0;
331 }
332 
333 static void
334 dsa_destroy_cb(void *io_device, void *ctx_buf)
335 {
336 	struct idxd_io_channel *chan = ctx_buf;
337 
338 	spdk_poller_unregister(&chan->poller);
339 	spdk_idxd_put_channel(chan->chan);
340 }
341 
342 static struct spdk_io_channel *
343 dsa_get_io_channel(void)
344 {
345 	return spdk_get_io_channel(&g_dsa_module);
346 }
347 
348 static void
349 attach_cb(void *cb_ctx, struct spdk_idxd_device *idxd)
350 {
351 	struct idxd_device *dev;
352 
353 	dev = calloc(1, sizeof(*dev));
354 	if (dev == NULL) {
355 		SPDK_ERRLOG("Failed to allocate device struct\n");
356 		return;
357 	}
358 
359 	dev->dsa = idxd;
360 	if (g_next_dev == NULL) {
361 		g_next_dev = dev;
362 	}
363 
364 	TAILQ_INSERT_TAIL(&g_dsa_devices, dev, tailq);
365 	g_num_devices++;
366 }
367 
368 void
369 accel_dsa_enable_probe(bool kernel_mode)
370 {
371 	g_kernel_mode = kernel_mode;
372 	g_dsa_enable = true;
373 	spdk_idxd_set_config(g_kernel_mode);
374 }
375 
376 static bool
377 probe_cb(void *cb_ctx, struct spdk_pci_device *dev)
378 {
379 	if (dev->id.device_id == PCI_DEVICE_ID_INTEL_DSA) {
380 		return true;
381 	}
382 
383 	return false;
384 }
385 
386 static int
387 accel_dsa_init(void)
388 {
389 	if (!g_dsa_enable) {
390 		return -EINVAL;
391 	}
392 
393 	if (spdk_idxd_probe(NULL, attach_cb, probe_cb) != 0) {
394 		SPDK_ERRLOG("spdk_idxd_probe() failed\n");
395 		return -EINVAL;
396 	}
397 
398 	if (TAILQ_EMPTY(&g_dsa_devices)) {
399 		SPDK_NOTICELOG("no available dsa devices\n");
400 		return -EINVAL;
401 	}
402 
403 	g_dsa_initialized = true;
404 	SPDK_NOTICELOG("Accel framework DSA module initialized.\n");
405 	spdk_io_device_register(&g_dsa_module, dsa_create_cb, dsa_destroy_cb,
406 				sizeof(struct idxd_io_channel), "dsa_accel_module");
407 	return 0;
408 }
409 
410 static void
411 accel_dsa_exit(void *ctx)
412 {
413 	struct idxd_device *dev;
414 
415 	if (g_dsa_initialized) {
416 		spdk_io_device_unregister(&g_dsa_module, NULL);
417 		g_dsa_initialized = false;
418 	}
419 
420 	while (!TAILQ_EMPTY(&g_dsa_devices)) {
421 		dev = TAILQ_FIRST(&g_dsa_devices);
422 		TAILQ_REMOVE(&g_dsa_devices, dev, tailq);
423 		spdk_idxd_detach(dev->dsa);
424 		free(dev);
425 	}
426 
427 	spdk_accel_module_finish();
428 }
429 
430 static void
431 accel_dsa_write_config_json(struct spdk_json_write_ctx *w)
432 {
433 	if (g_dsa_enable) {
434 		spdk_json_write_object_begin(w);
435 		spdk_json_write_named_string(w, "method", "dsa_scan_accel_module");
436 		spdk_json_write_named_object_begin(w, "params");
437 		spdk_json_write_named_bool(w, "config_kernel_mode", g_kernel_mode);
438 		spdk_json_write_object_end(w);
439 		spdk_json_write_object_end(w);
440 	}
441 }
442 
443 SPDK_TRACE_REGISTER_FN(dsa_trace, "dsa", TRACE_GROUP_ACCEL_DSA)
444 {
445 	spdk_trace_register_description("DSA_OP_SUBMIT", TRACE_ACCEL_DSA_OP_SUBMIT, OWNER_NONE, OBJECT_NONE,
446 					0,
447 					SPDK_TRACE_ARG_TYPE_INT, "count");
448 	spdk_trace_register_description("DSA_OP_COMPLETE", TRACE_ACCEL_DSA_OP_COMPLETE, OWNER_NONE,
449 					OBJECT_NONE,
450 					0, SPDK_TRACE_ARG_TYPE_INT, "count");
451 }
452 
453 SPDK_LOG_REGISTER_COMPONENT(accel_dsa)
454