// SPDX-License-Identifier: GPL-2.0-only /* * Copyright © 2022-2024 Rivos Inc. * Copyright © 2023 FORTH-ICS/CARV * * RISCV IOMMU as a PCIe device * * Authors * Tomasz Jeznach * Nick Kossifidis */ #include #include #include #include #include #include "iommu-bits.h" #include "iommu.h" /* QEMU RISC-V IOMMU implementation */ #define PCI_DEVICE_ID_REDHAT_RISCV_IOMMU 0x0014 /* Rivos Inc. assigned PCI Vendor and Device IDs */ #ifndef PCI_VENDOR_ID_RIVOS #define PCI_VENDOR_ID_RIVOS 0x1efd #endif #define PCI_DEVICE_ID_RIVOS_RISCV_IOMMU_GA 0x0008 static int riscv_iommu_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct device *dev = &pdev->dev; struct riscv_iommu_device *iommu; int rc, vec; rc = pcim_enable_device(pdev); if (rc) return rc; if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) return -ENODEV; if (pci_resource_len(pdev, 0) < RISCV_IOMMU_REG_SIZE) return -ENODEV; rc = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev)); if (rc) return dev_err_probe(dev, rc, "pcim_iomap_regions failed\n"); iommu = devm_kzalloc(dev, sizeof(*iommu), GFP_KERNEL); if (!iommu) return -ENOMEM; iommu->dev = dev; iommu->reg = pcim_iomap_table(pdev)[0]; pci_set_master(pdev); dev_set_drvdata(dev, iommu); /* Check device reported capabilities / features. */ iommu->caps = riscv_iommu_readq(iommu, RISCV_IOMMU_REG_CAPABILITIES); iommu->fctl = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_FCTL); /* The PCI driver only uses MSIs, make sure the IOMMU supports this */ switch (FIELD_GET(RISCV_IOMMU_CAPABILITIES_IGS, iommu->caps)) { case RISCV_IOMMU_CAPABILITIES_IGS_MSI: case RISCV_IOMMU_CAPABILITIES_IGS_BOTH: break; default: return dev_err_probe(dev, -ENODEV, "unable to use message-signaled interrupts\n"); } /* Allocate and assign IRQ vectors for the various events */ rc = pci_alloc_irq_vectors(pdev, 1, RISCV_IOMMU_INTR_COUNT, PCI_IRQ_MSIX | PCI_IRQ_MSI); if (rc <= 0) return dev_err_probe(dev, -ENODEV, "unable to allocate irq vectors\n"); iommu->irqs_count = rc; for (vec = 0; vec < iommu->irqs_count; vec++) iommu->irqs[vec] = msi_get_virq(dev, vec); /* Enable message-signaled interrupts, fctl.WSI */ if (iommu->fctl & RISCV_IOMMU_FCTL_WSI) { iommu->fctl ^= RISCV_IOMMU_FCTL_WSI; riscv_iommu_writel(iommu, RISCV_IOMMU_REG_FCTL, iommu->fctl); } return riscv_iommu_init(iommu); } static void riscv_iommu_pci_remove(struct pci_dev *pdev) { struct riscv_iommu_device *iommu = dev_get_drvdata(&pdev->dev); riscv_iommu_remove(iommu); } static const struct pci_device_id riscv_iommu_pci_tbl[] = { {PCI_VDEVICE(REDHAT, PCI_DEVICE_ID_REDHAT_RISCV_IOMMU), 0}, {PCI_VDEVICE(RIVOS, PCI_DEVICE_ID_RIVOS_RISCV_IOMMU_GA), 0}, {0,} }; static struct pci_driver riscv_iommu_pci_driver = { .name = KBUILD_MODNAME, .id_table = riscv_iommu_pci_tbl, .probe = riscv_iommu_pci_probe, .remove = riscv_iommu_pci_remove, .driver = { .suppress_bind_attrs = true, }, }; builtin_pci_driver(riscv_iommu_pci_driver);