xref: /spdk/module/accel/iaa/accel_iaa.c (revision 30afc27748e69257ca50f7e3a4b4ca6466ffc26b)
1 /*   SPDX-License-Identifier: BSD-3-Clause
2  *   Copyright (C) 2022 Intel Corporation.
3  *   Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES.
4  *   All rights reserved.
5  */
6 
7 #include "accel_iaa.h"
8 
9 #include "spdk/stdinc.h"
10 
11 #include "spdk/accel_module.h"
12 #include "spdk/log.h"
13 #include "spdk_internal/idxd.h"
14 
15 #include "spdk/env.h"
16 #include "spdk/event.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_iaa_enable = false;
25 
26 enum channel_state {
27 	IDXD_CHANNEL_ACTIVE,
28 	IDXD_CHANNEL_ERROR,
29 };
30 
31 static bool g_iaa_initialized = false;
32 
33 struct idxd_device {
34 	struct spdk_idxd_device		*iaa;
35 	TAILQ_ENTRY(idxd_device)	tailq;
36 };
37 
38 static TAILQ_HEAD(, idxd_device) g_iaa_devices = TAILQ_HEAD_INITIALIZER(g_iaa_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 	STAILQ_HEAD(, spdk_accel_task)	queued_tasks;
55 };
56 
57 static struct spdk_io_channel *iaa_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 numa_id = spdk_env_get_numa_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_iaa_devices);
77 		}
78 		dev = g_next_dev;
79 		pthread_mutex_unlock(&g_dev_lock);
80 
81 		if (numa_id != spdk_idxd_get_socket(dev->iaa)) {
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->iaa);
91 		if (chan->chan != NULL) {
92 			SPDK_DEBUGLOG(accel_iaa, "On socket %d using device on numa %d\n",
93 				      numa_id, spdk_idxd_get_socket(dev->iaa));
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 IAA devices available on the local socket.\n");
104 	return NULL;
105 }
106 
107 static void
108 iaa_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_IAA_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 _process_single_task(struct spdk_io_channel *ch, struct spdk_accel_task *task)
124 {
125 	struct idxd_io_channel *chan = spdk_io_channel_get_ctx(ch);
126 	struct idxd_task *idxd_task;
127 	int rc = 0;
128 	int flags = 0;
129 
130 	idxd_task = SPDK_CONTAINEROF(task, struct idxd_task, task);
131 	idxd_task->chan = chan;
132 
133 	/* TODO: iovec support */
134 	if (task->d.iovcnt > 1 || task->s.iovcnt > 1) {
135 		SPDK_ERRLOG("fatal: IAA does not support > 1 iovec\n");
136 		assert(0);
137 	}
138 
139 	switch (task->op_code) {
140 	case SPDK_ACCEL_OPC_COMPRESS:
141 		rc = spdk_idxd_submit_compress(chan->chan, task->d.iovs[0].iov_base, task->d.iovs[0].iov_len,
142 					       task->s.iovs, task->s.iovcnt, task->output_size, flags,
143 					       iaa_done, idxd_task);
144 		break;
145 	case SPDK_ACCEL_OPC_DECOMPRESS:
146 		rc = spdk_idxd_submit_decompress(chan->chan, task->d.iovs, task->d.iovcnt, task->s.iovs,
147 						 task->s.iovcnt, flags, iaa_done, idxd_task);
148 		break;
149 	default:
150 		assert(false);
151 		rc = -EINVAL;
152 		break;
153 	}
154 
155 	if (rc == 0) {
156 		chan->num_outstanding++;
157 		spdk_trace_record(TRACE_ACCEL_IAA_OP_SUBMIT, 0, 0, 0, chan->num_outstanding);
158 	}
159 
160 	return rc;
161 }
162 
163 static int
164 iaa_submit_tasks(struct spdk_io_channel *ch, struct spdk_accel_task *first_task)
165 {
166 	struct idxd_io_channel *chan = spdk_io_channel_get_ctx(ch);
167 	struct spdk_accel_task *task, *tmp;
168 	int rc = 0;
169 
170 	task = first_task;
171 
172 	if (chan->state == IDXD_CHANNEL_ERROR) {
173 		while (task) {
174 			tmp = STAILQ_NEXT(task, link);
175 			spdk_accel_task_complete(task, -EINVAL);
176 			task = tmp;
177 		}
178 		return 0;
179 	}
180 
181 	if (!STAILQ_EMPTY(&chan->queued_tasks)) {
182 		goto queue_tasks;
183 	}
184 
185 	/* The caller will either submit a single task or a group of tasks that are
186 	 * linked together but they cannot be on a list. For example, see idxd_poll()
187 	 * where a list of queued tasks is being resubmitted, the list they are on
188 	 * is initialized after saving off the first task from the list which is then
189 	 * passed in here.  Similar thing is done in the accel framework.
190 	 */
191 	while (task) {
192 		tmp = STAILQ_NEXT(task, link);
193 		rc = _process_single_task(ch, task);
194 
195 		if (rc == -EBUSY) {
196 			goto queue_tasks;
197 		} else if (rc) {
198 			spdk_accel_task_complete(task, rc);
199 		}
200 		task = tmp;
201 	}
202 
203 	return 0;
204 
205 queue_tasks:
206 	while (task != NULL) {
207 		tmp = STAILQ_NEXT(task, link);
208 		STAILQ_INSERT_TAIL(&chan->queued_tasks, task, link);
209 		task = tmp;
210 	}
211 	return 0;
212 }
213 
214 static int
215 idxd_poll(void *arg)
216 {
217 	struct idxd_io_channel *chan = arg;
218 	struct spdk_accel_task *task = NULL;
219 	struct idxd_task *idxd_task;
220 	int count;
221 
222 	count = spdk_idxd_process_events(chan->chan);
223 
224 	/* Check if there are any pending ops to process if the channel is active */
225 	if (chan->state == IDXD_CHANNEL_ACTIVE) {
226 		/* Submit queued tasks */
227 		if (!STAILQ_EMPTY(&chan->queued_tasks)) {
228 			task = STAILQ_FIRST(&chan->queued_tasks);
229 			idxd_task = SPDK_CONTAINEROF(task, struct idxd_task, task);
230 
231 			STAILQ_INIT(&chan->queued_tasks);
232 
233 			iaa_submit_tasks(spdk_io_channel_from_ctx(idxd_task->chan), task);
234 		}
235 	}
236 
237 	return count > 0 ? SPDK_POLLER_BUSY : SPDK_POLLER_IDLE;
238 }
239 
240 static size_t
241 accel_iaa_get_ctx_size(void)
242 {
243 	return sizeof(struct idxd_task);
244 }
245 
246 static bool
247 iaa_supports_opcode(enum spdk_accel_opcode opc)
248 {
249 	if (!g_iaa_initialized) {
250 		return false;
251 	}
252 
253 	switch (opc) {
254 	case SPDK_ACCEL_OPC_COMPRESS:
255 	case SPDK_ACCEL_OPC_DECOMPRESS:
256 		return true;
257 	default:
258 		return false;
259 	}
260 }
261 
262 static int accel_iaa_init(void);
263 static void accel_iaa_exit(void *ctx);
264 static void accel_iaa_write_config_json(struct spdk_json_write_ctx *w);
265 
266 static struct spdk_accel_module_if g_iaa_module = {
267 	.module_init = accel_iaa_init,
268 	.module_fini = accel_iaa_exit,
269 	.write_config_json = accel_iaa_write_config_json,
270 	.get_ctx_size = accel_iaa_get_ctx_size,
271 	.name			= "iaa",
272 	.supports_opcode	= iaa_supports_opcode,
273 	.get_io_channel		= iaa_get_io_channel,
274 	.submit_tasks		= iaa_submit_tasks
275 };
276 
277 static int
278 idxd_create_cb(void *io_device, void *ctx_buf)
279 {
280 	struct idxd_io_channel *chan = ctx_buf;
281 	struct idxd_device *iaa;
282 
283 	iaa = idxd_select_device(chan);
284 	if (iaa == NULL) {
285 		SPDK_ERRLOG("Failed to get an idxd channel\n");
286 		return -EINVAL;
287 	}
288 
289 	chan->dev = iaa;
290 	chan->poller = SPDK_POLLER_REGISTER(idxd_poll, chan, 0);
291 	STAILQ_INIT(&chan->queued_tasks);
292 	chan->num_outstanding = 0;
293 	chan->state = IDXD_CHANNEL_ACTIVE;
294 
295 	return 0;
296 }
297 
298 static void
299 idxd_destroy_cb(void *io_device, void *ctx_buf)
300 {
301 	struct idxd_io_channel *chan = ctx_buf;
302 
303 	spdk_poller_unregister(&chan->poller);
304 	spdk_idxd_put_channel(chan->chan);
305 }
306 
307 static struct spdk_io_channel *
308 iaa_get_io_channel(void)
309 {
310 	return spdk_get_io_channel(&g_iaa_module);
311 }
312 
313 static void
314 attach_cb(void *cb_ctx, struct spdk_idxd_device *iaa)
315 {
316 	struct idxd_device *dev;
317 
318 	dev = calloc(1, sizeof(*dev));
319 	if (dev == NULL) {
320 		SPDK_ERRLOG("Failed to allocate device struct\n");
321 		return;
322 	}
323 
324 	dev->iaa = iaa;
325 	if (g_next_dev == NULL) {
326 		g_next_dev = dev;
327 	}
328 
329 	TAILQ_INSERT_TAIL(&g_iaa_devices, dev, tailq);
330 	g_num_devices++;
331 }
332 
333 int
334 accel_iaa_enable_probe(void)
335 {
336 	int rc;
337 
338 	if (g_iaa_enable) {
339 		return -EALREADY;
340 	}
341 
342 	/* TODO initially only support user mode w/IAA */
343 	rc = spdk_idxd_set_config(false);
344 	if (rc != 0) {
345 		return rc;
346 	}
347 
348 	spdk_accel_module_list_add(&g_iaa_module);
349 	g_iaa_enable = true;
350 
351 	return 0;
352 }
353 
354 static bool
355 caller_probe_cb(void *cb_ctx, struct spdk_pci_device *dev)
356 {
357 	if (dev->id.device_id == PCI_DEVICE_ID_INTEL_IAA) {
358 		return true;
359 	}
360 
361 	return false;
362 }
363 
364 static int
365 accel_iaa_init(void)
366 {
367 	if (!g_iaa_enable) {
368 		assert(0);
369 		return -EINVAL;
370 	}
371 
372 	if (spdk_idxd_probe(NULL, attach_cb, caller_probe_cb) != 0) {
373 		SPDK_ERRLOG("spdk_idxd_probe() failed\n");
374 		return -EINVAL;
375 	}
376 
377 	if (TAILQ_EMPTY(&g_iaa_devices)) {
378 		return -ENODEV;
379 	}
380 
381 	g_iaa_initialized = true;
382 	spdk_io_device_register(&g_iaa_module, idxd_create_cb, idxd_destroy_cb,
383 				sizeof(struct idxd_io_channel), "iaa_accel_module");
384 	return 0;
385 }
386 
387 static void
388 accel_iaa_exit(void *ctx)
389 {
390 	struct idxd_device *dev;
391 
392 	if (g_iaa_initialized) {
393 		spdk_io_device_unregister(&g_iaa_module, NULL);
394 		g_iaa_initialized = false;
395 	}
396 
397 	while (!TAILQ_EMPTY(&g_iaa_devices)) {
398 		dev = TAILQ_FIRST(&g_iaa_devices);
399 		TAILQ_REMOVE(&g_iaa_devices, dev, tailq);
400 		spdk_idxd_detach(dev->iaa);
401 		free(dev);
402 	}
403 
404 	spdk_accel_module_finish();
405 }
406 
407 static void
408 accel_iaa_write_config_json(struct spdk_json_write_ctx *w)
409 {
410 	if (g_iaa_enable) {
411 		spdk_json_write_object_begin(w);
412 		spdk_json_write_named_string(w, "method", "iaa_scan_accel_module");
413 		spdk_json_write_object_end(w);
414 	}
415 }
416 
417 SPDK_TRACE_REGISTER_FN(iaa_trace, "iaa", TRACE_GROUP_ACCEL_IAA)
418 {
419 	spdk_trace_register_description("IAA_OP_SUBMIT", TRACE_ACCEL_IAA_OP_SUBMIT, OWNER_TYPE_NONE,
420 					OBJECT_NONE, 0, SPDK_TRACE_ARG_TYPE_INT, "count");
421 	spdk_trace_register_description("IAA_OP_COMPLETE", TRACE_ACCEL_IAA_OP_COMPLETE, OWNER_TYPE_NONE,
422 					OBJECT_NONE, 0, SPDK_TRACE_ARG_TYPE_INT, "count");
423 }
424 
425 SPDK_LOG_REGISTER_COMPONENT(accel_iaa)
426