.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * ARM GIC v2m MSI(-X) support |
---|
3 | 4 | * Support for Message Signaled Interrupts for systems that |
---|
.. | .. |
---|
7 | 8 | * Authors: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com> |
---|
8 | 9 | * Harish Kasiviswanathan <harish.kasiviswanathan@amd.com> |
---|
9 | 10 | * Brandon Anderson <brandon.anderson@amd.com> |
---|
10 | | - * |
---|
11 | | - * This program is free software; you can redistribute it and/or modify it |
---|
12 | | - * under the terms of the GNU General Public License version 2 as published |
---|
13 | | - * by the Free Software Foundation. |
---|
14 | 11 | */ |
---|
15 | 12 | |
---|
16 | 13 | #define pr_fmt(fmt) "GICv2m: " fmt |
---|
.. | .. |
---|
20 | 17 | #include <linux/irq.h> |
---|
21 | 18 | #include <linux/irqdomain.h> |
---|
22 | 19 | #include <linux/kernel.h> |
---|
| 20 | +#include <linux/pci.h> |
---|
23 | 21 | #include <linux/msi.h> |
---|
24 | 22 | #include <linux/of_address.h> |
---|
25 | 23 | #include <linux/of_pci.h> |
---|
.. | .. |
---|
56 | 54 | |
---|
57 | 55 | /* List of flags for specific v2m implementation */ |
---|
58 | 56 | #define GICV2M_NEEDS_SPI_OFFSET 0x00000001 |
---|
| 57 | +#define GICV2M_GRAVITON_ADDRESS_ONLY 0x00000002 |
---|
59 | 58 | |
---|
60 | 59 | static LIST_HEAD(v2m_nodes); |
---|
61 | 60 | static DEFINE_SPINLOCK(v2m_lock); |
---|
.. | .. |
---|
98 | 97 | .chip = &gicv2m_msi_irq_chip, |
---|
99 | 98 | }; |
---|
100 | 99 | |
---|
| 100 | +static phys_addr_t gicv2m_get_msi_addr(struct v2m_data *v2m, int hwirq) |
---|
| 101 | +{ |
---|
| 102 | + if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) |
---|
| 103 | + return v2m->res.start | ((hwirq - 32) << 3); |
---|
| 104 | + else |
---|
| 105 | + return v2m->res.start + V2M_MSI_SETSPI_NS; |
---|
| 106 | +} |
---|
| 107 | + |
---|
101 | 108 | static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) |
---|
102 | 109 | { |
---|
103 | 110 | struct v2m_data *v2m = irq_data_get_irq_chip_data(data); |
---|
104 | | - phys_addr_t addr = v2m->res.start + V2M_MSI_SETSPI_NS; |
---|
| 111 | + phys_addr_t addr = gicv2m_get_msi_addr(v2m, data->hwirq); |
---|
105 | 112 | |
---|
106 | 113 | msg->address_hi = upper_32_bits(addr); |
---|
107 | 114 | msg->address_lo = lower_32_bits(addr); |
---|
108 | | - msg->data = data->hwirq; |
---|
109 | 115 | |
---|
| 116 | + if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) |
---|
| 117 | + msg->data = 0; |
---|
| 118 | + else |
---|
| 119 | + msg->data = data->hwirq; |
---|
110 | 120 | if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET) |
---|
111 | 121 | msg->data -= v2m->spi_offset; |
---|
112 | 122 | |
---|
113 | | - iommu_dma_map_msi_msg(data->irq, msg); |
---|
| 123 | + iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), msg); |
---|
114 | 124 | } |
---|
115 | 125 | |
---|
116 | 126 | static struct irq_chip gicv2m_irq_chip = { |
---|
.. | .. |
---|
167 | 177 | static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, |
---|
168 | 178 | unsigned int nr_irqs, void *args) |
---|
169 | 179 | { |
---|
| 180 | + msi_alloc_info_t *info = args; |
---|
170 | 181 | struct v2m_data *v2m = NULL, *tmp; |
---|
171 | 182 | int hwirq, offset, i, err = 0; |
---|
172 | 183 | |
---|
.. | .. |
---|
185 | 196 | return -ENOSPC; |
---|
186 | 197 | |
---|
187 | 198 | hwirq = v2m->spi_start + offset; |
---|
| 199 | + |
---|
| 200 | + err = iommu_dma_prepare_msi(info->desc, |
---|
| 201 | + gicv2m_get_msi_addr(v2m, hwirq)); |
---|
| 202 | + if (err) |
---|
| 203 | + return err; |
---|
188 | 204 | |
---|
189 | 205 | for (i = 0; i < nr_irqs; i++) { |
---|
190 | 206 | err = gicv2m_irq_gic_domain_alloc(domain, virq + i, hwirq + i); |
---|
.. | .. |
---|
301 | 317 | |
---|
302 | 318 | static int __init gicv2m_init_one(struct fwnode_handle *fwnode, |
---|
303 | 319 | u32 spi_start, u32 nr_spis, |
---|
304 | | - struct resource *res) |
---|
| 320 | + struct resource *res, u32 flags) |
---|
305 | 321 | { |
---|
306 | 322 | int ret; |
---|
307 | 323 | struct v2m_data *v2m; |
---|
.. | .. |
---|
314 | 330 | |
---|
315 | 331 | INIT_LIST_HEAD(&v2m->entry); |
---|
316 | 332 | v2m->fwnode = fwnode; |
---|
| 333 | + v2m->flags = flags; |
---|
317 | 334 | |
---|
318 | 335 | memcpy(&v2m->res, res, sizeof(struct resource)); |
---|
319 | 336 | |
---|
.. | .. |
---|
328 | 345 | v2m->spi_start = spi_start; |
---|
329 | 346 | v2m->nr_spis = nr_spis; |
---|
330 | 347 | } else { |
---|
331 | | - u32 typer = readl_relaxed(v2m->base + V2M_MSI_TYPER); |
---|
| 348 | + u32 typer; |
---|
| 349 | + |
---|
| 350 | + /* Graviton should always have explicit spi_start/nr_spis */ |
---|
| 351 | + if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) { |
---|
| 352 | + ret = -EINVAL; |
---|
| 353 | + goto err_iounmap; |
---|
| 354 | + } |
---|
| 355 | + typer = readl_relaxed(v2m->base + V2M_MSI_TYPER); |
---|
332 | 356 | |
---|
333 | 357 | v2m->spi_start = V2M_MSI_TYPER_BASE_SPI(typer); |
---|
334 | 358 | v2m->nr_spis = V2M_MSI_TYPER_NUM_SPI(typer); |
---|
.. | .. |
---|
347 | 371 | * the MSI data is the absolute value within the range from |
---|
348 | 372 | * spi_start to (spi_start + num_spis). |
---|
349 | 373 | * |
---|
350 | | - * Broadom NS2 GICv2m implementation has an erratum where the MSI data |
---|
| 374 | + * Broadcom NS2 GICv2m implementation has an erratum where the MSI data |
---|
351 | 375 | * is 'spi_number - 32' |
---|
| 376 | + * |
---|
| 377 | + * Reading that register fails on the Graviton implementation |
---|
352 | 378 | */ |
---|
353 | | - switch (readl_relaxed(v2m->base + V2M_MSI_IIDR)) { |
---|
354 | | - case XGENE_GICV2M_MSI_IIDR: |
---|
355 | | - v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; |
---|
356 | | - v2m->spi_offset = v2m->spi_start; |
---|
357 | | - break; |
---|
358 | | - case BCM_NS2_GICV2M_MSI_IIDR: |
---|
359 | | - v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; |
---|
360 | | - v2m->spi_offset = 32; |
---|
361 | | - break; |
---|
| 379 | + if (!(v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY)) { |
---|
| 380 | + switch (readl_relaxed(v2m->base + V2M_MSI_IIDR)) { |
---|
| 381 | + case XGENE_GICV2M_MSI_IIDR: |
---|
| 382 | + v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; |
---|
| 383 | + v2m->spi_offset = v2m->spi_start; |
---|
| 384 | + break; |
---|
| 385 | + case BCM_NS2_GICV2M_MSI_IIDR: |
---|
| 386 | + v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; |
---|
| 387 | + v2m->spi_offset = 32; |
---|
| 388 | + break; |
---|
| 389 | + } |
---|
362 | 390 | } |
---|
363 | | - |
---|
364 | 391 | v2m->bm = kcalloc(BITS_TO_LONGS(v2m->nr_spis), sizeof(long), |
---|
365 | 392 | GFP_KERNEL); |
---|
366 | 393 | if (!v2m->bm) { |
---|
.. | .. |
---|
413 | 440 | pr_info("DT overriding V2M MSI_TYPER (base:%u, num:%u)\n", |
---|
414 | 441 | spi_start, nr_spis); |
---|
415 | 442 | |
---|
416 | | - ret = gicv2m_init_one(&child->fwnode, spi_start, nr_spis, &res); |
---|
| 443 | + ret = gicv2m_init_one(&child->fwnode, spi_start, nr_spis, |
---|
| 444 | + &res, 0); |
---|
417 | 445 | if (ret) { |
---|
418 | 446 | of_node_put(child); |
---|
419 | 447 | break; |
---|
.. | .. |
---|
445 | 473 | return data->fwnode; |
---|
446 | 474 | } |
---|
447 | 475 | |
---|
| 476 | +static bool acpi_check_amazon_graviton_quirks(void) |
---|
| 477 | +{ |
---|
| 478 | + static struct acpi_table_madt *madt; |
---|
| 479 | + acpi_status status; |
---|
| 480 | + bool rc = false; |
---|
| 481 | + |
---|
| 482 | +#define ACPI_AMZN_OEM_ID "AMAZON" |
---|
| 483 | + |
---|
| 484 | + status = acpi_get_table(ACPI_SIG_MADT, 0, |
---|
| 485 | + (struct acpi_table_header **)&madt); |
---|
| 486 | + |
---|
| 487 | + if (ACPI_FAILURE(status) || !madt) |
---|
| 488 | + return rc; |
---|
| 489 | + rc = !memcmp(madt->header.oem_id, ACPI_AMZN_OEM_ID, ACPI_OEM_ID_SIZE); |
---|
| 490 | + acpi_put_table((struct acpi_table_header *)madt); |
---|
| 491 | + |
---|
| 492 | + return rc; |
---|
| 493 | +} |
---|
| 494 | + |
---|
448 | 495 | static int __init |
---|
449 | | -acpi_parse_madt_msi(struct acpi_subtable_header *header, |
---|
| 496 | +acpi_parse_madt_msi(union acpi_subtable_headers *header, |
---|
450 | 497 | const unsigned long end) |
---|
451 | 498 | { |
---|
452 | 499 | int ret; |
---|
.. | .. |
---|
454 | 501 | u32 spi_start = 0, nr_spis = 0; |
---|
455 | 502 | struct acpi_madt_generic_msi_frame *m; |
---|
456 | 503 | struct fwnode_handle *fwnode; |
---|
| 504 | + u32 flags = 0; |
---|
457 | 505 | |
---|
458 | 506 | m = (struct acpi_madt_generic_msi_frame *)header; |
---|
459 | 507 | if (BAD_MADT_ENTRY(m, end)) |
---|
.. | .. |
---|
463 | 511 | res.end = m->base_address + SZ_4K - 1; |
---|
464 | 512 | res.flags = IORESOURCE_MEM; |
---|
465 | 513 | |
---|
| 514 | + if (acpi_check_amazon_graviton_quirks()) { |
---|
| 515 | + pr_info("applying Amazon Graviton quirk\n"); |
---|
| 516 | + res.end = res.start + SZ_8K - 1; |
---|
| 517 | + flags |= GICV2M_GRAVITON_ADDRESS_ONLY; |
---|
| 518 | + gicv2m_msi_domain_info.flags &= ~MSI_FLAG_MULTI_PCI_MSI; |
---|
| 519 | + } |
---|
| 520 | + |
---|
466 | 521 | if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) { |
---|
467 | 522 | spi_start = m->spi_base; |
---|
468 | 523 | nr_spis = m->spi_count; |
---|
.. | .. |
---|
471 | 526 | spi_start, nr_spis); |
---|
472 | 527 | } |
---|
473 | 528 | |
---|
474 | | - fwnode = irq_domain_alloc_fwnode((void *)m->base_address); |
---|
| 529 | + fwnode = irq_domain_alloc_fwnode(&res.start); |
---|
475 | 530 | if (!fwnode) { |
---|
476 | 531 | pr_err("Unable to allocate GICv2m domain token\n"); |
---|
477 | 532 | return -EINVAL; |
---|
478 | 533 | } |
---|
479 | 534 | |
---|
480 | | - ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res); |
---|
| 535 | + ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res, flags); |
---|
481 | 536 | if (ret) |
---|
482 | 537 | irq_domain_free_fwnode(fwnode); |
---|
483 | 538 | |
---|