diff --git a/xen/arch/x86/hvm/vmsi.c b/xen/arch/x86/hvm/vmsi.c index 7126de7841..be59c56d43 100644 --- a/xen/arch/x86/hvm/vmsi.c +++ b/xen/arch/x86/hvm/vmsi.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -621,3 +622,144 @@ void msix_write_completion(struct vcpu *v) if ( msixtbl_write(v, ctrl_address, 4, 0) != X86EMUL_OKAY ) gdprintk(XENLOG_WARNING, "MSI-X write completion failure\n"); } + +static unsigned int msi_gflags(uint16_t data, uint64_t addr, bool masked) +{ + /* + * We need to use the DOMCTL constants here because the output of this + * function is used as input to pt_irq_create_bind, which also takes the + * input from the DOMCTL itself. + */ + return MASK_INSR(MASK_EXTR(addr, MSI_ADDR_DEST_ID_MASK), + XEN_DOMCTL_VMSI_X86_DEST_ID_MASK) | + MASK_INSR(MASK_EXTR(addr, MSI_ADDR_REDIRECTION_MASK), + XEN_DOMCTL_VMSI_X86_RH_MASK) | + MASK_INSR(MASK_EXTR(addr, MSI_ADDR_DESTMODE_MASK), + XEN_DOMCTL_VMSI_X86_DM_MASK) | + MASK_INSR(MASK_EXTR(data, MSI_DATA_DELIVERY_MODE_MASK), + XEN_DOMCTL_VMSI_X86_DELIV_MASK) | + MASK_INSR(MASK_EXTR(data, MSI_DATA_TRIGGER_MASK), + XEN_DOMCTL_VMSI_X86_TRIG_MASK) | + /* NB: by default MSI vectors are bound masked. */ + (masked ? 0 : XEN_DOMCTL_VMSI_X86_UNMASKED); +} + +void vpci_msi_arch_mask(struct vpci_msi *msi, const struct pci_dev *pdev, + unsigned int entry, bool mask) +{ + unsigned long flags; + struct irq_desc *desc = domain_spin_lock_irq_desc(pdev->domain, + msi->arch.pirq + entry, + &flags); + + if ( !desc ) + return; + guest_mask_msi_irq(desc, mask); + spin_unlock_irqrestore(&desc->lock, flags); +} + +int vpci_msi_arch_enable(struct vpci_msi *msi, const struct pci_dev *pdev, + unsigned int vectors) +{ + struct msi_info msi_info = { + .seg = pdev->seg, + .bus = pdev->bus, + .devfn = pdev->devfn, + .entry_nr = vectors, + }; + unsigned int i; + int rc; + + ASSERT(msi->arch.pirq == INVALID_PIRQ); + + /* Get a PIRQ. */ + rc = allocate_and_map_msi_pirq(pdev->domain, -1, &msi->arch.pirq, + MAP_PIRQ_TYPE_MULTI_MSI, &msi_info); + if ( rc ) + { + gdprintk(XENLOG_ERR, "%04x:%02x:%02x.%u: failed to map PIRQ: %d\n", + pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), rc); + return rc; + } + + for ( i = 0; i < vectors; i++ ) + { + uint8_t vector = MASK_EXTR(msi->data, MSI_DATA_VECTOR_MASK); + uint8_t vector_mask = 0xff >> (8 - fls(msi->vectors) + 1); + struct xen_domctl_bind_pt_irq bind = { + .machine_irq = msi->arch.pirq + i, + .irq_type = PT_IRQ_TYPE_MSI, + .u.msi.gvec = (vector & ~vector_mask) | + ((vector + i) & vector_mask), + .u.msi.gflags = msi_gflags(msi->data, msi->address, + (msi->mask >> i) & 1), + }; + + pcidevs_lock(); + rc = pt_irq_create_bind(pdev->domain, &bind); + if ( rc ) + { + gdprintk(XENLOG_ERR, + "%04x:%02x:%02x.%u: failed to bind PIRQ %u: %d\n", + pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), msi->arch.pirq + i, rc); + while ( bind.machine_irq-- ) + pt_irq_destroy_bind(pdev->domain, &bind); + spin_lock(&pdev->domain->event_lock); + unmap_domain_pirq(pdev->domain, msi->arch.pirq); + spin_unlock(&pdev->domain->event_lock); + pcidevs_unlock(); + msi->arch.pirq = INVALID_PIRQ; + return rc; + } + pcidevs_unlock(); + } + + return 0; +} + +void vpci_msi_arch_disable(struct vpci_msi *msi, const struct pci_dev *pdev) +{ + unsigned int i; + + ASSERT(msi->arch.pirq != INVALID_PIRQ); + + pcidevs_lock(); + for ( i = 0; i < msi->vectors; i++ ) + { + struct xen_domctl_bind_pt_irq bind = { + .machine_irq = msi->arch.pirq + i, + .irq_type = PT_IRQ_TYPE_MSI, + }; + int rc; + + rc = pt_irq_destroy_bind(pdev->domain, &bind); + ASSERT(!rc); + } + + spin_lock(&pdev->domain->event_lock); + unmap_domain_pirq(pdev->domain, msi->arch.pirq); + spin_unlock(&pdev->domain->event_lock); + pcidevs_unlock(); + + msi->arch.pirq = INVALID_PIRQ; +} + +void vpci_msi_arch_init(struct vpci_msi *msi) +{ + msi->arch.pirq = INVALID_PIRQ; +} + +void vpci_msi_arch_print(const struct vpci_msi *msi) +{ + printk("vec=%#02x%7s%6s%3sassert%5s%7s dest_id=%lu pirq: %d\n", + MASK_EXTR(msi->data, MSI_DATA_VECTOR_MASK), + msi->data & MSI_DATA_DELIVERY_LOWPRI ? "lowest" : "fixed", + msi->data & MSI_DATA_TRIGGER_LEVEL ? "level" : "edge", + msi->data & MSI_DATA_LEVEL_ASSERT ? "" : "de", + msi->address & MSI_ADDR_DESTMODE_LOGIC ? "log" : "phys", + msi->address & MSI_ADDR_REDIRECTION_LOWPRI ? "lowest" : "fixed", + MASK_EXTR(msi->address, MSI_ADDR_DEST_ID_MASK), + msi->arch.pirq); +} diff --git a/xen/arch/x86/msi.c b/xen/arch/x86/msi.c index 8c89f072a8..5567990fbd 100644 --- a/xen/arch/x86/msi.c +++ b/xen/arch/x86/msi.c @@ -30,6 +30,7 @@ #include #include #include +#include static s8 __read_mostly use_msi = -1; boolean_param("msi", use_msi); @@ -1527,6 +1528,8 @@ static void dump_msi(unsigned char key) attr.guest_masked ? 'G' : ' ', mask); } + + vpci_dump_msi(); } static int __init msi_setup_keyhandler(void) diff --git a/xen/drivers/vpci/Makefile b/xen/drivers/vpci/Makefile index 241467212f..62cec9e82b 100644 --- a/xen/drivers/vpci/Makefile +++ b/xen/drivers/vpci/Makefile @@ -1 +1 @@ -obj-y += vpci.o header.o +obj-y += vpci.o header.o msi.o diff --git a/xen/drivers/vpci/msi.c b/xen/drivers/vpci/msi.c new file mode 100644 index 0000000000..c3c69ec453 --- /dev/null +++ b/xen/drivers/vpci/msi.c @@ -0,0 +1,324 @@ +/* + * Handlers for accesses to the MSI capability structure. + * + * Copyright (C) 2017 Citrix Systems R&D + * + * This program is free software; you can redistribute it and/or + * modify it under the terms and conditions of the GNU General Public + * License, version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see . + */ + +#include +#include +#include + +#include + +static uint32_t control_read(const struct pci_dev *pdev, unsigned int reg, + void *data) +{ + const struct vpci_msi *msi = data; + + return MASK_INSR(fls(msi->max_vectors) - 1, PCI_MSI_FLAGS_QMASK) | + MASK_INSR(fls(msi->vectors) - 1, PCI_MSI_FLAGS_QSIZE) | + (msi->enabled ? PCI_MSI_FLAGS_ENABLE : 0) | + (msi->masking ? PCI_MSI_FLAGS_MASKBIT : 0) | + (msi->address64 ? PCI_MSI_FLAGS_64BIT : 0); +} + +static void control_write(const struct pci_dev *pdev, unsigned int reg, + uint32_t val, void *data) +{ + struct vpci_msi *msi = data; + unsigned int vectors = min_t(uint8_t, + 1u << MASK_EXTR(val, PCI_MSI_FLAGS_QSIZE), + msi->max_vectors); + bool new_enabled = val & PCI_MSI_FLAGS_ENABLE; + + /* + * No change if the enable field and the number of vectors is + * the same or the device is not enabled, in which case the + * vectors field can be updated directly. + */ + if ( new_enabled == msi->enabled && + (vectors == msi->vectors || !msi->enabled) ) + { + msi->vectors = vectors; + return; + } + + if ( new_enabled ) + { + /* + * If the device is already enabled it means the number of + * enabled messages has changed. Disable and re-enable the + * device in order to apply the change. + */ + if ( msi->enabled ) + { + vpci_msi_arch_disable(msi, pdev); + msi->enabled = false; + } + + if ( vpci_msi_arch_enable(msi, pdev, vectors) ) + return; + } + else + vpci_msi_arch_disable(msi, pdev); + + msi->vectors = vectors; + msi->enabled = new_enabled; + + pci_conf_write16(pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), reg, + control_read(pdev, reg, data)); +} + +static void update_msi(const struct pci_dev *pdev, struct vpci_msi *msi) +{ + if ( !msi->enabled ) + return; + + vpci_msi_arch_disable(msi, pdev); + if ( vpci_msi_arch_enable(msi, pdev, msi->vectors) ) + msi->enabled = false; +} + +/* Handlers for the address field (32bit or low part of a 64bit address). */ +static uint32_t address_read(const struct pci_dev *pdev, unsigned int reg, + void *data) +{ + const struct vpci_msi *msi = data; + + return msi->address; +} + +static void address_write(const struct pci_dev *pdev, unsigned int reg, + uint32_t val, void *data) +{ + struct vpci_msi *msi = data; + + /* Clear low part. */ + msi->address &= ~0xffffffffull; + msi->address |= val; + + update_msi(pdev, msi); +} + +/* Handlers for the high part of a 64bit address field. */ +static uint32_t address_hi_read(const struct pci_dev *pdev, unsigned int reg, + void *data) +{ + const struct vpci_msi *msi = data; + + return msi->address >> 32; +} + +static void address_hi_write(const struct pci_dev *pdev, unsigned int reg, + uint32_t val, void *data) +{ + struct vpci_msi *msi = data; + + /* Clear and update high part. */ + msi->address &= 0xffffffff; + msi->address |= (uint64_t)val << 32; + + update_msi(pdev, msi); +} + +/* Handlers for the data field. */ +static uint32_t data_read(const struct pci_dev *pdev, unsigned int reg, + void *data) +{ + const struct vpci_msi *msi = data; + + return msi->data; +} + +static void data_write(const struct pci_dev *pdev, unsigned int reg, + uint32_t val, void *data) +{ + struct vpci_msi *msi = data; + + msi->data = val; + + update_msi(pdev, msi); +} + +/* Handlers for the MSI mask bits. */ +static uint32_t mask_read(const struct pci_dev *pdev, unsigned int reg, + void *data) +{ + const struct vpci_msi *msi = data; + + return msi->mask; +} + +static void mask_write(const struct pci_dev *pdev, unsigned int reg, + uint32_t val, void *data) +{ + struct vpci_msi *msi = data; + uint32_t dmask = msi->mask ^ val; + + if ( !dmask ) + return; + + if ( msi->enabled ) + { + unsigned int i; + + for ( i = ffs(dmask) - 1; dmask && i < msi->vectors; + i = ffs(dmask) - 1 ) + { + vpci_msi_arch_mask(msi, pdev, i, (val >> i) & 1); + __clear_bit(i, &dmask); + } + } + + msi->mask = val; +} + +static int init_msi(struct pci_dev *pdev) +{ + uint8_t slot = PCI_SLOT(pdev->devfn), func = PCI_FUNC(pdev->devfn); + unsigned int pos = pci_find_cap_offset(pdev->seg, pdev->bus, slot, func, + PCI_CAP_ID_MSI); + uint16_t control; + int ret; + + if ( !pos ) + return 0; + + pdev->vpci->msi = xzalloc(struct vpci_msi); + if ( !pdev->vpci->msi ) + return -ENOMEM; + + ret = vpci_add_register(pdev->vpci, control_read, control_write, + msi_control_reg(pos), 2, pdev->vpci->msi); + if ( ret ) + /* + * NB: there's no need to free the msi struct or remove the register + * handlers form the config space, the caller will take care of the + * cleanup. + */ + return ret; + + /* Get the maximum number of vectors the device supports. */ + control = pci_conf_read16(pdev->seg, pdev->bus, slot, func, + msi_control_reg(pos)); + + /* + * FIXME: I've only been able to test this code with devices using a single + * MSI interrupt and no mask register. + */ + pdev->vpci->msi->max_vectors = multi_msi_capable(control); + ASSERT(pdev->vpci->msi->max_vectors <= 32); + + /* The multiple message enable is 0 after reset (1 message enabled). */ + pdev->vpci->msi->vectors = 1; + + /* No PIRQ bound yet. */ + vpci_msi_arch_init(pdev->vpci->msi); + + pdev->vpci->msi->address64 = is_64bit_address(control); + pdev->vpci->msi->masking = is_mask_bit_support(control); + + ret = vpci_add_register(pdev->vpci, address_read, address_write, + msi_lower_address_reg(pos), 4, pdev->vpci->msi); + if ( ret ) + return ret; + + ret = vpci_add_register(pdev->vpci, data_read, data_write, + msi_data_reg(pos, pdev->vpci->msi->address64), 2, + pdev->vpci->msi); + if ( ret ) + return ret; + + if ( pdev->vpci->msi->address64 ) + { + ret = vpci_add_register(pdev->vpci, address_hi_read, address_hi_write, + msi_upper_address_reg(pos), 4, pdev->vpci->msi); + if ( ret ) + return ret; + } + + if ( pdev->vpci->msi->masking ) + { + ret = vpci_add_register(pdev->vpci, mask_read, mask_write, + msi_mask_bits_reg(pos, + pdev->vpci->msi->address64), + 4, pdev->vpci->msi); + if ( ret ) + return ret; + /* + * FIXME: do not add any handler for the pending bits for the hardware + * domain, which means direct access. This will be revisited when + * adding unprivileged domain support. + */ + } + + return 0; +} +REGISTER_VPCI_INIT(init_msi); + +void vpci_dump_msi(void) +{ + const struct domain *d; + + rcu_read_lock(&domlist_read_lock); + for_each_domain ( d ) + { + const struct pci_dev *pdev; + + if ( !has_vpci(d) ) + continue; + + printk("vPCI MSI d%d\n", d->domain_id); + + list_for_each_entry ( pdev, &d->arch.pdev_list, domain_list ) + { + const struct vpci_msi *msi; + + if ( !pdev->vpci || !spin_trylock(&pdev->vpci->lock) ) + continue; + + msi = pdev->vpci->msi; + if ( msi && msi->enabled ) + { + printk("%04x:%02x:%02x.%u MSI\n", pdev->seg, pdev->bus, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + + printk(" enabled: %d 64-bit: %d", + msi->enabled, msi->address64); + if ( msi->masking ) + printk(" mask=%08x", msi->mask); + printk(" vectors max: %u enabled: %u\n", + msi->max_vectors, msi->vectors); + + vpci_msi_arch_print(msi); + } + + spin_unlock(&pdev->vpci->lock); + process_pending_softirqs(); + } + } + rcu_read_unlock(&domlist_read_lock); +} + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/drivers/vpci/vpci.c b/xen/drivers/vpci/vpci.c index e5b49b9d82..3012b30013 100644 --- a/xen/drivers/vpci/vpci.c +++ b/xen/drivers/vpci/vpci.c @@ -47,6 +47,7 @@ void vpci_remove_device(struct pci_dev *pdev) xfree(r); } spin_unlock(&pdev->vpci->lock); + xfree(pdev->vpci->msi); xfree(pdev->vpci); pdev->vpci = NULL; } diff --git a/xen/include/asm-x86/hvm/io.h b/xen/include/asm-x86/hvm/io.h index 16465ceb30..0fedb3473c 100644 --- a/xen/include/asm-x86/hvm/io.h +++ b/xen/include/asm-x86/hvm/io.h @@ -127,6 +127,11 @@ void hvm_dpci_eoi(struct domain *d, unsigned int guest_irq, void msix_write_completion(struct vcpu *); void msixtbl_init(struct domain *d); +/* Arch-specific MSI data for vPCI. */ +struct vpci_arch_msi { + int pirq; +}; + enum stdvga_cache_state { STDVGA_CACHE_UNINITIALIZED, STDVGA_CACHE_ENABLED, diff --git a/xen/include/asm-x86/msi.h b/xen/include/asm-x86/msi.h index 37d37b820e..10387dce2e 100644 --- a/xen/include/asm-x86/msi.h +++ b/xen/include/asm-x86/msi.h @@ -48,6 +48,7 @@ #define MSI_ADDR_REDIRECTION_SHIFT 3 #define MSI_ADDR_REDIRECTION_CPU (0 << MSI_ADDR_REDIRECTION_SHIFT) #define MSI_ADDR_REDIRECTION_LOWPRI (1 << MSI_ADDR_REDIRECTION_SHIFT) +#define MSI_ADDR_REDIRECTION_MASK (1 << MSI_ADDR_REDIRECTION_SHIFT) #define MSI_ADDR_DEST_ID_SHIFT 12 #define MSI_ADDR_DEST_ID_MASK 0x00ff000 @@ -152,6 +153,8 @@ int msi_free_irq(struct msi_desc *entry); ( (is64bit == 1) ? base+PCI_MSI_DATA_64 : base+PCI_MSI_DATA_32 ) #define msi_mask_bits_reg(base, is64bit) \ ( (is64bit == 1) ? base+PCI_MSI_MASK_BIT : base+PCI_MSI_MASK_BIT-4) +#define msi_pending_bits_reg(base, is64bit) \ + ((base) + PCI_MSI_MASK_BIT + ((is64bit) ? 4 : 0)) #define msi_disable(control) control &= ~PCI_MSI_FLAGS_ENABLE #define multi_msi_capable(control) \ (1 << ((control & PCI_MSI_FLAGS_QMASK) >> 1)) diff --git a/xen/include/xen/irq.h b/xen/include/xen/irq.h index 0aa817e266..586b78393a 100644 --- a/xen/include/xen/irq.h +++ b/xen/include/xen/irq.h @@ -133,6 +133,7 @@ struct pirq { struct arch_pirq arch; }; +#define INVALID_PIRQ (-1) #define pirq_info(d, p) ((struct pirq *)radix_tree_lookup(&(d)->pirq_tree, p)) /* Use this instead of pirq_info() if the structure may need allocating. */ diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index 6bf8b22b4f..116b93f519 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -87,6 +87,30 @@ struct vpci { /* FIXME: currently there's no support for SR-IOV. */ } header; #endif + + /* MSI data. */ + struct vpci_msi { +#ifdef __XEN__ + /* Address. */ + uint64_t address; + /* Mask bitfield. */ + uint32_t mask; + /* Data. */ + uint16_t data; + /* Maximum number of vectors supported by the device. */ + uint8_t max_vectors : 5; + /* Enabled? */ + bool enabled : 1; + /* Supports per-vector masking? */ + bool masking : 1; + /* 64-bit address capable? */ + bool address64 : 1; + /* Number of vectors configured. */ + uint8_t vectors : 5; + /* Arch-specific data. */ + struct vpci_arch_msi arch; +#endif + } *msi; }; struct vpci_vcpu { @@ -97,6 +121,20 @@ struct vpci_vcpu { bool rom_only : 1; }; +#ifdef __XEN__ +void vpci_dump_msi(void); + +/* Arch-specific vPCI MSI helpers. */ +void vpci_msi_arch_mask(struct vpci_msi *msi, const struct pci_dev *pdev, + unsigned int entry, bool mask); +int __must_check vpci_msi_arch_enable(struct vpci_msi *msi, + const struct pci_dev *pdev, + unsigned int vectors); +void vpci_msi_arch_disable(struct vpci_msi *msi, const struct pci_dev *pdev); +void vpci_msi_arch_init(struct vpci_msi *msi); +void vpci_msi_arch_print(const struct vpci_msi *msi); +#endif /* __XEN__ */ + #else /* !CONFIG_HAS_VPCI */ struct vpci_vcpu {}; #endif