iommu/riscv: Add RISC-V IOMMU platform device driver

Introduce platform device driver for implementation of RISC-V IOMMU
architected hardware.

Hardware interface definition located in file iommu-bits.h is based on
ratified RISC-V IOMMU Architecture Specification version 1.0.0.

This patch implements platform device initialization, early check and
configuration of the IOMMU interfaces and enables global pass-through
address translation mode (iommu_mode == BARE), without registering
hardware instance in the IOMMU subsystem.

Link: https://github.com/riscv-non-isa/riscv-iommu
Co-developed-by: Nick Kossifidis <mick@ics.forth.gr>
Signed-off-by: Nick Kossifidis <mick@ics.forth.gr>
Co-developed-by: Sebastien Boeuf <seb@rivosinc.com>
Signed-off-by: Sebastien Boeuf <seb@rivosinc.com>
Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Tomasz Jeznach <tjeznach@rivosinc.com>
Acked-by: Palmer Dabbelt <palmer@rivosinc.com>
Link: https://lore.kernel.org/r/2f2e4530c0ee4a81385efa90f1da932f5179f3fb.1729059707.git.tjeznach@rivosinc.com
Signed-off-by: Joerg Roedel <jroedel@suse.de>
This commit is contained in:
Tomasz Jeznach
2024-10-29 09:46:23 +01:00
committed by Joerg Roedel
parent 14d050cd45
commit 5c0ebbd3c6
9 changed files with 987 additions and 1 deletions
+2
View File
@@ -19895,7 +19895,9 @@ M: Tomasz Jeznach <tjeznach@rivosinc.com>
L: iommu@lists.linux.dev
L: linux-riscv@lists.infradead.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git
F: Documentation/devicetree/bindings/iommu/riscv,iommu.yaml
F: drivers/iommu/riscv/
RISC-V MICROCHIP FPGA SUPPORT
M: Conor Dooley <conor.dooley@microchip.com>
+1
View File
@@ -195,6 +195,7 @@ config MSM_IOMMU
source "drivers/iommu/amd/Kconfig"
source "drivers/iommu/intel/Kconfig"
source "drivers/iommu/iommufd/Kconfig"
source "drivers/iommu/riscv/Kconfig"
config IRQ_REMAP
bool "Support for Interrupt Remapping"
+1 -1
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-y += amd/ intel/ arm/ iommufd/
obj-y += amd/ intel/ arm/ iommufd/ riscv/
obj-$(CONFIG_IOMMU_API) += iommu.o
obj-$(CONFIG_IOMMU_API) += iommu-traces.o
obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
# RISC-V IOMMU support
config RISCV_IOMMU
bool "RISC-V IOMMU Support"
depends on RISCV && 64BIT
default y
select IOMMU_API
help
Support for implementations of the RISC-V IOMMU architecture that
complements the RISC-V MMU capabilities, providing similar address
translation and protection functions for accesses from I/O devices.
Say Y here if your SoC includes an IOMMU device implementing
the RISC-V IOMMU architecture.
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_RISCV_IOMMU) += iommu.o iommu-platform.o
File diff suppressed because it is too large Load Diff
+92
View File
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* RISC-V IOMMU as a platform device
*
* Copyright © 2023 FORTH-ICS/CARV
* Copyright © 2023-2024 Rivos Inc.
*
* Authors
* Nick Kossifidis <mick@ics.forth.gr>
* Tomasz Jeznach <tjeznach@rivosinc.com>
*/
#include <linux/kernel.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include "iommu-bits.h"
#include "iommu.h"
static int riscv_iommu_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct riscv_iommu_device *iommu = NULL;
struct resource *res = NULL;
int vec;
iommu = devm_kzalloc(dev, sizeof(*iommu), GFP_KERNEL);
if (!iommu)
return -ENOMEM;
iommu->dev = dev;
iommu->reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(iommu->reg))
return dev_err_probe(dev, PTR_ERR(iommu->reg),
"could not map register region\n");
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);
/* For now we only support WSI */
switch (FIELD_GET(RISCV_IOMMU_CAPABILITIES_IGS, iommu->caps)) {
case RISCV_IOMMU_CAPABILITIES_IGS_WSI:
case RISCV_IOMMU_CAPABILITIES_IGS_BOTH:
break;
default:
return dev_err_probe(dev, -ENODEV,
"unable to use wire-signaled interrupts\n");
}
iommu->irqs_count = platform_irq_count(pdev);
if (iommu->irqs_count <= 0)
return dev_err_probe(dev, -ENODEV,
"no IRQ resources provided\n");
if (iommu->irqs_count > RISCV_IOMMU_INTR_COUNT)
iommu->irqs_count = RISCV_IOMMU_INTR_COUNT;
for (vec = 0; vec < iommu->irqs_count; vec++)
iommu->irqs[vec] = platform_get_irq(pdev, vec);
/* Enable wire-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_platform_remove(struct platform_device *pdev)
{
riscv_iommu_remove(dev_get_drvdata(&pdev->dev));
};
static const struct of_device_id riscv_iommu_of_match[] = {
{.compatible = "riscv,iommu",},
{},
};
static struct platform_driver riscv_iommu_platform_driver = {
.probe = riscv_iommu_platform_probe,
.remove_new = riscv_iommu_platform_remove,
.driver = {
.name = "riscv,iommu",
.of_match_table = riscv_iommu_of_match,
.suppress_bind_attrs = true,
},
};
builtin_platform_driver(riscv_iommu_platform_driver);
+103
View File
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* IOMMU API for RISC-V IOMMU implementations.
*
* Copyright © 2022-2024 Rivos Inc.
* Copyright © 2023 FORTH-ICS/CARV
*
* Authors
* Tomasz Jeznach <tjeznach@rivosinc.com>
* Nick Kossifidis <mick@ics.forth.gr>
*/
#define pr_fmt(fmt) "riscv-iommu: " fmt
#include <linux/compiler.h>
#include <linux/crash_dump.h>
#include <linux/init.h>
#include <linux/iommu.h>
#include <linux/kernel.h>
#include "iommu-bits.h"
#include "iommu.h"
/* Timeouts in [us] */
#define RISCV_IOMMU_DDTP_TIMEOUT 50000
/*
* This is best effort IOMMU translation shutdown flow.
* Disable IOMMU without waiting for hardware response.
*/
static void riscv_iommu_disable(struct riscv_iommu_device *iommu)
{
riscv_iommu_writeq(iommu, RISCV_IOMMU_REG_DDTP, 0);
riscv_iommu_writel(iommu, RISCV_IOMMU_REG_CQCSR, 0);
riscv_iommu_writel(iommu, RISCV_IOMMU_REG_FQCSR, 0);
riscv_iommu_writel(iommu, RISCV_IOMMU_REG_PQCSR, 0);
}
static int riscv_iommu_init_check(struct riscv_iommu_device *iommu)
{
u64 ddtp;
/*
* Make sure the IOMMU is switched off or in pass-through mode during
* regular boot flow and disable translation when we boot into a kexec
* kernel and the previous kernel left them enabled.
*/
ddtp = riscv_iommu_readq(iommu, RISCV_IOMMU_REG_DDTP);
if (ddtp & RISCV_IOMMU_DDTP_BUSY)
return -EBUSY;
if (FIELD_GET(RISCV_IOMMU_DDTP_IOMMU_MODE, ddtp) >
RISCV_IOMMU_DDTP_IOMMU_MODE_BARE) {
if (!is_kdump_kernel())
return -EBUSY;
riscv_iommu_disable(iommu);
}
/* Configure accesses to in-memory data structures for CPU-native byte order. */
if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) !=
!!(iommu->fctl & RISCV_IOMMU_FCTL_BE)) {
if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_END))
return -EINVAL;
riscv_iommu_writel(iommu, RISCV_IOMMU_REG_FCTL,
iommu->fctl ^ RISCV_IOMMU_FCTL_BE);
iommu->fctl = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_FCTL);
if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) !=
!!(iommu->fctl & RISCV_IOMMU_FCTL_BE))
return -EINVAL;
}
return 0;
}
void riscv_iommu_remove(struct riscv_iommu_device *iommu)
{
iommu_device_sysfs_remove(&iommu->iommu);
}
int riscv_iommu_init(struct riscv_iommu_device *iommu)
{
int rc;
rc = riscv_iommu_init_check(iommu);
if (rc)
return dev_err_probe(iommu->dev, rc, "unexpected device state\n");
/*
* Placeholder for a complete IOMMU device initialization. For now,
* only bare minimum: enable global identity mapping mode and register sysfs.
*/
riscv_iommu_writeq(iommu, RISCV_IOMMU_REG_DDTP,
FIELD_PREP(RISCV_IOMMU_DDTP_IOMMU_MODE,
RISCV_IOMMU_DDTP_IOMMU_MODE_BARE));
rc = iommu_device_sysfs_add(&iommu->iommu, NULL, NULL, "riscv-iommu@%s",
dev_name(iommu->dev));
if (rc)
return dev_err_probe(iommu->dev, rc,
"cannot register sysfs interface\n");
return 0;
}
+62
View File
@@ -0,0 +1,62 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright © 2022-2024 Rivos Inc.
* Copyright © 2023 FORTH-ICS/CARV
*
* Authors
* Tomasz Jeznach <tjeznach@rivosinc.com>
* Nick Kossifidis <mick@ics.forth.gr>
*/
#ifndef _RISCV_IOMMU_H_
#define _RISCV_IOMMU_H_
#include <linux/iommu.h>
#include <linux/types.h>
#include <linux/iopoll.h>
#include "iommu-bits.h"
struct riscv_iommu_device {
/* iommu core interface */
struct iommu_device iommu;
/* iommu hardware */
struct device *dev;
/* hardware control register space */
void __iomem *reg;
/* supported and enabled hardware capabilities */
u64 caps;
u32 fctl;
/* available interrupt numbers, MSI or WSI */
unsigned int irqs[RISCV_IOMMU_INTR_COUNT];
unsigned int irqs_count;
};
int riscv_iommu_init(struct riscv_iommu_device *iommu);
void riscv_iommu_remove(struct riscv_iommu_device *iommu);
#define riscv_iommu_readl(iommu, addr) \
readl_relaxed((iommu)->reg + (addr))
#define riscv_iommu_readq(iommu, addr) \
readq_relaxed((iommu)->reg + (addr))
#define riscv_iommu_writel(iommu, addr, val) \
writel_relaxed((val), (iommu)->reg + (addr))
#define riscv_iommu_writeq(iommu, addr, val) \
writeq_relaxed((val), (iommu)->reg + (addr))
#define riscv_iommu_readq_timeout(iommu, addr, val, cond, delay_us, timeout_us) \
readx_poll_timeout(readq_relaxed, (iommu)->reg + (addr), val, cond, \
delay_us, timeout_us)
#define riscv_iommu_readl_timeout(iommu, addr, val, cond, delay_us, timeout_us) \
readx_poll_timeout(readl_relaxed, (iommu)->reg + (addr), val, cond, \
delay_us, timeout_us)
#endif