diff options
author | Marc Zyngier <marc.zyngier@arm.com> | 2014-11-24 09:35:16 -0500 |
---|---|---|
committer | Jason Cooper <jason@lakedaemon.net> | 2014-11-26 10:55:14 -0500 |
commit | b48ac83d6bbc20a973c3e8133fd1ebda873d026a (patch) | |
tree | f592163f05badbefbba54110f89e647145b06881 /drivers/irqchip | |
parent | 84a6a2e7fc18dae444c5c88cc6af8878552867a5 (diff) |
irqchip: GICv3: ITS: MSI support
Now, the bit of code that allow us to use the ITS as a MSI controller.
Both MSI and MSI-X are supported.
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Link: https://lkml.kernel.org/r/1416839720-18400-10-git-send-email-marc.zyngier@arm.com
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
Diffstat (limited to 'drivers/irqchip')
-rw-r--r-- | drivers/irqchip/irq-gic-v3-its.c | 176 |
1 files changed, 176 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, | |||
587 | return IRQ_SET_MASK_OK_DONE; | 587 | return IRQ_SET_MASK_OK_DONE; |
588 | } | 588 | } |
589 | 589 | ||
590 | static void its_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) | ||
591 | { | ||
592 | struct its_device *its_dev = irq_data_get_irq_chip_data(d); | ||
593 | struct its_node *its; | ||
594 | u64 addr; | ||
595 | |||
596 | its = its_dev->its; | ||
597 | addr = its->phys_base + GITS_TRANSLATER; | ||
598 | |||
599 | msg->address_lo = addr & ((1UL << 32) - 1); | ||
600 | msg->address_hi = addr >> 32; | ||
601 | msg->data = its_get_event_id(d); | ||
602 | } | ||
603 | |||
590 | static struct irq_chip its_irq_chip = { | 604 | static struct irq_chip its_irq_chip = { |
591 | .name = "ITS", | 605 | .name = "ITS", |
592 | .irq_mask = its_mask_irq, | 606 | .irq_mask = its_mask_irq, |
593 | .irq_unmask = its_unmask_irq, | 607 | .irq_unmask = its_unmask_irq, |
594 | .irq_eoi = its_eoi_irq, | 608 | .irq_eoi = its_eoi_irq, |
595 | .irq_set_affinity = its_set_affinity, | 609 | .irq_set_affinity = its_set_affinity, |
610 | .irq_compose_msi_msg = its_irq_compose_msi_msg, | ||
611 | }; | ||
612 | |||
613 | static void its_mask_msi_irq(struct irq_data *d) | ||
614 | { | ||
615 | pci_msi_mask_irq(d); | ||
616 | irq_chip_mask_parent(d); | ||
617 | } | ||
618 | |||
619 | static void its_unmask_msi_irq(struct irq_data *d) | ||
620 | { | ||
621 | pci_msi_unmask_irq(d); | ||
622 | irq_chip_unmask_parent(d); | ||
623 | } | ||
624 | |||
625 | static struct irq_chip its_msi_irq_chip = { | ||
626 | .name = "ITS-MSI", | ||
627 | .irq_unmask = its_unmask_msi_irq, | ||
628 | .irq_mask = its_mask_msi_irq, | ||
629 | .irq_eoi = irq_chip_eoi_parent, | ||
630 | .irq_write_msi_msg = pci_msi_domain_write_msg, | ||
596 | }; | 631 | }; |
597 | 632 | ||
598 | /* | 633 | /* |
@@ -1055,3 +1090,144 @@ static void its_free_device(struct its_device *its_dev) | |||
1055 | kfree(its_dev->itt); | 1090 | kfree(its_dev->itt); |
1056 | kfree(its_dev); | 1091 | kfree(its_dev); |
1057 | } | 1092 | } |
1093 | |||
1094 | static int its_alloc_device_irq(struct its_device *dev, irq_hw_number_t *hwirq) | ||
1095 | { | ||
1096 | int idx; | ||
1097 | |||
1098 | idx = find_first_zero_bit(dev->lpi_map, dev->nr_lpis); | ||
1099 | if (idx == dev->nr_lpis) | ||
1100 | return -ENOSPC; | ||
1101 | |||
1102 | *hwirq = dev->lpi_base + idx; | ||
1103 | set_bit(idx, dev->lpi_map); | ||
1104 | |||
1105 | /* Map the GIC irq ID to the device */ | ||
1106 | its_send_mapvi(dev, *hwirq, idx); | ||
1107 | |||
1108 | return 0; | ||
1109 | } | ||
1110 | |||
1111 | static int its_msi_prepare(struct irq_domain *domain, struct device *dev, | ||
1112 | int nvec, msi_alloc_info_t *info) | ||
1113 | { | ||
1114 | struct pci_dev *pdev; | ||
1115 | struct its_node *its; | ||
1116 | u32 dev_id; | ||
1117 | struct its_device *its_dev; | ||
1118 | |||
1119 | if (!dev_is_pci(dev)) | ||
1120 | return -EINVAL; | ||
1121 | |||
1122 | pdev = to_pci_dev(dev); | ||
1123 | dev_id = PCI_DEVID(pdev->bus->number, pdev->devfn); | ||
1124 | its = domain->parent->host_data; | ||
1125 | |||
1126 | its_dev = its_find_device(its, dev_id); | ||
1127 | if (WARN_ON(its_dev)) | ||
1128 | return -EINVAL; | ||
1129 | |||
1130 | its_dev = its_create_device(its, dev_id, nvec); | ||
1131 | if (!its_dev) | ||
1132 | return -ENOMEM; | ||
1133 | |||
1134 | dev_dbg(&pdev->dev, "ITT %d entries, %d bits\n", nvec, ilog2(nvec)); | ||
1135 | |||
1136 | info->scratchpad[0].ptr = its_dev; | ||
1137 | info->scratchpad[1].ptr = dev; | ||
1138 | return 0; | ||
1139 | } | ||
1140 | |||
1141 | static struct msi_domain_ops its_pci_msi_ops = { | ||
1142 | .msi_prepare = its_msi_prepare, | ||
1143 | }; | ||
1144 | |||
1145 | static struct msi_domain_info its_pci_msi_domain_info = { | ||
1146 | .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | | ||
1147 | MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX), | ||
1148 | .ops = &its_pci_msi_ops, | ||
1149 | .chip = &its_msi_irq_chip, | ||
1150 | }; | ||
1151 | |||
1152 | static int its_irq_gic_domain_alloc(struct irq_domain *domain, | ||
1153 | unsigned int virq, | ||
1154 | irq_hw_number_t hwirq) | ||
1155 | { | ||
1156 | struct of_phandle_args args; | ||
1157 | |||
1158 | args.np = domain->parent->of_node; | ||
1159 | args.args_count = 3; | ||
1160 | args.args[0] = GIC_IRQ_TYPE_LPI; | ||
1161 | args.args[1] = hwirq; | ||
1162 | args.args[2] = IRQ_TYPE_EDGE_RISING; | ||
1163 | |||
1164 | return irq_domain_alloc_irqs_parent(domain, virq, 1, &args); | ||
1165 | } | ||
1166 | |||
1167 | static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, | ||
1168 | unsigned int nr_irqs, void *args) | ||
1169 | { | ||
1170 | msi_alloc_info_t *info = args; | ||
1171 | struct its_device *its_dev = info->scratchpad[0].ptr; | ||
1172 | irq_hw_number_t hwirq; | ||
1173 | int err; | ||
1174 | int i; | ||
1175 | |||
1176 | for (i = 0; i < nr_irqs; i++) { | ||
1177 | err = its_alloc_device_irq(its_dev, &hwirq); | ||
1178 | if (err) | ||
1179 | return err; | ||
1180 | |||
1181 | err = its_irq_gic_domain_alloc(domain, virq + i, hwirq); | ||
1182 | if (err) | ||
1183 | return err; | ||
1184 | |||
1185 | irq_domain_set_hwirq_and_chip(domain, virq + i, | ||
1186 | hwirq, &its_irq_chip, its_dev); | ||
1187 | dev_dbg(info->scratchpad[1].ptr, "ID:%d pID:%d vID:%d\n", | ||
1188 | (int)(hwirq - its_dev->lpi_base), (int)hwirq, virq + i); | ||
1189 | } | ||
1190 | |||
1191 | return 0; | ||
1192 | } | ||
1193 | |||
1194 | static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq, | ||
1195 | unsigned int nr_irqs) | ||
1196 | { | ||
1197 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); | ||
1198 | struct its_device *its_dev = irq_data_get_irq_chip_data(d); | ||
1199 | int i; | ||
1200 | |||
1201 | for (i = 0; i < nr_irqs; i++) { | ||
1202 | struct irq_data *data = irq_domain_get_irq_data(domain, | ||
1203 | virq + i); | ||
1204 | int event = its_get_event_id(data); | ||
1205 | |||
1206 | /* Stop the delivery of interrupts */ | ||
1207 | its_send_discard(its_dev, event); | ||
1208 | |||
1209 | /* Mark interrupt index as unused */ | ||
1210 | clear_bit(event, its_dev->lpi_map); | ||
1211 | |||
1212 | /* Nuke the entry in the domain */ | ||
1213 | irq_domain_reset_irq_data(d); | ||
1214 | } | ||
1215 | |||
1216 | /* If all interrupts have been freed, start mopping the floor */ | ||
1217 | if (bitmap_empty(its_dev->lpi_map, its_dev->nr_lpis)) { | ||
1218 | its_lpi_free(its_dev->lpi_map, | ||
1219 | its_dev->lpi_base, | ||
1220 | its_dev->nr_lpis); | ||
1221 | |||
1222 | /* Unmap device/itt */ | ||
1223 | its_send_mapd(its_dev, 0); | ||
1224 | its_free_device(its_dev); | ||
1225 | } | ||
1226 | |||
1227 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | ||
1228 | } | ||
1229 | |||
1230 | static const struct irq_domain_ops its_domain_ops = { | ||
1231 | .alloc = its_irq_domain_alloc, | ||
1232 | .free = its_irq_domain_free, | ||
1233 | }; | ||