* Re: [PATCH 2/2] hwmon: raspberrypi: Add voltage input support
From: Guenter Roeck @ 2026-05-16 17:13 UTC (permalink / raw)
To: Shubham Chakraborty, Florian Fainelli,
Broadcom internal kernel review list, linux-hwmon,
linux-rpi-kernel, linux-arm-kernel, linux-kernel
In-Reply-To: <20260516164407.25255-2-chakrabortyshubham66@gmail.com>
On 5/16/26 09:44, Shubham Chakraborty wrote:
> Extend the raspberrypi-hwmon driver to expose firmware-provided
> voltage measurements through the hwmon subsystem.
>
> The driver now exports the following voltage inputs:
>
> - in0_input (core)
> - in1_input (sdram_c)
> - in2_input (sdram_i)
> - in3_input (sdram_p)
>
> Voltage values returned by firmware are converted from microvolts
> to millivolts as expected by the hwmon subsystem.
>
> The existing undervoltage sticky alarm handling is preserved and
> associated with the first voltage channel.
>
> Tested in -
> - Raspberry Pi 3b+ (Linux raspberrypi 6.12.75+rpt-rpi-v8 #1 SMP PREEMPT
> Debian 1:6.12.75-1+rpt1 (2026-03-11) aarch64 GNU/Linux)
>
> Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
I wasn't copied on patch 1/2, so I have no idea what is in there
and how it is related to this patch. Either way it seems unlikely that
the new functionality is supported by all versions of Raspberry Pi supported
by this driver. Just returning an error when the user tries to read a sensor
that is not supported is unacceptable. This needs either evidence that the
sensors are supported by all board variants and firmware versions supported
by this driver, or the is_visible function needs to selectively enable the
supported sensors.
> ---
> drivers/hwmon/raspberrypi-hwmon.c | 112 +++++++++++++++++++++++++++++-
Documentation/hwmon/raspberrypi-hwmon.rst needs to be updated as well.
> 1 file changed, 109 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c
> index a2938881ccd2..c73a970db025 100644
> --- a/drivers/hwmon/raspberrypi-hwmon.c
> +++ b/drivers/hwmon/raspberrypi-hwmon.c
> @@ -5,6 +5,7 @@
> * Based on firmware/raspberrypi.c by Noralf Trønnes
> *
> * Copyright (C) 2018 Stefan Wahren <stefan.wahren@i2se.com>
> + * Copyright (C) 2026 Shubham Chakraborty <chakrabortyshubham66@gmail.com>
> */
> #include <linux/device.h>
> #include <linux/devm-helpers.h>
> @@ -18,6 +19,11 @@
>
> #define UNDERVOLTAGE_STICKY_BIT BIT(16)
>
> +struct rpi_firmware_get_value {
> + __le32 id;
> + __le32 val;
> +} __packed;
> +
> struct rpi_hwmon_data {
> struct device *hwmon_dev;
> struct rpi_firmware *fw;
> @@ -56,6 +62,23 @@ static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
> hwmon_notify_event(data->hwmon_dev, hwmon_in, hwmon_in_lcrit_alarm, 0);
> }
>
> +static int rpi_firmware_get_voltage(struct rpi_hwmon_data *data, u32 id,
> + long *val)
> +{
> + struct rpi_firmware_get_value packet;
> + int ret;
> +
> + packet.id = cpu_to_le32(id);
> + packet.val = 0;
> + ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_VOLTAGE,
> + &packet, sizeof(packet));
> + if (ret)
> + return ret;
> +
> + *val = le32_to_cpu(packet.val) / 1000;
> + return 0;
> +}
> +
> static void get_values_poll(struct work_struct *work)
> {
> struct rpi_hwmon_data *data;
> @@ -77,19 +100,101 @@ static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
> {
> struct rpi_hwmon_data *data = dev_get_drvdata(dev);
>
> - *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
> + if (type == hwmon_in) {
> + switch (attr) {
> + case hwmon_in_input:
> + switch (channel) {
> + case 0:
> + return rpi_firmware_get_voltage(data,
> + RPI_FIRMWARE_VOLT_ID_CORE,
> + val);
> + case 1:
> + return rpi_firmware_get_voltage(data,
> + RPI_FIRMWARE_VOLT_ID_SDRAM_C,
> + val);
> + case 2:
> + return rpi_firmware_get_voltage(data,
> + RPI_FIRMWARE_VOLT_ID_SDRAM_I,
> + val);
> + case 3:
> + return rpi_firmware_get_voltage(data,
> + RPI_FIRMWARE_VOLT_ID_SDRAM_P,
> + val);
> + default:
> + return -EOPNOTSUPP;
> + }
> + case hwmon_in_lcrit_alarm:
> + if (channel == 0) {
> + *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
> + return 0;
> + }
> + return -EOPNOTSUPP;
> + default:
> + return -EOPNOTSUPP;
> + }
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static int rpi_read_string(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, const char **str)
> +{
> + if (type == hwmon_in && attr == hwmon_in_label) {
> + switch (channel) {
> + case 0:
> + *str = "core";
> + return 0;
> + case 1:
> + *str = "sdram_c";
> + return 0;
> + case 2:
> + *str = "sdram_i";
> + return 0;
> + case 3:
> + *str = "sdram_p";
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
This can be implemented as string array.
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + if (type == hwmon_in) {
> + switch (attr) {
> + case hwmon_in_input:
> + case hwmon_in_label:
> + return 0444;
> + case hwmon_in_lcrit_alarm:
> + if (channel == 0)
> + return 0444;
> + return 0;
> + default:
> + return 0;
> + }
> + }
> +
> return 0;
> }
>
> static const struct hwmon_channel_info * const rpi_info[] = {
> HWMON_CHANNEL_INFO(in,
> - HWMON_I_LCRIT_ALARM),
> + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT_ALARM,
> + HWMON_I_INPUT | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL),
> NULL
> };
>
> static const struct hwmon_ops rpi_hwmon_ops = {
> - .visible = 0444,
> + .is_visible = rpi_is_visible,
> .read = rpi_read,
> + .read_string = rpi_read_string,
> };
>
> static const struct hwmon_chip_info rpi_chip_info = {
> @@ -159,6 +264,7 @@ static struct platform_driver rpi_hwmon_driver = {
> module_platform_driver(rpi_hwmon_driver);
>
> MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
> +MODULE_AUTHOR("Shubham Chakraborty <chakrabortyshubham66@gmail.com>");
> MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver");
> MODULE_LICENSE("GPL v2");
> MODULE_ALIAS("platform:raspberrypi-hwmon");
^ permalink raw reply
* [PATCH v6 0/8] Add VIM4 MCU/FAN support
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau, Conor Dooley
The Khadas VIM4 board features a different MCU variant compared to
previous VIM boards.
While it shares the same I2C-based communication model,
it differs in some ways:
- A distinct register map with its own volatile/writeable register set
- A fan control with 0–100 levels instead of the 0–3 levels previously
- A fan power supply gated through a regulator
This series adds support for this new variant by:
1. Refactoring the khadas-mcu MFD driver to use per-variant data
structures (regmap config, cells, fan platform data),
and adding the khadas,vim4-mcu compatible string.
2. Extending the fan thermal driver to retrieve the fan register
and maximum level from platform_data,
and to optionally manage a power regulator for the fan supply.
3. Adding the corresponding DTS node for the VIM4, wiring the MCU to
the I2C AO_A bus and exposing it as a thermal cooling device.
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
Changes in v6:
- PATCH 4: Address Lee's review comments:
- Use an enum to discriminate between MCU types instead of passing
MFD data through the DT match table
- Fix error code from -EINVAL to -ENODEV when no MCU type is matched
- Make khadas_mcu_fan_cells and khadas_mcu_cells const
- Use dev_err_probe() for regmap initialization error
- Document fan speed levels for max_level
- Link to v5: https://lore.kernel.org/r/20260424-add-mcu-fan-khadas-vim4-v5-0-afcfa7157b23@aliel.fr
Changes in v5:
- PATCH 5: Replace devm_regulator_get_optional() with devm_regulator_get()
to simplify error handling and remove NULL checks, also
ordering as reverse christmas according to Neil's feedback.
- Link to v4: https://lore.kernel.org/r/20260421-add-mcu-fan-khadas-vim4-v4-0-447114a28f2d@aliel.fr
Changes in v4:
- PATCH 1: limit fan-supply property by compatible according to Conor's feedback.
- Link to v3: https://lore.kernel.org/r/20260417-add-mcu-fan-khadas-vim4-v3-0-a6a7f570b11b@aliel.fr
Changes in v3:
- PATCH 1: adding comment on vim4 compatible saying it is not discoverable,
thanks to Rob's and Neil's feedback.
- Link to v2: https://lore.kernel.org/r/20260403-add-mcu-fan-khadas-vim4-v2-0-70536b22439a@aliel.fr
Changes in v2:
- PATCH 5: Add regulator_disable on suspend thanks to Neil's feedback.
- Link to v1: https://lore.kernel.org/r/20260402-add-mcu-fan-khadas-vim4-v1-0-2b12eb4ac7b0@aliel.fr
---
Ronald Claveau (8):
dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU
dt-bindings: i2c: amlogic: Add compatible for T7 SOC
mfd: khadas-mcu: Add per-variant configuration infrastructure and VIM4 support
mfd: khadas-mcu: Add support for VIM4 MCU variant
thermal: khadas-mcu-fan: Add fan config from platform data Add regulator support
arm64: dts: amlogic: t7: Add i2c pinctrl node
arm64: dts: amlogic: t7: Add i2c controller node
arm64: dts: amlogic: t7: khadas-vim4: Add i2c MCU fan node
.../bindings/i2c/amlogic,meson6-i2c.yaml | 13 ++-
.../devicetree/bindings/mfd/khadas,mcu.yaml | 18 ++++
.../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 13 +++
arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 20 ++++
drivers/mfd/khadas-mcu.c | 119 ++++++++++++++++++---
drivers/thermal/khadas_mcu_fan.c | 37 +++++--
include/linux/mfd/khadas-mcu.h | 44 +++++++-
7 files changed, 236 insertions(+), 28 deletions(-)
---
base-commit: f7b64ed948718290209074a50bb0df17e5944873
change-id: 20260402-add-mcu-fan-khadas-vim4-ac1cbe553c9b
prerequisite-message-id: <20260326092645.1053261-1-jian.hu@amlogic.com>
prerequisite-patch-id: f03a086b4137158412b2d47b3de793b858de8dde
prerequisite-patch-id: 123970c9b29c2090440f2fd71c85d3c6fd8e36de
prerequisite-patch-id: 3e2e56b0926ba327b520f935df4ced5089bbe503
prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e
prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278
prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16
prerequisite-change-id: 20260326-add-bcm43752-compatible-e264a4f7973a:v2
prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4
prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b
prerequisite-change-id: 20260330-fix-invalid-property-bbe54d933f71:v2
prerequisite-patch-id: 8d675e7a239985c762843515b241f0a2f45f9c92
prerequisite-change-id: 20260331-fix-aml-t7-null-reset-2b608ebf9da4:v1
prerequisite-patch-id: 5b5de77af11747ce964404fb827d2ee2bff47ea5
prerequisite-patch-id: 1e37fc75fed1e533adee0f3e7e6ead1f8ff3c55c
prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e
prerequisite-patch-id: 2daf583fb5e7449a02bd217d8aca330171b598aa
prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278
prerequisite-patch-id: d1ddf9b7710e91f8062de83bd7ba55afb2c4c112
prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16
prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4
prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b
prerequisite-patch-id: 9debd88fa60febed9cd7208f86603b4c2d270520
prerequisite-patch-id: 314ef9ff0c4d1d15dab1dea9d92aa065f1eac3e9
Best regards,
--
Ronald Claveau <linux-kernel-dev@aliel.fr>
^ permalink raw reply
* [PATCH v6 2/8] dt-bindings: i2c: amlogic: Add compatible for T7 SOC
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau
In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add the T7 SOC compatible which fallback to AXG compatible.
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
.../devicetree/bindings/i2c/amlogic,meson6-i2c.yaml | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/Documentation/devicetree/bindings/i2c/amlogic,meson6-i2c.yaml b/Documentation/devicetree/bindings/i2c/amlogic,meson6-i2c.yaml
index c4cc8af182807..7b59b60b62e5b 100644
--- a/Documentation/devicetree/bindings/i2c/amlogic,meson6-i2c.yaml
+++ b/Documentation/devicetree/bindings/i2c/amlogic,meson6-i2c.yaml
@@ -16,10 +16,15 @@ allOf:
properties:
compatible:
- enum:
- - amlogic,meson6-i2c # Meson6, Meson8 and compatible SoCs
- - amlogic,meson-gxbb-i2c # GXBB and compatible SoCs
- - amlogic,meson-axg-i2c # AXG and compatible SoCs
+ oneOf:
+ - items:
+ - enum:
+ - amlogic,t7-i2c
+ - const: amlogic,meson-axg-i2c
+ - enum:
+ - amlogic,meson6-i2c # Meson6, Meson8 and compatible SoCs
+ - amlogic,meson-gxbb-i2c # GXBB and compatible SoCs
+ - amlogic,meson-axg-i2c # AXG and compatible SoCs
reg:
maxItems: 1
--
2.49.0
^ permalink raw reply related
* [PATCH v6 6/8] arm64: dts: amlogic: t7: Add i2c pinctrl node
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau
In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add the T7 pinctrl used by the Khadas VIM4 for MCU communication.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
index 7fe72c94ed623..e96fe10b251a0 100644
--- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
+++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
@@ -376,6 +376,16 @@ mux {
};
};
+ i2c0_ao_d_pins: i2c0-ao-d {
+ mux {
+ groups = "i2c0_ao_sck_d",
+ "i2c0_ao_sda_d";
+ function = "i2c0_ao";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
pwm_a_pins: pwm-a {
mux {
groups = "pwm_a";
--
2.49.0
^ permalink raw reply related
* [PATCH v6 1/8] dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau, Conor Dooley
In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
The Khadas VIM4 MCU register is slightly different
from previous boards' MCU.
This board also features a switchable power source for its fan.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
Documentation/devicetree/bindings/mfd/khadas,mcu.yaml | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml b/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
index 084960fd5a1fd..1f135618e3b6f 100644
--- a/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
+++ b/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
@@ -18,6 +18,7 @@ properties:
compatible:
enum:
- khadas,mcu # MCU revision is discoverable
+ - khadas,vim4-mcu # Different MCU variant, not discoverable
"#cooling-cells": # Only needed for boards having FAN control feature
const: 2
@@ -25,10 +26,27 @@ properties:
reg:
maxItems: 1
+ fan-supply:
+ description: Phandle to the regulator that powers the fan.
+ $ref: /schemas/types.yaml#/definitions/phandle
+
required:
- compatible
- reg
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: khadas,vim4-mcu
+ then:
+ required:
+ - fan-supply
+ else:
+ properties:
+ fan-supply: false
+
additionalProperties: false
examples:
--
2.49.0
^ permalink raw reply related
* [PATCH v6 3/8] mfd: khadas-mcu: Add per-variant configuration infrastructure and VIM4 support
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau
In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Introduce a per-variant configuration structure (khadas_mcu_data)
holding the regmap config and MFD cells,
selected at probe time via the of_device_id match data.
This makes adding other variants straightforward.
Add an enum khadas_mcu_type used as value to match.
Also introduce khadas_mcu_fan_pdata to pass fan register address and
maximum level to the fan sub-driver, removing the hardcoded constants.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
include/linux/mfd/khadas-mcu.h | 44 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 42 insertions(+), 2 deletions(-)
diff --git a/include/linux/mfd/khadas-mcu.h b/include/linux/mfd/khadas-mcu.h
index a99ba2ed0e4e0..88de49b78f5e6 100644
--- a/include/linux/mfd/khadas-mcu.h
+++ b/include/linux/mfd/khadas-mcu.h
@@ -70,6 +70,13 @@
#define KHADAS_MCU_WOL_INIT_START_REG 0x87 /* WO */
#define KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG 0x88 /* WO */
+/* VIM4 specific registers */
+#define KHADAS_MCU_VIM4_REST_CONF_REG 0x2c /* WO - reset EEPROM */
+#define KHADAS_MCU_VIM4_LED_ON_RAM_REG 0x89 /* WO - LED volatile */
+#define KHADAS_MCU_VIM4_FAN_CTRL_REG 0x8a /* WO */
+#define KHADAS_MCU_VIM4_WDT_EN_REG 0x8b /* WO */
+#define KHADAS_MCU_VIM4_SYS_RST_REG 0x91 /* WO */
+
enum {
KHADAS_BOARD_VIM1 = 0x1,
KHADAS_BOARD_VIM2,
@@ -82,10 +89,43 @@ enum {
* struct khadas_mcu - Khadas MCU structure
* @device: device reference used for logs
* @regmap: register map
+ * @data: pointer to variant-specific config
*/
struct khadas_mcu {
- struct device *dev;
- struct regmap *regmap;
+ struct device *dev;
+ struct regmap *regmap;
+ const struct khadas_mcu_data *data;
+};
+
+/**
+ * struct khadas_mcu_data - per-variant configuration
+ * @regmap_config: regmap configuration
+ * @cells: MFD sub-devices
+ * @ncells: number of sub-devices
+ * @fan_cells: MFD fan sub-devices
+ * @nfan_cells: number of fan sub-devices
+ */
+struct khadas_mcu_data {
+ const struct regmap_config *regmap_config;
+ const struct mfd_cell *cells;
+ int ncells;
+ const struct mfd_cell *fan_cells;
+ int nfan_cells;
+};
+
+/**
+ * struct khadas_mcu_fan_pdata - fan sub-driver configuration
+ * @fan_reg: register address to write the fan level
+ * @max_level: maximum fan level
+ */
+struct khadas_mcu_fan_pdata {
+ unsigned int fan_reg;
+ unsigned int max_level;
+};
+
+enum khadas_mcu_type {
+ KHADAS_MCU_GENERIC, /* VIM1/2/3, Edge, Edge-V */
+ KHADAS_MCU_VIM4,
};
#endif /* MFD_KHADAS_MCU_H */
--
2.49.0
^ permalink raw reply related
* [PATCH v6 5/8] thermal: khadas-mcu-fan: Add fan config from platform data Add regulator support
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau
In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Replace the hardcoded MAX_LEVEL constant and fan register
with values read from platform_data (fan_reg, max_level),
as new MCUs need different values.
Optionally acquire and enable a "fan" regulator supply
at probe time and on resume,
so boards that gate fan power through a regulator are handled.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
drivers/thermal/khadas_mcu_fan.c | 37 ++++++++++++++++++++++++++++++-------
1 file changed, 30 insertions(+), 7 deletions(-)
diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c
index d35e5313bea41..5603fa099a858 100644
--- a/drivers/thermal/khadas_mcu_fan.c
+++ b/drivers/thermal/khadas_mcu_fan.c
@@ -13,13 +13,15 @@
#include <linux/regmap.h>
#include <linux/sysfs.h>
#include <linux/thermal.h>
-
-#define MAX_LEVEL 3
+#include <linux/regulator/consumer.h>
struct khadas_mcu_fan_ctx {
struct khadas_mcu *mcu;
+ unsigned int fan_reg;
unsigned int level;
+ unsigned int max_level;
struct thermal_cooling_device *cdev;
+ struct regulator *power;
};
static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
@@ -27,8 +29,7 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
{
int ret;
- ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
- level);
+ ret = regmap_write(ctx->mcu->regmap, ctx->fan_reg, level);
if (ret)
return ret;
@@ -40,7 +41,9 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
- *state = MAX_LEVEL;
+ struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+ *state = ctx->max_level;
return 0;
}
@@ -61,7 +64,7 @@ khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
{
struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
- if (state > MAX_LEVEL)
+ if (state > ctx->max_level)
return -EINVAL;
if (state == ctx->level)
@@ -78,6 +81,7 @@ static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
static int khadas_mcu_fan_probe(struct platform_device *pdev)
{
+ const struct khadas_mcu_fan_pdata *pdata = dev_get_platdata(&pdev->dev);
struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
struct thermal_cooling_device *cdev;
struct device *dev = &pdev->dev;
@@ -87,7 +91,21 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev)
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
+
ctx->mcu = mcu;
+ ctx->fan_reg = pdata->fan_reg;
+ ctx->max_level = pdata->max_level;
+
+ ctx->power = devm_regulator_get(dev->parent, "fan");
+ if (IS_ERR(ctx->power))
+ return PTR_ERR(ctx->power);
+
+ ret = regulator_enable(ctx->power);
+ if (ret) {
+ dev_err(dev, "Failed to enable fan power supply: %d\n", ret);
+ return ret;
+ }
+
platform_set_drvdata(pdev, ctx);
cdev = devm_thermal_of_cooling_device_register(dev->parent,
@@ -124,12 +142,17 @@ static int khadas_mcu_fan_suspend(struct device *dev)
ctx->level = level_save;
- return 0;
+ return regulator_disable(ctx->power);
}
static int khadas_mcu_fan_resume(struct device *dev)
{
struct khadas_mcu_fan_ctx *ctx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_enable(ctx->power);
+ if (ret)
+ return ret;
return khadas_mcu_fan_set_level(ctx, ctx->level);
}
--
2.49.0
^ permalink raw reply related
* [PATCH v6 8/8] arm64: dts: amlogic: t7: khadas-vim4: Add i2c MCU fan node
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau
In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Enable and configure i2c MCU node to get fan working on Khadas VIM4.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
.../boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts
index 69d6118ba57e7..5d7f5390f3a66 100644
--- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts
+++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts
@@ -157,6 +157,19 @@ wifi32k: wifi32k {
};
};
+&i2c_m_ao_a {
+ status = "okay";
+ pinctrl-0 = <&i2c0_ao_d_pins>;
+ pinctrl-names = "default";
+
+ khadas_mcu: system-controller@18 {
+ compatible = "khadas,vim4-mcu";
+ reg = <0x18>;
+ fan-supply = <&vcc5v>;
+ #cooling-cells = <2>;
+ };
+};
+
&pwm_ab {
status = "okay";
pinctrl-0 = <&pwm_a_pins>;
--
2.49.0
^ permalink raw reply related
* [PATCH v6 4/8] mfd: khadas-mcu: Add support for VIM4 MCU variant
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau
In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Refactor probe() to use per-variant khadas_mcu_data
instead of hardcoded globals.
Add dedicated regmap configuration and device data for the VIM4 MCU,
with its own volatile/writeable registers.
Add the fan control register
(0–100 levels vs 0–3 for previous supported boards).
Add a new compatible string "khadas,vim4-mcu".
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
drivers/mfd/khadas-mcu.c | 119 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 104 insertions(+), 15 deletions(-)
diff --git a/drivers/mfd/khadas-mcu.c b/drivers/mfd/khadas-mcu.c
index ba981a7886921..7bc538232a445 100644
--- a/drivers/mfd/khadas-mcu.c
+++ b/drivers/mfd/khadas-mcu.c
@@ -75,15 +75,91 @@ static const struct regmap_config khadas_mcu_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
-static struct mfd_cell khadas_mcu_fan_cells[] = {
+static const struct khadas_mcu_fan_pdata khadas_mcu_fan_pdata = {
+ .fan_reg = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
+ .max_level = 3, /* Fan speed: 0 = off, 1 = low, 2 = medium, 3 = high */
+};
+
+static const struct mfd_cell khadas_mcu_fan_cells[] = {
/* VIM1/2 Rev13+ and VIM3 only */
- { .name = "khadas-mcu-fan-ctrl", },
+ {
+ .name = "khadas-mcu-fan-ctrl",
+ .platform_data = &khadas_mcu_fan_pdata,
+ .pdata_size = sizeof(khadas_mcu_fan_pdata),
+ },
};
-static struct mfd_cell khadas_mcu_cells[] = {
+static const struct mfd_cell khadas_mcu_cells[] = {
{ .name = "khadas-mcu-user-mem", },
};
+static const struct khadas_mcu_data khadas_mcu_data = {
+ .regmap_config = &khadas_mcu_regmap_config,
+ .cells = khadas_mcu_cells,
+ .ncells = ARRAY_SIZE(khadas_mcu_cells),
+ .fan_cells = khadas_mcu_fan_cells,
+ .nfan_cells = ARRAY_SIZE(khadas_mcu_fan_cells),
+};
+
+static bool khadas_mcu_vim4_reg_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case KHADAS_MCU_PWR_OFF_CMD_REG:
+ case KHADAS_MCU_VIM4_REST_CONF_REG:
+ case KHADAS_MCU_WOL_INIT_START_REG:
+ case KHADAS_MCU_VIM4_LED_ON_RAM_REG:
+ case KHADAS_MCU_VIM4_FAN_CTRL_REG:
+ case KHADAS_MCU_VIM4_WDT_EN_REG:
+ case KHADAS_MCU_VIM4_SYS_RST_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool khadas_mcu_vim4_reg_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case KHADAS_MCU_VERSION_0_REG:
+ case KHADAS_MCU_VERSION_1_REG:
+ case KHADAS_MCU_SHUTDOWN_NORMAL_STATUS_REG:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config khadas_mcu_vim4_regmap_config = {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .max_register = KHADAS_MCU_VIM4_SYS_RST_REG,
+ .volatile_reg = khadas_mcu_vim4_reg_volatile,
+ .writeable_reg = khadas_mcu_vim4_reg_writeable,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static const struct khadas_mcu_fan_pdata khadas_vim4_fan_pdata = {
+ .fan_reg = KHADAS_MCU_VIM4_FAN_CTRL_REG,
+ .max_level = 0x64,
+};
+
+static const struct mfd_cell khadas_mcu_vim4_cells[] = {
+ {
+ .name = "khadas-mcu-fan-ctrl",
+ .platform_data = &khadas_vim4_fan_pdata,
+ .pdata_size = sizeof(khadas_vim4_fan_pdata),
+ },
+};
+
+static const struct khadas_mcu_data khadas_vim4_mcu_data = {
+ .regmap_config = &khadas_mcu_vim4_regmap_config,
+ .cells = NULL,
+ .ncells = 0,
+ .fan_cells = khadas_mcu_vim4_cells,
+ .nfan_cells = ARRAY_SIZE(khadas_mcu_vim4_cells),
+};
+
static int khadas_mcu_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -94,28 +170,40 @@ static int khadas_mcu_probe(struct i2c_client *client)
if (!ddata)
return -ENOMEM;
+ switch ((uintptr_t)i2c_get_match_data(client)) {
+ case KHADAS_MCU_GENERIC:
+ ddata->data = &khadas_mcu_data;
+ break;
+ case KHADAS_MCU_VIM4:
+ ddata->data = &khadas_vim4_mcu_data;
+ break;
+ default:
+ return -ENODEV;
+ }
+
i2c_set_clientdata(client, ddata);
ddata->dev = dev;
- ddata->regmap = devm_regmap_init_i2c(client, &khadas_mcu_regmap_config);
+ ddata->regmap = devm_regmap_init_i2c(client, ddata->data->regmap_config);
if (IS_ERR(ddata->regmap)) {
ret = PTR_ERR(ddata->regmap);
- dev_err(dev, "Failed to allocate register map: %d\n", ret);
- return ret;
+ return dev_err_probe(dev, ret, "Failed to allocate register map\n");
}
- ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
- khadas_mcu_cells,
- ARRAY_SIZE(khadas_mcu_cells),
- NULL, 0, NULL);
- if (ret)
- return ret;
+ if (ddata->data->cells && ddata->data->ncells) {
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+ ddata->data->cells,
+ ddata->data->ncells,
+ NULL, 0, NULL);
+ if (ret)
+ return ret;
+ }
if (of_property_present(dev->of_node, "#cooling-cells"))
return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
- khadas_mcu_fan_cells,
- ARRAY_SIZE(khadas_mcu_fan_cells),
+ ddata->data->fan_cells,
+ ddata->data->nfan_cells,
NULL, 0, NULL);
return 0;
@@ -123,7 +211,8 @@ static int khadas_mcu_probe(struct i2c_client *client)
#ifdef CONFIG_OF
static const struct of_device_id khadas_mcu_of_match[] = {
- { .compatible = "khadas,mcu", },
+ { .compatible = "khadas,mcu", .data = (void *)KHADAS_MCU_GENERIC },
+ { .compatible = "khadas,vim4-mcu", .data = (void *)KHADAS_MCU_VIM4 },
{},
};
MODULE_DEVICE_TABLE(of, khadas_mcu_of_match);
--
2.49.0
^ permalink raw reply related
* [PATCH v6 7/8] arm64: dts: amlogic: t7: Add i2c controller node
From: Ronald Claveau via B4 Relay @ 2026-05-16 17:17 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Beniamino Galvani, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Liam Girdwood, Mark Brown
Cc: linux-amlogic, devicetree, linux-kernel, linux-i2c,
linux-arm-kernel, linux-pm, Ronald Claveau
In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add the T7 i2c controller node used by the Khadas VIM4
for MCU communication.
Use amlogic,meson-axg-i2c as fallback compatible.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
index e96fe10b251a0..560c9dce35266 100644
--- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
+++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
@@ -711,6 +711,16 @@ pwm_ao_cd: pwm@60000 {
status = "disabled";
};
+ i2c_m_ao_a: i2c@76000 {
+ compatible = "amlogic,t7-i2c", "amlogic,meson-axg-i2c";
+ reg = <0x0 0x76000 0x0 0x48>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 330 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc_periphs CLKID_SYS_I2C_AO_A>;
+ status = "disabled";
+ };
+
sd_emmc_a: mmc@88000 {
compatible = "amlogic,t7-mmc", "amlogic,meson-axg-mmc";
reg = <0x0 0x88000 0x0 0x800>;
--
2.49.0
^ permalink raw reply related
* Re: [PATCH 2/2] hwmon: raspberrypi: Add voltage input support
From: Guenter Roeck @ 2026-05-16 18:24 UTC (permalink / raw)
To: Shubham Chakraborty, Florian Fainelli,
Broadcom internal kernel review list, linux-hwmon,
linux-rpi-kernel, linux-arm-kernel, linux-kernel
In-Reply-To: <ff7bf6f8-0090-4aea-ba60-3477cc81a458@roeck-us.net>
On 5/16/26 10:13, Guenter Roeck wrote:
> On 5/16/26 09:44, Shubham Chakraborty wrote:
>> Extend the raspberrypi-hwmon driver to expose firmware-provided
>> voltage measurements through the hwmon subsystem.
>>
>> The driver now exports the following voltage inputs:
>>
>> - in0_input (core)
>> - in1_input (sdram_c)
>> - in2_input (sdram_i)
>> - in3_input (sdram_p)
>>
>> Voltage values returned by firmware are converted from microvolts
>> to millivolts as expected by the hwmon subsystem.
>>
>> The existing undervoltage sticky alarm handling is preserved and
>> associated with the first voltage channel.
>>
>> Tested in -
>> - Raspberry Pi 3b+ (Linux raspberrypi 6.12.75+rpt-rpi-v8 #1 SMP PREEMPT
>> Debian 1:6.12.75-1+rpt1 (2026-03-11) aarch64 GNU/Linux)
>>
>> Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
>
> I wasn't copied on patch 1/2, so I have no idea what is in there
> and how it is related to this patch. Either way it seems unlikely that
> the new functionality is supported by all versions of Raspberry Pi supported
> by this driver. Just returning an error when the user tries to read a sensor
> that is not supported is unacceptable. This needs either evidence that the
> sensors are supported by all board variants and firmware versions supported
> by this driver, or the is_visible function needs to selectively enable the
> supported sensors.
>
>> ---
>> drivers/hwmon/raspberrypi-hwmon.c | 112 +++++++++++++++++++++++++++++-
>
> Documentation/hwmon/raspberrypi-hwmon.rst needs to be updated as well.
>
>> 1 file changed, 109 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c
>> index a2938881ccd2..c73a970db025 100644
>> --- a/drivers/hwmon/raspberrypi-hwmon.c
>> +++ b/drivers/hwmon/raspberrypi-hwmon.c
>> @@ -5,6 +5,7 @@
>> * Based on firmware/raspberrypi.c by Noralf Trønnes
>> *
>> * Copyright (C) 2018 Stefan Wahren <stefan.wahren@i2se.com>
>> + * Copyright (C) 2026 Shubham Chakraborty <chakrabortyshubham66@gmail.com>
>> */
>> #include <linux/device.h>
>> #include <linux/devm-helpers.h>
>> @@ -18,6 +19,11 @@
>> #define UNDERVOLTAGE_STICKY_BIT BIT(16)
>> +struct rpi_firmware_get_value {
>> + __le32 id;
>> + __le32 val;
>> +} __packed;
>> +
More feedback:
Why define this here but RPI_FIRMWARE_VOLT_ID_CORE etc. in the
firmware API include file ? struct rpi_firmware_clk_rate_request
is quite similar and defined (and documented) in the include file.
Also, the name is quite generic, suggesting a common structure.
rpi_firmware_clk_rate_request, as used for the clock, is much more
specific. I would suggest to either use a single structure for all
requests or clarify that this is for voltages only (i.e., name the
structure and the returned value appropriately).
Last but not least, please send both patches to the same set of people
and mailing lists.
Thanks,
Guenter
>> struct rpi_hwmon_data {
>> struct device *hwmon_dev;
>> struct rpi_firmware *fw;
>> @@ -56,6 +62,23 @@ static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
>> hwmon_notify_event(data->hwmon_dev, hwmon_in, hwmon_in_lcrit_alarm, 0);
>> }
>> +static int rpi_firmware_get_voltage(struct rpi_hwmon_data *data, u32 id,
>> + long *val)
>> +{
>> + struct rpi_firmware_get_value packet;
>> + int ret;
>> +
>> + packet.id = cpu_to_le32(id);
>> + packet.val = 0;
>> + ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_VOLTAGE,
>> + &packet, sizeof(packet));
>> + if (ret)
>> + return ret;
>> +
>> + *val = le32_to_cpu(packet.val) / 1000;
>> + return 0;
>> +}
>> +
>> static void get_values_poll(struct work_struct *work)
>> {
>> struct rpi_hwmon_data *data;
>> @@ -77,19 +100,101 @@ static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
>> {
>> struct rpi_hwmon_data *data = dev_get_drvdata(dev);
>> - *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
>> + if (type == hwmon_in) {
>> + switch (attr) {
>> + case hwmon_in_input:
>> + switch (channel) {
>> + case 0:
>> + return rpi_firmware_get_voltage(data,
>> + RPI_FIRMWARE_VOLT_ID_CORE,
>> + val);
>> + case 1:
>> + return rpi_firmware_get_voltage(data,
>> + RPI_FIRMWARE_VOLT_ID_SDRAM_C,
>> + val);
>> + case 2:
>> + return rpi_firmware_get_voltage(data,
>> + RPI_FIRMWARE_VOLT_ID_SDRAM_I,
>> + val);
>> + case 3:
>> + return rpi_firmware_get_voltage(data,
>> + RPI_FIRMWARE_VOLT_ID_SDRAM_P,
>> + val);
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> + case hwmon_in_lcrit_alarm:
>> + if (channel == 0) {
>> + *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
>> + return 0;
>> + }
>> + return -EOPNOTSUPP;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> + }
>> +
>> + return -EOPNOTSUPP;
>> +}
>> +
>> +static int rpi_read_string(struct device *dev, enum hwmon_sensor_types type,
>> + u32 attr, int channel, const char **str)
>> +{
>> + if (type == hwmon_in && attr == hwmon_in_label) {
>> + switch (channel) {
>> + case 0:
>> + *str = "core";
>> + return 0;
>> + case 1:
>> + *str = "sdram_c";
>> + return 0;
>> + case 2:
>> + *str = "sdram_i";
>> + return 0;
>> + case 3:
>> + *str = "sdram_p";
>> + return 0;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>
> This can be implemented as string array.
>
>> + }
>> +
>> + return -EOPNOTSUPP;
>> +}
>> +
>> +static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type,
>> + u32 attr, int channel)
>> +{
>> + if (type == hwmon_in) {
>> + switch (attr) {
>> + case hwmon_in_input:
>> + case hwmon_in_label:
>> + return 0444;
>> + case hwmon_in_lcrit_alarm:
>> + if (channel == 0)
>> + return 0444;
>> + return 0;
>> + default:
>> + return 0;
>> + }
>> + }
>> +
>> return 0;
>> }
>> static const struct hwmon_channel_info * const rpi_info[] = {
>> HWMON_CHANNEL_INFO(in,
>> - HWMON_I_LCRIT_ALARM),
>> + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT_ALARM,
>> + HWMON_I_INPUT | HWMON_I_LABEL,
>> + HWMON_I_INPUT | HWMON_I_LABEL,
>> + HWMON_I_INPUT | HWMON_I_LABEL),
>> NULL
>> };
>> static const struct hwmon_ops rpi_hwmon_ops = {
>> - .visible = 0444,
>> + .is_visible = rpi_is_visible,
>> .read = rpi_read,
>> + .read_string = rpi_read_string,
>> };
>> static const struct hwmon_chip_info rpi_chip_info = {
>> @@ -159,6 +264,7 @@ static struct platform_driver rpi_hwmon_driver = {
>> module_platform_driver(rpi_hwmon_driver);
>> MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
>> +MODULE_AUTHOR("Shubham Chakraborty <chakrabortyshubham66@gmail.com>");
>> MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver");
>> MODULE_LICENSE("GPL v2");
>> MODULE_ALIAS("platform:raspberrypi-hwmon");
>
>
^ permalink raw reply
* [PATCH v3 1/9] KVM: arm64: selftests: Add GPR save/restore functions for NV
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
Adapt entry.S and hyp-entry.S from arch/arm64/kvm/hyp so that guest
hypervisors can save and restore GPRs, and provide exception handlers
to regain control after the nested guest exits. Other system register
save/restore will be added later on demand.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 3 +
.../selftests/kvm/include/arm64/nested.h | 45 ++++++
tools/testing/selftests/kvm/lib/arm64/entry.S | 132 ++++++++++++++++++
.../selftests/kvm/lib/arm64/hyp-entry.S | 77 ++++++++++
.../testing/selftests/kvm/lib/arm64/nested.c | 12 ++
5 files changed, 269 insertions(+)
create mode 100644 tools/testing/selftests/kvm/include/arm64/nested.h
create mode 100644 tools/testing/selftests/kvm/lib/arm64/entry.S
create mode 100644 tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
create mode 100644 tools/testing/selftests/kvm/lib/arm64/nested.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 98da9fa4b8b7..3dc3e39f7025 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -30,10 +30,13 @@ LIBKVM_x86 += lib/x86/svm.c
LIBKVM_x86 += lib/x86/ucall.c
LIBKVM_x86 += lib/x86/vmx.c
+LIBKVM_arm64 += lib/arm64/entry.S
LIBKVM_arm64 += lib/arm64/gic.c
LIBKVM_arm64 += lib/arm64/gic_v3.c
LIBKVM_arm64 += lib/arm64/gic_v3_its.c
LIBKVM_arm64 += lib/arm64/handlers.S
+LIBKVM_arm64 += lib/arm64/hyp-entry.S
+LIBKVM_arm64 += lib/arm64/nested.c
LIBKVM_arm64 += lib/arm64/processor.c
LIBKVM_arm64 += lib/arm64/spinlock.c
LIBKVM_arm64 += lib/arm64/ucall.c
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
new file mode 100644
index 000000000000..86d931facacb
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ARM64 Nested virtualization defines
+ */
+
+#ifndef SELFTEST_KVM_NESTED_H
+#define SELFTEST_KVM_NESTED_H
+
+#define ARM_EXCEPTION_IRQ 0
+#define ARM_EXCEPTION_EL1_SERROR 1
+#define ARM_EXCEPTION_TRAP 2
+#define ARM_EXCEPTION_IL 3
+#define ARM_EXCEPTION_EL2_IRQ 4
+#define ARM_EXCEPTION_EL2_SERROR 5
+#define ARM_EXCEPTION_EL2_TRAP 6
+
+#ifndef __ASSEMBLER__
+
+#include <asm/ptrace.h>
+#include "kvm_util.h"
+
+extern char hyp_vectors[];
+
+struct cpu_context {
+ struct user_pt_regs regs; /* sp = sp_el0 */
+};
+
+struct vcpu {
+ struct cpu_context context;
+};
+
+/*
+ * KVM has host_data and hyp_context, combine them because we're only doing
+ * hyp context.
+ */
+struct hyp_data {
+ struct cpu_context hyp_context;
+};
+
+u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
+void __hyp_exception(u64 type);
+
+#endif /* !__ASSEMBLER__ */
+
+#endif /* SELFTEST_KVM_NESTED_H */
diff --git a/tools/testing/selftests/kvm/lib/arm64/entry.S b/tools/testing/selftests/kvm/lib/arm64/entry.S
new file mode 100644
index 000000000000..33bedf5e7fb2
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/arm64/entry.S
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * adapted from arch/arm64/kvm/hyp/entry.S
+ */
+
+/*
+ * Manually define these for now
+ */
+// offsetof(struct vcpu, context)
+#define CPU_CONTEXT 0
+// offsetof(struct cpu_context, regs)
+#define CPU_USER_PT_REGS 0
+
+#define CPU_XREG_OFFSET(x) (CPU_USER_PT_REGS + 8*x)
+#define CPU_LR_OFFSET CPU_XREG_OFFSET(30)
+#define CPU_SP_EL0_OFFSET (CPU_LR_OFFSET + 8)
+
+.macro save_callee_saved_regs ctxt
+ str x18, [\ctxt, #CPU_XREG_OFFSET(18)]
+ stp x19, x20, [\ctxt, #CPU_XREG_OFFSET(19)]
+ stp x21, x22, [\ctxt, #CPU_XREG_OFFSET(21)]
+ stp x23, x24, [\ctxt, #CPU_XREG_OFFSET(23)]
+ stp x25, x26, [\ctxt, #CPU_XREG_OFFSET(25)]
+ stp x27, x28, [\ctxt, #CPU_XREG_OFFSET(27)]
+ stp x29, lr, [\ctxt, #CPU_XREG_OFFSET(29)]
+.endm
+
+.macro restore_callee_saved_regs ctxt
+ ldr x18, [\ctxt, #CPU_XREG_OFFSET(18)]
+ ldp x19, x20, [\ctxt, #CPU_XREG_OFFSET(19)]
+ ldp x21, x22, [\ctxt, #CPU_XREG_OFFSET(21)]
+ ldp x23, x24, [\ctxt, #CPU_XREG_OFFSET(23)]
+ ldp x25, x26, [\ctxt, #CPU_XREG_OFFSET(25)]
+ ldp x27, x28, [\ctxt, #CPU_XREG_OFFSET(27)]
+ ldp x29, lr, [\ctxt, #CPU_XREG_OFFSET(29)]
+.endm
+
+.macro save_sp_el0 ctxt, tmp
+ mrs \tmp, sp_el0
+ str \tmp, [\ctxt, #CPU_SP_EL0_OFFSET]
+.endm
+
+.macro restore_sp_el0 ctxt, tmp
+ ldr \tmp, [\ctxt, #CPU_SP_EL0_OFFSET]
+ msr sp_el0, \tmp
+.endm
+
+/*
+ * u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
+ */
+.globl __guest_enter
+__guest_enter:
+ // x0: vcpu
+ // x1: hyp context
+
+ // Store vcpu and hyp context pointer on the stack
+ stp x0, x1, [sp, #-16]!
+
+ // Store the hyp regs
+ save_callee_saved_regs x1
+
+ // Save hyp's sp_el0
+ save_sp_el0 x1, x2
+
+ // x29 = vCPU user pt regs
+ add x29, x0, #CPU_CONTEXT
+
+ // Restore the guest's sp_el0
+ restore_sp_el0 x29, x0
+
+ // Restore guest regs x0-x17
+ ldp x0, x1, [x29, #CPU_XREG_OFFSET(0)]
+ ldp x2, x3, [x29, #CPU_XREG_OFFSET(2)]
+ ldp x4, x5, [x29, #CPU_XREG_OFFSET(4)]
+ ldp x6, x7, [x29, #CPU_XREG_OFFSET(6)]
+ ldp x8, x9, [x29, #CPU_XREG_OFFSET(8)]
+ ldp x10, x11, [x29, #CPU_XREG_OFFSET(10)]
+ ldp x12, x13, [x29, #CPU_XREG_OFFSET(12)]
+ ldp x14, x15, [x29, #CPU_XREG_OFFSET(14)]
+ ldp x16, x17, [x29, #CPU_XREG_OFFSET(16)]
+
+ // Restore guest regs x18-x29, lr
+ restore_callee_saved_regs x29
+
+ // Do not touch any register after this!
+ eret
+
+.globl __guest_exit
+__guest_exit:
+ // x0: return code
+ // x1: vcpu
+ // x2-x29,lr: vcpu regs
+ // vcpu x0-x1 on the stack
+
+ add x1, x1, #CPU_CONTEXT
+
+ // Store the guest regs x2 and x3
+ stp x2, x3, [x1, #CPU_XREG_OFFSET(2)]
+
+ // Retrieve the guest regs x0-x1 from the stack
+ ldp x2, x3, [sp], #16 // x0, x1
+
+ // Store the guest regs x0-x1 and x4-x17
+ stp x2, x3, [x1, #CPU_XREG_OFFSET(0)]
+ stp x4, x5, [x1, #CPU_XREG_OFFSET(4)]
+ stp x6, x7, [x1, #CPU_XREG_OFFSET(6)]
+ stp x8, x9, [x1, #CPU_XREG_OFFSET(8)]
+ stp x10, x11, [x1, #CPU_XREG_OFFSET(10)]
+ stp x12, x13, [x1, #CPU_XREG_OFFSET(12)]
+ stp x14, x15, [x1, #CPU_XREG_OFFSET(14)]
+ stp x16, x17, [x1, #CPU_XREG_OFFSET(16)]
+
+ // Store the guest regs x18-x29, lr
+ save_callee_saved_regs x1
+
+ // Store the guest's sp_el0
+ save_sp_el0 x1, x2
+
+ // At this point x0 and x1 on the stack is popped, so next is vCPU
+ // pointer, then hyp_context pointer
+ // *sp == vCPU, *(sp + 8) == hyp_context
+ // load x2 = hyp_context, x3 is just for ldp and popping sp
+ ldp x3, x2, [sp], #16
+
+ // Restore hyp's sp_el0
+ restore_sp_el0 x2, x3
+
+ // Now restore the hyp regs
+ restore_callee_saved_regs x2
+
+ dsb sy // Synchronize against in-flight ld/st
+ ret
diff --git a/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S b/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
new file mode 100644
index 000000000000..6341f6e05c90
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * adapted from arch/arm64/kvm/hyp/hyp-entry.S
+ */
+
+#include "nested.h"
+
+// skip over x0, x1 saved on entry, must be used only before the stack is modified
+.macro get_vcpu_ptr vcpu
+ ldr \vcpu, [sp, #16]
+.endm
+
+ .text
+
+el1_sync: // Guest trapped into EL2
+
+ get_vcpu_ptr x1
+ mov x0, #ARM_EXCEPTION_TRAP
+ b __guest_exit
+
+el1_irq:
+el1_fiq:
+ get_vcpu_ptr x1
+ mov x0, #ARM_EXCEPTION_IRQ
+ b __guest_exit
+
+el1_error:
+ get_vcpu_ptr x1
+ mov x0, #ARM_EXCEPTION_EL1_SERROR
+ b __guest_exit
+
+el2_sync:
+ mov x0, #ARM_EXCEPTION_EL2_TRAP
+ b __hyp_exception
+
+el2_irq:
+el2_fiq:
+ mov x0, #ARM_EXCEPTION_EL2_IRQ
+ b __hyp_exception
+
+el2_error:
+ mov x0, #ARM_EXCEPTION_EL2_SERROR
+ b __hyp_exception
+
+
+ .ltorg
+
+ .align 11
+
+.globl hyp_vectors
+hyp_vectors:
+
+.macro exception_vector target
+ .align 7
+ stp x0, x1, [sp, #-16]!
+ b \target
+.endm
+
+ exception_vector el2_sync // Synchronous EL2t
+ exception_vector el2_irq // IRQ EL2t
+ exception_vector el2_fiq // FIQ EL2t
+ exception_vector el2_error // Error EL2t
+
+ exception_vector el2_sync // Synchronous EL2h
+ exception_vector el2_irq // IRQ EL2h
+ exception_vector el2_fiq // FIQ EL2h
+ exception_vector el2_error // Error EL2h
+
+ exception_vector el1_sync // Synchronous 64-bit EL1
+ exception_vector el1_irq // IRQ 64-bit EL1
+ exception_vector el1_fiq // FIQ 64-bit EL1
+ exception_vector el1_error // Error 64-bit EL1
+
+ exception_vector el1_sync // Synchronous 32-bit EL1
+ exception_vector el1_irq // IRQ 32-bit EL1
+ exception_vector el1_fiq // FIQ 32-bit EL1
+ exception_vector el1_error // Error 32-bit EL1
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
new file mode 100644
index 000000000000..06ddaab2436f
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ARM64 Nested virtualization helpers
+ */
+
+#include "nested.h"
+#include "test_util.h"
+
+void __hyp_exception(u64 type)
+{
+ GUEST_FAIL("Unexpected hyp exception! type: %lx\n", type);
+}
--
2.43.0
^ permalink raw reply related
* [PATCH v3 2/9] KVM: arm64: selftests: Add helpers for guest hypervisors
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
Add helpers so that guest hypervisors can run nested guests. SP_EL1
save/restore is added to allow nested guests to use a stack.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
.../selftests/kvm/include/arm64/nested.h | 17 +++++++
tools/testing/selftests/kvm/lib/arm64/entry.S | 5 ++
.../testing/selftests/kvm/lib/arm64/nested.c | 46 +++++++++++++++++++
3 files changed, 68 insertions(+)
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index 86d931facacb..30e626e427da 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -21,8 +21,17 @@
extern char hyp_vectors[];
+enum vcpu_sysreg {
+ __INVALID_SYSREG__, /* 0 is reserved as an invalid value */
+
+ SP_EL1,
+
+ NR_SYS_REGS
+};
+
struct cpu_context {
struct user_pt_regs regs; /* sp = sp_el0 */
+ u64 sys_regs[NR_SYS_REGS];
};
struct vcpu {
@@ -37,9 +46,17 @@ struct hyp_data {
struct cpu_context hyp_context;
};
+void prepare_hyp(void);
+void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
+int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
+
+void do_hvc(void);
u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
void __hyp_exception(u64 type);
+void __sysreg_save_el1_state(struct cpu_context *ctxt);
+void __sysreg_restore_el1_state(struct cpu_context *ctxt);
+
#endif /* !__ASSEMBLER__ */
#endif /* SELFTEST_KVM_NESTED_H */
diff --git a/tools/testing/selftests/kvm/lib/arm64/entry.S b/tools/testing/selftests/kvm/lib/arm64/entry.S
index 33bedf5e7fb2..df3af3463c6c 100644
--- a/tools/testing/selftests/kvm/lib/arm64/entry.S
+++ b/tools/testing/selftests/kvm/lib/arm64/entry.S
@@ -3,6 +3,11 @@
* adapted from arch/arm64/kvm/hyp/entry.S
*/
+ .globl do_hvc
+ do_hvc:
+ hvc #0
+ ret
+
/*
* Manually define these for now
*/
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
index 06ddaab2436f..f6c24beb01d0 100644
--- a/tools/testing/selftests/kvm/lib/arm64/nested.c
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -4,7 +4,53 @@
*/
#include "nested.h"
+#include "processor.h"
#include "test_util.h"
+#include <asm/sysreg.h>
+
+void prepare_hyp(void)
+{
+ write_sysreg(HCR_EL2_E2H | HCR_EL2_RW, hcr_el2);
+ write_sysreg(hyp_vectors, vbar_el2);
+ isb();
+}
+
+void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top)
+{
+ memset(vcpu, 0, sizeof(*vcpu));
+ vcpu->context.regs.pc = l2_pc;
+ vcpu->context.regs.pstate = PSR_MODE_EL1h | PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT;
+ vcpu->context.sys_regs[SP_EL1] = l2_stack_top;
+}
+
+void __sysreg_save_el1_state(struct cpu_context *ctxt)
+{
+ ctxt->sys_regs[SP_EL1] = read_sysreg(sp_el1);
+}
+
+void __sysreg_restore_el1_state(struct cpu_context *ctxt)
+{
+ write_sysreg(ctxt->sys_regs[SP_EL1], sp_el1);
+}
+
+int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data)
+{
+ u64 ret;
+
+ __sysreg_restore_el1_state(&vcpu->context);
+
+ write_sysreg(vcpu->context.regs.pstate, spsr_el2);
+ write_sysreg(vcpu->context.regs.pc, elr_el2);
+
+ ret = __guest_enter(vcpu, &hyp_data->hyp_context);
+
+ vcpu->context.regs.pc = read_sysreg(elr_el2);
+ vcpu->context.regs.pstate = read_sysreg(spsr_el2);
+
+ __sysreg_save_el1_state(&vcpu->context);
+
+ return ret;
+}
void __hyp_exception(u64 type)
{
--
2.43.0
^ permalink raw reply related
* [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
Hi,
This is v3 of adding basic support for running nested guests (L2) in
kselftest. This time a framework for enabling stage-2 in the guest is
added, including the s2_mmu struct, s2 translation control setup, and
a stage-2 page table generator. Similar to L2 vCPU management, all
stage-2 work is done in the guest instead of userspace.
An additional shadow_stage2 test is added which leverages the framework
to run L2 with stage-2 enabled.
* Changes from v2 [1]:
- Update vm_paddr_t to gpa_t, vm_vaddr_t to gva_t.
- Added framework for enabling stage-2 in the guest.
- Added shadow_stage2 test.
Thanks!
[1]: https://lore.kernel.org/kvmarm/20260412142216.3806482-1-weilin.chang@arm.com/
Wei-Lin Chang (9):
KVM: arm64: selftests: Add GPR save/restore functions for NV
KVM: arm64: selftests: Add helpers for guest hypervisors
KVM: arm64: selftests: Add hello_nested basic NV selftest
KVM: arm64: selftests: Enhance hello_nested test
KVM: arm64: selftests: Add shadow_stage2 test
KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated
pool
KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule
size
KVM: arm64: selftests: Add infrastructure for using stage-2 in guest
KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for
the nested guest
tools/testing/selftests/kvm/Makefile.kvm | 5 +
.../selftests/kvm/arm64/hello_nested.c | 134 +++++++++
.../selftests/kvm/arm64/shadow_stage2.c | 165 +++++++++++
.../selftests/kvm/include/arm64/nested.h | 85 ++++++
tools/testing/selftests/kvm/lib/arm64/entry.S | 137 +++++++++
.../selftests/kvm/lib/arm64/hyp-entry.S | 77 +++++
.../testing/selftests/kvm/lib/arm64/nested.c | 264 ++++++++++++++++++
7 files changed, 867 insertions(+)
create mode 100644 tools/testing/selftests/kvm/arm64/hello_nested.c
create mode 100644 tools/testing/selftests/kvm/arm64/shadow_stage2.c
create mode 100644 tools/testing/selftests/kvm/include/arm64/nested.h
create mode 100644 tools/testing/selftests/kvm/lib/arm64/entry.S
create mode 100644 tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
create mode 100644 tools/testing/selftests/kvm/lib/arm64/nested.c
--
2.43.0
^ permalink raw reply
* [PATCH v3 4/9] KVM: arm64: selftests: Enhance hello_nested test
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
Handle an "add" hypercall in L1 to add 2 numbers passed by L2, and
return the result. This better tests our save/restore functionality.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
.../selftests/kvm/arm64/hello_nested.c | 32 ++++++++++++++++++-
.../selftests/kvm/include/arm64/nested.h | 2 +-
2 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/hello_nested.c b/tools/testing/selftests/kvm/arm64/hello_nested.c
index 1cab56e4597b..9ed5285f5f2d 100644
--- a/tools/testing/selftests/kvm/arm64/hello_nested.c
+++ b/tools/testing/selftests/kvm/arm64/hello_nested.c
@@ -11,6 +11,10 @@
#define XLATE2GPA (0xABCD)
#define L2STACKSZ (0x100)
+#define L2SUCCESS (0x0)
+#define L2FAILED (0x1)
+#define L2ADD (0x2)
+
/*
* TPIDR_EL2 is used to store vcpu id, so save and restore it.
*/
@@ -31,7 +35,14 @@ static gpa_t ucall_translate_to_gpa(void *gva)
static void l2_guest_code(void)
{
- do_hvc();
+ int ans = 0;
+
+ ans = do_hvc(L2ADD, 2, 3);
+
+ if (ans == 5)
+ do_hvc(L2SUCCESS, 0, 0);
+ else
+ do_hvc(L2FAILED, 0, 0);
}
static void guest_code(void)
@@ -42,6 +53,7 @@ static void guest_code(void)
gpa_t l2_pc, l2_stack_top;
/* force 16-byte alignment for the stack pointer */
u8 l2_stack[L2STACKSZ] __attribute__((aligned(16)));
+ u64 arg1, arg2;
GUEST_ASSERT_EQ(get_current_el(), 2);
GUEST_PRINTF("vEL2 entry\n");
@@ -55,6 +67,24 @@ static void guest_code(void)
ret = run_l2(&vcpu, &hyp_data);
GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
GUEST_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el2)), ESR_ELx_EC_HVC64);
+
+ if (vcpu.context.regs.regs[0] == L2ADD) {
+ arg1 = vcpu.context.regs.regs[1];
+ arg2 = vcpu.context.regs.regs[2];
+ GUEST_PRINTF("L2 add request, arg1: %lx, arg2: %lx\n", arg1, arg2);
+ vcpu.context.regs.regs[0] = arg1 + arg2;
+ } else {
+ GUEST_FAIL("Unexpected hvc action\n");
+ }
+
+ ret = run_l2(&vcpu, &hyp_data);
+ GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
+ GUEST_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el2)), ESR_ELx_EC_HVC64);
+
+ if (vcpu.context.regs.regs[0] != L2SUCCESS)
+ GUEST_FAIL("L2 failed\n");
+
+ GUEST_PRINTF("L2 success!\n");
GUEST_DONE();
}
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index 30e626e427da..c10ef4a85be7 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -50,7 +50,7 @@ void prepare_hyp(void);
void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
-void do_hvc(void);
+u64 do_hvc(u64 action, u64 arg1, u64 arg2);
u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
void __hyp_exception(u64 type);
--
2.43.0
^ permalink raw reply related
* [PATCH v3 3/9] KVM: arm64: selftests: Add hello_nested basic NV selftest
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
This selftest simply starts an L1, which starts its own guest (L2). L2
runs without stage-1 and 2 translations, it calls an HVC to jump back
to L1.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/arm64/hello_nested.c | 104 ++++++++++++++++++
2 files changed, 105 insertions(+)
create mode 100644 tools/testing/selftests/kvm/arm64/hello_nested.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 3dc3e39f7025..e8c108e0c487 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -168,6 +168,7 @@ TEST_GEN_PROGS_arm64 += arm64/arch_timer_edge_cases
TEST_GEN_PROGS_arm64 += arm64/at
TEST_GEN_PROGS_arm64 += arm64/debug-exceptions
TEST_GEN_PROGS_arm64 += arm64/hello_el2
+TEST_GEN_PROGS_arm64 += arm64/hello_nested
TEST_GEN_PROGS_arm64 += arm64/host_sve
TEST_GEN_PROGS_arm64 += arm64/hypercalls
TEST_GEN_PROGS_arm64 += arm64/external_aborts
diff --git a/tools/testing/selftests/kvm/arm64/hello_nested.c b/tools/testing/selftests/kvm/arm64/hello_nested.c
new file mode 100644
index 000000000000..1cab56e4597b
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/hello_nested.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * hello_nested - Go from vEL2 to EL1 then back
+ */
+
+#include "nested.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall.h"
+
+#define XLATE2GPA (0xABCD)
+#define L2STACKSZ (0x100)
+
+/*
+ * TPIDR_EL2 is used to store vcpu id, so save and restore it.
+ */
+static gpa_t ucall_translate_to_gpa(void *gva)
+{
+ gpa_t gpa;
+ u64 vcpu_id = read_sysreg(tpidr_el2);
+
+ GUEST_SYNC2(XLATE2GPA, gva);
+
+ /* get the result from userspace */
+ gpa = read_sysreg(tpidr_el2);
+
+ write_sysreg(vcpu_id, tpidr_el2);
+
+ return gpa;
+}
+
+static void l2_guest_code(void)
+{
+ do_hvc();
+}
+
+static void guest_code(void)
+{
+ struct vcpu vcpu;
+ struct hyp_data hyp_data;
+ int ret;
+ gpa_t l2_pc, l2_stack_top;
+ /* force 16-byte alignment for the stack pointer */
+ u8 l2_stack[L2STACKSZ] __attribute__((aligned(16)));
+
+ GUEST_ASSERT_EQ(get_current_el(), 2);
+ GUEST_PRINTF("vEL2 entry\n");
+
+ l2_pc = ucall_translate_to_gpa(l2_guest_code);
+ l2_stack_top = ucall_translate_to_gpa(&l2_stack[L2STACKSZ]);
+
+ init_vcpu(&vcpu, l2_pc, l2_stack_top);
+ prepare_hyp();
+
+ ret = run_l2(&vcpu, &hyp_data);
+ GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
+ GUEST_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el2)), ESR_ELx_EC_HVC64);
+ GUEST_DONE();
+}
+
+int main(void)
+{
+ struct kvm_vcpu_init init;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+ gpa_t gpa;
+
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
+ vm = vm_create(1);
+
+ kvm_get_default_vcpu_target(vm, &init);
+ init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
+ vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ while (true) {
+ vcpu_run(vcpu);
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ if (uc.args[0] == XLATE2GPA) {
+ gpa = addr_gva2gpa(vm, (gva_t)uc.args[1]);
+ vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_TPIDR_EL2), gpa);
+ }
+ break;
+ case UCALL_PRINTF:
+ pr_info("%s", uc.buffer);
+ break;
+ case UCALL_DONE:
+ pr_info("DONE!\n");
+ goto end;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ fallthrough;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld\n", uc.cmd);
+ }
+ }
+
+end:
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.43.0
^ permalink raw reply related
* [PATCH v3 5/9] KVM: arm64: selftests: Add shadow_stage2 test
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
The shadow_stage2 test is aimed to exercise the shadow page table
management code in KVM. In this first patch a basic test similar to
hello_nested is created. Right now it doesn't turn on stage-2 for the
nested guest (L2) yet, therefore the shadow page table code in KVM will
only be triggered minimally now.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/arm64/shadow_stage2.c | 128 ++++++++++++++++++
2 files changed, 129 insertions(+)
create mode 100644 tools/testing/selftests/kvm/arm64/shadow_stage2.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index e8c108e0c487..c0fac2ba1339 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -176,6 +176,7 @@ TEST_GEN_PROGS_arm64 += arm64/page_fault_test
TEST_GEN_PROGS_arm64 += arm64/psci_test
TEST_GEN_PROGS_arm64 += arm64/sea_to_user
TEST_GEN_PROGS_arm64 += arm64/set_id_regs
+TEST_GEN_PROGS_arm64 += arm64/shadow_stage2
TEST_GEN_PROGS_arm64 += arm64/smccc_filter
TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config
TEST_GEN_PROGS_arm64 += arm64/vgic_init
diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
new file mode 100644
index 000000000000..cf76a2b0582d
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * shadow_stage2 - Test correctness of shadow stage 2
+ */
+
+#include "nested.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall.h"
+
+#define XLATE2GPA (0xABCD)
+#define L2STACKSZ (0x100)
+
+#define L2SUCCESS (0x0)
+#define L2FAILED (0x1)
+#define L2SYNC (0x2)
+
+/*
+ * TPIDR_EL2 is used to store vcpu id, so save and restore it.
+ */
+static gpa_t ucall_translate_to_gpa(void *gva)
+{
+ gpa_t gpa;
+ u64 vcpu_id = read_sysreg(tpidr_el2);
+
+ GUEST_SYNC2(XLATE2GPA, gva);
+
+ /* get the result from userspace */
+ gpa = read_sysreg(tpidr_el2);
+
+ write_sysreg(vcpu_id, tpidr_el2);
+
+ return gpa;
+}
+
+static void l2_guest_code(void)
+{
+ do_hvc(L2SYNC, 10, 0);
+ do_hvc(L2SYNC, 20, 0);
+ do_hvc(L2SYNC, 30, 0);
+
+ do_hvc(L2SUCCESS, 0, 0);
+}
+
+static void guest_code(void)
+{
+ struct vcpu vcpu;
+ struct hyp_data hyp_data;
+ int ret, i = 0;
+ gpa_t l2_pc, l2_stack_top;
+ /* force 16-byte alignment for the stack pointer */
+ u8 l2_stack[L2STACKSZ] __attribute__((aligned(16)));
+
+ GUEST_ASSERT_EQ(get_current_el(), 2);
+ GUEST_PRINTF("vEL2 entry\n");
+
+ l2_pc = ucall_translate_to_gpa(l2_guest_code);
+ l2_stack_top = ucall_translate_to_gpa(&l2_stack[L2STACKSZ]);
+
+ init_vcpu(&vcpu, l2_pc, l2_stack_top);
+ prepare_hyp();
+
+ while (true) {
+ GUEST_PRINTF("L2 enter\n");
+ ret = run_l2(&vcpu, &hyp_data);
+ GUEST_PRINTF("L2 exit\n");
+ GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
+ GUEST_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el2)), ESR_ELx_EC_HVC64);
+
+ if (vcpu.context.regs.regs[0] == L2SYNC)
+ GUEST_SYNC3(L2SYNC, i++, vcpu.context.regs.regs[1]);
+ else
+ break;
+ }
+
+ if (vcpu.context.regs.regs[0] != L2SUCCESS)
+ GUEST_FAIL("L2 failed\n");
+
+ GUEST_PRINTF("L2 success!\n");
+ GUEST_DONE();
+}
+
+int main(void)
+{
+ struct kvm_vcpu_init init;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+ gpa_t gpa;
+
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
+ vm = vm_create(1);
+
+ kvm_get_default_vcpu_target(vm, &init);
+ init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
+ vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ while (true) {
+ vcpu_run(vcpu);
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ if (uc.args[0] == XLATE2GPA) {
+ gpa = addr_gva2gpa(vm, (gva_t)uc.args[1]);
+ vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_TPIDR_EL2), gpa);
+ }
+ if (uc.args[0] == L2SYNC)
+ pr_info("L2SYNC, L1 info: %ld, L2 info: %ld\n", uc.args[1], uc.args[2]);
+ break;
+ case UCALL_PRINTF:
+ pr_info("[L1] %s", uc.buffer);
+ break;
+ case UCALL_DONE:
+ pr_info("DONE!\n");
+ goto end;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ fallthrough;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld\n", uc.cmd);
+ }
+ }
+
+end:
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.43.0
^ permalink raw reply related
* [PATCH v3 6/9] KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated pool
From: Wei-Lin Chang @ 2026-05-16 18:30 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
Instead of using L1's stack, create a simple page allocator and use that
to allocate L2 stack. The allocator will also be used later on when the
stage-2 page table generator builds stage-2 mappings for the nested
guest (L2).
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
.../selftests/kvm/arm64/shadow_stage2.c | 20 ++++++++---
.../selftests/kvm/include/arm64/nested.h | 9 +++++
.../testing/selftests/kvm/lib/arm64/nested.c | 33 +++++++++++++++++++
3 files changed, 58 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index cf76a2b0582d..1ad510a38654 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -9,12 +9,16 @@
#include "ucall.h"
#define XLATE2GPA (0xABCD)
-#define L2STACKSZ (0x100)
#define L2SUCCESS (0x0)
#define L2FAILED (0x1)
#define L2SYNC (0x2)
+/* Used for L2 stack and guest S2 page tables. */
+#define L2_PAGE_POOL_ADDR (0x80000000)
+#define L2_PAGE_POOL_NPAGES (512)
+#define L2_PAGE_POOL_MEMSLOT (0x2)
+
/*
* TPIDR_EL2 is used to store vcpu id, so save and restore it.
*/
@@ -48,14 +52,18 @@ static void guest_code(void)
struct hyp_data hyp_data;
int ret, i = 0;
gpa_t l2_pc, l2_stack_top;
- /* force 16-byte alignment for the stack pointer */
- u8 l2_stack[L2STACKSZ] __attribute__((aligned(16)));
+ struct page_pool pp;
GUEST_ASSERT_EQ(get_current_el(), 2);
GUEST_PRINTF("vEL2 entry\n");
+ pp.start = L2_PAGE_POOL_ADDR;
+ pp.npages = L2_PAGE_POOL_NPAGES;
+ pp.current = L2_PAGE_POOL_ADDR;
+ pp.page_size = get_page_size();
+
+ l2_stack_top = alloc_page(&pp) + pp.page_size;
l2_pc = ucall_translate_to_gpa(l2_guest_code);
- l2_stack_top = ucall_translate_to_gpa(&l2_stack[L2STACKSZ]);
init_vcpu(&vcpu, l2_pc, l2_stack_top);
prepare_hyp();
@@ -96,6 +104,10 @@ int main(void)
vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
kvm_arch_vm_finalize_vcpus(vm);
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+ L2_PAGE_POOL_ADDR, L2_PAGE_POOL_MEMSLOT,
+ L2_PAGE_POOL_NPAGES, 0);
+
while (true) {
vcpu_run(vcpu);
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index c10ef4a85be7..8e7d7738b381 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -46,6 +46,15 @@ struct hyp_data {
struct cpu_context hyp_context;
};
+struct page_pool {
+ gpa_t start;
+ gpa_t current;
+ size_t npages;
+ size_t page_size;
+};
+
+size_t get_page_size(void);
+gpa_t alloc_page(struct page_pool *pp);
void prepare_hyp(void);
void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
index f6c24beb01d0..7f47e340f00d 100644
--- a/tools/testing/selftests/kvm/lib/arm64/nested.c
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -7,6 +7,39 @@
#include "processor.h"
#include "test_util.h"
#include <asm/sysreg.h>
+#include <linux/sizes.h>
+
+size_t get_page_size(void)
+{
+ u64 tcr_el1 = read_sysreg(tcr_el1);
+ u64 tg0 = SYS_FIELD_GET(TCR_EL1, TG0, tcr_el1);
+
+ switch (tg0) {
+ case TCR_EL1_TG0_4K:
+ return SZ_4K;
+ case TCR_EL1_TG0_16K:
+ return SZ_16K;
+ case TCR_EL1_TG0_64K:
+ return SZ_64K;
+ default:
+ GUEST_FAIL("Unexpected tg0 value!\n");
+ return 0;
+ }
+}
+
+gpa_t alloc_page(struct page_pool *pp)
+{
+ gpa_t page = pp->current;
+
+ pp->current += pp->page_size;
+
+ if ((pp->current - pp->start) / pp->page_size <= pp->npages) {
+ return page;
+ } else {
+ GUEST_FAIL("%s failed!\n", __func__);
+ return 0;
+ }
+}
void prepare_hyp(void)
{
--
2.43.0
^ permalink raw reply related
* [PATCH v3 7/9] KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule size
From: Wei-Lin Chang @ 2026-05-16 18:30 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
When selecting a granule size for stage-2, the supported stage-2 granule
size must be checked. Add the check. For simplicity, we check whether
the guest stage-1 granule size is supported for stage-2, and skip the
test otherwise.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
.../selftests/kvm/arm64/shadow_stage2.c | 8 +++++
.../selftests/kvm/include/arm64/nested.h | 1 +
.../testing/selftests/kvm/lib/arm64/nested.c | 30 +++++++++++++++++++
3 files changed, 39 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index 1ad510a38654..c5332b8b5683 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -14,6 +14,8 @@
#define L2FAILED (0x1)
#define L2SYNC (0x2)
+#define TGRAN2NOSUP (0x3)
+
/* Used for L2 stack and guest S2 page tables. */
#define L2_PAGE_POOL_ADDR (0x80000000)
#define L2_PAGE_POOL_NPAGES (512)
@@ -53,6 +55,7 @@ static void guest_code(void)
int ret, i = 0;
gpa_t l2_pc, l2_stack_top;
struct page_pool pp;
+ u64 mmfr0 = read_sysreg(id_aa64mmfr0_el1);
GUEST_ASSERT_EQ(get_current_el(), 2);
GUEST_PRINTF("vEL2 entry\n");
@@ -62,6 +65,9 @@ static void guest_code(void)
pp.current = L2_PAGE_POOL_ADDR;
pp.page_size = get_page_size();
+ if (!has_tgran_2(mmfr0, pp.page_size))
+ GUEST_SYNC1(TGRAN2NOSUP);
+
l2_stack_top = alloc_page(&pp) + pp.page_size;
l2_pc = ucall_translate_to_gpa(l2_guest_code);
@@ -119,6 +125,8 @@ int main(void)
}
if (uc.args[0] == L2SYNC)
pr_info("L2SYNC, L1 info: %ld, L2 info: %ld\n", uc.args[1], uc.args[2]);
+ if (uc.args[0] == TGRAN2NOSUP)
+ ksft_exit_skip("Guest page size not supported as guest stage-2 page size!\n");
break;
case UCALL_PRINTF:
pr_info("[L1] %s", uc.buffer);
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index 8e7d7738b381..fc59fabff12d 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -55,6 +55,7 @@ struct page_pool {
size_t get_page_size(void);
gpa_t alloc_page(struct page_pool *pp);
+bool has_tgran_2(u64 mmfr0, size_t size);
void prepare_hyp(void);
void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
index 7f47e340f00d..cda41f355263 100644
--- a/tools/testing/selftests/kvm/lib/arm64/nested.c
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -9,6 +9,36 @@
#include <asm/sysreg.h>
#include <linux/sizes.h>
+#define _has_tgran_2(__r, __sz) \
+ ({ \
+ u64 _s1, _s2, _mmfr0 = __r; \
+ \
+ _s2 = SYS_FIELD_GET(ID_AA64MMFR0_EL1, \
+ TGRAN##__sz##_2, _mmfr0); \
+ \
+ _s1 = SYS_FIELD_GET(ID_AA64MMFR0_EL1, \
+ TGRAN##__sz, _mmfr0); \
+ \
+ ((_s2 != ID_AA64MMFR0_EL1_TGRAN##__sz##_2_NI && \
+ _s2 != ID_AA64MMFR0_EL1_TGRAN##__sz##_2_TGRAN##__sz) || \
+ (_s2 == ID_AA64MMFR0_EL1_TGRAN##__sz##_2_TGRAN##__sz && \
+ _s1 != ID_AA64MMFR0_EL1_TGRAN##__sz##_NI)); \
+ })
+
+bool has_tgran_2(u64 mmfr0, size_t size)
+{
+ switch (size) {
+ case SZ_4K:
+ return _has_tgran_2(mmfr0, 4);
+ case SZ_16K:
+ return _has_tgran_2(mmfr0, 16);
+ case SZ_64K:
+ return _has_tgran_2(mmfr0, 64);
+ default:
+ return false;
+ }
+}
+
size_t get_page_size(void)
{
u64 tcr_el1 = read_sysreg(tcr_el1);
--
2.43.0
^ permalink raw reply related
* [PATCH v3 8/9] KVM: arm64: selftests: Add infrastructure for using stage-2 in guest
From: Wei-Lin Chang @ 2026-05-16 18:30 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
Add a stage-2 page table generator, the s2_mmu structure, and vEL2
stage-2 preparation code for a guest hypervisor to turn on stage-2
translation for its nested guest.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
.../selftests/kvm/arm64/hello_nested.c | 2 +-
.../selftests/kvm/arm64/shadow_stage2.c | 2 +-
.../selftests/kvm/include/arm64/nested.h | 15 +-
.../testing/selftests/kvm/lib/arm64/nested.c | 145 +++++++++++++++++-
4 files changed, 160 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/hello_nested.c b/tools/testing/selftests/kvm/arm64/hello_nested.c
index 9ed5285f5f2d..b57e41c73214 100644
--- a/tools/testing/selftests/kvm/arm64/hello_nested.c
+++ b/tools/testing/selftests/kvm/arm64/hello_nested.c
@@ -62,7 +62,7 @@ static void guest_code(void)
l2_stack_top = ucall_translate_to_gpa(&l2_stack[L2STACKSZ]);
init_vcpu(&vcpu, l2_pc, l2_stack_top);
- prepare_hyp();
+ prepare_hyp_no_s2();
ret = run_l2(&vcpu, &hyp_data);
GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index c5332b8b5683..2b274b810dcf 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -72,7 +72,7 @@ static void guest_code(void)
l2_pc = ucall_translate_to_gpa(l2_guest_code);
init_vcpu(&vcpu, l2_pc, l2_stack_top);
- prepare_hyp();
+ prepare_hyp_no_s2();
while (true) {
GUEST_PRINTF("L2 enter\n");
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index fc59fabff12d..1bcbb31b8d67 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -38,6 +38,14 @@ struct vcpu {
struct cpu_context context;
};
+struct s2_mmu {
+ gpa_t pgd;
+ unsigned int vmid;
+ unsigned int page_size_shift;
+ u64 vtcr;
+ u64 ipa_bits;
+};
+
/*
* KVM has host_data and hyp_context, combine them because we're only doing
* hyp context.
@@ -56,8 +64,13 @@ struct page_pool {
size_t get_page_size(void);
gpa_t alloc_page(struct page_pool *pp);
bool has_tgran_2(u64 mmfr0, size_t size);
-void prepare_hyp(void);
+void prepare_hyp_no_s2(void);
+void prepare_hyp(struct s2_mmu *mmu);
void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
+void create_s2_mapping(struct s2_mmu *mmu, u64 ipa, u64 pa, size_t size,
+ struct page_pool *pp);
+void init_s2_mmu(struct s2_mmu *mmu, unsigned int vmid, gpa_t pgd,
+ size_t page_size, u64 ipa_bits);
int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
u64 do_hvc(u64 action, u64 arg1, u64 arg2);
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
index cda41f355263..9848d607ef64 100644
--- a/tools/testing/selftests/kvm/lib/arm64/nested.c
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -71,13 +71,22 @@ gpa_t alloc_page(struct page_pool *pp)
}
}
-void prepare_hyp(void)
+void prepare_hyp_no_s2(void)
{
write_sysreg(HCR_EL2_E2H | HCR_EL2_RW, hcr_el2);
write_sysreg(hyp_vectors, vbar_el2);
isb();
}
+void prepare_hyp(struct s2_mmu *mmu)
+{
+ write_sysreg(mmu->vtcr, vtcr_el2);
+ write_sysreg(mmu->pgd | ((u64)mmu->vmid << 48), vttbr_el2);
+ write_sysreg(HCR_EL2_E2H | HCR_EL2_RW | HCR_EL2_VM, hcr_el2);
+ write_sysreg(hyp_vectors, vbar_el2);
+ isb();
+}
+
void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top)
{
memset(vcpu, 0, sizeof(*vcpu));
@@ -86,6 +95,140 @@ void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top)
vcpu->context.sys_regs[SP_EL1] = l2_stack_top;
}
+static int stage2_levels(unsigned int page_size_shift, u64 ipa_bits)
+{
+ /* taken from ARM64_HW_PGTABLE_LEVELS(ipa) in KVM */
+ return (ipa_bits - 4) / (page_size_shift - 3);
+}
+
+static u64 get_index(struct s2_mmu *mmu, u64 ipa, int level)
+{
+ int width = mmu->page_size_shift - 3;
+ int shift_amount = mmu->page_size_shift + (3 - level) * width;
+
+ return (ipa >> shift_amount) & GENMASK_ULL(width - 1, 0);
+}
+
+static u64 pte_gpa_to_gva(u64 gpa)
+{
+ /*
+ * This depends on how the memory used for s2pt is mapped in GVA,
+ * currently it is assumed they are idmapped.
+ */
+ return gpa;
+}
+
+static u64 pte_to_pt_base(u64 pte)
+{
+ return pte & GENMASK_ULL(47, 12);
+}
+
+#define S2_PTE_AF (1ULL << 10)
+#define S2_PTE_SH_INNER (3ULL << 8)
+#define S2_PTE_S2AP_RW (3ULL << 6)
+#define S2_PTE_ATTR_NORMAL_WB (0xfULL << 2)
+#define S2_PTE_TYPE_TABLE (1ULL << 1)
+#define S2_PTE_TYPE_PAGE (1ULL << 1)
+#define S2_PTE_VALID 1ULL
+
+/* No block mappings for now. */
+static void create_one_s2_mapping(struct s2_mmu *mmu, u64 ipa, u64 pa,
+ struct page_pool *pp)
+{
+ int levels = stage2_levels(mmu->page_size_shift, mmu->ipa_bits);
+ u64 index, pte, pte_new, table_attr, page_attr;
+ gpa_t pte_addr, pt_base = mmu->pgd;
+
+ table_attr = S2_PTE_TYPE_TABLE | S2_PTE_VALID;
+ page_attr = S2_PTE_AF | S2_PTE_SH_INNER | S2_PTE_S2AP_RW |
+ S2_PTE_ATTR_NORMAL_WB | S2_PTE_TYPE_PAGE | S2_PTE_VALID;
+
+ for (int level = 4 - levels; level <= 3; level++) {
+ index = get_index(mmu, ipa, level);
+ pte_addr = pt_base + index * 8;
+ pte = *((u64 *)pte_gpa_to_gva(pte_addr));
+
+ if (level == 3) {
+ /* Last level, install leaf entry. */
+ pte_new = pa & ~GENMASK_ULL(mmu->page_size_shift - 1, 0);
+ pte_new |= page_attr;
+ *((u64 *)pte_gpa_to_gva(pte_addr)) = pte_new;
+ } else if (!(pte & S2_PTE_VALID)) {
+ /* Empty next level table, allocate and install. */
+ pte_new = alloc_page(pp);
+ pte_new |= table_attr;
+ *((u64 *)pte_gpa_to_gva(pte_addr)) = pte_new;
+ pt_base = pte_to_pt_base(pte_new);
+ } else {
+ /* Next level table found, descend into it. */
+ pt_base = pte_to_pt_base(pte);
+ }
+ }
+}
+
+void create_s2_mapping(struct s2_mmu *mmu, u64 ipa, u64 pa, size_t size,
+ struct page_pool *pp)
+{
+ u64 ipa_end;
+ u64 mask = pp->page_size - 1;
+
+ ipa_end = (ipa + size + mask) & ~mask;
+ ipa &= ~mask;
+ pa &= ~mask;
+
+ while (ipa < ipa_end) {
+ create_one_s2_mapping(mmu, ipa, pa, pp);
+ pa += pp->page_size;
+ ipa += pp->page_size;
+ }
+ dsb(ishst);
+}
+
+void init_s2_mmu(struct s2_mmu *mmu, unsigned int vmid, gpa_t pgd,
+ size_t page_size, u64 ipa_bits)
+{
+ u64 ps, tg0, sl0_base, mmfr0 = read_sysreg(id_aa64mmfr0_el1);
+ int levels;
+
+ mmu->vmid = vmid;
+ mmu->pgd = pgd;
+ mmu->ipa_bits = ipa_bits;
+ mmu->vtcr = 0;
+
+ switch (page_size) {
+ case SZ_4K:
+ tg0 = VTCR_EL2_TG0_4K;
+ mmu->page_size_shift = 12;
+ sl0_base = 2;
+ break;
+ case SZ_16K:
+ tg0 = VTCR_EL2_TG0_16K;
+ mmu->page_size_shift = 14;
+ sl0_base = 3;
+ break;
+ case SZ_64K:
+ default:
+ tg0 = VTCR_EL2_TG0_64K;
+ mmu->page_size_shift = 16;
+ sl0_base = 3;
+ break;
+ }
+
+ levels = stage2_levels(mmu->page_size_shift, mmu->ipa_bits);
+ mmu->vtcr |= FIELD_PREP(VTCR_EL2_SL0, (sl0_base - (4 - levels)));
+
+ ps = SYS_FIELD_GET(ID_AA64MMFR0_EL1, PARANGE, mmfr0);
+ /* cap ps to 48-bit */
+ ps = ps > 0b0101 ? 0b0101 : ps;
+ mmu->vtcr |= VTCR_EL2_RES1 | SYS_FIELD_PREP(VTCR_EL2, PS, ps) |
+ SYS_FIELD_PREP(VTCR_EL2, TG0, tg0) |
+ SYS_FIELD_PREP_ENUM(VTCR_EL2, SH0, INNER) |
+ SYS_FIELD_PREP_ENUM(VTCR_EL2, ORGN0, WBWA) |
+ SYS_FIELD_PREP_ENUM(VTCR_EL2, IRGN0, WBWA);
+
+ mmu->vtcr |= FIELD_PREP(VTCR_EL2_T0SZ, 64 - ipa_bits);
+}
+
void __sysreg_save_el1_state(struct cpu_context *ctxt)
{
ctxt->sys_regs[SP_EL1] = read_sysreg(sp_el1);
--
2.43.0
^ permalink raw reply related
* [PATCH v3 9/9] KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for the nested guest
From: Wei-Lin Chang @ 2026-05-16 18:30 UTC (permalink / raw)
To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
Itaru Kitayama, Wei-Lin Chang
In-Reply-To: <20260516183003.799058-1-weilin.chang@arm.com>
Utilize the stage-2 library functions to initialize a s2_mmu, build a
stage-2 page table, and turn on stage-2 translation for the nested
guest. This better tests out the shadow page table code in KVM.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
.../selftests/kvm/arm64/shadow_stage2.c | 23 ++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index 2b274b810dcf..5bce55abdea7 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -51,9 +51,11 @@ static void l2_guest_code(void)
static void guest_code(void)
{
struct vcpu vcpu;
+ struct s2_mmu mmu;
struct hyp_data hyp_data;
int ret, i = 0;
- gpa_t l2_pc, l2_stack_top;
+ gpa_t l2_pc, l2_stack_start, l2_stack_top, s2_pgd;
+ gpa_t do_hvc_gpa;
struct page_pool pp;
u64 mmfr0 = read_sysreg(id_aa64mmfr0_el1);
@@ -68,11 +70,20 @@ static void guest_code(void)
if (!has_tgran_2(mmfr0, pp.page_size))
GUEST_SYNC1(TGRAN2NOSUP);
- l2_stack_top = alloc_page(&pp) + pp.page_size;
+ l2_stack_start = alloc_page(&pp);
+ l2_stack_top = l2_stack_start + pp.page_size;
l2_pc = ucall_translate_to_gpa(l2_guest_code);
+ do_hvc_gpa = ucall_translate_to_gpa(do_hvc);
+
+ s2_pgd = alloc_page(&pp);
init_vcpu(&vcpu, l2_pc, l2_stack_top);
- prepare_hyp_no_s2();
+ init_s2_mmu(&mmu, 0, s2_pgd, pp.page_size, 40);
+ create_s2_mapping(&mmu, l2_pc, l2_pc, pp.page_size * 2, &pp);
+ create_s2_mapping(&mmu, do_hvc_gpa, do_hvc_gpa, pp.page_size, &pp);
+ create_s2_mapping(&mmu, l2_stack_start, l2_stack_start, pp.page_size, &pp);
+
+ prepare_hyp(&mmu);
while (true) {
GUEST_PRINTF("L2 enter\n");
@@ -113,6 +124,12 @@ int main(void)
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
L2_PAGE_POOL_ADDR, L2_PAGE_POOL_MEMSLOT,
L2_PAGE_POOL_NPAGES, 0);
+ /*
+ * This idmap allows L1 to traverse and build its guest stage-2, where
+ * it must do a PA to VA conversion in order to descend to the next
+ * level.
+ */
+ virt_map(vm, L2_PAGE_POOL_ADDR, L2_PAGE_POOL_ADDR, L2_PAGE_POOL_NPAGES);
while (true) {
vcpu_run(vcpu);
--
2.43.0
^ permalink raw reply related
* [PATCH v6 01/22] drm: bridge: dw_hdmi: Disable scrambler feature when not supported
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel, Christopher Obbard
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
The scrambler feature can be left enabled when hotplugging from a sink
and mode that require scrambling to a sink that does not support SCDC or
scrambling.
Typically a blank screen or 'no signal' message can be observed after
using a HDMI 2.0 4K@60Hz mode and then hotplugging to a sink that only
support HDMI 1.4.
Fix this by disabling the scrambler feature when SCDC is not supported.
Fixes: 264fce6cc2c1 ("drm/bridge: dw-hdmi: Add SCDC and TMDS Scrambling support")
Reported-by: Christopher Obbard <chris.obbard@collabora.com>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change
v4: No change
v3: Collect r-b tag
v2: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 41b3a9cfa2f5..d3e6a6562870 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2135,6 +2135,8 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
HDMI_MC_SWRSTZ);
drm_scdc_set_scrambling(hdmi->curr_conn, 0);
}
+ } else if (hdmi->version >= 0x200a) {
+ hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL);
}
/* Set up horizontal active pixel width */
--
2.54.0
^ permalink raw reply related
* [PATCH v6 00/22] drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner
Cc: Laurent Pinchart, Jernej Skrabec, Luca Ceresoli, Liu Ying,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Sandy Huang, Andy Yan, Chen-Yu Tsai,
Christian Hewitt, Diederik de Haas, Nicolas Frattaroli,
Dmitry Baryshkov, dri-devel, linux-arm-kernel, linux-rockchip,
linux-amlogic, linux-sunxi, imx, linux-kernel, Jonas Karlman
This is a revival of an old dw-hdmi series and is the first series part
of a new effort to upstream old LibreELEC HDMI 2.0 patches for Rockchip
RK33xx devices.
This series ensure poweron/poweroff and CEC phys addr invalidation is
happening during normal DRM funcs, ensures EDID and CEC phys addr is
updated in detect() similar to how the bridge connector works with a
HDMI bridge attached, and also changes to debounce hotplug processing
to prevent a full disable/enable cycle during a HPD low voltage pulse.
After this series HPD, EDID and CEC handling should work very similar
regardless is the dw-hdmi connector or the bridge connector is used.
It should also help ensure a smoother transition when dw-hdmi is fully
converted into a HDMI bridge in a future series.
These changes have mainly been tested on Rockchip RK3328, RK3399 and
RK3568 devices using both the dw-hdmi connector and also using a basic
convert to use a bridge connector. The changes has also been tested on
a Amlogic S905X device that uses the bridge connector.
Testing with a Rock Pi 4 (RK3399) using a Reaspberry Pi Monitor with
Linux kms client console using drm.debug=0xe should log something like
following:
Power cycle monitor using the power button:
[CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a
[CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz
[CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156
[CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0
[CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1
[CONNECTOR:68:HDMI-A-1] Same epoch counter 10
Cable unplugged:
[CONNECTOR:68:HDMI-A-1] EDID changed, epoch counter 11
[CONNECTOR:68:HDMI-A-1] status updated from connected to disconnected
[CONNECTOR:68:HDMI-A-1] Changed epoch counter 10 => 12
[CONNECTOR:68:HDMI-A-1] generating connector hotplug event
[CONNECTOR:68:HDMI-A-1] Sent hotplug event
Cable connected:
[CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a
[CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz
[CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156
[CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0
[CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1
[CONNECTOR:68:HDMI-A-1] status updated from disconnected to connected
[CONNECTOR:68:HDMI-A-1] Changed epoch counter 12 => 13
[CONNECTOR:68:HDMI-A-1] generating connector hotplug event
[CONNECTOR:68:HDMI-A-1] Sent hotplug event
This series has evolved into an initial part of a larger multi series
effort to:
- drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup [v6]
- drm/bridge: dw-hdmi: Improve input/output bus format handling
- drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector
- drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops
- drm/meson: hdmi: Misc cleanup and use CEC notifier helpers
- phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops [v3]
- drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format [v1]
- drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes
Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260508-rk-hdmi-v4/
Changes in v6:
- Update EDID and CEC phys addr in the bridge detect() func
- Add CEC notifier bridge op for the bridge connector
- Change back to disable_delayed_work_sync() in hpd disable ops,
a possible deadlock is avoided by not using drm_bridge_hpd_notify()
- Drop use of a suspend helper now that hpd disable ops use sync() calls
- Ensure HPD interrupt is masked and IRQ handler is disabled early
in dw_hdmi_remove() to prevent any irq re-arming of delayed work
- Update a few commit messages and cover letter
- Collect t-b tags
Link to v5: https://lore.kernel.org/dri-devel/20260510124111.1226584-1-jonas@kwiboo.se/
Changes in v5:
- Add patch that holds a bridge ref until connector cleanup, to fix
a use-after-free issue during connector cleanup
- Add patch that unregister CEC notifier during connector cleanup
- Add patch that adds a common suspend helper
- Add patch that drops call to drm_bridge_hpd_notify()
- Collect r-b tag
- Rebased on next-20260508
Link to v4: https://lore.kernel.org/dri-devel/20260504191059.275928-1-jonas@kwiboo.se/
Changes in v4:
- Change to use generic CEC notifier helpers
- Disable/mask hpd_work until enable_hpd()/hpd_enable()
- Read connector status directly from HW regs in hpd_work
- Continued rework of HDP and RXSENSE interrupt handling
- Collect r-b tags
- Rebased on next-20260430
Link to v3: https://lore.kernel.org/dri-devel/20260403185303.80748-1-jonas@kwiboo.se/
Changes in v3:
- Rework EDID refresh handling to closer match bridge connector
- Use delayed work to debounce HPD processing
- Update commit messages
- Collect r-b tags
- Rebased on next-20260401
Link to v2: https://lore.kernel.org/dri-devel/20240908132823.3308029-1-jonas@kwiboo.se/
Changes in v2:
- Add patch to disable scrambler feature when not supported
- Add patch to only notify connected status on HPD interrupt
- Update commit messages
- Collect r-b tags
- Rebased on next-20240906
Link to v1: https://lore.kernel.org/dri-devel/20240611155108.1436502-1-jonas@kwiboo.se/
Jonas Karlman (22):
drm: bridge: dw_hdmi: Disable scrambler feature when not supported
drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt
drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable
drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode
drm: bridge: dw_hdmi: Fold poweron and setup functions
drm: bridge: dw_hdmi: Remove previous_mode and mode_set
drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup
drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup
drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect
drm: bridge: dw_hdmi: Remove cec_notifier_mutex
drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update()
drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update()
drm: bridge: dw_hdmi: Use generic CEC notifier helpers
drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect()
drm: bridge: dw_hdmi: Declare bridge CEC notifier support
drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio
drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify()
drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event
drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling
drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense()
drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd()
drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers
drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 -
drivers/gpu/drm/bridge/synopsys/Kconfig | 1 +
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 498 ++++++++------------
drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 -
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 -
drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 -
include/drm/bridge/dw_hdmi.h | 6 -
7 files changed, 187 insertions(+), 326 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH v6 03/22] drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
Change to only call poweron/poweroff from atomic_enable/atomic_disable
funcs instead of trying to be clever by keeping a bridge_is_on state and
poweron/off in the hotplug IRQ handler.
The bridge is already enabled/disabled depending on connection status
with the call to drm_helper_hpd_irq_event() in hotplug IRQ handler.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Update commit message,
Collect t-b tag
v5: No change
v4: No change
v3: Collect r-b tag
v2: Update commit message
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 33 ++---------------------
1 file changed, 2 insertions(+), 31 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index b7bfc0e9a6b2..8f7949d2c7f2 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -171,7 +171,6 @@ struct dw_hdmi {
enum drm_connector_force force; /* mutex-protected force state */
struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */
bool disabled; /* DRM has disabled our bridge */
- bool bridge_is_on; /* indicates the bridge is on */
bool rxsense; /* rxsense state */
u8 phy_mask; /* desired phy int mask settings */
u8 mc_clkdis; /* clock disable register */
@@ -2400,8 +2399,6 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi)
static void dw_hdmi_poweron(struct dw_hdmi *hdmi)
{
- hdmi->bridge_is_on = true;
-
/*
* The curr_conn field is guaranteed to be valid here, as this function
* is only be called when !hdmi->disabled.
@@ -2415,30 +2412,6 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi)
hdmi->phy.ops->disable(hdmi, hdmi->phy.data);
hdmi->phy.enabled = false;
}
-
- hdmi->bridge_is_on = false;
-}
-
-static void dw_hdmi_update_power(struct dw_hdmi *hdmi)
-{
- int force = hdmi->force;
-
- if (hdmi->disabled) {
- force = DRM_FORCE_OFF;
- } else if (force == DRM_FORCE_UNSPECIFIED) {
- if (hdmi->rxsense)
- force = DRM_FORCE_ON;
- else
- force = DRM_FORCE_OFF;
- }
-
- if (force == DRM_FORCE_OFF) {
- if (hdmi->bridge_is_on)
- dw_hdmi_poweroff(hdmi);
- } else {
- if (!hdmi->bridge_is_on)
- dw_hdmi_poweron(hdmi);
- }
}
/*
@@ -2563,7 +2536,6 @@ static void dw_hdmi_connector_force(struct drm_connector *connector)
mutex_lock(&hdmi->mutex);
hdmi->force = connector->force;
- dw_hdmi_update_power(hdmi);
dw_hdmi_update_phy_mask(hdmi);
mutex_unlock(&hdmi->mutex);
}
@@ -2988,7 +2960,7 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
mutex_lock(&hdmi->mutex);
hdmi->disabled = true;
hdmi->curr_conn = NULL;
- dw_hdmi_update_power(hdmi);
+ dw_hdmi_poweroff(hdmi);
dw_hdmi_update_phy_mask(hdmi);
handle_plugged_change(hdmi, false);
mutex_unlock(&hdmi->mutex);
@@ -3006,7 +2978,7 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
mutex_lock(&hdmi->mutex);
hdmi->disabled = false;
hdmi->curr_conn = connector;
- dw_hdmi_update_power(hdmi);
+ dw_hdmi_poweron(hdmi);
dw_hdmi_update_phy_mask(hdmi);
handle_plugged_change(hdmi, true);
mutex_unlock(&hdmi->mutex);
@@ -3106,7 +3078,6 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense)
if (hpd)
hdmi->rxsense = true;
- dw_hdmi_update_power(hdmi);
dw_hdmi_update_phy_mask(hdmi);
}
mutex_unlock(&hdmi->mutex);
--
2.54.0
^ permalink raw reply related
* [PATCH v6 04/22] drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
Use the passed mode instead of mixing use of passed mode and the stored
previous_mode in dw_hdmi_setup(). The passed mode is currenly always the
previous_mode.
Also fix a small typo and add a variable to help shorten a code line.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change
v4: No change
v3: Collect r-b tag
v2: Update commit message, s/type/typo/
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 8f7949d2c7f2..aa12397b3343 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2258,6 +2258,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi,
const struct drm_connector *connector,
const struct drm_display_mode *mode)
{
+ const struct drm_display_info *display = &connector->display_info;
int ret;
hdmi_disable_overflow_interrupts(hdmi);
@@ -2303,12 +2304,10 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi,
hdmi->hdmi_data.video_mode.mdataenablepolarity = true;
/* HDMI Initialization Step B.1 */
- hdmi_av_composer(hdmi, &connector->display_info, mode);
+ hdmi_av_composer(hdmi, display, mode);
- /* HDMI Initializateion Step B.2 */
- ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data,
- &connector->display_info,
- &hdmi->previous_mode);
+ /* HDMI Initialization Step B.2 */
+ ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, display, mode);
if (ret)
return ret;
hdmi->phy.enabled = true;
--
2.54.0
^ permalink raw reply related
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