| .. | .. |
|---|
| 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); |
|---|
| .. | .. |
|---|
| 349 | 373 | * |
|---|
| 350 | 374 | * Broadom 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 | |
|---|