diff options
| -rw-r--r-- | drivers/irqchip/irq-gic-v3-its.c | 176 | ||||
| -rw-r--r-- | include/linux/irqchip/arm-gic-v3.h | 6 | 
2 files changed, 182 insertions, 0 deletions
| diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index d687fd43fbbb..532c6df89992 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -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, +}; diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index 21c9d70426d1..0ed30d7d9338 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -295,6 +295,12 @@  #include <linux/stringify.h> +/* + * We need a value to serve as a irq-type for LPIs. Choose one that will + * hopefully pique the interest of the reviewer. + */ +#define GIC_IRQ_TYPE_LPI		0xa110c8ed +  struct rdists {  	struct {  		void __iomem	*rd_base; | 
