Commit 8dd28afe authored by Jean-Philippe Brucker's avatar Jean-Philippe Brucker Committed by Will Deacon
Browse files

vfio-pci: add MSI support



Allow guests to use the MSI capability in devices that support it. Emulate
the MSI capability, which is simpler than MSI-X as it doesn't rely on
external tables. Reuse most of the MSI-X code. Guests may choose between
MSI and MSI-X at runtime since we present both capabilities, but they
cannot enable MSI and MSI-X at the same time (forbidden by PCI).
Reviewed-by: default avatarPunit Agrawal <punit.agrawal@arm.com>
Signed-off-by: default avatarJean-Philippe Brucker <jean-philippe.brucker@arm.com>
Signed-off-by: default avatarWill Deacon <will.deacon@arm.com>
parent c9888d95
......@@ -59,6 +59,29 @@ struct msix_cap {
u32 pba_offset;
};
struct msi_cap_64 {
u8 cap;
u8 next;
u16 ctrl;
u32 address_lo;
u32 address_hi;
u16 data;
u16 _align;
u32 mask_bits;
u32 pend_bits;
};
struct msi_cap_32 {
u8 cap;
u8 next;
u16 ctrl;
u32 address_lo;
u16 data;
u16 _align;
u32 mask_bits;
u32 pend_bits;
};
struct pci_cap_hdr {
u8 type;
u8 next;
......
......@@ -75,6 +75,7 @@ struct vfio_pci_device {
unsigned long irq_modes;
int intx_fd;
unsigned int intx_gsi;
struct vfio_pci_msi_common msi;
struct vfio_pci_msi_common msix;
struct vfio_pci_msix_table msix_table;
struct vfio_pci_msix_pba msix_pba;
......
......@@ -29,13 +29,14 @@ struct vfio_irq_eventfd {
static void vfio_pci_disable_intx(struct kvm *kvm, struct vfio_device *vdev);
static int vfio_pci_enable_msis(struct kvm *kvm, struct vfio_device *vdev)
static int vfio_pci_enable_msis(struct kvm *kvm, struct vfio_device *vdev,
bool msix)
{
size_t i;
int ret = 0;
int *eventfds;
struct vfio_pci_device *pdev = &vdev->pci;
struct vfio_pci_msi_common *msis = &pdev->msix;
struct vfio_pci_msi_common *msis = msix ? &pdev->msix : &pdev->msi;
struct vfio_irq_eventfd single = {
.irq = {
.argsz = sizeof(single),
......@@ -135,11 +136,12 @@ static int vfio_pci_enable_msis(struct kvm *kvm, struct vfio_device *vdev)
return ret;
}
static int vfio_pci_disable_msis(struct kvm *kvm, struct vfio_device *vdev)
static int vfio_pci_disable_msis(struct kvm *kvm, struct vfio_device *vdev,
bool msix)
{
int ret;
struct vfio_pci_device *pdev = &vdev->pci;
struct vfio_pci_msi_common *msis = &pdev->msix;
struct vfio_pci_msi_common *msis = msix ? &pdev->msix : &pdev->msi;
struct vfio_irq_set irq_set = {
.argsz = sizeof(irq_set),
.flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_TRIGGER,
......@@ -287,7 +289,7 @@ static void vfio_pci_msix_table_access(struct kvm_cpu *vcpu, u64 addr, u8 *data,
vfio_dev_err(vdev, "failed to configure MSIX vector %zu", vector);
/* Update the physical capability if necessary */
if (vfio_pci_enable_msis(kvm, vdev))
if (vfio_pci_enable_msis(kvm, vdev, true))
vfio_dev_err(vdev, "cannot enable MSIX");
out_unlock:
......@@ -318,14 +320,120 @@ static void vfio_pci_msix_cap_write(struct kvm *kvm,
enable = flags & PCI_MSIX_FLAGS_ENABLE;
msi_set_enabled(pdev->msix.virt_state, enable);
if (enable && vfio_pci_enable_msis(kvm, vdev))
if (enable && vfio_pci_enable_msis(kvm, vdev, true))
vfio_dev_err(vdev, "cannot enable MSIX");
else if (!enable && vfio_pci_disable_msis(kvm, vdev))
else if (!enable && vfio_pci_disable_msis(kvm, vdev, true))
vfio_dev_err(vdev, "cannot disable MSIX");
mutex_unlock(&pdev->msix.mutex);
}
static int vfio_pci_msi_vector_write(struct kvm *kvm, struct vfio_device *vdev,
u8 off, u8 *data, u32 sz)
{
size_t i;
u32 mask = 0;
size_t mask_pos, start, limit;
struct vfio_pci_msi_entry *entry;
struct vfio_pci_device *pdev = &vdev->pci;
struct msi_cap_64 *msi_cap_64 = PCI_CAP(&pdev->hdr, pdev->msi.pos);
if (!(msi_cap_64->ctrl & PCI_MSI_FLAGS_MASKBIT))
return 0;
if (msi_cap_64->ctrl & PCI_MSI_FLAGS_64BIT)
mask_pos = PCI_MSI_MASK_64;
else
mask_pos = PCI_MSI_MASK_32;
if (off >= mask_pos + 4 || off + sz <= mask_pos)
return 0;
/* Set mask to current state */
for (i = 0; i < pdev->msi.nr_entries; i++) {
entry = &pdev->msi.entries[i];
mask |= !!msi_is_masked(entry->virt_state) << i;
}
/* Update mask following the intersection of access and register */
start = max_t(size_t, off, mask_pos);
limit = min_t(size_t, off + sz, mask_pos + 4);
memcpy((void *)&mask + start - mask_pos, data + start - off,
limit - start);
/* Update states if necessary */
for (i = 0; i < pdev->msi.nr_entries; i++) {
bool masked = mask & (1 << i);
entry = &pdev->msi.entries[i];
if (masked != msi_is_masked(entry->virt_state)) {
msi_set_masked(entry->virt_state, masked);
vfio_pci_update_msi_entry(kvm, vdev, entry);
}
}
return 1;
}
static void vfio_pci_msi_cap_write(struct kvm *kvm, struct vfio_device *vdev,
u8 off, u8 *data, u32 sz)
{
u8 ctrl;
struct msi_msg msg;
size_t i, nr_vectors;
struct vfio_pci_msi_entry *entry;
struct vfio_pci_device *pdev = &vdev->pci;
struct msi_cap_64 *msi_cap_64 = PCI_CAP(&pdev->hdr, pdev->msi.pos);
off -= pdev->msi.pos;
mutex_lock(&pdev->msi.mutex);
/* Check if the guest is trying to update mask bits */
if (vfio_pci_msi_vector_write(kvm, vdev, off, data, sz))
goto out_unlock;
/* Only modify routes when guest pokes the enable bit */
if (off > PCI_MSI_FLAGS || off + sz <= PCI_MSI_FLAGS)
goto out_unlock;
ctrl = *(u8 *)(data + PCI_MSI_FLAGS - off);
msi_set_enabled(pdev->msi.virt_state, ctrl & PCI_MSI_FLAGS_ENABLE);
if (!msi_is_enabled(pdev->msi.virt_state)) {
vfio_pci_disable_msis(kvm, vdev, false);
goto out_unlock;
}
/* Create routes for the requested vectors */
nr_vectors = 1 << ((ctrl & PCI_MSI_FLAGS_QSIZE) >> 4);
msg.address_lo = msi_cap_64->address_lo;
if (msi_cap_64->ctrl & PCI_MSI_FLAGS_64BIT) {
msg.address_hi = msi_cap_64->address_hi;
msg.data = msi_cap_64->data;
} else {
struct msi_cap_32 *msi_cap_32 = (void *)msi_cap_64;
msg.address_hi = 0;
msg.data = msi_cap_32->data;
}
for (i = 0; i < nr_vectors; i++) {
entry = &pdev->msi.entries[i];
entry->config.msg = msg;
vfio_pci_update_msi_entry(kvm, vdev, entry);
}
/* Update the physical capability if necessary */
if (vfio_pci_enable_msis(kvm, vdev, false))
vfio_dev_err(vdev, "cannot enable MSI");
out_unlock:
mutex_unlock(&pdev->msi.mutex);
}
static void vfio_pci_cfg_read(struct kvm *kvm, struct pci_device_header *pci_hdr,
u8 offset, void *data, int sz)
{
......@@ -364,16 +472,33 @@ static void vfio_pci_cfg_write(struct kvm *kvm, struct pci_device_header *pci_hd
if (pdev->irq_modes & VFIO_PCI_IRQ_MODE_MSIX)
vfio_pci_msix_cap_write(kvm, vdev, offset, data, sz);
if (pdev->irq_modes & VFIO_PCI_IRQ_MODE_MSI)
vfio_pci_msi_cap_write(kvm, vdev, offset, data, sz);
if (pread(vdev->fd, base + offset, sz, info->offset + offset) != sz)
vfio_dev_warn(vdev, "Failed to read %d bytes from Configuration Space at 0x%x",
sz, offset);
}
static ssize_t vfio_pci_msi_cap_size(struct msi_cap_64 *cap_hdr)
{
size_t size = 10;
if (cap_hdr->ctrl & PCI_MSI_FLAGS_64BIT)
size += 4;
if (cap_hdr->ctrl & PCI_MSI_FLAGS_MASKBIT)
size += 10;
return size;
}
static ssize_t vfio_pci_cap_size(struct pci_cap_hdr *cap_hdr)
{
switch (cap_hdr->type) {
case PCI_CAP_ID_MSIX:
return PCI_CAP_MSIX_SIZEOF;
case PCI_CAP_ID_MSI:
return vfio_pci_msi_cap_size((void *)cap_hdr);
default:
pr_err("unknown PCI capability 0x%x", cap_hdr->type);
return 0;
......@@ -442,6 +567,14 @@ static int vfio_pci_parse_caps(struct vfio_device *vdev)
pdev->msix.pos = pos;
pdev->irq_modes |= VFIO_PCI_IRQ_MODE_MSIX;
break;
case PCI_CAP_ID_MSI:
ret = vfio_pci_add_cap(vdev, virt_hdr, cap, pos);
if (ret)
return ret;
pdev->msi.pos = pos;
pdev->irq_modes |= VFIO_PCI_IRQ_MODE_MSI;
break;
}
}
......@@ -644,6 +777,19 @@ out_free:
return ret;
}
static int vfio_pci_create_msi_cap(struct kvm *kvm, struct vfio_pci_device *pdev)
{
struct msi_cap_64 *cap = PCI_CAP(&pdev->hdr, pdev->msi.pos);
pdev->msi.nr_entries = 1 << ((cap->ctrl & PCI_MSI_FLAGS_QMASK) >> 1),
pdev->msi.entries = calloc(pdev->msi.nr_entries,
sizeof(struct vfio_pci_msi_entry));
if (!pdev->msi.entries)
return -ENOMEM;
return 0;
}
static int vfio_pci_configure_bar(struct kvm *kvm, struct vfio_device *vdev,
size_t nr)
{
......@@ -716,6 +862,12 @@ static int vfio_pci_configure_dev_regions(struct kvm *kvm,
return ret;
}
if (pdev->irq_modes & VFIO_PCI_IRQ_MODE_MSI) {
ret = vfio_pci_create_msi_cap(kvm, pdev);
if (ret)
return ret;
}
for (i = VFIO_PCI_BAR0_REGION_INDEX; i <= VFIO_PCI_BAR5_REGION_INDEX; ++i) {
/* Ignore top half of 64-bit BAR */
if (i % 2 && is_64bit)
......@@ -971,6 +1123,16 @@ static int vfio_pci_configure_dev_irqs(struct kvm *kvm, struct vfio_device *vdev
return ret;
}
if (pdev->irq_modes & VFIO_PCI_IRQ_MODE_MSI) {
pdev->msi.info = (struct vfio_irq_info) {
.argsz = sizeof(pdev->msi.info),
.index = VFIO_PCI_MSI_IRQ_INDEX,
};
ret = vfio_pci_init_msis(kvm, vdev, &pdev->msi);
if (ret)
return ret;
}
if (pdev->irq_modes & VFIO_PCI_IRQ_MODE_INTX)
ret = vfio_pci_enable_intx(kvm, vdev);
......@@ -1019,4 +1181,6 @@ void vfio_pci_teardown_device(struct kvm *kvm, struct vfio_device *vdev)
free(pdev->msix.irq_set);
free(pdev->msix.entries);
free(pdev->msi.irq_set);
free(pdev->msi.entries);
}
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