|
13 | 13 | #include <asm/hw_irq.h>
|
14 | 14 | #include <asm/ppc-pci.h>
|
15 | 15 | #include <asm/machdep.h>
|
| 16 | +#include <asm/xive.h> |
16 | 17 |
|
17 | 18 | #include "pseries.h"
|
18 | 19 |
|
@@ -518,6 +519,190 @@ static int rtas_setup_msi_irqs(struct pci_dev *pdev, int nvec_in, int type)
|
518 | 519 | return 0;
|
519 | 520 | }
|
520 | 521 |
|
| 522 | +static int pseries_msi_ops_prepare(struct irq_domain *domain, struct device *dev, |
| 523 | + int nvec, msi_alloc_info_t *arg) |
| 524 | +{ |
| 525 | + struct pci_dev *pdev = to_pci_dev(dev); |
| 526 | + struct msi_desc *desc = first_pci_msi_entry(pdev); |
| 527 | + int type = desc->msi_attrib.is_msix ? PCI_CAP_ID_MSIX : PCI_CAP_ID_MSI; |
| 528 | + |
| 529 | + return rtas_prepare_msi_irqs(pdev, nvec, type, arg); |
| 530 | +} |
| 531 | + |
| 532 | +static struct msi_domain_ops pseries_pci_msi_domain_ops = { |
| 533 | + .msi_prepare = pseries_msi_ops_prepare, |
| 534 | +}; |
| 535 | + |
| 536 | +static void pseries_msi_shutdown(struct irq_data *d) |
| 537 | +{ |
| 538 | + d = d->parent_data; |
| 539 | + if (d->chip->irq_shutdown) |
| 540 | + d->chip->irq_shutdown(d); |
| 541 | +} |
| 542 | + |
| 543 | +static void pseries_msi_mask(struct irq_data *d) |
| 544 | +{ |
| 545 | + pci_msi_mask_irq(d); |
| 546 | + irq_chip_mask_parent(d); |
| 547 | +} |
| 548 | + |
| 549 | +static void pseries_msi_unmask(struct irq_data *d) |
| 550 | +{ |
| 551 | + pci_msi_unmask_irq(d); |
| 552 | + irq_chip_unmask_parent(d); |
| 553 | +} |
| 554 | + |
| 555 | +static struct irq_chip pseries_pci_msi_irq_chip = { |
| 556 | + .name = "pSeries-PCI-MSI", |
| 557 | + .irq_shutdown = pseries_msi_shutdown, |
| 558 | + .irq_mask = pseries_msi_mask, |
| 559 | + .irq_unmask = pseries_msi_unmask, |
| 560 | + .irq_eoi = irq_chip_eoi_parent, |
| 561 | +}; |
| 562 | + |
| 563 | +static struct msi_domain_info pseries_msi_domain_info = { |
| 564 | + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | |
| 565 | + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX), |
| 566 | + .ops = &pseries_pci_msi_domain_ops, |
| 567 | + .chip = &pseries_pci_msi_irq_chip, |
| 568 | +}; |
| 569 | + |
| 570 | +static void pseries_msi_compose_msg(struct irq_data *data, struct msi_msg *msg) |
| 571 | +{ |
| 572 | + __pci_read_msi_msg(irq_data_get_msi_desc(data), msg); |
| 573 | +} |
| 574 | + |
| 575 | +static struct irq_chip pseries_msi_irq_chip = { |
| 576 | + .name = "pSeries-MSI", |
| 577 | + .irq_shutdown = pseries_msi_shutdown, |
| 578 | + .irq_mask = irq_chip_mask_parent, |
| 579 | + .irq_unmask = irq_chip_unmask_parent, |
| 580 | + .irq_eoi = irq_chip_eoi_parent, |
| 581 | + .irq_set_affinity = irq_chip_set_affinity_parent, |
| 582 | + .irq_compose_msi_msg = pseries_msi_compose_msg, |
| 583 | +}; |
| 584 | + |
| 585 | +static int pseries_irq_parent_domain_alloc(struct irq_domain *domain, unsigned int virq, |
| 586 | + irq_hw_number_t hwirq) |
| 587 | +{ |
| 588 | + struct irq_fwspec parent_fwspec; |
| 589 | + int ret; |
| 590 | + |
| 591 | + parent_fwspec.fwnode = domain->parent->fwnode; |
| 592 | + parent_fwspec.param_count = 2; |
| 593 | + parent_fwspec.param[0] = hwirq; |
| 594 | + parent_fwspec.param[1] = IRQ_TYPE_EDGE_RISING; |
| 595 | + |
| 596 | + ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec); |
| 597 | + if (ret) |
| 598 | + return ret; |
| 599 | + |
| 600 | + return 0; |
| 601 | +} |
| 602 | + |
| 603 | +static int pseries_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, |
| 604 | + unsigned int nr_irqs, void *arg) |
| 605 | +{ |
| 606 | + struct pci_controller *phb = domain->host_data; |
| 607 | + msi_alloc_info_t *info = arg; |
| 608 | + struct msi_desc *desc = info->desc; |
| 609 | + struct pci_dev *pdev = msi_desc_to_pci_dev(desc); |
| 610 | + int hwirq; |
| 611 | + int i, ret; |
| 612 | + |
| 613 | + hwirq = rtas_query_irq_number(pci_get_pdn(pdev), desc->msi_attrib.entry_nr); |
| 614 | + if (hwirq < 0) { |
| 615 | + dev_err(&pdev->dev, "Failed to query HW IRQ: %d\n", hwirq); |
| 616 | + return hwirq; |
| 617 | + } |
| 618 | + |
| 619 | + dev_dbg(&pdev->dev, "%s bridge %pOF %d/%x #%d\n", __func__, |
| 620 | + phb->dn, virq, hwirq, nr_irqs); |
| 621 | + |
| 622 | + for (i = 0; i < nr_irqs; i++) { |
| 623 | + ret = pseries_irq_parent_domain_alloc(domain, virq + i, hwirq + i); |
| 624 | + if (ret) |
| 625 | + goto out; |
| 626 | + |
| 627 | + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, |
| 628 | + &pseries_msi_irq_chip, domain->host_data); |
| 629 | + } |
| 630 | + |
| 631 | + return 0; |
| 632 | + |
| 633 | +out: |
| 634 | + /* TODO: handle RTAS cleanup in ->msi_finish() ? */ |
| 635 | + irq_domain_free_irqs_parent(domain, virq, i - 1); |
| 636 | + return ret; |
| 637 | +} |
| 638 | + |
| 639 | +static void pseries_irq_domain_free(struct irq_domain *domain, unsigned int virq, |
| 640 | + unsigned int nr_irqs) |
| 641 | +{ |
| 642 | + struct irq_data *d = irq_domain_get_irq_data(domain, virq); |
| 643 | + struct pci_controller *phb = irq_data_get_irq_chip_data(d); |
| 644 | + |
| 645 | + pr_debug("%s bridge %pOF %d #%d\n", __func__, phb->dn, virq, nr_irqs); |
| 646 | + |
| 647 | + irq_domain_free_irqs_parent(domain, virq, nr_irqs); |
| 648 | +} |
| 649 | + |
| 650 | +static const struct irq_domain_ops pseries_irq_domain_ops = { |
| 651 | + .alloc = pseries_irq_domain_alloc, |
| 652 | + .free = pseries_irq_domain_free, |
| 653 | +}; |
| 654 | + |
| 655 | +static int __pseries_msi_allocate_domains(struct pci_controller *phb, |
| 656 | + unsigned int count) |
| 657 | +{ |
| 658 | + struct irq_domain *parent = irq_get_default_host(); |
| 659 | + |
| 660 | + phb->fwnode = irq_domain_alloc_named_id_fwnode("pSeries-MSI", |
| 661 | + phb->global_number); |
| 662 | + if (!phb->fwnode) |
| 663 | + return -ENOMEM; |
| 664 | + |
| 665 | + phb->dev_domain = irq_domain_create_hierarchy(parent, 0, count, |
| 666 | + phb->fwnode, |
| 667 | + &pseries_irq_domain_ops, phb); |
| 668 | + if (!phb->dev_domain) { |
| 669 | + pr_err("PCI: failed to create IRQ domain bridge %pOF (domain %d)\n", |
| 670 | + phb->dn, phb->global_number); |
| 671 | + irq_domain_free_fwnode(phb->fwnode); |
| 672 | + return -ENOMEM; |
| 673 | + } |
| 674 | + |
| 675 | + phb->msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(phb->dn), |
| 676 | + &pseries_msi_domain_info, |
| 677 | + phb->dev_domain); |
| 678 | + if (!phb->msi_domain) { |
| 679 | + pr_err("PCI: failed to create MSI IRQ domain bridge %pOF (domain %d)\n", |
| 680 | + phb->dn, phb->global_number); |
| 681 | + irq_domain_free_fwnode(phb->fwnode); |
| 682 | + irq_domain_remove(phb->dev_domain); |
| 683 | + return -ENOMEM; |
| 684 | + } |
| 685 | + |
| 686 | + return 0; |
| 687 | +} |
| 688 | + |
| 689 | +int pseries_msi_allocate_domains(struct pci_controller *phb) |
| 690 | +{ |
| 691 | + int count; |
| 692 | + |
| 693 | + /* Only supported by the XIVE driver */ |
| 694 | + if (!xive_enabled()) |
| 695 | + return -ENODEV; |
| 696 | + |
| 697 | + if (!__find_pe_total_msi(phb->dn, &count)) { |
| 698 | + pr_err("PCI: failed to find MSIs for bridge %pOF (domain %d)\n", |
| 699 | + phb->dn, phb->global_number); |
| 700 | + return -ENOSPC; |
| 701 | + } |
| 702 | + |
| 703 | + return __pseries_msi_allocate_domains(phb, count); |
| 704 | +} |
| 705 | + |
521 | 706 | static void rtas_msi_pci_irq_fixup(struct pci_dev *pdev)
|
522 | 707 | {
|
523 | 708 | /* No LSI -> leave MSIs (if any) configured */
|
|
0 commit comments