Message ID | 20220212013252.1293396-2-david.e.box@linux.intel.com (mailing list archive) |
---|---|
State | Accepted, archived |
Headers | show |
Series | Intel Software Defined Silicon | expand |
Hi, On 2/12/22 02:32, David E. Box wrote: > Intel Software Defined Silicon (SDSi) is a post manufacturing mechanism for > activating additional silicon features. Features are enabled through a > license activation process. The SDSi driver provides a per socket, sysfs > attribute interface for applications to perform 3 main provisioning > functions: > > 1. Provision an Authentication Key Certificate (AKC), a key written to > internal NVRAM that is used to authenticate a capability specific > activation payload. > > 2. Provision a Capability Activation Payload (CAP), a token authenticated > using the AKC and applied to the CPU configuration to activate a new > feature. > > 3. Read the SDSi State Certificate, containing the CPU configuration > state. > > The operations perform function specific mailbox commands that forward the > requests to SDSi hardware to perform authentication of the payloads and > enable the silicon configuration (to be made available after power > cycling). > > The SDSi device itself is enumerated as an auxiliary device from the > intel_vsec driver and as such has a build dependency on CONFIG_INTEL_VSEC. > > Link: https://github.com/intel/intel-sdsi > Signed-off-by: David E. Box <david.e.box@linux.intel.com> > Reviewed-by: Mark Gross <markgross@kernel.org> Thank you for your patch, I've applied this patch to my review-hans branch: https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans Note it will show up in my review-hans branch once I've pushed my local branch there, which might take a while. Once I've run some tests on this branch the patches there will be added to the platform-drivers-x86/for-next branch and eventually will be included in the pdx86 pull-request to Linus for the next merge-window. Regards, Hans > --- > V7 > - Fix printk specifiers and typos. Suggested by Joe Perches. > > V6 > - Replace, > return (ret < 0) ? ret : size; > with, > if (ret) > return ret; > return size > > Besides the style change (suggested by GKH) this fixes a klocwork > warning. > > V5 > - Update kernel version to 5.18 in API doc and copyrights to 2022. > - Remove unneeded prototypes. > - In binary attribute handlers where ret is only used for errors, > replace, > return (ret < 0) ? ret : size; > with, > return ret ?: size; > > V4 > - Replace dropped semicolon on sdsi_aux_driver struct. > V3 > - In state_certificate_read(), return the actual size instead of the > requested count. Return 0 if offset is non-zero so that subsequent > calls attempting to read the rest of the count end. > - s/folder/directory in ABI documentation. > - Add comment that all driver resources are devm managed so remove() > is not needed. > V2 > - Use sysfs_emit() in guid_show() > - Fix language in ABI, suggested by Bjorn > - Fix wrong directory name in ABI doc > > .../ABI/testing/sysfs-driver-intel_sdsi | 77 +++ > MAINTAINERS | 5 + > drivers/platform/x86/intel/Kconfig | 12 + > drivers/platform/x86/intel/Makefile | 2 + > drivers/platform/x86/intel/sdsi.c | 574 ++++++++++++++++++ > drivers/platform/x86/intel/vsec.c | 12 +- > 6 files changed, 681 insertions(+), 1 deletion(-) > create mode 100644 Documentation/ABI/testing/sysfs-driver-intel_sdsi > create mode 100644 drivers/platform/x86/intel/sdsi.c > > diff --git a/Documentation/ABI/testing/sysfs-driver-intel_sdsi b/Documentation/ABI/testing/sysfs-driver-intel_sdsi > new file mode 100644 > index 000000000000..ab122125ff9a > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-driver-intel_sdsi > @@ -0,0 +1,77 @@ > +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X > +Date: Feb 2022 > +KernelVersion: 5.18 > +Contact: "David E. Box" <david.e.box@linux.intel.com> > +Description: > + This directory contains interface files for accessing Intel > + Software Defined Silicon (SDSi) features on a CPU. X > + represents the socket instance (though not the socket ID). > + The socket ID is determined by reading the registers file > + and decoding it per the specification. > + > + Some files communicate with SDSi hardware through a mailbox. > + Should the operation fail, one of the following error codes > + may be returned: > + > + Error Code Cause > + ---------- ----- > + EIO General mailbox failure. Log may indicate cause. > + EBUSY Mailbox is owned by another agent. > + EPERM SDSI capability is not enabled in hardware. > + EPROTO Failure in mailbox protocol detected by driver. > + See log for details. > + EOVERFLOW For provision commands, the size of the data > + exceeds what may be written. > + ESPIPE Seeking is not allowed. > + ETIMEDOUT Failure to complete mailbox transaction in time. > + > +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/guid > +Date: Feb 2022 > +KernelVersion: 5.18 > +Contact: "David E. Box" <david.e.box@linux.intel.com> > +Description: > + (RO) The GUID for the registers file. The GUID identifies > + the layout of the registers file in this directory. > + Information about the register layouts for a particular GUID > + is available at http://github.com/intel/intel-sdsi > + > +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/registers > +Date: Feb 2022 > +KernelVersion: 5.18 > +Contact: "David E. Box" <david.e.box@linux.intel.com> > +Description: > + (RO) Contains information needed by applications to provision > + a CPU and monitor status information. The layout of this file > + is determined by the GUID in this directory. Information about > + the layout for a particular GUID is available at > + http://github.com/intel/intel-sdsi > + > +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_akc > +Date: Feb 2022 > +KernelVersion: 5.18 > +Contact: "David E. Box" <david.e.box@linux.intel.com> > +Description: > + (WO) Used to write an Authentication Key Certificate (AKC) to > + the SDSi NVRAM for the CPU. The AKC is used to authenticate a > + Capability Activation Payload. Mailbox command. > + > +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_cap > +Date: Feb 2022 > +KernelVersion: 5.18 > +Contact: "David E. Box" <david.e.box@linux.intel.com> > +Description: > + (WO) Used to write a Capability Activation Payload (CAP) to the > + SDSi NVRAM for the CPU. CAPs are used to activate a given CPU > + feature. A CAP is validated by SDSi hardware using a previously > + provisioned AKC file. Upon successful authentication, the CPU > + configuration is updated. A cold reboot is required to fully > + activate the feature. Mailbox command. > + > +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/state_certificate > +Date: Feb 2022 > +KernelVersion: 5.18 > +Contact: "David E. Box" <david.e.box@linux.intel.com> > +Description: > + (RO) Used to read back the current State Certificate for the CPU > + from SDSi hardware. The State Certificate contains information > + about the current licenses on the CPU. Mailbox command. > diff --git a/MAINTAINERS b/MAINTAINERS > index 69a2935daf6c..29d0945f5a63 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -9869,6 +9869,11 @@ S: Maintained > F: arch/x86/include/asm/intel_scu_ipc.h > F: drivers/platform/x86/intel_scu_* > > +INTEL SDSI DRIVER > +M: David E. Box <david.e.box@linux.intel.com> > +S: Supported > +F: drivers/platform/x86/intel/sdsi.c > + > INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER > M: Daniel Scally <djrscally@gmail.com> > S: Maintained > diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig > index 8e65086bb6c8..99c8834ec979 100644 > --- a/drivers/platform/x86/intel/Kconfig > +++ b/drivers/platform/x86/intel/Kconfig > @@ -134,6 +134,18 @@ config INTEL_RST > firmware will copy the memory contents back to RAM and resume the OS > as usual. > > +config INTEL_SDSI > + tristate "Intel Software Defined Silicon Driver" > + depends on INTEL_VSEC > + depends on X86_64 > + help > + This driver enables access to the Intel Software Defined Silicon > + interface used to provision silicon features with an authentication > + certificate and capability license. > + > + To compile this driver as a module, choose M here: the module will > + be called intel_sdsi. > + > config INTEL_SMARTCONNECT > tristate "Intel Smart Connect disabling driver" > depends on ACPI > diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile > index 35f2066578b2..a765d60b6002 100644 > --- a/drivers/platform/x86/intel/Makefile > +++ b/drivers/platform/x86/intel/Makefile > @@ -26,6 +26,8 @@ intel_int0002_vgpio-y := int0002_vgpio.o > obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o > intel_oaktrail-y := oaktrail.o > obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o > +intel_sdsi-y := sdsi.o > +obj-$(CONFIG_INTEL_SDSI) += intel_sdsi.o > intel_vsec-y := vsec.o > obj-$(CONFIG_INTEL_VSEC) += intel_vsec.o > > diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c > new file mode 100644 > index 000000000000..99ec93f465a8 > --- /dev/null > +++ b/drivers/platform/x86/intel/sdsi.c > @@ -0,0 +1,574 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Intel Software Defined Silicon driver > + * > + * Copyright (c) 2022, Intel Corporation. > + * All Rights Reserved. > + * > + * Author: "David E. Box" <david.e.box@linux.intel.com> > + */ > + > +#include <linux/auxiliary_bus.h> > +#include <linux/bits.h> > +#include <linux/bitfield.h> > +#include <linux/device.h> > +#include <linux/iopoll.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/pci.h> > +#include <linux/slab.h> > +#include <linux/sysfs.h> > +#include <linux/types.h> > +#include <linux/uaccess.h> > + > +#include "vsec.h" > + > +#define ACCESS_TYPE_BARID 2 > +#define ACCESS_TYPE_LOCAL 3 > + > +#define SDSI_MIN_SIZE_DWORDS 276 > +#define SDSI_SIZE_CONTROL 8 > +#define SDSI_SIZE_MAILBOX 1024 > +#define SDSI_SIZE_REGS 72 > +#define SDSI_SIZE_CMD sizeof(u64) > + > +/* > + * Write messages are currently up to the size of the mailbox > + * while read messages are up to 4 times the size of the > + * mailbox, sent in packets > + */ > +#define SDSI_SIZE_WRITE_MSG SDSI_SIZE_MAILBOX > +#define SDSI_SIZE_READ_MSG (SDSI_SIZE_MAILBOX * 4) > + > +#define SDSI_ENABLED_FEATURES_OFFSET 16 > +#define SDSI_ENABLED BIT(3) > +#define SDSI_SOCKET_ID_OFFSET 64 > +#define SDSI_SOCKET_ID GENMASK(3, 0) > + > +#define SDSI_MBOX_CMD_SUCCESS 0x40 > +#define SDSI_MBOX_CMD_TIMEOUT 0x80 > + > +#define MBOX_TIMEOUT_US 2000 > +#define MBOX_TIMEOUT_ACQUIRE_US 1000 > +#define MBOX_POLLING_PERIOD_US 100 > +#define MBOX_MAX_PACKETS 4 > + > +#define MBOX_OWNER_NONE 0x00 > +#define MBOX_OWNER_INBAND 0x01 > + > +#define CTRL_RUN_BUSY BIT(0) > +#define CTRL_READ_WRITE BIT(1) > +#define CTRL_SOM BIT(2) > +#define CTRL_EOM BIT(3) > +#define CTRL_OWNER GENMASK(5, 4) > +#define CTRL_COMPLETE BIT(6) > +#define CTRL_READY BIT(7) > +#define CTRL_STATUS GENMASK(15, 8) > +#define CTRL_PACKET_SIZE GENMASK(31, 16) > +#define CTRL_MSG_SIZE GENMASK(63, 48) > + > +#define DISC_TABLE_SIZE 12 > +#define DT_ACCESS_TYPE GENMASK(3, 0) > +#define DT_SIZE GENMASK(27, 12) > +#define DT_TBIR GENMASK(2, 0) > +#define DT_OFFSET(v) ((v) & GENMASK(31, 3)) > + > +enum sdsi_command { > + SDSI_CMD_PROVISION_AKC = 0x04, > + SDSI_CMD_PROVISION_CAP = 0x08, > + SDSI_CMD_READ_STATE = 0x10, > +}; > + > +struct sdsi_mbox_info { > + u64 *payload; > + u64 *buffer; > + int size; > +}; > + > +struct disc_table { > + u32 access_info; > + u32 guid; > + u32 offset; > +}; > + > +struct sdsi_priv { > + struct mutex mb_lock; /* Mailbox access lock */ > + struct device *dev; > + void __iomem *control_addr; > + void __iomem *mbox_addr; > + void __iomem *regs_addr; > + u32 guid; > + bool sdsi_enabled; > +}; > + > +/* SDSi mailbox operations must be performed using 64bit mov instructions */ > +static __always_inline void > +sdsi_memcpy64_toio(u64 __iomem *to, const u64 *from, size_t count_bytes) > +{ > + size_t count = count_bytes / sizeof(*to); > + int i; > + > + for (i = 0; i < count; i++) > + writeq(from[i], &to[i]); > +} > + > +static __always_inline void > +sdsi_memcpy64_fromio(u64 *to, const u64 __iomem *from, size_t count_bytes) > +{ > + size_t count = count_bytes / sizeof(*to); > + int i; > + > + for (i = 0; i < count; i++) > + to[i] = readq(&from[i]); > +} > + > +static inline void sdsi_complete_transaction(struct sdsi_priv *priv) > +{ > + u64 control = FIELD_PREP(CTRL_COMPLETE, 1); > + > + lockdep_assert_held(&priv->mb_lock); > + writeq(control, priv->control_addr); > +} > + > +static int sdsi_status_to_errno(u32 status) > +{ > + switch (status) { > + case SDSI_MBOX_CMD_SUCCESS: > + return 0; > + case SDSI_MBOX_CMD_TIMEOUT: > + return -ETIMEDOUT; > + default: > + return -EIO; > + } > +} > + > +static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, > + size_t *data_size) > +{ > + struct device *dev = priv->dev; > + u32 total, loop, eom, status, message_size; > + u64 control; > + int ret; > + > + lockdep_assert_held(&priv->mb_lock); > + > + /* Format and send the read command */ > + control = FIELD_PREP(CTRL_EOM, 1) | > + FIELD_PREP(CTRL_SOM, 1) | > + FIELD_PREP(CTRL_RUN_BUSY, 1) | > + FIELD_PREP(CTRL_PACKET_SIZE, info->size); > + writeq(control, priv->control_addr); > + > + /* For reads, data sizes that are larger than the mailbox size are read in packets. */ > + total = 0; > + loop = 0; > + do { > + int offset = SDSI_SIZE_MAILBOX * loop; > + void __iomem *addr = priv->mbox_addr + offset; > + u64 *buf = info->buffer + offset / SDSI_SIZE_CMD; > + u32 packet_size; > + > + /* Poll on ready bit */ > + ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY, > + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); > + if (ret) > + break; > + > + eom = FIELD_GET(CTRL_EOM, control); > + status = FIELD_GET(CTRL_STATUS, control); > + packet_size = FIELD_GET(CTRL_PACKET_SIZE, control); > + message_size = FIELD_GET(CTRL_MSG_SIZE, control); > + > + ret = sdsi_status_to_errno(status); > + if (ret) > + break; > + > + /* Only the last packet can be less than the mailbox size. */ > + if (!eom && packet_size != SDSI_SIZE_MAILBOX) { > + dev_err(dev, "Invalid packet size\n"); > + ret = -EPROTO; > + break; > + } > + > + if (packet_size > SDSI_SIZE_MAILBOX) { > + dev_err(dev, "Packet size too large\n"); > + ret = -EPROTO; > + break; > + } > + > + sdsi_memcpy64_fromio(buf, addr, round_up(packet_size, SDSI_SIZE_CMD)); > + > + total += packet_size; > + > + sdsi_complete_transaction(priv); > + } while (!eom && ++loop < MBOX_MAX_PACKETS); > + > + if (ret) { > + sdsi_complete_transaction(priv); > + return ret; > + } > + > + if (!eom) { > + dev_err(dev, "Exceeded read attempts\n"); > + return -EPROTO; > + } > + > + /* Message size check is only valid for multi-packet transfers */ > + if (loop && total != message_size) > + dev_warn(dev, "Read count %u differs from expected count %u\n", > + total, message_size); > + > + *data_size = total; > + > + return 0; > +} > + > +static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) > +{ > + u64 control; > + u32 status; > + int ret; > + > + lockdep_assert_held(&priv->mb_lock); > + > + /* Write rest of the payload */ > + sdsi_memcpy64_toio(priv->mbox_addr + SDSI_SIZE_CMD, info->payload + 1, > + info->size - SDSI_SIZE_CMD); > + > + /* Format and send the write command */ > + control = FIELD_PREP(CTRL_EOM, 1) | > + FIELD_PREP(CTRL_SOM, 1) | > + FIELD_PREP(CTRL_RUN_BUSY, 1) | > + FIELD_PREP(CTRL_READ_WRITE, 1) | > + FIELD_PREP(CTRL_PACKET_SIZE, info->size); > + writeq(control, priv->control_addr); > + > + /* Poll on run_busy bit */ > + ret = readq_poll_timeout(priv->control_addr, control, !(control & CTRL_RUN_BUSY), > + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); > + > + if (ret) > + goto release_mbox; > + > + status = FIELD_GET(CTRL_STATUS, control); > + ret = sdsi_status_to_errno(status); > + > +release_mbox: > + sdsi_complete_transaction(priv); > + > + return ret; > +} > + > +static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info) > +{ > + u64 control; > + u32 owner; > + int ret; > + > + lockdep_assert_held(&priv->mb_lock); > + > + /* Check mailbox is available */ > + control = readq(priv->control_addr); > + owner = FIELD_GET(CTRL_OWNER, control); > + if (owner != MBOX_OWNER_NONE) > + return -EBUSY; > + > + /* Write first qword of payload */ > + writeq(info->payload[0], priv->mbox_addr); > + > + /* Check for ownership */ > + ret = readq_poll_timeout(priv->control_addr, control, > + FIELD_GET(CTRL_OWNER, control) & MBOX_OWNER_INBAND, > + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_ACQUIRE_US); > + > + return ret; > +} > + > +static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) > +{ > + int ret; > + > + lockdep_assert_held(&priv->mb_lock); > + > + ret = sdsi_mbox_acquire(priv, info); > + if (ret) > + return ret; > + > + return sdsi_mbox_cmd_write(priv, info); > +} > + > +static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size) > +{ > + int ret; > + > + lockdep_assert_held(&priv->mb_lock); > + > + ret = sdsi_mbox_acquire(priv, info); > + if (ret) > + return ret; > + > + return sdsi_mbox_cmd_read(priv, info, data_size); > +} > + > +static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count, > + enum sdsi_command command) > +{ > + struct sdsi_mbox_info info; > + int ret; > + > + if (!priv->sdsi_enabled) > + return -EPERM; > + > + if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD)) > + return -EOVERFLOW; > + > + /* Qword aligned message + command qword */ > + info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD; > + > + info.payload = kzalloc(info.size, GFP_KERNEL); > + if (!info.payload) > + return -ENOMEM; > + > + /* Copy message to payload buffer */ > + memcpy(info.payload, buf, count); > + > + /* Command is last qword of payload buffer */ > + info.payload[(info.size - SDSI_SIZE_CMD) / SDSI_SIZE_CMD] = command; > + > + ret = mutex_lock_interruptible(&priv->mb_lock); > + if (ret) > + goto free_payload; > + ret = sdsi_mbox_write(priv, &info); > + mutex_unlock(&priv->mb_lock); > + > +free_payload: > + kfree(info.payload); > + > + if (ret) > + return ret; > + > + return count; > +} > + > +static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj, > + struct bin_attribute *attr, char *buf, loff_t off, > + size_t count) > +{ > + struct device *dev = kobj_to_dev(kobj); > + struct sdsi_priv *priv = dev_get_drvdata(dev); > + > + if (off) > + return -ESPIPE; > + > + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_AKC); > +} > +static BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG); > + > +static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj, > + struct bin_attribute *attr, char *buf, loff_t off, > + size_t count) > +{ > + struct device *dev = kobj_to_dev(kobj); > + struct sdsi_priv *priv = dev_get_drvdata(dev); > + > + if (off) > + return -ESPIPE; > + > + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_CAP); > +} > +static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG); > + > +static long state_certificate_read(struct file *filp, struct kobject *kobj, > + struct bin_attribute *attr, char *buf, loff_t off, > + size_t count) > +{ > + struct device *dev = kobj_to_dev(kobj); > + struct sdsi_priv *priv = dev_get_drvdata(dev); > + u64 command = SDSI_CMD_READ_STATE; > + struct sdsi_mbox_info info; > + size_t size; > + int ret; > + > + if (!priv->sdsi_enabled) > + return -EPERM; > + > + if (off) > + return 0; > + > + /* Buffer for return data */ > + info.buffer = kmalloc(SDSI_SIZE_READ_MSG, GFP_KERNEL); > + if (!info.buffer) > + return -ENOMEM; > + > + info.payload = &command; > + info.size = sizeof(command); > + > + ret = mutex_lock_interruptible(&priv->mb_lock); > + if (ret) > + goto free_buffer; > + ret = sdsi_mbox_read(priv, &info, &size); > + mutex_unlock(&priv->mb_lock); > + if (ret < 0) > + goto free_buffer; > + > + if (size > count) > + size = count; > + > + memcpy(buf, info.buffer, size); > + > +free_buffer: > + kfree(info.buffer); > + > + if (ret) > + return ret; > + > + return size; > +} > +static BIN_ATTR(state_certificate, 0400, state_certificate_read, NULL, SDSI_SIZE_READ_MSG); > + > +static ssize_t registers_read(struct file *filp, struct kobject *kobj, > + struct bin_attribute *attr, char *buf, loff_t off, > + size_t count) > +{ > + struct device *dev = kobj_to_dev(kobj); > + struct sdsi_priv *priv = dev_get_drvdata(dev); > + void __iomem *addr = priv->regs_addr; > + > + memcpy_fromio(buf, addr + off, count); > + > + return count; > +} > +static BIN_ATTR(registers, 0400, registers_read, NULL, SDSI_SIZE_REGS); > + > +static struct bin_attribute *sdsi_bin_attrs[] = { > + &bin_attr_registers, > + &bin_attr_state_certificate, > + &bin_attr_provision_akc, > + &bin_attr_provision_cap, > + NULL > +}; > + > +static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf) > +{ > + struct sdsi_priv *priv = dev_get_drvdata(dev); > + > + return sysfs_emit(buf, "0x%x\n", priv->guid); > +} > +static DEVICE_ATTR_RO(guid); > + > +static struct attribute *sdsi_attrs[] = { > + &dev_attr_guid.attr, > + NULL > +}; > + > +static const struct attribute_group sdsi_group = { > + .attrs = sdsi_attrs, > + .bin_attrs = sdsi_bin_attrs, > +}; > +__ATTRIBUTE_GROUPS(sdsi); > + > +static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent, > + struct disc_table *disc_table, struct resource *disc_res) > +{ > + u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info); > + u32 size = FIELD_GET(DT_SIZE, disc_table->access_info); > + u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset); > + u32 offset = DT_OFFSET(disc_table->offset); > + u32 features_offset; > + struct resource res = {}; > + > + /* Starting location of SDSi MMIO region based on access type */ > + switch (access_type) { > + case ACCESS_TYPE_LOCAL: > + if (tbir) { > + dev_err(priv->dev, "Unsupported BAR index %u for access type %u\n", > + tbir, access_type); > + return -EINVAL; > + } > + > + /* > + * For access_type LOCAL, the base address is as follows: > + * base address = end of discovery region + base offset + 1 > + */ > + res.start = disc_res->end + offset + 1; > + break; > + > + case ACCESS_TYPE_BARID: > + res.start = pci_resource_start(parent, tbir) + offset; > + break; > + > + default: > + dev_err(priv->dev, "Unrecognized access_type %u\n", access_type); > + return -EINVAL; > + } > + > + res.end = res.start + size * sizeof(u32) - 1; > + res.flags = IORESOURCE_MEM; > + > + priv->control_addr = devm_ioremap_resource(priv->dev, &res); > + if (IS_ERR(priv->control_addr)) > + return PTR_ERR(priv->control_addr); > + > + priv->mbox_addr = priv->control_addr + SDSI_SIZE_CONTROL; > + priv->regs_addr = priv->mbox_addr + SDSI_SIZE_MAILBOX; > + > + features_offset = readq(priv->regs_addr + SDSI_ENABLED_FEATURES_OFFSET); > + priv->sdsi_enabled = !!(features_offset & SDSI_ENABLED); > + > + return 0; > +} > + > +static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) > +{ > + struct intel_vsec_device *intel_cap_dev = auxdev_to_ivdev(auxdev); > + struct disc_table disc_table; > + struct resource *disc_res; > + void __iomem *disc_addr; > + struct sdsi_priv *priv; > + int ret; > + > + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->dev = &auxdev->dev; > + mutex_init(&priv->mb_lock); > + auxiliary_set_drvdata(auxdev, priv); > + > + /* Get the SDSi discovery table */ > + disc_res = &intel_cap_dev->resource[0]; > + disc_addr = devm_ioremap_resource(&auxdev->dev, disc_res); > + if (IS_ERR(disc_addr)) > + return PTR_ERR(disc_addr); > + > + memcpy_fromio(&disc_table, disc_addr, DISC_TABLE_SIZE); > + > + priv->guid = disc_table.guid; > + > + /* Map the SDSi mailbox registers */ > + ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static const struct auxiliary_device_id sdsi_aux_id_table[] = { > + { .name = "intel_vsec.sdsi" }, > + {} > +}; > +MODULE_DEVICE_TABLE(auxiliary, sdsi_aux_id_table); > + > +static struct auxiliary_driver sdsi_aux_driver = { > + .driver = { > + .dev_groups = sdsi_groups, > + }, > + .id_table = sdsi_aux_id_table, > + .probe = sdsi_probe, > + /* No remove. All resources are handled under devm */ > +}; > +module_auxiliary_driver(sdsi_aux_driver); > + > +MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>"); > +MODULE_DESCRIPTION("Intel Software Defined Silicon driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c > index c3bdd75ed690..bed436bf181f 100644 > --- a/drivers/platform/x86/intel/vsec.c > +++ b/drivers/platform/x86/intel/vsec.c > @@ -32,6 +32,7 @@ > #define TABLE_OFFSET_SHIFT 3 > > static DEFINE_IDA(intel_vsec_ida); > +static DEFINE_IDA(intel_vsec_sdsi_ida); > > /** > * struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers. > @@ -63,12 +64,14 @@ enum intel_vsec_id { > VSEC_ID_TELEMETRY = 2, > VSEC_ID_WATCHER = 3, > VSEC_ID_CRASHLOG = 4, > + VSEC_ID_SDSI = 65, > }; > > static enum intel_vsec_id intel_vsec_allow_list[] = { > VSEC_ID_TELEMETRY, > VSEC_ID_WATCHER, > VSEC_ID_CRASHLOG, > + VSEC_ID_SDSI, > }; > > static const char *intel_vsec_name(enum intel_vsec_id id) > @@ -83,6 +86,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id) > case VSEC_ID_CRASHLOG: > return "crashlog"; > > + case VSEC_ID_SDSI: > + return "sdsi"; > + > default: > return NULL; > } > @@ -211,7 +217,11 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he > intel_vsec_dev->resource = res; > intel_vsec_dev->num_resources = header->num_entries; > intel_vsec_dev->quirks = quirks; > - intel_vsec_dev->ida = &intel_vsec_ida; > + > + if (header->id == VSEC_ID_SDSI) > + intel_vsec_dev->ida = &intel_vsec_sdsi_ida; > + else > + intel_vsec_dev->ida = &intel_vsec_ida; > > return intel_vsec_add_aux(pdev, intel_vsec_dev, intel_vsec_name(header->id)); > }
diff --git a/Documentation/ABI/testing/sysfs-driver-intel_sdsi b/Documentation/ABI/testing/sysfs-driver-intel_sdsi new file mode 100644 index 000000000000..ab122125ff9a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-intel_sdsi @@ -0,0 +1,77 @@ +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" <david.e.box@linux.intel.com> +Description: + This directory contains interface files for accessing Intel + Software Defined Silicon (SDSi) features on a CPU. X + represents the socket instance (though not the socket ID). + The socket ID is determined by reading the registers file + and decoding it per the specification. + + Some files communicate with SDSi hardware through a mailbox. + Should the operation fail, one of the following error codes + may be returned: + + Error Code Cause + ---------- ----- + EIO General mailbox failure. Log may indicate cause. + EBUSY Mailbox is owned by another agent. + EPERM SDSI capability is not enabled in hardware. + EPROTO Failure in mailbox protocol detected by driver. + See log for details. + EOVERFLOW For provision commands, the size of the data + exceeds what may be written. + ESPIPE Seeking is not allowed. + ETIMEDOUT Failure to complete mailbox transaction in time. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/guid +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" <david.e.box@linux.intel.com> +Description: + (RO) The GUID for the registers file. The GUID identifies + the layout of the registers file in this directory. + Information about the register layouts for a particular GUID + is available at http://github.com/intel/intel-sdsi + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/registers +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" <david.e.box@linux.intel.com> +Description: + (RO) Contains information needed by applications to provision + a CPU and monitor status information. The layout of this file + is determined by the GUID in this directory. Information about + the layout for a particular GUID is available at + http://github.com/intel/intel-sdsi + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_akc +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" <david.e.box@linux.intel.com> +Description: + (WO) Used to write an Authentication Key Certificate (AKC) to + the SDSi NVRAM for the CPU. The AKC is used to authenticate a + Capability Activation Payload. Mailbox command. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_cap +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" <david.e.box@linux.intel.com> +Description: + (WO) Used to write a Capability Activation Payload (CAP) to the + SDSi NVRAM for the CPU. CAPs are used to activate a given CPU + feature. A CAP is validated by SDSi hardware using a previously + provisioned AKC file. Upon successful authentication, the CPU + configuration is updated. A cold reboot is required to fully + activate the feature. Mailbox command. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/state_certificate +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" <david.e.box@linux.intel.com> +Description: + (RO) Used to read back the current State Certificate for the CPU + from SDSi hardware. The State Certificate contains information + about the current licenses on the CPU. Mailbox command. diff --git a/MAINTAINERS b/MAINTAINERS index 69a2935daf6c..29d0945f5a63 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9869,6 +9869,11 @@ S: Maintained F: arch/x86/include/asm/intel_scu_ipc.h F: drivers/platform/x86/intel_scu_* +INTEL SDSI DRIVER +M: David E. Box <david.e.box@linux.intel.com> +S: Supported +F: drivers/platform/x86/intel/sdsi.c + INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally <djrscally@gmail.com> S: Maintained diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 8e65086bb6c8..99c8834ec979 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -134,6 +134,18 @@ config INTEL_RST firmware will copy the memory contents back to RAM and resume the OS as usual. +config INTEL_SDSI + tristate "Intel Software Defined Silicon Driver" + depends on INTEL_VSEC + depends on X86_64 + help + This driver enables access to the Intel Software Defined Silicon + interface used to provision silicon features with an authentication + certificate and capability license. + + To compile this driver as a module, choose M here: the module will + be called intel_sdsi. + config INTEL_SMARTCONNECT tristate "Intel Smart Connect disabling driver" depends on ACPI diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 35f2066578b2..a765d60b6002 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -26,6 +26,8 @@ intel_int0002_vgpio-y := int0002_vgpio.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o intel_oaktrail-y := oaktrail.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o +intel_sdsi-y := sdsi.o +obj-$(CONFIG_INTEL_SDSI) += intel_sdsi.o intel_vsec-y := vsec.o obj-$(CONFIG_INTEL_VSEC) += intel_vsec.o diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c new file mode 100644 index 000000000000..99ec93f465a8 --- /dev/null +++ b/drivers/platform/x86/intel/sdsi.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Software Defined Silicon driver + * + * Copyright (c) 2022, Intel Corporation. + * All Rights Reserved. + * + * Author: "David E. Box" <david.e.box@linux.intel.com> + */ + +#include <linux/auxiliary_bus.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#include "vsec.h" + +#define ACCESS_TYPE_BARID 2 +#define ACCESS_TYPE_LOCAL 3 + +#define SDSI_MIN_SIZE_DWORDS 276 +#define SDSI_SIZE_CONTROL 8 +#define SDSI_SIZE_MAILBOX 1024 +#define SDSI_SIZE_REGS 72 +#define SDSI_SIZE_CMD sizeof(u64) + +/* + * Write messages are currently up to the size of the mailbox + * while read messages are up to 4 times the size of the + * mailbox, sent in packets + */ +#define SDSI_SIZE_WRITE_MSG SDSI_SIZE_MAILBOX +#define SDSI_SIZE_READ_MSG (SDSI_SIZE_MAILBOX * 4) + +#define SDSI_ENABLED_FEATURES_OFFSET 16 +#define SDSI_ENABLED BIT(3) +#define SDSI_SOCKET_ID_OFFSET 64 +#define SDSI_SOCKET_ID GENMASK(3, 0) + +#define SDSI_MBOX_CMD_SUCCESS 0x40 +#define SDSI_MBOX_CMD_TIMEOUT 0x80 + +#define MBOX_TIMEOUT_US 2000 +#define MBOX_TIMEOUT_ACQUIRE_US 1000 +#define MBOX_POLLING_PERIOD_US 100 +#define MBOX_MAX_PACKETS 4 + +#define MBOX_OWNER_NONE 0x00 +#define MBOX_OWNER_INBAND 0x01 + +#define CTRL_RUN_BUSY BIT(0) +#define CTRL_READ_WRITE BIT(1) +#define CTRL_SOM BIT(2) +#define CTRL_EOM BIT(3) +#define CTRL_OWNER GENMASK(5, 4) +#define CTRL_COMPLETE BIT(6) +#define CTRL_READY BIT(7) +#define CTRL_STATUS GENMASK(15, 8) +#define CTRL_PACKET_SIZE GENMASK(31, 16) +#define CTRL_MSG_SIZE GENMASK(63, 48) + +#define DISC_TABLE_SIZE 12 +#define DT_ACCESS_TYPE GENMASK(3, 0) +#define DT_SIZE GENMASK(27, 12) +#define DT_TBIR GENMASK(2, 0) +#define DT_OFFSET(v) ((v) & GENMASK(31, 3)) + +enum sdsi_command { + SDSI_CMD_PROVISION_AKC = 0x04, + SDSI_CMD_PROVISION_CAP = 0x08, + SDSI_CMD_READ_STATE = 0x10, +}; + +struct sdsi_mbox_info { + u64 *payload; + u64 *buffer; + int size; +}; + +struct disc_table { + u32 access_info; + u32 guid; + u32 offset; +}; + +struct sdsi_priv { + struct mutex mb_lock; /* Mailbox access lock */ + struct device *dev; + void __iomem *control_addr; + void __iomem *mbox_addr; + void __iomem *regs_addr; + u32 guid; + bool sdsi_enabled; +}; + +/* SDSi mailbox operations must be performed using 64bit mov instructions */ +static __always_inline void +sdsi_memcpy64_toio(u64 __iomem *to, const u64 *from, size_t count_bytes) +{ + size_t count = count_bytes / sizeof(*to); + int i; + + for (i = 0; i < count; i++) + writeq(from[i], &to[i]); +} + +static __always_inline void +sdsi_memcpy64_fromio(u64 *to, const u64 __iomem *from, size_t count_bytes) +{ + size_t count = count_bytes / sizeof(*to); + int i; + + for (i = 0; i < count; i++) + to[i] = readq(&from[i]); +} + +static inline void sdsi_complete_transaction(struct sdsi_priv *priv) +{ + u64 control = FIELD_PREP(CTRL_COMPLETE, 1); + + lockdep_assert_held(&priv->mb_lock); + writeq(control, priv->control_addr); +} + +static int sdsi_status_to_errno(u32 status) +{ + switch (status) { + case SDSI_MBOX_CMD_SUCCESS: + return 0; + case SDSI_MBOX_CMD_TIMEOUT: + return -ETIMEDOUT; + default: + return -EIO; + } +} + +static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, + size_t *data_size) +{ + struct device *dev = priv->dev; + u32 total, loop, eom, status, message_size; + u64 control; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Format and send the read command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size); + writeq(control, priv->control_addr); + + /* For reads, data sizes that are larger than the mailbox size are read in packets. */ + total = 0; + loop = 0; + do { + int offset = SDSI_SIZE_MAILBOX * loop; + void __iomem *addr = priv->mbox_addr + offset; + u64 *buf = info->buffer + offset / SDSI_SIZE_CMD; + u32 packet_size; + + /* Poll on ready bit */ + ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY, + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); + if (ret) + break; + + eom = FIELD_GET(CTRL_EOM, control); + status = FIELD_GET(CTRL_STATUS, control); + packet_size = FIELD_GET(CTRL_PACKET_SIZE, control); + message_size = FIELD_GET(CTRL_MSG_SIZE, control); + + ret = sdsi_status_to_errno(status); + if (ret) + break; + + /* Only the last packet can be less than the mailbox size. */ + if (!eom && packet_size != SDSI_SIZE_MAILBOX) { + dev_err(dev, "Invalid packet size\n"); + ret = -EPROTO; + break; + } + + if (packet_size > SDSI_SIZE_MAILBOX) { + dev_err(dev, "Packet size too large\n"); + ret = -EPROTO; + break; + } + + sdsi_memcpy64_fromio(buf, addr, round_up(packet_size, SDSI_SIZE_CMD)); + + total += packet_size; + + sdsi_complete_transaction(priv); + } while (!eom && ++loop < MBOX_MAX_PACKETS); + + if (ret) { + sdsi_complete_transaction(priv); + return ret; + } + + if (!eom) { + dev_err(dev, "Exceeded read attempts\n"); + return -EPROTO; + } + + /* Message size check is only valid for multi-packet transfers */ + if (loop && total != message_size) + dev_warn(dev, "Read count %u differs from expected count %u\n", + total, message_size); + + *data_size = total; + + return 0; +} + +static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + u64 control; + u32 status; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Write rest of the payload */ + sdsi_memcpy64_toio(priv->mbox_addr + SDSI_SIZE_CMD, info->payload + 1, + info->size - SDSI_SIZE_CMD); + + /* Format and send the write command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_READ_WRITE, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size); + writeq(control, priv->control_addr); + + /* Poll on run_busy bit */ + ret = readq_poll_timeout(priv->control_addr, control, !(control & CTRL_RUN_BUSY), + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); + + if (ret) + goto release_mbox; + + status = FIELD_GET(CTRL_STATUS, control); + ret = sdsi_status_to_errno(status); + +release_mbox: + sdsi_complete_transaction(priv); + + return ret; +} + +static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + u64 control; + u32 owner; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Check mailbox is available */ + control = readq(priv->control_addr); + owner = FIELD_GET(CTRL_OWNER, control); + if (owner != MBOX_OWNER_NONE) + return -EBUSY; + + /* Write first qword of payload */ + writeq(info->payload[0], priv->mbox_addr); + + /* Check for ownership */ + ret = readq_poll_timeout(priv->control_addr, control, + FIELD_GET(CTRL_OWNER, control) & MBOX_OWNER_INBAND, + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_ACQUIRE_US); + + return ret; +} + +static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + int ret; + + lockdep_assert_held(&priv->mb_lock); + + ret = sdsi_mbox_acquire(priv, info); + if (ret) + return ret; + + return sdsi_mbox_cmd_write(priv, info); +} + +static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size) +{ + int ret; + + lockdep_assert_held(&priv->mb_lock); + + ret = sdsi_mbox_acquire(priv, info); + if (ret) + return ret; + + return sdsi_mbox_cmd_read(priv, info, data_size); +} + +static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count, + enum sdsi_command command) +{ + struct sdsi_mbox_info info; + int ret; + + if (!priv->sdsi_enabled) + return -EPERM; + + if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD)) + return -EOVERFLOW; + + /* Qword aligned message + command qword */ + info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD; + + info.payload = kzalloc(info.size, GFP_KERNEL); + if (!info.payload) + return -ENOMEM; + + /* Copy message to payload buffer */ + memcpy(info.payload, buf, count); + + /* Command is last qword of payload buffer */ + info.payload[(info.size - SDSI_SIZE_CMD) / SDSI_SIZE_CMD] = command; + + ret = mutex_lock_interruptible(&priv->mb_lock); + if (ret) + goto free_payload; + ret = sdsi_mbox_write(priv, &info); + mutex_unlock(&priv->mb_lock); + +free_payload: + kfree(info.payload); + + if (ret) + return ret; + + return count; +} + +static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + if (off) + return -ESPIPE; + + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_AKC); +} +static BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG); + +static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + if (off) + return -ESPIPE; + + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_CAP); +} +static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG); + +static long state_certificate_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + u64 command = SDSI_CMD_READ_STATE; + struct sdsi_mbox_info info; + size_t size; + int ret; + + if (!priv->sdsi_enabled) + return -EPERM; + + if (off) + return 0; + + /* Buffer for return data */ + info.buffer = kmalloc(SDSI_SIZE_READ_MSG, GFP_KERNEL); + if (!info.buffer) + return -ENOMEM; + + info.payload = &command; + info.size = sizeof(command); + + ret = mutex_lock_interruptible(&priv->mb_lock); + if (ret) + goto free_buffer; + ret = sdsi_mbox_read(priv, &info, &size); + mutex_unlock(&priv->mb_lock); + if (ret < 0) + goto free_buffer; + + if (size > count) + size = count; + + memcpy(buf, info.buffer, size); + +free_buffer: + kfree(info.buffer); + + if (ret) + return ret; + + return size; +} +static BIN_ATTR(state_certificate, 0400, state_certificate_read, NULL, SDSI_SIZE_READ_MSG); + +static ssize_t registers_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + void __iomem *addr = priv->regs_addr; + + memcpy_fromio(buf, addr + off, count); + + return count; +} +static BIN_ATTR(registers, 0400, registers_read, NULL, SDSI_SIZE_REGS); + +static struct bin_attribute *sdsi_bin_attrs[] = { + &bin_attr_registers, + &bin_attr_state_certificate, + &bin_attr_provision_akc, + &bin_attr_provision_cap, + NULL +}; + +static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdsi_priv *priv = dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%x\n", priv->guid); +} +static DEVICE_ATTR_RO(guid); + +static struct attribute *sdsi_attrs[] = { + &dev_attr_guid.attr, + NULL +}; + +static const struct attribute_group sdsi_group = { + .attrs = sdsi_attrs, + .bin_attrs = sdsi_bin_attrs, +}; +__ATTRIBUTE_GROUPS(sdsi); + +static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent, + struct disc_table *disc_table, struct resource *disc_res) +{ + u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info); + u32 size = FIELD_GET(DT_SIZE, disc_table->access_info); + u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset); + u32 offset = DT_OFFSET(disc_table->offset); + u32 features_offset; + struct resource res = {}; + + /* Starting location of SDSi MMIO region based on access type */ + switch (access_type) { + case ACCESS_TYPE_LOCAL: + if (tbir) { + dev_err(priv->dev, "Unsupported BAR index %u for access type %u\n", + tbir, access_type); + return -EINVAL; + } + + /* + * For access_type LOCAL, the base address is as follows: + * base address = end of discovery region + base offset + 1 + */ + res.start = disc_res->end + offset + 1; + break; + + case ACCESS_TYPE_BARID: + res.start = pci_resource_start(parent, tbir) + offset; + break; + + default: + dev_err(priv->dev, "Unrecognized access_type %u\n", access_type); + return -EINVAL; + } + + res.end = res.start + size * sizeof(u32) - 1; + res.flags = IORESOURCE_MEM; + + priv->control_addr = devm_ioremap_resource(priv->dev, &res); + if (IS_ERR(priv->control_addr)) + return PTR_ERR(priv->control_addr); + + priv->mbox_addr = priv->control_addr + SDSI_SIZE_CONTROL; + priv->regs_addr = priv->mbox_addr + SDSI_SIZE_MAILBOX; + + features_offset = readq(priv->regs_addr + SDSI_ENABLED_FEATURES_OFFSET); + priv->sdsi_enabled = !!(features_offset & SDSI_ENABLED); + + return 0; +} + +static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) +{ + struct intel_vsec_device *intel_cap_dev = auxdev_to_ivdev(auxdev); + struct disc_table disc_table; + struct resource *disc_res; + void __iomem *disc_addr; + struct sdsi_priv *priv; + int ret; + + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &auxdev->dev; + mutex_init(&priv->mb_lock); + auxiliary_set_drvdata(auxdev, priv); + + /* Get the SDSi discovery table */ + disc_res = &intel_cap_dev->resource[0]; + disc_addr = devm_ioremap_resource(&auxdev->dev, disc_res); + if (IS_ERR(disc_addr)) + return PTR_ERR(disc_addr); + + memcpy_fromio(&disc_table, disc_addr, DISC_TABLE_SIZE); + + priv->guid = disc_table.guid; + + /* Map the SDSi mailbox registers */ + ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res); + if (ret) + return ret; + + return 0; +} + +static const struct auxiliary_device_id sdsi_aux_id_table[] = { + { .name = "intel_vsec.sdsi" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, sdsi_aux_id_table); + +static struct auxiliary_driver sdsi_aux_driver = { + .driver = { + .dev_groups = sdsi_groups, + }, + .id_table = sdsi_aux_id_table, + .probe = sdsi_probe, + /* No remove. All resources are handled under devm */ +}; +module_auxiliary_driver(sdsi_aux_driver); + +MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>"); +MODULE_DESCRIPTION("Intel Software Defined Silicon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index c3bdd75ed690..bed436bf181f 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -32,6 +32,7 @@ #define TABLE_OFFSET_SHIFT 3 static DEFINE_IDA(intel_vsec_ida); +static DEFINE_IDA(intel_vsec_sdsi_ida); /** * struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers. @@ -63,12 +64,14 @@ enum intel_vsec_id { VSEC_ID_TELEMETRY = 2, VSEC_ID_WATCHER = 3, VSEC_ID_CRASHLOG = 4, + VSEC_ID_SDSI = 65, }; static enum intel_vsec_id intel_vsec_allow_list[] = { VSEC_ID_TELEMETRY, VSEC_ID_WATCHER, VSEC_ID_CRASHLOG, + VSEC_ID_SDSI, }; static const char *intel_vsec_name(enum intel_vsec_id id) @@ -83,6 +86,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id) case VSEC_ID_CRASHLOG: return "crashlog"; + case VSEC_ID_SDSI: + return "sdsi"; + default: return NULL; } @@ -211,7 +217,11 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he intel_vsec_dev->resource = res; intel_vsec_dev->num_resources = header->num_entries; intel_vsec_dev->quirks = quirks; - intel_vsec_dev->ida = &intel_vsec_ida; + + if (header->id == VSEC_ID_SDSI) + intel_vsec_dev->ida = &intel_vsec_sdsi_ida; + else + intel_vsec_dev->ida = &intel_vsec_ida; return intel_vsec_add_aux(pdev, intel_vsec_dev, intel_vsec_name(header->id)); }