diff options
Diffstat (limited to 'drivers/xen/xen-pciback/pciback_ops.c')
-rw-r--r-- | drivers/xen/xen-pciback/pciback_ops.c | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/drivers/xen/xen-pciback/pciback_ops.c b/drivers/xen/xen-pciback/pciback_ops.c index 011db675e437..6c398fde7a83 100644 --- a/drivers/xen/xen-pciback/pciback_ops.c +++ b/drivers/xen/xen-pciback/pciback_ops.c @@ -13,6 +13,77 @@ int verbose_request; module_param(verbose_request, int, 0644); +/* Ensure a device is has the fake IRQ handler "turned on/off" and is + * ready to be exported. This MUST be run after pciback_reset_device + * which does the actual PCI device enable/disable. + */ +void pciback_control_isr(struct pci_dev *dev, int reset) +{ + struct pciback_dev_data *dev_data; + int rc; + int enable = 0; + + dev_data = pci_get_drvdata(dev); + if (!dev_data) + return; + + /* We don't deal with bridges */ + if (dev->hdr_type != PCI_HEADER_TYPE_NORMAL) + return; + + if (reset) { + dev_data->enable_intx = 0; + dev_data->ack_intr = 0; + } + enable = dev_data->enable_intx; + + /* Asked to disable, but ISR isn't runnig */ + if (!enable && !dev_data->isr_on) + return; + + /* Squirrel away the IRQs in the dev_data. We need this + * b/c when device transitions to MSI, the dev->irq is + * overwritten with the MSI vector. + */ + if (enable) + dev_data->irq = dev->irq; + + dev_dbg(&dev->dev, "%s: #%d %s %s%s %s-> %s\n", + dev_data->irq_name, + dev_data->irq, + pci_is_enabled(dev) ? "on" : "off", + dev->msi_enabled ? "MSI" : "", + dev->msix_enabled ? "MSI/X" : "", + dev_data->isr_on ? "enable" : "disable", + enable ? "enable" : "disable"); + + if (enable) { + rc = request_irq(dev_data->irq, + pciback_guest_interrupt, IRQF_SHARED, + dev_data->irq_name, dev); + if (rc) { + dev_err(&dev->dev, "%s: failed to install fake IRQ " \ + "handler for IRQ %d! (rc:%d)\n", + dev_data->irq_name, dev_data->irq, rc); + goto out; + } + } else { + free_irq(dev_data->irq, dev); + dev_data->irq = 0; + } + dev_data->isr_on = enable; + dev_data->ack_intr = enable; +out: + dev_dbg(&dev->dev, "%s: #%d %s %s%s %s\n", + dev_data->irq_name, + dev_data->irq, + pci_is_enabled(dev) ? "on" : "off", + dev->msi_enabled ? "MSI" : "", + dev->msix_enabled ? "MSI/X" : "", + enable ? (dev_data->isr_on ? "enabled" : "failed to enable") : + (dev_data->isr_on ? "failed to disable" : "disabled")); +} + /* Ensure a device is "turned off" and ready to be exported. * (Also see pciback_config_reset to ensure virtual configuration space is * ready to be re-exported) @@ -21,6 +92,8 @@ void pciback_reset_device(struct pci_dev *dev) { u16 cmd; + pciback_control_isr(dev, 1 /* reset device */); + /* Disable devices (but not bridges) */ if (dev->hdr_type == PCI_HEADER_TYPE_NORMAL) { #ifdef CONFIG_PCI_MSI @@ -78,13 +151,18 @@ void pciback_do_op(struct work_struct *data) struct pciback_device *pdev = container_of(data, struct pciback_device, op_work); struct pci_dev *dev; + struct pciback_dev_data *dev_data = NULL; struct xen_pci_op *op = &pdev->sh_info->op; + int test_intx = 0; dev = pciback_get_pci_dev(pdev, op->domain, op->bus, op->devfn); if (dev == NULL) op->err = XEN_PCI_ERR_dev_not_found; else { + dev_data = pci_get_drvdata(dev); + if (dev_data) + test_intx = dev_data->enable_intx; switch (op->cmd) { case XEN_PCI_OP_conf_read: op->err = pciback_config_read(dev, @@ -113,6 +191,11 @@ void pciback_do_op(struct work_struct *data) break; } } + if (!op->err && dev && dev_data) { + /* Transition detected */ + if ((dev_data->enable_intx != test_intx)) + pciback_control_isr(dev, 0 /* no reset */); + } /* Tell the driver domain that we're done. */ wmb(); clear_bit(_XEN_PCIF_active, (unsigned long *)&pdev->sh_info->flags); @@ -137,3 +220,22 @@ irqreturn_t pciback_handle_event(int irq, void *dev_id) return IRQ_HANDLED; } +irqreturn_t pciback_guest_interrupt(int irq, void *dev_id) +{ + struct pci_dev *dev = (struct pci_dev *)dev_id; + struct pciback_dev_data *dev_data = pci_get_drvdata(dev); + + if (dev_data->isr_on && dev_data->ack_intr) { + dev_data->handled++; + if ((dev_data->handled % 1000) == 0) { + if (xen_test_irq_shared(irq)) { + printk(KERN_INFO "%s IRQ line is not shared " + "with other domains. Turning ISR off\n", + dev_data->irq_name); + dev_data->ack_intr = 0; + } + } + return IRQ_HANDLED; + } + return IRQ_NONE; +} |