On Mon, 15 Feb 2021 17:45:35 -0800 Ben Widawsky <ben.widawsky@xxxxxxxxx> wrote: > CXL devices identified by the memory-device class code must implement > the Device Command Interface (described in 8.2.9 of the CXL 2.0 spec). > While the driver already maintains a list of commands it supports, there > is still a need to be able to distinguish between commands that the > driver knows about from commands that are optionally supported by the > hardware. > > The Command Effects Log (CEL) is specified in the CXL 2.0 specification. > The CEL is one of two types of logs, the other being vendor specific. > They are distinguished in hardware/spec via UUID. The CEL is useful for > 2 things: > 1. Determine which optional commands are supported by the CXL device. > 2. Enumerate any vendor specific commands > > The CEL is used by the driver to determine which commands are available > in the hardware and therefore which commands userspace is allowed to > execute. The set of enabled commands might be a subset of commands which > are advertised in UAPI via CXL_MEM_SEND_COMMAND IOCTL. > > With the CEL enabling comes a internal flag to indicate a base set of > commands that are enabled regardless of CEL. Such commands are required > for basic interaction with the hardware and thus can be useful in debug > cases, for example if the CEL is corrupted. > > The implementation leaves the statically defined table of commands and > supplements it with a bitmap to determine commands that are enabled. > This organization was chosen for the following reasons: > - Smaller memory footprint. Doesn't need a table per device. > - Reduce memory allocation complexity. > - Fixed command IDs to opcode mapping for all devices makes development > and debugging easier. > - Certain helpers are easily achievable, like cxl_for_each_cmd(). > > Signed-off-by: Ben Widawsky <ben.widawsky@xxxxxxxxx> > Reviewed-by: Dan Williams <dan.j.williams@xxxxxxxxx> A few trivial, now unnecessary casts following on from you changing the types of the buffer pointers passed to cxl_mem_mbox_send_cmd() to void * Otherwise looks good to me. Reviewed-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> > --- > drivers/cxl/cxl.h | 2 + > drivers/cxl/mem.c | 213 ++++++++++++++++++++++++++++++++++- > include/uapi/linux/cxl_mem.h | 1 + > 3 files changed, 213 insertions(+), 3 deletions(-) > > diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h > index f69313dc3b4e..cb7a77ed443d 100644 > --- a/drivers/cxl/cxl.h > +++ b/drivers/cxl/cxl.h > @@ -67,6 +67,7 @@ struct cxl_memdev; > * (CXL 2.0 8.2.8.4.3 Mailbox Capabilities Register) > * @mbox_mutex: Mutex to synchronize mailbox access. > * @firmware_version: Firmware version for the memory device. > + * @enabled_commands: Hardware commands found enabled in CEL. > * @pmem: Persistent memory capacity information. > * @ram: Volatile memory capacity information. > */ > @@ -82,6 +83,7 @@ struct cxl_mem { > size_t payload_size; > struct mutex mbox_mutex; /* Protects device mailbox and firmware */ > char firmware_version[0x10]; > + unsigned long *enabled_cmds; > > struct range pmem_range; > struct range ram_range; > diff --git a/drivers/cxl/mem.c b/drivers/cxl/mem.c > index 6b4feb0ce47d..4d921b4375f9 100644 > --- a/drivers/cxl/mem.c > +++ b/drivers/cxl/mem.c > @@ -46,6 +46,8 @@ enum opcode { > CXL_MBOX_OP_INVALID = 0x0000, > CXL_MBOX_OP_RAW = CXL_MBOX_OP_INVALID, > CXL_MBOX_OP_ACTIVATE_FW = 0x0202, > + CXL_MBOX_OP_GET_SUPPORTED_LOGS = 0x0400, > + CXL_MBOX_OP_GET_LOG = 0x0401, > CXL_MBOX_OP_IDENTIFY = 0x4000, > CXL_MBOX_OP_SET_PARTITION_INFO = 0x4101, > CXL_MBOX_OP_SET_LSA = 0x4103, > @@ -104,10 +106,28 @@ static DEFINE_IDA(cxl_memdev_ida); > static struct dentry *cxl_debugfs; > static bool cxl_raw_allow_all; > > +enum { > + CEL_UUID, > + VENDOR_DEBUG_UUID, > +}; > + > +/* See CXL 2.0 Table 170. Get Log Input Payload */ > +static const uuid_t log_uuid[] = { > + [CEL_UUID] = UUID_INIT(0xda9c0b5, 0xbf41, 0x4b78, 0x8f, 0x79, 0x96, > + 0xb1, 0x62, 0x3b, 0x3f, 0x17), > + [VENDOR_DEBUG_UUID] = UUID_INIT(0xe1819d9, 0x11a9, 0x400c, 0x81, 0x1f, > + 0xd6, 0x07, 0x19, 0x40, 0x3d, 0x86), > +}; > + > /** > * struct cxl_mem_command - Driver representation of a memory device command > * @info: Command information as it exists for the UAPI > * @opcode: The actual bits used for the mailbox protocol > + * @flags: Set of flags effecting driver behavior. > + * > + * * %CXL_CMD_FLAG_FORCE_ENABLE: In cases of error, commands with this flag > + * will be enabled by the driver regardless of what hardware may have > + * advertised. > * > * The cxl_mem_command is the driver's internal representation of commands that > * are supported by the driver. Some of these commands may not be supported by > @@ -119,9 +139,12 @@ static bool cxl_raw_allow_all; > struct cxl_mem_command { > struct cxl_command_info info; > enum opcode opcode; > + u32 flags; > +#define CXL_CMD_FLAG_NONE 0 > +#define CXL_CMD_FLAG_FORCE_ENABLE BIT(0) > }; > > -#define CXL_CMD(_id, sin, sout) \ > +#define CXL_CMD(_id, sin, sout, _flags) \ > [CXL_MEM_COMMAND_ID_##_id] = { \ > .info = { \ > .id = CXL_MEM_COMMAND_ID_##_id, \ > @@ -129,6 +152,7 @@ struct cxl_mem_command { > .size_out = sout, \ > }, \ > .opcode = CXL_MBOX_OP_##_id, \ > + .flags = _flags, \ > } > > /* > @@ -138,10 +162,11 @@ struct cxl_mem_command { > * 0, and the user passed in 1, it is an error. > */ > static struct cxl_mem_command mem_commands[] = { > - CXL_CMD(IDENTIFY, 0, 0x43), > + CXL_CMD(IDENTIFY, 0, 0x43, CXL_CMD_FLAG_FORCE_ENABLE), > #ifdef CONFIG_CXL_MEM_RAW_COMMANDS > - CXL_CMD(RAW, ~0, ~0), > + CXL_CMD(RAW, ~0, ~0, 0), > #endif > + CXL_CMD(GET_SUPPORTED_LOGS, 0, ~0, CXL_CMD_FLAG_FORCE_ENABLE), > }; > > /* > @@ -630,6 +655,10 @@ static int cxl_validate_cmd_from_user(struct cxl_mem *cxlm, > c = &mem_commands[send_cmd->id]; > info = &c->info; > > + /* Check that the command is enabled for hardware */ > + if (!test_bit(info->id, cxlm->enabled_cmds)) > + return -ENOTTY; > + > /* Check the input buffer is the expected size */ > if (info->size_in >= 0 && info->size_in != send_cmd->in.size) > return -ENOMEM; > @@ -935,6 +964,14 @@ static struct cxl_mem *cxl_mem_create(struct pci_dev *pdev, u32 reg_lo, > mutex_init(&cxlm->mbox_mutex); > cxlm->pdev = pdev; > cxlm->regs = regs + offset; > + cxlm->enabled_cmds = > + devm_kmalloc_array(dev, BITS_TO_LONGS(cxl_cmd_count), > + sizeof(unsigned long), > + GFP_KERNEL | __GFP_ZERO); > + if (!cxlm->enabled_cmds) { > + dev_err(dev, "No memory available for bitmap\n"); > + return NULL; > + } > > dev_dbg(dev, "Mapped CXL Memory Device resource\n"); > return cxlm; > @@ -1154,6 +1191,172 @@ static int cxl_mem_add_memdev(struct cxl_mem *cxlm) > return rc; > } > > +static int cxl_xfer_log(struct cxl_mem *cxlm, uuid_t *uuid, u32 size, u8 *out) > +{ > + u32 remaining = size; > + u32 offset = 0; > + > + while (remaining) { > + u32 xfer_size = min_t(u32, remaining, cxlm->payload_size); > + struct cxl_mbox_get_log { > + uuid_t uuid; > + __le32 offset; > + __le32 length; > + } __packed log = { > + .uuid = *uuid, > + .offset = cpu_to_le32(offset), > + .length = cpu_to_le32(xfer_size) > + }; > + int rc; > + > + rc = cxl_mem_mbox_send_cmd(cxlm, CXL_MBOX_OP_GET_LOG, > + (u8 *)&log, sizeof(log), out, Shouldn't need that u8 * cast any more now parameter is void * > + xfer_size); > + if (rc < 0) > + return rc; > + > + out += xfer_size; > + remaining -= xfer_size; > + offset += xfer_size; > + } > + > + return 0; > +} > + > +static inline struct cxl_mem_command *cxl_mem_find_command(u16 opcode) > +{ > + struct cxl_mem_command *c; > + > + cxl_for_each_cmd(c) > + if (c->opcode == opcode) > + return c; > + > + return NULL; > +} > + > +/** > + * cxl_walk_cel() - Walk through the Command Effects Log. > + * @cxlm: Device. > + * @size: Length of the Command Effects Log. > + * @cel: CEL > + * > + * Iterate over each entry in the CEL and determine if the driver supports the > + * command. If so, the command is enabled for the device and can be used later. > + */ > +static void cxl_walk_cel(struct cxl_mem *cxlm, size_t size, u8 *cel) > +{ > + struct cel_entry { > + __le16 opcode; > + __le16 effect; > + } __packed * cel_entry; > + const int cel_entries = size / sizeof(*cel_entry); > + int i; > + > + cel_entry = (struct cel_entry *)cel; > + > + for (i = 0; i < cel_entries; i++) { > + u16 opcode = le16_to_cpu(cel_entry[i].opcode); > + struct cxl_mem_command *cmd = cxl_mem_find_command(opcode); > + > + if (!cmd) { > + dev_dbg(&cxlm->pdev->dev, > + "Opcode 0x%04x unsupported by driver", opcode); > + continue; > + } > + > + set_bit(cmd->info.id, cxlm->enabled_cmds); > + } > +} > + > +struct cxl_mbox_get_supported_logs { > + __le16 entries; > + u8 rsvd[6]; > + struct gsl_entry { > + uuid_t uuid; > + __le32 size; > + } __packed entry[]; > +} __packed; > + > +static struct cxl_mbox_get_supported_logs *cxl_get_gsl(struct cxl_mem *cxlm) > +{ > + struct cxl_mbox_get_supported_logs *ret; > + int rc; > + > + ret = kvmalloc(cxlm->payload_size, GFP_KERNEL); > + if (!ret) > + return ERR_PTR(-ENOMEM); > + > + rc = cxl_mem_mbox_send_cmd(cxlm, CXL_MBOX_OP_GET_SUPPORTED_LOGS, NULL, > + 0, (u8 *)ret, sizeof(*ret)); Parameter type for out is now void *, so should be no need to cast another type to it explicitly. > + if (rc < 0) { > + kvfree(ret); > + return ERR_PTR(rc); > + } > + > + return ret; > +} > + > +/** > + * cxl_mem_enumerate_cmds() - Enumerate commands for a device. > + * @cxlm: The device. > + * > + * Returns 0 if enumerate completed successfully. > + * > + * CXL devices have optional support for certain commands. This function will > + * determine the set of supported commands for the hardware and update the > + * enabled_cmds bitmap in the @cxlm. > + */ > +static int cxl_mem_enumerate_cmds(struct cxl_mem *cxlm) > +{ > + struct cxl_mbox_get_supported_logs *gsl; > + struct device *dev = &cxlm->pdev->dev; > + struct cxl_mem_command *cmd; > + int i, rc; > + > + gsl = cxl_get_gsl(cxlm); > + if (IS_ERR(gsl)) > + return PTR_ERR(gsl); > + > + rc = -ENOENT; > + for (i = 0; i < le16_to_cpu(gsl->entries); i++) { > + u32 size = le32_to_cpu(gsl->entry[i].size); > + uuid_t uuid = gsl->entry[i].uuid; > + u8 *log; > + > + dev_dbg(dev, "Found LOG type %pU of size %d", &uuid, size); > + > + if (!uuid_equal(&uuid, &log_uuid[CEL_UUID])) > + continue; > + > + log = kvmalloc(size, GFP_KERNEL); > + if (!log) { > + rc = -ENOMEM; > + goto out; > + } > + > + rc = cxl_xfer_log(cxlm, &uuid, size, log); > + if (rc) { > + kvfree(log); > + goto out; > + } > + > + cxl_walk_cel(cxlm, size, log); > + kvfree(log); > + > + /* In case CEL was bogus, enable some default commands. */ Worth a warning if it is bogus? > + cxl_for_each_cmd(cmd) > + if (cmd->flags & CXL_CMD_FLAG_FORCE_ENABLE) > + set_bit(cmd->info.id, cxlm->enabled_cmds); > + > + /* Found the required CEL */ > + rc = 0; Is that not already set appropriately? I guess this kind of acts as documentation. > + } > + > +out: > + kvfree(gsl); > + return rc; > +} > + > /** > * cxl_mem_identify() - Send the IDENTIFY command to the device. > * @cxlm: The device to identify. > @@ -1253,6 +1456,10 @@ static int cxl_mem_probe(struct pci_dev *pdev, const struct pci_device_id *id) > if (rc) > return rc; > > + rc = cxl_mem_enumerate_cmds(cxlm); > + if (rc) > + return rc; > + > rc = cxl_mem_identify(cxlm); > if (rc) > return rc; > diff --git a/include/uapi/linux/cxl_mem.h b/include/uapi/linux/cxl_mem.h > index 8eb669150ecb..3b5bf4b58fb4 100644 > --- a/include/uapi/linux/cxl_mem.h > +++ b/include/uapi/linux/cxl_mem.h > @@ -23,6 +23,7 @@ > ___C(INVALID, "Invalid Command"), \ > ___C(IDENTIFY, "Identify Command"), \ > ___C(RAW, "Raw device command"), \ > + ___C(GET_SUPPORTED_LOGS, "Get Supported Logs"), \ > ___C(MAX, "invalid / last command") > > #define ___C(a, b) CXL_MEM_COMMAND_ID_##a