|
|
|
@ -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, |
|
|
|
|
}; |
|
|
|
|