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