obj-$(CONFIG_XEN_PCIDEV_BACKEND) += pciback.o
pciback-y := pci_stub.o pciback_ops.o xenbus.o
-pciback-y += conf_space.o conf_space_header.o
+pciback-y += conf_space.o conf_space_header.o \
+ conf_space_capability.o \
+ conf_space_capability_vpd.o \
+ conf_space_capability_pm.o
pciback-$(CONFIG_XEN_PCIDEV_BACKEND_VPCI) += vpci.o
pciback-$(CONFIG_XEN_PCIDEV_BACKEND_PASS) += passthrough.o
static int permissive = 0;
module_param(permissive, bool, 0644);
-#define DEFINE_PCI_CONFIG(op,size,type) \
-int pciback_##op##_config_##size \
+#define DEFINE_PCI_CONFIG(op,size,type) \
+int pciback_##op##_config_##size \
(struct pci_dev *dev, int offset, type value, void *data) \
-{ \
+{ \
return pci_##op##_config_##size (dev, offset, value); \
}
req_start = offset;
req_end = offset + size;
- field_start = field->offset;
- field_end = field->offset + field->size;
+ field_start = OFFSET(cfg_entry);
+ field_end = OFFSET(cfg_entry) + field->size;
if ((req_start >= field_start && req_start < field_end)
|| (req_end > field_start && req_end <= field_end)) {
req_start = offset;
req_end = offset + size;
- field_start = field->offset;
- field_end = field->offset + field->size;
+ field_start = OFFSET(cfg_entry);
+ field_end = OFFSET(cfg_entry) + field->size;
if ((req_start >= field_start && req_start < field_end)
|| (req_end > field_start && req_end <= field_end)) {
err = conf_space_write(dev, cfg_entry, field_start,
tmp_val);
+
+ /* handled is set true here, but not every byte
+ * may have been written! Properly detecting if
+ * every byte is handled is unnecessary as the
+ * flag is used to detect devices that need
+ * special helpers to work correctly.
+ */
handled = 1;
}
}
- if (!handled && !err && permissive) {
- switch (size) {
- case 1:
- err = pci_write_config_byte(dev, offset, (u8)value);
- break;
- case 2:
- err = pci_write_config_word(dev, offset, (u16)value);
- break;
- case 4:
- err = pci_write_config_dword(dev, offset, (u32)value);
- break;
+ if (!handled && !err) {
+ /* By default, anything not specificially handled above is
+ * read-only. The permissive flag changes this behavior so
+ * that anything not specifically handled above is writable.
+ * This means that some fields may still be read-only because
+ * they have entries in the config_field list that intercept
+ * the write and do nothing. */
+ if (permissive) {
+ switch (size) {
+ case 1:
+ err = pci_write_config_byte(dev, offset,
+ (u8)value);
+ break;
+ case 2:
+ err = pci_write_config_word(dev, offset,
+ (u16)value);
+ break;
+ case 4:
+ err = pci_write_config_dword(dev, offset,
+ (u32)value);
+ break;
+ }
+ } else if (!dev_data->warned_on_write) {
+ dev_data->warned_on_write = 1;
+ dev_warn(&dev->dev, "Driver wrote to a read-only "
+ "configuration space field!\n");
+ dev_warn(&dev->dev, "Write at offset 0x%x size %d\n",
+ offset, size);
+ dev_warn(&dev->dev, "This may be harmless, but if\n");
+ dev_warn(&dev->dev, "you have problems with your "
+ "device:\n");
+ dev_warn(&dev->dev, "1) see the permissive "
+ "attribute in sysfs.\n");
+ dev_warn(&dev->dev, "2) report problems to the "
+ "xen-devel mailing list along\n");
+ dev_warn(&dev->dev, " with details of your device "
+ "obtained from lspci.\n");
}
}
return pcibios_err_to_errno(err);
}
-void pciback_config_reset(struct pci_dev *dev)
+void pciback_config_reset_dev(struct pci_dev *dev)
{
struct pciback_dev_data *dev_data = pci_get_drvdata(dev);
struct config_field_entry *cfg_entry;
struct config_field *field;
+ dev_dbg(&dev->dev, "resetting virtual configuration space\n");
+
list_for_each_entry(cfg_entry, &dev_data->config_fields, list) {
field = cfg_entry->field;
if (field->reset)
- field->reset(dev, field->offset, cfg_entry->data);
+ field->reset(dev, OFFSET(cfg_entry), cfg_entry->data);
}
}
-void pciback_config_free(struct pci_dev *dev)
+void pciback_config_free_dev(struct pci_dev *dev)
{
struct pciback_dev_data *dev_data = pci_get_drvdata(dev);
struct config_field_entry *cfg_entry, *t;
struct config_field *field;
+ dev_dbg(&dev->dev, "free-ing virtual configuration space fields\n");
+
list_for_each_entry_safe(cfg_entry, t, &dev_data->config_fields, list) {
list_del(&cfg_entry->list);
field = cfg_entry->field;
if (field->release)
- field->release(dev, field->offset, cfg_entry->data);
+ field->release(dev, OFFSET(cfg_entry), cfg_entry->data);
kfree(cfg_entry);
}
}
-int pciback_config_add_field(struct pci_dev *dev, struct config_field *field)
+int pciback_config_add_field_offset(struct pci_dev *dev,
+ struct config_field *field,
+ unsigned int offset)
{
int err = 0;
struct pciback_dev_data *dev_data = pci_get_drvdata(dev);
cfg_entry->data = NULL;
cfg_entry->field = field;
+ cfg_entry->base_offset = offset;
if (field->init) {
- tmp = field->init(dev, field->offset);
+ tmp = field->init(dev, OFFSET(cfg_entry));
if (IS_ERR(tmp)) {
err = PTR_ERR(tmp);
cfg_entry->data = tmp;
}
+ dev_dbg(&dev->dev, "added config field at offset 0x%02x\n",
+ OFFSET(cfg_entry));
list_add_tail(&cfg_entry->list, &dev_data->config_fields);
out:
* certain registers (like the base address registers (BARs) so that we can
* keep the client from manipulating them directly.
*/
-int pciback_config_init(struct pci_dev *dev)
+int pciback_config_init_dev(struct pci_dev *dev)
{
int err = 0;
struct pciback_dev_data *dev_data = pci_get_drvdata(dev);
+ dev_dbg(&dev->dev, "initializing virtual configuration space\n");
+
INIT_LIST_HEAD(&dev_data->config_fields);
err = pciback_config_header_add_fields(dev);
+ if (err)
+ goto out;
+
+ err = pciback_config_capability_add_fields(dev);
+
+ out:
+ return err;
+}
+
+int pciback_config_init(void)
+{
+ int err;
+
+ err = pciback_config_capability_init();
return err;
}
#define __XEN_PCIBACK_CONF_SPACE_H__
#include <linux/list.h>
+#include <linux/err.h>
+/* conf_field_init can return an errno in a ptr with ERR_PTR() */
typedef void *(*conf_field_init) (struct pci_dev * dev, int offset);
typedef void (*conf_field_reset) (struct pci_dev * dev, int offset, void *data);
typedef void (*conf_field_free) (struct pci_dev * dev, int offset, void *data);
struct config_field_entry {
struct list_head list;
struct config_field *field;
+ unsigned int base_offset;
void *data;
};
+#define OFFSET(cfg_entry) ((cfg_entry)->base_offset+(cfg_entry)->field->offset)
+
/* Add fields to a device - the add_fields macro expects to get a pointer to
* the first entry in an array (of which the ending is marked by size==0)
*/
-int pciback_config_add_field(struct pci_dev *dev, struct config_field *field);
+int pciback_config_add_field_offset(struct pci_dev *dev,
+ struct config_field *field,
+ unsigned int offset);
+
+static inline int pciback_config_add_field(struct pci_dev *dev,
+ struct config_field *field)
+{
+ return pciback_config_add_field_offset(dev, field, 0);
+}
+
static inline int pciback_config_add_fields(struct pci_dev *dev,
struct config_field *field)
{
return err;
}
-/* Initializers which add fields to the virtual configuration space
- * ** We could add initializers to allow a guest domain to touch
- * the capability lists (for power management, the AGP bridge, etc.)
- */
-int pciback_config_header_add_fields(struct pci_dev *dev);
+static inline int pciback_config_add_fields_offset(struct pci_dev *dev,
+ struct config_field *field,
+ unsigned int offset)
+{
+ int i, err = 0;
+ for (i = 0; field[i].size != 0; i++) {
+ err = pciback_config_add_field_offset(dev, &field[i], offset);
+ if (err)
+ break;
+ }
+ return err;
+}
/* Read/Write the real configuration space */
int pciback_read_config_byte(struct pci_dev *dev, int offset, u8 * value,
int pciback_write_config_dword(struct pci_dev *dev, int offset, u32 value,
void *data);
+int pciback_config_capability_init(void);
+
+int pciback_config_header_add_fields(struct pci_dev *dev);
+int pciback_config_capability_add_fields(struct pci_dev *dev);
+
#endif /* __XEN_PCIBACK_CONF_SPACE_H__ */
--- /dev/null
+/*
+ * PCI Backend - Handles the virtual fields found on the capability lists
+ * in the configuration space.
+ *
+ * Author: Ryan Wilson <hap9@epoch.ncsc.mil>
+ */
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include "pciback.h"
+#include "conf_space.h"
+#include "conf_space_capability.h"
+
+static LIST_HEAD(capabilities);
+
+static struct config_field caplist_header[] = {
+ {
+ .offset = PCI_CAP_LIST_ID,
+ .size = 2, /* encompass PCI_CAP_LIST_ID & PCI_CAP_LIST_NEXT */
+ .u.w.read = pciback_read_config_word,
+ .u.w.write = NULL,
+ },
+ {
+ .size = 0,
+ },
+};
+
+static inline void register_capability(struct pciback_config_capability *cap)
+{
+ list_add_tail(&cap->cap_list, &capabilities);
+}
+
+int pciback_config_capability_add_fields(struct pci_dev *dev)
+{
+ int err = 0;
+ struct pciback_config_capability *cap;
+ int cap_offset;
+
+ list_for_each_entry(cap, &capabilities, cap_list) {
+ cap_offset = pci_find_capability(dev, cap->capability);
+ if (cap_offset) {
+ dev_dbg(&dev->dev, "Found capability 0x%x at 0x%x\n",
+ cap->capability, cap_offset);
+
+ err = pciback_config_add_fields_offset(dev,
+ caplist_header,
+ cap_offset);
+ if (err)
+ goto out;
+ err = pciback_config_add_fields_offset(dev,
+ cap->fields,
+ cap_offset);
+ if (err)
+ goto out;
+ }
+ }
+
+ out:
+ return err;
+}
+
+extern struct pciback_config_capability pciback_config_capability_vpd;
+extern struct pciback_config_capability pciback_config_capability_pm;
+
+int pciback_config_capability_init(void)
+{
+ register_capability(&pciback_config_capability_vpd);
+ register_capability(&pciback_config_capability_pm);
+
+ return 0;
+}
--- /dev/null
+/*
+ * PCI Backend - Data structures for special overlays for structures on
+ * the capability list.
+ *
+ * Author: Ryan Wilson <hap9@epoch.ncsc.mil>
+ */
+
+#ifndef __PCIBACK_CONFIG_CAPABILITY_H__
+#define __PCIBACK_CONFIG_CAPABILITY_H__
+
+#include <linux/pci.h>
+#include <linux/list.h>
+
+struct pciback_config_capability {
+ struct list_head cap_list;
+
+ int capability;
+
+ /* If the device has the capability found above, add these fields */
+ struct config_field *fields;
+};
+
+#endif
--- /dev/null
+/*
+ * PCI Backend - Configuration space overlay for power management
+ *
+ * Author: Ryan Wilson <hap9@epoch.ncsc.mil>
+ */
+
+#include <linux/pci.h>
+#include "conf_space.h"
+#include "conf_space_capability.h"
+
+static int pm_caps_read(struct pci_dev *dev, int offset, u16 *value,
+ void *data)
+{
+ int err;
+ u16 real_value;
+
+ err = pci_read_config_word(dev, offset, &real_value);
+ if (err)
+ goto out;
+
+ *value = real_value & ~PCI_PM_CAP_PME_MASK;
+
+ out:
+ return err;
+}
+
+/* PM_OK_BITS specifies the bits that the driver domain is allowed to change.
+ * Can't allow driver domain to enable PMEs - they're shared */
+#define PM_OK_BITS (PCI_PM_CTRL_PME_STATUS|PCI_PM_CTRL_DATA_SEL_MASK)
+
+static int pm_ctrl_write(struct pci_dev *dev, int offset, u16 new_value,
+ void *data)
+{
+ int err;
+ u16 cur_value;
+ pci_power_t new_state;
+
+ /* Handle setting power state separately */
+ new_state = (pci_power_t)(new_value & PCI_PM_CTRL_STATE_MASK);
+
+ err = pci_read_config_word(dev, offset, &cur_value);
+ if (err)
+ goto out;
+
+ new_value &= PM_OK_BITS;
+ if ((cur_value & PM_OK_BITS) != new_value) {
+ new_value = (cur_value & ~PM_OK_BITS) | new_value;
+ err = pci_write_config_word(dev, offset, new_value);
+ if (err)
+ goto out;
+ }
+
+ /* Let pci core handle the power management change */
+ dev_dbg(&dev->dev, "set power state to %x\n", new_state);
+ err = pci_set_power_state(dev, new_state);
+ if (err)
+ err = PCIBIOS_SET_FAILED;
+
+ out:
+ return err;
+}
+
+/* Ensure PMEs are disabled */
+static void *pm_ctrl_init(struct pci_dev *dev, int offset)
+{
+ int err;
+ u16 value;
+
+ err = pci_read_config_word(dev, offset, &value);
+ if (err)
+ goto out;
+
+ if (value & PCI_PM_CTRL_PME_ENABLE) {
+ value &= ~PCI_PM_CTRL_PME_ENABLE;
+ err = pci_write_config_word(dev, offset, value);
+ }
+
+ out:
+ return ERR_PTR(err);
+}
+
+static struct config_field caplist_pm[] = {
+ {
+ .offset = PCI_PM_PMC,
+ .size = 2,
+ .u.w.read = pm_caps_read,
+ },
+ {
+ .offset = PCI_PM_CTRL,
+ .size = 2,
+ .init = pm_ctrl_init,
+ .u.w.read = pciback_read_config_word,
+ .u.w.write = pm_ctrl_write,
+ },
+ {
+ .offset = PCI_PM_PPB_EXTENSIONS,
+ .size = 1,
+ .u.b.read = pciback_read_config_byte,
+ },
+ {
+ .offset = PCI_PM_DATA_REGISTER,
+ .size = 1,
+ .u.b.read = pciback_read_config_byte,
+ },
+ {
+ .size = 0,
+ },
+};
+
+struct pciback_config_capability pciback_config_capability_pm = {
+ .capability = PCI_CAP_ID_PM,
+ .fields = caplist_pm,
+};
--- /dev/null
+/*
+ * PCI Backend - Configuration space overlay for Vital Product Data
+ *
+ * Author: Ryan Wilson <hap9@epoch.ncsc.mil>
+ */
+
+#include <linux/pci.h>
+#include "conf_space.h"
+#include "conf_space_capability.h"
+
+static int vpd_address_write(struct pci_dev *dev, int offset, u16 value,
+ void *data)
+{
+ /* Disallow writes to the vital product data */
+ if (value & PCI_VPD_ADDR_F)
+ return PCIBIOS_SET_FAILED;
+ else
+ return pci_write_config_word(dev, offset, value);
+}
+
+static struct config_field caplist_vpd[] = {
+ {
+ .offset = PCI_VPD_ADDR,
+ .size = 2,
+ .u.w.read = pciback_read_config_word,
+ .u.w.write = vpd_address_write,
+ },
+ {
+ .offset = PCI_VPD_DATA,
+ .size = 4,
+ .u.dw.read = pciback_read_config_dword,
+ .u.dw.write = NULL,
+ },
+ {
+ .size = 0,
+ },
+};
+
+struct pciback_config_capability pciback_config_capability_vpd = {
+ .capability = PCI_CAP_ID_VPD,
+ .fields = caplist_vpd,
+};
return 0;
}
-struct config_field header_common[] = {
+static int bist_write(struct pci_dev *dev, int offset, u8 value, void *data)
+{
+ u8 cur_value;
+ int err;
+
+ err = pci_read_config_byte(dev, offset, &cur_value);
+ if (err)
+ goto out;
+
+ if ((cur_value & ~PCI_BIST_START) == (value & ~PCI_BIST_START)
+ || value == PCI_BIST_START)
+ err = pci_write_config_byte(dev, offset, value);
+
+ out:
+ return err;
+}
+
+static struct config_field header_common[] = {
{
.offset = PCI_COMMAND,
.size = 2,
.u.w.read = pciback_read_config_word,
.u.w.write = command_write,
- },
+ },
{
.offset = PCI_INTERRUPT_LINE,
.size = 1,
.u.b.read = interrupt_read,
- .u.b.write = NULL,
- },
+ },
+ {
+ .offset = PCI_INTERRUPT_PIN,
+ .size = 1,
+ .u.b.read = pciback_read_config_byte,
+ },
{
/* Any side effects of letting driver domain control cache line? */
.offset = PCI_CACHE_LINE_SIZE,
.size = 1,
.u.b.read = pciback_read_config_byte,
.u.b.write = pciback_write_config_byte,
- },
+ },
+ {
+ .offset = PCI_LATENCY_TIMER,
+ .size = 1,
+ .u.b.read = pciback_read_config_byte,
+ },
+ {
+ .offset = PCI_BIST,
+ .size = 1,
+ .u.b.read = pciback_read_config_byte,
+ .u.b.write = bist_write,
+ },
{
.size = 0,
- },
+ },
};
#define CFG_FIELD_BAR(reg_offset) \
.u.dw.write = rom_write, \
}
-struct config_field header_0[] = {
+static struct config_field header_0[] = {
CFG_FIELD_BAR(PCI_BASE_ADDRESS_0),
CFG_FIELD_BAR(PCI_BASE_ADDRESS_1),
CFG_FIELD_BAR(PCI_BASE_ADDRESS_2),
CFG_FIELD_ROM(PCI_ROM_ADDRESS),
{
.size = 0,
- },
+ },
};
-struct config_field header_1[] = {
+static struct config_field header_1[] = {
CFG_FIELD_BAR(PCI_BASE_ADDRESS_0),
CFG_FIELD_BAR(PCI_BASE_ADDRESS_1),
CFG_FIELD_ROM(PCI_ROM_ADDRESS1),
{
.size = 0,
- },
+ },
};
int pciback_config_header_add_fields(struct pci_dev *dev)
/* Clean-up the device */
pciback_reset_device(psdev->dev);
- pciback_config_free(psdev->dev);
+ pciback_config_free_dev(psdev->dev);
kfree(pci_get_drvdata(psdev->dev));
pci_set_drvdata(psdev->dev, NULL);
* (so it's ready for the next domain)
*/
pciback_reset_device(found_psdev->dev);
- pciback_config_reset(found_psdev->dev);
+ pciback_config_reset_dev(found_psdev->dev);
spin_lock_irqsave(&found_psdev->lock, flags);
found_psdev->pdev = NULL;
* would need to be called somewhere to free the memory allocated
* here and then to call kfree(pci_get_drvdata(psdev->dev)).
*/
- dev_data = kmalloc(sizeof(*dev_data), GFP_ATOMIC);
+ dev_data = kzalloc(sizeof(*dev_data), GFP_ATOMIC);
if (!dev_data) {
err = -ENOMEM;
goto out;
pci_set_drvdata(dev, dev_data);
dev_dbg(&dev->dev, "initializing config\n");
- err = pciback_config_init(dev);
+ err = pciback_config_init_dev(dev);
if (err)
goto out;
return 0;
config_release:
- pciback_config_free(dev);
+ pciback_config_free_dev(dev);
out:
pci_set_drvdata(dev, NULL);
{
struct pcistub_device *psdev;
unsigned long flags;
- int initialize_devices_copy;
int err = 0;
psdev = pcistub_device_alloc(dev);
if (!psdev)
return -ENOMEM;
- /* initialize_devices has to be accessed under a spin lock. But since
- * it can only change from 0 -> 1, if it's already 1, we don't have to
- * worry about it changing. That's why we can take a *copy* of
- * initialize_devices and wait till we're outside of the lock to
- * check if it's 1 (don't ever check if it's 0 outside of the lock)
- */
spin_lock_irqsave(&pcistub_devices_lock, flags);
- initialize_devices_copy = initialize_devices;
+ if (initialize_devices) {
+ spin_unlock_irqrestore(&pcistub_devices_lock, flags);
+
+ /* don't want irqs disabled when calling pcistub_init_device */
+ err = pcistub_init_device(psdev->dev);
+
+ spin_lock_irqsave(&pcistub_devices_lock, flags);
- if (!initialize_devices_copy) {
+ if (!err)
+ list_add(&psdev->dev_list, &pcistub_devices);
+ } else {
dev_dbg(&dev->dev, "deferring initialization\n");
list_add(&psdev->dev_list, &seized_devices);
}
spin_unlock_irqrestore(&pcistub_devices_lock, flags);
- if (initialize_devices_copy) {
- /* don't want irqs disabled when calling pcistub_init_device */
- err = pcistub_init_device(psdev->dev);
- if (err)
- goto out;
-
- list_add(&psdev->dev_list, &pcistub_devices);
- }
-
- out:
if (err)
pcistub_device_put(psdev);
static int __init pciback_init(void)
{
-#ifdef MODULE
int err;
+ err = pciback_config_init();
+ if (err)
+ return err;
+
+#ifdef MODULE
err = pcistub_init();
if (err < 0)
return err;
struct pciback_dev_data {
struct list_head config_fields;
+ int warned_on_write;
};
/* Get/Put PCI Devices that are hidden from the PCI Backend Domain */
void pciback_reset_device(struct pci_dev *pdev);
/* Access a virtual configuration space for a PCI device */
-int pciback_config_init(struct pci_dev *dev);
-void pciback_config_reset(struct pci_dev *dev);
-void pciback_config_free(struct pci_dev *dev);
+int pciback_config_init(void);
+int pciback_config_init_dev(struct pci_dev *dev);
+void pciback_config_reset_dev(struct pci_dev *dev);
+void pciback_config_free_dev(struct pci_dev *dev);
int pciback_config_read(struct pci_dev *dev, int offset, int size,
u32 * ret_val);
int pciback_config_write(struct pci_dev *dev, int offset, int size, u32 value);
dev->is_busmaster = 0;
}
}
-
- pciback_config_reset(dev);
}
static inline void test_and_schedule_op(struct pciback_device *pdev)