|
@@ -587,12 +587,47 @@ static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
|
|
|
return IRQ_SET_MASK_OK_DONE;
|
|
|
}
|
|
|
|
|
|
+static void its_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
|
|
|
+{
|
|
|
+ struct its_device *its_dev = irq_data_get_irq_chip_data(d);
|
|
|
+ struct its_node *its;
|
|
|
+ u64 addr;
|
|
|
+
|
|
|
+ its = its_dev->its;
|
|
|
+ addr = its->phys_base + GITS_TRANSLATER;
|
|
|
+
|
|
|
+ msg->address_lo = addr & ((1UL << 32) - 1);
|
|
|
+ msg->address_hi = addr >> 32;
|
|
|
+ msg->data = its_get_event_id(d);
|
|
|
+}
|
|
|
+
|
|
|
static struct irq_chip its_irq_chip = {
|
|
|
.name = "ITS",
|
|
|
.irq_mask = its_mask_irq,
|
|
|
.irq_unmask = its_unmask_irq,
|
|
|
.irq_eoi = its_eoi_irq,
|
|
|
.irq_set_affinity = its_set_affinity,
|
|
|
+ .irq_compose_msi_msg = its_irq_compose_msi_msg,
|
|
|
+};
|
|
|
+
|
|
|
+static void its_mask_msi_irq(struct irq_data *d)
|
|
|
+{
|
|
|
+ pci_msi_mask_irq(d);
|
|
|
+ irq_chip_mask_parent(d);
|
|
|
+}
|
|
|
+
|
|
|
+static void its_unmask_msi_irq(struct irq_data *d)
|
|
|
+{
|
|
|
+ pci_msi_unmask_irq(d);
|
|
|
+ irq_chip_unmask_parent(d);
|
|
|
+}
|
|
|
+
|
|
|
+static struct irq_chip its_msi_irq_chip = {
|
|
|
+ .name = "ITS-MSI",
|
|
|
+ .irq_unmask = its_unmask_msi_irq,
|
|
|
+ .irq_mask = its_mask_msi_irq,
|
|
|
+ .irq_eoi = irq_chip_eoi_parent,
|
|
|
+ .irq_write_msi_msg = pci_msi_domain_write_msg,
|
|
|
};
|
|
|
|
|
|
/*
|
|
@@ -1055,3 +1090,144 @@ static void its_free_device(struct its_device *its_dev)
|
|
|
kfree(its_dev->itt);
|
|
|
kfree(its_dev);
|
|
|
}
|
|
|
+
|
|
|
+static int its_alloc_device_irq(struct its_device *dev, irq_hw_number_t *hwirq)
|
|
|
+{
|
|
|
+ int idx;
|
|
|
+
|
|
|
+ idx = find_first_zero_bit(dev->lpi_map, dev->nr_lpis);
|
|
|
+ if (idx == dev->nr_lpis)
|
|
|
+ return -ENOSPC;
|
|
|
+
|
|
|
+ *hwirq = dev->lpi_base + idx;
|
|
|
+ set_bit(idx, dev->lpi_map);
|
|
|
+
|
|
|
+ /* Map the GIC irq ID to the device */
|
|
|
+ its_send_mapvi(dev, *hwirq, idx);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int its_msi_prepare(struct irq_domain *domain, struct device *dev,
|
|
|
+ int nvec, msi_alloc_info_t *info)
|
|
|
+{
|
|
|
+ struct pci_dev *pdev;
|
|
|
+ struct its_node *its;
|
|
|
+ u32 dev_id;
|
|
|
+ struct its_device *its_dev;
|
|
|
+
|
|
|
+ if (!dev_is_pci(dev))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ pdev = to_pci_dev(dev);
|
|
|
+ dev_id = PCI_DEVID(pdev->bus->number, pdev->devfn);
|
|
|
+ its = domain->parent->host_data;
|
|
|
+
|
|
|
+ its_dev = its_find_device(its, dev_id);
|
|
|
+ if (WARN_ON(its_dev))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ its_dev = its_create_device(its, dev_id, nvec);
|
|
|
+ if (!its_dev)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ dev_dbg(&pdev->dev, "ITT %d entries, %d bits\n", nvec, ilog2(nvec));
|
|
|
+
|
|
|
+ info->scratchpad[0].ptr = its_dev;
|
|
|
+ info->scratchpad[1].ptr = dev;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static struct msi_domain_ops its_pci_msi_ops = {
|
|
|
+ .msi_prepare = its_msi_prepare,
|
|
|
+};
|
|
|
+
|
|
|
+static struct msi_domain_info its_pci_msi_domain_info = {
|
|
|
+ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
|
|
+ MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX),
|
|
|
+ .ops = &its_pci_msi_ops,
|
|
|
+ .chip = &its_msi_irq_chip,
|
|
|
+};
|
|
|
+
|
|
|
+static int its_irq_gic_domain_alloc(struct irq_domain *domain,
|
|
|
+ unsigned int virq,
|
|
|
+ irq_hw_number_t hwirq)
|
|
|
+{
|
|
|
+ struct of_phandle_args args;
|
|
|
+
|
|
|
+ args.np = domain->parent->of_node;
|
|
|
+ args.args_count = 3;
|
|
|
+ args.args[0] = GIC_IRQ_TYPE_LPI;
|
|
|
+ args.args[1] = hwirq;
|
|
|
+ args.args[2] = IRQ_TYPE_EDGE_RISING;
|
|
|
+
|
|
|
+ return irq_domain_alloc_irqs_parent(domain, virq, 1, &args);
|
|
|
+}
|
|
|
+
|
|
|
+static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
|
|
+ unsigned int nr_irqs, void *args)
|
|
|
+{
|
|
|
+ msi_alloc_info_t *info = args;
|
|
|
+ struct its_device *its_dev = info->scratchpad[0].ptr;
|
|
|
+ irq_hw_number_t hwirq;
|
|
|
+ int err;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < nr_irqs; i++) {
|
|
|
+ err = its_alloc_device_irq(its_dev, &hwirq);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ err = its_irq_gic_domain_alloc(domain, virq + i, hwirq);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ irq_domain_set_hwirq_and_chip(domain, virq + i,
|
|
|
+ hwirq, &its_irq_chip, its_dev);
|
|
|
+ dev_dbg(info->scratchpad[1].ptr, "ID:%d pID:%d vID:%d\n",
|
|
|
+ (int)(hwirq - its_dev->lpi_base), (int)hwirq, virq + i);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
|
|
|
+ unsigned int nr_irqs)
|
|
|
+{
|
|
|
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
|
|
|
+ struct its_device *its_dev = irq_data_get_irq_chip_data(d);
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < nr_irqs; i++) {
|
|
|
+ struct irq_data *data = irq_domain_get_irq_data(domain,
|
|
|
+ virq + i);
|
|
|
+ int event = its_get_event_id(data);
|
|
|
+
|
|
|
+ /* Stop the delivery of interrupts */
|
|
|
+ its_send_discard(its_dev, event);
|
|
|
+
|
|
|
+ /* Mark interrupt index as unused */
|
|
|
+ clear_bit(event, its_dev->lpi_map);
|
|
|
+
|
|
|
+ /* Nuke the entry in the domain */
|
|
|
+ irq_domain_reset_irq_data(d);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If all interrupts have been freed, start mopping the floor */
|
|
|
+ if (bitmap_empty(its_dev->lpi_map, its_dev->nr_lpis)) {
|
|
|
+ its_lpi_free(its_dev->lpi_map,
|
|
|
+ its_dev->lpi_base,
|
|
|
+ its_dev->nr_lpis);
|
|
|
+
|
|
|
+ /* Unmap device/itt */
|
|
|
+ its_send_mapd(its_dev, 0);
|
|
|
+ its_free_device(its_dev);
|
|
|
+ }
|
|
|
+
|
|
|
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct irq_domain_ops its_domain_ops = {
|
|
|
+ .alloc = its_irq_domain_alloc,
|
|
|
+ .free = its_irq_domain_free,
|
|
|
+};
|