* [PATCH v6 3/7] mfd: mt6397: Use MFD_CELL_* to describe sub-devices
From: Luca Leonardo Scorcia @ 2026-06-12 20:04 UTC (permalink / raw)
To: linux-mediatek
Cc: Luca Leonardo Scorcia, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sen Chu, Sean Wang,
Macpaul Lin, Lee Jones, Matthias Brugger,
AngeloGioacchino Del Regno, Linus Walleij, Louis-Alexis Eyraud,
Julien Massot, Val Packett, Fabien Parent, Akari Tsuyukusa,
Chen Zhong, linux-input, devicetree, linux-kernel, linux-pm,
linux-arm-kernel, linux-gpio
In-Reply-To: <20260612200717.361018-1-l.scorcia@gmail.com>
Use the MFD_CELL_* macros to describe sub-devices. No functional changes.
Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
---
drivers/mfd/mt6397-core.c | 197 ++++++++++++--------------------------
1 file changed, 63 insertions(+), 134 deletions(-)
diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c
index 1bdacda9a933..ccd97d66d7f1 100644
--- a/drivers/mfd/mt6397-core.c
+++ b/drivers/mfd/mt6397-core.c
@@ -124,159 +124,88 @@ static const struct resource mt6323_pwrc_resources[] = {
};
static const struct mfd_cell mt6323_devs[] = {
- {
- .name = "mt6323-rtc",
- .num_resources = ARRAY_SIZE(mt6323_rtc_resources),
- .resources = mt6323_rtc_resources,
- .of_compatible = "mediatek,mt6323-rtc",
- }, {
- .name = "mt6323-regulator",
- .of_compatible = "mediatek,mt6323-regulator"
- }, {
- .name = "mt6323-led",
- .of_compatible = "mediatek,mt6323-led"
- }, {
- .name = "mt6323-keys",
- .num_resources = ARRAY_SIZE(mt6323_keys_resources),
- .resources = mt6323_keys_resources,
- .of_compatible = "mediatek,mt6323-keys"
- }, {
- .name = "mt6323-pwrc",
- .num_resources = ARRAY_SIZE(mt6323_pwrc_resources),
- .resources = mt6323_pwrc_resources,
- .of_compatible = "mediatek,mt6323-pwrc"
- },
+ MFD_CELL_OF("mt6323-rtc", mt6323_rtc_resources, NULL, 0, 0,
+ "mediatek,mt6323-rtc"),
+ MFD_CELL_OF("mt6323-regulator", NULL, NULL, 0, 0,
+ "mediatek,mt6323-regulator"),
+ MFD_CELL_OF("mt6323-led", NULL, NULL, 0, 0,
+ "mediatek,mt6323-led"),
+ MFD_CELL_OF("mt6323-keys", mt6323_keys_resources, NULL, 0, 0,
+ "mediatek,mt6323-keys"),
+ MFD_CELL_OF("mt6323-pwrc", mt6323_pwrc_resources, NULL, 0, 0,
+ "mediatek,mt6323-pwrc"),
};
static const struct mfd_cell mt6328_devs[] = {
- {
- .name = "mt6328-regulator",
- .of_compatible = "mediatek,mt6328-regulator"
- }, {
- .name = "mt6328-keys",
- .num_resources = ARRAY_SIZE(mt6328_keys_resources),
- .resources = mt6328_keys_resources,
- .of_compatible = "mediatek,mt6328-keys"
- },
+ MFD_CELL_OF("mt6328-regulator", NULL, NULL, 0, 0,
+ "mediatek,mt6328-regulator"),
+ MFD_CELL_OF("mt6328-keys", mt6328_keys_resources, NULL, 0, 0,
+ "mediatek,mt6328-keys"),
};
static const struct mfd_cell mt6357_devs[] = {
- {
- .name = "mt6359-auxadc",
- .of_compatible = "mediatek,mt6357-auxadc"
- }, {
- .name = "mt6357-regulator",
- }, {
- .name = "mt6357-rtc",
- .num_resources = ARRAY_SIZE(mt6357_rtc_resources),
- .resources = mt6357_rtc_resources,
- .of_compatible = "mediatek,mt6357-rtc",
- }, {
- .name = "mt6357-sound",
- .of_compatible = "mediatek,mt6357-sound"
- }, {
- .name = "mt6357-keys",
- .num_resources = ARRAY_SIZE(mt6357_keys_resources),
- .resources = mt6357_keys_resources,
- .of_compatible = "mediatek,mt6357-keys"
- },
+ MFD_CELL_OF("mt6359-auxadc", NULL, NULL, 0, 0,
+ "mediatek,mt6357-auxadc"),
+ MFD_CELL_NAME("mt6357-regulator"),
+ MFD_CELL_OF("mt6357-rtc", mt6357_rtc_resources, NULL, 0, 0,
+ "mediatek,mt6357-rtc"),
+ MFD_CELL_OF("mt6357-sound", NULL, NULL, 0, 0,
+ "mediatek,mt6357-sound"),
+ MFD_CELL_OF("mt6357-keys", mt6357_keys_resources, NULL, 0, 0,
+ "mediatek,mt6357-keys"),
};
/* MT6331 is always used in combination with MT6332 */
static const struct mfd_cell mt6331_mt6332_devs[] = {
- {
- .name = "mt6331-rtc",
- .num_resources = ARRAY_SIZE(mt6331_rtc_resources),
- .resources = mt6331_rtc_resources,
- .of_compatible = "mediatek,mt6331-rtc",
- }, {
- .name = "mt6331-regulator",
- .of_compatible = "mediatek,mt6331-regulator"
- }, {
- .name = "mt6332-regulator",
- .of_compatible = "mediatek,mt6332-regulator"
- }, {
- .name = "mt6331-keys",
- .num_resources = ARRAY_SIZE(mt6331_keys_resources),
- .resources = mt6331_keys_resources,
- .of_compatible = "mediatek,mt6331-keys"
- },
+ MFD_CELL_OF("mt6331-rtc", mt6331_rtc_resources, NULL, 0, 0,
+ "mediatek,mt6331-rtc"),
+ MFD_CELL_OF("mt6331-regulator", NULL, NULL, 0, 0,
+ "mediatek,mt6331-regulator"),
+ MFD_CELL_OF("mt6332-regulator", NULL, NULL, 0, 0,
+ "mediatek,mt6332-regulator"),
+ MFD_CELL_OF("mt6331-keys", mt6331_keys_resources, NULL, 0, 0,
+ "mediatek,mt6331-keys"),
};
static const struct mfd_cell mt6358_devs[] = {
- {
- .name = "mt6359-auxadc",
- .of_compatible = "mediatek,mt6358-auxadc"
- }, {
- .name = "mt6358-regulator",
- .of_compatible = "mediatek,mt6358-regulator"
- }, {
- .name = "mt6358-rtc",
- .num_resources = ARRAY_SIZE(mt6358_rtc_resources),
- .resources = mt6358_rtc_resources,
- .of_compatible = "mediatek,mt6358-rtc",
- }, {
- .name = "mt6358-sound",
- .of_compatible = "mediatek,mt6358-sound"
- }, {
- .name = "mt6358-keys",
- .num_resources = ARRAY_SIZE(mt6358_keys_resources),
- .resources = mt6358_keys_resources,
- .of_compatible = "mediatek,mt6358-keys"
- },
+ MFD_CELL_OF("mt6359-auxadc", NULL, NULL, 0, 0,
+ "mediatek,mt6358-auxadc"),
+ MFD_CELL_OF("mt6358-regulator", NULL, NULL, 0, 0,
+ "mediatek,mt6358-regulator"),
+ MFD_CELL_OF("mt6358-rtc", mt6358_rtc_resources, NULL, 0, 0,
+ "mediatek,mt6358-rtc"),
+ MFD_CELL_OF("mt6358-sound", NULL, NULL, 0, 0,
+ "mediatek,mt6358-sound"),
+ MFD_CELL_OF("mt6358-keys", mt6358_keys_resources, NULL, 0, 0,
+ "mediatek,mt6358-keys"),
};
static const struct mfd_cell mt6359_devs[] = {
- {
- .name = "mt6359-auxadc",
- .of_compatible = "mediatek,mt6359-auxadc"
- },
- { .name = "mt6359-regulator", },
- {
- .name = "mt6359-rtc",
- .num_resources = ARRAY_SIZE(mt6358_rtc_resources),
- .resources = mt6358_rtc_resources,
- .of_compatible = "mediatek,mt6358-rtc",
- },
- { .name = "mt6359-sound", },
- {
- .name = "mt6359-keys",
- .num_resources = ARRAY_SIZE(mt6359_keys_resources),
- .resources = mt6359_keys_resources,
- .of_compatible = "mediatek,mt6359-keys"
- },
- {
- .name = "mt6359-accdet",
- .of_compatible = "mediatek,mt6359-accdet",
- .num_resources = ARRAY_SIZE(mt6359_accdet_resources),
- .resources = mt6359_accdet_resources,
- },
+ MFD_CELL_OF("mt6359-auxadc", NULL, NULL, 0, 0,
+ "mediatek,mt6359-auxadc"),
+ MFD_CELL_NAME("mt6359-regulator"),
+ MFD_CELL_OF("mt6359-rtc", mt6358_rtc_resources, NULL, 0, 0,
+ "mediatek,mt6358-rtc"),
+ MFD_CELL_NAME("mt6359-sound"),
+ MFD_CELL_OF("mt6359-keys", mt6359_keys_resources, NULL, 0, 0,
+ "mediatek,mt6359-keys"),
+ MFD_CELL_OF("mt6359-accdet", mt6359_accdet_resources, NULL, 0, 0,
+ "mediatek,mt6359-accdet"),
};
static const struct mfd_cell mt6397_devs[] = {
- {
- .name = "mt6397-rtc",
- .num_resources = ARRAY_SIZE(mt6397_rtc_resources),
- .resources = mt6397_rtc_resources,
- .of_compatible = "mediatek,mt6397-rtc",
- }, {
- .name = "mt6397-regulator",
- .of_compatible = "mediatek,mt6397-regulator",
- }, {
- .name = "mt6397-codec",
- .of_compatible = "mediatek,mt6397-codec",
- }, {
- .name = "mt6397-clk",
- .of_compatible = "mediatek,mt6397-clk",
- }, {
- .name = "mt6397-pinctrl",
- .of_compatible = "mediatek,mt6397-pinctrl",
- }, {
- .name = "mt6397-keys",
- .num_resources = ARRAY_SIZE(mt6397_keys_resources),
- .resources = mt6397_keys_resources,
- .of_compatible = "mediatek,mt6397-keys"
- }
+ MFD_CELL_OF("mt6397-rtc", mt6397_rtc_resources, NULL, 0, 0,
+ "mediatek,mt6397-rtc"),
+ MFD_CELL_OF("mt6397-regulator", NULL, NULL, 0, 0,
+ "mediatek,mt6397-regulator"),
+ MFD_CELL_OF("mt6397-codec", NULL, NULL, 0, 0,
+ "mediatek,mt6397-codec"),
+ MFD_CELL_OF("mt6397-clk", NULL, NULL, 0, 0,
+ "mediatek,mt6397-clk"),
+ MFD_CELL_OF("mt6397-pinctrl", NULL, NULL, 0, 0,
+ "mediatek,mt6397-pinctrl"),
+ MFD_CELL_OF("mt6397-keys", mt6397_keys_resources, NULL, 0, 0,
+ "mediatek,mt6397-keys"),
};
struct chip_data {
--
2.43.0
^ permalink raw reply related
* [PATCH v6 2/7] dt-bindings: input: mtk-pmic-keys: Add MT6392 PMIC keys
From: Luca Leonardo Scorcia @ 2026-06-12 20:04 UTC (permalink / raw)
To: linux-mediatek
Cc: Fabien Parent, Val Packett, Luca Leonardo Scorcia,
AngeloGioacchino Del Regno, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sen Chu, Sean Wang,
Macpaul Lin, Lee Jones, Matthias Brugger, Linus Walleij,
Louis-Alexis Eyraud, Julien Massot, Akari Tsuyukusa, Chen Zhong,
linux-input, devicetree, linux-kernel, linux-pm, linux-arm-kernel,
linux-gpio
In-Reply-To: <20260612200717.361018-1-l.scorcia@gmail.com>
From: Fabien Parent <parent.f@gmail.com>
Add the binding documentation of mtk-pmic-keys for the MT6392 PMIC.
Signed-off-by: Fabien Parent <parent.f@gmail.com>
Signed-off-by: Val Packett <val@packett.cool>
Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
Documentation/devicetree/bindings/input/mediatek,pmic-keys.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/input/mediatek,pmic-keys.yaml b/Documentation/devicetree/bindings/input/mediatek,pmic-keys.yaml
index 140a862ecfbe..ff720588128b 100644
--- a/Documentation/devicetree/bindings/input/mediatek,pmic-keys.yaml
+++ b/Documentation/devicetree/bindings/input/mediatek,pmic-keys.yaml
@@ -31,6 +31,7 @@ properties:
- mediatek,mt6357-keys
- mediatek,mt6358-keys
- mediatek,mt6359-keys
+ - mediatek,mt6392-keys
- mediatek,mt6397-keys
- items:
- enum:
--
2.43.0
^ permalink raw reply related
* [PATCH v6 1/7] dt-bindings: mfd: mt6397: Add MT6392 PMIC
From: Luca Leonardo Scorcia @ 2026-06-12 20:04 UTC (permalink / raw)
To: linux-mediatek
Cc: Fabien Parent, Val Packett, Luca Leonardo Scorcia,
Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Sen Chu, Sean Wang, Macpaul Lin, Lee Jones, Matthias Brugger,
AngeloGioacchino Del Regno, Linus Walleij, Julien Massot,
Louis-Alexis Eyraud, Akari Tsuyukusa, Chen Zhong, linux-input,
devicetree, linux-kernel, linux-pm, linux-arm-kernel, linux-gpio
In-Reply-To: <20260612200717.361018-1-l.scorcia@gmail.com>
From: Fabien Parent <parent.f@gmail.com>
Add the initial bindings for the MT6392 PMIC and its RTC device.
Signed-off-by: Fabien Parent <parent.f@gmail.com>
Signed-off-by: Val Packett <val@packett.cool>
Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
---
.../devicetree/bindings/mfd/mediatek,mt6397.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
index 3cbc0dc12c31..e39e81aa9924 100644
--- a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
+++ b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
@@ -40,6 +40,10 @@ properties:
- mediatek,mt6358
- mediatek,mt6359
- mediatek,mt6397
+ - items:
+ - enum:
+ - mediatek,mt6392
+ - const: mediatek,mt6323
- items:
- enum:
- mediatek,mt6366
@@ -72,6 +76,10 @@ properties:
- mediatek,mt6331-rtc
- mediatek,mt6358-rtc
- mediatek,mt6397-rtc
+ - items:
+ - enum:
+ - mediatek,mt6392-rtc
+ - const: mediatek,mt6323-rtc
- items:
- enum:
- mediatek,mt6359-rtc
--
2.43.0
^ permalink raw reply related
* [PATCH v6 0/7] Add support for MT6392 PMIC
From: Luca Leonardo Scorcia @ 2026-06-12 20:04 UTC (permalink / raw)
To: linux-mediatek
Cc: Luca Leonardo Scorcia, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sen Chu, Sean Wang,
Macpaul Lin, Lee Jones, Matthias Brugger,
AngeloGioacchino Del Regno, Linus Walleij, Julien Massot,
Louis-Alexis Eyraud, Val Packett, Fabien Parent, Akari Tsuyukusa,
Chen Zhong, linux-input, devicetree, linux-kernel, linux-pm,
linux-arm-kernel, linux-gpio
The MediaTek MT6392 PMIC is usually found on devices powered by
the MT8516/MT8167 SoC and is yet another MT6323/MT6397 variant.
This series is mostly based around patches submitted a couple
years ago by Fabien Parent and not merged and from Val Packett's
submission from Jan 2025 that included extra cleanups, fixes, and a
new dtsi file similar to ones that exist for other PMICs. Some
comments weren't addressed and the series was ultimately not merged.
These patches only enable three functions: keys, pinctrl and RTC.
Regulators and speaker amp will follow later as I still need to further
improve those two, but getting the main PMIC in will make the series
easier to review.
I added a handful of device tree improvements to fix some dtbs_check
errors, added support for the pinctrl device and addressed the comments
from last year's reviews.
Please note that patch 0005 depends on patch 0004 as they both need the
registers.h file, but they belong to different driver areas. I'm not sure
if I'm supposed to squash them even if they belong to different driver
areas of if it's fine like this. Any advice is welcome.
The series has been tested on Xiaomi Mi Smart Clock X04G and on the
Lenovo Smart Clock 2.
Changes in v6:
- Dropped the regulators driver for the moment
- Explained the FCHR key name origin in the commit message
- Introduced the MFD_CELL_* macro in the sub-devices definitions.
A separate, independent commit introduced MFD_CELL_* to all the
subdevices in the mt6397-core.c file for consistency
- Replaced of_device_get_match_data with device_get_match_data
- Removed the mfd_match_data enum in favor of the preexisting
chip_id enum
- Adjusted the error message if the device is unsupported
Changes in v5 [5]:
- Double checked regulator driver with data sheet and Android sources.
The data sheet I have misses a lot of register descriptions, but
Android sources have been helpful to fill the gaps
- Reintroduced the required attribute for the regulator compatible
in the bindings
- Fixed the missing reference to the MT6392 schema
- Fixed casts/unused vars reported by kernel test robot
- Removed Reviewed-by tags from the regulator patches as they have been
modified in this version
Changes in v4 [4]:
- Dropped usage of the regulator compatible
- Fixed commit messages text to properly reference the target subsystem
- Added supply rails to the regulator
- Reworked the regulator schema and PMIC dtsi. Now all supplies are
documented and the schema no longer includes voltage information
- Removed redundant ldo- / buck- prefixes
- Renamed the pinfunc header to mediatek,mt6392-pinfunc.h
- Modified the MFD driver to use a simple identifier in the of_match
data properties
Changes in v3 [3]:
- Added pinctrl device
- Changed mt6397-rtc fallback to mt6323-rtc
- Added schema for regulators
- Fixed checkpatch issues
Changes in v2 [2]:
- Replaced explicit compatibles with fallbacks
Initial version: [1]
[1] https://lore.kernel.org/linux-mediatek/cover.1771865014.git.l.scorcia@gmail.com/
[2] https://lore.kernel.org/linux-mediatek/20260306120521.163654-1-l.scorcia@gmail.com/
[3] https://lore.kernel.org/linux-mediatek/20260317184507.523060-1-l.scorcia@gmail.com/
[4] https://lore.kernel.org/linux-mediatek/20260330083429.359819-1-l.scorcia@gmail.com/
[5] https://lore.kernel.org/linux-mediatek/20260420213529.1645560-1-l.scorcia@gmail.com/
Fabien Parent (3):
dt-bindings: mfd: mt6397: Add MT6392 PMIC
dt-bindings: input: mtk-pmic-keys: Add MT6392 PMIC keys
mfd: mt6397: Add support for MT6392 PMIC
Luca Leonardo Scorcia (2):
mfd: mt6397: Use MFD_CELL_* to describe sub-devices
pinctrl: mediatek: mt6397: Add MediaTek MT6392
Val Packett (2):
input: keyboard: mtk-pmic-keys: Add MT6392 support
arm64: dts: mediatek: Add MediaTek MT6392 PMIC dtsi
.../bindings/input/mediatek,pmic-keys.yaml | 1 +
.../bindings/mfd/mediatek,mt6397.yaml | 8 +
arch/arm64/boot/dts/mediatek/mt6392.dtsi | 41 ++
drivers/input/keyboard/mtk-pmic-keys.c | 17 +
drivers/mfd/mt6397-core.c | 294 +++++------
drivers/mfd/mt6397-irq.c | 8 +
drivers/pinctrl/mediatek/pinctrl-mt6397.c | 37 +-
drivers/pinctrl/mediatek/pinctrl-mtk-mt6392.h | 64 +++
include/linux/mfd/mt6392/core.h | 43 ++
include/linux/mfd/mt6392/registers.h | 488 ++++++++++++++++++
include/linux/mfd/mt6397/core.h | 1 +
11 files changed, 840 insertions(+), 162 deletions(-)
create mode 100644 arch/arm64/boot/dts/mediatek/mt6392.dtsi
create mode 100644 drivers/pinctrl/mediatek/pinctrl-mtk-mt6392.h
create mode 100644 include/linux/mfd/mt6392/core.h
create mode 100644 include/linux/mfd/mt6392/registers.h
--
2.43.0
^ permalink raw reply
* Re: [PATCH v1 phy-next 7/8] soc: fsl: guts: implement the RCW override procedure
From: Vladimir Oltean @ 2026-06-12 20:25 UTC (permalink / raw)
To: Conor Dooley
Cc: linux-phy, devicetree, linuxppc-dev, linux-arm-kernel,
Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
Christophe Leroy (CS GROUP), Michael Walle, Shawn Guo, Frank Li,
linux-kernel, Krzysztof Kozlowski, Rob Herring
In-Reply-To: <20260612-twenty-diary-4aab5327f9c2@spud>
On Fri, Jun 12, 2026 at 04:44:30PM +0100, Conor Dooley wrote:
> On Thu, Jun 11, 2026 at 10:39:39PM +0300, Vladimir Oltean wrote:
> > From: Ioana Ciornei <ioana.ciornei@nxp.com>
> >
> > Add support for the RCW override procedure which enables runtime
> > reconfiguration of the protocol running on a SerDes lane. The procedure
> > is done through the DCFG DCSR space which now can be defined as the
> > second memory region of the guts DT node.
> > Support is added on the following SoCs: LS1046A, LS1088A, LS2088A.
> >
> > The procedure is exported to the "client" driver - the Lynx10G SerDes
> > PHY driver - through the following functions:
> > - fsl_guts_lane_init() used to notify the initial / boot time lane mode
> > running on a SerDes lane.
> > - fsl_guts_lane_validate() used to validate that changing the protocol
> > on a specific lane is supported.
> > - fsl_guts_lane_set_mode() which can be used to request the RCW
> > procedure be executed for a specific lane.
> >
> > Since the RCW override procedure is different depending on the SoC, the
> > private fsl_soc_data structure is updated with two new per SoC callbacks
> > (.serdes_get_rcw_override() and .serdes_init_rcwcr()) which get used
> > from the generic fsl_guts_lane_set_mode() function. These two callbacks
> > hide all the SoC specific register offsets, masks and values so that the
> > _set_mode() procedure is straightforward.
> >
> > Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
> > Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
> > ---
> > Cc: Conor Dooley <conor@kernel.org>
> > Cc: Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>
> > Cc: Rob Herring <robh@kernel.org>
> > Cc: devicetree@vger.kernel.org
>
> Wrong CC list for this specific patch?
No, it was intentional. Provided for context, so DT reviewers can check
that extra properties not defined in the schema aren't being used.
^ permalink raw reply
* Re: [PATCH v1 phy-next 6/8] dt-bindings: fsl: layerscape-dcfg: define DCFG_DCSR region
From: Vladimir Oltean @ 2026-06-12 20:23 UTC (permalink / raw)
To: Conor Dooley
Cc: linux-phy, devicetree, linuxppc-dev, linux-arm-kernel,
Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
Christophe Leroy (CS GROUP), Michael Walle, Shawn Guo, Frank Li,
linux-kernel, Krzysztof Kozlowski, Rob Herring
In-Reply-To: <20260612-graveyard-hacked-c65d54a1ece8@spud>
On Fri, Jun 12, 2026 at 04:44:00PM +0100, Conor Dooley wrote:
> On Thu, Jun 11, 2026 at 10:39:38PM +0300, Vladimir Oltean wrote:
> > In Layerscape (Arm) and QorIQ (PowerPC) devices, hardware peripherals
> > are accessed by the CPU through a portion of the SoC address space
> > called CCSR ("Configuration, Control, and Status Registers"). All
> > hardware IP blocks have their registers mapped here, and the Device
> > Configuration block makes no exception.
> >
> > However, there exists a secondary range of the address space named DCSR
> > ("Debug Control and Status Registers") which, like CCSR, also holds
> > registers of hardware IP blocks, except the DCSR contents is hidden in
> > all public reference manuals.
> >
> > The intention of the CCSR/DCSR split, to the best of my knowledge, was
> > to place the functionality that is too low level for normal use, and
> > which is necessary only for debug, in a completely separate address
> > space which can be hidden.
> >
> > A use case has appeared where networking SerDes lanes need to be
> > reconfigured at runtime for a different protocol (example: 10GBase-R to
> > SGMII), and the architecture of the SoCs does not normally permit that.
> > The Reset Configuration Word (RCW) is a data structure read by the SoC
> > preboot loader (PBL) which contains stuff like pinmuxing and SerDes
> > protocol mapping for each lane.
> >
> > The RCW that the PBL has loaded is visible in the DCFG block's normal
> > status registers (from CCSR), as read only. Turns out, the RCW is also
> > mapped in the DCFG's shadow register map (in DCSR), in a write-only
> > form. Writing to the RCW registers from the DCFG's DCSR space to change
> > what the PBL has loaded is called "RCW override".
> >
> > It has been validated that the RCW override procedure is necessary to
> > reconfigure the networking data path when a SerDes lane performs a major
> > protocol change. It changes some internal muxes which connect the PCS to
> > either the 10G MAC or to the 1G MAC.
> >
> > Defining the DCSR area of the DCFG as a secondary 'reg' array element
> > allows operating systems to perform RCW overrides. Since it is
> > introduced late in the binding's lifetime, it is optional. It can be
> > identified by name, but also by index (first 'reg' is CCSR).
> >
> > Note that while all SoCs should have a DCFG register block in DCSR, we
> > only need to expose it for the SoCs where the RCW override procedure is
> > known to be needed and has been validated.
> >
> > Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
> > ---
> > Cc: Conor Dooley <conor@kernel.org>
>
> Where did this email come from btw? get_maintainer.pl result should have
> +dt in it.
Historical data. I am keeping all sorts of commands (here git send-email)
in *.txt files and that's where I got the stale email addresses from.
Krzysztof's is also bouncing. Will fix for v2.
> Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
> pw-bot: not-applicable
Thanks for the review!
^ permalink raw reply
* [PATCH 07/21] perf: arm_pmuv3: Add method to partition the PMU
From: Colton Lewis @ 2026-06-12 19:28 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
For PMUv3, the register field MDCR_EL2.HPMN partitions the PMU
counters into two ranges where counters 0..HPMN-1 are accessible by
EL1 and, if allowed, EL0 while counters HPMN..N are only accessible by
EL2.
Create a module parameter reserved_host_counters to reserve a number
of counters for the host. Counters not reserved for the host may be
used by a guest VM when the PMU is partitioned.
Add the function armv8pmu_partition() to check the validity of the
reservation and record a partition has happened and the maximum
allowable value for HPMN.
Due to the difficulty this feature would create for the driver running
in nVHE mode, partitioning is only allowed in VHE mode. In order to
support a partitioning on nVHE we'd need to explicitly disable guest
counters on every exit and reset HPMN to place all counters in the
first range.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm/include/asm/arm_pmuv3.h | 4 ++
arch/arm64/include/asm/arm_pmuv3.h | 5 ++
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/pmu-direct.c | 22 +++++++++
drivers/perf/arm_pmuv3.c | 77 ++++++++++++++++++++++++++++--
include/kvm/arm_pmu.h | 10 +++-
include/linux/perf/arm_pmu.h | 7 +++
7 files changed, 121 insertions(+), 6 deletions(-)
create mode 100644 arch/arm64/kvm/pmu-direct.c
diff --git a/arch/arm/include/asm/arm_pmuv3.h b/arch/arm/include/asm/arm_pmuv3.h
index ecfede0c03486..fddcd6e6f76b2 100644
--- a/arch/arm/include/asm/arm_pmuv3.h
+++ b/arch/arm/include/asm/arm_pmuv3.h
@@ -221,6 +221,10 @@ static inline bool kvm_pmu_counter_deferred(struct perf_event_attr *attr)
return false;
}
+static inline bool has_host_pmu_partition_support(void)
+{
+ return false;
+}
static inline bool kvm_set_pmuserenr(u64 val)
{
return false;
diff --git a/arch/arm64/include/asm/arm_pmuv3.h b/arch/arm64/include/asm/arm_pmuv3.h
index cf2b2212e00a2..27c4d6d47da31 100644
--- a/arch/arm64/include/asm/arm_pmuv3.h
+++ b/arch/arm64/include/asm/arm_pmuv3.h
@@ -171,6 +171,11 @@ static inline bool pmuv3_implemented(int pmuver)
pmuver == ID_AA64DFR0_EL1_PMUVer_NI);
}
+static inline bool is_pmuv3p1(int pmuver)
+{
+ return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P1;
+}
+
static inline bool is_pmuv3p4(int pmuver)
{
return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P4;
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 59612d2f277c1..0e7b8e65c4c93 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -26,7 +26,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \
vgic/vgic-v5.o
-kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
+kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu-direct.o pmu.o
kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
kvm-$(CONFIG_PTDUMP_STAGE2_DEBUGFS) += ptdump.o
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
new file mode 100644
index 0000000000000..74e40e4915416
--- /dev/null
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Google LLC
+ * Author: Colton Lewis <coltonlewis@google.com>
+ */
+
+#include <linux/kvm_host.h>
+
+#include <asm/arm_pmuv3.h>
+
+/**
+ * has_host_pmu_partition_support() - Determine if partitioning is possible
+ *
+ * Partitioning is only supported in VHE mode with PMUv3
+ *
+ * Return: True if partitioning is possible, false otherwise
+ */
+bool has_host_pmu_partition_support(void)
+{
+ return has_vhe() &&
+ system_supports_pmuv3();
+}
diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c
index d7a49dc0b0be6..c187397134990 100644
--- a/drivers/perf/arm_pmuv3.c
+++ b/drivers/perf/arm_pmuv3.c
@@ -42,6 +42,13 @@
#define ARMV8_THUNDER_PERFCTR_L1I_CACHE_PREF_ACCESS 0xEC
#define ARMV8_THUNDER_PERFCTR_L1I_CACHE_PREF_MISS 0xED
+static int reserved_host_counters __read_mostly = -1;
+bool armv8pmu_is_partitioned;
+
+module_param(reserved_host_counters, int, 0);
+MODULE_PARM_DESC(reserved_host_counters,
+ "PMU Partition: -1 = No partition; +N = Reserve N counters for the host");
+
/*
* ARMv8 Architectural defined events, not all of these may
* be supported on any given implementation. Unsupported events will
@@ -532,6 +539,11 @@ static void armv8pmu_pmcr_write(u64 val)
write_pmcr(val);
}
+static u64 armv8pmu_pmcr_n_read(void)
+{
+ return FIELD_GET(ARMV8_PMU_PMCR_N, armv8pmu_pmcr_read());
+}
+
static int armv8pmu_has_overflowed(u64 pmovsr)
{
return !!(pmovsr & ARMV8_PMU_CNT_MASK_ALL);
@@ -1317,6 +1329,54 @@ struct armv8pmu_probe_info {
bool present;
};
+/**
+ * armv8pmu_reservation_is_valid() - Determine if reservation is allowed
+ * @host_counters: Number of host counters to reserve
+ *
+ * Determine if the number of host counters in the argument is an
+ * allowed reservation, 0 to NR_COUNTERS inclusive.
+ *
+ * Return: True if reservation allowed, false otherwise
+ */
+static bool armv8pmu_reservation_is_valid(int host_counters)
+{
+ return host_counters >= 0 &&
+ host_counters <= armv8pmu_pmcr_n_read();
+}
+
+/**
+ * armv8pmu_partition() - Partition the PMU
+ * @pmu: Pointer to pmu being partitioned
+ * @host_counters: Number of host counters to reserve
+ *
+ * Partition the given PMU by taking a number of host counters to
+ * reserve and, if it is a valid reservation, recording the
+ * corresponding HPMN value in the max_guest_counters field of the PMU and
+ * clearing the guest-reserved counters from the counter mask.
+ *
+ * Return: 0 on success, -ERROR otherwise
+ */
+static int armv8pmu_partition(struct arm_pmu *pmu, int host_counters)
+{
+ u8 nr_counters;
+ u8 hpmn;
+
+ if (!armv8pmu_reservation_is_valid(host_counters)) {
+ pr_err("PMU partition reservation of %d host counters is not valid", host_counters);
+ return -EINVAL;
+ }
+
+ nr_counters = armv8pmu_pmcr_n_read();
+ hpmn = nr_counters - host_counters;
+
+ pmu->max_guest_counters = hpmn;
+ armv8pmu_is_partitioned = true;
+
+ pr_info("Partitioned PMU with %d host counters -> %u guest counters", host_counters, hpmn);
+
+ return 0;
+}
+
static void __armv8pmu_probe_pmu(void *info)
{
struct armv8pmu_probe_info *probe = info;
@@ -1331,17 +1391,26 @@ static void __armv8pmu_probe_pmu(void *info)
cpu_pmu->pmuver = pmuver;
probe->present = true;
+ cpu_pmu->max_guest_counters = -1;
/* Read the nb of CNTx counters supported from PMNC */
- bitmap_set(cpu_pmu->cntr_mask,
- 0, FIELD_GET(ARMV8_PMU_PMCR_N, armv8pmu_pmcr_read()));
+ bitmap_set(cpu_pmu->hw_cntr_impl, 0, armv8pmu_pmcr_n_read());
/* Add the CPU cycles counter */
- set_bit(ARMV8_PMU_CYCLE_IDX, cpu_pmu->cntr_mask);
+ set_bit(ARMV8_PMU_CYCLE_IDX, cpu_pmu->hw_cntr_impl);
/* Add the CPU instructions counter */
if (pmuv3_has_icntr())
- set_bit(ARMV8_PMU_INSTR_IDX, cpu_pmu->cntr_mask);
+ set_bit(ARMV8_PMU_INSTR_IDX, cpu_pmu->hw_cntr_impl);
+
+ bitmap_copy(cpu_pmu->cntr_mask, cpu_pmu->hw_cntr_impl, ARMPMU_MAX_HWEVENTS);
+
+ if (reserved_host_counters >= 0) {
+ if (has_host_pmu_partition_support())
+ armv8pmu_partition(cpu_pmu, reserved_host_counters);
+ else
+ pr_err("PMU partition is not supported");
+ }
pmceid[0] = pmceid_raw[0] = read_pmceid0();
pmceid[1] = pmceid_raw[1] = read_pmceid1();
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 520f6d926ac8c..f9a0823666949 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -50,8 +50,11 @@ struct arm_pmu_entry {
struct arm_pmu *arm_pmu;
};
+extern bool armv8pmu_is_partitioned;
+
bool kvm_supports_guest_pmuv3(void);
-#define kvm_arm_pmu_irq_initialized(v) ((v)->arch.pmu.irq_num != 0)
+bool has_host_pmu_partition_support(void);
+#define kvm_arm_pmu_irq_initialized(v) ((v)->arch.pmu.irq_num >= VGIC_NR_SGIS)
u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu, u64 select_idx);
void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
@@ -120,6 +123,11 @@ static inline bool kvm_supports_guest_pmuv3(void)
return false;
}
+static inline bool has_host_pmu_partition_support(void)
+{
+ return false;
+}
+
#define kvm_arm_pmu_irq_initialized(v) (false)
static inline u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu,
u64 select_idx)
diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
index 52b37f7bdbf9e..2e1e7a48e05ff 100644
--- a/include/linux/perf/arm_pmu.h
+++ b/include/linux/perf/arm_pmu.h
@@ -109,6 +109,12 @@ struct arm_pmu {
*/
int (*map_pmuv3_event)(unsigned int eventsel);
DECLARE_BITMAP(cntr_mask, ARMPMU_MAX_HWEVENTS);
+ /*
+ * Keep a copy at initialization of which counters the
+ * hardware implements to restore cntr_mask after
+ * modifications.
+ */
+ DECLARE_BITMAP(hw_cntr_impl, ARMPMU_MAX_HWEVENTS);
bool secure_access; /* 32-bit ARM only */
struct platform_device *plat_device;
struct pmu_hw_events __percpu *hw_events;
@@ -129,6 +135,7 @@ struct arm_pmu {
/* Only to be used by ACPI probing code */
unsigned long acpi_cpuid;
+ int max_guest_counters;
};
#define to_arm_pmu(p) (container_of(p, struct arm_pmu, pmu))
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* Re: [PATCH net-next 0/3] Introduce HSR/PRP HW offload support for PRU-ICSSM Ethernet driver
From: Simon Horman @ 2026-06-12 20:01 UTC (permalink / raw)
To: Parvathi Pudi
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar, rogerq,
pmohan, afd, basharath, arnd, linux-kernel, netdev,
linux-arm-kernel, pratheesh, j-rameshbabu, vigneshr, praneeth,
srk, rogerq, m-malladi, krishna, mohan
In-Reply-To: <20260611123636.376577-1-parvathi@couthit.com>
On Thu, Jun 11, 2026 at 06:03:25PM +0530, Parvathi Pudi wrote:
> Hi,
>
> This series introduces HSR and PRP protocol HW offload support for
> ICSSM-Prueth driver. HW offload support for HSR/PRP is implemented using
> dedicated HSR/PRP firmware running on 2 PRU cores(PRU-ICSS) as a "DAN"
> available in AM57xx, AM437x and AM335x.
Hi Parvathi,
There is AI-generated review of this patch-set available on
https://sashiko.dev
I would appreciate it if you could look over that with a view
to addressing any issues that directly affect this patch-set.
^ permalink raw reply
* [PATCH 08/21] KVM: arm64: Set up FGT for Partitioned PMU
From: Colton Lewis @ 2026-06-12 19:28 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
In order to gain the best performance benefit from partitioning the
PMU, utilize fine grain traps (FEAT_FGT and FEAT_FGT2) to avoid
trapping common PMU register accesses by the guest to remove that
overhead.
Untrapped:
* PMCR_EL0
* PMUSERENR_EL0
* PMSELR_EL0
* PMCCNTR_EL0
* PMCNTEN_EL0
* PMINTEN_EL1
* PMEVCNTRn_EL0
These are safe to untrap because writing MDCR_EL2.HPMN as this series
will do limits the effect of writes to any of these registers to the
partition of counters 0..HPMN-1. Reads from these registers will not
leak information from between guests as all these registers are
context swapped by a later patch in this series. Reads from these
registers also do not leak any information about the host's hardware
beyond what is promised by PMUv3.
Trapped:
* PMOVS_EL0
* PMEVTYPERn_EL0
* PMCCFILTR_EL0
* PMICNTR_EL0
* PMICFILTR_EL0
* PMCEIDn_EL0
* PMMIR_EL1
PMOVS remains trapped so KVM can track overflow IRQs that will need to
be injected into the guest.
PMICNTR and PMIFILTR remain trapped because KVM is not handling them
yet.
PMEVTYPERn remains trapped so KVM can limit which events guests can
count, such as disallowing counting at EL2. PMCCFILTR and PMCIFILTR
are special cases of the same.
PMCEIDn and PMMIR remain trapped because they can leak information
specific to the host hardware implementation.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm/include/asm/arm_pmuv3.h | 4 +++
arch/arm64/include/asm/kvm_host.h | 2 ++
arch/arm64/kvm/config.c | 41 ++++++++++++++++++++++++++++---
arch/arm64/kvm/pmu-direct.c | 36 +++++++++++++++++++++++++++
include/kvm/arm_pmu.h | 12 +++++++++
5 files changed, 92 insertions(+), 3 deletions(-)
diff --git a/arch/arm/include/asm/arm_pmuv3.h b/arch/arm/include/asm/arm_pmuv3.h
index fddcd6e6f76b2..eedf58ea01b10 100644
--- a/arch/arm/include/asm/arm_pmuv3.h
+++ b/arch/arm/include/asm/arm_pmuv3.h
@@ -231,6 +231,10 @@ static inline bool kvm_set_pmuserenr(u64 val)
}
static inline void kvm_vcpu_pmu_resync_el0(void) {}
+static inline bool pmu_is_partitioned(struct arm_pmu *pmu)
+{
+ return false;
+}
/* PMU Version in DFR Register */
#define ARMV8_PMU_DFR_VER_NI 0
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 0d7a620c69ee2..9c7e9b92dfbd3 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -367,6 +367,8 @@ struct kvm_arch {
#define KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS 10
/* Unhandled SEAs are taken to userspace */
#define KVM_ARCH_FLAG_EXIT_SEA 11
+ /* Partitioned PMU Enabled */
+#define KVM_ARCH_FLAG_PARTITION_PMU_ENABLED 12
unsigned long flags;
/* VM-wide vCPU feature set */
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 0622162b089e5..f052ec8a00309 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1685,12 +1685,47 @@ static void __compute_hfgwtr(struct kvm_vcpu *vcpu)
*vcpu_fgt(vcpu, HFGWTR_EL2) |= HFGWTR_EL2_TCR_EL1;
}
+static void __compute_hdfgrtr(struct kvm_vcpu *vcpu)
+{
+ __compute_fgt(vcpu, HDFGRTR_EL2);
+
+ *vcpu_fgt(vcpu, HDFGRTR_EL2) |=
+ HDFGRTR_EL2_PMOVS |
+ HDFGRTR_EL2_PMCCFILTR_EL0 |
+ HDFGRTR_EL2_PMEVTYPERn_EL0 |
+ HDFGRTR_EL2_PMCEIDn_EL0 |
+ HDFGRTR_EL2_PMMIR_EL1;
+}
+
static void __compute_hdfgwtr(struct kvm_vcpu *vcpu)
{
__compute_fgt(vcpu, HDFGWTR_EL2);
if (is_hyp_ctxt(vcpu))
*vcpu_fgt(vcpu, HDFGWTR_EL2) |= HDFGWTR_EL2_MDSCR_EL1;
+
+ *vcpu_fgt(vcpu, HDFGWTR_EL2) |=
+ HDFGWTR_EL2_PMOVS |
+ HDFGWTR_EL2_PMCCFILTR_EL0 |
+ HDFGWTR_EL2_PMEVTYPERn_EL0;
+}
+
+static void __compute_hdfgrtr2(struct kvm_vcpu *vcpu)
+{
+ __compute_fgt(vcpu, HDFGRTR2_EL2);
+
+ *vcpu_fgt(vcpu, HDFGRTR2_EL2) &=
+ ~(HDFGRTR2_EL2_nPMICFILTR_EL0 |
+ HDFGRTR2_EL2_nPMICNTR_EL0);
+}
+
+static void __compute_hdfgwtr2(struct kvm_vcpu *vcpu)
+{
+ __compute_fgt(vcpu, HDFGWTR2_EL2);
+
+ *vcpu_fgt(vcpu, HDFGWTR2_EL2) &=
+ ~(HDFGWTR2_EL2_nPMICFILTR_EL0 |
+ HDFGWTR2_EL2_nPMICNTR_EL0);
}
static void __compute_ich_hfgrtr(struct kvm_vcpu *vcpu)
@@ -1727,7 +1762,7 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
__compute_fgt(vcpu, HFGRTR_EL2);
__compute_hfgwtr(vcpu);
__compute_fgt(vcpu, HFGITR_EL2);
- __compute_fgt(vcpu, HDFGRTR_EL2);
+ __compute_hdfgrtr(vcpu);
__compute_hdfgwtr(vcpu);
__compute_fgt(vcpu, HAFGRTR_EL2);
@@ -1735,8 +1770,8 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
__compute_fgt(vcpu, HFGRTR2_EL2);
__compute_fgt(vcpu, HFGWTR2_EL2);
__compute_fgt(vcpu, HFGITR2_EL2);
- __compute_fgt(vcpu, HDFGRTR2_EL2);
- __compute_fgt(vcpu, HDFGWTR2_EL2);
+ __compute_hdfgrtr2(vcpu);
+ __compute_hdfgwtr2(vcpu);
}
if (cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF)) {
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index 74e40e4915416..0135989667564 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -5,6 +5,8 @@
*/
#include <linux/kvm_host.h>
+#include <linux/perf/arm_pmu.h>
+#include <linux/perf/arm_pmuv3.h>
#include <asm/arm_pmuv3.h>
@@ -20,3 +22,37 @@ bool has_host_pmu_partition_support(void)
return has_vhe() &&
system_supports_pmuv3();
}
+
+/**
+ * pmu_is_partitioned() - Determine if given PMU is partitioned
+ * @pmu: Pointer to arm_pmu struct
+ *
+ * Determine if given PMU is partitioned by looking at hpmn field. The
+ * PMU is partitioned if this field is less than the number of
+ * counters in the system.
+ *
+ * Return: True if the PMU is partitioned, false otherwise
+ */
+bool pmu_is_partitioned(struct arm_pmu *pmu)
+{
+ if (!pmu)
+ return false;
+
+ return pmu->max_guest_counters >= 0 &&
+ pmu->max_guest_counters <= *host_data_ptr(nr_event_counters);
+}
+
+/**
+ * kvm_pmu_is_partitioned() - Determine if KVM has a partitioned PMU
+ * @kvm: Pointer to kvm struct
+ *
+ * Determine if KVM has a partitioned PMU by extracting that field and
+ * passing it to :c:func:`pmu_is_partitioned`
+ *
+ * Return: True if the KVM PMU is partitioned, false otherwise
+ */
+bool kvm_pmu_is_partitioned(struct kvm *kvm)
+{
+ return pmu_is_partitioned(kvm->arch.arm_pmu) &&
+ test_bit(KVM_ARCH_FLAG_PARTITION_PMU_ENABLED, &kvm->arch.flags);
+}
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index f9a0823666949..36960b9e52da2 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -95,6 +95,9 @@ void kvm_vcpu_pmu_resync_el0(void);
#define kvm_vcpu_has_pmu(vcpu) \
(vcpu_has_feature(vcpu, KVM_ARM_VCPU_PMU_V3))
+bool pmu_is_partitioned(struct arm_pmu *pmu);
+bool kvm_pmu_is_partitioned(struct kvm *kvm);
+
/*
* Updates the vcpu's view of the pmu events for this cpu.
* Must be called before every vcpu run after disabling interrupts, to ensure
@@ -134,6 +137,10 @@ static inline u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu,
{
return 0;
}
+static inline bool kvm_pmu_is_partitioned(struct kvm *kvm)
+{
+ return false;
+}
static inline void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu,
u64 select_idx, u64 val) {}
static inline void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu,
@@ -231,6 +238,11 @@ static inline bool kvm_pmu_counter_is_hyp(struct kvm_vcpu *vcpu, unsigned int id
static inline void kvm_pmu_nested_transition(struct kvm_vcpu *vcpu) {}
+static inline bool pmu_is_partitioned(void *pmu)
+{
+ return false;
+}
+
#endif
#endif
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* Re: [PATCH v2] power: supply: macsmc: Support macOS 27 SMC firmware
From: Janne Grunau @ 2026-06-12 19:56 UTC (permalink / raw)
To: Sasha Finkelstein
Cc: Sven Peter, Neal Gompa, Sebastian Reichel, asahi,
linux-arm-kernel, linux-pm, linux-kernel
In-Reply-To: <18396D14-17AF-4B43-AD6D-A88D2E7C44D3@chaosmail.tech>
On Fri, Jun 12, 2026 at 09:42:12PM +0200, Sasha Finkelstein wrote:
>
> > On Jun 12, 2026, at 21:33, Janne Grunau <j@jannau.net> wrote:
> >
> > I think the following is slightly easier to read
> >
> > {
> > if (power->bcf0_1byte) {
> > u8 tval = 0;
> > int ret = apple_smc_read_u8(power->smc, SMC_KEY(BCF0), &tval);
> > *val = tval;
> > return ret;
> > }
> >
> > return apple_smc_read_u32(power->smc, SMC_KEY(BCF0), val);
> > }
>
> I personally prefer using early returns as much as possible, as it makes
> it easier for me to read in linear order and due to line length reasons.
> But no strong opinion either way.
I don't disagree in general but in this case the code inside the 4 lines
inside the if are not much more complex than the early return. It
limits the scope of the local variables and avois a not on the
condition.
I can live with either version and the strength of my opinion will be
depleted by writing this reply.
Janne
^ permalink raw reply
* [PATCH 09/21] KVM: arm64: Add Partitioned PMU register trap handlers
From: Colton Lewis @ 2026-06-12 19:28 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
We may want a partitioned PMU but not have FEAT_FGT to untrap the
specific registers that would normally be untrapped. Add handling for
those trapped register accesses that does the right thing if the PMU
is partitioned.
For registers that shouldn't be written to hardware because they
require special handling (PMEVTYPER and PMOVS), write to the virtual
register. A later patch will ensure these are handled correctly at
vcpu_load time.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm64/kvm/pmu-direct.c | 30 ++++
arch/arm64/kvm/sys_regs.c | 265 +++++++++++++++++++++++++++++-------
include/kvm/arm_pmu.h | 7 +
3 files changed, 254 insertions(+), 48 deletions(-)
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index 0135989667564..43f04c2f33d44 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -9,6 +9,7 @@
#include <linux/perf/arm_pmuv3.h>
#include <asm/arm_pmuv3.h>
+#include <asm/kvm_emulate.h>
/**
* has_host_pmu_partition_support() - Determine if partitioning is possible
@@ -56,3 +57,32 @@ bool kvm_pmu_is_partitioned(struct kvm *kvm)
return pmu_is_partitioned(kvm->arch.arm_pmu) &&
test_bit(KVM_ARCH_FLAG_PARTITION_PMU_ENABLED, &kvm->arch.flags);
}
+
+void kvm_pmu_direct_pmcr_write(struct kvm_vcpu *vcpu, u64 val)
+{
+ bool reset_p = val & ARMV8_PMU_PMCR_P;
+ unsigned long mask;
+ int i;
+
+ val &= ~ARMV8_PMU_PMCR_P;
+
+ write_sysreg(val, pmcr_el0);
+
+ if (reset_p) {
+ mask = kvm_pmu_implemented_counter_mask(vcpu) & ~BIT(ARMV8_PMU_CYCLE_IDX);
+
+ if (!vcpu_is_el2(vcpu))
+ mask &= ~kvm_pmu_hyp_counter_mask(vcpu);
+
+ for_each_set_bit(i, &mask, ARMV8_PMU_MAX_GENERAL_COUNTERS)
+ write_pmevcntrn(i, 0);
+ }
+}
+
+u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu)
+{
+ return u64_replace_bits(
+ read_sysreg(pmcr_el0),
+ vcpu->kvm->arch.nr_pmu_counters,
+ ARMV8_PMU_PMCR_N);
+}
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index c52873a6f91ed..94572bc52c32a 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1070,9 +1070,192 @@ static u64 reset_pmcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
return __vcpu_sys_reg(vcpu, r->reg);
}
+/**
+ * pmu_reg_write() - Register writes for Partitioned PMU
+ * @vcpu: Pointer to vcpu
+ * @reg: vcpu register
+ * @val: value to write
+ * @set: setting or clearing a mask
+ *
+ * Helper for sys_reg.c register accessor functions.
+ */
+static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val, bool set)
+{
+ unsigned long flags;
+ u64 mask;
+ int idx;
+
+ switch (reg) {
+ case PMCR_EL0:
+ if (kvm_pmu_is_partitioned(vcpu->kvm))
+ kvm_pmu_direct_pmcr_write(vcpu, val);
+ else
+ kvm_pmu_handle_pmcr(vcpu, val);
+ break;
+ case PMSELR_EL0:
+ if (kvm_pmu_is_partitioned(vcpu->kvm))
+ write_sysreg(val, pmselr_el0);
+ else
+ __vcpu_assign_sys_reg(vcpu, reg, val);
+ break;
+ case PMEVCNTR0_EL0 ... PMCCNTR_EL0:
+ idx = reg - PMEVCNTR0_EL0;
+
+ if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (idx == ARMV8_PMU_CYCLE_IDX)
+ write_sysreg(val, pmccntr_el0);
+ else
+ write_pmevcntrn(idx, val);
+ } else {
+ kvm_pmu_set_counter_value(vcpu, idx, val);
+ }
+ break;
+ case PMEVTYPER0_EL0 ... PMCCFILTR_EL0:
+ idx = reg - PMEVTYPER0_EL0;
+
+ if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ mask = kvm_pmu_evtyper_mask(vcpu->kvm);
+ __vcpu_assign_sys_reg(vcpu, reg, val & mask);
+ } else {
+ kvm_pmu_set_counter_event_type(vcpu, val, idx);
+ kvm_vcpu_pmu_restore_guest(vcpu);
+ }
+ break;
+ case PMCNTENSET_EL0:
+ if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (set)
+ write_sysreg(val, pmcntenset_el0);
+ else
+ write_sysreg(val, pmcntenclr_el0);
+ } else {
+ if (set)
+ /* accessing PMCNTENSET_EL0 */
+ __vcpu_rmw_sys_reg(vcpu, PMCNTENSET_EL0, |=, val);
+ else
+ /* accessing PMINTENCLR_EL1 */
+ __vcpu_rmw_sys_reg(vcpu, PMCNTENSET_EL0, &=, ~val);
+
+ kvm_pmu_reprogram_counter_mask(vcpu, val);
+ }
+ break;
+ case PMINTENSET_EL1:
+ if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (set)
+ write_sysreg(val, pmintenset_el1);
+ else
+ write_sysreg(val, pmintenclr_el1);
+ } else {
+ if (set)
+ /* accessing PMINTENSET_EL1 */
+ __vcpu_rmw_sys_reg(vcpu, PMINTENSET_EL1, |=, val);
+ else
+ /* accessing PMINTENCLR_EL1 */
+ __vcpu_rmw_sys_reg(vcpu, PMINTENSET_EL1, &=, ~val);
+
+ kvm_pmu_reprogram_counter_mask(vcpu, val);
+ }
+ break;
+ case PMOVSSET_EL0:
+ local_irq_save(flags);
+ if (set)
+ /* accessing PMOVSSET_EL0 */
+ __vcpu_rmw_sys_reg(vcpu, PMOVSSET_EL0, |=, val);
+ else
+ /* accessing PMOVSCLR_EL0 */
+ __vcpu_rmw_sys_reg(vcpu, PMOVSSET_EL0, &=, ~val);
+ local_irq_restore(flags);
+ break;
+ case PMUSERENR_EL0:
+ if (kvm_pmu_is_partitioned(vcpu->kvm))
+ write_sysreg(val, pmuserenr_el0);
+ else
+ __vcpu_assign_sys_reg(vcpu, reg, val);
+ break;
+ default:
+ WARN_ON(1);
+ break;
+ }
+
+}
+
+/**
+ * pmu_reg_read() - Register reads for Partitioned PMU
+ * @vcpu: Pointer to vcpu
+ * @reg: vcpu register
+ *
+ * Helper for sys_reg.c register accessor functions.
+ *
+ * Return: value read
+ */
+static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
+{
+ u64 val = 0;
+ int idx;
+
+ switch (reg) {
+ case PMCR_EL0:
+ if (kvm_pmu_is_partitioned(vcpu->kvm))
+ val = kvm_pmu_direct_pmcr_read(vcpu);
+ else
+ val = kvm_vcpu_read_pmcr(vcpu);
+ break;
+ case PMSELR_EL0:
+ if (kvm_pmu_is_partitioned(vcpu->kvm))
+ val = read_sysreg(pmselr_el0);
+ else
+ val = __vcpu_sys_reg(vcpu, reg);
+ break;
+ case PMEVCNTR0_EL0 ... PMCCNTR_EL0:
+ idx = reg - PMEVCNTR0_EL0;
+
+ if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (idx == ARMV8_PMU_CYCLE_IDX)
+ val = read_sysreg(pmccntr_el0);
+ else
+ val = read_pmevcntrn(idx);
+ } else {
+ val = kvm_pmu_get_counter_value(vcpu, idx);
+ }
+ break;
+ case PMEVTYPER0_EL0 ... PMCCFILTR_EL0:
+ val = __vcpu_sys_reg(vcpu, reg);
+ break;
+ case PMCNTENSET_EL0:
+ if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ val = read_sysreg(pmcntenset_el0);
+ val &= kvm_pmu_guest_counter_mask(vcpu->kvm->arch.arm_pmu);
+ } else {
+ val = __vcpu_sys_reg(vcpu, reg);
+ }
+ break;
+ case PMINTENSET_EL1:
+ if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ val = read_sysreg(pmintenset_el1);
+ val &= kvm_pmu_guest_counter_mask(vcpu->kvm->arch.arm_pmu);
+ } else {
+ val = __vcpu_sys_reg(vcpu, reg);
+ }
+ break;
+ case PMOVSSET_EL0:
+ val = __vcpu_sys_reg(vcpu, reg);
+ break;
+ case PMUSERENR_EL0:
+ if (kvm_pmu_is_partitioned(vcpu->kvm))
+ val = read_sysreg(pmuserenr_el0);
+ else
+ val = __vcpu_sys_reg(vcpu, reg);
+ break;
+ default:
+ WARN_ON(1);
+ break;
+ }
+
+ return val;
+}
+
static bool check_pmu_access_disabled(struct kvm_vcpu *vcpu, u64 flags)
{
- u64 reg = __vcpu_sys_reg(vcpu, PMUSERENR_EL0);
+ u64 reg = pmu_reg_read(vcpu, PMUSERENR_EL0);
bool enabled = (reg & flags) || vcpu_mode_priv(vcpu);
if (!enabled)
@@ -1111,18 +1294,17 @@ static bool access_pmcr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
if (p->is_write) {
/*
- * Only update writeable bits of PMCR (continuing into
- * kvm_pmu_handle_pmcr() as well)
+ * Only update writeable bits of PMCR
*/
- val = kvm_vcpu_read_pmcr(vcpu);
+ val = pmu_reg_read(vcpu, PMCR_EL0);
val &= ~ARMV8_PMU_PMCR_MASK;
val |= p->regval & ARMV8_PMU_PMCR_MASK;
if (!kvm_supports_32bit_el0())
val |= ARMV8_PMU_PMCR_LC;
- kvm_pmu_handle_pmcr(vcpu, val);
+ pmu_reg_write(vcpu, PMCR_EL0, val, 0);
} else {
/* PMCR.P & PMCR.C are RAZ */
- val = kvm_vcpu_read_pmcr(vcpu)
+ val = pmu_reg_read(vcpu, PMCR_EL0)
& ~(ARMV8_PMU_PMCR_P | ARMV8_PMU_PMCR_C);
p->regval = val;
}
@@ -1137,10 +1319,10 @@ static bool access_pmselr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
return false;
if (p->is_write)
- __vcpu_assign_sys_reg(vcpu, PMSELR_EL0, p->regval);
+ pmu_reg_write(vcpu, PMSELR_EL0, p->regval, 0);
else
/* return PMSELR.SEL field */
- p->regval = __vcpu_sys_reg(vcpu, PMSELR_EL0)
+ p->regval = pmu_reg_read(vcpu, PMSELR_EL0)
& PMSELR_EL0_SEL_MASK;
return true;
@@ -1217,6 +1399,7 @@ static bool access_pmu_evcntr(struct kvm_vcpu *vcpu,
struct sys_reg_params *p,
const struct sys_reg_desc *r)
{
+ enum vcpu_sysreg reg;
u64 idx = ~0UL;
if (r->CRn == 9 && r->CRm == 13) {
@@ -1226,7 +1409,7 @@ static bool access_pmu_evcntr(struct kvm_vcpu *vcpu,
return false;
idx = SYS_FIELD_GET(PMSELR_EL0, SEL,
- __vcpu_sys_reg(vcpu, PMSELR_EL0));
+ pmu_reg_read(vcpu, PMSELR_EL0));
} else if (r->Op2 == 0) {
/* PMCCNTR_EL0 */
if (pmu_access_cycle_counter_el0_disabled(vcpu))
@@ -1254,18 +1437,21 @@ static bool access_pmu_evcntr(struct kvm_vcpu *vcpu,
if (!pmu_counter_idx_valid(vcpu, idx))
return false;
+ reg = PMEVCNTR0_EL0 + idx;
+
if (p->is_write) {
if (pmu_access_el0_disabled(vcpu))
return false;
- kvm_pmu_set_counter_value(vcpu, idx, p->regval);
+ pmu_reg_write(vcpu, reg, p->regval, 0);
} else {
- p->regval = kvm_pmu_get_counter_value(vcpu, idx);
+ p->regval = pmu_reg_read(vcpu, reg);
}
return true;
}
+
static bool access_pmu_evtyper(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
const struct sys_reg_desc *r)
{
@@ -1276,7 +1462,7 @@ static bool access_pmu_evtyper(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
if (r->CRn == 9 && r->CRm == 13 && r->Op2 == 1) {
/* PMXEVTYPER_EL0 */
- idx = SYS_FIELD_GET(PMSELR_EL0, SEL, __vcpu_sys_reg(vcpu, PMSELR_EL0));
+ idx = SYS_FIELD_GET(PMSELR_EL0, SEL, pmu_reg_read(vcpu, PMSELR_EL0));
reg = PMEVTYPER0_EL0 + idx;
} else if (r->CRn == 14 && (r->CRm & 12) == 12) {
idx = ((r->CRm & 3) << 3) | (r->Op2 & 7);
@@ -1292,12 +1478,10 @@ static bool access_pmu_evtyper(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
if (!pmu_counter_idx_valid(vcpu, idx))
return false;
- if (p->is_write) {
- kvm_pmu_set_counter_event_type(vcpu, p->regval, idx);
- kvm_vcpu_pmu_restore_guest(vcpu);
- } else {
- p->regval = __vcpu_sys_reg(vcpu, reg);
- }
+ if (p->is_write)
+ pmu_reg_write(vcpu, reg, p->regval, 0);
+ else
+ p->regval = pmu_reg_read(vcpu, reg);
return true;
}
@@ -1331,16 +1515,9 @@ static bool access_pmcnten(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
mask = kvm_pmu_accessible_counter_mask(vcpu);
if (p->is_write) {
val = p->regval & mask;
- if (r->Op2 & 0x1)
- /* accessing PMCNTENSET_EL0 */
- __vcpu_rmw_sys_reg(vcpu, PMCNTENSET_EL0, |=, val);
- else
- /* accessing PMCNTENCLR_EL0 */
- __vcpu_rmw_sys_reg(vcpu, PMCNTENSET_EL0, &=, ~val);
-
- kvm_pmu_reprogram_counter_mask(vcpu, val);
+ pmu_reg_write(vcpu, PMCNTENSET_EL0, val, r->Op2 & 0x1);
} else {
- p->regval = __vcpu_sys_reg(vcpu, PMCNTENSET_EL0);
+ p->regval = pmu_reg_read(vcpu, PMCNTENSET_EL0);
}
return true;
@@ -1349,22 +1526,17 @@ static bool access_pmcnten(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
static bool access_pminten(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
const struct sys_reg_desc *r)
{
- u64 mask = kvm_pmu_accessible_counter_mask(vcpu);
+ u64 val, mask;
if (check_pmu_access_disabled(vcpu, 0))
return false;
+ mask = kvm_pmu_accessible_counter_mask(vcpu);
if (p->is_write) {
- u64 val = p->regval & mask;
-
- if (r->Op2 & 0x1)
- /* accessing PMINTENSET_EL1 */
- __vcpu_rmw_sys_reg(vcpu, PMINTENSET_EL1, |=, val);
- else
- /* accessing PMINTENCLR_EL1 */
- __vcpu_rmw_sys_reg(vcpu, PMINTENSET_EL1, &=, ~val);
+ val = p->regval & mask;
+ pmu_reg_write(vcpu, PMINTENSET_EL1, val, r->Op2 & 0x1);
} else {
- p->regval = __vcpu_sys_reg(vcpu, PMINTENSET_EL1);
+ p->regval = pmu_reg_read(vcpu, PMINTENSET_EL1);
}
return true;
@@ -1373,20 +1545,18 @@ static bool access_pminten(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
static bool access_pmovs(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
const struct sys_reg_desc *r)
{
- u64 mask = kvm_pmu_accessible_counter_mask(vcpu);
+ u64 val, mask;
if (pmu_access_el0_disabled(vcpu))
return false;
+ mask = kvm_pmu_accessible_counter_mask(vcpu);
+
if (p->is_write) {
- if (r->CRm & 0x2)
- /* accessing PMOVSSET_EL0 */
- __vcpu_rmw_sys_reg(vcpu, PMOVSSET_EL0, |=, (p->regval & mask));
- else
- /* accessing PMOVSCLR_EL0 */
- __vcpu_rmw_sys_reg(vcpu, PMOVSSET_EL0, &=, ~(p->regval & mask));
+ val = p->regval & mask;
+ pmu_reg_write(vcpu, PMOVSSET_EL0, val, r->CRm & 0x2);
} else {
- p->regval = __vcpu_sys_reg(vcpu, PMOVSSET_EL0);
+ p->regval = pmu_reg_read(vcpu, PMOVSSET_EL0);
}
return true;
@@ -1415,10 +1585,9 @@ static bool access_pmuserenr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
if (!vcpu_mode_priv(vcpu))
return undef_access(vcpu, p, r);
- __vcpu_assign_sys_reg(vcpu, PMUSERENR_EL0,
- (p->regval & ARMV8_PMU_USERENR_MASK));
+ pmu_reg_write(vcpu, PMUSERENR_EL0, p->regval & ARMV8_PMU_USERENR_MASK, 0);
} else {
- p->regval = __vcpu_sys_reg(vcpu, PMUSERENR_EL0)
+ p->regval = pmu_reg_read(vcpu, PMUSERENR_EL0)
& ARMV8_PMU_USERENR_MASK;
}
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 36960b9e52da2..700d5f275b557 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -97,6 +97,8 @@ void kvm_vcpu_pmu_resync_el0(void);
bool pmu_is_partitioned(struct arm_pmu *pmu);
bool kvm_pmu_is_partitioned(struct kvm *kvm);
+void kvm_pmu_direct_pmcr_write(struct kvm_vcpu *vcpu, u64 val);
+u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu);
/*
* Updates the vcpu's view of the pmu events for this cpu.
@@ -141,6 +143,11 @@ static inline bool kvm_pmu_is_partitioned(struct kvm *kvm)
{
return false;
}
+static inline void kvm_pmu_direct_pmcr_write(struct kvm_vcpu *vcpu, u64 val) {}
+static inline u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu)
+{
+ return 0;
+}
static inline void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu,
u64 select_idx, u64 val) {}
static inline void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu,
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* [PATCH 11/21] KVM: arm64: Context swap Partitioned PMU guest registers
From: Colton Lewis @ 2026-06-12 19:28 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Save and restore newly untrapped registers that can be directly
accessed by the guest when the PMU is partitioned.
* PMEVCNTRn_EL0
* PMCCNTR_EL0
* PMSELR_EL0
* PMCR_EL0
* PMCNTEN_EL0
* PMINTEN_EL1
If we know we are not partitioned (that is, using the emulated vPMU),
then return immediately. A later patch will make this lazy so the
context swaps don't happen unless the guest has accessed the PMU.
PMEVTYPER is handled in a following patch since we must apply the KVM
event filter before writing values to hardware.
PMOVS guest counters are cleared to avoid the possibility of
generating spurious interrupts when PMINTEN is written. This is fine
because the virtual register for PMOVS is always the canonical value.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm/include/asm/arm_pmuv3.h | 4 +
arch/arm64/kvm/arm.c | 2 +
arch/arm64/kvm/pmu-direct.c | 183 +++++++++++++++++++++++++++++++
include/kvm/arm_pmu.h | 16 +++
4 files changed, 205 insertions(+)
diff --git a/arch/arm/include/asm/arm_pmuv3.h b/arch/arm/include/asm/arm_pmuv3.h
index eedf58ea01b10..f6031bd522718 100644
--- a/arch/arm/include/asm/arm_pmuv3.h
+++ b/arch/arm/include/asm/arm_pmuv3.h
@@ -235,6 +235,10 @@ static inline bool pmu_is_partitioned(struct arm_pmu *pmu)
{
return false;
}
+static inline u64 kvm_pmu_host_counter_mask(struct arm_pmu *pmu)
+{
+ return ~0;
+}
/* PMU Version in DFR Register */
#define ARMV8_PMU_DFR_VER_NI 0
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 9453321ef8c67..24f63edc8b384 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -700,6 +700,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
kvm_vcpu_load_vhe(vcpu);
kvm_arch_vcpu_load_fp(vcpu);
kvm_vcpu_pmu_restore_guest(vcpu);
+ kvm_pmu_load(vcpu);
if (kvm_arm_is_pvtime_enabled(&vcpu->arch))
kvm_make_request(KVM_REQ_RECORD_STEAL, vcpu);
@@ -743,6 +744,7 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
kvm_timer_vcpu_put(vcpu);
kvm_vgic_put(vcpu);
kvm_vcpu_pmu_restore_host(vcpu);
+ kvm_pmu_put(vcpu);
if (vcpu_has_nv(vcpu))
kvm_vcpu_put_hw_mmu(vcpu);
kvm_arm_vmid_clear_active();
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index 43f04c2f33d44..79022447cfb9a 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -86,3 +86,186 @@ u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu)
vcpu->kvm->arch.nr_pmu_counters,
ARMV8_PMU_PMCR_N);
}
+
+/**
+ * kvm_pmu_host_counter_mask() - Compute bitmask of host-reserved counters
+ * @pmu: Pointer to arm_pmu struct
+ *
+ * Compute the bitmask that selects the host-reserved counters in the
+ * {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers. These are the counters
+ * in HPMN..N
+ *
+ * Return: Bitmask
+ */
+u64 kvm_pmu_host_counter_mask(struct arm_pmu *pmu)
+{
+ u8 nr_counters = *host_data_ptr(nr_event_counters);
+
+ if (pmu_is_partitioned(pmu))
+ return GENMASK_ULL(nr_counters - 1, pmu->max_guest_counters);
+
+ return ARMV8_PMU_CNT_MASK_ALL;
+}
+
+/**
+ * kvm_pmu_guest_counter_mask() - Compute bitmask of guest-reserved counters
+ * @pmu: Pointer to arm_pmu struct
+ *
+ * Compute the bitmask that selects the guest-reserved counters in the
+ * {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers. These are the counters
+ * in 0..HPMN and the cycle and instruction counters.
+ *
+ * Return: Bitmask
+ */
+u64 kvm_pmu_guest_counter_mask(struct arm_pmu *pmu)
+{
+ if (pmu_is_partitioned(pmu)) {
+ u64 mask = ARMV8_PMU_CNT_MASK_C;
+
+ if (pmu->max_guest_counters > 0)
+ mask |= GENMASK_ULL(pmu->max_guest_counters - 1, 0);
+
+ return mask;
+ }
+
+ return 0;
+}
+
+/**
+ * kvm_pmu_load() - Load untrapped PMU registers
+ * @vcpu: Pointer to struct kvm_vcpu
+ *
+ * Load all untrapped PMU registers from the VCPU into the PCPU. Mask
+ * to only bits belonging to guest-reserved counters and leave
+ * host-reserved counters alone in bitmask registers.
+ */
+void kvm_pmu_load(struct kvm_vcpu *vcpu)
+{
+ struct arm_pmu *pmu;
+ unsigned long guest_counters;
+ u64 mask;
+ u8 i;
+ u64 val;
+
+ /*
+ * If we aren't guest-owned then we know the guest isn't using
+ * the PMU anyway, so no need to bother with the swap.
+ */
+ if (!kvm_pmu_is_partitioned(vcpu->kvm))
+ return;
+
+ preempt_disable();
+
+ pmu = vcpu->kvm->arch.arm_pmu;
+ guest_counters = kvm_pmu_guest_counter_mask(pmu);
+
+ for_each_set_bit(i, &guest_counters, ARMPMU_MAX_HWEVENTS) {
+ val = __vcpu_sys_reg(vcpu, PMEVCNTR0_EL0 + i);
+
+ if (i == ARMV8_PMU_CYCLE_IDX)
+ write_pmccntr(val);
+ else
+ write_pmevcntrn(i, val);
+ }
+
+ val = __vcpu_sys_reg(vcpu, PMSELR_EL0);
+ write_sysreg(val, pmselr_el0);
+
+ /* Save only the stateful writable bits. */
+ val = __vcpu_sys_reg(vcpu, PMCR_EL0);
+ mask = ARMV8_PMU_PMCR_MASK &
+ ~(ARMV8_PMU_PMCR_P | ARMV8_PMU_PMCR_C);
+ write_sysreg(val & mask, pmcr_el0);
+
+ /*
+ * When handling these:
+ * 1. Apply only the bits for guest counters (indicated by mask)
+ * 2. Use the different registers for set and clear
+ */
+ mask = kvm_pmu_guest_counter_mask(pmu);
+
+ /* Clear the hardware overflow flags so there is no chance of
+ * creating spurious interrupts. The hardware here is never
+ * the canonical version anyway.
+ */
+ write_sysreg(mask, pmovsclr_el0);
+
+ val = __vcpu_sys_reg(vcpu, PMCNTENSET_EL0);
+ write_sysreg(val & mask, pmcntenset_el0);
+ write_sysreg(~val & mask, pmcntenclr_el0);
+
+ val = __vcpu_sys_reg(vcpu, PMINTENSET_EL1);
+ write_sysreg(val & mask, pmintenset_el1);
+ write_sysreg(~val & mask, pmintenclr_el1);
+
+ preempt_enable();
+}
+
+/**
+ * kvm_pmu_put() - Put untrapped PMU registers
+ * @vcpu: Pointer to struct kvm_vcpu
+ *
+ * Put all untrapped PMU registers from the VCPU into the PCPU. Mask
+ * to only bits belonging to guest-reserved counters and leave
+ * host-reserved counters alone in bitmask registers.
+ */
+void kvm_pmu_put(struct kvm_vcpu *vcpu)
+{
+ struct arm_pmu *pmu;
+ unsigned long guest_counters;
+ unsigned long flags;
+ u64 mask;
+ u8 i;
+ u64 val;
+
+ /*
+ * If we aren't guest-owned then we know the guest is not
+ * accessing the PMU anyway, so no need to bother with the
+ * swap.
+ */
+ if (!kvm_pmu_is_partitioned(vcpu->kvm))
+ return;
+
+ preempt_disable();
+
+ pmu = vcpu->kvm->arch.arm_pmu;
+ guest_counters = kvm_pmu_guest_counter_mask(pmu);
+
+ for_each_set_bit(i, &guest_counters, ARMPMU_MAX_HWEVENTS) {
+ if (i == ARMV8_PMU_CYCLE_IDX)
+ val = read_pmccntr();
+ else
+ val = read_pmevcntrn(i);
+
+ __vcpu_assign_sys_reg(vcpu, PMEVCNTR0_EL0 + i, val);
+ }
+
+ val = read_sysreg(pmselr_el0);
+ __vcpu_assign_sys_reg(vcpu, PMSELR_EL0, val);
+
+ val = read_sysreg(pmcr_el0);
+ __vcpu_assign_sys_reg(vcpu, PMCR_EL0, val);
+
+ /* Mask these to only save the guest relevant bits. */
+ mask = kvm_pmu_guest_counter_mask(pmu);
+
+ val = read_sysreg(pmcntenset_el0);
+ __vcpu_assign_sys_reg(vcpu, PMCNTENSET_EL0, val & mask);
+
+ val = read_sysreg(pmintenset_el1);
+ __vcpu_assign_sys_reg(vcpu, PMINTENSET_EL1, val & mask);
+
+ /* Save pending guest hardware overflows. */
+ local_irq_save(flags);
+ val = read_sysreg(pmovsset_el0);
+ __vcpu_rmw_sys_reg(vcpu, PMOVSSET_EL0, |=, val & mask);
+ write_sysreg(val & mask, pmovsclr_el0);
+ local_irq_restore(flags);
+
+ /* Stop guest counters and disable interrupts in hardware. */
+ write_sysreg(mask, pmcntenclr_el0);
+ write_sysreg(mask, pmintenclr_el1);
+
+ kvm_pmu_set_guest_counters(pmu, 0);
+ preempt_enable();
+}
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 700d5f275b557..61f8d4ed35e10 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -99,6 +99,10 @@ bool pmu_is_partitioned(struct arm_pmu *pmu);
bool kvm_pmu_is_partitioned(struct kvm *kvm);
void kvm_pmu_direct_pmcr_write(struct kvm_vcpu *vcpu, u64 val);
u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu);
+u64 kvm_pmu_host_counter_mask(struct arm_pmu *pmu);
+u64 kvm_pmu_guest_counter_mask(struct arm_pmu *pmu);
+void kvm_pmu_load(struct kvm_vcpu *vcpu);
+void kvm_pmu_put(struct kvm_vcpu *vcpu);
/*
* Updates the vcpu's view of the pmu events for this cpu.
@@ -148,6 +152,8 @@ static inline u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu)
{
return 0;
}
+static inline void kvm_pmu_load(struct kvm_vcpu *vcpu) {}
+static inline void kvm_pmu_put(struct kvm_vcpu *vcpu) {}
static inline void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu,
u64 select_idx, u64 val) {}
static inline void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu,
@@ -250,6 +256,16 @@ static inline bool pmu_is_partitioned(void *pmu)
return false;
}
+static inline u64 kvm_pmu_host_counter_mask(void *kvm)
+{
+ return ~0;
+}
+
+static inline u64 kvm_pmu_guest_counter_mask(void *kvm)
+{
+ return 0;
+}
+
#endif
#endif
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* Re: [PATCH v2] power: supply: macsmc: Support macOS 27 SMC firmware
From: Joshua Peisach @ 2026-06-12 19:53 UTC (permalink / raw)
To: Sasha Finkelstein, Sven Peter, Janne Grunau, Neal Gompa,
Sebastian Reichel
Cc: asahi, linux-arm-kernel, linux-pm, linux-kernel
In-Reply-To: <20260612-gate-power-v2-1-de76cb9bb7ef@chaosmail.tech>
On Fri Jun 12, 2026 at 2:36 PM EDT, Sasha Finkelstein wrote:
> The SMC firmware included in macOS 27 changed the size of BCF0 key from
> 4 to 1 bytes. This key is used for indicating that battery state is
> critically low.
>
> Reviewed-by: Sven Peter <sven@kernel.org>
> Signed-off-by: Sasha Finkelstein <k@chaosmail.tech>
> ---
> Changes in v2:
> - Addressing review feedback
> - Link to v1: https://patch.msgid.link/20260611-gate-power-v1-1-8a62721086c7@chaosmail.tech
> ---
> drivers/power/supply/macsmc-power.c | 35 ++++++++++++++++++++++++++++++++---
> 1 file changed, 32 insertions(+), 3 deletions(-)
>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
^ permalink raw reply
* [PATCH 18/21] KVM: arm64: Add vCPU device attr to partition the PMU
From: Colton Lewis @ 2026-06-12 19:29 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Add a new PMU device attr to enable the partitioned PMU for a given
VM. This capability can be set when the PMU is initially configured
before the vCPU starts running and is allowed where PMUv3 and VHE are
supported and the host driver was configured with
arm_pmuv3.reserved_host_counters.
The enabled capability is tracked by the new flag
KVM_ARCH_FLAG_PARTITION_PMU_ENABLED.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm64/include/uapi/asm/kvm.h | 2 ++
arch/arm64/kvm/pmu-direct.c | 30 ++++++++++++++++++++++++++++++
arch/arm64/kvm/pmu.c | 23 +++++++++++++++++++++++
include/kvm/arm_pmu.h | 9 +++++++++
4 files changed, 64 insertions(+)
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 1c13bfa2d38aa..7f57b8c132925 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -437,6 +437,8 @@ enum {
#define KVM_ARM_VCPU_PMU_V3_FILTER 2
#define KVM_ARM_VCPU_PMU_V3_SET_PMU 3
#define KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS 4
+#define KVM_ARM_VCPU_PMU_V3_ENABLE_PARTITION 5
+
#define KVM_ARM_VCPU_TIMER_CTRL 1
#define KVM_ARM_VCPU_TIMER_IRQ_VTIMER 0
#define KVM_ARM_VCPU_TIMER_IRQ_PTIMER 1
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index 0062d1d8e1999..2d2294b78ebe0 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -24,6 +24,36 @@ bool has_host_pmu_partition_support(void)
system_supports_pmuv3();
}
+
+/**
+ * has_kvm_pmu_partition_support() - If we can enable/disable partition
+ *
+ * Return: true if allowed, false otherwise.
+ */
+bool has_kvm_pmu_partition_support(void)
+{
+ return has_host_pmu_partition_support() &&
+ kvm_supports_guest_pmuv3() &&
+ armv8pmu_is_partitioned;
+}
+
+/**
+ * kvm_pmu_partition_enable() - Enable/disable partition flag
+ * @kvm: Pointer to vcpu
+ * @enable: Whether to enable or disable
+ *
+ * If we want to enable the partition, the guest is free to grab
+ * hardware by accessing PMU registers. Otherwise, the host maintains
+ * control.
+ */
+void kvm_pmu_partition_enable(struct kvm *kvm, bool enable)
+{
+ if (enable)
+ set_bit(KVM_ARCH_FLAG_PARTITION_PMU_ENABLED, &kvm->arch.flags);
+ else
+ clear_bit(KVM_ARCH_FLAG_PARTITION_PMU_ENABLED, &kvm->arch.flags);
+}
+
/**
* pmu_is_partitioned() - Determine if given PMU is partitioned
* @pmu: Pointer to arm_pmu struct
diff --git a/arch/arm64/kvm/pmu.c b/arch/arm64/kvm/pmu.c
index f5ee18b4dfae7..4e15948ac2565 100644
--- a/arch/arm64/kvm/pmu.c
+++ b/arch/arm64/kvm/pmu.c
@@ -769,6 +769,28 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
return kvm_arm_pmu_v3_set_nr_counters(vcpu, n);
}
+ case KVM_ARM_VCPU_PMU_V3_ENABLE_PARTITION: {
+ unsigned int __user *uaddr = (unsigned int __user *)(long)attr->addr;
+ u32 val;
+
+ if (get_user(val, uaddr))
+ return -EFAULT;
+
+ if (!has_kvm_pmu_partition_support())
+ return -EPERM;
+
+ if (kvm_vm_has_ran_once(kvm))
+ return -EBUSY;
+
+ kvm_pmu_partition_enable(kvm, val);
+ if (val) {
+ unsigned int max_counters = kvm_arm_pmu_get_max_counters(kvm);
+
+ if (kvm->arch.nr_pmu_counters > max_counters)
+ kvm_arm_set_nr_counters(kvm, max_counters);
+ }
+ return 0;
+ }
case KVM_ARM_VCPU_PMU_V3_INIT:
return kvm_arm_pmu_v3_init(vcpu);
}
@@ -808,6 +830,7 @@ int kvm_arm_pmu_v3_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
case KVM_ARM_VCPU_PMU_V3_FILTER:
case KVM_ARM_VCPU_PMU_V3_SET_PMU:
case KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS:
+ case KVM_ARM_VCPU_PMU_V3_ENABLE_PARTITION:
if (kvm_vcpu_has_pmu(vcpu))
return 0;
}
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index f72d080ee7ba2..6a5572994b7fa 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -99,6 +99,8 @@ bool kvm_pmu_part_overflow_status(struct kvm_vcpu *vcpu);
#define kvm_vcpu_has_pmu(vcpu) \
(vcpu_has_feature(vcpu, KVM_ARM_VCPU_PMU_V3))
+bool has_kvm_pmu_partition_support(void);
+void kvm_pmu_partition_enable(struct kvm *kvm, bool enable);
bool pmu_is_partitioned(struct arm_pmu *pmu);
bool kvm_pmu_is_partitioned(struct kvm *kvm);
void kvm_pmu_direct_pmcr_write(struct kvm_vcpu *vcpu, u64 val);
@@ -279,6 +281,13 @@ static inline u64 kvm_pmu_guest_counter_mask(void *kvm)
static inline void kvm_pmu_handle_guest_irq(struct arm_pmu *pmu, u64 pmovsr) {}
+static inline bool has_kvm_pmu_partition_support(void)
+{
+ return false;
+}
+
+static inline void kvm_pmu_partition_enable(struct kvm *kvm, bool enable) {}
+
#endif
#endif
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* [PATCH 05/21] perf: arm_pmuv3: Check cntr_mask before using pmccntr
From: Colton Lewis @ 2026-06-12 19:28 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Check cntr_mask before using pmccntr to ensure it's available. With a
partitioned PMU, there may be instances where pmccntr is being used by
the guest and will be absent from cntr_mask.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
drivers/perf/arm_pmuv3.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c
index 1cceb1f614515..17bb1cfdc271c 100644
--- a/drivers/perf/arm_pmuv3.c
+++ b/drivers/perf/arm_pmuv3.c
@@ -1028,7 +1028,8 @@ static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc,
/* Always prefer to place a cycle counter into the cycle counter. */
if (armv8pmu_can_use_pmccntr(cpuc, event)) {
- if (!test_and_set_bit(ARMV8_PMU_CYCLE_IDX, cpuc->used_mask))
+ if (test_bit(ARMV8_PMU_CYCLE_IDX, cpu_pmu->cntr_mask) &&
+ !test_and_set_bit(ARMV8_PMU_CYCLE_IDX, cpuc->used_mask))
return ARMV8_PMU_CYCLE_IDX;
else if (armv8pmu_event_is_64bit(event) &&
armv8pmu_event_want_user_access(event) &&
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* [PATCH 16/21] perf: arm_pmuv3: Handle IRQs for Partitioned PMU guest counters
From: Colton Lewis @ 2026-06-12 19:29 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Because ARM hardware is not yet capable of direct PPI injection into
guests, guest counters will still trigger interrupts that need to be
handled by the host PMU interrupt handler. Clear the overflow flags in
hardware to handle the interrupt as normal, but update the virtual overflow register for later injecting the interrupt into the guest.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm/include/asm/arm_pmuv3.h | 6 ++++++
arch/arm64/include/asm/arm_pmuv3.h | 5 +++++
arch/arm64/kvm/pmu-direct.c | 22 ++++++++++++++++++++++
drivers/perf/arm_pmuv3.c | 24 +++++++++++++++++-------
include/kvm/arm_pmu.h | 3 +++
5 files changed, 53 insertions(+), 7 deletions(-)
diff --git a/arch/arm/include/asm/arm_pmuv3.h b/arch/arm/include/asm/arm_pmuv3.h
index f6031bd522718..896fc5d6add0c 100644
--- a/arch/arm/include/asm/arm_pmuv3.h
+++ b/arch/arm/include/asm/arm_pmuv3.h
@@ -180,6 +180,11 @@ static inline void write_pmintenset(u32 val)
write_sysreg(val, PMINTENSET);
}
+static inline u32 read_pmintenset(void)
+{
+ return read_sysreg(PMINTENSET);
+}
+
static inline void write_pmintenclr(u32 val)
{
write_sysreg(val, PMINTENCLR);
@@ -239,6 +244,7 @@ static inline u64 kvm_pmu_host_counter_mask(struct arm_pmu *pmu)
{
return ~0;
}
+static inline void kvm_pmu_handle_guest_irq(struct arm_pmu *pmu, u64 pmovsr) {}
/* PMU Version in DFR Register */
#define ARMV8_PMU_DFR_VER_NI 0
diff --git a/arch/arm64/include/asm/arm_pmuv3.h b/arch/arm64/include/asm/arm_pmuv3.h
index 27c4d6d47da31..69ff4d014bf39 100644
--- a/arch/arm64/include/asm/arm_pmuv3.h
+++ b/arch/arm64/include/asm/arm_pmuv3.h
@@ -110,6 +110,11 @@ static inline void write_pmintenset(u64 val)
write_sysreg(val, pmintenset_el1);
}
+static inline u64 read_pmintenset(void)
+{
+ return read_sysreg(pmintenset_el1);
+}
+
static inline void write_pmintenclr(u64 val)
{
write_sysreg(val, pmintenclr_el1);
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index bb1f3dca03869..64f40cfb31012 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -405,3 +405,25 @@ void kvm_pmu_set_guest_owned(struct kvm_vcpu *vcpu)
kvm_arm_setup_mdcr_el2(vcpu);
}
}
+
+/**
+ * kvm_pmu_handle_guest_irq() - Record IRQs in guest counters
+ * @pmu: PMU to check for overflows
+ * @pmovsr: Overflow flags reported by driver
+ *
+ * Set overflow flags in guest-reserved counters in the VCPU register
+ * for the guest to clear later.
+ */
+void kvm_pmu_handle_guest_irq(struct arm_pmu *pmu, u64 pmovsr)
+{
+ struct kvm_vcpu *vcpu = kvm_get_running_vcpu();
+ u64 mask = kvm_pmu_guest_counter_mask(pmu);
+ u64 govf = pmovsr & mask;
+
+ write_pmovsclr(govf);
+
+ if (!vcpu)
+ return;
+
+ __vcpu_rmw_sys_reg(vcpu, PMOVSSET_EL0, |=, govf);
+}
diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c
index c187397134990..6ab15a5209608 100644
--- a/drivers/perf/arm_pmuv3.c
+++ b/drivers/perf/arm_pmuv3.c
@@ -774,16 +774,15 @@ static void armv8pmu_disable_event_irq(struct perf_event *event)
armv8pmu_disable_intens(BIT(event->hw.idx));
}
-static u64 armv8pmu_getreset_flags(void)
+static u64 armv8pmu_getovf_flags(void)
{
u64 value;
/* Read */
value = read_pmovsclr();
- /* Write to clear flags */
- value &= ARMV8_PMU_CNT_MASK_ALL;
- write_pmovsclr(value);
+ /* Only report interrupt enabled counters. */
+ value &= read_pmintenset();
return value;
}
@@ -897,16 +896,17 @@ static void read_branch_records(struct pmu_hw_events *cpuc,
static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
{
- u64 pmovsr;
struct perf_sample_data data;
struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
struct pt_regs *regs;
+ u64 host_set = kvm_pmu_host_counter_mask(cpu_pmu);
+ u64 pmovsr;
int idx;
/*
- * Get and reset the IRQ flags
+ * Get the IRQ flags
*/
- pmovsr = armv8pmu_getreset_flags();
+ pmovsr = armv8pmu_getovf_flags();
/*
* Did an overflow occur?
@@ -914,6 +914,12 @@ static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
if (!armv8pmu_has_overflowed(pmovsr))
return IRQ_NONE;
+ /*
+ * Guest flag reset is handled the kvm hook at the bottom of
+ * this function.
+ */
+ write_pmovsclr(pmovsr & host_set);
+
/*
* Handle the counter(s) overflow(s)
*/
@@ -955,6 +961,10 @@ static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
*/
perf_event_overflow(event, &data, regs);
}
+
+ if (pmu_is_partitioned(cpu_pmu))
+ kvm_pmu_handle_guest_irq(cpu_pmu, pmovsr);
+
armv8pmu_start(cpu_pmu);
return IRQ_HANDLED;
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index b77ddb94dc99b..25163a689ae80 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -106,6 +106,7 @@ u64 kvm_pmu_guest_counter_mask(struct arm_pmu *pmu);
void kvm_pmu_load(struct kvm_vcpu *vcpu);
void kvm_pmu_put(struct kvm_vcpu *vcpu);
void kvm_pmu_set_guest_owned(struct kvm_vcpu *vcpu);
+void kvm_pmu_handle_guest_irq(struct arm_pmu *pmu, u64 pmovsr);
#define kvm_pmu_get_access(vcpu) ((vcpu)->arch.pmu.access)
@@ -274,6 +275,8 @@ static inline u64 kvm_pmu_guest_counter_mask(void *kvm)
return 0;
}
+static inline void kvm_pmu_handle_guest_irq(struct arm_pmu *pmu, u64 pmovsr) {}
+
#endif
#endif
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* [PATCH 15/21] KVM: arm64: Implement lazy PMU context swaps
From: Colton Lewis @ 2026-06-12 19:29 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Since many guests will never touch the PMU, they need not pay the cost
of context swapping those registers.
Use an enum to implement a simple state machine for PMU register
access. The PMU is either free or guest owned. We only need to context
swap if the PMU registers are guest owned. The PMU initially starts as
free and only transitions to guest owned if a guest has touched the
PMU registers.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/include/asm/kvm_types.h | 6 +++++-
arch/arm64/kvm/debug.c | 5 +++--
arch/arm64/kvm/pmu-direct.c | 21 +++++++++++++++++++--
arch/arm64/kvm/sys_regs.c | 29 ++++++++++++++++-------------
include/kvm/arm_pmu.h | 8 ++++++++
6 files changed, 52 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 9c7e9b92dfbd3..32573b10d9c5b 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1445,6 +1445,7 @@ static inline bool kvm_system_needs_idmapped_vectors(void)
return cpus_have_final_cap(ARM64_SPECTRE_V3A);
}
+void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu);
void kvm_init_host_debug_data(void);
void kvm_debug_init_vhe(void);
void kvm_vcpu_load_debug(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/include/asm/kvm_types.h b/arch/arm64/include/asm/kvm_types.h
index 9a126b9e2d7c9..4e39cbc80aa0b 100644
--- a/arch/arm64/include/asm/kvm_types.h
+++ b/arch/arm64/include/asm/kvm_types.h
@@ -4,5 +4,9 @@
#define KVM_ARCH_NR_OBJS_PER_MEMORY_CACHE 40
-#endif /* _ASM_ARM64_KVM_TYPES_H */
+enum vcpu_pmu_register_access {
+ VCPU_PMU_ACCESS_FREE,
+ VCPU_PMU_ACCESS_GUEST_OWNED,
+};
+#endif /* _ASM_ARM64_KVM_TYPES_H */
diff --git a/arch/arm64/kvm/debug.c b/arch/arm64/kvm/debug.c
index c84321277d893..ab80325e67c5c 100644
--- a/arch/arm64/kvm/debug.c
+++ b/arch/arm64/kvm/debug.c
@@ -35,7 +35,7 @@ static int cpu_has_spe(u64 dfr0)
* - Self-hosted Trace Filter controls (MDCR_EL2_TTRF)
* - Self-hosted Trace (MDCR_EL2_TTRF/MDCR_EL2_E2TB)
*/
-static void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu)
+void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu)
{
preempt_disable();
@@ -63,7 +63,8 @@ static void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu)
* fine grain traps and enforce counter access with
* HPMN.
*/
- if (!vcpu_on_unsupported_cpu(vcpu) &&
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED &&
+ !vcpu_on_unsupported_cpu(vcpu) &&
cpus_have_final_cap(ARM64_HAS_FGT) &&
(cpus_have_final_cap(ARM64_HAS_HPMN0) || nr_guest_cntr > 0)) {
vcpu->arch.mdcr_el2 &= ~(MDCR_EL2_TPM | MDCR_EL2_TPMCR | MDCR_EL2_HPMN);
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index 044f011c9c84b..bb1f3dca03869 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -269,7 +269,7 @@ void kvm_pmu_load(struct kvm_vcpu *vcpu)
* If we aren't guest-owned then we know the guest isn't using
* the PMU anyway, so no need to bother with the swap.
*/
- if (!kvm_pmu_is_partitioned(vcpu->kvm))
+ if (vcpu->arch.pmu.access != VCPU_PMU_ACCESS_GUEST_OWNED)
return;
preempt_disable();
@@ -343,7 +343,7 @@ void kvm_pmu_put(struct kvm_vcpu *vcpu)
* accessing the PMU anyway, so no need to bother with the
* swap.
*/
- if (!kvm_pmu_is_partitioned(vcpu->kvm))
+ if (vcpu->arch.pmu.access != VCPU_PMU_ACCESS_GUEST_OWNED)
return;
preempt_disable();
@@ -388,3 +388,20 @@ void kvm_pmu_put(struct kvm_vcpu *vcpu)
kvm_pmu_set_guest_counters(pmu, 0);
preempt_enable();
}
+
+/**
+ * kvm_pmu_set_guest_owned() - Give PMU ownership to guest
+ * @vcpu: Pointer to vcpu struct
+ *
+ * Reconfigure the guest for physical access of PMU hardware if
+ * allowed. This means reconfiguring mdcr_el2.
+ *
+ */
+void kvm_pmu_set_guest_owned(struct kvm_vcpu *vcpu)
+{
+ if (kvm_pmu_is_partitioned(vcpu->kvm) &&
+ vcpu->arch.pmu.access == VCPU_PMU_ACCESS_FREE) {
+ vcpu->arch.pmu.access = VCPU_PMU_ACCESS_GUEST_OWNED;
+ kvm_arm_setup_mdcr_el2(vcpu);
+ }
+}
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 94572bc52c32a..f0eebeeb5ed96 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1085,15 +1085,17 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
u64 mask;
int idx;
+ kvm_pmu_set_guest_owned(vcpu);
+
switch (reg) {
case PMCR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
kvm_pmu_direct_pmcr_write(vcpu, val);
else
kvm_pmu_handle_pmcr(vcpu, val);
break;
case PMSELR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
write_sysreg(val, pmselr_el0);
else
__vcpu_assign_sys_reg(vcpu, reg, val);
@@ -1101,7 +1103,7 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
case PMEVCNTR0_EL0 ... PMCCNTR_EL0:
idx = reg - PMEVCNTR0_EL0;
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
if (idx == ARMV8_PMU_CYCLE_IDX)
write_sysreg(val, pmccntr_el0);
else
@@ -1122,7 +1124,7 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
}
break;
case PMCNTENSET_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
if (set)
write_sysreg(val, pmcntenset_el0);
else
@@ -1139,7 +1141,7 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
}
break;
case PMINTENSET_EL1:
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
if (set)
write_sysreg(val, pmintenset_el1);
else
@@ -1166,7 +1168,7 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
local_irq_restore(flags);
break;
case PMUSERENR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
write_sysreg(val, pmuserenr_el0);
else
__vcpu_assign_sys_reg(vcpu, reg, val);
@@ -1175,7 +1177,6 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
WARN_ON(1);
break;
}
-
}
/**
@@ -1192,15 +1193,17 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
u64 val = 0;
int idx;
+ kvm_pmu_set_guest_owned(vcpu);
+
switch (reg) {
case PMCR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
val = kvm_pmu_direct_pmcr_read(vcpu);
else
val = kvm_vcpu_read_pmcr(vcpu);
break;
case PMSELR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
val = read_sysreg(pmselr_el0);
else
val = __vcpu_sys_reg(vcpu, reg);
@@ -1208,7 +1211,7 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
case PMEVCNTR0_EL0 ... PMCCNTR_EL0:
idx = reg - PMEVCNTR0_EL0;
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
if (idx == ARMV8_PMU_CYCLE_IDX)
val = read_sysreg(pmccntr_el0);
else
@@ -1221,7 +1224,7 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
val = __vcpu_sys_reg(vcpu, reg);
break;
case PMCNTENSET_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
val = read_sysreg(pmcntenset_el0);
val &= kvm_pmu_guest_counter_mask(vcpu->kvm->arch.arm_pmu);
} else {
@@ -1229,7 +1232,7 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
}
break;
case PMINTENSET_EL1:
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
val = read_sysreg(pmintenset_el1);
val &= kvm_pmu_guest_counter_mask(vcpu->kvm->arch.arm_pmu);
} else {
@@ -1240,7 +1243,7 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
val = __vcpu_sys_reg(vcpu, reg);
break;
case PMUSERENR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
val = read_sysreg(pmuserenr_el0);
else
val = __vcpu_sys_reg(vcpu, reg);
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 61f8d4ed35e10..b77ddb94dc99b 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -7,6 +7,7 @@
#ifndef __ASM_ARM_KVM_PMU_H
#define __ASM_ARM_KVM_PMU_H
+#include <linux/kvm_types.h>
#include <linux/perf_event.h>
#include <linux/perf/arm_pmuv3.h>
#include <linux/perf/arm_pmu.h>
@@ -43,6 +44,7 @@ struct kvm_pmu {
int irq_num;
bool created;
bool irq_level;
+ enum vcpu_pmu_register_access access;
};
struct arm_pmu_entry {
@@ -103,6 +105,9 @@ u64 kvm_pmu_host_counter_mask(struct arm_pmu *pmu);
u64 kvm_pmu_guest_counter_mask(struct arm_pmu *pmu);
void kvm_pmu_load(struct kvm_vcpu *vcpu);
void kvm_pmu_put(struct kvm_vcpu *vcpu);
+void kvm_pmu_set_guest_owned(struct kvm_vcpu *vcpu);
+
+#define kvm_pmu_get_access(vcpu) ((vcpu)->arch.pmu.access)
/*
* Updates the vcpu's view of the pmu events for this cpu.
@@ -147,6 +152,8 @@ static inline bool kvm_pmu_is_partitioned(struct kvm *kvm)
{
return false;
}
+
+#define kvm_pmu_get_access(vcpu) (VCPU_PMU_ACCESS_FREE)
static inline void kvm_pmu_direct_pmcr_write(struct kvm_vcpu *vcpu, u64 val) {}
static inline u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu)
{
@@ -154,6 +161,7 @@ static inline u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu)
}
static inline void kvm_pmu_load(struct kvm_vcpu *vcpu) {}
static inline void kvm_pmu_put(struct kvm_vcpu *vcpu) {}
+static inline void kvm_pmu_set_guest_owned(struct kvm_vcpu *vcpu) {}
static inline void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu,
u64 select_idx, u64 val) {}
static inline void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu,
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* Re: [PATCH] wifi: mt76: fix airoha_npu dependency tracking
From: Arnd Bergmann @ 2026-06-12 19:43 UTC (permalink / raw)
To: Arnd Bergmann, Felix Fietkau, Lorenzo Bianconi, Ryder Lee,
Matthias Brugger, AngeloGioacchino Del Regno
Cc: Shayne Chen, Sean Wang, Rex Lu, linux-wireless, linux-kernel,
linux-arm-kernel, linux-mediatek
In-Reply-To: <20260611125912.3387021-1-arnd@kernel.org>
On Thu, Jun 11, 2026, at 14:58, Arnd Bergmann wrote:
>
> config MT76_NPU
> - bool
> - depends on MT76_CORE
> + tristate
> + depends on NET_AIROHA_NPU=y || MT76=NET_AIROHA_NPU
>
Further testing showed that there is a typo here, it should
be MT76_CORE instead of MT76.
I'll send a v2 patch.
Arnd
^ permalink raw reply
* Re: [PATCH v2] power: supply: macsmc: Support macOS 27 SMC firmware
From: Sasha Finkelstein @ 2026-06-12 19:42 UTC (permalink / raw)
To: Janne Grunau
Cc: Sven Peter, Neal Gompa, Sebastian Reichel, asahi,
linux-arm-kernel, linux-pm, linux-kernel
In-Reply-To: <20260612193353.GA7993@robin.jannau.net>
> On Jun 12, 2026, at 21:33, Janne Grunau <j@jannau.net> wrote:
>
> I think the following is slightly easier to read
>
> {
> if (power->bcf0_1byte) {
> u8 tval = 0;
> int ret = apple_smc_read_u8(power->smc, SMC_KEY(BCF0), &tval);
> *val = tval;
> return ret;
> }
>
> return apple_smc_read_u32(power->smc, SMC_KEY(BCF0), val);
> }
I personally prefer using early returns as much as possible, as it makes
it easier for me to read in linear order and due to line length reasons.
But no strong opinion either way.
^ permalink raw reply
* Re: [PATCH v2] power: supply: macsmc: Support macOS 27 SMC firmware
From: Janne Grunau @ 2026-06-12 19:33 UTC (permalink / raw)
To: Sasha Finkelstein
Cc: Sven Peter, Neal Gompa, Sebastian Reichel, asahi,
linux-arm-kernel, linux-pm, linux-kernel
In-Reply-To: <20260612-gate-power-v2-1-de76cb9bb7ef@chaosmail.tech>
On Fri, Jun 12, 2026 at 08:36:03PM +0200, Sasha Finkelstein wrote:
> The SMC firmware included in macOS 27 changed the size of BCF0 key from
> 4 to 1 bytes. This key is used for indicating that battery state is
> critically low.
>
> Reviewed-by: Sven Peter <sven@kernel.org>
> Signed-off-by: Sasha Finkelstein <k@chaosmail.tech>
I think
Fixes: 0ebf821cf6c7 ("power: supply: Add macsmc-power driver for Apple Silicon")
and "Cc: stable" could be justified. Timing is a little unfortunate with
v7.1 expected on sunday.
> ---
> Changes in v2:
> - Addressing review feedback
> - Link to v1: https://patch.msgid.link/20260611-gate-power-v1-1-8a62721086c7@chaosmail.tech
> ---
> drivers/power/supply/macsmc-power.c | 35 ++++++++++++++++++++++++++++++++---
> 1 file changed, 32 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c
> index 33ca07460f3a..747315e372bc 100644
> --- a/drivers/power/supply/macsmc-power.c
> +++ b/drivers/power/supply/macsmc-power.c
> @@ -86,6 +86,7 @@ struct macsmc_power {
> bool has_ch0i; /* Force discharge (Older firmware) */
> bool has_ch0c; /* Inhibit charge (Older firmware) */
> bool has_chte; /* Inhibit charge (Modern firmware) */
> + bool bcf0_1byte; /* Battery critical key is 1 byte (Modern firmware) */
>
> u8 num_cells;
> int nominal_voltage_mv;
> @@ -273,6 +274,19 @@ static int macsmc_battery_get_date(const char *s, int *out)
> return 0;
> }
>
> +static int macsmc_battery_read_bcf0(struct macsmc_power *power, u32 *val)
> +{
> + u8 tval = 0;
> + int ret;
> +
> + if (!power->bcf0_1byte)
> + return apple_smc_read_u32(power->smc, SMC_KEY(BCF0), val);
> +
> + ret = apple_smc_read_u8(power->smc, SMC_KEY(BCF0), &tval);
> + *val = tval;
> + return ret;
> +}
I think the following is slightly easier to read
{
if (power->bcf0_1byte) {
u8 tval = 0;
int ret = apple_smc_read_u8(power->smc, SMC_KEY(BCF0), &tval);
*val = tval;
return ret;
}
return apple_smc_read_u32(power->smc, SMC_KEY(BCF0), val);
}
Not worth a respin in itself but worthwhile if there's a v3 for the the
Fixes: tag.
> +
> static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
> {
> bool flag;
> @@ -280,7 +294,7 @@ static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
> int ret;
>
> /* Check for emergency shutdown condition */
> - if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val)
> + if (macsmc_battery_read_bcf0(power, &val) >= 0 && val)
> return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
>
> /* Check AC status for whether we could boot in this state */
> @@ -577,7 +591,7 @@ static void macsmc_power_critical_work(struct work_struct *wrk)
> * Check if SMC flagged the battery as empty.
> * We trigger a graceful shutdown to let the OS save data.
> */
> - if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0) == 0 && bcf0 != 0) {
> + if (macsmc_battery_read_bcf0(power, &bcf0) == 0 && bcf0 != 0) {
> power->orderly_shutdown_triggered = true;
> dev_crit(power->dev, "Battery critical (empty flag set). Triggering orderly shutdown.\n");
> orderly_poweroff(true);
> @@ -616,6 +630,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
> struct device *dev = &pdev->dev;
> struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
> struct power_supply_config psy_cfg = {};
> + struct apple_smc_key_info info;
> struct macsmc_power *power;
> bool has_battery = false;
> bool has_ac_adapter = false;
> @@ -714,6 +729,20 @@ static int macsmc_power_probe(struct platform_device *pdev)
> if (apple_smc_key_exists(smc, SMC_KEY(CH0I)))
> power->has_ch0i = true;
>
> + ret = apple_smc_get_key_info(power->smc, SMC_KEY(BCF0), &info);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to determine BCF0 key size\n");
> + return ret;
> + }
> + if (info.size == 1)
> + power->bcf0_1byte = true;
> + else if (info.size == 4)
> + power->bcf0_1byte = false;
> + else {
> + dev_err(&pdev->dev, "Unexpected BCF0 key size %d\n", info.size);
> + return -EIO;
> + }
> +
> /* Reset "Optimised Battery Charging" flags to default state */
> if (power->has_chte)
> apple_smc_write_u32(smc, SMC_KEY(CHTE), 0);
> @@ -766,7 +795,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
> power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
>
> /* Enable critical shutdown notifications by reading status once */
> - apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32);
> + macsmc_battery_read_bcf0(power, &val32);
>
> psy_cfg.drv_data = power;
> power->batt = devm_power_supply_register(dev, &power->batt_desc, &psy_cfg);
>
> ---
Reviewed-by: Janne Grunau <j@jannau.net>
thanks
Janne
^ permalink raw reply
* [PATCH 20/21] KVM: arm64: selftests: Add test case for Partitioned PMU
From: Colton Lewis @ 2026-06-12 19:29 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Rerun all tests for a Partitioned PMU in vpmu_counter_access.
Create an enum specifying whether we are testing the emulated or
Partitioned PMU and all the test functions are modified to take the
implementation as an argument and make the difference in setup
appropriately.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
.../selftests/kvm/arm64/vpmu_counter_access.c | 94 ++++++++++++++-----
1 file changed, 73 insertions(+), 21 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
index 22223395969e0..9be6034335283 100644
--- a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
+++ b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
@@ -25,9 +25,20 @@
/* The cycle counter bit position that's common among the PMU registers */
#define ARMV8_PMU_CYCLE_IDX 31
+enum pmu_impl {
+ EMULATED,
+ PARTITIONED
+};
+
+const char *pmu_impl_str[] = {
+ "Emulated",
+ "Partitioned"
+};
+
struct vpmu_vm {
struct kvm_vm *vm;
struct kvm_vcpu *vcpu;
+ bool pmu_partitioned;
};
static struct vpmu_vm vpmu_vm;
@@ -399,7 +410,7 @@ static void guest_code(u64 expected_pmcr_n)
}
/* Create a VM that has one vCPU with PMUv3 configured. */
-static void create_vpmu_vm(void *guest_code)
+static void create_vpmu_vm(void *guest_code, enum pmu_impl impl)
{
struct kvm_vcpu_init init;
u8 pmuver, ec;
@@ -409,6 +420,13 @@ static void create_vpmu_vm(void *guest_code)
.attr = KVM_ARM_VCPU_PMU_V3_IRQ,
.addr = (u64)&irq,
};
+ u32 partition = (impl == PARTITIONED);
+ struct kvm_device_attr part_attr = {
+ .group = KVM_ARM_VCPU_PMU_V3_CTRL,
+ .attr = KVM_ARM_VCPU_PMU_V3_ENABLE_PARTITION,
+ .addr = (uint64_t)&partition
+ };
+ int ret;
/* The test creates the vpmu_vm multiple times. Ensure a clean state */
memset(&vpmu_vm, 0, sizeof(vpmu_vm));
@@ -436,6 +454,15 @@ static void create_vpmu_vm(void *guest_code)
"Unexpected PMUVER (0x%x) on the vCPU with PMUv3", pmuver);
vcpu_ioctl(vpmu_vm.vcpu, KVM_SET_DEVICE_ATTR, &irq_attr);
+
+ ret = __vcpu_has_device_attr(
+ vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, KVM_ARM_VCPU_PMU_V3_ENABLE_PARTITION);
+ if (!ret) {
+ vcpu_ioctl(vpmu_vm.vcpu, KVM_SET_DEVICE_ATTR, &part_attr);
+ vpmu_vm.pmu_partitioned = partition;
+ pr_debug("Set PMU partitioning: %d\n", partition);
+ }
+
}
static void destroy_vpmu_vm(void)
@@ -461,13 +488,14 @@ static void run_vcpu(struct kvm_vcpu *vcpu, u64 pmcr_n)
}
}
-static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool expect_fail)
+static void test_create_vpmu_vm_with_nr_counters(
+ unsigned int nr_counters, enum pmu_impl impl, bool expect_fail)
{
struct kvm_vcpu *vcpu;
unsigned int prev;
int ret;
- create_vpmu_vm(guest_code);
+ create_vpmu_vm(guest_code, impl);
vcpu = vpmu_vm.vcpu;
prev = get_pmcr_n(vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0)));
@@ -489,7 +517,7 @@ static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool
* Create a guest with one vCPU, set the PMCR_EL0.N for the vCPU to @pmcr_n,
* and run the test.
*/
-static void run_access_test(u64 pmcr_n)
+static void run_access_test(u64 pmcr_n, enum pmu_impl impl)
{
u64 sp;
struct kvm_vcpu *vcpu;
@@ -497,7 +525,7 @@ static void run_access_test(u64 pmcr_n)
pr_debug("Test with pmcr_n %lu\n", pmcr_n);
- test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
+ test_create_vpmu_vm_with_nr_counters(pmcr_n, impl, false);
vcpu = vpmu_vm.vcpu;
/* Save the initial sp to restore them later to run the guest again */
@@ -531,14 +559,14 @@ static struct pmreg_sets validity_check_reg_sets[] = {
* Create a VM, and check if KVM handles the userspace accesses of
* the PMU register sets in @validity_check_reg_sets[] correctly.
*/
-static void run_pmregs_validity_test(u64 pmcr_n)
+static void run_pmregs_validity_test(u64 pmcr_n, enum pmu_impl impl)
{
int i;
struct kvm_vcpu *vcpu;
u64 set_reg_id, clr_reg_id, reg_val;
u64 valid_counters_mask, max_counters_mask;
- test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
+ test_create_vpmu_vm_with_nr_counters(pmcr_n, impl, false);
vcpu = vpmu_vm.vcpu;
valid_counters_mask = get_counters_mask(pmcr_n);
@@ -588,11 +616,11 @@ static void run_pmregs_validity_test(u64 pmcr_n)
* the vCPU to @pmcr_n, which is larger than the host value.
* The attempt should fail as @pmcr_n is too big to set for the vCPU.
*/
-static void run_error_test(u64 pmcr_n)
+static void run_error_test(u64 pmcr_n, enum pmu_impl impl)
{
- pr_debug("Error test with pmcr_n %lu (larger than the host)\n", pmcr_n);
+ pr_debug("Error test with pmcr_n %lu (larger than the host allows)\n", pmcr_n);
- test_create_vpmu_vm_with_nr_counters(pmcr_n, true);
+ test_create_vpmu_vm_with_nr_counters(pmcr_n, impl, true);
destroy_vpmu_vm();
}
@@ -600,11 +628,11 @@ static void run_error_test(u64 pmcr_n)
* Return the default number of implemented PMU event counters excluding
* the cycle counter (i.e. PMCR_EL0.N value) for the guest.
*/
-static u64 get_pmcr_n_limit(void)
+static u64 get_pmcr_n_limit(enum pmu_impl impl)
{
u64 pmcr;
- create_vpmu_vm(guest_code);
+ create_vpmu_vm(guest_code, impl);
pmcr = vcpu_get_reg(vpmu_vm.vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0));
destroy_vpmu_vm();
return get_pmcr_n(pmcr);
@@ -614,7 +642,7 @@ static bool kvm_supports_nr_counters_attr(void)
{
bool supported;
- create_vpmu_vm(NULL);
+ create_vpmu_vm(NULL, EMULATED);
supported = !__vcpu_has_device_attr(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS);
destroy_vpmu_vm();
@@ -622,22 +650,46 @@ static bool kvm_supports_nr_counters_attr(void)
return supported;
}
-int main(void)
+static bool kvm_supports_partition_attr(void)
+{
+ bool supported;
+
+ create_vpmu_vm(NULL, EMULATED);
+ supported = !__vcpu_has_device_attr(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_ENABLE_PARTITION);
+ destroy_vpmu_vm();
+
+ return supported;
+}
+
+void test_pmu(enum pmu_impl impl)
{
u64 i, pmcr_n;
- TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));
- TEST_REQUIRE(kvm_supports_vgic_v3());
- TEST_REQUIRE(kvm_supports_nr_counters_attr());
+ pr_info("Testing PMU: Implementation = %s\n", pmu_impl_str[impl]);
+
+ pmcr_n = get_pmcr_n_limit(impl);
+ pr_debug("PMCR_EL0.N: Limit = %lu\n", pmcr_n);
- pmcr_n = get_pmcr_n_limit();
for (i = 0; i <= pmcr_n; i++) {
- run_access_test(i);
- run_pmregs_validity_test(i);
+ run_access_test(i, impl);
+ run_pmregs_validity_test(i, impl);
}
for (i = pmcr_n + 1; i < ARMV8_PMU_MAX_COUNTERS; i++)
- run_error_test(i);
+ run_error_test(i, impl);
+}
+
+int main(void)
+{
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));
+ TEST_REQUIRE(kvm_supports_vgic_v3());
+ TEST_REQUIRE(kvm_supports_nr_counters_attr());
+
+ test_pmu(EMULATED);
+
+ if (kvm_supports_partition_attr())
+ test_pmu(PARTITIONED);
return 0;
}
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* [PATCH 21/21] KVM: arm64: selftests: Relax testing for exceptions when partitioned
From: Colton Lewis @ 2026-06-12 19:29 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Because the Partitioned PMU must lean heavily on underlying hardware
support, it can't guarantee an exception occurs when accessing an
invalid pmc index.
The ARM manual specifies that accessing PMEVCNTR<n>_EL0 where n is
greater than the number of counters on the system is constrained
unpredictable when FEAT_FGT is not implemented, and it is desired the
Partitioned PMU still work without FEAT_FGT.
Though KVM could enforce exceptions here since all PMU accesses
without FEAT_FGT are trapped, that creates further difficulties. For
one example, the manual also says that after writing a value to
PMSELR_EL0 greater than the number of counters on a system, direct
reads will return an unknown value, meaning KVM could not rely on the
hardware register to hold the correct value.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
.../selftests/kvm/arm64/vpmu_counter_access.c | 20 ++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
index 9be6034335283..e8c3856df77b7 100644
--- a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
+++ b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
@@ -38,10 +38,14 @@ const char *pmu_impl_str[] = {
struct vpmu_vm {
struct kvm_vm *vm;
struct kvm_vcpu *vcpu;
+};
+
+struct guest_context {
bool pmu_partitioned;
};
static struct vpmu_vm vpmu_vm;
+static struct guest_context guest_context;
struct pmreg_sets {
u64 set_reg_id;
@@ -342,11 +346,16 @@ static void test_access_invalid_pmc_regs(struct pmc_accessor *acc, int pmc_idx)
/*
* Reading/writing the event count/type registers should cause
* an UNDEFINED exception.
+ *
+ * If the pmu is partitioned, we can't guarantee it because
+ * hardware doesn't.
*/
- TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->read_cntr(pmc_idx));
- TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->write_cntr(pmc_idx, 0));
- TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->read_typer(pmc_idx));
- TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->write_typer(pmc_idx, 0));
+ if (!guest_context.pmu_partitioned) {
+ TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->read_cntr(pmc_idx));
+ TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->write_cntr(pmc_idx, 0));
+ TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->read_typer(pmc_idx));
+ TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->write_typer(pmc_idx, 0));
+ }
/*
* The bit corresponding to the (unimplemented) counter in
* {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers should be RAZ.
@@ -459,7 +468,7 @@ static void create_vpmu_vm(void *guest_code, enum pmu_impl impl)
vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, KVM_ARM_VCPU_PMU_V3_ENABLE_PARTITION);
if (!ret) {
vcpu_ioctl(vpmu_vm.vcpu, KVM_SET_DEVICE_ATTR, &part_attr);
- vpmu_vm.pmu_partitioned = partition;
+ guest_context.pmu_partitioned = partition;
pr_debug("Set PMU partitioning: %d\n", partition);
}
@@ -511,6 +520,7 @@ static void test_create_vpmu_vm_with_nr_counters(
TEST_ASSERT(!ret, KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));
vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, KVM_ARM_VCPU_PMU_V3_INIT, NULL);
+ sync_global_to_guest(vpmu_vm.vm, guest_context);
}
/*
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* [PATCH 12/21] KVM: arm64: Enforce PMU event filter at vcpu_load()
From: Colton Lewis @ 2026-06-12 19:29 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
The KVM API for event filtering says that counters do not count when
blocked by the event filter. To enforce that, the event filter must be
rechecked on every load since it might have changed since the last
time the guest wrote a value. If the event is filtered, exclude
counting at all exception levels before writing the hardware.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm64/kvm/pmu-direct.c | 52 +++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index 79022447cfb9a..49f1feb5d280c 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -131,6 +131,57 @@ u64 kvm_pmu_guest_counter_mask(struct arm_pmu *pmu)
return 0;
}
+/**
+ * kvm_pmu_apply_event_filter()
+ * @vcpu: Pointer to vcpu struct
+ *
+ * To uphold the guarantee of the KVM PMU event filter, we must ensure
+ * no counter counts if the event is filtered. Accomplish this by
+ * filtering all exception levels if the event is filtered.
+ */
+static void kvm_pmu_apply_event_filter(struct kvm_vcpu *vcpu)
+{
+ struct arm_pmu *pmu = vcpu->kvm->arch.arm_pmu;
+ unsigned long guest_counters;
+ u64 evtyper_set = ARMV8_PMU_EXCLUDE_EL0 |
+ ARMV8_PMU_EXCLUDE_EL1;
+ u64 evtyper_clr = ARMV8_PMU_INCLUDE_EL2;
+ bool guest_include_el2;
+ u8 i;
+ u64 val;
+ u64 evsel;
+
+ if (!pmu)
+ return;
+
+ guest_counters = kvm_pmu_guest_counter_mask(pmu);
+
+ for_each_set_bit(i, &guest_counters, ARMPMU_MAX_HWEVENTS) {
+ if (i == ARMV8_PMU_CYCLE_IDX) {
+ val = __vcpu_sys_reg(vcpu, PMCCFILTR_EL0);
+ evsel = ARMV8_PMUV3_PERFCTR_CPU_CYCLES;
+ } else {
+ val = __vcpu_sys_reg(vcpu, PMEVTYPER0_EL0 + i);
+ evsel = val & kvm_pmu_event_mask(vcpu->kvm);
+ }
+
+ guest_include_el2 = (val & ARMV8_PMU_INCLUDE_EL2);
+ val &= ~evtyper_clr;
+
+ if (unlikely(is_hyp_ctxt(vcpu)) && guest_include_el2)
+ val &= ~ARMV8_PMU_EXCLUDE_EL1;
+
+ if (vcpu->kvm->arch.pmu_filter &&
+ !test_bit(evsel, vcpu->kvm->arch.pmu_filter))
+ val |= evtyper_set;
+
+ if (i == ARMV8_PMU_CYCLE_IDX)
+ write_pmccfiltr(val);
+ else
+ write_pmevtypern(i, val);
+ }
+}
+
/**
* kvm_pmu_load() - Load untrapped PMU registers
* @vcpu: Pointer to struct kvm_vcpu
@@ -158,6 +209,7 @@ void kvm_pmu_load(struct kvm_vcpu *vcpu)
pmu = vcpu->kvm->arch.arm_pmu;
guest_counters = kvm_pmu_guest_counter_mask(pmu);
+ kvm_pmu_apply_event_filter(vcpu);
for_each_set_bit(i, &guest_counters, ARMPMU_MAX_HWEVENTS) {
val = __vcpu_sys_reg(vcpu, PMEVCNTR0_EL0 + i);
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* [PATCH 19/21] KVM: selftests: Add find_bit to KVM library
From: Colton Lewis @ 2026-06-12 19:29 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Some selftests have a dependency on find_bit and weren't compiling
separately without it, so I've added it to the KVM library here using
the same method as files like rbtree.c.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
tools/testing/selftests/kvm/lib/find_bit.c | 2 ++
2 files changed, 3 insertions(+)
create mode 100644 tools/testing/selftests/kvm/lib/find_bit.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 9118a5a51b89f..fa7a2746b1c13 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -5,6 +5,7 @@ all:
LIBKVM += lib/assert.c
LIBKVM += lib/elf.c
+LIBKVM += lib/find_bit.c
LIBKVM += lib/guest_modes.c
LIBKVM += lib/io.c
LIBKVM += lib/kvm_util.c
diff --git a/tools/testing/selftests/kvm/lib/find_bit.c b/tools/testing/selftests/kvm/lib/find_bit.c
new file mode 100644
index 0000000000000..5534248c663f7
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/find_bit.c
@@ -0,0 +1,2 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "../../../../lib/find_bit.c"
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related
* [PATCH 06/21] perf: arm_pmuv3: Allocate counter indices from high to low
From: Colton Lewis @ 2026-06-12 19:28 UTC (permalink / raw)
To: kvm
Cc: Alexandru Elisei, Paolo Bonzini, Jonathan Corbet, Russell King,
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Mingwei Zhang, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Mark Rutland, Shuah Khan, Ganapatrao Kulkarni, James Clark,
linux-doc, linux-kernel, linux-arm-kernel, kvmarm,
linux-perf-users, linux-kselftest, Colton Lewis
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
To minimize collisions between host and guest counters, allocate host
counters from high to low. How the pivot HPMN is defined to partition the counters gives the guest the low index counters.
Doing this with index math instead of defining a
for_each_set_bit_reverse macro is safe because cntr_mask is always a
dense range while the host is running.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
drivers/perf/arm_pmuv3.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c
index 17bb1cfdc271c..d7a49dc0b0be6 100644
--- a/drivers/perf/arm_pmuv3.c
+++ b/drivers/perf/arm_pmuv3.c
@@ -953,10 +953,12 @@ static int armv8pmu_get_single_idx(struct pmu_hw_events *cpuc,
{
int idx;
- for_each_set_bit(idx, cpu_pmu->cntr_mask, ARMV8_PMU_MAX_GENERAL_COUNTERS) {
- if (!test_and_set_bit(idx, cpuc->used_mask))
+ for (idx = ARMV8_PMU_MAX_GENERAL_COUNTERS - 1; idx >= 0; idx--) {
+ if (test_bit(idx, cpu_pmu->cntr_mask) &&
+ !test_and_set_bit(idx, cpuc->used_mask))
return idx;
}
+
return -EAGAIN;
}
@@ -969,17 +971,22 @@ static int armv8pmu_get_chain_idx(struct pmu_hw_events *cpuc,
* Chaining requires two consecutive event counters, where
* the lower idx must be even.
*/
- for_each_set_bit(idx, cpu_pmu->cntr_mask, ARMV8_PMU_MAX_GENERAL_COUNTERS) {
+ for (idx = ARMV8_PMU_MAX_GENERAL_COUNTERS - 1; idx >= 0; idx--) {
if (!(idx & 0x1))
continue;
- if (!test_and_set_bit(idx, cpuc->used_mask)) {
- /* Check if the preceding even counter is available */
- if (!test_and_set_bit(idx - 1, cpuc->used_mask))
- return idx;
- /* Release the Odd counter */
- clear_bit(idx, cpuc->used_mask);
+
+ if (test_bit(idx, cpu_pmu->cntr_mask) &&
+ test_bit(idx - 1, cpu_pmu->cntr_mask)) {
+ if (!test_and_set_bit(idx, cpuc->used_mask)) {
+ /* Check if the preceding even counter is available */
+ if (!test_and_set_bit(idx - 1, cpuc->used_mask))
+ return idx;
+ /* Release the Odd counter */
+ clear_bit(idx, cpuc->used_mask);
+ }
}
}
+
return -EAGAIN;
}
--
2.54.0.1136.gdb2ca164c4-goog
^ 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