Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 1 | #include <linux/types.h> |
| 2 | #include <linux/kernel.h> |
| 3 | #include <linux/irq.h> |
| 4 | #include <linux/smp.h> |
| 5 | #include <linux/interrupt.h> |
| 6 | #include <linux/init.h> |
| 7 | #include <linux/cpu.h> |
| 8 | #include <linux/of.h> |
| 9 | #include <linux/spinlock.h> |
| 10 | #include <linux/msi.h> |
| 11 | |
| 12 | #include <asm/prom.h> |
| 13 | #include <asm/smp.h> |
| 14 | #include <asm/machdep.h> |
| 15 | #include <asm/irq.h> |
| 16 | #include <asm/errno.h> |
| 17 | #include <asm/xics.h> |
| 18 | #include <asm/rtas.h> |
| 19 | |
| 20 | /* RTAS service tokens */ |
| 21 | static int ibm_get_xive; |
| 22 | static int ibm_set_xive; |
| 23 | static int ibm_int_on; |
| 24 | static int ibm_int_off; |
| 25 | |
| 26 | static int ics_rtas_map(struct ics *ics, unsigned int virq); |
| 27 | static void ics_rtas_mask_unknown(struct ics *ics, unsigned long vec); |
| 28 | static long ics_rtas_get_server(struct ics *ics, unsigned long vec); |
Michael Ellerman | 5ca1237 | 2011-04-14 22:31:59 +0000 | [diff] [blame] | 29 | static int ics_rtas_host_match(struct ics *ics, struct device_node *node); |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 30 | |
| 31 | /* Only one global & state struct ics */ |
| 32 | static struct ics ics_rtas = { |
| 33 | .map = ics_rtas_map, |
| 34 | .mask_unknown = ics_rtas_mask_unknown, |
| 35 | .get_server = ics_rtas_get_server, |
Michael Ellerman | 5ca1237 | 2011-04-14 22:31:59 +0000 | [diff] [blame] | 36 | .host_match = ics_rtas_host_match, |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 37 | }; |
| 38 | |
| 39 | static void ics_rtas_unmask_irq(struct irq_data *d) |
| 40 | { |
Grant Likely | 476eb49 | 2011-05-04 15:02:15 +1000 | [diff] [blame] | 41 | unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d); |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 42 | int call_status; |
| 43 | int server; |
| 44 | |
| 45 | pr_devel("xics: unmask virq %d [hw 0x%x]\n", d->irq, hw_irq); |
| 46 | |
| 47 | if (hw_irq == XICS_IPI || hw_irq == XICS_IRQ_SPURIOUS) |
| 48 | return; |
| 49 | |
Jiang Liu | da92b4e | 2015-06-01 16:05:33 +0800 | [diff] [blame] | 50 | server = xics_get_irq_server(d->irq, irq_data_get_affinity_mask(d), 0); |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 51 | |
| 52 | call_status = rtas_call(ibm_set_xive, 3, 1, NULL, hw_irq, server, |
| 53 | DEFAULT_PRIORITY); |
| 54 | if (call_status != 0) { |
| 55 | printk(KERN_ERR |
| 56 | "%s: ibm_set_xive irq %u server %x returned %d\n", |
| 57 | __func__, hw_irq, server, call_status); |
| 58 | return; |
| 59 | } |
| 60 | |
| 61 | /* Now unmask the interrupt (often a no-op) */ |
| 62 | call_status = rtas_call(ibm_int_on, 1, 1, NULL, hw_irq); |
| 63 | if (call_status != 0) { |
| 64 | printk(KERN_ERR "%s: ibm_int_on irq=%u returned %d\n", |
| 65 | __func__, hw_irq, call_status); |
| 66 | return; |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | static unsigned int ics_rtas_startup(struct irq_data *d) |
| 71 | { |
| 72 | #ifdef CONFIG_PCI_MSI |
| 73 | /* |
| 74 | * The generic MSI code returns with the interrupt disabled on the |
| 75 | * card, using the MSI mask bits. Firmware doesn't appear to unmask |
| 76 | * at that level, so we do it here by hand. |
| 77 | */ |
Jiang Liu | 507a883 | 2015-06-01 16:05:42 +0800 | [diff] [blame] | 78 | if (irq_data_get_msi_desc(d)) |
Thomas Gleixner | 280510f | 2014-11-23 12:23:20 +0100 | [diff] [blame] | 79 | pci_msi_unmask_irq(d); |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 80 | #endif |
| 81 | /* unmask it */ |
| 82 | ics_rtas_unmask_irq(d); |
| 83 | return 0; |
| 84 | } |
| 85 | |
| 86 | static void ics_rtas_mask_real_irq(unsigned int hw_irq) |
| 87 | { |
| 88 | int call_status; |
| 89 | |
| 90 | if (hw_irq == XICS_IPI) |
| 91 | return; |
| 92 | |
| 93 | call_status = rtas_call(ibm_int_off, 1, 1, NULL, hw_irq); |
| 94 | if (call_status != 0) { |
| 95 | printk(KERN_ERR "%s: ibm_int_off irq=%u returned %d\n", |
| 96 | __func__, hw_irq, call_status); |
| 97 | return; |
| 98 | } |
| 99 | |
| 100 | /* Have to set XIVE to 0xff to be able to remove a slot */ |
| 101 | call_status = rtas_call(ibm_set_xive, 3, 1, NULL, hw_irq, |
| 102 | xics_default_server, 0xff); |
| 103 | if (call_status != 0) { |
| 104 | printk(KERN_ERR "%s: ibm_set_xive(0xff) irq=%u returned %d\n", |
| 105 | __func__, hw_irq, call_status); |
| 106 | return; |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | static void ics_rtas_mask_irq(struct irq_data *d) |
| 111 | { |
Grant Likely | 476eb49 | 2011-05-04 15:02:15 +1000 | [diff] [blame] | 112 | unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d); |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 113 | |
| 114 | pr_devel("xics: mask virq %d [hw 0x%x]\n", d->irq, hw_irq); |
| 115 | |
| 116 | if (hw_irq == XICS_IPI || hw_irq == XICS_IRQ_SPURIOUS) |
| 117 | return; |
| 118 | ics_rtas_mask_real_irq(hw_irq); |
| 119 | } |
| 120 | |
| 121 | static int ics_rtas_set_affinity(struct irq_data *d, |
| 122 | const struct cpumask *cpumask, |
| 123 | bool force) |
| 124 | { |
Grant Likely | 476eb49 | 2011-05-04 15:02:15 +1000 | [diff] [blame] | 125 | unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d); |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 126 | int status; |
| 127 | int xics_status[2]; |
| 128 | int irq_server; |
| 129 | |
| 130 | if (hw_irq == XICS_IPI || hw_irq == XICS_IRQ_SPURIOUS) |
| 131 | return -1; |
| 132 | |
| 133 | status = rtas_call(ibm_get_xive, 1, 3, xics_status, hw_irq); |
| 134 | |
| 135 | if (status) { |
| 136 | printk(KERN_ERR "%s: ibm,get-xive irq=%u returns %d\n", |
| 137 | __func__, hw_irq, status); |
| 138 | return -1; |
| 139 | } |
| 140 | |
| 141 | irq_server = xics_get_irq_server(d->irq, cpumask, 1); |
| 142 | if (irq_server == -1) { |
Tejun Heo | 0c118b7b | 2015-02-13 14:37:06 -0800 | [diff] [blame] | 143 | pr_warning("%s: No online cpus in the mask %*pb for irq %d\n", |
| 144 | __func__, cpumask_pr_args(cpumask), d->irq); |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 145 | return -1; |
| 146 | } |
| 147 | |
| 148 | status = rtas_call(ibm_set_xive, 3, 1, NULL, |
| 149 | hw_irq, irq_server, xics_status[1]); |
| 150 | |
| 151 | if (status) { |
| 152 | printk(KERN_ERR "%s: ibm,set-xive irq=%u returns %d\n", |
| 153 | __func__, hw_irq, status); |
| 154 | return -1; |
| 155 | } |
| 156 | |
| 157 | return IRQ_SET_MASK_OK; |
| 158 | } |
| 159 | |
| 160 | static struct irq_chip ics_rtas_irq_chip = { |
| 161 | .name = "XICS", |
| 162 | .irq_startup = ics_rtas_startup, |
| 163 | .irq_mask = ics_rtas_mask_irq, |
| 164 | .irq_unmask = ics_rtas_unmask_irq, |
| 165 | .irq_eoi = NULL, /* Patched at init time */ |
| 166 | .irq_set_affinity = ics_rtas_set_affinity |
| 167 | }; |
| 168 | |
| 169 | static int ics_rtas_map(struct ics *ics, unsigned int virq) |
| 170 | { |
Grant Likely | 476eb49 | 2011-05-04 15:02:15 +1000 | [diff] [blame] | 171 | unsigned int hw_irq = (unsigned int)virq_to_hw(virq); |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 172 | int status[2]; |
| 173 | int rc; |
| 174 | |
| 175 | if (WARN_ON(hw_irq == XICS_IPI || hw_irq == XICS_IRQ_SPURIOUS)) |
| 176 | return -EINVAL; |
| 177 | |
| 178 | /* Check if RTAS knows about this interrupt */ |
| 179 | rc = rtas_call(ibm_get_xive, 1, 3, status, hw_irq); |
| 180 | if (rc) |
| 181 | return -ENXIO; |
| 182 | |
| 183 | irq_set_chip_and_handler(virq, &ics_rtas_irq_chip, handle_fasteoi_irq); |
| 184 | irq_set_chip_data(virq, &ics_rtas); |
| 185 | |
| 186 | return 0; |
| 187 | } |
| 188 | |
| 189 | static void ics_rtas_mask_unknown(struct ics *ics, unsigned long vec) |
| 190 | { |
| 191 | ics_rtas_mask_real_irq(vec); |
| 192 | } |
| 193 | |
| 194 | static long ics_rtas_get_server(struct ics *ics, unsigned long vec) |
| 195 | { |
| 196 | int rc, status[2]; |
| 197 | |
| 198 | rc = rtas_call(ibm_get_xive, 1, 3, status, vec); |
| 199 | if (rc) |
| 200 | return -1; |
| 201 | return status[0]; |
| 202 | } |
| 203 | |
Michael Ellerman | 5ca1237 | 2011-04-14 22:31:59 +0000 | [diff] [blame] | 204 | static int ics_rtas_host_match(struct ics *ics, struct device_node *node) |
| 205 | { |
| 206 | /* IBM machines have interrupt parents of various funky types for things |
| 207 | * like vdevices, events, etc... The trick we use here is to match |
| 208 | * everything here except the legacy 8259 which is compatible "chrp,iic" |
| 209 | */ |
| 210 | return !of_device_is_compatible(node, "chrp,iic"); |
| 211 | } |
| 212 | |
Daniel Borkmann | 174ea47 | 2013-02-05 05:07:06 +0000 | [diff] [blame] | 213 | __init int ics_rtas_init(void) |
Benjamin Herrenschmidt | 0b05ac6 | 2011-04-04 13:46:58 +1000 | [diff] [blame] | 214 | { |
| 215 | ibm_get_xive = rtas_token("ibm,get-xive"); |
| 216 | ibm_set_xive = rtas_token("ibm,set-xive"); |
| 217 | ibm_int_on = rtas_token("ibm,int-on"); |
| 218 | ibm_int_off = rtas_token("ibm,int-off"); |
| 219 | |
| 220 | /* We enable the RTAS "ICS" if RTAS is present with the |
| 221 | * appropriate tokens |
| 222 | */ |
| 223 | if (ibm_get_xive == RTAS_UNKNOWN_SERVICE || |
| 224 | ibm_set_xive == RTAS_UNKNOWN_SERVICE) |
| 225 | return -ENODEV; |
| 226 | |
| 227 | /* We need to patch our irq chip's EOI to point to the |
| 228 | * right ICP |
| 229 | */ |
| 230 | ics_rtas_irq_chip.irq_eoi = icp_ops->eoi; |
| 231 | |
| 232 | /* Register ourselves */ |
| 233 | xics_register_ics(&ics_rtas); |
| 234 | |
| 235 | return 0; |
| 236 | } |
| 237 | |