| @@ -1,5 +1,6 @@ |
| 1 | // SPDX-License-Identifier: GPL-2.0-only | 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* Copyright(c) 2020 Intel Corporation. All rights reserved. */ | 2 | /* Copyright(c) 2020 Intel Corporation. All rights reserved. */ |
| | 3 | #include <uapi/linux/cxl_mem.h> |
| 3 | #include <linux/module.h> | 4 | #include <linux/module.h> |
| 4 | #include <linux/mutex.h> | 5 | #include <linux/mutex.h> |
| 5 | #include <linux/cdev.h> | 6 | #include <linux/cdev.h> |
| @@ -40,6 +41,7 @@ · 34 unmodified lines |
| 40 | #define CXL_MAILBOX_TIMEOUT_MS (2 * HZ) | 41 | #define CXL_MAILBOX_TIMEOUT_MS (2 * HZ) |
| 41 | | 42 | |
| 42 | enum opcode { | 43 | enum opcode { |
| | 44 | CXL_MBOX_OP_INVALID = 0x0000, |
| 43 | CXL_MBOX_OP_IDENTIFY = 0x4000, | 45 | CXL_MBOX_OP_IDENTIFY = 0x4000, |
| 44 | CXL_MBOX_OP_MAX = 0x10000 | 46 | CXL_MBOX_OP_MAX = 0x10000 |
| 45 | }; | 47 | }; |
| @@ -95,6 +97,49 @@ struct cxl_memdev { · 49 unmodified lines |
| 95 | static int cxl_mem_major; | 97 | static int cxl_mem_major; |
| 96 | static DEFINE_IDA(cxl_memdev_ida); | 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 | static int cxl_mem_wait_for_doorbell(struct cxl_mem *cxlm) | 143 | static int cxl_mem_wait_for_doorbell(struct cxl_mem *cxlm) |
| 99 | { | 144 | { |
| 100 | const unsigned long start = jiffies; | 145 | const unsigned long start = jiffies; |
| @@ -324,6 +369,242 @@ static void cxl_mem_mbox_put(struct cxl_mem *cxlm) · 223 unmodified lines |
| 324 | mutex_unlock(&cxlm->mbox_mutex); | 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 | static long cxl_memdev_ioctl(struct file *file, unsigned int cmd, | 608 | static long cxl_memdev_ioctl(struct file *file, unsigned int cmd, |
| 328 | unsigned long arg) | 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 | if (!percpu_ref_tryget_live(&cxlmd->ops_active)) | 618 | if (!percpu_ref_tryget_live(&cxlmd->ops_active)) |
| 338 | return -ENXIO; | 619 | return -ENXIO; |
| 339 | | 620 | |
| 340 | /* TODO: ioctl body */ | 621 | rc = __cxl_memdev_ioctl(cxlmd, cmd, arg); |
| 341 | | 622 | |
| 342 | percpu_ref_put(&cxlmd->ops_active); | 623 | percpu_ref_put(&cxlmd->ops_active); |
| 343 | | 624 | |