Commit 023fdaae authored by Jean-Philippe Brucker's avatar Jean-Philippe Brucker Committed by Will Deacon
Browse files

pci: add config operations callbacks on the PCI header



When implementing PCI device passthrough, we will need to forward config
accesses from a guest to the VFIO driver. Add a private cfg_ops structure
to the PCI header, and use it in the PCI config access functions.

A read from the guest first calls into the device's cfg_ops.read, to let
the backend update the local header before filling the guest register.
Same happens for a write, we let the backend perform the write and replace
the guest-provided register with whatever sticks, before updating the local
header.

Try to untangle the PCI config access logic while we're at it.
Reviewed-by: default avatarPunit Agrawal <punit.agrawal@arm.com>
Signed-off-by: default avatarWill Deacon <will.deacon@arm.com>
[JPB: moved to a separate patch]
Signed-off-by: default avatarJean-Philippe Brucker <jean-philippe.brucker@arm.com>
Signed-off-by: default avatarWill Deacon <will.deacon@arm.com>
parent 9a006940
......@@ -57,33 +57,55 @@ struct msix_cap {
u32 pba_offset;
};
#define PCI_BAR_OFFSET(b) (offsetof(struct pci_device_header, bar[b]))
#define PCI_DEV_CFG_SIZE 256
#define PCI_DEV_CFG_MASK (PCI_DEV_CFG_SIZE - 1)
struct pci_device_header;
struct pci_config_operations {
void (*write)(struct kvm *kvm, struct pci_device_header *pci_hdr,
u8 offset, void *data, int sz);
void (*read)(struct kvm *kvm, struct pci_device_header *pci_hdr,
u8 offset, void *data, int sz);
};
struct pci_device_header {
u16 vendor_id;
u16 device_id;
u16 command;
u16 status;
u8 revision_id;
u8 class[3];
u8 cacheline_size;
u8 latency_timer;
u8 header_type;
u8 bist;
u32 bar[6];
u32 card_bus;
u16 subsys_vendor_id;
u16 subsys_id;
u32 exp_rom_bar;
u8 capabilities;
u8 reserved1[3];
u32 reserved2;
u8 irq_line;
u8 irq_pin;
u8 min_gnt;
u8 max_lat;
struct msix_cap msix;
u8 empty[136]; /* Rest of PCI config space */
/* Configuration space, as seen by the guest */
union {
struct {
u16 vendor_id;
u16 device_id;
u16 command;
u16 status;
u8 revision_id;
u8 class[3];
u8 cacheline_size;
u8 latency_timer;
u8 header_type;
u8 bist;
u32 bar[6];
u32 card_bus;
u16 subsys_vendor_id;
u16 subsys_id;
u32 exp_rom_bar;
u8 capabilities;
u8 reserved1[3];
u32 reserved2;
u8 irq_line;
u8 irq_pin;
u8 min_gnt;
u8 max_lat;
struct msix_cap msix;
} __attribute__((packed));
/* Pad to PCI config space size */
u8 __pad[PCI_DEV_CFG_SIZE];
};
/* Private to lkvm */
u32 bar_size[6];
} __attribute__((packed));
struct pci_config_operations cfg_ops;
};
int pci__init(struct kvm *kvm);
int pci__exit(struct kvm *kvm);
......
......@@ -8,8 +8,6 @@
#include <linux/err.h>
#include <assert.h>
#define PCI_BAR_OFFSET(b) (offsetof(struct pci_device_header, bar[b]))
static u32 pci_config_address_bits;
/* This is within our PCI gap - in an unused area.
......@@ -131,59 +129,56 @@ static struct ioport_operations pci_config_data_ops = {
void pci__config_wr(struct kvm *kvm, union pci_config_address addr, void *data, int size)
{
u8 dev_num;
dev_num = addr.device_number;
if (pci_device_exists(0, dev_num, 0)) {
unsigned long offset;
offset = addr.w & 0xff;
if (offset < sizeof(struct pci_device_header)) {
void *p = device__find_dev(DEVICE_BUS_PCI, dev_num)->data;
struct pci_device_header *hdr = p;
u8 bar = (offset - PCI_BAR_OFFSET(0)) / (sizeof(u32));
u32 sz = cpu_to_le32(PCI_IO_SIZE);
if (bar < 6 && hdr->bar_size[bar])
sz = hdr->bar_size[bar];
/*
* If the kernel masks the BAR it would expect to find the
* size of the BAR there next time it reads from it.
* When the kernel got the size it would write the address
* back.
*/
if (*(u32 *)(p + offset)) {
/* See if kernel tries to mask one of the BARs */
if ((offset >= PCI_BAR_OFFSET(0)) &&
(offset <= PCI_BAR_OFFSET(6)) &&
(ioport__read32(data) == 0xFFFFFFFF))
memcpy(p + offset, &sz, sizeof(sz));
else
memcpy(p + offset, data, size);
}
}
void *base;
u8 bar, offset;
struct pci_device_header *pci_hdr;
u8 dev_num = addr.device_number;
if (!pci_device_exists(addr.bus_number, dev_num, 0))
return;
offset = addr.w & PCI_DEV_CFG_MASK;
base = pci_hdr = device__find_dev(DEVICE_BUS_PCI, dev_num)->data;
if (pci_hdr->cfg_ops.write)
pci_hdr->cfg_ops.write(kvm, pci_hdr, offset, data, size);
/*
* legacy hack: ignore writes to uninitialized regions (e.g. ROM BAR).
* Not very nice but has been working so far.
*/
if (*(u32 *)(base + offset) == 0)
return;
bar = (offset - PCI_BAR_OFFSET(0)) / sizeof(u32);
/*
* If the kernel masks the BAR it would expect to find the size of the
* BAR there next time it reads from it. When the kernel got the size it
* would write the address back.
*/
if (bar < 6 && ioport__read32(data) == 0xFFFFFFFF) {
u32 sz = pci_hdr->bar_size[bar];
memcpy(base + offset, &sz, sizeof(sz));
} else {
memcpy(base + offset, data, size);
}
}
void pci__config_rd(struct kvm *kvm, union pci_config_address addr, void *data, int size)
{
u8 dev_num;
dev_num = addr.device_number;
u8 offset;
struct pci_device_header *pci_hdr;
u8 dev_num = addr.device_number;
if (pci_device_exists(0, dev_num, 0)) {
unsigned long offset;
if (pci_device_exists(addr.bus_number, dev_num, 0)) {
pci_hdr = device__find_dev(DEVICE_BUS_PCI, dev_num)->data;
offset = addr.w & PCI_DEV_CFG_MASK;
offset = addr.w & 0xff;
if (offset < sizeof(struct pci_device_header)) {
void *p = device__find_dev(DEVICE_BUS_PCI, dev_num)->data;
if (pci_hdr->cfg_ops.read)
pci_hdr->cfg_ops.read(kvm, pci_hdr, offset, data, size);
memcpy(data, p + offset, size);
} else {
memset(data, 0x00, size);
}
memcpy(data, (void *)pci_hdr + offset, size);
} else {
memset(data, 0xff, size);
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment