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, | |||
| 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_do | ||
