| @@ -1,5 +1,6 @@ |
| 1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | 2 | /* Copyright(c) 2020 Intel Corporation. All rights reserved. */ |
| 3 | +#include <uapi/linux/cxl_mem.h> |
| 3 | 4 | #include <linux/module.h> |
| 4 | 5 | #include <linux/mutex.h> |
| 5 | 6 | #include <linux/cdev.h> |
| @@ -40,6 +41,7 @@ · 34 unmodified lines |
| 40 | 41 | #define CXL_MAILBOX_TIMEOUT_MS (2 * HZ) |
| 41 | 42 | |
| 42 | 43 | enum opcode { |
| 44 | + CXL_MBOX_OP_INVALID = 0x0000, |
| 43 | 45 | CXL_MBOX_OP_IDENTIFY = 0x4000, |
| 44 | 46 | CXL_MBOX_OP_MAX = 0x10000 |
| 45 | 47 | }; |
| @@ -95,6 +97,49 @@ struct cxl_memdev { · 49 unmodified lines |
| 95 | 97 | static int cxl_mem_major; |
| 96 | 98 | static DEFINE_IDA(cxl_memdev_ida); |
| 97 | 99 | |
| 100 | +/** |
| 101 | + * struct cxl_mem_command - Driver representation of a memory device command |
| 102 | + * @info: Command information as it exists for the UAPI |
| 103 | + * @opcode: The actual bits used for the mailbox protocol |
| 104 | + * |
| 105 | + * The cxl_mem_command is the driver's internal representation of commands that |
| 106 | + * are supported by the driver. Some of these commands may not be supported by |
| 107 | + * the hardware. The driver will use @info to validate the fields passed in by |
| 108 | + * the user then submit the @opcode to the hardware. |
| 109 | + * |
| 110 | + * See struct cxl_command_info. |
| 111 | + */ |
| 112 | +struct cxl_mem_command { |
| 113 | + struct cxl_command_info info; |
| 114 | + enum opcode opcode; |
| 115 | +}; |
| 116 | + |
| 117 | +#define CXL_CMD(_id, sin, sout) \ |
| 118 | + [CXL_MEM_COMMAND_ID_##_id] = { \ |
| 119 | + .info = { \ |
| 120 | + .id = CXL_MEM_COMMAND_ID_##_id, \ |
| 121 | + .size_in = sin, \ |
| 122 | + .size_out = sout, \ |
| 123 | + }, \ |
| 124 | + .opcode = CXL_MBOX_OP_##_id, \ |
| 125 | + } |
| 126 | + |
| 127 | +/* |
| 128 | + * This table defines the supported mailbox commands for the driver. This table |
| 129 | + * is made up of a UAPI structure. Non-negative values as parameters in the |
| 130 | + * table will be validated against the user's input. For example, if size_in is |
| 131 | + * 0, and the user passed in 1, it is an error. |
| 132 | + */ |
| 133 | +static struct cxl_mem_command mem_commands[] = { |
| 134 | + CXL_CMD(IDENTIFY, 0, 0x43), |
| 135 | +}; |
| 136 | + |
| 137 | +#define cxl_for_each_cmd(cmd) \ |
| 138 | + for ((cmd) = &mem_commands[0]; \ |
| 139 | + ((cmd) - mem_commands) < ARRAY_SIZE(mem_commands); (cmd)++) |
| 140 | + |
| 141 | +#define cxl_cmd_count ARRAY_SIZE(mem_commands) |
| 142 | + |
| 98 | 143 | static int cxl_mem_wait_for_doorbell(struct cxl_mem *cxlm) |
| 99 | 144 | { |
| 100 | 145 | const unsigned long start = jiffies; |
| @@ -324,6 +369,242 @@ static void cxl_mem_mbox_put(struct cxl_mem *cxlm) · 223 unmodified lines |
| 324 | 369 | mutex_unlock(&cxlm->mbox_mutex); |
| 325 | 370 | } |
| 326 | 371 | |
| 372 | +/** |
| 373 | + * handle_mailbox_cmd_from_user() - Dispatch a mailbox command for userspace. |
| 374 | + * @cxlm: The CXL memory device to communicate with. |
| 375 | + * @cmd: The validated command. |
| 376 | + * @in_payload: Pointer to userspace's input payload. |
| 377 | + * @out_payload: Pointer to userspace's output payload. |
| 378 | + * @size_out: (Input) Max payload size to copy out. |
| 379 | + * (Output) Payload size hardware generated. |
| 380 | + * @retval: Hardware generated return code from the operation. |
| 381 | + * |
| 382 | + * Return: |
| 383 | + * * %0 - Mailbox transaction succeeded. This implies the mailbox |
| 384 | + * protocol completed successfully not that the operation itself |
| 385 | + * was successful. |
| 386 | + * * %-ENOMEM - Couldn't allocate a bounce buffer. |
| 387 | + * * %-EFAULT - Something happened with copy_to/from_user. |
| 388 | + * * %-EINTR - Mailbox acquisition interrupted. |
| 389 | + * * %-EXXX - Transaction level failures. |
| 390 | + * |
| 391 | + * Creates the appropriate mailbox command and dispatches it on behalf of a |
| 392 | + * userspace request. The input and output payloads are copied between |
| 393 | + * userspace. |
| 394 | + * |
| 395 | + * See cxl_send_cmd(). |
| 396 | + */ |
| 397 | +static int handle_mailbox_cmd_from_user(struct cxl_mem *cxlm, |
| 398 | + const struct cxl_mem_command *cmd, |
| 399 | + u64 in_payload, u64 out_payload, |
| 400 | + s32 *size_out, u32 *retval) |
| 401 | +{ |
| 402 | + struct device *dev = &cxlm->pdev->dev; |
| 403 | + struct mbox_cmd mbox_cmd = { |
| 404 | + .opcode = cmd->opcode, |
| 405 | + .size_in = cmd->info.size_in, |
| 406 | + .size_out = cmd->info.size_out, |
| 407 | + }; |
| 408 | + int rc; |
| 409 | + |
| 410 | + if (cmd->info.size_out) { |
| 411 | + mbox_cmd.payload_out = kvzalloc(cmd->info.size_out, GFP_KERNEL); |
| 412 | + if (!mbox_cmd.payload_out) |
| 413 | + return -ENOMEM; |
| 414 | + } |
| 415 | + |
| 416 | + if (cmd->info.size_in) { |
| 417 | + mbox_cmd.payload_in = vmemdup_user(u64_to_user_ptr(in_payload), |
| 418 | + cmd->info.size_in); |
| 419 | + if (IS_ERR(mbox_cmd.payload_in)) |
| 420 | + return PTR_ERR(mbox_cmd.payload_in); |
| 421 | + } |
| 422 | + |
| 423 | + rc = cxl_mem_mbox_get(cxlm); |
| 424 | + if (rc) |
| 425 | + goto out; |
| 426 | + |
| 427 | + dev_dbg(dev, |
| 428 | + "Submitting %s command for user\n" |
| 429 | + "\topcode: %x\n" |
| 430 | + "\tsize: %ub\n", |
| 431 | + cxl_command_names[cmd->info.id].name, mbox_cmd.opcode, |
| 432 | + cmd->info.size_in); |
| 433 | + |
| 434 | + rc = __cxl_mem_mbox_send_cmd(cxlm, &mbox_cmd); |
| 435 | + cxl_mem_mbox_put(cxlm); |
| 436 | + if (rc) |
| 437 | + goto out; |
| 438 | + |
| 439 | + /* |
| 440 | + * @size_out contains the max size that's allowed to be written back out |
| 441 | + * to userspace. While the payload may have written more output than |
| 442 | + * this it will have to be ignored. |
| 443 | + */ |
| 444 | + if (mbox_cmd.size_out) { |
| 445 | + dev_WARN_ONCE(dev, mbox_cmd.size_out > *size_out, |
| 446 | + "Invalid return size\n"); |
| 447 | + if (copy_to_user(u64_to_user_ptr(out_payload), |
| 448 | + mbox_cmd.payload_out, mbox_cmd.size_out)) { |
| 449 | + rc = -EFAULT; |
| 450 | + goto out; |
| 451 | + } |
| 452 | + } |
| 453 | + |
| 454 | + *size_out = mbox_cmd.size_out; |
| 455 | + *retval = mbox_cmd.return_code; |
| 456 | + |
| 457 | +out: |
| 458 | + kvfree(mbox_cmd.payload_in); |
| 459 | + kvfree(mbox_cmd.payload_out); |
| 460 | + return rc; |
| 461 | +} |
| 462 | + |
| 463 | +/** |
| 464 | + * cxl_validate_cmd_from_user() - Check fields for CXL_MEM_SEND_COMMAND. |
| 465 | + * @cxlm: &struct cxl_mem device whose mailbox will be used. |
| 466 | + * @send_cmd: &struct cxl_send_command copied in from userspace. |
| 467 | + * @out_cmd: Sanitized and populated &struct cxl_mem_command. |
| 468 | + * |
| 469 | + * Return: |
| 470 | + * * %0 - @out_cmd is ready to send. |
| 471 | + * * %-ENOTTY - Invalid command specified. |
| 472 | + * * %-EINVAL - Reserved fields or invalid values were used. |
| 473 | + * * %-ENOMEM - Input or output buffer wasn't sized properly. |
| 474 | + * |
| 475 | + * The result of this command is a fully validated command in @out_cmd that is |
| 476 | + * safe to send to the hardware. |
| 477 | + * |
| 478 | + * See handle_mailbox_cmd_from_user() |
| 479 | + */ |
| 480 | +static int cxl_validate_cmd_from_user(struct cxl_mem *cxlm, |
| 481 | + const struct cxl_send_command *send_cmd, |
| 482 | + struct cxl_mem_command *out_cmd) |
| 483 | +{ |
| 484 | + const struct cxl_command_info *info; |
| 485 | + struct cxl_mem_command *c; |
| 486 | + |
| 487 | + if (send_cmd->id == 0 || send_cmd->id >= CXL_MEM_COMMAND_ID_MAX) |
| 488 | + return -ENOTTY; |
| 489 | + |
| 490 | + /* |
| 491 | + * The user can never specify an input payload larger than what hardware |
| 492 | + * supports, but output can be arbitrarily large (simply write out as |
| 493 | + * much data as the hardware provides). |
| 494 | + */ |
| 495 | + if (send_cmd->in.size > cxlm->payload_size) |
| 496 | + return -EINVAL; |
| 497 | + |
| 498 | + if (send_cmd->flags & ~CXL_MEM_COMMAND_FLAG_MASK) |
| 499 | + return -EINVAL; |
| 500 | + |
| 501 | + if (send_cmd->rsvd) |
| 502 | + return -EINVAL; |
| 503 | + |
| 504 | + if (send_cmd->in.rsvd || send_cmd->out.rsvd) |
| 505 | + return -EINVAL; |
| 506 | + |
| 507 | + /* Convert user's command into the internal representation */ |
| 508 | + c = &mem_commands[send_cmd->id]; |
| 509 | + info = &c->info; |
| 510 | + |
| 511 | + /* Check the input buffer is the expected size */ |
| 512 | + if (info->size_in >= 0 && info->size_in != send_cmd->in.size) |
| 513 | + return -ENOMEM; |
| 514 | + |
| 515 | + /* Check the output buffer is at least large enough */ |
| 516 | + if (info->size_out >= 0 && send_cmd->out.size < info->size_out) |
| 517 | + return -ENOMEM; |
| 518 | + |
| 519 | + memcpy(out_cmd, c, sizeof(*c)); |
| 520 | + out_cmd->info.size_in = send_cmd->in.size; |
| 521 | + /* |
| 522 | + * XXX: out_cmd->info.size_out will be controlled by the driver, and the |
| 523 | + * specified number of bytes @send_cmd->out.size will be copied back out |
| 524 | + * to userspace. |
| 525 | + */ |
| 526 | + |
| 527 | + return 0; |
| 528 | +} |
| 529 | + |
| 530 | +static int cxl_query_cmd(struct cxl_memdev *cxlmd, |
| 531 | + struct cxl_mem_query_commands __user *q) |
| 532 | +{ |
| 533 | + struct device *dev = &cxlmd->dev; |
| 534 | + struct cxl_mem_command *cmd; |
| 535 | + u32 n_commands; |
| 536 | + int j = 0; |
| 537 | + |
| 538 | + dev_dbg(dev, "Query IOCTL\n"); |
| 539 | + |
| 540 | + if (get_user(n_commands, &q->n_commands)) |
| 541 | + return -EFAULT; |
| 542 | + |
| 543 | + /* returns the total number if 0 elements are requested. */ |
| 544 | + if (n_commands == 0) |
| 545 | + return put_user(cxl_cmd_count, &q->n_commands); |
| 546 | + |
| 547 | + /* |
| 548 | + * otherwise, return max(n_commands, total commands) cxl_command_info |
| 549 | + * structures. |
| 550 | + */ |
| 551 | + cxl_for_each_cmd(cmd) { |
| 552 | + const struct cxl_command_info *info = &cmd->info; |
| 553 | + |
| 554 | + if (copy_to_user(&q->commands[j++], info, sizeof(*info))) |
| 555 | + return -EFAULT; |
| 556 | + |
| 557 | + if (j == n_commands) |
| 558 | + break; |
| 559 | + } |
| 560 | + |
| 561 | + return 0; |
| 562 | +} |
| 563 | + |
| 564 | +static int cxl_send_cmd(struct cxl_memdev *cxlmd, |
| 565 | + struct cxl_send_command __user *s) |
| 566 | +{ |
| 567 | + struct cxl_mem *cxlm = cxlmd->cxlm; |
| 568 | + struct device *dev = &cxlmd->dev; |
| 569 | + struct cxl_send_command send; |
| 570 | + struct cxl_mem_command c; |
| 571 | + int rc; |
| 572 | + |
| 573 | + dev_dbg(dev, "Send IOCTL\n"); |
| 574 | + |
| 575 | + if (copy_from_user(&send, s, sizeof(send))) |
| 576 | + return -EFAULT; |
| 577 | + |
| 578 | + rc = cxl_validate_cmd_from_user(cxlmd->cxlm, &send, &c); |
| 579 | + if (rc) |
| 580 | + return rc; |
| 581 | + |
| 582 | + /* Prepare to handle a full payload for variable sized output */ |
| 583 | + if (c.info.size_out < 0) |
| 584 | + c.info.size_out = cxlm->payload_size; |
| 585 | + |
| 586 | + rc = handle_mailbox_cmd_from_user(cxlm, &c, send.in.payload, |
| 587 | + send.out.payload, &send.out.size, |
| 588 | + &send.retval); |
| 589 | + if (rc) |
| 590 | + return rc; |
| 591 | + |
| 592 | + return copy_to_user(s, &send, sizeof(send)); |
| 593 | +} |
| 594 | + |
| 595 | +static long __cxl_memdev_ioctl(struct cxl_memdev *cxlmd, unsigned int cmd, |
| 596 | + unsigned long arg) |
| 597 | +{ |
| 598 | + switch (cmd) { |
| 599 | + case CXL_MEM_QUERY_COMMANDS: |
| 600 | + return cxl_query_cmd(cxlmd, (void __user *)arg); |
| 601 | + case CXL_MEM_SEND_COMMAND: |
| 602 | + return cxl_send_cmd(cxlmd, (void __user *)arg); |
| 603 | + default: |
| 604 | + return -ENOTTY; |
| 605 | + } |
| 606 | +} |
| 607 | + |
| 327 | 608 | static long cxl_memdev_ioctl(struct file *file, unsigned int cmd, |
| 328 | 609 | unsigned long arg) |
| 329 | 610 | { |
| @@ -337,7 +618,7 @@ static long cxl_memdev_ioctl(struct file *file, unsigned int cmd, · 7 unmodified lines |
| 337 | 618 | if (!percpu_ref_tryget_live(&cxlmd->ops_active)) |
| 338 | 619 | return -ENXIO; |
| 339 | 620 | |
| 340 | | − /* TODO: ioctl body */ |
| 621 | + rc = __cxl_memdev_ioctl(cxlmd, cmd, arg); |
| 341 | 622 | |
| 342 | 623 | percpu_ref_put(&cxlmd->ops_active); |
| 343 | 624 | |