diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/Makefile --- a/linux-2.6-xen-sparse/drivers/xen/pciback/Makefile Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/Makefile Wed Apr 26 19:40:00 2006 @@ -1,7 +1,10 @@ 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 diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/conf_space.c --- a/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space.c Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space.c Wed Apr 26 19:40:00 2006 @@ -17,10 +17,10 @@ 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); \ } @@ -175,8 +175,8 @@ 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)) { @@ -222,8 +222,8 @@ 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)) { @@ -239,60 +239,99 @@ 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); - } -} - -void pciback_config_free(struct pci_dev *dev) + field->reset(dev, OFFSET(cfg_entry), cfg_entry->data); + } +} + +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); @@ -307,9 +346,10 @@ 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); @@ -319,6 +359,8 @@ 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: @@ -332,14 +374,30 @@ * 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; +} diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/conf_space.h --- a/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space.h Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space.h Wed Apr 26 19:40:00 2006 @@ -8,7 +8,9 @@ #define __XEN_PCIBACK_CONF_SPACE_H__ #include +#include +/* 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); @@ -55,13 +57,25 @@ 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) { @@ -74,11 +88,18 @@ 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, @@ -94,4 +115,9 @@ 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__ */ diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_header.c --- a/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_header.c Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_header.c Wed Apr 26 19:40:00 2006 @@ -169,29 +169,61 @@ 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) \ @@ -216,7 +248,7 @@ .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), @@ -226,16 +258,16 @@ 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) diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/pci_stub.c --- a/linux-2.6-xen-sparse/drivers/xen/pciback/pci_stub.c Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/pci_stub.c Wed Apr 26 19:40:00 2006 @@ -76,7 +76,7 @@ /* 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); @@ -180,7 +180,7 @@ * (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; @@ -235,7 +235,7 @@ * 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; @@ -243,7 +243,7 @@ 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; @@ -268,7 +268,7 @@ return 0; config_release: - pciback_config_free(dev); + pciback_config_free_dev(dev); out: pci_set_drvdata(dev, NULL); @@ -324,40 +324,31 @@ { 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_copy) { + 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 (!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); @@ -663,9 +654,13 @@ static int __init pciback_init(void) { + int err; + + err = pciback_config_init(); + if (err) + return err; + #ifdef MODULE - int err; - err = pcistub_init(); if (err < 0) return err; diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/pciback.h --- a/linux-2.6-xen-sparse/drivers/xen/pciback/pciback.h Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/pciback.h Wed Apr 26 19:40:00 2006 @@ -44,6 +44,7 @@ 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 */ @@ -58,9 +59,10 @@ 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); diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/pciback_ops.c --- a/linux-2.6-xen-sparse/drivers/xen/pciback/pciback_ops.c Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/pciback_ops.c Wed Apr 26 19:40:00 2006 @@ -36,8 +36,6 @@ dev->is_busmaster = 0; } } - - pciback_config_reset(dev); } static inline void test_and_schedule_op(struct pciback_device *pdev) diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_capability.c --- /dev/null Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_capability.c Wed Apr 26 19:40:00 2006 @@ -0,0 +1,71 @@ +/* + * PCI Backend - Handles the virtual fields found on the capability lists + * in the configuration space. + * + * Author: Ryan Wilson + */ + +#include +#include +#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; +} diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_capability.h --- /dev/null Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_capability.h Wed Apr 26 19:40:00 2006 @@ -0,0 +1,23 @@ +/* + * PCI Backend - Data structures for special overlays for structures on + * the capability list. + * + * Author: Ryan Wilson + */ + +#ifndef __PCIBACK_CONFIG_CAPABILITY_H__ +#define __PCIBACK_CONFIG_CAPABILITY_H__ + +#include +#include + +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 diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_capability_pm.c --- /dev/null Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_capability_pm.c Wed Apr 26 19:40:00 2006 @@ -0,0 +1,113 @@ +/* + * PCI Backend - Configuration space overlay for power management + * + * Author: Ryan Wilson + */ + +#include +#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, +}; diff -r 9df603eff58a -r ef86ff3c8514 linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_capability_vpd.c --- /dev/null Wed Apr 26 09:43:16 2006 +++ b/linux-2.6-xen-sparse/drivers/xen/pciback/conf_space_capability_vpd.c Wed Apr 26 19:40:00 2006 @@ -0,0 +1,42 @@ +/* + * PCI Backend - Configuration space overlay for Vital Product Data + * + * Author: Ryan Wilson + */ + +#include +#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, +};