[PATCH] PCI: vmd: Expose oob management window to users

From: Jon Derrick
Date: Mon Sep 16 2019 - 16:52:46 EST


Some VMD devices provide a management window within MEMBAR2 that is used
to communicate out-of-band with child devices. This patch creates a
binary file for interacting with the interface.

OOB Reads/Writes are bounds-checked by sysfs_fs_bin_{read,write}

Signed-off-by: Jon Derrick <jonathan.derrick@xxxxxxxxx>
---
Depends on https://lore.kernel.org/linux-pci/20190916135435.5017-1-jonathan.derrick@xxxxxxxxx/T/#t

drivers/pci/controller/vmd.c | 128 ++++++++++++++++++++++++++++++++---
1 file changed, 117 insertions(+), 11 deletions(-)

diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c
index a35d3f3996d7..b13954cf9c96 100644
--- a/drivers/pci/controller/vmd.c
+++ b/drivers/pci/controller/vmd.c
@@ -33,6 +33,8 @@

#define MB2_SHADOW_OFFSET 0x2000
#define MB2_SHADOW_SIZE 16
+#define MB2_OOB_WINDOW_OFFSET 0x2010
+#define MB2_OOB_WINDOW_SIZE 128

enum vmd_features {
/*
@@ -47,6 +49,12 @@ enum vmd_features {
* bus numbering
*/
VMD_FEAT_HAS_BUS_RESTRICTIONS = (1 << 1),
+
+ /*
+ * Device may provide an out-of-band management interface through a
+ * read/write window
+ */
+ VMD_FEAT_HAS_OOB_WINDOW = (1 << 2),
};

/*
@@ -101,6 +109,10 @@ struct vmd_dev {

struct dma_map_ops dma_ops;
struct dma_domain dma_domain;
+
+ spinlock_t oob_lock;
+ char __iomem *oob_addr;
+ struct bin_attribute *oob_attr;
};

static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus)
@@ -543,6 +555,68 @@ static void vmd_detach_resources(struct vmd_dev *vmd)
vmd->dev->resource[VMD_MEMBAR2].child = NULL;
}

+static ssize_t vmd_oob_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct vmd_dev *vmd = attr->private;
+ unsigned long flags;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+
+ spin_lock_irqsave(&vmd->oob_lock, flags);
+ memcpy_fromio(&buf[off], &vmd->oob_addr[off], count);
+ spin_unlock_irqrestore(&vmd->oob_lock, flags);
+
+ return count;
+}
+
+static ssize_t vmd_oob_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct vmd_dev *vmd = attr->private;
+ unsigned long flags;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+
+ spin_lock_irqsave(&vmd->oob_lock, flags);
+ memcpy_toio(&vmd->oob_addr[off], &buf[off], count);
+ spin_unlock_irqrestore(&vmd->oob_lock, flags);
+
+ return count;
+}
+
+static int vmd_create_oob_file(struct vmd_dev *vmd)
+{
+ struct pci_dev *dev = vmd->dev;
+ struct bin_attribute *oob_attr;
+
+ oob_attr = devm_kzalloc(&vmd->dev->dev, sizeof(*oob_attr), GFP_ATOMIC);
+ if (!oob_attr)
+ return -ENOMEM;
+
+ spin_lock_init(&vmd->oob_lock);
+ sysfs_bin_attr_init(oob_attr);
+ vmd->oob_attr = oob_attr;
+ oob_attr->attr.name = "oob";
+ oob_attr->attr.mode = S_IRUSR | S_IWUSR;
+ oob_attr->size = MB2_OOB_WINDOW_SIZE;
+ oob_attr->read = vmd_oob_read;
+ oob_attr->write = vmd_oob_write;
+ oob_attr->private = (void *)vmd;
+
+ return sysfs_create_bin_file(&dev->dev.kobj, oob_attr);
+}
+
+static void vmd_destroy_oob_file(struct vmd_dev *vmd)
+{
+ if (vmd->oob_attr)
+ sysfs_remove_bin_file(&vmd->dev->dev.kobj, vmd->oob_attr);
+}
+
/*
* VMD domains start at 0x10000 to not clash with ACPI _SEG domains.
* Per ACPI r6.0, sec 6.5.6, _SEG returns an integer, of which the lower
@@ -570,6 +644,7 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
resource_size_t offset[2] = {0};
resource_size_t membar2_offset = 0x2000;
struct pci_bus *child;
+ int ret;

/*
* Shadow registers may exist in certain VMD device ids which allow
@@ -579,7 +654,6 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
*/
if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) {
u32 vmlock;
- int ret;

membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE;
ret = pci_read_config_dword(vmd->dev, PCI_REG_VMLOCK, &vmlock);
@@ -614,6 +688,24 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
vmd->busn_start = 128;
}

+ /*
+ * Certain VMD devices provide a window for communicating with child
+ * devices through a management interface
+ */
+ if (features & VMD_FEAT_HAS_OOB_WINDOW) {
+ membar2_offset = MB2_OOB_WINDOW_OFFSET + MB2_OOB_WINDOW_SIZE;
+ vmd->oob_addr = devm_ioremap(&vmd->dev->dev,
+ vmd->dev->resource[VMD_MEMBAR2].start +
+ MB2_OOB_WINDOW_OFFSET,
+ MB2_OOB_WINDOW_SIZE);
+ if (!vmd->oob_addr)
+ return -ENOMEM;
+
+ ret = vmd_create_oob_file(vmd);
+ if (ret)
+ return ret;
+ }
+
res = &vmd->dev->resource[VMD_CFGBAR];
vmd->resources[0] = (struct resource) {
.name = "VMD CFGBAR",
@@ -667,20 +759,26 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)

sd->vmd_domain = true;
sd->domain = vmd_find_free_domain();
- if (sd->domain < 0)
- return sd->domain;
+ if (sd->domain < 0) {
+ ret = sd->domain;
+ goto destroy_oob_file;
+ }

sd->node = pcibus_to_node(vmd->dev->bus);

fn = irq_domain_alloc_named_id_fwnode("VMD-MSI", vmd->sysdata.domain);
- if (!fn)
- return -ENODEV;
+ if (!fn) {
+ ret = -ENODEV;
+ goto destroy_oob_file;
+ }

vmd->irq_domain = pci_msi_create_irq_domain(fn, &vmd_msi_domain_info,
x86_vector_domain);
irq_domain_free_fwnode(fn);
- if (!vmd->irq_domain)
- return -ENODEV;
+ if (!vmd->irq_domain) {
+ ret = -ENODEV;
+ goto destroy_oob_file;
+ }

pci_add_resource(&resources, &vmd->resources[0]);
pci_add_resource_offset(&resources, &vmd->resources[1], offset[0]);
@@ -689,9 +787,8 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
vmd->bus = pci_create_root_bus(&vmd->dev->dev, vmd->busn_start,
&vmd_ops, sd, &resources);
if (!vmd->bus) {
- pci_free_resource_list(&resources);
- irq_domain_remove(vmd->irq_domain);
- return -ENODEV;
+ ret = -ENODEV;
+ goto remove_irq_domain;
}

vmd_attach_resources(vmd);
@@ -714,6 +811,13 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
WARN(sysfs_create_link(&vmd->dev->dev.kobj, &vmd->bus->dev.kobj,
"domain"), "Can't create symlink to domain\n");
return 0;
+
+remove_irq_domain:
+ pci_free_resource_list(&resources);
+ irq_domain_remove(vmd->irq_domain);
+destroy_oob_file:
+ vmd_destroy_oob_file(vmd);
+ return ret;
}

static irqreturn_t vmd_irq(int irq, void *data)
@@ -807,6 +911,7 @@ static void vmd_remove(struct pci_dev *dev)
struct vmd_dev *vmd = pci_get_drvdata(dev);

sysfs_remove_link(&vmd->dev->dev.kobj, "domain");
+ vmd_destroy_oob_file(vmd);
pci_stop_root_bus(vmd->bus);
pci_remove_root_bus(vmd->bus);
vmd_cleanup_srcu(vmd);
@@ -853,7 +958,8 @@ static const struct pci_device_id vmd_ids[] = {
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_VMD_201D),},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_VMD_28C0),
.driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW |
- VMD_FEAT_HAS_BUS_RESTRICTIONS,},
+ VMD_FEAT_HAS_BUS_RESTRICTIONS |
+ VMD_FEAT_HAS_OOB_WINDOW,},
{0,}
};
MODULE_DEVICE_TABLE(pci, vmd_ids);
--
2.20.1