On Thu, Jul 10, 2025 at 2:28 PM Sunil V L <sunilvl@xxxxxxxxxxxxxxxx> wrote: > > RISC-V IO Mapping Table (RIMT) is a static ACPI table to communicate > IOMMU information to the OS. The spec is available at [1]. > > The changes at high level are, > a) Initialize data structures required for IOMMU/device > configuration using the data from RIMT. Provide APIs required > for device configuration. > b) Provide an API for IOMMU drivers to register the > fwnode with RIMT data structures. This API will create a > fwnode for PCIe IOMMU. > > [1] - https://github.com/riscv-non-isa/riscv-acpi-rimt > > Signed-off-by: Sunil V L <sunilvl@xxxxxxxxxxxxxxxx> > --- > MAINTAINERS | 1 + > arch/riscv/Kconfig | 1 + > drivers/acpi/Kconfig | 4 + > drivers/acpi/riscv/Kconfig | 7 + > drivers/acpi/riscv/Makefile | 1 + > drivers/acpi/riscv/init.c | 2 + > drivers/acpi/riscv/init.h | 1 + > drivers/acpi/riscv/rimt.c | 520 ++++++++++++++++++++++++++++++++++++ > include/linux/acpi_rimt.h | 28 ++ > 9 files changed, 565 insertions(+) > create mode 100644 drivers/acpi/riscv/Kconfig > create mode 100644 drivers/acpi/riscv/rimt.c > create mode 100644 include/linux/acpi_rimt.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index fad6cb025a19..21125f7c0d0a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -345,6 +345,7 @@ L: linux-acpi@xxxxxxxxxxxxxxx > L: linux-riscv@xxxxxxxxxxxxxxxxxxx > S: Maintained > F: drivers/acpi/riscv/ > +F: include/linux/acpi_rimt.h > > ACPI PCC(Platform Communication Channel) MAILBOX DRIVER > M: Sudeep Holla <sudeep.holla@xxxxxxx> > diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig > index d71ea0f4466f..67bbf3b7302d 100644 > --- a/arch/riscv/Kconfig > +++ b/arch/riscv/Kconfig > @@ -16,6 +16,7 @@ config RISCV > select ACPI_MCFG if (ACPI && PCI) > select ACPI_PPTT if ACPI > select ACPI_REDUCED_HARDWARE_ONLY if ACPI > + select ACPI_RIMT if ACPI > select ACPI_SPCR_TABLE if ACPI > select ARCH_DMA_DEFAULT_COHERENT > select ARCH_ENABLE_HUGEPAGE_MIGRATION if HUGETLB_PAGE && MIGRATION > diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig > index 7bc40c2735ac..4381803c308c 100644 > --- a/drivers/acpi/Kconfig > +++ b/drivers/acpi/Kconfig > @@ -546,6 +546,10 @@ if ARM64 > source "drivers/acpi/arm64/Kconfig" > endif > > +if RISCV > +source "drivers/acpi/riscv/Kconfig" > +endif > + > config ACPI_PPTT > bool > > diff --git a/drivers/acpi/riscv/Kconfig b/drivers/acpi/riscv/Kconfig > new file mode 100644 > index 000000000000..046296a18d00 > --- /dev/null > +++ b/drivers/acpi/riscv/Kconfig > @@ -0,0 +1,7 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +# > +# ACPI Configuration for RISC-V > +# > + > +config ACPI_RIMT > + bool > diff --git a/drivers/acpi/riscv/Makefile b/drivers/acpi/riscv/Makefile > index a96fdf1e2cb8..1284a076fa88 100644 > --- a/drivers/acpi/riscv/Makefile > +++ b/drivers/acpi/riscv/Makefile > @@ -2,3 +2,4 @@ > obj-y += rhct.o init.o irq.o > obj-$(CONFIG_ACPI_PROCESSOR_IDLE) += cpuidle.o > obj-$(CONFIG_ACPI_CPPC_LIB) += cppc.o > +obj-$(CONFIG_ACPI_RIMT) += rimt.o > diff --git a/drivers/acpi/riscv/init.c b/drivers/acpi/riscv/init.c > index 673e4d5dd752..7c00f7995e86 100644 > --- a/drivers/acpi/riscv/init.c > +++ b/drivers/acpi/riscv/init.c > @@ -10,4 +10,6 @@ > void __init acpi_arch_init(void) > { > riscv_acpi_init_gsi_mapping(); > + if (IS_ENABLED(CONFIG_ACPI_RIMT)) > + riscv_acpi_rimt_init(); > } > diff --git a/drivers/acpi/riscv/init.h b/drivers/acpi/riscv/init.h > index 0b9a07e4031f..1680aa2aaf23 100644 > --- a/drivers/acpi/riscv/init.h > +++ b/drivers/acpi/riscv/init.h > @@ -2,3 +2,4 @@ > #include <linux/init.h> > > void __init riscv_acpi_init_gsi_mapping(void); > +void __init riscv_acpi_rimt_init(void); > diff --git a/drivers/acpi/riscv/rimt.c b/drivers/acpi/riscv/rimt.c > new file mode 100644 > index 000000000000..0cb486b19470 > --- /dev/null > +++ b/drivers/acpi/riscv/rimt.c > @@ -0,0 +1,520 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (C) 2024-2025, Ventana Micro Systems Inc > + * Author: Sunil V L <sunilvl@xxxxxxxxxxxxxxxx> > + * > + */ > + > +#define pr_fmt(fmt) "ACPI: RIMT: " fmt > + > +#include <linux/acpi.h> > +#include <linux/acpi_rimt.h> > +#include <linux/iommu.h> > +#include <linux/list.h> > +#include <linux/pci.h> > +#include <linux/platform_device.h> > +#include "init.h" > + > +struct rimt_fwnode { > + struct list_head list; > + struct acpi_rimt_node *rimt_node; > + struct fwnode_handle *fwnode; > +}; > + > +static LIST_HEAD(rimt_fwnode_list); > +static DEFINE_SPINLOCK(rimt_fwnode_lock); > + > +#define RIMT_TYPE_MASK(type) (1 << (type)) Can we not use BIT() here ? > +#define RIMT_IOMMU_TYPE BIT(0) > + > +/* Root pointer to the mapped RIMT table */ > +static struct acpi_table_header *rimt_table; > + > +/** > + * rimt_set_fwnode() - Create rimt_fwnode and use it to register > + * iommu data in the rimt_fwnode_list > + * > + * @rimt_node: RIMT table node associated with the IOMMU > + * @fwnode: fwnode associated with the RIMT node > + * > + * Returns: 0 on success > + * <0 on failure > + */ > +static int rimt_set_fwnode(struct acpi_rimt_node *rimt_node, > + struct fwnode_handle *fwnode) > +{ > + struct rimt_fwnode *np; > + > + np = kzalloc(sizeof(*np), GFP_ATOMIC); > + > + if (WARN_ON(!np)) > + return -ENOMEM; > + > + INIT_LIST_HEAD(&np->list); > + np->rimt_node = rimt_node; > + np->fwnode = fwnode; > + > + spin_lock(&rimt_fwnode_lock); > + list_add_tail(&np->list, &rimt_fwnode_list); > + spin_unlock(&rimt_fwnode_lock); > + > + return 0; > +} > + > +/** > + * rimt_get_fwnode() - Retrieve fwnode associated with an RIMT node > + * > + * @node: RIMT table node to be looked-up > + * > + * Returns: fwnode_handle pointer on success, NULL on failure > + */ > +static struct fwnode_handle *rimt_get_fwnode(struct acpi_rimt_node *node) > +{ > + struct rimt_fwnode *curr; > + struct fwnode_handle *fwnode = NULL; Inverted pyramid declaration of local variable here and everywhere else below. > + > + spin_lock(&rimt_fwnode_lock); > + list_for_each_entry(curr, &rimt_fwnode_list, list) { > + if (curr->rimt_node == node) { > + fwnode = curr->fwnode; > + break; > + } > + } > + spin_unlock(&rimt_fwnode_lock); > + > + return fwnode; > +} > + > +static acpi_status rimt_match_node_callback(struct acpi_rimt_node *node, > + void *context) > +{ > + struct device *dev = context; > + acpi_status status = AE_NOT_FOUND; > + > + if (node->type == ACPI_RIMT_NODE_TYPE_IOMMU) { > + struct acpi_rimt_iommu *iommu_node = (struct acpi_rimt_iommu *)&node->node_data; > + > + if (dev_is_pci(dev)) { > + struct pci_dev *pdev; > + u16 bdf; > + > + pdev = to_pci_dev(dev); > + bdf = PCI_DEVID(pdev->bus->number, pdev->devfn); > + if ((pci_domain_nr(pdev->bus) == iommu_node->pcie_segment_number) && > + bdf == iommu_node->pcie_bdf) { > + status = AE_OK; > + } else { > + status = AE_NOT_FOUND; > + } > + } else { > + struct platform_device *pdev = to_platform_device(dev); > + struct resource *res; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (res && res->start == iommu_node->base_address) > + status = AE_OK; > + else > + status = AE_NOT_FOUND; > + } > + } else if (node->type == ACPI_RIMT_NODE_TYPE_PCIE_ROOT_COMPLEX) { > + struct acpi_rimt_pcie_rc *pci_rc; > + struct pci_bus *bus; > + > + bus = to_pci_bus(dev); > + pci_rc = (struct acpi_rimt_pcie_rc *)node->node_data; > + > + /* > + * It is assumed that PCI segment numbers maps one-to-one > + * with root complexes. Each segment number can represent only > + * one root complex. > + */ > + status = pci_rc->pcie_segment_number == pci_domain_nr(bus) ? > + AE_OK : AE_NOT_FOUND; > + } else if (node->type == ACPI_RIMT_NODE_TYPE_PLAT_DEVICE) { > + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; > + struct acpi_device *adev; > + struct acpi_rimt_platform_device *ncomp; > + struct device *plat_dev = dev; > + > + /* > + * Walk the device tree to find a device with an > + * ACPI companion; there is no point in scanning > + * RIMT for a device matching a platform device if > + * the device does not have an ACPI companion to > + * start with. > + */ > + do { > + adev = ACPI_COMPANION(plat_dev); > + if (adev) > + break; > + > + plat_dev = plat_dev->parent; > + } while (plat_dev); > + > + if (!adev) > + return status; > + > + status = acpi_get_name(adev->handle, ACPI_FULL_PATHNAME, &buf); > + if (ACPI_FAILURE(status)) { > + dev_warn(plat_dev, "Can't get device full path name\n"); > + return status; > + } > + > + ncomp = (struct acpi_rimt_platform_device *)node->node_data; > + status = !strcmp(ncomp->device_name, buf.pointer) ? > + AE_OK : AE_NOT_FOUND; > + acpi_os_free(buf.pointer); > + } > + > + return status; > +} > + > +static struct acpi_rimt_node *rimt_scan_node(enum acpi_rimt_node_type type, > + void *context) > +{ > + struct acpi_rimt_node *rimt_node, *rimt_end; > + struct acpi_table_rimt *rimt; > + int i; > + > + if (!rimt_table) > + return NULL; > + > + /* Get the first RIMT node */ > + rimt = (struct acpi_table_rimt *)rimt_table; > + rimt_node = ACPI_ADD_PTR(struct acpi_rimt_node, rimt, > + rimt->node_offset); > + rimt_end = ACPI_ADD_PTR(struct acpi_rimt_node, rimt_table, > + rimt_table->length); > + > + for (i = 0; i < rimt->num_nodes; i++) { > + if (WARN_TAINT(rimt_node >= rimt_end, TAINT_FIRMWARE_WORKAROUND, > + "RIMT node pointer overflows, bad table!\n")) > + return NULL; > + > + if (rimt_node->type == type && > + ACPI_SUCCESS(rimt_match_node_callback(rimt_node, context))) > + return rimt_node; > + > + rimt_node = ACPI_ADD_PTR(struct acpi_rimt_node, rimt_node, > + rimt_node->length); > + } > + > + return NULL; > +} > + > +static bool rimt_pcie_rc_supports_ats(struct acpi_rimt_node *node) > +{ > + struct acpi_rimt_pcie_rc *pci_rc; > + > + pci_rc = (struct acpi_rimt_pcie_rc *)node->node_data; > + return pci_rc->flags & ACPI_RIMT_PCIE_ATS_SUPPORTED; > +} > + > +static int rimt_iommu_xlate(struct device *dev, struct acpi_rimt_node *node, u32 deviceid) > +{ > + struct fwnode_handle *rimt_fwnode; > + > + if (!node) > + return -ENODEV; > + > + rimt_fwnode = rimt_get_fwnode(node); > + > + /* > + * The IOMMU drivers may not be probed yet. > + * Defer the IOMMU configuration > + */ > + if (!rimt_fwnode) > + return -EPROBE_DEFER; > + > + return acpi_iommu_fwspec_init(dev, deviceid, rimt_fwnode); > +} > + > +struct rimt_pci_alias_info { > + struct device *dev; > + struct acpi_rimt_node *node; > + const struct iommu_ops *ops; > +}; > + > +static int rimt_id_map(struct acpi_rimt_id_mapping *map, u8 type, u32 rid_in, u32 *rid_out) > +{ > + if (rid_in < map->source_id_base || > + (rid_in > map->source_id_base + map->num_ids)) > + return -ENXIO; > + > + *rid_out = map->dest_id_base + (rid_in - map->source_id_base); > + return 0; > +} > + > +static struct acpi_rimt_node *rimt_node_get_id(struct acpi_rimt_node *node, > + u32 *id_out, int index) > +{ > + struct acpi_rimt_platform_device *plat_node; > + struct acpi_rimt_pcie_rc *pci_node; > + u32 id_mapping_offset, num_id_mapping; > + struct acpi_rimt_id_mapping *map; > + struct acpi_rimt_node *parent; > + > + if (node->type == ACPI_RIMT_NODE_TYPE_PCIE_ROOT_COMPLEX) { > + pci_node = (struct acpi_rimt_pcie_rc *)&node->node_data; > + id_mapping_offset = pci_node->id_mapping_offset; > + num_id_mapping = pci_node->num_id_mappings; > + } else if (node->type == ACPI_RIMT_NODE_TYPE_PLAT_DEVICE) { > + plat_node = (struct acpi_rimt_platform_device *)&node->node_data; > + id_mapping_offset = plat_node->id_mapping_offset; > + num_id_mapping = plat_node->num_id_mappings; > + } else { > + return NULL; > + } > + > + if (!id_mapping_offset || !num_id_mapping || index >= num_id_mapping) > + return NULL; > + > + map = ACPI_ADD_PTR(struct acpi_rimt_id_mapping, node, > + id_mapping_offset + index * sizeof(*map)); > + > + /* Firmware bug! */ > + if (!map->dest_offset) { > + pr_err(FW_BUG "[node %p type %d] ID map has NULL parent reference\n", > + node, node->type); > + return NULL; > + } > + > + parent = ACPI_ADD_PTR(struct acpi_rimt_node, rimt_table, map->dest_offset); > + > + if (node->type == ACPI_RIMT_NODE_TYPE_PLAT_DEVICE || > + node->type == ACPI_RIMT_NODE_TYPE_PCIE_ROOT_COMPLEX) { > + *id_out = map->dest_offset; > + return parent; > + } > + > + return NULL; > +} > + > +static struct acpi_rimt_node *rimt_node_map_id(struct acpi_rimt_node *node, > + u32 id_in, u32 *id_out, > + u8 type_mask) > +{ > + struct acpi_rimt_pcie_rc *pci_node; > + struct acpi_rimt_platform_device *plat_node; > + u32 id = id_in; > + u32 id_mapping_offset, num_id_mapping; > + > + /* Parse the ID mapping tree to find specified node type */ > + while (node) { > + struct acpi_rimt_id_mapping *map; > + int i, rc = 0; > + u32 map_id = id; > + > + if (RIMT_TYPE_MASK(node->type) & type_mask) { > + if (id_out) > + *id_out = id; > + return node; > + } > + > + if (node->type == ACPI_RIMT_NODE_TYPE_PCIE_ROOT_COMPLEX) { > + pci_node = (struct acpi_rimt_pcie_rc *)&node->node_data; > + id_mapping_offset = pci_node->id_mapping_offset; > + num_id_mapping = pci_node->num_id_mappings; > + } else if (node->type == ACPI_RIMT_NODE_TYPE_PLAT_DEVICE) { > + plat_node = (struct acpi_rimt_platform_device *)&node->node_data; > + id_mapping_offset = plat_node->id_mapping_offset; > + num_id_mapping = plat_node->num_id_mappings; > + } else { > + goto fail_map; > + } > + > + if (!id_mapping_offset || !num_id_mapping) > + goto fail_map; > + > + map = ACPI_ADD_PTR(struct acpi_rimt_id_mapping, node, > + id_mapping_offset); > + > + /* Firmware bug! */ > + if (!map->dest_offset) { > + pr_err(FW_BUG "[node %p type %d] ID map has NULL parent reference\n", > + node, node->type); > + goto fail_map; > + } > + > + /* Do the ID translation */ > + for (i = 0; i < num_id_mapping; i++, map++) { > + rc = rimt_id_map(map, node->type, map_id, &id); > + if (!rc) > + break; > + } > + > + if (i == num_id_mapping) > + goto fail_map; > + > + node = ACPI_ADD_PTR(struct acpi_rimt_node, rimt_table, > + rc ? 0 : map->dest_offset); > + } > + > +fail_map: > + /* Map input ID to output ID unchanged on mapping failure */ > + if (id_out) > + *id_out = id_in; > + > + return NULL; > +} > + > +static struct acpi_rimt_node *rimt_node_map_platform_id(struct acpi_rimt_node *node, u32 *id_out, > + u8 type_mask, int index) > +{ > + struct acpi_rimt_node *parent; > + u32 id; > + > + parent = rimt_node_get_id(node, &id, index); > + if (!parent) > + return NULL; > + > + if (!(RIMT_TYPE_MASK(parent->type) & type_mask)) > + parent = rimt_node_map_id(parent, id, id_out, type_mask); > + else > + if (id_out) > + *id_out = id; > + > + return parent; > +} > + > +static int rimt_pci_iommu_init(struct pci_dev *pdev, u16 alias, void *data) > +{ > + struct rimt_pci_alias_info *info = data; > + struct acpi_rimt_node *parent; > + u32 deviceid; > + > + parent = rimt_node_map_id(info->node, alias, &deviceid, RIMT_IOMMU_TYPE); > + return rimt_iommu_xlate(info->dev, parent, deviceid); > +} > + > +/* > + * RISC-V supports IOMMU as a PCI device or a platform device. > + * When it is a platform device, there should be a namespace device as > + * well along with RIMT. To create the link between RIMT information and > + * the platform device, the IOMMU driver should register itself with the > + * RIMT module. This is true for PCI based IOMMU as well. > + */ > +int rimt_iommu_register(struct device *dev) > +{ > + struct fwnode_handle *rimt_fwnode; > + struct acpi_rimt_node *node; > + > + node = rimt_scan_node(ACPI_RIMT_NODE_TYPE_IOMMU, dev); > + if (!node) { > + pr_err("Could not find IOMMU node in RIMT\n"); > + return -ENODEV; > + } > + > + if (dev_is_pci(dev)) { > + rimt_fwnode = acpi_alloc_fwnode_static(); > + if (!rimt_fwnode) > + return -ENOMEM; > + > + rimt_fwnode->dev = dev; > + if (!dev->fwnode) > + dev->fwnode = rimt_fwnode; > + > + rimt_set_fwnode(node, rimt_fwnode); > + } else { > + rimt_set_fwnode(node, dev->fwnode); > + } > + > + return 0; > +} > + > +#ifdef CONFIG_IOMMU_API > + > +static int rimt_plat_iommu_map(struct device *dev, struct acpi_rimt_node *node) > +{ > + struct acpi_rimt_node *parent; > + int err = -ENODEV, i = 0; > + u32 deviceid = 0; > + > + do { > + parent = rimt_node_map_platform_id(node, &deviceid, > + RIMT_IOMMU_TYPE, > + i++); > + > + if (parent) > + err = rimt_iommu_xlate(dev, parent, deviceid); > + } while (parent && !err); > + > + return err; > +} > + > +static int rimt_plat_iommu_map_id(struct device *dev, > + struct acpi_rimt_node *node, > + const u32 *in_id) > +{ > + struct acpi_rimt_node *parent; > + u32 deviceid; > + > + parent = rimt_node_map_id(node, *in_id, &deviceid, RIMT_IOMMU_TYPE); > + if (parent) > + return rimt_iommu_xlate(dev, parent, deviceid); > + > + return -ENODEV; > +} > + > +/** > + * rimt_iommu_configure_id - Set-up IOMMU configuration for a device. > + * > + * @dev: device to configure > + * @id_in: optional input id const value pointer > + * > + * Returns: 0 on success, <0 on failure > + */ > +int rimt_iommu_configure_id(struct device *dev, const u32 *id_in) > +{ > + struct acpi_rimt_node *node; > + int err = -ENODEV; > + > + if (dev_is_pci(dev)) { > + struct iommu_fwspec *fwspec; > + struct pci_bus *bus = to_pci_dev(dev)->bus; > + struct rimt_pci_alias_info info = { .dev = dev }; > + > + node = rimt_scan_node(ACPI_RIMT_NODE_TYPE_PCIE_ROOT_COMPLEX, &bus->dev); > + if (!node) > + return -ENODEV; > + > + info.node = node; > + err = pci_for_each_dma_alias(to_pci_dev(dev), > + rimt_pci_iommu_init, &info); > + > + fwspec = dev_iommu_fwspec_get(dev); > + if (fwspec && rimt_pcie_rc_supports_ats(node)) > + fwspec->flags |= IOMMU_FWSPEC_PCI_RC_ATS; > + } else { > + node = rimt_scan_node(ACPI_RIMT_NODE_TYPE_PLAT_DEVICE, dev); > + if (!node) > + return -ENODEV; > + > + err = id_in ? rimt_plat_iommu_map_id(dev, node, id_in) : > + rimt_plat_iommu_map(dev, node); > + } > + > + return err; > +} > + > +#endif > + > +void __init riscv_acpi_rimt_init(void) > +{ > + acpi_status status; > + > + /* rimt_table will be used at runtime after the rimt init, > + * so we don't need to call acpi_put_table() to release > + * the RIMT table mapping. > + */ > + status = acpi_get_table(ACPI_SIG_RIMT, 0, &rimt_table); > + if (ACPI_FAILURE(status)) { > + if (status != AE_NOT_FOUND) { > + const char *msg = acpi_format_exception(status); > + > + pr_err("Failed to get table, %s\n", msg); > + } > + > + return; > + } > +} > diff --git a/include/linux/acpi_rimt.h b/include/linux/acpi_rimt.h > new file mode 100644 > index 000000000000..fad3adc4d899 > --- /dev/null > +++ b/include/linux/acpi_rimt.h > @@ -0,0 +1,28 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (C) 2024-2025, Ventana Micro Systems Inc. > + * Author: Sunil V L <sunilvl@xxxxxxxxxxxxxxxx> > + */ > + > +#ifndef _ACPI_RIMT_H > +#define _ACPI_RIMT_H > + > +#ifdef CONFIG_ACPI_RIMT > +int rimt_iommu_register(struct device *dev); > +#else > +static inline int rimt_iommu_register(struct device *dev) > +{ > + return -ENODEV; > +} > +#endif > + > +#if defined(CONFIG_IOMMU_API) && defined(CONFIG_ACPI_RIMT) > +int rimt_iommu_configure_id(struct device *dev, const u32 *id_in); > +#else > +static inline int rimt_iommu_configure_id(struct device *dev, const u32 *id_in) > +{ > + return -ENODEV; > +} > +#endif > + > +#endif /* _ACPI_RIMT_H */ > -- > 2.43.0 > > Otherwise, it looks good to me from RISC-V perspective. Reviewed-by: Anup Patel <anup@xxxxxxxxxxxxxx> Thanks, ANup