Line data Source code
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 0 : idxd_select_device(struct idxd_io_channel *chan)
61 : {
62 0 : uint32_t count = 0;
63 : struct idxd_device *dev;
64 0 : 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 0 : pthread_mutex_lock(&g_dev_lock);
74 0 : g_next_dev = TAILQ_NEXT(g_next_dev, tailq);
75 0 : if (g_next_dev == NULL) {
76 0 : g_next_dev = TAILQ_FIRST(&g_iaa_devices);
77 : }
78 0 : dev = g_next_dev;
79 0 : pthread_mutex_unlock(&g_dev_lock);
80 :
81 0 : if (numa_id != spdk_idxd_get_socket(dev->iaa)) {
82 0 : 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 0 : chan->chan = spdk_idxd_get_channel(dev->iaa);
91 0 : if (chan->chan != NULL) {
92 0 : SPDK_DEBUGLOG(accel_iaa, "On socket %d using device on numa %d\n",
93 : numa_id, spdk_idxd_get_socket(dev->iaa));
94 0 : return dev;
95 : }
96 0 : } 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 0 : SPDK_ERRLOG("No more IAA devices available on the local socket.\n");
104 0 : return NULL;
105 : }
106 :
107 : static void
108 0 : iaa_done(void *cb_arg, int status)
109 : {
110 0 : struct idxd_task *idxd_task = cb_arg;
111 : struct idxd_io_channel *chan;
112 :
113 0 : chan = idxd_task->chan;
114 :
115 0 : assert(chan->num_outstanding > 0);
116 0 : spdk_trace_record(TRACE_ACCEL_IAA_OP_COMPLETE, 0, 0, 0, chan->num_outstanding - 1);
117 0 : chan->num_outstanding--;
118 :
119 0 : spdk_accel_task_complete(&idxd_task->task, status);
120 0 : }
121 :
122 : static int
123 0 : _process_single_task(struct spdk_io_channel *ch, struct spdk_accel_task *task)
124 : {
125 0 : struct idxd_io_channel *chan = spdk_io_channel_get_ctx(ch);
126 : struct idxd_task *idxd_task;
127 0 : int rc = 0;
128 0 : int flags = 0;
129 :
130 0 : idxd_task = SPDK_CONTAINEROF(task, struct idxd_task, task);
131 0 : idxd_task->chan = chan;
132 :
133 : /* TODO: iovec support */
134 0 : if (task->d.iovcnt > 1 || task->s.iovcnt > 1) {
135 0 : SPDK_ERRLOG("fatal: IAA does not support > 1 iovec\n");
136 0 : assert(0);
137 : }
138 :
139 0 : switch (task->op_code) {
140 0 : case SPDK_ACCEL_OPC_COMPRESS:
141 0 : 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 0 : break;
145 0 : case SPDK_ACCEL_OPC_DECOMPRESS:
146 0 : 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 0 : break;
149 0 : default:
150 0 : assert(false);
151 : rc = -EINVAL;
152 : break;
153 : }
154 :
155 0 : if (rc == 0) {
156 0 : chan->num_outstanding++;
157 0 : spdk_trace_record(TRACE_ACCEL_IAA_OP_SUBMIT, 0, 0, 0, chan->num_outstanding);
158 : }
159 :
160 0 : return rc;
161 : }
162 :
163 : static int
164 0 : iaa_submit_tasks(struct spdk_io_channel *ch, struct spdk_accel_task *first_task)
165 : {
166 0 : struct idxd_io_channel *chan = spdk_io_channel_get_ctx(ch);
167 : struct spdk_accel_task *task, *tmp;
168 0 : int rc = 0;
169 :
170 0 : task = first_task;
171 :
172 0 : if (chan->state == IDXD_CHANNEL_ERROR) {
173 0 : while (task) {
174 0 : tmp = STAILQ_NEXT(task, link);
175 0 : spdk_accel_task_complete(task, -EINVAL);
176 0 : task = tmp;
177 : }
178 0 : return 0;
179 : }
180 :
181 0 : if (!STAILQ_EMPTY(&chan->queued_tasks)) {
182 0 : 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 0 : while (task) {
192 0 : tmp = STAILQ_NEXT(task, link);
193 0 : rc = _process_single_task(ch, task);
194 :
195 0 : if (rc == -EBUSY) {
196 0 : goto queue_tasks;
197 0 : } else if (rc) {
198 0 : spdk_accel_task_complete(task, rc);
199 : }
200 0 : task = tmp;
201 : }
202 :
203 0 : return 0;
204 :
205 0 : queue_tasks:
206 0 : while (task != NULL) {
207 0 : tmp = STAILQ_NEXT(task, link);
208 0 : STAILQ_INSERT_TAIL(&chan->queued_tasks, task, link);
209 0 : task = tmp;
210 : }
211 0 : return 0;
212 : }
213 :
214 : static int
215 0 : idxd_poll(void *arg)
216 : {
217 0 : struct idxd_io_channel *chan = arg;
218 0 : struct spdk_accel_task *task = NULL;
219 : struct idxd_task *idxd_task;
220 : int count;
221 :
222 0 : count = spdk_idxd_process_events(chan->chan);
223 :
224 : /* Check if there are any pending ops to process if the channel is active */
225 0 : if (chan->state == IDXD_CHANNEL_ACTIVE) {
226 : /* Submit queued tasks */
227 0 : if (!STAILQ_EMPTY(&chan->queued_tasks)) {
228 0 : task = STAILQ_FIRST(&chan->queued_tasks);
229 0 : idxd_task = SPDK_CONTAINEROF(task, struct idxd_task, task);
230 :
231 0 : STAILQ_INIT(&chan->queued_tasks);
232 :
233 0 : iaa_submit_tasks(spdk_io_channel_from_ctx(idxd_task->chan), task);
234 : }
235 : }
236 :
237 0 : return count > 0 ? SPDK_POLLER_BUSY : SPDK_POLLER_IDLE;
238 : }
239 :
240 : static size_t
241 0 : accel_iaa_get_ctx_size(void)
242 : {
243 0 : return sizeof(struct idxd_task);
244 : }
245 :
246 : static bool
247 0 : iaa_supports_opcode(enum spdk_accel_opcode opc)
248 : {
249 0 : if (!g_iaa_initialized) {
250 0 : return false;
251 : }
252 :
253 0 : switch (opc) {
254 0 : case SPDK_ACCEL_OPC_COMPRESS:
255 : case SPDK_ACCEL_OPC_DECOMPRESS:
256 0 : return true;
257 0 : default:
258 0 : return false;
259 : }
260 : }
261 :
262 : static bool
263 0 : iaa_compress_supports_algo(enum spdk_accel_comp_algo algo)
264 : {
265 0 : if (algo == SPDK_ACCEL_COMP_ALGO_DEFLATE) {
266 0 : return true;
267 : }
268 :
269 0 : return false;
270 : }
271 :
272 : static int
273 0 : iaa_get_compress_level_range(enum spdk_accel_comp_algo algo,
274 : uint32_t *min_level, uint32_t *max_level)
275 : {
276 0 : switch (algo) {
277 0 : case SPDK_ACCEL_COMP_ALGO_DEFLATE:
278 0 : *min_level = 0;
279 0 : *max_level = 0;
280 0 : return 0;
281 0 : default:
282 0 : return -EINVAL;
283 : }
284 : }
285 :
286 : static int accel_iaa_init(void);
287 : static void accel_iaa_exit(void *ctx);
288 : static void accel_iaa_write_config_json(struct spdk_json_write_ctx *w);
289 :
290 : static struct spdk_accel_module_if g_iaa_module = {
291 : .module_init = accel_iaa_init,
292 : .module_fini = accel_iaa_exit,
293 : .write_config_json = accel_iaa_write_config_json,
294 : .get_ctx_size = accel_iaa_get_ctx_size,
295 : .name = "iaa",
296 : .supports_opcode = iaa_supports_opcode,
297 : .get_io_channel = iaa_get_io_channel,
298 : .submit_tasks = iaa_submit_tasks,
299 : .compress_supports_algo = iaa_compress_supports_algo,
300 : .get_compress_level_range = iaa_get_compress_level_range,
301 : };
302 :
303 : static int
304 0 : idxd_create_cb(void *io_device, void *ctx_buf)
305 : {
306 0 : struct idxd_io_channel *chan = ctx_buf;
307 : struct idxd_device *iaa;
308 :
309 0 : iaa = idxd_select_device(chan);
310 0 : if (iaa == NULL) {
311 0 : SPDK_ERRLOG("Failed to get an idxd channel\n");
312 0 : return -EINVAL;
313 : }
314 :
315 0 : chan->dev = iaa;
316 0 : chan->poller = SPDK_POLLER_REGISTER(idxd_poll, chan, 0);
317 0 : STAILQ_INIT(&chan->queued_tasks);
318 0 : chan->num_outstanding = 0;
319 0 : chan->state = IDXD_CHANNEL_ACTIVE;
320 :
321 0 : return 0;
322 : }
323 :
324 : static void
325 0 : idxd_destroy_cb(void *io_device, void *ctx_buf)
326 : {
327 0 : struct idxd_io_channel *chan = ctx_buf;
328 :
329 0 : spdk_poller_unregister(&chan->poller);
330 0 : spdk_idxd_put_channel(chan->chan);
331 0 : }
332 :
333 : static struct spdk_io_channel *
334 0 : iaa_get_io_channel(void)
335 : {
336 0 : return spdk_get_io_channel(&g_iaa_module);
337 : }
338 :
339 : static void
340 0 : attach_cb(void *cb_ctx, struct spdk_idxd_device *iaa)
341 : {
342 : struct idxd_device *dev;
343 :
344 0 : dev = calloc(1, sizeof(*dev));
345 0 : if (dev == NULL) {
346 0 : SPDK_ERRLOG("Failed to allocate device struct\n");
347 0 : return;
348 : }
349 :
350 0 : dev->iaa = iaa;
351 0 : if (g_next_dev == NULL) {
352 0 : g_next_dev = dev;
353 : }
354 :
355 0 : TAILQ_INSERT_TAIL(&g_iaa_devices, dev, tailq);
356 0 : g_num_devices++;
357 : }
358 :
359 : int
360 0 : accel_iaa_enable_probe(void)
361 : {
362 : int rc;
363 :
364 0 : if (g_iaa_enable) {
365 0 : return -EALREADY;
366 : }
367 :
368 : /* TODO initially only support user mode w/IAA */
369 0 : rc = spdk_idxd_set_config(false);
370 0 : if (rc != 0) {
371 0 : return rc;
372 : }
373 :
374 0 : spdk_accel_module_list_add(&g_iaa_module);
375 0 : g_iaa_enable = true;
376 :
377 0 : return 0;
378 : }
379 :
380 : static bool
381 0 : caller_probe_cb(void *cb_ctx, struct spdk_pci_device *dev)
382 : {
383 0 : if (dev->id.device_id == PCI_DEVICE_ID_INTEL_IAA) {
384 0 : return true;
385 : }
386 :
387 0 : return false;
388 : }
389 :
390 : static int
391 0 : accel_iaa_init(void)
392 : {
393 0 : if (!g_iaa_enable) {
394 0 : assert(0);
395 : return -EINVAL;
396 : }
397 :
398 0 : if (spdk_idxd_probe(NULL, attach_cb, caller_probe_cb) != 0) {
399 0 : SPDK_ERRLOG("spdk_idxd_probe() failed\n");
400 0 : return -EINVAL;
401 : }
402 :
403 0 : if (TAILQ_EMPTY(&g_iaa_devices)) {
404 0 : return -ENODEV;
405 : }
406 :
407 0 : g_iaa_initialized = true;
408 0 : spdk_io_device_register(&g_iaa_module, idxd_create_cb, idxd_destroy_cb,
409 : sizeof(struct idxd_io_channel), "iaa_accel_module");
410 0 : return 0;
411 : }
412 :
413 : static void
414 0 : accel_iaa_exit(void *ctx)
415 : {
416 : struct idxd_device *dev;
417 :
418 0 : if (g_iaa_initialized) {
419 0 : spdk_io_device_unregister(&g_iaa_module, NULL);
420 0 : g_iaa_initialized = false;
421 : }
422 :
423 0 : while (!TAILQ_EMPTY(&g_iaa_devices)) {
424 0 : dev = TAILQ_FIRST(&g_iaa_devices);
425 0 : TAILQ_REMOVE(&g_iaa_devices, dev, tailq);
426 0 : spdk_idxd_detach(dev->iaa);
427 0 : free(dev);
428 : }
429 :
430 0 : spdk_accel_module_finish();
431 0 : }
432 :
433 : static void
434 0 : accel_iaa_write_config_json(struct spdk_json_write_ctx *w)
435 : {
436 0 : if (g_iaa_enable) {
437 0 : spdk_json_write_object_begin(w);
438 0 : spdk_json_write_named_string(w, "method", "iaa_scan_accel_module");
439 0 : spdk_json_write_object_end(w);
440 : }
441 0 : }
442 :
443 : static void
444 0 : iaa_trace(void)
445 : {
446 0 : spdk_trace_register_description("IAA_OP_SUBMIT", TRACE_ACCEL_IAA_OP_SUBMIT, OWNER_TYPE_NONE,
447 : OBJECT_NONE, 0, SPDK_TRACE_ARG_TYPE_INT, "count");
448 0 : spdk_trace_register_description("IAA_OP_COMPLETE", TRACE_ACCEL_IAA_OP_COMPLETE, OWNER_TYPE_NONE,
449 : OBJECT_NONE, 0, SPDK_TRACE_ARG_TYPE_INT, "count");
450 0 : }
451 0 : SPDK_TRACE_REGISTER_FN(iaa_trace, "iaa", TRACE_GROUP_ACCEL_IAA)
452 :
453 0 : SPDK_LOG_REGISTER_COMPONENT(accel_iaa)
|