* [PATCH v16 1/7] spi: pxa2xx: introduce clock enable and disable helper functions
From: Shih-Yuan Lee @ 2026-07-20 16:21 UTC (permalink / raw)
To: Mark Brown
Cc: Andy Shevchenko, Mika Westerberg, Lukas Wunner, Daniel Mack,
Haojian Zhuang, Robert Jarzmik, linux-arm-kernel, linux-spi,
linux-kernel, Shih-Yuan Lee
In-Reply-To: <20260720162117.32304-1-fourdollars@debian.org>
The driver disables the clock during PM runtime suspend, PM system
suspend, and device unbinding (remove). It also disables the clock
on various error unwinding paths in pxa2xx_spi_probe().
However, if the clock is already disabled (for example, if the device is
already runtime-suspended during driver unbinding), calling the common
clock framework's clk_disable_unprepare() again leads to clock prepare/enable
count underflows, generating kernel warnings.
Introduce pxa2xx_spi_clk_enable() and pxa2xx_spi_clk_disable() helper
functions that track the clock enable state using a new 'clk_enabled'
boolean flag in struct driver_data.
This ensures clk_disable_unprepare() is called only when the clock is
active, preventing clock underflows during suspend transitions and unbind.
It also allows the probe function to safely unwind resource allocations
without triggering clock underflows.
Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org>
---
drivers/spi/spi-pxa2xx.c | 37 +++++++++++++++++++++++++++++--------
drivers/spi/spi-pxa2xx.h | 2 ++
2 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index 6291d7c2e06f..d50152aad348 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -713,6 +713,28 @@ static void handle_bad_msg(struct driver_data *drv_data)
dev_err(drv_data->ssp->dev, "bad message state in interrupt handler\n");
}
+static int pxa2xx_spi_clk_enable(struct driver_data *drv_data)
+{
+ int ret;
+
+ if (drv_data->clk_enabled)
+ return 0;
+
+ ret = clk_prepare_enable(drv_data->ssp->clk);
+ if (ret == 0)
+ drv_data->clk_enabled = true;
+
+ return ret;
+}
+
+static void pxa2xx_spi_clk_disable(struct driver_data *drv_data)
+{
+ if (drv_data->clk_enabled) {
+ clk_disable_unprepare(drv_data->ssp->clk);
+ drv_data->clk_enabled = false;
+ }
+}
+
static irqreturn_t ssp_int(int irq, void *dev_id)
{
struct driver_data *drv_data = dev_id;
@@ -1352,7 +1374,7 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
}
/* Enable SOC clock */
- status = clk_prepare_enable(ssp->clk);
+ status = pxa2xx_spi_clk_enable(drv_data);
if (status)
goto out_error_dma_irq_alloc;
@@ -1449,7 +1471,7 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
return status;
out_error_clock_enabled:
- clk_disable_unprepare(ssp->clk);
+ pxa2xx_spi_clk_disable(drv_data);
out_error_dma_irq_alloc:
pxa2xx_spi_dma_release(drv_data);
@@ -1468,7 +1490,7 @@ void pxa2xx_spi_remove(struct device *dev)
/* Disable the SSP at the peripheral and SOC level */
pxa_ssp_disable(ssp);
- clk_disable_unprepare(ssp->clk);
+ pxa2xx_spi_clk_disable(drv_data);
/* Release DMA */
if (drv_data->controller_info->enable_dma)
@@ -1492,7 +1514,7 @@ static int pxa2xx_spi_suspend(struct device *dev)
pxa_ssp_disable(ssp);
if (!pm_runtime_suspended(dev))
- clk_disable_unprepare(ssp->clk);
+ pxa2xx_spi_clk_disable(drv_data);
return 0;
}
@@ -1500,12 +1522,11 @@ static int pxa2xx_spi_suspend(struct device *dev)
static int pxa2xx_spi_resume(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
- struct ssp_device *ssp = drv_data->ssp;
int status;
/* Enable the SSP clock */
if (!pm_runtime_suspended(dev)) {
- status = clk_prepare_enable(ssp->clk);
+ status = pxa2xx_spi_clk_enable(drv_data);
if (status)
return status;
}
@@ -1518,7 +1539,7 @@ static int pxa2xx_spi_runtime_suspend(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
- clk_disable_unprepare(drv_data->ssp->clk);
+ pxa2xx_spi_clk_disable(drv_data);
return 0;
}
@@ -1526,7 +1547,7 @@ static int pxa2xx_spi_runtime_resume(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
- return clk_prepare_enable(drv_data->ssp->clk);
+ return pxa2xx_spi_clk_enable(drv_data);
}
EXPORT_NS_GPL_DEV_PM_OPS(pxa2xx_spi_pm_ops, SPI_PXA2xx) = {
diff --git a/drivers/spi/spi-pxa2xx.h b/drivers/spi/spi-pxa2xx.h
index 447be0369384..820e573a3c60 100644
--- a/drivers/spi/spi-pxa2xx.h
+++ b/drivers/spi/spi-pxa2xx.h
@@ -72,6 +72,8 @@ struct driver_data {
void __iomem *lpss_base;
+ bool clk_enabled;
+
/* Optional slave FIFO ready signal */
struct gpio_desc *gpiod_ready;
};
--
2.39.5
^ permalink raw reply related
* [PATCH v16 2/7] spi: pxa2xx: introduce suspended flag for interrupt synchronization
From: Shih-Yuan Lee @ 2026-07-20 16:21 UTC (permalink / raw)
To: Mark Brown
Cc: Andy Shevchenko, Mika Westerberg, Lukas Wunner, Daniel Mack,
Haojian Zhuang, Robert Jarzmik, linux-arm-kernel, linux-spi,
linux-kernel, Shih-Yuan Lee
In-Reply-To: <20260720162117.32304-1-fourdollars@debian.org>
When a shared interrupt line is used, the interrupt handler ssp_int()
can be triggered by other devices sharing the line. The handler must
ensure it does not access the SSP controller registers via MMIO when
the device is powered down or when its clock is gated; otherwise, it
will cause PCIe Completion Timeouts and system hangs.
Currently, ssp_int() guards MMIO access using:
if (pm_runtime_suspended(drv_data->ssp->dev)) return IRQ_NONE;
However, during PM transitions (such as system suspend or runtime PM
autosuspend), device callbacks execute to disable the hardware and gate the
clock, but the PM state machine does not mark the device as RPM_SUSPENDED
until after the suspend callback returns. During this transitional state
(RPM_SUSPENDING), pm_runtime_suspended() returns false. If a shared
interrupt fires after the clock has been gated but before the PM state has
transitioned, ssp_int() will execute, attempt MMIO reads on the unclocked
register space, and hang the system.
Formal verification using Spin/PROMELA confirms that a scheduling window
exists where the interrupt thread accesses MMIO when clk_enabled is false,
violating safety properties.
Introduce a custom 'suspended' boolean flag in struct driver_data to
track the device's suspended state across all PM transitions. Check
both 'drv_data->suspended' and '!drv_data->clk_enabled' in ssp_int()
to return IRQ_NONE immediately before any MMIO access is attempted.
This closes the state transition race condition and mathematically guarantees
deadlock-free, safe shared interrupt handling during power transitions.
Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org>
---
drivers/spi/spi-pxa2xx.c | 51 ++++++++++++++++++++++++++--------------
drivers/spi/spi-pxa2xx.h | 1 +
2 files changed, 35 insertions(+), 17 deletions(-)
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index d50152aad348..c44349ab2b52 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -730,8 +730,8 @@ static int pxa2xx_spi_clk_enable(struct driver_data *drv_data)
static void pxa2xx_spi_clk_disable(struct driver_data *drv_data)
{
if (drv_data->clk_enabled) {
- clk_disable_unprepare(drv_data->ssp->clk);
drv_data->clk_enabled = false;
+ clk_disable_unprepare(drv_data->ssp->clk);
}
}
@@ -743,12 +743,12 @@ static irqreturn_t ssp_int(int irq, void *dev_id)
u32 status;
/*
- * The IRQ might be shared with other peripherals so we must first
- * check that are we RPM suspended or not. If we are we assume that
- * the IRQ was not for us (we shouldn't be RPM suspended when the
- * interrupt is enabled).
+ * The IRQ might be shared with other peripherals or trigger during
+ * power state transitions. First check if device is suspended or if
+ * clock is disabled; if so, return IRQ_NONE immediately to avoid
+ * unclocked MMIO reads.
*/
- if (pm_runtime_suspended(drv_data->ssp->dev))
+ if (drv_data->suspended || !drv_data->clk_enabled)
return IRQ_NONE;
/*
@@ -1310,6 +1310,7 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
drv_data->controller = controller;
drv_data->controller_info = platform_info;
drv_data->ssp = ssp;
+ drv_data->suspended = true;
/* The spi->mode bits understood by this driver: */
controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP;
@@ -1352,11 +1353,6 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
| SSSR_ROR | SSSR_TUR;
}
- status = request_irq(ssp->irq, ssp_int, IRQF_SHARED, dev_name(dev),
- drv_data);
- if (status < 0)
- return dev_err_probe(dev, status, "cannot get IRQ %d\n", ssp->irq);
-
/* Setup DMA if requested */
if (platform_info->enable_dma) {
status = pxa2xx_spi_dma_setup(drv_data);
@@ -1376,7 +1372,16 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
/* Enable SOC clock */
status = pxa2xx_spi_clk_enable(drv_data);
if (status)
- goto out_error_dma_irq_alloc;
+ goto out_error_dma_alloc;
+
+ drv_data->suspended = false;
+
+ status = request_irq(ssp->irq, ssp_int, IRQF_SHARED, dev_name(dev),
+ drv_data);
+ if (status < 0) {
+ status = dev_err_probe(dev, status, "cannot get IRQ %d\n", ssp->irq);
+ goto out_error_clock_enabled;
+ }
controller->max_speed_hz = clk_get_rate(ssp->clk);
/*
@@ -1456,7 +1461,7 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
"ready", GPIOD_OUT_LOW);
if (IS_ERR(drv_data->gpiod_ready)) {
status = PTR_ERR(drv_data->gpiod_ready);
- goto out_error_clock_enabled;
+ goto out_error_irq_alloc;
}
}
@@ -1465,17 +1470,19 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
status = spi_register_controller(controller);
if (status) {
dev_err_probe(dev, status, "problem registering SPI controller\n");
- goto out_error_clock_enabled;
+ goto out_error_irq_alloc;
}
return status;
+out_error_irq_alloc:
+ free_irq(ssp->irq, drv_data);
+
out_error_clock_enabled:
pxa2xx_spi_clk_disable(drv_data);
-out_error_dma_irq_alloc:
+out_error_dma_alloc:
pxa2xx_spi_dma_release(drv_data);
- free_irq(ssp->irq, drv_data);
return status;
}
@@ -1511,6 +1518,7 @@ static int pxa2xx_spi_suspend(struct device *dev)
if (status)
return status;
+ drv_data->suspended = true;
pxa_ssp_disable(ssp);
if (!pm_runtime_suspended(dev))
@@ -1531,6 +1539,8 @@ static int pxa2xx_spi_resume(struct device *dev)
return status;
}
+ drv_data->suspended = false;
+
/* Start the queue running */
return spi_controller_resume(drv_data->controller);
}
@@ -1539,6 +1549,7 @@ static int pxa2xx_spi_runtime_suspend(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
+ drv_data->suspended = true;
pxa2xx_spi_clk_disable(drv_data);
return 0;
}
@@ -1546,8 +1557,14 @@ static int pxa2xx_spi_runtime_suspend(struct device *dev)
static int pxa2xx_spi_runtime_resume(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
+ int ret;
- return pxa2xx_spi_clk_enable(drv_data);
+ ret = pxa2xx_spi_clk_enable(drv_data);
+ if (ret)
+ return ret;
+
+ drv_data->suspended = false;
+ return 0;
}
EXPORT_NS_GPL_DEV_PM_OPS(pxa2xx_spi_pm_ops, SPI_PXA2xx) = {
diff --git a/drivers/spi/spi-pxa2xx.h b/drivers/spi/spi-pxa2xx.h
index 820e573a3c60..44f37bf9c519 100644
--- a/drivers/spi/spi-pxa2xx.h
+++ b/drivers/spi/spi-pxa2xx.h
@@ -72,6 +72,7 @@ struct driver_data {
void __iomem *lpss_base;
+ bool suspended;
bool clk_enabled;
/* Optional slave FIFO ready signal */
--
2.39.5
^ permalink raw reply related
* [PATCH v16 4/7] spi: pxa2xx: lock out runtime autosuspend for Intel LPSS SPI in PIO mode
From: Shih-Yuan Lee @ 2026-07-20 16:21 UTC (permalink / raw)
To: Mark Brown
Cc: Andy Shevchenko, Mika Westerberg, Lukas Wunner, Daniel Mack,
Haojian Zhuang, Robert Jarzmik, linux-arm-kernel, linux-spi,
linux-kernel, Shih-Yuan Lee
In-Reply-To: <20260720162117.32304-1-fourdollars@debian.org>
When operating in PIO mode on Intel LPSS SPI controllers, runtime PM
autosuspend clock-gates the hardware block when idle. On subsequent
transfers, MMIO accesses to trigger resume are performed before the runtime
PM state machine can wake the controller, causing PCIe Completion Timeouts.
This issue does not affect DMA mode, where the DMA engine holds the required
resources, nor does it affect non-LPSS controllers.
Lock out runtime PM autosuspend for LPSS SPI controllers specifically
when operating in PIO mode.
Acquire a runtime PM reference via pm_runtime_get_noresume() in
pxa2xx_spi_probe() after registration, and release it via
pm_runtime_put_noidle() in pxa2xx_spi_remove() before hardware teardown to
ensure the runtime PM reference held in probe is released without invoking
runtime_suspend on already-unclocked hardware.
Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org>
---
drivers/spi/spi-pxa2xx.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index 6bfd3382acc3..34241a6742eb 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -1473,6 +1473,9 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
goto out_error_irq_alloc;
}
+ if (is_lpss_ssp(drv_data) && !platform_info->enable_dma)
+ pm_runtime_get_noresume(dev);
+
return status;
out_error_irq_alloc:
@@ -1495,6 +1498,13 @@ void pxa2xx_spi_remove(struct device *dev)
spi_unregister_controller(drv_data->controller);
+ /* Release the PM reference held in probe for LPSS PIO mode before
+ * hardware teardown so the PM core does not invoke runtime_suspend
+ * on already-unclocked hardware.
+ */
+ if (is_lpss_ssp(drv_data) && !drv_data->controller_info->enable_dma)
+ pm_runtime_put_noidle(dev);
+
/* Disable SSP interrupt generation on hardware level while clock is active */
pxa2xx_spi_off(drv_data);
--
2.39.5
^ permalink raw reply related
* [PATCH v16 3/7] spi: pxa2xx: overhaul teardown and suspend sequence using pxa2xx_spi_off
From: Shih-Yuan Lee @ 2026-07-20 16:21 UTC (permalink / raw)
To: Mark Brown
Cc: Andy Shevchenko, Mika Westerberg, Lukas Wunner, Daniel Mack,
Haojian Zhuang, Robert Jarzmik, linux-arm-kernel, linux-spi,
linux-kernel, Shih-Yuan Lee
In-Reply-To: <20260720162117.32304-1-fourdollars@debian.org>
When removing the driver or suspending the device, the clock must not
be disabled while shared interrupts are still active. Gating the clock
before waiting for in-flight interrupt handlers to complete results
in race conditions where the handler performs unclocked MMIO accesses,
causing PCIe Completion Timeouts.
Overhaul the remove, suspend, and runtime_suspend paths to use a strict
synchronized teardown order:
1. Disable hardware interrupt generation at the controller level.
2. Mark the device state as suspended (suspended = true) to prevent
subsequent interrupt handlers from attempting MMIO reads.
3. Call synchronize_irq() to wait for any active interrupt handlers
to drain completely.
4. Gate the clock via pxa2xx_spi_clk_disable().
Additionally, commit 29d7e05c5f75 ("spi: pxa2xx: Avoid touching
SSCR0_SSE on MMP2") documented that disabling the hardware block via SSE on
MMP2 SoC platforms corrupts the RX/TX FIFO. Instead of calling
pxa_ssp_disable() directly, use the helper function pxa2xx_spi_off(),
which respects the MMP2 platform quirk by bypassing SSE register writes.
Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org>
---
drivers/spi/spi-pxa2xx.c | 45 ++++++++++++++++++++++++++++------------
1 file changed, 32 insertions(+), 13 deletions(-)
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index c44349ab2b52..6bfd3382acc3 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -1495,16 +1495,24 @@ void pxa2xx_spi_remove(struct device *dev)
spi_unregister_controller(drv_data->controller);
- /* Disable the SSP at the peripheral and SOC level */
- pxa_ssp_disable(ssp);
+ /* Disable SSP interrupt generation on hardware level while clock is active */
+ pxa2xx_spi_off(drv_data);
+
+ /* Mark as suspended to prevent further IRQ handling */
+ drv_data->suspended = true;
+
+ /* Wait for any pending interrupt handlers to complete */
+ synchronize_irq(ssp->irq);
+
+ /* Release IRQ */
+ free_irq(ssp->irq, drv_data);
+
+ /* Safe to disable the SSP clock now */
pxa2xx_spi_clk_disable(drv_data);
/* Release DMA */
if (drv_data->controller_info->enable_dma)
pxa2xx_spi_dma_release(drv_data);
-
- /* Release IRQ */
- free_irq(ssp->irq, drv_data);
}
EXPORT_SYMBOL_NS_GPL(pxa2xx_spi_remove, "SPI_PXA2xx");
@@ -1519,10 +1527,10 @@ static int pxa2xx_spi_suspend(struct device *dev)
return status;
drv_data->suspended = true;
- pxa_ssp_disable(ssp);
+ pxa2xx_spi_off(drv_data);
+ synchronize_irq(ssp->irq);
- if (!pm_runtime_suspended(dev))
- pxa2xx_spi_clk_disable(drv_data);
+ pxa2xx_spi_clk_disable(drv_data);
return 0;
}
@@ -1530,6 +1538,7 @@ static int pxa2xx_spi_suspend(struct device *dev)
static int pxa2xx_spi_resume(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
+ struct ssp_device *ssp = drv_data->ssp;
int status;
/* Enable the SSP clock */
@@ -1542,7 +1551,15 @@ static int pxa2xx_spi_resume(struct device *dev)
drv_data->suspended = false;
/* Start the queue running */
- return spi_controller_resume(drv_data->controller);
+ status = spi_controller_resume(drv_data->controller);
+ if (status) {
+ drv_data->suspended = true;
+ synchronize_irq(ssp->irq);
+ pxa2xx_spi_clk_disable(drv_data);
+ return status;
+ }
+
+ return 0;
}
static int pxa2xx_spi_runtime_suspend(struct device *dev)
@@ -1550,6 +1567,8 @@ static int pxa2xx_spi_runtime_suspend(struct device *dev)
struct driver_data *drv_data = dev_get_drvdata(dev);
drv_data->suspended = true;
+ pxa2xx_spi_off(drv_data);
+ synchronize_irq(drv_data->ssp->irq);
pxa2xx_spi_clk_disable(drv_data);
return 0;
}
@@ -1557,11 +1576,11 @@ static int pxa2xx_spi_runtime_suspend(struct device *dev)
static int pxa2xx_spi_runtime_resume(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
- int ret;
+ int status;
- ret = pxa2xx_spi_clk_enable(drv_data);
- if (ret)
- return ret;
+ status = pxa2xx_spi_clk_enable(drv_data);
+ if (status)
+ return status;
drv_data->suspended = false;
return 0;
--
2.39.5
^ permalink raw reply related
* [PATCH v16 5/7] spi: pxa2xx: disable DMA for Apple MacBook8,1
From: Shih-Yuan Lee @ 2026-07-20 16:21 UTC (permalink / raw)
To: Mark Brown
Cc: Andy Shevchenko, Mika Westerberg, Lukas Wunner, Daniel Mack,
Haojian Zhuang, Robert Jarzmik, linux-arm-kernel, linux-spi,
linux-kernel, Shih-Yuan Lee
In-Reply-To: <20260720162117.32304-1-fourdollars@debian.org>
On MacBook8,1 (early 2015 12" MacBook), the LPSS SPI controller at
00:15.4 suffers from hardware DMA handshake failures and interrupt
routing bugs, causing keyboard/touchpad transactions to fail when
DMA is enabled.
Move the forced PIO mode DMI quirk to spi-pxa2xx-pci.c (the LPSS host
controller PCI glue driver) to avoid layering violations in client
drivers (such as applespi).
Add an explicit DMI match table for MacBook8,1 and a module parameter
spi_pxa2xx_force_pio to allow forcing PIO mode on demand.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=108331
Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org>
---
drivers/spi/spi-pxa2xx-pci.c | 35 +++++++++++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/drivers/spi/spi-pxa2xx-pci.c b/drivers/spi/spi-pxa2xx-pci.c
index cae77ac18520..31bdaa096d9e 100644
--- a/drivers/spi/spi-pxa2xx-pci.c
+++ b/drivers/spi/spi-pxa2xx-pci.c
@@ -18,9 +18,14 @@
#include <linux/dmaengine.h>
#include <linux/platform_data/dma-dw.h>
+#include <linux/dmi.h>
#include "spi-pxa2xx.h"
+static bool spi_pxa2xx_force_pio;
+module_param_named(force_pio, spi_pxa2xx_force_pio, bool, 0444);
+MODULE_PARM_DESC(force_pio, "Force PIO mode (disables DMA) for SPI transfers. ([0] = disabled, 1 = enabled)");
+
#define PCI_DEVICE_ID_INTEL_QUARK_X1000 0x0935
#define PCI_DEVICE_ID_INTEL_BYT 0x0f0e
#define PCI_DEVICE_ID_INTEL_MRFLD 0x1194
@@ -93,6 +98,32 @@ static void lpss_dma_put_device(void *dma_dev)
pci_dev_put(dma_dev);
}
+static const struct dmi_system_id pxa2xx_spi_pci_dmi_table[] = {
+ {
+ .ident = "Apple MacBook8,1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MacBook8,1"),
+ },
+ },
+ { }
+};
+
+static bool pxa2xx_spi_pci_can_dma(struct pci_dev *dev)
+{
+ if (spi_pxa2xx_force_pio) {
+ pci_info(dev, "Forcing PIO mode (disabling DMA)\n");
+ return false;
+ }
+
+ if (dmi_check_system(pxa2xx_spi_pci_dmi_table)) {
+ pci_info(dev, "MacBook8,1 detected: disabling DMA to force PIO mode\n");
+ return false;
+ }
+
+ return true;
+}
+
static int lpss_spi_setup(struct pci_dev *dev, struct pxa2xx_spi_controller *c)
{
struct ssp_device *ssp = &c->ssp;
@@ -166,7 +197,7 @@ static int lpss_spi_setup(struct pci_dev *dev, struct pxa2xx_spi_controller *c)
c->dma_filter = lpss_dma_filter;
c->dma_burst_size = 1;
- c->enable_dma = 1;
+ c->enable_dma = pxa2xx_spi_pci_can_dma(dev);
return 0;
}
@@ -238,7 +269,7 @@ static int mrfld_spi_setup(struct pci_dev *dev, struct pxa2xx_spi_controller *c)
c->dma_filter = lpss_dma_filter;
c->dma_burst_size = 8;
- c->enable_dma = 1;
+ c->enable_dma = pxa2xx_spi_pci_can_dma(dev);
return 0;
}
--
2.39.5
^ permalink raw reply related
* [PATCH v16 6/7] spi: pxa2xx: restore LPSS private register state on S3 resume
From: Shih-Yuan Lee @ 2026-07-20 16:21 UTC (permalink / raw)
To: Mark Brown
Cc: Andy Shevchenko, Mika Westerberg, Lukas Wunner, Daniel Mack,
Haojian Zhuang, Robert Jarzmik, linux-arm-kernel, linux-spi,
linux-kernel, Shih-Yuan Lee
In-Reply-To: <20260720162117.32304-1-fourdollars@debian.org>
Intel LPSS SPI controllers lose all private register state across S3
suspend because the LPSS power domain is fully removed. On resume the
driver only re-enables the SSP clock, leaving the LPSS private registers
in their power-on-reset state, which causes two problems:
1. LPSS_PRIV_RESETS (offset 0x04 within the LPSS private space) stays
zero, keeping the functional block in reset. Any MMIO access while
the block is held in reset causes a PCIe Completion Timeout and a
watchdog-triggered system reset. LPSS_PRIV_RESETS_FUNC and
LPSS_PRIV_RESETS_IDMA must be de-asserted before any other register
access on resume.
2. The LPSS software chip-select control register must not be blindly
restored from its suspend-time snapshot: if CS was asserted at the
moment of suspend, restoring that state corrupts the first
post-resume SPI transaction. Instead, call lpss_ssp_setup() which
unconditionally writes SW_MODE | CS_HIGH (idle/deasserted), matching
the state established at probe time.
To resolve these issues safely:
- Wrap S3 suspend/resume with pm_runtime_resume_and_get() and
pm_runtime_put_noidle() to guarantee active clocks during MMIO
access and preserve PM reference counting.
- Restrict LPSS private register save/restore to LPT, BYT, and BSW
platforms via pxa2xx_spi_need_lpss_restore() (newer platforms are
handled by intel-lpss.c).
- Save only the first 6 LPSS private registers (offsets 0x00..0x14) in
drv_data during suspend, avoiding reserved offsets beyond 0x14.
- On resume, de-assert resets first, restore saved registers, call
lpss_ssp_setup(), and clear drv_data->suspended to prevent unclocked
IRQ access.
- Add error recovery paths for spi_controller_suspend/resume failures.
- On the resume error path, call pm_runtime_set_suspended() before
pm_runtime_put_noidle() to align the PM runtime state with the
already-disabled hardware clock, preventing pxa2xx_spi_runtime_suspend()
from attempting unclocked MMIO via pxa2xx_spi_off().
Link: https://bugzilla.kernel.org/show_bug.cgi?id=108331
Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org>
---
drivers/spi/spi-pxa2xx.c | 105 +++++++++++++++++++++++++++++++++++++--
drivers/spi/spi-pxa2xx.h | 1 +
2 files changed, 102 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index 34241a6742eb..851626ead25e 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -72,6 +72,11 @@ struct chip_data {
#define LPSS_CAPS_CS_EN_SHIFT 9
#define LPSS_CAPS_CS_EN_MASK (0xf << LPSS_CAPS_CS_EN_SHIFT)
+/* Offsets from drv_data->lpss_base */
+#define LPSS_PRIV_RESETS 0x04
+#define LPSS_PRIV_RESETS_IDMA BIT(2)
+#define LPSS_PRIV_RESETS_FUNC 0x3
+
#define LPSS_PRIV_CLOCK_GATE 0x38
#define LPSS_PRIV_CLOCK_GATE_CLK_CTL_MASK 0x3
#define LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_ON 0x3
@@ -189,6 +194,18 @@ static bool is_lpss_ssp(const struct driver_data *drv_data)
}
}
+static bool pxa2xx_spi_need_lpss_restore(const struct driver_data *drv_data)
+{
+ switch (drv_data->ssp_type) {
+ case LPSS_LPT_SSP:
+ case LPSS_BYT_SSP:
+ case LPSS_BSW_SSP:
+ return true;
+ default:
+ return false;
+ }
+}
+
static bool is_quark_x1000_ssp(const struct driver_data *drv_data)
{
return drv_data->ssp_type == QUARK_X1000_SSP;
@@ -1532,17 +1549,44 @@ static int pxa2xx_spi_suspend(struct device *dev)
struct ssp_device *ssp = drv_data->ssp;
int status;
+ status = pm_runtime_resume_and_get(dev);
+ if (status < 0)
+ return status;
+
+
status = spi_controller_suspend(drv_data->controller);
if (status)
- return status;
+ goto out_put;
+ /* Disable SSP interrupt generation on hardware level while clock is active */
drv_data->suspended = true;
pxa2xx_spi_off(drv_data);
synchronize_irq(ssp->irq);
+ if (pxa2xx_spi_need_lpss_restore(drv_data)) {
+ unsigned int i;
+
+ /*
+ * Save the first 6 LPSS private registers (offsets 0x00 to 0x14)
+ * while the clock is still enabled. They are lost when the LPSS
+ * power domain is removed across S3 and must be restored on resume.
+ * Use drv_data->lpss_base so the correct per-platform offset
+ * is applied regardless of LPSS IP revision.
+ * Registers beyond 0x14 (except CS control at 0x18) are reserved
+ * or unimplemented on LPT, and accessing them triggers a PCIe
+ * Completion Timeout causing a system halt.
+ */
+ for (i = 0; i < 6; i++)
+ drv_data->lpss_priv_ctx[i] = readl(drv_data->lpss_base + i * 4);
+ }
+
pxa2xx_spi_clk_disable(drv_data);
return 0;
+
+out_put:
+ pm_runtime_put_noidle(dev);
+ return status;
}
static int pxa2xx_spi_resume(struct device *dev)
@@ -1555,9 +1599,47 @@ static int pxa2xx_spi_resume(struct device *dev)
if (!pm_runtime_suspended(dev)) {
status = pxa2xx_spi_clk_enable(drv_data);
if (status)
- return status;
+ goto out_put;
}
+ if (pxa2xx_spi_need_lpss_restore(drv_data)) {
+ unsigned int i;
+
+ /*
+ * The LPSS power domain is removed across S3, taking
+ * all private registers with it. De-assert the
+ * functional block and IDMA resets first; any MMIO
+ * access while the block is held in reset causes a
+ * PCIe Completion Timeout and a watchdog-triggered
+ * system reset.
+ */
+ writel(LPSS_PRIV_RESETS_FUNC | LPSS_PRIV_RESETS_IDMA,
+ drv_data->lpss_base + LPSS_PRIV_RESETS);
+
+ /* Restore the other 5 saved private registers */
+ for (i = 0; i < 6; i++) {
+ if (i == LPSS_PRIV_RESETS / 4)
+ continue;
+ writel(drv_data->lpss_priv_ctx[i],
+ drv_data->lpss_base + i * 4);
+ }
+ }
+
+ if (is_lpss_ssp(drv_data)) {
+ /*
+ * Re-initialise the SW chip-select control register so
+ * CS starts deasserted (SW_MODE | CS_HIGH), regardless
+ * of the state it was in at suspend time. A stale
+ * asserted CS on the first post-resume transaction
+ * corrupts the write-status response from the device.
+ */
+ lpss_ssp_setup(drv_data);
+ }
+
+ /*
+ * Now that resets are de-asserted and registers are restored,
+ * it is safe to handle interrupts.
+ */
drv_data->suspended = false;
/* Start the queue running */
@@ -1566,10 +1648,25 @@ static int pxa2xx_spi_resume(struct device *dev)
drv_data->suspended = true;
synchronize_irq(ssp->irq);
pxa2xx_spi_clk_disable(drv_data);
- return status;
+ goto out_put;
}
- return 0;
+out_put:
+ if (!pm_runtime_suspended(dev)) {
+ if (status)
+ /*
+ * Clock is already disabled on the error path; align
+ * the PM runtime state with hardware reality before
+ * releasing the reference so the PM core does not
+ * later invoke pxa2xx_spi_runtime_suspend() and attempt
+ * unclocked MMIO via pxa2xx_spi_off().
+ */
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+ }
+
+ return status;
+
}
static int pxa2xx_spi_runtime_suspend(struct device *dev)
diff --git a/drivers/spi/spi-pxa2xx.h b/drivers/spi/spi-pxa2xx.h
index 44f37bf9c519..48169494f74e 100644
--- a/drivers/spi/spi-pxa2xx.h
+++ b/drivers/spi/spi-pxa2xx.h
@@ -71,6 +71,7 @@ struct driver_data {
irqreturn_t (*transfer_handler)(struct driver_data *drv_data);
void __iomem *lpss_base;
+ u32 lpss_priv_ctx[6];
bool suspended;
bool clk_enabled;
--
2.39.5
^ permalink raw reply related
* [PATCH v16 7/7] spi: pxa2xx: rename local status variable to ret
From: Shih-Yuan Lee @ 2026-07-20 16:21 UTC (permalink / raw)
To: Mark Brown
Cc: Andy Shevchenko, Mika Westerberg, Lukas Wunner, Daniel Mack,
Haojian Zhuang, Robert Jarzmik, linux-arm-kernel, linux-spi,
linux-kernel, Shih-Yuan Lee
In-Reply-To: <20260720162117.32304-1-fourdollars@debian.org>
Rename the return value variable name from 'status' to 'ret' in the
pxa2xx_spi_probe(), pxa2xx_spi_suspend(), pxa2xx_spi_resume(), and
pxa2xx_spi_runtime_resume() functions to conform to standard Linux kernel
coding conventions.
Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org>
---
drivers/spi/spi-pxa2xx.c | 66 +++++++++++++++++++---------------------
1 file changed, 32 insertions(+), 34 deletions(-)
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index 851626ead25e..9379aac82c7a 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -1313,7 +1313,7 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
struct spi_controller *controller;
struct driver_data *drv_data;
const struct lpss_config *config;
- int status;
+ int ret;
u32 tmp;
if (platform_info->is_target)
@@ -1372,8 +1372,8 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
/* Setup DMA if requested */
if (platform_info->enable_dma) {
- status = pxa2xx_spi_dma_setup(drv_data);
- if (status) {
+ ret = pxa2xx_spi_dma_setup(drv_data);
+ if (ret) {
dev_warn(dev, "no DMA channels available, using PIO\n");
platform_info->enable_dma = false;
} else {
@@ -1387,16 +1387,16 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
}
/* Enable SOC clock */
- status = pxa2xx_spi_clk_enable(drv_data);
- if (status)
+ ret = pxa2xx_spi_clk_enable(drv_data);
+ if (ret)
goto out_error_dma_alloc;
drv_data->suspended = false;
- status = request_irq(ssp->irq, ssp_int, IRQF_SHARED, dev_name(dev),
+ ret = request_irq(ssp->irq, ssp_int, IRQF_SHARED, dev_name(dev),
drv_data);
- if (status < 0) {
- status = dev_err_probe(dev, status, "cannot get IRQ %d\n", ssp->irq);
+ if (ret < 0) {
+ ret = dev_err_probe(dev, ret, "cannot get IRQ %d\n", ssp->irq);
goto out_error_clock_enabled;
}
@@ -1477,23 +1477,23 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
drv_data->gpiod_ready = devm_gpiod_get_optional(dev,
"ready", GPIOD_OUT_LOW);
if (IS_ERR(drv_data->gpiod_ready)) {
- status = PTR_ERR(drv_data->gpiod_ready);
+ ret = PTR_ERR(drv_data->gpiod_ready);
goto out_error_irq_alloc;
}
}
/* Register with the SPI framework */
dev_set_drvdata(dev, drv_data);
- status = spi_register_controller(controller);
- if (status) {
- dev_err_probe(dev, status, "problem registering SPI controller\n");
+ ret = spi_register_controller(controller);
+ if (ret) {
+ dev_err_probe(dev, ret, "problem registering SPI controller\n");
goto out_error_irq_alloc;
}
if (is_lpss_ssp(drv_data) && !platform_info->enable_dma)
pm_runtime_get_noresume(dev);
- return status;
+ return ret;
out_error_irq_alloc:
free_irq(ssp->irq, drv_data);
@@ -1504,7 +1504,7 @@ int pxa2xx_spi_probe(struct device *dev, struct ssp_device *ssp,
out_error_dma_alloc:
pxa2xx_spi_dma_release(drv_data);
- return status;
+ return ret;
}
EXPORT_SYMBOL_NS_GPL(pxa2xx_spi_probe, "SPI_PXA2xx");
@@ -1547,15 +1547,14 @@ static int pxa2xx_spi_suspend(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
struct ssp_device *ssp = drv_data->ssp;
- int status;
-
- status = pm_runtime_resume_and_get(dev);
- if (status < 0)
- return status;
+ int ret;
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
- status = spi_controller_suspend(drv_data->controller);
- if (status)
+ ret = spi_controller_suspend(drv_data->controller);
+ if (ret)
goto out_put;
/* Disable SSP interrupt generation on hardware level while clock is active */
@@ -1586,19 +1585,19 @@ static int pxa2xx_spi_suspend(struct device *dev)
out_put:
pm_runtime_put_noidle(dev);
- return status;
+ return ret;
}
static int pxa2xx_spi_resume(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
struct ssp_device *ssp = drv_data->ssp;
- int status;
+ int ret;
/* Enable the SSP clock */
if (!pm_runtime_suspended(dev)) {
- status = pxa2xx_spi_clk_enable(drv_data);
- if (status)
+ ret = pxa2xx_spi_clk_enable(drv_data);
+ if (ret)
goto out_put;
}
@@ -1643,8 +1642,8 @@ static int pxa2xx_spi_resume(struct device *dev)
drv_data->suspended = false;
/* Start the queue running */
- status = spi_controller_resume(drv_data->controller);
- if (status) {
+ ret = spi_controller_resume(drv_data->controller);
+ if (ret) {
drv_data->suspended = true;
synchronize_irq(ssp->irq);
pxa2xx_spi_clk_disable(drv_data);
@@ -1653,7 +1652,7 @@ static int pxa2xx_spi_resume(struct device *dev)
out_put:
if (!pm_runtime_suspended(dev)) {
- if (status)
+ if (ret)
/*
* Clock is already disabled on the error path; align
* the PM runtime state with hardware reality before
@@ -1665,8 +1664,7 @@ static int pxa2xx_spi_resume(struct device *dev)
pm_runtime_put_noidle(dev);
}
- return status;
-
+ return ret;
}
static int pxa2xx_spi_runtime_suspend(struct device *dev)
@@ -1683,11 +1681,11 @@ static int pxa2xx_spi_runtime_suspend(struct device *dev)
static int pxa2xx_spi_runtime_resume(struct device *dev)
{
struct driver_data *drv_data = dev_get_drvdata(dev);
- int status;
+ int ret;
- status = pxa2xx_spi_clk_enable(drv_data);
- if (status)
- return status;
+ ret = pxa2xx_spi_clk_enable(drv_data);
+ if (ret)
+ return ret;
drv_data->suspended = false;
return 0;
--
2.39.5
^ permalink raw reply related
* Re: [PATCH v9 03/23] dt-bindings: ufs: mediatek,ufs: Add mt8196 variant
From: Louis-Alexis Eyraud @ 2026-07-20 16:21 UTC (permalink / raw)
To: Rob Herring, Nicolas Frattaroli
Cc: Alim Akhtar, Avri Altman, Bart Van Assche, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Chunfeng Yun, Vinod Koul, Kishon Vijay Abraham I, Peter Wang,
Stanley Jhu, James E.J. Bottomley, Martin K. Petersen,
Philipp Zabel, Liam Girdwood, Mark Brown, Chaotian Jing,
Neil Armstrong, kernel, linux-scsi, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, linux-phy, Conor Dooley
In-Reply-To: <CAL_Jsq+mF_Q7Ld8__ZTc5yDvWAT6uK5wxfBJM-YdFjOvdiDc-w@mail.gmail.com>
Hi Rob,
On Tue, 2026-03-10 at 13:10 -0500, Rob Herring wrote:
> On Fri, Mar 6, 2026 at 12:37 PM Nicolas Frattaroli
> <nicolas.frattaroli@collabora.com> wrote:
> >
> > On Friday, 6 March 2026 17:33:05 Central European Standard Time Rob
> > Herring wrote:
> > > On Fri, Mar 06, 2026 at 02:24:44PM +0100, Nicolas Frattaroli
> > > wrote:
> > > > The MediaTek MT8196 SoC's UFS controller uses three additional
> > > > clocks
> > > > compared to the MT8195, and a different set of supplies. It is
> > > > therefore
> > > > not compatible with the MT8195.
> > > >
> > > > While it does have a AVDD09_UFS_1 pin in addition to the
> > > > AVDD09_UFS pin,
> > > > it appears that these two pins are commoned together, as the
> > > > board
> > > > schematic I have access to uses the same supply for both, and
> > > > the
> > > > downstream driver does not distinguish between the two supplies
> > > > either.
> > > >
> > > > Add a compatible for it, and modify the binding
> > > > correspondingly.
> > > >
> > > > Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
> > > > Acked-by: Vinod Koul <vkoul@kernel.org>
> > > > Acked-by: Conor Dooley <conor.dooley@microchip.com>
> > > > Reviewed-by: AngeloGioacchino Del Regno
> > > > <angelogioacchino.delregno@collabora.com>
> > > > Signed-off-by: Nicolas Frattaroli
> > > > <nicolas.frattaroli@collabora.com>
> > > > ---
> > > > .../devicetree/bindings/ufs/mediatek,ufs.yaml | 58
> > > > +++++++++++++++++++++-
> > > > 1 file changed, 57 insertions(+), 1 deletion(-)
> > > >
> > > > diff --git
> > > > a/Documentation/devicetree/bindings/ufs/mediatek,ufs.yaml
> > > > b/Documentation/devicetree/bindings/ufs/mediatek,ufs.yaml
> > > > index e0aef3e5f56b..a82119ecbfe8 100644
> > > > --- a/Documentation/devicetree/bindings/ufs/mediatek,ufs.yaml
> > > > +++ b/Documentation/devicetree/bindings/ufs/mediatek,ufs.yaml
> > > > @@ -16,10 +16,11 @@ properties:
> > > > - mediatek,mt8183-ufshci
> > > > - mediatek,mt8192-ufshci
> > > > - mediatek,mt8195-ufshci
> > > > + - mediatek,mt8196-ufshci
> > > >
> > > > clocks:
> > > > minItems: 1
> > > > - maxItems: 13
> > > > + maxItems: 16
> > > >
> > > > clock-names:
> > > > minItems: 1
> > > > @@ -37,6 +38,9 @@ properties:
> > > > - const: crypt_perf
> > > > - const: ufs_rx_symbol0
> > > > - const: ufs_rx_symbol1
> > > > + - const: ufs_sel
> > >
> > > "ufs" is redundant as all the clocks are for UFS. Same comment on
> > > prior
> > > patch.
> >
> > Is this naming a big enough concern to block this series with two
> > explicit acks on this patch that fixes a wholly broken and useless
> > binding?
>
> Shrug... Is changing it really that hard?
>
Since I'm currently working on rebasing this series and fixing its
remaining open issues (compilation, dt-bindings warnings,...) to send a
new revision, I've looked at the questions you raised.
First, renaming those clocks and all the other starting with "ufs_"
prefix (including the one that is simply named ufs) is indeed easy and
needs just a little rework.
Mostly, a driver patch, as it is currently explicitly using the name of
the 3 ufs_sel clocks and devicetree patches to adapt to this change.
The next series revision will include the devicetree patches for MT8195
SoC and the two boards that integrate this SoC and an UFS storage
(Genio 1200 EVK UFS and Radxa NIO-12L), to fix the warnings that the
dt-binding changes, done by patch 1 (additional clocks, freq-table-hz
deprecation, additional power supplies) in the v9 revision, generate.
> > > > + - const: ufs_sel_min_src
> > > > + - const: ufs_sel_max_src
> > >
> > > "src" sounds like a parent clock? If so, probably shouldn't be in
> > > the
> > > clocks list. 'assigned-clocks' is for dealing with parent clocks.
> > >
> >
> > I don't know what it is, and I have no way to consult any
> > documentation
> > that would tell me what it is. I am trying to put out this dumpster
> > fire
> > of a downstream turd that made its way into mainline as the review
> > process
> > has been completely subverted, and is only getting worse with each
> > passing
> > month that MediaTek is allowed to block this series from
> > progressing while
> > sneaking further changes through.
>
> It's good Mediatek is active, then they can tell us what the clocks
> are for. I would think the driver would give some clue.
>
Second, when searching in the driver code and the git history, it shows
that ufs_sel is indeed a parent clock.
The ufs_sel/ufs_sel_min_src/ufs_sel_max_src clock use in the driver
code was introduced by the commit b7dbc686f60b ("scsi: ufs: ufs-
mediatek: Support clk-scaling to optimize power consumption") to
implement a dynamic clock scaling feature.
ufs_sel is supposed to be the parent clock of the main clock ("ufs" in
dt-bindings) and both ufs_sel_min_src/ufs_sel_max_src the parent of
ufs_sel.
The code switches conditionally the ufs_sel parent to modify the ufs
clock rate (ufs_sel_max_src for maximum performance, ufs_sel_min_src
otherwise).
I've also looked at different downstream kernel trees, the assigned
clocks values for those 3 clocks in the ufshci node in devicetree are
consistent with the clock hierarchy in the MT8196 clock controllers
drivers.
This feature also seems to be linked to another one that added the
clock scaling for the FDE clock (ufs_aes in dt-bindings).
The commit that introduced it is 5e5976f5242d ("scsi: ufs: host:
mediatek: Support FDE (AES) clock scaling") and it also added the 3
undocumented clocks: ufs_fde, ufs_fde_min_src, ufs_fde_max_src.
It is similar to the previous described one.
The current driver code seems to require having the "ufs_fde" clock
(supposed to be the ufs_aes parent clock) in the devicetree, so that
the clock scaling feature for the "ufs_sel" clock is performed and I
did not find why.
So, with this current series dt-bindings patches, ufs clock scaling
feature seems not completely described yet.
There is also the crypto boost feature that make use of the
crypt_mux/crypt_lp/crypt_perf clocks in a similar way.
They were missing from dt-bindings, before this series patches made by
Nicolas to documented them. Angelo also sent a patch two years ago in
that regard ([1]) but did not get picked.
The feature has been introduced by commit 590b0d2372fe ("scsi: ufs-
mediatek: Support performance mode for inline encryption engine")
The crypt_mux clock is also supposed to be the ufs_aes parent clock and
its own parent is switched on need between crypt_lp (low power) and
crypt_perf (performance).
This feature is also depending two undocumented property:
- dvfsrc-vcore-supply: Angelo sent [2] to add it and this current
series forgot to add it too (to be done for v10 ?)
- mediatek,ufs-boost-crypt: vendor specific property to enable this
feature. Angelo sent [3] to remove its need but got reject back then
Note that crypto boost and FDE clock scaling features seem not to be
supposed to be enabled at the same time.
Again, this crypto boost feature seems not completely described yet.
Sorry for the wall of text but I felt it was better to add extra
details regarding all those features and how they relate to each other
to have a more complete answer regarding ufs_sel.
[1]
https://lore.kernel.org/linux-mediatek/20240612074309.50278-8-angelogioacchino.delregno@collabora.com/
[2]
https://lore.kernel.org/linux-mediatek/20240612074309.50278-9-angelogioacchino.delregno@collabora.com/
[3]
https://lore.kernel.org/linux-mediatek/20240612074309.50278-4-angelogioacchino.delregno@collabora.com/
> I don't see how accepting sub-par bindings or not fixes the issues
> here.
So, since all these features rely heavily on optional parent clocks,
that were not documented before being used in driver code, what should
be done to make progress and fix these dt-bindings?
What would you recommend to do in order to resolve those topics?
I'm open for ideas, please.
Best regards,
Louis-Alexis
>
> Rob
^ permalink raw reply
* [PATCH v1 10/11] KVM: arm64: nVHE: Check hypercall handlers against the declared ABI
From: Fuad Tabba @ 2026-07-20 16:24 UTC (permalink / raw)
To: maz, oupton, linux-arm-kernel, kvmarm
Cc: catalin.marinas, will, rostedt, mhiramat, alexandru.elisei,
vdonnefort, joey.gouly, seiden, suzuki.poulose, yuzenghui,
qperret, ardb, linux-kernel, linux-trace-kernel, tabba,
fuad.tabba
In-Reply-To: <20260720161343.1367007-1-fuad.tabba@linux.dev>
Each hypercall handler unmarshals its arguments from the host context
with hand-written DECLARE_REG() casts that nothing ties to what the
caller passed: a handler can disagree with its caller in argument type,
count or register index without a diagnostic.
Generate the unmarshalling instead. DEFINE_KVM_HOST_HCALL() expands to
the handle_<name>() glue, modelled on the syscall wrappers, and checks
the handler's parameter list against the signature declared in
kvm_hcall.h, so both ends of every hypercall are now compiled against
the same declaration. Handler bodies keep their logic and lose the
DECLARE_REG() and return-register boilerplate. The compiled handlers
are instruction-for-instruction identical, apart from flush_hyp_vcpu()
and sync_hyp_vcpu() now being inlined into their only caller.
Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_hcall.h | 19 +-
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 420 ++++++++++++++---------------
2 files changed, 213 insertions(+), 226 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_hcall.h b/arch/arm64/include/asm/kvm_hcall.h
index 51bfd14748464..6aa2df90a14a4 100644
--- a/arch/arm64/include/asm/kvm_hcall.h
+++ b/arch/arm64/include/asm/kvm_hcall.h
@@ -41,6 +41,8 @@ struct vgic_v5_cpu_if;
#define __KVM_HCALL_MAP(n, ...) __KVM_HCALL_MAP##n(__VA_ARGS__)
#define __KVM_HCALL_DECL(t, a) t a
+#define __KVM_HCALL_LONG(t, a) unsigned long a
+#define __KVM_HCALL_CAST(t, a) (__force t) a
#define __KVM_HCALL_ARGS(t, a) a
#ifndef __KVM_NVHE_HYPERVISOR__
@@ -124,10 +126,19 @@ struct vgic_v5_cpu_if;
#define kvm_call_hyp_ret(f, ...) f(__VA_ARGS__)
#define kvm_call_hyp_nvhe(f, ...) f(__VA_ARGS__)
-#define DECLARE_KVM_HOST_HCALL(ret, name, x, ...)
-#define DECLARE_KVM_HOST_HCALL_VOID(name, x, ...)
-#define DECLARE_KVM_HOST_HCALL0(ret, name)
-#define DECLARE_KVM_HOST_HCALL0_VOID(name)
+/*
+ * At EL2 each declaration emits the canonical signature of the hypercall,
+ * which DEFINE_KVM_HOST_HCALL() in hyp-main.c checks the handler
+ * definition against.
+ */
+#define DECLARE_KVM_HOST_HCALL(ret, name, x, ...) \
+ typedef ret kvm_host_hcall_sig_##name(__KVM_HCALL_MAP(x, __KVM_HCALL_DECL, __VA_ARGS__));
+#define DECLARE_KVM_HOST_HCALL_VOID(name, x, ...) \
+ typedef void kvm_host_hcall_sig_##name(__KVM_HCALL_MAP(x, __KVM_HCALL_DECL, __VA_ARGS__));
+#define DECLARE_KVM_HOST_HCALL0(ret, name) \
+ typedef ret kvm_host_hcall_sig_##name(void);
+#define DECLARE_KVM_HOST_HCALL0_VOID(name) \
+ typedef void kvm_host_hcall_sig_##name(void);
#endif /* __KVM_NVHE_HYPERVISOR__ */
/* Hypercalls that are unavailable once pKVM has finalised. */
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 7537d422deab3..f2501249f4391 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -24,6 +24,62 @@
DEFINE_PER_CPU(struct kvm_nvhe_init_params, kvm_init_params);
+/*
+ * Define a hypercall handler: handle_<name> unmarshals the arguments from
+ * the host context and hands them, correctly typed, to the body that
+ * follows the macro. The parameter list is type-checked against the
+ * signature declared in <asm/kvm_hcall.h>, so the handler cannot drift
+ * from what the typed caller stubs marshal in. Modelled on the syscall
+ * wrappers.
+ */
+#define KVM_HOST_HCALL_REGS(x) \
+ __KVM_HCALL_MAP(x, __KVM_HCALL_ARGS \
+ ,, cpu_reg(host_ctxt, 1),, cpu_reg(host_ctxt, 2) \
+ ,, cpu_reg(host_ctxt, 3),, cpu_reg(host_ctxt, 4) \
+ ,, cpu_reg(host_ctxt, 5),, cpu_reg(host_ctxt, 6))
+
+#define DEFINE_KVM_HOST_HCALL(ret, name, x, ...) \
+ static kvm_host_hcall_sig_##name __do_##name; \
+ static __always_inline \
+ ret __se_##name(__KVM_HCALL_MAP(x, __KVM_HCALL_LONG, __VA_ARGS__)) \
+ { \
+ return __do_##name(__KVM_HCALL_MAP(x, __KVM_HCALL_CAST, __VA_ARGS__)); \
+ } \
+ static void handle_##name(struct kvm_cpu_context *host_ctxt) \
+ { \
+ cpu_reg(host_ctxt, 1) = __se_##name(KVM_HOST_HCALL_REGS(x)); \
+ } \
+ static ret __do_##name(__KVM_HCALL_MAP(x, __KVM_HCALL_DECL, __VA_ARGS__))
+
+#define DEFINE_KVM_HOST_HCALL_VOID(name, x, ...) \
+ static kvm_host_hcall_sig_##name __do_##name; \
+ static __always_inline \
+ void __se_##name(__KVM_HCALL_MAP(x, __KVM_HCALL_LONG, __VA_ARGS__)) \
+ { \
+ __do_##name(__KVM_HCALL_MAP(x, __KVM_HCALL_CAST, __VA_ARGS__)); \
+ } \
+ static void handle_##name(struct kvm_cpu_context *host_ctxt) \
+ { \
+ __se_##name(KVM_HOST_HCALL_REGS(x)); \
+ } \
+ static void __do_##name(__KVM_HCALL_MAP(x, __KVM_HCALL_DECL, __VA_ARGS__))
+
+#define DEFINE_KVM_HOST_HCALL0(ret, name) \
+ static kvm_host_hcall_sig_##name __do_##name; \
+ static void handle_##name(struct kvm_cpu_context *host_ctxt) \
+ { \
+ cpu_reg(host_ctxt, 1) = __do_##name(); \
+ } \
+ static ret __do_##name(void)
+
+#define DEFINE_KVM_HOST_HCALL0_VOID(name) \
+ static kvm_host_hcall_sig_##name __do_##name; \
+ static void handle_##name(struct kvm_cpu_context *host_ctxt) \
+ { \
+ __do_##name(); \
+ } \
+ static void __do_##name(void)
+
/* Number of implemented GICv3 LRs. Used by flush_hyp_vcpu(). */
unsigned int hyp_gicv3_nr_lr;
@@ -185,11 +241,9 @@ static void sync_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
host_cpu_if->vgic_lr[i] = hyp_cpu_if->vgic_lr[i];
}
-static void handle___pkvm_vcpu_load(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__pkvm_vcpu_load, 3,
+ pkvm_handle_t, handle, unsigned int, vcpu_idx, u64, hcr_el2)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
- DECLARE_REG(unsigned int, vcpu_idx, host_ctxt, 2);
- DECLARE_REG(u64, hcr_el2, host_ctxt, 3);
struct pkvm_hyp_vcpu *hyp_vcpu;
hyp_vcpu = pkvm_load_hyp_vcpu(handle, vcpu_idx);
@@ -206,7 +260,7 @@ static void handle___pkvm_vcpu_load(struct kvm_cpu_context *host_ctxt)
}
}
-static void handle___pkvm_vcpu_put(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0_VOID(__pkvm_vcpu_put)
{
struct pkvm_hyp_vcpu *hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
@@ -214,9 +268,9 @@ static void handle___pkvm_vcpu_put(struct kvm_cpu_context *host_ctxt)
pkvm_put_hyp_vcpu(hyp_vcpu);
}
-static void handle___kvm_vcpu_run(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __kvm_vcpu_run, 1,
+ struct kvm_vcpu *, host_vcpu)
{
- DECLARE_REG(struct kvm_vcpu *, host_vcpu, host_ctxt, 1);
int ret;
if (unlikely(is_protected_kvm_enabled())) {
@@ -228,15 +282,11 @@ static void handle___kvm_vcpu_run(struct kvm_cpu_context *host_ctxt)
* loading a vcpu. Therefore, if SME features enabled the host
* is misbehaving.
*/
- if (unlikely(system_supports_sme() && read_sysreg_s(SYS_SVCR))) {
- ret = -EINVAL;
- goto out;
- }
+ if (unlikely(system_supports_sme() && read_sysreg_s(SYS_SVCR)))
+ return -EINVAL;
- if (!hyp_vcpu) {
- ret = -EINVAL;
- goto out;
- }
+ if (!hyp_vcpu)
+ return -EINVAL;
flush_hyp_vcpu(hyp_vcpu);
@@ -251,8 +301,8 @@ static void handle___kvm_vcpu_run(struct kvm_cpu_context *host_ctxt)
ret = __kvm_vcpu_run(vcpu);
fpsimd_lazy_switch_to_host(vcpu);
}
-out:
- cpu_reg(host_ctxt, 1) = ret;
+
+ return ret;
}
static int pkvm_refill_memcache(struct pkvm_hyp_vcpu *hyp_vcpu)
@@ -264,184 +314,150 @@ static int pkvm_refill_memcache(struct pkvm_hyp_vcpu *hyp_vcpu)
&host_vcpu->arch.pkvm_memcache);
}
-static void handle___pkvm_host_donate_guest(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_donate_guest, 2,
+ u64, pfn, u64, gfn)
{
- DECLARE_REG(u64, pfn, host_ctxt, 1);
- DECLARE_REG(u64, gfn, host_ctxt, 2);
struct pkvm_hyp_vcpu *hyp_vcpu;
- int ret = -EINVAL;
+ int ret;
hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
if (!hyp_vcpu || !pkvm_hyp_vcpu_is_protected(hyp_vcpu))
- goto out;
+ return -EINVAL;
ret = pkvm_refill_memcache(hyp_vcpu);
if (ret)
- goto out;
+ return ret;
- ret = __pkvm_host_donate_guest(pfn, gfn, hyp_vcpu);
-out:
- cpu_reg(host_ctxt, 1) = ret;
+ return __pkvm_host_donate_guest(pfn, gfn, hyp_vcpu);
}
-static void handle___pkvm_host_share_guest(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_share_guest, 4,
+ u64, pfn, u64, gfn, u64, nr_pages, u64, prot)
{
- DECLARE_REG(u64, pfn, host_ctxt, 1);
- DECLARE_REG(u64, gfn, host_ctxt, 2);
- DECLARE_REG(u64, nr_pages, host_ctxt, 3);
- DECLARE_REG(enum kvm_pgtable_prot, prot, host_ctxt, 4);
struct pkvm_hyp_vcpu *hyp_vcpu;
- int ret = -EINVAL;
+ int ret;
hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
if (!hyp_vcpu || pkvm_hyp_vcpu_is_protected(hyp_vcpu))
- goto out;
+ return -EINVAL;
ret = pkvm_refill_memcache(hyp_vcpu);
if (ret)
- goto out;
+ return ret;
- ret = __pkvm_host_share_guest(pfn, gfn, nr_pages, hyp_vcpu, prot);
-out:
- cpu_reg(host_ctxt, 1) = ret;
+ return __pkvm_host_share_guest(pfn, gfn, nr_pages, hyp_vcpu, prot);
}
-static void handle___pkvm_host_unshare_guest(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_unshare_guest, 3,
+ pkvm_handle_t, handle, u64, gfn, u64, nr_pages)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
- DECLARE_REG(u64, gfn, host_ctxt, 2);
- DECLARE_REG(u64, nr_pages, host_ctxt, 3);
struct pkvm_hyp_vm *hyp_vm;
- int ret = -EINVAL;
+ int ret;
hyp_vm = get_np_pkvm_hyp_vm(handle);
if (!hyp_vm)
- goto out;
+ return -EINVAL;
ret = __pkvm_host_unshare_guest(gfn, nr_pages, hyp_vm);
put_pkvm_hyp_vm(hyp_vm);
-out:
- cpu_reg(host_ctxt, 1) = ret;
+
+ return ret;
}
-static void handle___pkvm_host_relax_perms_guest(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_relax_perms_guest, 2,
+ u64, gfn, u64, prot)
{
- DECLARE_REG(u64, gfn, host_ctxt, 1);
- DECLARE_REG(enum kvm_pgtable_prot, prot, host_ctxt, 2);
struct pkvm_hyp_vcpu *hyp_vcpu;
- int ret = -EINVAL;
hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
if (!hyp_vcpu || pkvm_hyp_vcpu_is_protected(hyp_vcpu))
- goto out;
+ return -EINVAL;
- ret = __pkvm_host_relax_perms_guest(gfn, hyp_vcpu, prot);
-out:
- cpu_reg(host_ctxt, 1) = ret;
+ return __pkvm_host_relax_perms_guest(gfn, hyp_vcpu, prot);
}
-static void handle___pkvm_host_wrprotect_guest(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_wrprotect_guest, 3,
+ pkvm_handle_t, handle, u64, gfn, u64, nr_pages)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
- DECLARE_REG(u64, gfn, host_ctxt, 2);
- DECLARE_REG(u64, nr_pages, host_ctxt, 3);
struct pkvm_hyp_vm *hyp_vm;
- int ret = -EINVAL;
+ int ret;
hyp_vm = get_np_pkvm_hyp_vm(handle);
if (!hyp_vm)
- goto out;
+ return -EINVAL;
ret = __pkvm_host_wrprotect_guest(gfn, nr_pages, hyp_vm);
put_pkvm_hyp_vm(hyp_vm);
-out:
- cpu_reg(host_ctxt, 1) = ret;
+
+ return ret;
}
-static void handle___pkvm_host_test_clear_young_guest(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_test_clear_young_guest, 4,
+ pkvm_handle_t, handle, u64, gfn, u64, nr_pages, bool, mkold)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
- DECLARE_REG(u64, gfn, host_ctxt, 2);
- DECLARE_REG(u64, nr_pages, host_ctxt, 3);
- DECLARE_REG(bool, mkold, host_ctxt, 4);
struct pkvm_hyp_vm *hyp_vm;
- int ret = -EINVAL;
+ int ret;
hyp_vm = get_np_pkvm_hyp_vm(handle);
if (!hyp_vm)
- goto out;
+ return -EINVAL;
ret = __pkvm_host_test_clear_young_guest(gfn, nr_pages, mkold, hyp_vm);
put_pkvm_hyp_vm(hyp_vm);
-out:
- cpu_reg(host_ctxt, 1) = ret;
+
+ return ret;
}
-static void handle___pkvm_host_mkyoung_guest(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_mkyoung_guest, 1,
+ u64, gfn)
{
- DECLARE_REG(u64, gfn, host_ctxt, 1);
struct pkvm_hyp_vcpu *hyp_vcpu;
- int ret = -EINVAL;
hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
if (!hyp_vcpu || pkvm_hyp_vcpu_is_protected(hyp_vcpu))
- goto out;
+ return -EINVAL;
- ret = __pkvm_host_mkyoung_guest(gfn, hyp_vcpu);
-out:
- cpu_reg(host_ctxt, 1) = ret;
+ return __pkvm_host_mkyoung_guest(gfn, hyp_vcpu);
}
-static void handle___kvm_adjust_pc(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__kvm_adjust_pc, 1,
+ struct kvm_vcpu *, vcpu)
{
- DECLARE_REG(struct kvm_vcpu *, vcpu, host_ctxt, 1);
-
__kvm_adjust_pc(kern_hyp_va(vcpu));
}
-static void handle___kvm_flush_vm_context(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0_VOID(__kvm_flush_vm_context)
{
__kvm_flush_vm_context();
}
-static void handle___kvm_tlb_flush_vmid_ipa(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_ipa, 3,
+ struct kvm_s2_mmu *, mmu, phys_addr_t, ipa, int, level)
{
- DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
- DECLARE_REG(phys_addr_t, ipa, host_ctxt, 2);
- DECLARE_REG(int, level, host_ctxt, 3);
-
__kvm_tlb_flush_vmid_ipa(kern_hyp_va(mmu), ipa, level);
}
-static void handle___kvm_tlb_flush_vmid_ipa_nsh(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_ipa_nsh, 3,
+ struct kvm_s2_mmu *, mmu, phys_addr_t, ipa, int, level)
{
- DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
- DECLARE_REG(phys_addr_t, ipa, host_ctxt, 2);
- DECLARE_REG(int, level, host_ctxt, 3);
-
__kvm_tlb_flush_vmid_ipa_nsh(kern_hyp_va(mmu), ipa, level);
}
-static void
-handle___kvm_tlb_flush_vmid_range(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_range, 3,
+ struct kvm_s2_mmu *, mmu, phys_addr_t, start, unsigned long, pages)
{
- DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
- DECLARE_REG(phys_addr_t, start, host_ctxt, 2);
- DECLARE_REG(unsigned long, pages, host_ctxt, 3);
-
__kvm_tlb_flush_vmid_range(kern_hyp_va(mmu), start, pages);
}
-static void handle___kvm_tlb_flush_vmid(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid, 1,
+ struct kvm_s2_mmu *, mmu)
{
- DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
-
__kvm_tlb_flush_vmid(kern_hyp_va(mmu));
}
-static void handle___pkvm_tlb_flush_vmid(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__pkvm_tlb_flush_vmid, 1,
+ pkvm_handle_t, handle)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
struct pkvm_hyp_vm *hyp_vm = get_np_pkvm_hyp_vm(handle);
if (!hyp_vm)
@@ -451,19 +467,19 @@ static void handle___pkvm_tlb_flush_vmid(struct kvm_cpu_context *host_ctxt)
put_pkvm_hyp_vm(hyp_vm);
}
-static void handle___kvm_flush_cpu_context(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__kvm_flush_cpu_context, 1,
+ struct kvm_s2_mmu *, mmu)
{
- DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
-
__kvm_flush_cpu_context(kern_hyp_va(mmu));
}
-static void handle___kvm_timer_set_cntvoff(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__kvm_timer_set_cntvoff, 1,
+ u64, cntvoff)
{
- __kvm_timer_set_cntvoff(cpu_reg(host_ctxt, 1));
+ __kvm_timer_set_cntvoff(cntvoff);
}
-static void handle___kvm_enable_ssbs(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0_VOID(__kvm_enable_ssbs)
{
u64 tmp;
@@ -472,72 +488,61 @@ static void handle___kvm_enable_ssbs(struct kvm_cpu_context *host_ctxt)
write_sysreg_el2(tmp, SYS_SCTLR);
}
-static void handle___vgic_v3_get_gic_config(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0(u64, __vgic_v3_get_gic_config)
{
- cpu_reg(host_ctxt, 1) = __vgic_v3_get_gic_config();
+ return __vgic_v3_get_gic_config();
}
-static void handle___vgic_v3_init_lrs(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0_VOID(__vgic_v3_init_lrs)
{
__vgic_v3_init_lrs();
}
-static void handle___vgic_v3_save_aprs(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__vgic_v3_save_aprs, 1,
+ struct vgic_v3_cpu_if *, cpu_if)
{
- DECLARE_REG(struct vgic_v3_cpu_if *, cpu_if, host_ctxt, 1);
-
__vgic_v3_save_aprs(kern_hyp_va(cpu_if));
}
-static void handle___vgic_v3_restore_vmcr_aprs(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__vgic_v3_restore_vmcr_aprs, 1,
+ struct vgic_v3_cpu_if *, cpu_if)
{
- DECLARE_REG(struct vgic_v3_cpu_if *, cpu_if, host_ctxt, 1);
-
__vgic_v3_restore_vmcr_aprs(kern_hyp_va(cpu_if));
}
-static void handle___pkvm_init(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_init, 4,
+ phys_addr_t, phys, unsigned long, size,
+ unsigned long *, per_cpu_base, u32, hyp_va_bits)
{
- DECLARE_REG(phys_addr_t, phys, host_ctxt, 1);
- DECLARE_REG(unsigned long, size, host_ctxt, 2);
- DECLARE_REG(unsigned long *, per_cpu_base, host_ctxt, 3);
- DECLARE_REG(u32, hyp_va_bits, host_ctxt, 4);
-
/*
* __pkvm_init() will return only if an error occurred, otherwise it
* will tail-call in __pkvm_init_finalise() which will have to deal
* with the host context directly.
*/
- cpu_reg(host_ctxt, 1) = __pkvm_init(phys, size, per_cpu_base, hyp_va_bits);
+ return __pkvm_init(phys, size, per_cpu_base, hyp_va_bits);
}
-static void handle___pkvm_cpu_set_vector(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_cpu_set_vector, 1,
+ enum arm64_hyp_spectre_vector, slot)
{
- DECLARE_REG(enum arm64_hyp_spectre_vector, slot, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = pkvm_cpu_set_vector(slot);
+ return pkvm_cpu_set_vector(slot);
}
-static void handle___pkvm_host_share_hyp(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_share_hyp, 1,
+ u64, pfn)
{
- DECLARE_REG(u64, pfn, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = __pkvm_host_share_hyp(pfn);
+ return __pkvm_host_share_hyp(pfn);
}
-static void handle___pkvm_host_unshare_hyp(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_host_unshare_hyp, 1,
+ u64, pfn)
{
- DECLARE_REG(u64, pfn, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = __pkvm_host_unshare_hyp(pfn);
+ return __pkvm_host_unshare_hyp(pfn);
}
-static void handle___pkvm_create_private_mapping(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(unsigned long, __pkvm_create_private_mapping, 3,
+ phys_addr_t, phys, size_t, size, u64, prot)
{
- DECLARE_REG(phys_addr_t, phys, host_ctxt, 1);
- DECLARE_REG(size_t, size, host_ctxt, 2);
- DECLARE_REG(enum kvm_pgtable_prot, prot, host_ctxt, 3);
-
/*
* __pkvm_create_private_mapping() populates a pointer with the
* hypervisor start address of the allocation.
@@ -554,154 +559,125 @@ static void handle___pkvm_create_private_mapping(struct kvm_cpu_context *host_ct
if (err)
haddr = (unsigned long)ERR_PTR(err);
- cpu_reg(host_ctxt, 1) = haddr;
+ return haddr;
}
-static void handle___pkvm_prot_finalize(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0(int, __pkvm_prot_finalize)
{
- cpu_reg(host_ctxt, 1) = __pkvm_prot_finalize();
+ return __pkvm_prot_finalize();
}
-static void handle___pkvm_reserve_vm(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0(int, __pkvm_reserve_vm)
{
- cpu_reg(host_ctxt, 1) = __pkvm_reserve_vm();
+ return __pkvm_reserve_vm();
}
-static void handle___pkvm_unreserve_vm(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__pkvm_unreserve_vm, 1,
+ pkvm_handle_t, handle)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
-
__pkvm_unreserve_vm(handle);
}
-static void handle___pkvm_init_vm(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_init_vm, 3,
+ struct kvm *, host_kvm, void *, vm_hva, void *, pgd_hva)
{
- DECLARE_REG(struct kvm *, host_kvm, host_ctxt, 1);
- DECLARE_REG(void *, vm_hva, host_ctxt, 2);
- DECLARE_REG(void *, pgd_hva, host_ctxt, 3);
-
- host_kvm = kern_hyp_va(host_kvm);
- cpu_reg(host_ctxt, 1) = __pkvm_init_vm(host_kvm, vm_hva, pgd_hva);
+ return __pkvm_init_vm(kern_hyp_va(host_kvm), vm_hva, pgd_hva);
}
-static void handle___pkvm_init_vcpu(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_init_vcpu, 3,
+ pkvm_handle_t, handle, struct kvm_vcpu *, host_vcpu,
+ void *, vcpu_hva)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
- DECLARE_REG(struct kvm_vcpu *, host_vcpu, host_ctxt, 2);
- DECLARE_REG(void *, vcpu_hva, host_ctxt, 3);
-
- host_vcpu = kern_hyp_va(host_vcpu);
- cpu_reg(host_ctxt, 1) = __pkvm_init_vcpu(handle, host_vcpu, vcpu_hva);
+ return __pkvm_init_vcpu(handle, kern_hyp_va(host_vcpu), vcpu_hva);
}
-static void handle___pkvm_vcpu_in_poison_fault(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0(int, __pkvm_vcpu_in_poison_fault)
{
- int ret;
struct pkvm_hyp_vcpu *hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
- ret = hyp_vcpu ? __pkvm_vcpu_in_poison_fault(hyp_vcpu) : -EINVAL;
- cpu_reg(host_ctxt, 1) = ret;
+ return hyp_vcpu ? __pkvm_vcpu_in_poison_fault(hyp_vcpu) : -EINVAL;
}
-static void handle___pkvm_force_reclaim_guest_page(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_force_reclaim_guest_page, 1,
+ phys_addr_t, phys)
{
- DECLARE_REG(phys_addr_t, phys, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = __pkvm_host_force_reclaim_page_guest(phys);
+ return __pkvm_host_force_reclaim_page_guest(phys);
}
-static void handle___pkvm_reclaim_dying_guest_page(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_reclaim_dying_guest_page, 2,
+ pkvm_handle_t, handle, u64, gfn)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
- DECLARE_REG(u64, gfn, host_ctxt, 2);
-
- cpu_reg(host_ctxt, 1) = __pkvm_reclaim_dying_guest_page(handle, gfn);
+ return __pkvm_reclaim_dying_guest_page(handle, gfn);
}
-static void handle___pkvm_start_teardown_vm(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_start_teardown_vm, 1,
+ pkvm_handle_t, handle)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = __pkvm_start_teardown_vm(handle);
+ return __pkvm_start_teardown_vm(handle);
}
-static void handle___pkvm_finalize_teardown_vm(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __pkvm_finalize_teardown_vm, 1,
+ pkvm_handle_t, handle)
{
- DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = __pkvm_finalize_teardown_vm(handle);
+ return __pkvm_finalize_teardown_vm(handle);
}
-static void handle___tracing_load(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __tracing_load, 2,
+ void *, desc_hva, size_t, desc_size)
{
- DECLARE_REG(void *, desc_hva, host_ctxt, 1);
- DECLARE_REG(size_t, desc_size, host_ctxt, 2);
-
- cpu_reg(host_ctxt, 1) = __tracing_load(desc_hva, desc_size);
+ return __tracing_load(desc_hva, desc_size);
}
-static void handle___tracing_unload(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL0_VOID(__tracing_unload)
{
__tracing_unload();
}
-static void handle___tracing_enable(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __tracing_enable, 1,
+ bool, enable)
{
- DECLARE_REG(bool, enable, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = __tracing_enable(enable);
+ return __tracing_enable(enable);
}
-static void handle___tracing_swap_reader(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __tracing_swap_reader, 1,
+ unsigned int, cpu)
{
- DECLARE_REG(unsigned int, cpu, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = __tracing_swap_reader(cpu);
+ return __tracing_swap_reader(cpu);
}
-static void handle___tracing_update_clock(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__tracing_update_clock, 4,
+ u32, mult, u32, shift, u64, epoch_ns, u64, epoch_cyc)
{
- DECLARE_REG(u32, mult, host_ctxt, 1);
- DECLARE_REG(u32, shift, host_ctxt, 2);
- DECLARE_REG(u64, epoch_ns, host_ctxt, 3);
- DECLARE_REG(u64, epoch_cyc, host_ctxt, 4);
-
__tracing_update_clock(mult, shift, epoch_ns, epoch_cyc);
}
-static void handle___tracing_reset(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __tracing_reset, 1,
+ unsigned int, cpu)
{
- DECLARE_REG(unsigned int, cpu, host_ctxt, 1);
-
- cpu_reg(host_ctxt, 1) = __tracing_reset(cpu);
+ return __tracing_reset(cpu);
}
-static void handle___tracing_enable_event(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL(int, __tracing_enable_event, 2,
+ unsigned short, id, bool, enable)
{
- DECLARE_REG(unsigned short, id, host_ctxt, 1);
- DECLARE_REG(bool, enable, host_ctxt, 2);
-
- cpu_reg(host_ctxt, 1) = __tracing_enable_event(id, enable);
+ return __tracing_enable_event(id, enable);
}
-static void handle___tracing_write_event(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__tracing_write_event, 1,
+ u64, id)
{
- DECLARE_REG(u64, id, host_ctxt, 1);
-
trace_selftest(id);
}
-static void handle___vgic_v5_save_apr(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__vgic_v5_save_apr, 1,
+ struct vgic_v5_cpu_if *, cpu_if)
{
- DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
-
__vgic_v5_save_apr(kern_hyp_va(cpu_if));
}
-static void handle___vgic_v5_restore_vmcr_apr(struct kvm_cpu_context *host_ctxt)
+DEFINE_KVM_HOST_HCALL_VOID(__vgic_v5_restore_vmcr_apr, 1,
+ struct vgic_v5_cpu_if *, cpu_if)
{
- DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
-
__vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
}
--
2.39.5
^ permalink raw reply related
* [PATCH v1 11/11] KVM: arm64: Tag host-VA hypercall parameters __hostva
From: Fuad Tabba @ 2026-07-20 16:24 UTC (permalink / raw)
To: maz, oupton, linux-arm-kernel, kvmarm
Cc: catalin.marinas, will, rostedt, mhiramat, alexandru.elisei,
vdonnefort, joey.gouly, seiden, suzuki.poulose, yuzenghui,
qperret, ardb, linux-kernel, linux-trace-kernel, tabba,
fuad.tabba
In-Reply-To: <20260720162412.1401272-1-fuad.tabba@linux.dev>
The nVHE hypervisor takes host virtual addresses as hypercall arguments
and translates each with kern_hyp_va() before use. Nothing marks them as
host-owned, so dereferencing one untranslated at EL2 - a recurring bug
class - is invisible to the compiler.
Add a __hostva sparse address space, active only for EL2 code, and tag
the host-VA parameters in the hypercall declarations. kern_hyp_va_host()
is the only sanctioned unwrap: it translates the address, preserves the
pointee type (stripped of qualifiers, as with the percpu accessors) and
drops the tag with a __force cast, so an untranslated host VA fails
sparse. The tag flows from the shared declaration into the generated
handler and on into the donated-memory and tracing-descriptor helpers,
so a handler cannot extract a host VA without it. Host code sees plain
pointers, and the tag is checker-only: no code is generated.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_hcall.h | 43 ++++++++++++-------
arch/arm64/include/asm/kvm_mmu.h | 10 +++++
arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 6 ++-
arch/arm64/kvm/hyp/include/nvhe/trace.h | 5 ++-
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 57 +++++++++++++------------
arch/arm64/kvm/hyp/nvhe/pkvm.c | 11 ++---
arch/arm64/kvm/hyp/nvhe/trace.c | 4 +-
7 files changed, 82 insertions(+), 54 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_hcall.h b/arch/arm64/include/asm/kvm_hcall.h
index 6aa2df90a14a4..3033a6ba1462a 100644
--- a/arch/arm64/include/asm/kvm_hcall.h
+++ b/arch/arm64/include/asm/kvm_hcall.h
@@ -27,6 +27,18 @@ struct kvm_vcpu;
struct vgic_v3_cpu_if;
struct vgic_v5_cpu_if;
+/*
+ * A host VA carried by a hypercall argument. At EL2 such a pointer must not
+ * be dereferenced until it is translated with kern_hyp_va_host(); sparse
+ * flags any use that skips the translation. The tag describes the EL2 view
+ * only: the host dereferences its own VAs freely.
+ */
+#if defined(__KVM_NVHE_HYPERVISOR__) && defined(__CHECKER__)
+#define __hostva __attribute__((noderef, address_space(__hostva)))
+#else
+#define __hostva
+#endif
+
/*
* Hypercall signatures are declared as (type, name) argument pairs.
* __KVM_HCALL_MAP() applies a macro to each pair, in the mold of __MAP()
@@ -157,24 +169,24 @@ DECLARE_KVM_HOST_HCALL0(int, __pkvm_prot_finalize)
/* Hypercalls that are always available and common to [nh]VHE/pKVM. */
DECLARE_KVM_HOST_HCALL_VOID(__kvm_adjust_pc, 1,
- struct kvm_vcpu *, vcpu)
+ struct kvm_vcpu __hostva *, vcpu)
DECLARE_KVM_HOST_HCALL(int, __kvm_vcpu_run, 1,
- struct kvm_vcpu *, vcpu)
+ struct kvm_vcpu __hostva *, vcpu)
DECLARE_KVM_HOST_HCALL0_VOID(__kvm_flush_vm_context)
DECLARE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_ipa, 3,
- struct kvm_s2_mmu *, mmu, phys_addr_t, ipa, int, level)
+ struct kvm_s2_mmu __hostva *, mmu, phys_addr_t, ipa, int, level)
DECLARE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_ipa_nsh, 3,
- struct kvm_s2_mmu *, mmu, phys_addr_t, ipa, int, level)
+ struct kvm_s2_mmu __hostva *, mmu, phys_addr_t, ipa, int, level)
DECLARE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid, 1,
- struct kvm_s2_mmu *, mmu)
+ struct kvm_s2_mmu __hostva *, mmu)
DECLARE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_range, 3,
- struct kvm_s2_mmu *, mmu, phys_addr_t, start, unsigned long, pages)
+ struct kvm_s2_mmu __hostva *, mmu, phys_addr_t, start, unsigned long, pages)
DECLARE_KVM_HOST_HCALL_VOID(__kvm_flush_cpu_context, 1,
- struct kvm_s2_mmu *, mmu)
+ struct kvm_s2_mmu __hostva *, mmu)
DECLARE_KVM_HOST_HCALL_VOID(__kvm_timer_set_cntvoff, 1,
u64, cntvoff)
DECLARE_KVM_HOST_HCALL(int, __tracing_load, 2,
- void *, desc_hva, size_t, desc_size)
+ void __hostva *, desc_hva, size_t, desc_size)
DECLARE_KVM_HOST_HCALL0_VOID(__tracing_unload)
DECLARE_KVM_HOST_HCALL(int, __tracing_enable, 1,
bool, enable)
@@ -189,13 +201,13 @@ DECLARE_KVM_HOST_HCALL(int, __tracing_enable_event, 2,
DECLARE_KVM_HOST_HCALL_VOID(__tracing_write_event, 1,
u64, id)
DECLARE_KVM_HOST_HCALL_VOID(__vgic_v3_save_aprs, 1,
- struct vgic_v3_cpu_if *, cpu_if)
+ struct vgic_v3_cpu_if __hostva *, cpu_if)
DECLARE_KVM_HOST_HCALL_VOID(__vgic_v3_restore_vmcr_aprs, 1,
- struct vgic_v3_cpu_if *, cpu_if)
+ struct vgic_v3_cpu_if __hostva *, cpu_if)
DECLARE_KVM_HOST_HCALL_VOID(__vgic_v5_save_apr, 1,
- struct vgic_v5_cpu_if *, cpu_if)
+ struct vgic_v5_cpu_if __hostva *, cpu_if)
DECLARE_KVM_HOST_HCALL_VOID(__vgic_v5_restore_vmcr_apr, 1,
- struct vgic_v5_cpu_if *, cpu_if)
+ struct vgic_v5_cpu_if __hostva *, cpu_if)
/* Hypercalls that are available only when pKVM has finalised. */
DECLARE_KVM_HOST_HCALL(int, __pkvm_host_share_hyp, 1,
@@ -220,10 +232,11 @@ DECLARE_KVM_HOST_HCALL0(int, __pkvm_reserve_vm)
DECLARE_KVM_HOST_HCALL_VOID(__pkvm_unreserve_vm, 1,
pkvm_handle_t, handle)
DECLARE_KVM_HOST_HCALL(int, __pkvm_init_vm, 3,
- struct kvm *, host_kvm, void *, vm_hva, void *, pgd_hva)
+ struct kvm __hostva *, host_kvm, void __hostva *, vm_hva,
+ void __hostva *, pgd_hva)
DECLARE_KVM_HOST_HCALL(int, __pkvm_init_vcpu, 3,
- pkvm_handle_t, handle, struct kvm_vcpu *, host_vcpu,
- void *, vcpu_hva)
+ pkvm_handle_t, handle, struct kvm_vcpu __hostva *, host_vcpu,
+ void __hostva *, vcpu_hva)
DECLARE_KVM_HOST_HCALL0(int, __pkvm_vcpu_in_poison_fault)
DECLARE_KVM_HOST_HCALL(int, __pkvm_force_reclaim_guest_page, 1,
phys_addr_t, phys)
diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h
index 6eae7e7e2a684..55fac40377c03 100644
--- a/arch/arm64/include/asm/kvm_mmu.h
+++ b/arch/arm64/include/asm/kvm_mmu.h
@@ -7,6 +7,8 @@
#ifndef __ARM64_KVM_MMU_H__
#define __ARM64_KVM_MMU_H__
+#include <linux/compiler.h>
+
#include <asm/page.h>
#include <asm/memory.h>
#include <asm/mmu.h>
@@ -140,6 +142,14 @@ static __always_inline unsigned long __kern_hyp_va(unsigned long v)
#define kern_hyp_va(v) ((typeof(v))(__kern_hyp_va((unsigned long)(v))))
+/*
+ * Translate a __hostva-tagged host VA, dropping the tag: the only sanctioned
+ * unwrap. Translation only, no ownership or bounds validation; the result
+ * carries the pointee type stripped of the tag and of any cv-qualifiers.
+ */
+#define kern_hyp_va_host(v) \
+ ((TYPEOF_UNQUAL(*(v)) *)__kern_hyp_va((unsigned long)(__force void *)(v)))
+
extern u32 __hyp_va_bits;
/*
diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
index 2643a1a819668..dcbe8b8913270 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
@@ -7,6 +7,7 @@
#ifndef __ARM64_KVM_NVHE_PKVM_H__
#define __ARM64_KVM_NVHE_PKVM_H__
+#include <asm/kvm_hcall.h>
#include <asm/kvm_pkvm.h>
#include <nvhe/gfp.h>
@@ -69,9 +70,10 @@ void pkvm_hyp_vm_table_init(void *tbl);
int __pkvm_reserve_vm(void);
void __pkvm_unreserve_vm(pkvm_handle_t handle);
-int __pkvm_init_vm(struct kvm *host_kvm, void *vm_hva, void *pgd_hva);
+int __pkvm_init_vm(struct kvm *host_kvm, void __hostva *vm_hva,
+ void __hostva *pgd_hva);
int __pkvm_init_vcpu(pkvm_handle_t handle, struct kvm_vcpu *host_vcpu,
- void *vcpu_hva);
+ void __hostva *vcpu_hva);
int __pkvm_reclaim_dying_guest_page(pkvm_handle_t handle, u64 gfn);
int __pkvm_start_teardown_vm(pkvm_handle_t handle);
diff --git a/arch/arm64/kvm/hyp/include/nvhe/trace.h b/arch/arm64/kvm/hyp/include/nvhe/trace.h
index 4aa36fd76b9e2..58e0ad3329034 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/trace.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/trace.h
@@ -4,6 +4,7 @@
#include <linux/trace_remote_event.h>
+#include <asm/kvm_hcall.h>
#include <asm/kvm_hyptrace.h>
static inline pid_t __tracing_get_vcpu_pid(struct kvm_cpu_context *host_ctxt)
@@ -46,7 +47,7 @@ static inline pid_t __tracing_get_vcpu_pid(struct kvm_cpu_context *host_ctxt)
void *tracing_reserve_entry(unsigned long length);
void tracing_commit_entry(void);
-int __tracing_load(void *desc_va, size_t desc_size);
+int __tracing_load(void __hostva *desc_va, size_t desc_size);
void __tracing_unload(void);
int __tracing_enable(bool enable);
int __tracing_swap_reader(unsigned int cpu);
@@ -59,7 +60,7 @@ static inline void tracing_commit_entry(void) { }
#define HYP_EVENT(__name, __proto, __struct, __assign, __printk) \
static inline void trace_##__name(__proto) {}
-static inline int __tracing_load(void *desc_va, size_t desc_size) { return -ENODEV; }
+static inline int __tracing_load(void __hostva *desc_va, size_t desc_size) { return -ENODEV; }
static inline void __tracing_unload(void) { }
static inline int __tracing_enable(bool enable) { return -ENODEV; }
static inline int __tracing_swap_reader(unsigned int cpu) { return -ENODEV; }
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index f2501249f4391..679b6df9c998d 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -269,7 +269,7 @@ DEFINE_KVM_HOST_HCALL0_VOID(__pkvm_vcpu_put)
}
DEFINE_KVM_HOST_HCALL(int, __kvm_vcpu_run, 1,
- struct kvm_vcpu *, host_vcpu)
+ struct kvm_vcpu __hostva *, host_vcpu)
{
int ret;
@@ -294,7 +294,7 @@ DEFINE_KVM_HOST_HCALL(int, __kvm_vcpu_run, 1,
sync_hyp_vcpu(hyp_vcpu);
} else {
- struct kvm_vcpu *vcpu = kern_hyp_va(host_vcpu);
+ struct kvm_vcpu *vcpu = kern_hyp_va_host(host_vcpu);
/* The host is fully trusted, run its vCPU directly. */
fpsimd_lazy_switch_to_guest(vcpu);
@@ -421,9 +421,9 @@ DEFINE_KVM_HOST_HCALL(int, __pkvm_host_mkyoung_guest, 1,
}
DEFINE_KVM_HOST_HCALL_VOID(__kvm_adjust_pc, 1,
- struct kvm_vcpu *, vcpu)
+ struct kvm_vcpu __hostva *, vcpu)
{
- __kvm_adjust_pc(kern_hyp_va(vcpu));
+ __kvm_adjust_pc(kern_hyp_va_host(vcpu));
}
DEFINE_KVM_HOST_HCALL0_VOID(__kvm_flush_vm_context)
@@ -432,27 +432,27 @@ DEFINE_KVM_HOST_HCALL0_VOID(__kvm_flush_vm_context)
}
DEFINE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_ipa, 3,
- struct kvm_s2_mmu *, mmu, phys_addr_t, ipa, int, level)
+ struct kvm_s2_mmu __hostva *, mmu, phys_addr_t, ipa, int, level)
{
- __kvm_tlb_flush_vmid_ipa(kern_hyp_va(mmu), ipa, level);
+ __kvm_tlb_flush_vmid_ipa(kern_hyp_va_host(mmu), ipa, level);
}
DEFINE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_ipa_nsh, 3,
- struct kvm_s2_mmu *, mmu, phys_addr_t, ipa, int, level)
+ struct kvm_s2_mmu __hostva *, mmu, phys_addr_t, ipa, int, level)
{
- __kvm_tlb_flush_vmid_ipa_nsh(kern_hyp_va(mmu), ipa, level);
+ __kvm_tlb_flush_vmid_ipa_nsh(kern_hyp_va_host(mmu), ipa, level);
}
DEFINE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid_range, 3,
- struct kvm_s2_mmu *, mmu, phys_addr_t, start, unsigned long, pages)
+ struct kvm_s2_mmu __hostva *, mmu, phys_addr_t, start, unsigned long, pages)
{
- __kvm_tlb_flush_vmid_range(kern_hyp_va(mmu), start, pages);
+ __kvm_tlb_flush_vmid_range(kern_hyp_va_host(mmu), start, pages);
}
DEFINE_KVM_HOST_HCALL_VOID(__kvm_tlb_flush_vmid, 1,
- struct kvm_s2_mmu *, mmu)
+ struct kvm_s2_mmu __hostva *, mmu)
{
- __kvm_tlb_flush_vmid(kern_hyp_va(mmu));
+ __kvm_tlb_flush_vmid(kern_hyp_va_host(mmu));
}
DEFINE_KVM_HOST_HCALL_VOID(__pkvm_tlb_flush_vmid, 1,
@@ -468,9 +468,9 @@ DEFINE_KVM_HOST_HCALL_VOID(__pkvm_tlb_flush_vmid, 1,
}
DEFINE_KVM_HOST_HCALL_VOID(__kvm_flush_cpu_context, 1,
- struct kvm_s2_mmu *, mmu)
+ struct kvm_s2_mmu __hostva *, mmu)
{
- __kvm_flush_cpu_context(kern_hyp_va(mmu));
+ __kvm_flush_cpu_context(kern_hyp_va_host(mmu));
}
DEFINE_KVM_HOST_HCALL_VOID(__kvm_timer_set_cntvoff, 1,
@@ -499,15 +499,15 @@ DEFINE_KVM_HOST_HCALL0_VOID(__vgic_v3_init_lrs)
}
DEFINE_KVM_HOST_HCALL_VOID(__vgic_v3_save_aprs, 1,
- struct vgic_v3_cpu_if *, cpu_if)
+ struct vgic_v3_cpu_if __hostva *, cpu_if)
{
- __vgic_v3_save_aprs(kern_hyp_va(cpu_if));
+ __vgic_v3_save_aprs(kern_hyp_va_host(cpu_if));
}
DEFINE_KVM_HOST_HCALL_VOID(__vgic_v3_restore_vmcr_aprs, 1,
- struct vgic_v3_cpu_if *, cpu_if)
+ struct vgic_v3_cpu_if __hostva *, cpu_if)
{
- __vgic_v3_restore_vmcr_aprs(kern_hyp_va(cpu_if));
+ __vgic_v3_restore_vmcr_aprs(kern_hyp_va_host(cpu_if));
}
DEFINE_KVM_HOST_HCALL(int, __pkvm_init, 4,
@@ -579,16 +579,17 @@ DEFINE_KVM_HOST_HCALL_VOID(__pkvm_unreserve_vm, 1,
}
DEFINE_KVM_HOST_HCALL(int, __pkvm_init_vm, 3,
- struct kvm *, host_kvm, void *, vm_hva, void *, pgd_hva)
+ struct kvm __hostva *, host_kvm, void __hostva *, vm_hva,
+ void __hostva *, pgd_hva)
{
- return __pkvm_init_vm(kern_hyp_va(host_kvm), vm_hva, pgd_hva);
+ return __pkvm_init_vm(kern_hyp_va_host(host_kvm), vm_hva, pgd_hva);
}
DEFINE_KVM_HOST_HCALL(int, __pkvm_init_vcpu, 3,
- pkvm_handle_t, handle, struct kvm_vcpu *, host_vcpu,
- void *, vcpu_hva)
+ pkvm_handle_t, handle, struct kvm_vcpu __hostva *, host_vcpu,
+ void __hostva *, vcpu_hva)
{
- return __pkvm_init_vcpu(handle, kern_hyp_va(host_vcpu), vcpu_hva);
+ return __pkvm_init_vcpu(handle, kern_hyp_va_host(host_vcpu), vcpu_hva);
}
DEFINE_KVM_HOST_HCALL0(int, __pkvm_vcpu_in_poison_fault)
@@ -623,7 +624,7 @@ DEFINE_KVM_HOST_HCALL(int, __pkvm_finalize_teardown_vm, 1,
}
DEFINE_KVM_HOST_HCALL(int, __tracing_load, 2,
- void *, desc_hva, size_t, desc_size)
+ void __hostva *, desc_hva, size_t, desc_size)
{
return __tracing_load(desc_hva, desc_size);
}
@@ -670,15 +671,15 @@ DEFINE_KVM_HOST_HCALL_VOID(__tracing_write_event, 1,
}
DEFINE_KVM_HOST_HCALL_VOID(__vgic_v5_save_apr, 1,
- struct vgic_v5_cpu_if *, cpu_if)
+ struct vgic_v5_cpu_if __hostva *, cpu_if)
{
- __vgic_v5_save_apr(kern_hyp_va(cpu_if));
+ __vgic_v5_save_apr(kern_hyp_va_host(cpu_if));
}
DEFINE_KVM_HOST_HCALL_VOID(__vgic_v5_restore_vmcr_apr, 1,
- struct vgic_v5_cpu_if *, cpu_if)
+ struct vgic_v5_cpu_if __hostva *, cpu_if)
{
- __vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
+ __vgic_v5_restore_vmcr_apr(kern_hyp_va_host(cpu_if));
}
typedef void (*hcall_t)(struct kvm_cpu_context *);
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 205c52535c887..bfc9f07f336a1 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -644,9 +644,9 @@ static size_t pkvm_get_hyp_vm_size(unsigned int nr_vcpus)
size_mul(sizeof(struct pkvm_hyp_vcpu *), nr_vcpus));
}
-static void *map_donated_memory_noclear(void *host_va, size_t size)
+static void *map_donated_memory_noclear(void __hostva *host_va, size_t size)
{
- void *va = kern_hyp_va(host_va);
+ void *va = kern_hyp_va_host(host_va);
if (!PAGE_ALIGNED(va))
return NULL;
@@ -658,7 +658,7 @@ static void *map_donated_memory_noclear(void *host_va, size_t size)
return va;
}
-static void *map_donated_memory(void *host_va, size_t size)
+static void *map_donated_memory(void __hostva *host_va, size_t size)
{
void *va = map_donated_memory_noclear(host_va, size);
@@ -805,7 +805,8 @@ void teardown_selftest_vm(void)
*
* Return 0 success, negative error code on failure.
*/
-int __pkvm_init_vm(struct kvm *host_kvm, void *vm_hva, void *pgd_hva)
+int __pkvm_init_vm(struct kvm *host_kvm, void __hostva *vm_hva,
+ void __hostva *pgd_hva)
{
struct pkvm_hyp_vm *hyp_vm = NULL;
size_t vm_size, pgd_size;
@@ -896,7 +897,7 @@ static int register_hyp_vcpu(struct pkvm_hyp_vm *hyp_vm,
}
int __pkvm_init_vcpu(pkvm_handle_t handle, struct kvm_vcpu *host_vcpu,
- void *vcpu_hva)
+ void __hostva *vcpu_hva)
{
struct pkvm_hyp_vcpu *hyp_vcpu;
struct pkvm_hyp_vm *hyp_vm;
diff --git a/arch/arm64/kvm/hyp/nvhe/trace.c b/arch/arm64/kvm/hyp/nvhe/trace.c
index 97203ddd3cf45..5d0fcdce9507d 100644
--- a/arch/arm64/kvm/hyp/nvhe/trace.c
+++ b/arch/arm64/kvm/hyp/nvhe/trace.c
@@ -206,9 +206,9 @@ static bool hyp_trace_desc_is_valid(struct hyp_trace_desc *desc, size_t desc_siz
return true;
}
-int __tracing_load(void *desc_hva, size_t desc_size)
+int __tracing_load(void __hostva *desc_hva, size_t desc_size)
{
- struct hyp_trace_desc *desc = kern_hyp_va(desc_hva);
+ struct hyp_trace_desc *desc = kern_hyp_va_host(desc_hva);
int ret;
ret = __admit_host_mem(desc, desc_size);
--
2.39.5
^ permalink raw reply related
* Re: [PATCH v13 3/3] media: nxp: Add i.MX95 CSI pixel formatter v4l2 driver
From: Laurent Pinchart @ 2026-07-20 16:25 UTC (permalink / raw)
To: guoniu.zhou
Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Frank Li, Loic Poulain, Bryan O'Donoghue,
Abel Vesa, Peng Fan, Michael Turquette, Stephen Boyd, imx,
linux-media, devicetree, linux-arm-kernel, linux-kernel,
linux-clk, Guoniu Zhou
In-Reply-To: <20260720-csi_formatter-v13-3-4dc9a80e4cfd@oss.nxp.com>
Hi Guoniu,
On Mon, Jul 20, 2026 at 05:59:48PM +0800, guoniu.zhou@oss.nxp.com wrote:
> From: Guoniu Zhou <guoniu.zhou@nxp.com>
>
> The CSI pixel formatter is a module found on i.MX95 used to reformat
> packet info, pixel and non-pixel data from CSI-2 host controller to
> match Pixel Link(PL) definition.
>
> Add data formatting support.
>
> Signed-off-by: Guoniu Zhou <guoniu.zhou@nxp.com>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> ---
> Changes in v13:
> - Replace pr_warn_once() with dev_warn() in csi_formatter_get_index_by_dt()
> to provide device context and warn on every occurrence (Loic)
> - Add WARN_ON() check for csi_formatter_find_format() return value in
> start/stop_stream functions to catch unexpected NULL (Loic)
> - Use regmap_set_bits()/regmap_clear_bits() instead of read-modify-write
> pattern and remove unused csi_formatter_read/write helper functions (Loic)
> - Add Reviewed-by tag from Frank Li
>
> Changes in v12:
> - Fix stream ID handling: iterate routing table instead of assuming
> stream ID equals loop index (0-7)
> - Remove stream_to_vc[] array: derive VC from routing table and frame
> descriptor on each start/stop operation
> - Remove V4L2_SUBDEV_FL_HAS_EVENTS flag since driver does not generate events
> - Support stream IDs 0-63 by using BIT_ULL() for stream masks
> - Add get_frame_desc call in stop_stream with proper error handling
> - Add csi_formatter_read() helper function for register reads
> - Use read-modify-write for CSI_VC_PIXEL_DATA_TYPE register to support
> multiplexed streams sharing the same virtual channel
> - Use route->sink_pad instead of hardcoded CSI_FORMATTER_PAD_SINK
> - Write back coerced format in set_fmt before propagating to source stream
> - Drop Frank's Reviewed-by tag due to significant changes, requesting re-review
>
> Changes in v10:
> - Use u8 for vc in csi_formatter_get_vc() and drop vc < 0 check
> - Add MFD_SYSCON dependency to Kconfig
> - Fix stream/VC mapping potential mismatch in start/stop_stream functions
>
> Changes in v8:
> - Remove fmt field and look up format from subdev state instead
> - Unify function and structure naming to use csi_formatter_ prefix
> - Remove misleading alignment comment from set_fmt function
> - Optimize get_frame_desc to call once per start_stream
> - Replace V4L2_FRAME_DESC_ENTRY_MAX with CSI_FORMATTER_VC_NUM in loops
> - Remove redundant debug message in enable_streams
> - Use MEDIA_PAD_FL_MUST_CONNECT flag instead of manual link check
> - Fix typo: Formater -> Formatter in Kconfig help text
> - Improve grammar in data type index mapping comment
>
> Changes in v7:
> - Update references from imx9 to imx95 for consistency with dt-bindings
> - Enable PM runtime before async registration
>
> Changes in v6:
> - Remove unused header includes
> - Unify macro naming: VCx/VCX -> VC and parameter x -> vc
> - Remove unused format field from csi_formatter struct
> - Use compact initialization for formats array
> - Make find_csi_format() return NULL instead of default format
> - Use unsigned int for array index in find_csi_format()
> - Add err_ prefix to error handling labels
> - Add v4l2_subdev_cleanup() and reorder cleanup sequence
> - Update enable_streams debug output format
> - Rename VC_MAX to VC_NUM and fix boundary check
> - Update CSI formatter Kconfig description
> - Use v4l2_subdev_get_frame_desc_passthrough() helper
> - Fix error paths in async registration and probe
> - Add mutex to protect enabled_streams
> - Switch to devm_pm_runtime_enable()
> - Remove redundant num_routes check in set_routing
> - Optimize get_index_by_dt() and add warning for unsupported type
> - csi_formatter_start/stop_stream: Process all streams in mask
> ---
> MAINTAINERS | 8 +
> drivers/media/platform/nxp/Kconfig | 15 +
> drivers/media/platform/nxp/Makefile | 1 +
> drivers/media/platform/nxp/imx95-csi-formatter.c | 808 +++++++++++++++++++++++
> 4 files changed, 832 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index efbf808063e5..05009228b162 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -19275,6 +19275,14 @@ S: Maintained
> F: Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml
> F: drivers/media/platform/nxp/imx-jpeg
>
> +NXP i.MX 95 CSI PIXEL FORMATTER V4L2 DRIVER
> +M: Guoniu Zhou <guoniu.zhou@nxp.com>
> +L: imx@lists.linux.dev
> +L: linux-media@vger.kernel.org
> +S: Maintained
> +F: Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml
> +F: drivers/media/platform/nxp/imx95-csi-formatter.c
> +
> NXP i.MX CLOCK DRIVERS
> M: Abel Vesa <abelvesa@kernel.org>
> R: Peng Fan <peng.fan@nxp.com>
> diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig
> index 40e3436669e2..8f49908b0022 100644
> --- a/drivers/media/platform/nxp/Kconfig
> +++ b/drivers/media/platform/nxp/Kconfig
> @@ -28,6 +28,21 @@ config VIDEO_IMX8MQ_MIPI_CSI2
> Video4Linux2 driver for the MIPI CSI-2 receiver found on the i.MX8MQ
> SoC.
>
> +config VIDEO_IMX95_CSI_FORMATTER
> + tristate "NXP i.MX95 CSI Pixel Formatter driver"
> + depends on ARCH_MXC || COMPILE_TEST
> + depends on MFD_SYSCON
Shouldn't this be
select MFD_SYSCON
? There are 40 occurences of "depends on" and 167 of "select".
> + depends on VIDEO_DEV
> + select MEDIA_CONTROLLER
> + select V4L2_FWNODE
> + select VIDEO_V4L2_SUBDEV_API
> + help
> + This driver provides support for the CSI Pixel Formatter found on
> + i.MX95 series SoCs. This module unpacks the pixels received from the
> + CSI-2 interface and reformats them to meet pixel link requirements.
> +
> + Say Y here to enable CSI Pixel Formatter module for i.MX95 SoC.
> +
> config VIDEO_IMX_MIPI_CSIS
> tristate "NXP MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models"
> depends on ARCH_MXC || COMPILE_TEST
> diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile
> index 4d90eb713652..6410115d870e 100644
> --- a/drivers/media/platform/nxp/Makefile
> +++ b/drivers/media/platform/nxp/Makefile
> @@ -6,6 +6,7 @@ obj-y += imx8-isi/
>
> obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
> obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o
> +obj-$(CONFIG_VIDEO_IMX95_CSI_FORMATTER) += imx95-csi-formatter.o
> obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o
> obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o
> obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o
> diff --git a/drivers/media/platform/nxp/imx95-csi-formatter.c b/drivers/media/platform/nxp/imx95-csi-formatter.c
> new file mode 100644
> index 000000000000..b0e8e753e94a
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx95-csi-formatter.c
> @@ -0,0 +1,808 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2025 NXP
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
You can drop those two headers.
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* CSI Pixel Formatter registers map */
> +
> +#define CSI_VC_INTERLACED_LINE_CNT(vc) (0x00 + (vc) * 0x04)
> +#define INTERLACED_ODD_LINE_CNT_SET(x) FIELD_PREP(GENMASK(13, 0), (x))
> +#define INTERLACED_EVEN_LINE_CNT_SET(x) FIELD_PREP(GENMASK(29, 16), (x))
> +
> +#define CSI_VC_INTERLACED_CTRL 0x20
> +
> +#define CSI_VC_INTERLACED_ERR 0x24
> +#define CSI_VC_ERR_MASK GENMASK(7, 0)
> +#define CSI_VC_ERR(vc) BIT((vc))
> +
> +#define CSI_VC_YUV420_FIRST_LINE_EVEN 0x28
> +#define YUV420_FIRST_LINE_EVEN(vc) BIT((vc))
> +
> +#define CSI_RAW32_CTRL 0x30
> +#define CSI_VC_RAW32_MODE(vc) BIT((vc))
> +#define CSI_VC_RAW32_SWAP_MODE(vc) BIT((vc) + 8)
> +
> +#define CSI_STREAM_FENCING_CTRL 0x34
> +#define CSI_VC_STREAM_FENCING(vc) BIT((vc))
> +#define CSI_VC_STREAM_FENCING_RST(vc) BIT((vc) + 8)
> +
> +#define CSI_STREAM_FENCING_STS 0x38
> +#define CSI_STREAM_FENCING_STS_MASK GENMASK(7, 0)
> +
> +#define CSI_VC_NON_PIXEL_DATA_TYPE(vc) (0x40 + (vc) * 0x04)
> +
> +#define CSI_VC_PIXEL_DATA_CTRL(vc) (0x60 + (vc) * 0x04)
> +#define NEW_VC(vc) FIELD_PREP(GENMASK(3, 1), vc)
> +#define REROUTE_VC_ENABLE BIT(0)
> +
> +#define CSI_VC_ROUTE_PIXEL_DATA_TYPE(vc) (0x80 + (vc) * 0x04)
> +
> +#define CSI_VC_NON_PIXEL_DATA_CTRL(vc) (0xa0 + (vc) * 0x04)
> +
> +#define CSI_VC_PIXEL_DATA_TYPE(vc) (0xc0 + (vc) * 0x04)
> +
> +#define CSI_VC_PIXEL_DATA_TYPE_ERR(vc) (0xe0 + (vc) * 0x04)
> +
> +#define CSI_FORMATTER_PAD_SINK 0
> +#define CSI_FORMATTER_PAD_SOURCE 1
> +#define CSI_FORMATTER_PAD_NUM 2
> +
> +#define CSI_FORMATTER_VC_NUM 8 /* Number of virtual channels */
> +
> +struct csi_formatter_pix_format {
> + u32 code;
> + u32 data_type;
> +};
> +
> +struct csi_formatter {
> + struct device *dev;
> + struct regmap *regs;
> + struct clk *clk;
> +
> + struct v4l2_subdev sd;
> + struct v4l2_subdev *csi_sd;
I would have called this remote_sd to match remote_pad. Up to you. I
would also move the field just before remote_pad to group them.
> + struct v4l2_async_notifier notifier;
> + struct media_pad pads[CSI_FORMATTER_PAD_NUM];
> +
> + u32 remote_pad;
> + u32 reg_offset;
reg_offset is a generic resource, I'd move it just after clk.
> +
> + /* Protects enabled_streams */
The lock and the enabled_streams fields are only used in the
.enable_streams() and .disable_streams(). Both functions are called with
the active state lock taken. You can drop this lock.
> + struct mutex lock;
> + u64 enabled_streams;
> +};
> +
> +struct csi_formatter_dt_index {
> + u8 dtype;
> + u8 index;
> +};
> +
> +/*
> + * The index corresponds to the bit index in the register that enables
> + * the data type of pixel data transported by the Formatter.
> + */
> +static const struct csi_formatter_dt_index formatter_dt_to_index_map[] = {
Let's use the csi_formatter_* prefix consistently everywhere.
> + { .dtype = MIPI_CSI2_DT_YUV420_8B, .index = 0 },
> + { .dtype = MIPI_CSI2_DT_YUV420_8B_LEGACY, .index = 2 },
> + { .dtype = MIPI_CSI2_DT_YUV422_8B, .index = 6 },
> + { .dtype = MIPI_CSI2_DT_RGB444, .index = 8 },
> + { .dtype = MIPI_CSI2_DT_RGB555, .index = 9 },
> + { .dtype = MIPI_CSI2_DT_RGB565, .index = 10 },
> + { .dtype = MIPI_CSI2_DT_RGB666, .index = 11 },
> + { .dtype = MIPI_CSI2_DT_RGB888, .index = 12 },
> + { .dtype = MIPI_CSI2_DT_RAW6, .index = 16 },
> + { .dtype = MIPI_CSI2_DT_RAW7, .index = 17 },
> + { .dtype = MIPI_CSI2_DT_RAW8, .index = 18 },
> + { .dtype = MIPI_CSI2_DT_RAW10, .index = 19 },
> + { .dtype = MIPI_CSI2_DT_RAW12, .index = 20 },
> + { .dtype = MIPI_CSI2_DT_RAW14, .index = 21 },
> + { .dtype = MIPI_CSI2_DT_RAW16, .index = 22 },
> +};
> +
> +static const struct csi_formatter_pix_format formats[] = {
And especially here, the name "formats" is very generic.
> + /* YUV formats */
> + { MEDIA_BUS_FMT_UYVY8_1X16, MIPI_CSI2_DT_YUV422_8B },
> + /* RGB formats */
> + { MEDIA_BUS_FMT_RGB565_1X16, MIPI_CSI2_DT_RGB565 },
> + { MEDIA_BUS_FMT_RGB888_1X24, MIPI_CSI2_DT_RGB888 },
> + /* RAW (Bayer and greyscale) formats */
> + { MEDIA_BUS_FMT_SBGGR8_1X8, MIPI_CSI2_DT_RAW8 },
> + { MEDIA_BUS_FMT_SGBRG8_1X8, MIPI_CSI2_DT_RAW8 },
> + { MEDIA_BUS_FMT_SGRBG8_1X8, MIPI_CSI2_DT_RAW8 },
> + { MEDIA_BUS_FMT_SRGGB8_1X8, MIPI_CSI2_DT_RAW8 },
> + { MEDIA_BUS_FMT_Y8_1X8, MIPI_CSI2_DT_RAW8 },
> + { MEDIA_BUS_FMT_SBGGR10_1X10, MIPI_CSI2_DT_RAW10 },
> + { MEDIA_BUS_FMT_SGBRG10_1X10, MIPI_CSI2_DT_RAW10 },
> + { MEDIA_BUS_FMT_SGRBG10_1X10, MIPI_CSI2_DT_RAW10 },
> + { MEDIA_BUS_FMT_SRGGB10_1X10, MIPI_CSI2_DT_RAW10 },
> + { MEDIA_BUS_FMT_Y10_1X10, MIPI_CSI2_DT_RAW10 },
> + { MEDIA_BUS_FMT_SBGGR12_1X12, MIPI_CSI2_DT_RAW12 },
> + { MEDIA_BUS_FMT_SGBRG12_1X12, MIPI_CSI2_DT_RAW12 },
> + { MEDIA_BUS_FMT_SGRBG12_1X12, MIPI_CSI2_DT_RAW12 },
> + { MEDIA_BUS_FMT_SRGGB12_1X12, MIPI_CSI2_DT_RAW12 },
> + { MEDIA_BUS_FMT_Y12_1X12, MIPI_CSI2_DT_RAW12 },
> + { MEDIA_BUS_FMT_SBGGR14_1X14, MIPI_CSI2_DT_RAW14 },
> + { MEDIA_BUS_FMT_SGBRG14_1X14, MIPI_CSI2_DT_RAW14 },
> + { MEDIA_BUS_FMT_SGRBG14_1X14, MIPI_CSI2_DT_RAW14 },
> + { MEDIA_BUS_FMT_SRGGB14_1X14, MIPI_CSI2_DT_RAW14 },
> + { MEDIA_BUS_FMT_SBGGR16_1X16, MIPI_CSI2_DT_RAW16 },
> + { MEDIA_BUS_FMT_SGBRG16_1X16, MIPI_CSI2_DT_RAW16 },
> + { MEDIA_BUS_FMT_SGRBG16_1X16, MIPI_CSI2_DT_RAW16 },
> + { MEDIA_BUS_FMT_SRGGB16_1X16, MIPI_CSI2_DT_RAW16 },
> +};
> +
> +static const struct v4l2_mbus_framefmt formatter_default_fmt = {
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .width = 1920U,
> + .height = 1080U,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SMPTE170M,
> + .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
> + .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
> + .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +};
> +
> +static const struct csi_formatter_pix_format *csi_formatter_find_format(u32 code)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(formats); i++)
for (i = 0; i < ARRAY_SIZE(formats); i++) {
or possibly even
for (unsigned int i = 0; i < ARRAY_SIZE(formats); i++) {
The local i variables can also be declared in the loop in
csi_formatter_get_index_by_dt() and csi_formatter_get_vc().
> + if (code == formats[i].code)
> + return &formats[i];
}
> +
> + return NULL;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 subdev operations
> + */
> +
> +static inline struct csi_formatter *sd_to_formatter(struct v4l2_subdev *sdev)
> +{
> + return container_of(sdev, struct csi_formatter, sd);
> +}
> +
> +static int __csi_formatter_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + int ret;
> +
> + ret = v4l2_subdev_routing_validate(sd, routing,
> + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> + if (ret)
> + return ret;
> +
> + return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
> + &formatter_default_fmt);
> +}
> +
> +static int csi_formatter_subdev_init_state(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state)
> +{
> + struct v4l2_subdev_route routes[] = {
> + {
> + .sink_pad = CSI_FORMATTER_PAD_SINK,
> + .sink_stream = 0,
> + .source_pad = CSI_FORMATTER_PAD_SOURCE,
> + .source_stream = 0,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + },
> + };
> +
> + struct v4l2_subdev_krouting routing = {
> + .num_routes = ARRAY_SIZE(routes),
> + .routes = routes,
> + };
> +
> + return __csi_formatter_subdev_set_routing(sd, sd_state, &routing);
> +}
> +
> +static int csi_formatter_subdev_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->pad == CSI_FORMATTER_PAD_SOURCE) {
> + struct v4l2_mbus_framefmt *fmt;
> +
> + if (code->index > 0)
> + return -EINVAL;
> +
> + fmt = v4l2_subdev_state_get_format(sd_state, code->pad,
> + code->stream);
> + code->code = fmt->code;
> + return 0;
> + }
> +
> + if (code->index >= ARRAY_SIZE(formats))
> + return -EINVAL;
> +
> + code->code = formats[code->index].code;
> +
> + return 0;
> +}
> +
> +static int csi_formatter_subdev_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *sdformat)
> +{
> + struct csi_formatter_pix_format const *format;
> + struct v4l2_mbus_framefmt *fmt;
> +
> + if (sdformat->pad == CSI_FORMATTER_PAD_SOURCE)
> + return v4l2_subdev_get_fmt(sd, sd_state, sdformat);
> +
> + format = csi_formatter_find_format(sdformat->format.code);
> + if (!format)
> + format = &formats[0];
You can write
if (!csi_formatter_find_format(sdformat->format.code))
sdformat->format.code = formats[0].code;
... (*)
> +
> + v4l_bound_align_image(&sdformat->format.width, 1, 0xffff, 2,
> + &sdformat->format.height, 1, 0xffff, 0, 0);
Does the pixel formatter support interlaced formats ? If not I would add
sdformat->format.field = V4L2_FIELD_NONE;
> +
> + fmt = v4l2_subdev_state_get_format(sd_state, sdformat->pad,
> + sdformat->stream);
> + *fmt = sdformat->format;
> +
> + /* Set default code if user set an invalid value */
> + fmt->code = format->code;
> + sdformat->format = *fmt;
(*) ... and drop this, as well as the local format variable. The
sdformat parameter could then be renamed to just format.
> +
> + /* Propagate the format from sink stream to source stream */
> + fmt = v4l2_subdev_state_get_opposite_stream_format(sd_state, sdformat->pad,
> + sdformat->stream);
> + if (!fmt)
> + return -EINVAL;
Can this happen ? If so I'd return -EPIPE, otherwise I would drop the
check.
> +
> + *fmt = sdformat->format;
> +
> + return 0;
> +}
> +
> +static int csi_formatter_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> + media_entity_is_streaming(&sd->entity))
> + return -EBUSY;
csi_formatter_subdev_set_fmt() needs the same check.
> +
> + return __csi_formatter_subdev_set_routing(sd, state, routing);
> +}
> +
> +static u8 csi_formatter_get_index_by_dt(struct csi_formatter *formatter,
> + u8 data_type)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(formatter_dt_to_index_map); ++i) {
> + const struct csi_formatter_dt_index *entry =
> + &formatter_dt_to_index_map[i];
> +
> + if (data_type == entry->dtype)
> + return entry->index;
> + }
> +
> + dev_warn(formatter->dev, "Unsupported data type 0x%x, using default\n",
> + data_type);
As this would be a driver bug (caused by a data type used in formats and
not listed in formatter_dt_to_index_map), I would even use a WARN_ON().
> +
> + return formatter_dt_to_index_map[0].index;
> +}
> +
> +static int csi_formatter_get_vc(struct csi_formatter *formatter,
> + struct v4l2_mbus_frame_desc *fd,
> + unsigned int stream)
> +{
> + struct v4l2_mbus_frame_desc_entry *entry = NULL;
> + unsigned int i;
> + u8 vc;
> +
> + for (i = 0; i < fd->num_entries; ++i) {
> + if (fd->entry[i].stream == stream) {
> + entry = &fd->entry[i];
> + break;
> + }
> + }
> +
> + if (!entry) {
> + dev_err(formatter->dev,
> + "No frame desc entry for stream %u\n", stream);
> + return -EPIPE;
> + }
> +
> + vc = entry->bus.csi2.vc;
> +
> + if (vc >= CSI_FORMATTER_VC_NUM) {
> + dev_err(formatter->dev, "Invalid virtual channel %u\n", vc);
> + return -EINVAL;
I'd go for EPIPE here too to signal something is broken in the pipeline.
EINVAL is used to indicate an invalid parameter passed by userspace.
> + }
> +
> + return vc;
> +}
> +
> +static void csi_formatter_stop_stream(struct csi_formatter *formatter,
> + struct v4l2_subdev_state *state,
> + u64 stream_mask)
> +{
> + const struct csi_formatter_pix_format *pix_fmt;
> + struct v4l2_mbus_frame_desc fd = {};
> + struct v4l2_subdev_route *route;
> + struct v4l2_mbus_framefmt *fmt;
> + unsigned int reg;
> + unsigned int mask;
> + int vc;
> + int ret;
> +
> + ret = v4l2_subdev_call(formatter->csi_sd, pad, get_frame_desc,
> + formatter->remote_pad, &fd);
> + if (ret < 0 && ret != -ENOIOCTLCMD) {
> + dev_err(formatter->dev, "Failed to get frame desc: %d\n", ret);
> + return;
> + }
If you add
/*
* If the source doesn't implement .get_frame_desc(), assume a single
* stream on VC 0. fd is zero-initialized, only set the fields that have
* a non-zero value.
*/
if (ret == -ENOIOCTLCMD) {
fd.type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
fd.num_entries = 1;
}
here, ... (*)
> +
> + for_each_active_route(&state->routing, route) {
> + if (route->source_pad != CSI_FORMATTER_PAD_SOURCE)
> + continue;
Can this happen ? There's a single source pad and a single sink pad.
> +
> + if (!(stream_mask & BIT_ULL(route->source_stream)))
> + continue;
> +
> + if (ret == -ENOIOCTLCMD) {
> + /*
> + * Source doesn't implement get_frame_desc, use
> + * default VC 0
> + */
> + vc = 0;
> + } else {
(*) ... you could drop this.
Those comments apply to csi_formatter_start_stream() as well.
> + vc = csi_formatter_get_vc(formatter, &fd,
> + route->sink_stream);
> + if (vc < 0)
> + continue;
> + }
> +
> + fmt = v4l2_subdev_state_get_format(state, route->sink_pad,
> + route->sink_stream);
> +
> + pix_fmt = csi_formatter_find_format(fmt->code);
> + if (WARN_ON(!pix_fmt))
> + continue;
> +
> + reg = CSI_VC_PIXEL_DATA_TYPE(vc) + formatter->reg_offset;
> + mask = BIT(csi_formatter_get_index_by_dt(formatter,
> + pix_fmt->data_type));
> +
> + /* Clear the data type bit to disable this VC */
> + regmap_clear_bits(formatter->regs, reg, mask);
> + }
> +}
> +
> +static int csi_formatter_start_stream(struct csi_formatter *formatter,
> + struct v4l2_subdev_state *state,
> + u64 stream_mask)
> +{
> + const struct csi_formatter_pix_format *pix_fmt;
> + struct v4l2_subdev_route *route;
> + struct v4l2_mbus_framefmt *fmt;
> + struct v4l2_mbus_frame_desc fd = {};
> + u64 configured_streams = 0;
> + unsigned int reg;
> + unsigned int mask;
> + int vc;
> + int ret;
> +
> + ret = v4l2_subdev_call(formatter->csi_sd, pad, get_frame_desc,
> + formatter->remote_pad, &fd);
> + if (ret < 0 && ret != -ENOIOCTLCMD) {
> + dev_err(formatter->dev, "Failed to get frame desc: %d\n", ret);
> + return ret;
> + }
> +
> + for_each_active_route(&state->routing, route) {
> + if (route->source_pad != CSI_FORMATTER_PAD_SOURCE)
> + continue;
> +
> + if (!(stream_mask & BIT_ULL(route->source_stream)))
> + continue;
> +
> + if (ret == -ENOIOCTLCMD) {
> + /*
> + * Source doesn't implement get_frame_desc, use
> + * default VC 0
> + */
> + vc = 0;
> + } else {
> + vc = csi_formatter_get_vc(formatter, &fd,
> + route->sink_stream);
> + if (vc < 0) {
> + ret = vc;
> + goto err_cleanup;
> + }
> + }
> +
> + fmt = v4l2_subdev_state_get_format(state, route->sink_pad,
> + route->sink_stream);
> +
> + pix_fmt = csi_formatter_find_format(fmt->code);
> + if (WARN_ON(!pix_fmt)) {
> + ret = -EINVAL;
> + goto err_cleanup;
> + }
> +
> + reg = CSI_VC_PIXEL_DATA_TYPE(vc) + formatter->reg_offset;
> + mask = BIT(csi_formatter_get_index_by_dt(formatter,
> + pix_fmt->data_type));
> +
> + /* Set the data type bit to enable this VC */
> + regmap_set_bits(formatter->regs, reg, mask);
> +
> + configured_streams |= BIT_ULL(route->source_stream);
> + }
> +
> + return 0;
> +
> +err_cleanup:
> + csi_formatter_stop_stream(formatter, state, configured_streams);
> + return ret;
> +}
> +
> +static int csi_formatter_subdev_enable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 streams_mask)
> +{
> + struct csi_formatter *formatter = sd_to_formatter(sd);
> + struct device *dev = formatter->dev;
> + u64 sink_streams;
> + int ret;
> +
> + sink_streams = v4l2_subdev_state_xlate_streams(state,
> + CSI_FORMATTER_PAD_SOURCE,
> + CSI_FORMATTER_PAD_SINK,
> + &streams_mask);
> + if (!sink_streams || !streams_mask)
> + return -EINVAL;
> +
> + guard(mutex)(&formatter->lock);
> +
> + if (!formatter->enabled_streams) {
> + ret = pm_runtime_resume_and_get(formatter->dev);
> + if (ret < 0) {
> + dev_err(dev, "Failed to resume runtime PM: %d\n", ret);
> + return ret;
> + }
> + }
> +
> + ret = csi_formatter_start_stream(formatter, state, streams_mask);
> + if (ret)
> + goto err_runtime_put;
> +
> + ret = v4l2_subdev_enable_streams(formatter->csi_sd,
> + formatter->remote_pad,
> + sink_streams);
> + if (ret)
> + goto err_stop_stream;
> +
> + formatter->enabled_streams |= streams_mask;
> +
> + return 0;
> +
> +err_stop_stream:
> + csi_formatter_stop_stream(formatter, state, streams_mask);
> +err_runtime_put:
> + if (!formatter->enabled_streams)
> + pm_runtime_put(formatter->dev);
> + return ret;
> +}
> +
> +static int csi_formatter_subdev_disable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 streams_mask)
> +{
> + struct csi_formatter *formatter = sd_to_formatter(sd);
> + u64 sink_streams;
> + int ret;
> +
> + sink_streams = v4l2_subdev_state_xlate_streams(state,
> + CSI_FORMATTER_PAD_SOURCE,
> + CSI_FORMATTER_PAD_SINK,
> + &streams_mask);
> + if (!sink_streams || !streams_mask)
> + return -EINVAL;
> +
> + guard(mutex)(&formatter->lock);
> +
> + ret = v4l2_subdev_disable_streams(formatter->csi_sd, formatter->remote_pad,
> + sink_streams);
> + if (ret)
> + dev_err(formatter->dev, "Failed to disable streams: %d\n", ret);
> +
> + csi_formatter_stop_stream(formatter, state, streams_mask);
> +
> + formatter->enabled_streams &= ~streams_mask;
> +
> + if (!formatter->enabled_streams)
> + pm_runtime_put(formatter->dev);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_subdev_pad_ops formatter_subdev_pad_ops = {
> + .enum_mbus_code = csi_formatter_subdev_enum_mbus_code,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = csi_formatter_subdev_set_fmt,
> + .get_frame_desc = v4l2_subdev_get_frame_desc_passthrough,
> + .set_routing = csi_formatter_subdev_set_routing,
> + .enable_streams = csi_formatter_subdev_enable_streams,
> + .disable_streams = csi_formatter_subdev_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops formatter_subdev_ops = {
> + .pad = &formatter_subdev_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops formatter_internal_ops = {
> + .init_state = csi_formatter_subdev_init_state,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Media entity operations
> + */
> +
> +static const struct media_entity_operations formatter_entity_ops = {
> + .link_validate = v4l2_subdev_link_validate,
> + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> +};
> +
> +static int csi_formatter_subdev_init(struct csi_formatter *formatter)
> +{
> + struct v4l2_subdev *sd = &formatter->sd;
> + int ret;
> +
> + v4l2_subdev_init(sd, &formatter_subdev_ops);
> +
> + snprintf(sd->name, sizeof(sd->name), "%s", dev_name(formatter->dev));
strscpy() should be enough, no need for snprintf().
> + sd->internal_ops = &formatter_internal_ops;
> +
> + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> + V4L2_SUBDEV_FL_STREAMS;
> + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> + sd->entity.ops = &formatter_entity_ops;
> + sd->dev = formatter->dev;
> +
> + formatter->pads[CSI_FORMATTER_PAD_SINK].flags = MEDIA_PAD_FL_SINK
> + | MEDIA_PAD_FL_MUST_CONNECT;
> + formatter->pads[CSI_FORMATTER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&sd->entity, CSI_FORMATTER_PAD_NUM,
> + formatter->pads);
> + if (ret) {
> + dev_err(formatter->dev, "Failed to init pads\n");
> + return ret;
> + }
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret)
> + media_entity_cleanup(&sd->entity);
> +
> + return ret;
> +}
> +
> +static inline struct csi_formatter *
> +notifier_to_csi_formatter(struct v4l2_async_notifier *n)
> +{
> + return container_of(n, struct csi_formatter, notifier);
> +}
> +
> +static int csi_formatter_notify_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *sd,
> + struct v4l2_async_connection *asc)
> +{
> + const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE
> + | MEDIA_LNK_FL_ENABLED;
> + struct csi_formatter *formatter = notifier_to_csi_formatter(notifier);
> + struct v4l2_subdev *sdev = &formatter->sd;
> + struct media_pad *sink = &sdev->entity.pads[CSI_FORMATTER_PAD_SINK];
> + struct media_pad *remote_pad;
> + int ret;
> +
> + formatter->csi_sd = sd;
> +
> + dev_dbg(formatter->dev, "Bound subdev: %s pad\n", sd->name);
I recommend dropping this, there's already a debug message in
v4l2_async_match_notify().
> +
> + ret = v4l2_create_fwnode_links_to_pad(sd, sink, link_flags);
> + if (ret < 0)
> + return ret;
> +
> + remote_pad = media_pad_remote_pad_first(sink);
formatter->remote_pad = media_pad_remote_pad_first(sink);
is fine too, and you can drop the local variable.
> + if (!remote_pad) {
> + dev_err(formatter->dev, "Pipe not setup correctly\n");
> + return -EPIPE;
> + }
> + formatter->remote_pad = remote_pad->index;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_async_notifier_operations formatter_notify_ops = {
> + .bound = csi_formatter_notify_bound,
> +};
> +
> +static int csi_formatter_async_register(struct csi_formatter *formatter)
> +{
> + struct device *dev = formatter->dev;
> + struct v4l2_async_connection *asc;
> + int ret;
> +
> + struct fwnode_handle *ep __free(fwnode_handle) =
> + fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0,
> + FWNODE_GRAPH_ENDPOINT_NEXT);
> + if (!ep)
> + return -ENOTCONN;
> +
> + v4l2_async_subdev_nf_init(&formatter->notifier, &formatter->sd);
> +
> + asc = v4l2_async_nf_add_fwnode_remote(&formatter->notifier, ep,
> + struct v4l2_async_connection);
> + if (IS_ERR(asc)) {
> + ret = PTR_ERR(asc);
> + goto err_cleanup_notifier;
> + }
> +
> + formatter->notifier.ops = &formatter_notify_ops;
> +
> + ret = v4l2_async_nf_register(&formatter->notifier);
> + if (ret)
> + goto err_cleanup_notifier;
> +
> + ret = v4l2_async_register_subdev(&formatter->sd);
> + if (ret)
> + goto err_unregister_notifier;
> +
> + return 0;
> +
> +err_unregister_notifier:
> + v4l2_async_nf_unregister(&formatter->notifier);
> +err_cleanup_notifier:
> + v4l2_async_nf_cleanup(&formatter->notifier);
> + return ret;
> +}
> +
> +static void csi_formatter_async_unregister(struct csi_formatter *formatter)
> +{
> + v4l2_async_unregister_subdev(&formatter->sd);
> + v4l2_async_nf_unregister(&formatter->notifier);
> + v4l2_async_nf_cleanup(&formatter->notifier);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Suspend/resume
> + */
> +
> +static int csi_formatter_runtime_suspend(struct device *dev)
> +{
> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
> + struct csi_formatter *formatter = sd_to_formatter(sd);
> +
> + clk_disable_unprepare(formatter->clk);
> +
> + return 0;
> +}
> +
> +static int csi_formatter_runtime_resume(struct device *dev)
> +{
> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
> + struct csi_formatter *formatter = sd_to_formatter(sd);
> +
> + return clk_prepare_enable(formatter->clk);
> +}
> +
> +static DEFINE_RUNTIME_DEV_PM_OPS(csi_formatter_pm_ops,
> + csi_formatter_runtime_suspend,
> + csi_formatter_runtime_resume, NULL);
> +
> +static int csi_formatter_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct csi_formatter *formatter;
> + u32 val;
> + int ret;
> +
> + formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
> + if (!formatter)
> + return -ENOMEM;
> +
> + formatter->dev = dev;
> +
> + ret = devm_mutex_init(dev, &formatter->lock);
> + if (ret)
> + return ret;
> +
> + formatter->regs = syscon_node_to_regmap(dev->parent->of_node);
> + if (IS_ERR(formatter->regs))
> + return dev_err_probe(dev, PTR_ERR(formatter->regs),
> + "Failed to get csi formatter regmap\n");
> +
> + ret = of_property_read_u32(dev->of_node, "reg", &val);
You can write
ret = of_property_read_u32(dev->of_node, "reg", &formatter->reg_offset);
and drop the val local variable.
> + if (ret < 0)
> + return dev_err_probe(dev, ret,
> + "Failed to get csi formatter reg property\n");
> +
> + formatter->reg_offset = val;
> +
> + formatter->clk = devm_clk_get(dev, NULL);
> + if (IS_ERR(formatter->clk))
> + return dev_err_probe(dev, PTR_ERR(formatter->clk),
> + "Failed to get pixel clock\n");
> +
> + ret = csi_formatter_subdev_init(formatter);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to initialize formatter subdev\n");
> +
> + platform_set_drvdata(pdev, &formatter->sd);
Store the formatter pointer, not the subdev pointer, that will save you
three calls to sd_to_formatter().
> +
> + /* Enable runtime PM. */
> + ret = devm_pm_runtime_enable(dev);
Would enabling autosuspend be useful ? It can be done on top.
> + if (ret)
> + goto err_cleanup_subdev;
> +
> + ret = csi_formatter_async_register(formatter);
> + if (ret < 0) {
> + dev_err_probe(dev, ret, "Failed to register async subdevice\n");
> + goto err_cleanup_subdev;
> + }
> +
> + return 0;
> +
> +err_cleanup_subdev:
> + v4l2_subdev_cleanup(&formatter->sd);
> + media_entity_cleanup(&formatter->sd.entity);
> + return ret;
> +}
> +
> +static void csi_formatter_remove(struct platform_device *pdev)
> +{
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct csi_formatter *formatter = sd_to_formatter(sd);
> +
> + csi_formatter_async_unregister(formatter);
> +
> + v4l2_subdev_cleanup(&formatter->sd);
> + media_entity_cleanup(&formatter->sd.entity);
> +}
> +
> +static const struct of_device_id csi_formatter_of_match[] = {
> + { .compatible = "fsl,imx95-csi-formatter" },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, csi_formatter_of_match);
> +
> +static struct platform_driver csi_formatter_device_driver = {
> + .driver = {
> + .name = "csi-pixel-formatter",
> + .of_match_table = csi_formatter_of_match,
> + .pm = pm_ptr(&csi_formatter_pm_ops),
> + },
> + .probe = csi_formatter_probe,
> + .remove = csi_formatter_remove,
> +};
> +
> +module_platform_driver(csi_formatter_device_driver);
> +
> +MODULE_AUTHOR("NXP Semiconductor, Inc.");
> +MODULE_DESCRIPTION("NXP i.MX95 CSI Pixel Formatter driver");
> +MODULE_LICENSE("GPL");
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH 0/2] ARM: dts: imx: clean up fsl,emi-bus related CHECK_DTBS warnings
From: Frank.Li @ 2026-07-20 16:31 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Frank.Li
Cc: Frank Li, devicetree, linux-kernel, imx, linux-arm-kernel
In-Reply-To: <20260708-imx53-eim-v1-0-913b4559e5b5@nxp.com>
From: Frank Li <Frank.Li@nxp.com>
On Wed, 08 Jul 2026 16:00:43 -0400, Frank.Li@oss.nxp.com wrote:
> i.MX2 use fsl,emi-bus. i.MX5 use fsl,eim-bus. Since it is very very old
> chips. Just leave compatible string as it and allow two kinds name.
Applied, thanks!
[1/2] dt-bindings: soc: imx: Add fsl,eim-bus
commit: b68333c76738be779876f0c619dddd4b0a7ddd50
[2/2] ARM: dts: imx53-ard: change node name eim-cs1 to eim-cs1-bus
commit: 9d40f2a740fd64991919d6f58d741a44f736535e
Best regards,
--
Frank Li <Frank.Li@nxp.com>
^ permalink raw reply
* Re: [PATCH] ARM: dts: aspeed-g6: add pcie-lpc and pcie-kcs4
From: Grégoire Layet @ 2026-07-20 16:31 UTC (permalink / raw)
To: Tan Siewert
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
Andrew Jeffery, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel
In-Reply-To: <178412588365.243141.16731478852761544781.b4-review@b4>
> > diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> > index 56bb3b0444f7..ac351f01048f 100644
> > --- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> > +++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> > @@ -658,6 +658,21 @@ ibt: ibt@140 {
> > };
> > };
> >
> > + pcie_lpc: pcie-lpc@1e789800 {
>
> lpc@1e789000 already maps 0x1e789000-0x1e78a000 and 0x1e789914 falls inside it,
I agree that there is an overlap in address space in my patch.
I will fix this in a new revision.
> so you're describing a second `ast2600-lpc-v2` node which is unnecessary.
>
> Suggestion: Merge pcie_kcs4 into lpc@1e789000 and use 914 as offset. That way
> you don't accidentally cause an overlap for the devices if you describe more in
> the future.
The 'kcs_bmc_aspeed' driver has the kcs channels address hard-coded.
If the 'reg' property does not contain the three addresses used by one
of the four channels, the driver returns -EINVAL.
So having a pcie-kcs4 with regs 0x914, 0x918 and 0x91c doesn't work.
This is why a second LPC node was added, for the pcie-kcs4 to have
regs 0x114, 0x118 and 0x11c;
I know we should write device tree sources based on hardware rather
than around driver limitations.
Changing how the driver behaves seems excessive for supporting KCS over PCIe.
I think a point can be made that the LPC over PCIe is a different LPC bus.
Regards,
Grégoire
^ permalink raw reply
* Re: [PATCH 0/5] ARM: dts: ls1021a: dts CHECK_DTBS warning cleanup
From: Frank.Li @ 2026-07-20 16:33 UTC (permalink / raw)
To: Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Frank.Li
Cc: Frank Li, linux-arm-kernel, imx, devicetree, linux-kernel,
ioana.ciornei, vladimir.oltean, SZ Lin
In-Reply-To: <20260708-ls-dts-warning-v1-0-5daa24061c31@nxp.com>
From: Frank Li <Frank.Li@nxp.com>
On Wed, 08 Jul 2026 15:26:08 -0400, Frank.Li@oss.nxp.com wrote:
> Collect dts changes to clean up CHECK_DTBS warning.
Applied, thanks!
[1/5] ARM: dts: ls1021a-moxa-uc-8410a: add led suffix to fix CHECK_DTBS warnings
commit: 8381098761d85ec09a838c02827df3b1dca8db6b
[2/5] ARM: dts: ls1021a-twr: add power-supply for lcd panel
commit: 5bd40da5afd7a09a0dfe655b26189197935eb493
[3/5] ARM: dts: ls1021a-moxa-uc-8410a: use compatible string ethernet-phy-ieee802.3-c22
commit: 09ea402c1c2c5f6bf6ef7b0f00045f91b5e8e926
[4/5] ARM: dts: ls1021a-moxa-uc-8410a: replace spansion,s25fl164k with jedec,spi-nor
commit: b5a62376f6f0a3dbf585c9007aa1b196022435f9
[5/5] ARM: dts: ls1021a-moxa-uc-8410a: remove undocument property default-state of gpio-keys
commit: b022b074d615aa04abf3d73ec497c88d7ce09a9d
Best regards,
--
Frank Li <Frank.Li@nxp.com>
^ permalink raw reply
* Re: [PATCH v3 phy-next 8/8] phy: lynx-10g: use RCW override procedure for dynamic protocol change
From: Vinod Koul @ 2026-07-20 16:34 UTC (permalink / raw)
To: Vladimir Oltean
Cc: linux-phy, devicetree, linuxppc-dev, linux-arm-kernel,
Ioana Ciornei, Neil Armstrong, Tanjeff Moos,
Christophe Leroy (CS GROUP), Michael Walle, Shawn Guo, Frank Li,
linux-kernel
In-Reply-To: <20260720133642.136324-9-vladimir.oltean@nxp.com>
On 20-07-26, 16:36, Vladimir Oltean wrote:
> Up until this patch, the only protocol change supported was between
> 1000Base-X/SGMII and 2500Base-X. The others require an RCW override
> procedure which was lacking.
>
> Since now the guts driver provides the means of applying this procedure,
> make use of it and remove any comment which mentioned the limitation.
lgtm, is there any dependency. If not I can pick it
--
~Vinod
^ permalink raw reply
* Re: [PATCH 0/4] ARM: dts: imx: small change to fix CHECK_DTBS warnings
From: Frank.Li @ 2026-07-20 16:36 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Frank.Li
Cc: Frank Li, devicetree, imx, linux-arm-kernel, linux-kernel
In-Reply-To: <20260708-arm_dts_mini-v1-0-11b88825fd1c@nxp.com>
From: Frank Li <Frank.Li@nxp.com>
On Wed, 08 Jul 2026 16:08:29 -0400, Frank.Li@oss.nxp.com wrote:
>
Applied, thanks!
[1/4] ARM: dts: imx6dl-plym2m: change #io-channel-cells to 1 of voltage divider
commit: b8d669c5805736a7664524ec625425111863eec0
[2/4] ARM: dts: imx53-smd: remove undocument property clock-names of ovti,ov5642
commit: aa5c8f1b5ee936661058455480e6e843fdf1d5b7
[3/4] ARM: dts: imx6ul-isiot: remove undocument property clock-names of fsl,sgtl5000
commit: 5af3e703f2604f7a3bc44422c1d30b128aa49f7c
[4/4] ARM: dts: imx6ull-engicam-microgea: remove nand-ecc-strength and nand-ecc-step-size
commit: 12ed4b9ab36a84a5082e13004d799c0d70200382
Best regards,
--
Frank Li <Frank.Li@nxp.com>
^ permalink raw reply
* Re: [PATCH v8 00/21] ARM64 PMU Partitioning
From: James Clark @ 2026-07-20 16:46 UTC (permalink / raw)
To: Colton Lewis, kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-perf-users,
linux-kselftest
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
On 12/06/2026 8:28 pm, Colton Lewis wrote:
> This series creates a new PMU scheme on ARM, a partitioned PMU that
> allows reserving a subset of counters for more direct guest access,
> significantly reducing overhead. More details, including performance
> benchmarks, can be read in the v1 cover letter linked below.
>
> An overview of what this series accomplishes was presented at KVM
> Forum 2025. Slides [1] and video [2] are linked below.
>
> The kernel command line parameter for the driver still exists, but now
> only defines an upper limit of counters the guest might use rather
> than taking those counters from the host permanently.
>
> I would appreciate any discussion on whether that parameter should
> still exist as it's an inconvenient enabling gate on the feature that
> is no longer required. The question comes down to what, if any, guards
> we want against a guest monopolizing all counters on a system.
>
Hi Colton,
The existence of the parameter makes sense, but can't the default be
arm_pmuv3.reserved_host_counters=0 instead of -1 (partition disabled)?
It's still a bit fiddly having to do two things to make it work, and
there's no documentation about what the defaults or other prerequisites
are. IMO it's not even trivial to work out that you need to prefix it
with "arm_pmuv3.", which documentation would improve.
Testing the whole set I ran into a few issues:
This warn is hit when there is some kind of interaction with sleeping. I
tried to bisect it but it only appears on the last commit when the
option to enable partitioning is added.
/*
* ARM pmu always has to reprogram the period, so ignore
* PERF_EF_RELOAD, see the comment below.
*/
if (flags & PERF_EF_RELOAD)
WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE));
Steps to reproduce:
Host (arm_pmuv3.reserved_host_counters=0):
$ sudo perf stat -C 2 -e \
'branches,branches,branches,branches,branches,branches'
$ sudo taskset --cpu-list 2 ./lkvm run --kernel \
/boot/vmlinux-7.1.0-rc7+ -m 1024 -c 1 --pmu
Guest:
$ sleep 1
WARNING: drivers/perf/arm_pmu.c:302 at cpu_pm_pmu_notify+0x278/0x2c0,
CPU#2: swapper/2/0
Call trace:
cpu_pm_pmu_notify+0x278/0x2c0 (P)
notifier_call_chain+0x84/0x1d0
raw_notifier_call_chain+0x24/0x38
cpu_pm_exit+0x34/0x68
acpi_processor_ffh_lpi_enter+0x40/0x78
acpi_idle_lpi_enter+0x54/0x78
cpuidle_enter_state+0xb4/0x248
cpuidle_enter+0x44/0x68
do_idle+0x21c/0x300
cpu_startup_entry+0x40/0x50
secondary_start_kernel+0x120/0x150
__secondary_switched+0xc0/0xc8
When running the guest on a single CPU I get different counts for the
same event for a single process, although this never happens on a host.
I think there might even be some Perf tests which expect them to be the
same, and this doesn't depend on whether any events are running on the
host or not. Not sure if you ran all the Perf selftests in a guest or not?
I'm not sure if the exception level filtering isn't working or there is
something wrong with freezing. Doesn't the host PMU driver need to
freeze with HPME? I see it's still doing it with PMCR_EL0.E which
freezes the guest's counters now doesn't it?
Host (arm_pmuv3.reserved_host_counters=0):
$ sudo taskset --cpu-list 2 ./lkvm run --kernel \
/boot/vmlinux-7.1.0-rc7+ -m 1024 -c 1 --pmu
Guest:
$ perf stat -e branches,branches true
Performance counter stats for 'true':
167963 branches
160925 branches
$ perf stat -e branches,branches,branches,branches,branches true
Performance counter stats for 'true':
164425 branches
164425 branches
164425 branches
157743 branches
157743 branches
When running the guest on two CPUs I just get zeros. Do the kvm
selftests not catch this? Or is it something to do with my setup:
Host (arm_pmuv3.reserved_host_counters=0):
$ sudo taskset --cpu-list 2-3 ./lkvm run --kernel \
/boot/vmlinux-7.1.0-rc7+ -m 1024 -c 1 --pmu
Guest:
$ perf stat -e branches,branches true
Performance counter stats for 'true':
0 branches
0 branches
I also noticed I get the "squeezed" warning printed after launching the
guest but not using Perf. This comment implies that not using counters
makes it a nop:
/*
* If we aren't guest-owned then we know the guest isn't using
* the PMU anyway, so no need to bother with the swap.
*/
if (vcpu->arch.pmu.access != VCPU_PMU_ACCESS_GUEST_OWNED)
return;
But maybe linux is touching them in a way that makes them guest owned,
even on probe? Or is the guest/host ownership tracking not working, I
didn't look too hard.
Finally I think there are 3 critical Sashiko comments on this version.
If they're false positives, then maybe some comments in the code or
commit messages could reassure it.
Thanks
James
> v8:
>
> * Rebase on top of v7.1-rc7.
>
> * Implement Oliver Upton's accessor proposal to centralize PMU
> register access and simplify trap handlers. Instead of one singular
> accessor, implement as two because the read and write paths are
> always different anyway.
>
> * Introduce the partitioning flag along with the
> kvm_pmu_is_partitioned predicate
>
> * Don't use ifdef for partitioning predicates as that can be handled
> by has_vhe
>
> * Clean up MDCR_EL2 handling by open-coding use_fgt and hpmn and
> unconditionally setting RES0 bits.
>
> * Use {read,write}_pmcrcntrn in context swaps
>
> * Put operators on preceeding lines
>
> * Rename hw_cntr_mask to hw_cntr_impl to clarify it tracks the number
> of counters implemented by hardware
>
> * Use GENMASK_ULL in mask functions returning u64
>
> * warn_once when host events are squeezed out by guest counter
> allocations.
>
> * Address Sashiko AI Review findings:
>
> - Critical fixes for lazy PMU context swaps (ensuring guest state is
> loaded on transition to GUEST_OWNED), PMSELR_EL0 trapping to
> prevent stale selector index, and masking guest PMCR_EL0 writes to
> prevent host reset.
>
> - High priority fixes for lock safety (disabling IRQs when acquiring
> perf context lock), disabling guest counters on vCPU put,
> preserving VHE host profiling in MDCR_EL2, waking halted vCPUs on
> guest PMU interrupts, masking host configuration leaks, preemption
> safety in per-CPU accesses, emulating PMCR.N reads, and preventing
> data races in PMOVSSET_EL0 accesses.
>
> - Medium/Low fixes for user-access fallback safety, VM-wide state
> modification restrictions, selftests type safety, and cleanup of
> unused fields and typos.
>
> v7:
> https://lore.kernel.org/kvmarm/20260504211813.1804997-1-coltonlewis@google.com/
>
> v6:
> https://lore.kernel.org/kvmarm/20260209221414.2169465-1-coltonlewis@google.com/
>
> v5:
> https://lore.kernel.org/kvmarm/20251209205121.1871534-1-coltonlewis@google.com/
>
> v4:
> https://lore.kernel.org/kvmarm/20250714225917.1396543-1-coltonlewis@google.com/
>
> v3:
> https://lore.kernel.org/kvm/20250626200459.1153955-1-coltonlewis@google.com/
>
> v2:
> https://lore.kernel.org/kvm/20250620221326.1261128-1-coltonlewis@google.com/
>
> v1:
> https://lore.kernel.org/kvm/20250602192702.2125115-1-coltonlewis@google.com/
>
> [1] https://gitlab.com/qemu-project/kvm-forum/-/raw/main/_attachments/2025/Optimizing__itvHkhc.pdf
> [2] https://www.youtube.com/watch?v=YRzZ8jMIA6M&list=PLW3ep1uCIRfxwmllXTOA2txfDWN6vUOHp&index=9
>
> Colton Lewis (20):
> arm64: cpufeature: Add cpucap for HPMN0
> KVM: arm64: Reorganize PMU functions
> perf: arm_pmuv3: Generalize counter bitmasks
> perf: arm_pmuv3: Check cntr_mask before using pmccntr
> perf: arm_pmuv3: Allocate counter indices from high to low
> perf: arm_pmuv3: Add method to partition the PMU
> KVM: arm64: Set up FGT for Partitioned PMU
> KVM: arm64: Add Partitioned PMU register trap handlers
> KVM: arm64: Set up MDCR_EL2 to handle a Partitioned PMU
> KVM: arm64: Context swap Partitioned PMU guest registers
> KVM: arm64: Enforce PMU event filter at vcpu_load()
> perf: Add perf_pmu_resched_update()
> KVM: arm64: Apply dynamic guest counter reservations
> KVM: arm64: Implement lazy PMU context swaps
> perf: arm_pmuv3: Handle IRQs for Partitioned PMU guest counters
> KVM: arm64: Detect overflows for the Partitioned PMU
> KVM: arm64: Add vCPU device attr to partition the PMU
> KVM: selftests: Add find_bit to KVM library
> KVM: arm64: selftests: Add test case for Partitioned PMU
> KVM: arm64: selftests: Relax testing for exceptions when partitioned
>
> Marc Zyngier (1):
> KVM: arm64: Reorganize PMU includes
>
> arch/arm/include/asm/arm_pmuv3.h | 18 +
> arch/arm64/include/asm/arm_pmuv3.h | 12 +-
> arch/arm64/include/asm/kvm_host.h | 17 +-
> arch/arm64/include/asm/kvm_types.h | 6 +-
> arch/arm64/include/uapi/asm/kvm.h | 2 +
> arch/arm64/kernel/cpufeature.c | 10 +-
> arch/arm64/kvm/Makefile | 2 +-
> arch/arm64/kvm/arm.c | 2 +
> arch/arm64/kvm/config.c | 41 +-
> arch/arm64/kvm/debug.c | 30 +-
> arch/arm64/kvm/pmu-direct.c | 507 ++++++++++++
> arch/arm64/kvm/pmu-emul.c | 684 +----------------
> arch/arm64/kvm/pmu.c | 720 ++++++++++++++++++
> arch/arm64/kvm/sys_regs.c | 271 +++++--
> arch/arm64/tools/cpucaps | 1 +
> arch/arm64/tools/sysreg | 6 +-
> drivers/perf/arm_pmuv3.c | 136 +++-
> include/kvm/arm_pmu.h | 93 ++-
> include/linux/perf/arm_pmu.h | 8 +
> include/linux/perf/arm_pmuv3.h | 14 +-
> include/linux/perf_event.h | 3 +
> kernel/events/core.c | 31 +-
> tools/include/perf/arm_pmuv3.h | 12 +-
> tools/testing/selftests/kvm/Makefile.kvm | 1 +
> .../selftests/kvm/arm64/vpmu_counter_access.c | 112 ++-
> tools/testing/selftests/kvm/lib/find_bit.c | 2 +
> 26 files changed, 1918 insertions(+), 823 deletions(-)
> create mode 100644 arch/arm64/kvm/pmu-direct.c
> create mode 100644 tools/testing/selftests/kvm/lib/find_bit.c
>
>
> base-commit: 4549871118cf616eecdd2d939f78e3b9e1dddc48
^ permalink raw reply
* Re: [PATCH 11/21] KVM: arm64: Context swap Partitioned PMU guest registers
From: James Clark @ 2026-07-20 16:46 UTC (permalink / raw)
To: Colton Lewis, kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-perf-users,
linux-kselftest
In-Reply-To: <20260612192909.1153907-12-coltonlewis@google.com>
On 12/06/2026 20:28, Colton Lewis wrote:
> Save and restore newly untrapped registers that can be directly
> accessed by the guest when the PMU is partitioned.
>
> * PMEVCNTRn_EL0
> * PMCCNTR_EL0
> * PMSELR_EL0
> * PMCR_EL0
> * PMCNTEN_EL0
> * PMINTEN_EL1
>
> If we know we are not partitioned (that is, using the emulated vPMU),
> then return immediately. A later patch will make this lazy so the
> context swaps don't happen unless the guest has accessed the PMU.
>
> PMEVTYPER is handled in a following patch since we must apply the KVM
> event filter before writing values to hardware.
>
> PMOVS guest counters are cleared to avoid the possibility of
> generating spurious interrupts when PMINTEN is written. This is fine
> because the virtual register for PMOVS is always the canonical value.
>
> Signed-off-by: Colton Lewis <coltonlewis@google.com>
> ---
> arch/arm/include/asm/arm_pmuv3.h | 4 +
> arch/arm64/kvm/arm.c | 2 +
> arch/arm64/kvm/pmu-direct.c | 183 +++++++++++++++++++++++++++++++
> include/kvm/arm_pmu.h | 16 +++
> 4 files changed, 205 insertions(+)
>
[...]
> +
> +/**
> + * kvm_pmu_put() - Put untrapped PMU registers
> + * @vcpu: Pointer to struct kvm_vcpu
> + *
> + * Put all untrapped PMU registers from the VCPU into the PCPU. Mask
> + * to only bits belonging to guest-reserved counters and leave
> + * host-reserved counters alone in bitmask registers.
> + */
> +void kvm_pmu_put(struct kvm_vcpu *vcpu)
> +{
> + struct arm_pmu *pmu;
> + unsigned long guest_counters;
> + unsigned long flags;
> + u64 mask;
> + u8 i;
> + u64 val;
> +
> + /*
> + * If we aren't guest-owned then we know the guest is not
> + * accessing the PMU anyway, so no need to bother with the
> + * swap.
> + */
> + if (!kvm_pmu_is_partitioned(vcpu->kvm))
> + return;
> +
> + preempt_disable();
> +
> + pmu = vcpu->kvm->arch.arm_pmu;
> + guest_counters = kvm_pmu_guest_counter_mask(pmu);
> +
> + for_each_set_bit(i, &guest_counters, ARMPMU_MAX_HWEVENTS) {
> + if (i == ARMV8_PMU_CYCLE_IDX)
> + val = read_pmccntr();
> + else
> + val = read_pmevcntrn(i);
> +
> + __vcpu_assign_sys_reg(vcpu, PMEVCNTR0_EL0 + i, val);
> + }
> +
> + val = read_sysreg(pmselr_el0);
> + __vcpu_assign_sys_reg(vcpu, PMSELR_EL0, val);
> +
> + val = read_sysreg(pmcr_el0);
> + __vcpu_assign_sys_reg(vcpu, PMCR_EL0, val);
> +
> + /* Mask these to only save the guest relevant bits. */
> + mask = kvm_pmu_guest_counter_mask(pmu);
> +
> + val = read_sysreg(pmcntenset_el0);
> + __vcpu_assign_sys_reg(vcpu, PMCNTENSET_EL0, val & mask);
> +
> + val = read_sysreg(pmintenset_el1);
> + __vcpu_assign_sys_reg(vcpu, PMINTENSET_EL1, val & mask);
> +
> + /* Save pending guest hardware overflows. */
> + local_irq_save(flags);
> + val = read_sysreg(pmovsset_el0);
> + __vcpu_rmw_sys_reg(vcpu, PMOVSSET_EL0, |=, val & mask);
> + write_sysreg(val & mask, pmovsclr_el0);
> + local_irq_restore(flags);
> +
> + /* Stop guest counters and disable interrupts in hardware. */
> + write_sysreg(mask, pmcntenclr_el0);
> + write_sysreg(mask, pmintenclr_el1);
> +
> + kvm_pmu_set_guest_counters(pmu, 0);
Hi Colton,
This function doesn't get added until "KVM: arm64: Apply dynamic guest
counter reservations" a few commits later.
kvm_pmu_guest_counter_mask() is also used in a commit before it's added.
^ permalink raw reply
* Re: [PATCH RFC] arm64: dts: allwinner: a523: Add SPDIF to x96qproplus device
From: Per Larsson @ 2026-07-20 16:46 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jernej Skrabec,
Samuel Holland,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
moderated list:ARM/Allwinner sunXi SoC support,
open list:ARM/Allwinner sunXi SoC support, open list
In-Reply-To: <CAGb2v64rRipC9VV5QyHC0nR4rAho8q=wX0ngVVxRND3xMOnofA@mail.gmail.com>
On Mon, 20 Jul 2026 22:59:57 +0800
Chen-Yu Tsai <wens@kernel.org> wrote:
> On Sun, Jul 19, 2026 at 11:10 AM Per Larsson <per@palvencia.se> wrote:
> >
> >
> > Signed-off-by: Per Larsson <per@palvencia.se>
> > ---
> > Marking this as RFC for a few reasons
> > 1. This is my first submission, hoping everything is properly
> > organized.
>
> This is pretty good. The commit message is also well written. I would
> like to see the patch split into two patches though. The first adds
> the pinmux (and you can mention in the commit message that a
> subsequent patch will reference it). The second patch enables SPDIF
> on the device you have.
>
Ok, will split into two patches.
> > 2. My testing setup is not the best: I get sound with this patch,
> > but the pulseaudio daemon needs to be restarted far too often.
> > Hopefully it's just something on this minirootfs. Testing
> > welcome. 3. Not sure where to get the hash for a fixes tag or if
> > that's even OK
>
> This is a new addition, not a fix, so no fixes tag is warranted.
>
Understood.
Also saw that issue #2 in the list above was indeed because of issues in
my rootfs and not because of anything related to the patch, so no more
RFC-markings for next version.
>
> Thanks
> ChenYu
>
> > ---
^ permalink raw reply
* Re: [PATCH] ARM: imx: Fix suspend/resume crash with Clang CFI
From: Nick Desaulniers @ 2026-07-20 16:49 UTC (permalink / raw)
To: Yo'av Moshe
Cc: Frank Li, Sascha Hauer, Russell King, Pengutronix Kernel Team,
Fabio Estevam, Nathan Chancellor, Bill Wendling, Justin Stitt,
imx, linux-arm-kernel, llvm, stable, linux-kernel, Kees Cook,
Sami Tolvanen
In-Reply-To: <20260717141648.1007059-1-linux@yoavmoshe.com>
On Fri, Jul 17, 2026 at 7:17 AM Yo'av Moshe <linux@yoavmoshe.com> wrote:
>
> Relocated suspend code in OCRAM lacks compiler-generated CFI type
> signatures. When CONFIG_CFI=y is active, the indirect call to
> imx6_suspend_in_ocram_fn triggers a strict CFI violation panic.
>
> Annotate imx6q_suspend_finish with __nocfi to bypass CFI checking
> for this specific indirect call.
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Yo'av Moshe <linux@yoavmoshe.com>
Thanks for the patch.
Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
> ---
> Tested on a Kobo Clara HD (i.MX6SLL SoC) running postmarketOS edge.
> Before this patch, suspending the device caused an immediate silent
> hang requiring a hard-reboot. With this patch applied, suspend and
> resume work successfully.
>
> arch/arm/mach-imx/pm-imx6.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c
> index a671ca498..d9b0c1803 100644
> --- a/arch/arm/mach-imx/pm-imx6.c
> +++ b/arch/arm/mach-imx/pm-imx6.c
> @@ -360,7 +360,7 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode)
> return 0;
> }
>
> -static int imx6q_suspend_finish(unsigned long val)
> +static int __nocfi imx6q_suspend_finish(unsigned long val)
> {
> if (!imx6_suspend_in_ocram_fn) {
> cpu_do_idle();
> --
> 2.55.0
>
--
Thanks,
~Nick Desaulniers
^ permalink raw reply
* Re: [PATCH 1/1] MAINTAINERS: media: nxp: imx8-isi: Add Frank Li as reviewer and i.MX mailing list
From: Laurent Pinchart @ 2026-07-20 16:51 UTC (permalink / raw)
To: Frank.Li
Cc: mchehab, kernel, estevam, linux-media, imx, linux-arm-kernel,
linux-kernel, Frank Li
In-Reply-To: <20260630163456.3317624-1-Frank.Li@oss.nxp.com>
Hi Frank,
On Tue, Jun 30, 2026 at 12:34:56PM -0400, Frank.Li@oss.nxp.com wrote:
> From: Frank Li <Frank.Li@nxp.com>
>
> Add Frank Li as a reviewer and the i.MX mailing list for the i.MX8 ISI
> driver. This helps ensure patches receive review by the NXP i.MX
> maintainers.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
Thank you for volunteering.
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> MAINTAINERS | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 361a4f447277c..62ed60238b1cb 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -19470,7 +19470,9 @@ F: drivers/iio/adc/vf610_adc.c
>
> NXP i.MX 8M ISI DRIVER
> M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> +R: Frank Li <Frank.Li@nxp.com>
> L: linux-media@vger.kernel.org
> +L: imx@lists.linux.dev
> S: Maintained
> F: Documentation/devicetree/bindings/media/fsl,imx8*-isi.yaml
> F: Documentation/devicetree/bindings/media/nxp,imx8-isi.yaml
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH] ARM: imx: Fix suspend/resume crash with Clang CFI
From: Nick Desaulniers @ 2026-07-20 16:51 UTC (permalink / raw)
To: Yo'av Moshe
Cc: Frank Li, Sascha Hauer, Russell King, Pengutronix Kernel Team,
Fabio Estevam, Nathan Chancellor, Bill Wendling, Justin Stitt,
imx, linux-arm-kernel, llvm, stable, linux-kernel, Kees Cook,
Sami Tolvanen
In-Reply-To: <CAKwvOdkGYLWAR+5q+XaO6VZc3-sGEm0nX0MvXoJOghx7Gn1tRg@mail.gmail.com>
On Mon, Jul 20, 2026 at 9:49 AM Nick Desaulniers
<ndesaulniers@google.com> wrote:
>
> On Fri, Jul 17, 2026 at 7:17 AM Yo'av Moshe <linux@yoavmoshe.com> wrote:
> >
> > Relocated suspend code in OCRAM lacks compiler-generated CFI type
> > signatures. When CONFIG_CFI=y is active, the indirect call to
> > imx6_suspend_in_ocram_fn triggers a strict CFI violation panic.
> >
> > Annotate imx6q_suspend_finish with __nocfi to bypass CFI checking
> > for this specific indirect call.
> >
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Yo'av Moshe <linux@yoavmoshe.com>
>
> Thanks for the patch.
> Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
Ah, looks like I'm reviewing v1, but there's already been a v2 and v3.
Disregard.
>
> > ---
> > Tested on a Kobo Clara HD (i.MX6SLL SoC) running postmarketOS edge.
> > Before this patch, suspending the device caused an immediate silent
> > hang requiring a hard-reboot. With this patch applied, suspend and
> > resume work successfully.
> >
> > arch/arm/mach-imx/pm-imx6.c | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c
> > index a671ca498..d9b0c1803 100644
> > --- a/arch/arm/mach-imx/pm-imx6.c
> > +++ b/arch/arm/mach-imx/pm-imx6.c
> > @@ -360,7 +360,7 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode)
> > return 0;
> > }
> >
> > -static int imx6q_suspend_finish(unsigned long val)
> > +static int __nocfi imx6q_suspend_finish(unsigned long val)
> > {
> > if (!imx6_suspend_in_ocram_fn) {
> > cpu_do_idle();
> > --
> > 2.55.0
> >
>
>
> --
> Thanks,
> ~Nick Desaulniers
--
Thanks,
~Nick Desaulniers
^ permalink raw reply
* Re: [PATCH v3] ARM: imx: Fix suspend/resume crash with Clang CFI
From: Nick Desaulniers @ 2026-07-20 16:53 UTC (permalink / raw)
To: Yo'av Moshe
Cc: Frank Li, Sascha Hauer, Russell King, Pengutronix Kernel Team,
Fabio Estevam, Nathan Chancellor, Bill Wendling, Justin Stitt,
imx, linux-arm-kernel, llvm, stable, linux-kernel
In-Reply-To: <20260718111340.159896-1-linux@yoavmoshe.com>
On Sat, Jul 18, 2026 at 4:14 AM Yo'av Moshe <linux@yoavmoshe.com> wrote:
>
> Relocated suspend code in OCRAM lacks compiler-generated CFI type
> signatures. When CONFIG_CFI=y is active, the indirect call to
> imx6_suspend_in_ocram_fn triggers a strict CFI violation panic.
>
> To resolve this safely without reducing CFI protection scope:
> 1. Create a minimal wrapper function imx6_suspend_in_ocram annotated
> with __nocfi to handle the unverified indirect call.
> 2. Remove the __nocfi annotation from the main imx6q_suspend_finish
> function to preserve full CFI coverage for other indirect calls
> in that scope (such as cpu_do_idle() and flush_cache_all()).
> 3. Mark global variables ccm_base, suspend_ocram_base, and the
> imx6_suspend_in_ocram_fn pointer as __ro_after_init to prevent
> them from being used as target vectors for CFI bypass exploits.
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Yo'av Moshe <linux@yoavmoshe.com>
> ---
> Tested on a Kobo Clara HD (i.MX6SLL SoC) running postmarketOS edge.
> Before this patch, suspending the device caused an immediate silent
> hang requiring a hard-reboot. With this patch applied, suspend and
> resume work successfully.
>
> Differences from v2:
> - Restrained __nocfi scope by adding a dedicated, minimal 1-line
> wrapper function (imx6_suspend_in_ocram) for the OCRAM call,
> avoiding disabling CFI checks for cpu_do_idle() and flush_cache_all().
> - Marked global pointers ccm_base and suspend_ocram_base as
> __ro_after_init to fully neutralize Write-What-Where exploit bypasses.
>
> arch/arm/mach-imx/pm-imx6.c | 13 +++++++++----
> 1 file changed, 9 insertions(+), 4 deletions(-)
>
> diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c
> index a671ca498..3d5b960c5 100644
> --- a/arch/arm/mach-imx/pm-imx6.c
> +++ b/arch/arm/mach-imx/pm-imx6.c
> @@ -61,9 +61,9 @@
> #define MX6Q_SUSPEND_OCRAM_SIZE 0x1000
> #define MX6_MAX_MMDC_IO_NUM 33
>
> -static void __iomem *ccm_base;
> -static void __iomem *suspend_ocram_base;
> -static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase);
> +static void __iomem *ccm_base __ro_after_init;
> +static void __iomem *suspend_ocram_base __ro_after_init;
> +static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase) __ro_after_init;
Are we able to just put __nocfi on the declaration of
`imx6_suspend_in_ocram_fn`, rather than bother with a wrapper
(imx6_suspend_in_ocram)? I don't know if that works, but surely you
can test that quickly?
>
> /*
> * suspend ocram space layout:
> @@ -360,6 +360,11 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode)
> return 0;
> }
>
> +static void __nocfi imx6_suspend_in_ocram(void __iomem *ocram_vbase)
> +{
> + imx6_suspend_in_ocram_fn(ocram_vbase);
> +}
> +
> static int imx6q_suspend_finish(unsigned long val)
> {
> if (!imx6_suspend_in_ocram_fn) {
> @@ -374,7 +379,7 @@ static int imx6q_suspend_finish(unsigned long val)
> if (!((struct imx6_cpu_pm_info *)
> suspend_ocram_base)->l2_base.vbase)
> flush_cache_all();
> - imx6_suspend_in_ocram_fn(suspend_ocram_base);
> + imx6_suspend_in_ocram(suspend_ocram_base);
> }
>
> return 0;
> --
> 2.55.0
>
--
Thanks,
~Nick Desaulniers
^ permalink raw reply
* Re: [PATCH v5 1/2] media: imx8-isi: crossbar: Add get_frame_desc operation
From: Laurent Pinchart @ 2026-07-20 16:53 UTC (permalink / raw)
To: Guoniu Zhou
Cc: Mauro Carvalho Chehab, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Aisheng Dong, linux-media,
imx, linux-arm-kernel, linux-kernel, Guoniu Zhou
In-Reply-To: <20260521-isi_vc-v5-1-a38eb4fcd58e@oss.nxp.com>
Hi Guoniu,
Thank you for the patch.
On Thu, May 21, 2026 at 05:10:04PM +0800, Guoniu Zhou wrote:
> From: "Guoniu.zhou" <guoniu.zhou@nxp.com>
>
> Implement the get_frame_desc pad operation for the crossbar subdev using
> the v4l2_subdev_get_frame_desc_passthrough() helper. This allows the
> crossbar to properly propagate frame descriptors from its sink pads to
> its source pads, which is necessary for proper stream configuration in
> multiplexed streams scenarios.
>
> Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> Changes in v5:
> - Use v4l2_subdev_get_frame_desc_passthrough helper
> - Rewrote commit message
>
> Changes in v4:
> - Use %d instead of %u for ret variable in error messages
> - Fix potential -ENOIOCTLCMD leak by resetting ret to 0 on continue
>
> Changes in v3:
> - New patch added based on feedback from Laurent Pinchart
> ---
> drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c
> index 605a45124103..0b593aed618b 100644
> --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c
> @@ -404,6 +404,7 @@ static const struct v4l2_subdev_pad_ops mxc_isi_crossbar_subdev_pad_ops = {
> .enum_mbus_code = mxc_isi_crossbar_enum_mbus_code,
> .get_fmt = v4l2_subdev_get_fmt,
> .set_fmt = mxc_isi_crossbar_set_fmt,
> + .get_frame_desc = v4l2_subdev_get_frame_desc_passthrough,
> .set_routing = mxc_isi_crossbar_set_routing,
> .enable_streams = mxc_isi_crossbar_enable_streams,
> .disable_streams = mxc_isi_crossbar_disable_streams,
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH v1] arm64: dts: imx943-evk: Remove 'supports-clkreq' from PCIe1
From: Frank.Li @ 2026-07-20 16:53 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, frank.li, s.hauer, festevam,
hongxing.zhu
Cc: Frank Li, kernel, devicetree, imx, linux-arm-kernel, linux-kernel,
Richard Zhu
In-Reply-To: <20260714040518.241871-1-hongxing.zhu@oss.nxp.com>
From: Frank Li <Frank.Li@nxp.com>
On Tue, 14 Jul 2026 12:05:18 +0800, hongxing.zhu@oss.nxp.com wrote:
> Remove the 'supports-clkreq' property from PCIe1 as the standard PCIe
> slot on i.MX943 EVK may not have CLKREQ# signal wired, causing
> compatibility issues with some PCIe cards.
Applied, thanks!
[1/1] arm64: dts: imx943-evk: Remove 'supports-clkreq' from PCIe1
commit: e0e1a9712fb1767dcf5ab69ae47dac2d1a080c3c
Best regards,
--
Frank Li <Frank.Li@nxp.com>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox