* [PATCH v3 1/4] dt-bindings: hwmon: (pmbus/max20830): add VOUT feedback resistor properties and complete examples
From: Alexis Czezar Torreno @ 2026-07-20 3:14 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc,
Alexis Czezar Torreno, Krzysztof Kozlowski
In-Reply-To: <20260720-dev-max20830c-v3-0-9d06d27d6da3@analog.com>
Add adi,vout-rfb1-ohms and adi,vout-rfb2-ohms properties to support
external voltage divider configuration for VOUT sensing. When the
desired output voltage is higher than VREF, a resistor divider (RFB1
and RFB2) is required to reach the intended value.
The properties use a dependency constraint to ensure both resistors
are specified together, or neither. This prevents misconfiguration
where only one resistor value is provided.
This patch also added missing entries in the examples.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
---
.../bindings/hwmon/pmbus/adi,max20830.yaml | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
index 1625dd59417f1b3ca689a9c86ca266da913d1217..214adf1a3b9e9efdeca7db3bd7679c70656e9a7e 100644
--- a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
+++ b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
@@ -44,11 +44,26 @@ properties:
GPIO connected to the power-good status output pin.
maxItems: 1
+ adi,vout-rfb1-ohms:
+ description:
+ Top feedback resistor (RFB1) value in ohms for VOUT sensing divider.
+ When the desired output voltage is higher than VREF, a resistor divider
+ is required. VOUT = VREF × (1 + RFB1/RFB2)
+
+ adi,vout-rfb2-ohms:
+ description:
+ Bottom feedback resistor (RFB2) value in ohms for VOUT sensing divider.
+ Datasheet recommends that RFB2 does not exceed 2.5kΩ.
+
required:
- compatible
- reg
- vddh-supply
+dependencies:
+ adi,vout-rfb1-ohms: ['adi,vout-rfb2-ohms']
+ adi,vout-rfb2-ohms: ['adi,vout-rfb1-ohms']
+
unevaluatedProperties: false
examples:
@@ -61,6 +76,11 @@ examples:
compatible = "adi,max20830";
reg = <0x30>;
vddh-supply = <&vddh>;
+ avdd-supply = <&avdd>;
+ ldoin-supply = <&ldoin>;
+ pwr-good-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
+ adi,vout-rfb1-ohms = <10000>;
+ adi,vout-rfb2-ohms = <2000>;
};
};
...
--
2.34.1
^ permalink raw reply related
* [PATCH RESEND v6 2/2] PM: dpm_watchdog: Allow disabling DPM watchdog by default
From: Tzung-Bi Shih @ 2026-07-20 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Rafael J. Wysocki, Greg Kroah-Hartman,
Danilo Krummrich
Cc: Shuah Khan, Pavel Machek, Len Brown, tzungbi, linux-doc,
linux-kernel, linux-pm, driver-core, tfiga, senozhatsky,
Randy Dunlap
In-Reply-To: <20260720030821.2780257-1-tzungbi@kernel.org>
Introduce the "dpm_watchdog_enabled" module parameter to allow the DPM
watchdog to be enabled or disabled at boot time and runtime.
Additionally, introduce the CONFIG_DPM_WATCHDOG_ENABLED Kconfig option
to set the default value of the module parameter at compile time.
The primary motivation for this configurability resolves around Android
GKI (Generic Kernel Image). We want to enable CONFIG_DPM_WATCHDOG in
the GKI so the feature is available. However, because the GKI is shared
across many different devices, we don't want to inadvertently affect
devices that are unaware of this feature. This provides a way to
compile it in, but keep it disabled by default for those devices via the
kernel command line or module parameters.
To maintain backward compatibility, CONFIG_DPM_WATCHDOG_ENABLED relies
on `default y`. Previously, the DPM watchdog was always active if
CONFIG_DPM_WATCHDOG was set. Defaulting this new option to 'y' ensures
that the behavior remains unchanged for existing users and defconfigs
when they upgrade.
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v6:
- Change the prefix "pm_sleep" -> "pm".
v5: https://lore.kernel.org/all/20260701045640.3130090-3-tzungbi@kernel.org
- No changes.
v4: https://lore.kernel.org/all/20260611021219.2093476-3-tzungbi@kernel.org
- Rewrite the commit message to indicate the module parameter is the
main change in the patch.
- DPM_WATCHDOG_DEFAULT_ENABLED -> DPM_WATCHDOG_ENABLED.
v3: https://lore.kernel.org/all/20260608021526.1023248-3-tzungbi@kernel.org
- Add "PM" tag (was missing).
- Update the format and specify dependencies in kernel-parameters.txt.
- Update the help message in Kconfig to reflect that dpm_watchdog_enabled
can be set at runtime as well.
v2: https://lore.kernel.org/all/20260604090756.2884671-3-tzungbi@kernel.org
- Use module parameter and bool for dpm_watchdog_enabled.
- Use IS_ENABLED().
v1: https://lore.kernel.org/all/20260528103215.505795-1-tzungbi@kernel.org
---
Documentation/admin-guide/kernel-parameters.txt | 7 +++++++
drivers/base/power/main.c | 11 +++++++++++
kernel/power/Kconfig | 10 ++++++++++
3 files changed, 28 insertions(+)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5493a7f8f22..ea0b70f87472 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -47,6 +47,7 @@
PCI PCI bus support is enabled.
PCIE PCI Express support is enabled.
PCMCIA The PCMCIA subsystem is enabled.
+ PM Power Management support is enabled.
PNP Plug & Play support is enabled.
PPC PowerPC architecture is enabled.
PPT Parallel port support is enabled.
@@ -5354,6 +5355,12 @@ Kernel parameters
pm_debug_messages [SUSPEND,KNL]
Enable suspend/resume debug messages during boot up.
+ pm.dpm_watchdog_enabled=
+ [PM] Enable or disable the DPM watchdog. Requires
+ CONFIG_PM_SLEEP and CONFIG_DPM_WATCHDOG enabled.
+ Format: <bool>
+ Default value is set by CONFIG_DPM_WATCHDOG_ENABLED.
+
pnp.debug=1 [PNP]
Enable PNP debug messages (depends on the
CONFIG_PNP_DEBUG_MESSAGES option). Change at run-time
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 49ea6e2cd735..184dc4b3b938 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -535,6 +535,11 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644);
MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace,
"Backtrace all CPUs on DPM watchdog timeout");
+static bool __read_mostly dpm_watchdog_enabled =
+ IS_ENABLED(CONFIG_DPM_WATCHDOG_ENABLED);
+module_param(dpm_watchdog_enabled, bool, 0644);
+MODULE_PARM_DESC(dpm_watchdog_enabled, "Enable DPM watchdog");
+
static unsigned int __read_mostly dpm_watchdog_timeout = CONFIG_DPM_WATCHDOG_TIMEOUT;
static unsigned int __read_mostly dpm_watchdog_warning_timeout =
CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT;
@@ -630,6 +635,9 @@ static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev)
{
struct timer_list *timer = &wd->timer;
+ if (!dpm_watchdog_enabled)
+ return;
+
wd->dev = dev;
wd->tsk = current;
wd->fatal = dpm_watchdog_timeout == dpm_watchdog_warning_timeout;
@@ -648,6 +656,9 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd)
{
struct timer_list *timer = &wd->timer;
+ if (!dpm_watchdog_enabled)
+ return;
+
timer_delete_sync(timer);
timer_destroy_on_stack(timer);
}
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 530c897311d4..71165e7f04f4 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -268,6 +268,16 @@ config DPM_WATCHDOG
captured in pstore device for inspection in subsequent
boot session.
+config DPM_WATCHDOG_ENABLED
+ bool "Enable DPM watchdog by default"
+ depends on DPM_WATCHDOG
+ default y
+ help
+ If you say Y here, the DPM watchdog will be enabled by default.
+ If you say N, it will be compiled in but disabled. It can be
+ enabled at boot time via the "pm.dpm_watchdog_enabled" kernel
+ parameter or at runtime via sysfs.
+
config DPM_WATCHDOG_TIMEOUT
int "Watchdog timeout to panic in seconds"
range 1 120
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH RESEND v6 1/2] PM: sleep: Rename module parameters prefix to "pm"
From: Tzung-Bi Shih @ 2026-07-20 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Rafael J. Wysocki, Greg Kroah-Hartman,
Danilo Krummrich
Cc: Shuah Khan, Pavel Machek, Len Brown, tzungbi, linux-doc,
linux-kernel, linux-pm, driver-core, tfiga, senozhatsky,
Randy Dunlap
In-Reply-To: <20260720030821.2780257-1-tzungbi@kernel.org>
Currently, the module parameters defined in drivers/base/power/main.c
use the default prefix "main" (derived from the filename). The prefix
is too generic and non-descriptive.
Redefine MODULE_PARAM_PREFIX to "pm." to group the module
parameters under the namespace instead. This makes the parameters more
descriptive.
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v6:
- Change the prefix "pm_sleep" -> "pm".
v5: https://lore.kernel.org/all/20260701045640.3130090-2-tzungbi@kernel.org
- No changes.
v4: https://lore.kernel.org/all/20260611021219.2093476-2-tzungbi@kernel.org
- "power" -> "pm_sleep".
v3: https://lore.kernel.org/all/20260608021526.1023248-2-tzungbi@kernel.org
- No changes.
v2: https://lore.kernel.org/all/20260604090756.2884671-2-tzungbi@kernel.org
- New to the series.
v1: Doesn't exist.
---
drivers/base/power/main.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index f71467f6ada4..49ea6e2cd735 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -41,6 +41,9 @@
#include "../base.h"
#include "power.h"
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "pm."
+
typedef int (*pm_callback_t)(struct device *);
/*
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH RESEND v6 0/2] PM: dpm_watchdog: Improve DPM watchdog configurability
From: Tzung-Bi Shih @ 2026-07-20 3:08 UTC (permalink / raw)
To: Jonathan Corbet, Rafael J. Wysocki, Greg Kroah-Hartman,
Danilo Krummrich
Cc: Shuah Khan, Pavel Machek, Len Brown, tzungbi, linux-doc,
linux-kernel, linux-pm, driver-core, tfiga, senozhatsky,
Randy Dunlap
This series improves the configurability of the DPM watchdog.
Currently, the DPM watchdog is always enabled if compiled in. Also, the
module parameters defined in drivers/base/power/main.c use the generic
and non-descriptive "main" prefix.
This series addresses these limitations.
Patch 1 renames the module parameter prefix for
drivers/base/power/main.c from "main" to "pm".
Patch 2 introduces the "dpm_watchdog_enabled" module parameter to allow
enabling/disabling the watchdog at boot time and runtime. It also adds
CONFIG_DPM_WATCHDOG_ENABLED to set the default value of the module
parameter at compile time.
The primary motivation for this configurability revolves around Android
GKI (Generic Kernel Image). We want to enable CONFIG_DPM_WATCHDOG in
the GKI so the feature is available. However, because the GKI is shared
across many different devices, we don't want to inadvertently affect
devices that are unaware of this feature. This provides a way to
compile it in, but keep it disabled by default for those devices via the
kernel command line or module parameters.
To maintain backward compatibility, CONFIG_DPM_WATCHDOG_ENABLED relies
on `default y`. Previously, the DPM watchdog was always active if
CONFIG_DPM_WATCHDOG was set. Defaulting this new option to 'y' ensures
that the behavior remains unchanged for existing users and defconfigs
when they upgrade.
---
Since there was no further feedback on v5, I'm sending out v6.
Any feedback would be appreciated.
---
v6:
- Change the prefix "pm_sleep" -> "pm".
v5: https://lore.kernel.org/all/20260701045640.3130090-1-tzungbi@kernel.org
- Rebase to v7.2-rc1.
- Fix Signed-off-by lines.
v4: https://lore.kernel.org/all/20260611021219.2093476-1-tzungbi@kernel.org
- Address review comments.
- Patch 3 in v3 has applied separately.
v3: https://lore.kernel.org/all/20260608021526.1023248-1-tzungbi@kernel.org
- Address review comments on patch 2.
v2: https://lore.kernel.org/all/20260604090756.2884671-1-tzungbi@kernel.org
- Form a new series.
v1: Doesn't exist.
Tzung-Bi Shih (2):
PM: sleep: Rename module parameters prefix to "pm"
PM: dpm_watchdog: Allow disabling DPM watchdog by default
Documentation/admin-guide/kernel-parameters.txt | 7 +++++++
drivers/base/power/main.c | 14 ++++++++++++++
kernel/power/Kconfig | 10 ++++++++++
3 files changed, 31 insertions(+)
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply
* [RFC PATCH v3 3/3] selftests/damon: add functional test for DAMOS_SPLIT
From: Lian Wang @ 2026-07-20 3:03 UTC (permalink / raw)
To: damon, linux-mm
Cc: sj, akpm, linux-kernel, david, ljs, liam, vbabka, rppt, surenb,
mhocko, npache, ziy, baolin.wang, ryan.roberts, daichaobing,
wangkefeng.wang, gutierrez.asier, zengheng4, kasong, corbet,
skhan, linux-doc, linux-kselftest, lianux.mm, lianux.wang,
kunwu.chan
In-Reply-To: <20260720030327.80153-1-lianux.mm@gmail.com>
Add damos_split.py, which allocates a MADV_HUGEPAGE-backed region in a
child process and runs a DAMON/DAMOS scheme with action 'split' and
target_order 0 against it, then verifies the huge pages are split into
base pages (the child's AnonHugePages drops).
Extend the _damon_sysfs.py Damos helper with the target_order parameter
so a scheme's split target can be expressed.
Co-developed-by: Kunwu Chan <kunwu.chan@linux.dev>
Signed-off-by: Kunwu Chan <kunwu.chan@linux.dev>
Signed-off-by: Lian Wang (Processmission) <lianux.mm@gmail.com>
---
tools/testing/selftests/damon/Makefile | 1 +
tools/testing/selftests/damon/_damon_sysfs.py | 9 +-
tools/testing/selftests/damon/damos_split.py | 125 ++++++++++++++++++
3 files changed, 134 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/damon/damos_split.py
diff --git a/tools/testing/selftests/damon/Makefile b/tools/testing/selftests/damon/Makefile
index ece244e5c5b9..a623c271355f 100644
--- a/tools/testing/selftests/damon/Makefile
+++ b/tools/testing/selftests/damon/Makefile
@@ -12,6 +12,7 @@ TEST_PROGS += sysfs.sh
TEST_PROGS += sysfs.py
TEST_PROGS += sysfs_update_schemes_tried_regions_wss_estimation.py
TEST_PROGS += damos_quota.py damos_quota_goal.py damos_apply_interval.py
+TEST_PROGS += damos_split.py
TEST_PROGS += damos_tried_regions.py damon_nr_regions.py
TEST_PROGS += sysfs_refresh.py
TEST_PROGS += reclaim.sh lru_sort.sh
diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py
index e6a2265d721e..5a01f31ad9f2 100644
--- a/tools/testing/selftests/damon/_damon_sysfs.py
+++ b/tools/testing/selftests/damon/_damon_sysfs.py
@@ -419,6 +419,7 @@ class Damos:
filters = None
apply_interval_us = None
target_nid = None
+ target_order = None
dests = None
idx = None
context = None
@@ -429,7 +430,7 @@ class Damos:
def __init__(self, action='stat', access_pattern=DamosAccessPattern(),
quota=DamosQuota(), watermarks=DamosWatermarks(),
core_filters=[], ops_filters=[], filters=[], target_nid=0,
- dests=DamosDests(), apply_interval_us=0):
+ target_order=0, dests=DamosDests(), apply_interval_us=0):
self.action = action
self.access_pattern = access_pattern
self.access_pattern.scheme = self
@@ -448,6 +449,7 @@ class Damos:
self.filters.scheme = self
self.target_nid = target_nid
+ self.target_order = target_order
self.dests = dests
self.dests.scheme = self
@@ -492,6 +494,11 @@ class Damos:
if err is not None:
return err
+ err = write_file(os.path.join(self.sysfs_dir(), 'target_order'), '%d' %
+ self.target_order)
+ if err is not None:
+ return err
+
err = self.dests.stage()
if err is not None:
return err
diff --git a/tools/testing/selftests/damon/damos_split.py b/tools/testing/selftests/damon/damos_split.py
new file mode 100644
index 000000000000..089fbe0f0d4d
--- /dev/null
+++ b/tools/testing/selftests/damon/damos_split.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# Functional test for the DAMOS_SPLIT action.
+#
+# A child process allocates a MADV_HUGEPAGE-backed anonymous region and
+# faults it in so that it is backed by (m)THPs. The parent then runs a
+# DAMON/DAMOS scheme with action 'split' and target_order 0 against the
+# child and checks that the huge pages are split into base pages, i.e. the
+# child's AnonHugePages (as reported by /proc/<pid>/smaps) drops.
+
+import ctypes
+import os
+import signal
+import sys
+import time
+
+import _damon_sysfs
+
+PMD_SIZE = 2 * 1024 * 1024
+MADV_HUGEPAGE = 14
+PROT_READ_WRITE = 0x1 | 0x2
+MAP_PRIVATE_ANON = 0x2 | 0x20
+REGION_SIZE = 32 * PMD_SIZE
+
+def child_workload():
+ '''Allocate a PMD-aligned, THP-backed region, fault it in, then idle.'''
+ libc = ctypes.CDLL('libc.so.6', use_errno=True)
+ libc.mmap.restype = ctypes.c_void_p
+ libc.mmap.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int,
+ ctypes.c_int, ctypes.c_int, ctypes.c_long]
+ libc.madvise.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int]
+
+ # Over-allocate so that a PMD-aligned window is available.
+ raw = libc.mmap(None, REGION_SIZE + PMD_SIZE, PROT_READ_WRITE,
+ MAP_PRIVATE_ANON, -1, 0)
+ if raw is None or raw == ctypes.c_void_p(-1).value:
+ os._exit(2)
+ base = (raw + PMD_SIZE - 1) & ~(PMD_SIZE - 1)
+ libc.madvise(ctypes.c_void_p(base), REGION_SIZE, MADV_HUGEPAGE)
+
+ buf = (ctypes.c_char * REGION_SIZE).from_address(base)
+ for off in range(0, REGION_SIZE, 4096):
+ buf[off] = 1
+
+ # Ready; idle until the parent tears us down.
+ signal.pause()
+
+def anon_huge_kb(pid):
+ total = 0
+ try:
+ with open('/proc/%d/smaps' % pid) as f:
+ for line in f:
+ if line.startswith('AnonHugePages:'):
+ total += int(line.split()[1])
+ except FileNotFoundError:
+ return -1
+ return total
+
+def main():
+ if not os.path.exists('/sys/kernel/mm/transparent_hugepage/enabled'):
+ print('SKIP: transparent hugepage is not available')
+ exit(0)
+
+ pid = os.fork()
+ if pid == 0:
+ child_workload()
+ os._exit(0)
+
+ try:
+ # Give the child time to fault in its huge pages.
+ time.sleep(2)
+ before = anon_huge_kb(pid)
+ if before <= 0:
+ print('SKIP: workload did not get any THP (AnonHugePages=%d)'
+ % before)
+ os.kill(pid, signal.SIGKILL)
+ exit(0)
+
+ # Split every large folio in the target down to order-0 base pages.
+ kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
+ contexts=[_damon_sysfs.DamonCtx(
+ ops='vaddr',
+ targets=[_damon_sysfs.DamonTarget(pid=pid)],
+ schemes=[_damon_sysfs.Damos(
+ action='split',
+ target_order=0,
+ # match every region regardless of access/age/size, so
+ # the ARM64 stale-TLB blind spot cannot mask the target
+ access_pattern=_damon_sysfs.DamosAccessPattern(
+ size=[0, 2**64 - 1],
+ nr_accesses=[0, 2**64 - 1],
+ age=[0, 2**64 - 1]),
+ apply_interval_us=0)])])])
+ err = kdamonds.start()
+ if err is not None:
+ print('kdamonds start failed: %s' % err)
+ os.kill(pid, signal.SIGKILL)
+ exit(1)
+
+ # Let the scheme find and split the regions.
+ after = before
+ for _ in range(50):
+ time.sleep(0.2)
+ after = anon_huge_kb(pid)
+ if after == 0:
+ break
+
+ kdamonds.stop()
+ os.kill(pid, signal.SIGKILL)
+
+ if after >= before:
+ print('FAIL: AnonHugePages did not shrink: before=%d KiB '
+ 'after=%d KiB' % (before, after))
+ exit(1)
+ print('PASS: AnonHugePages %d KiB -> %d KiB after DAMOS_SPLIT'
+ % (before, after))
+ finally:
+ try:
+ os.kill(pid, signal.SIGKILL)
+ except ProcessLookupError:
+ pass
+
+if __name__ == '__main__':
+ main()
^ permalink raw reply related
* [RFC PATCH v3 2/3] mm/damon/vaddr: implement DAMOS_SPLIT handler
From: Lian Wang @ 2026-07-20 3:03 UTC (permalink / raw)
To: damon, linux-mm
Cc: sj, akpm, linux-kernel, david, ljs, liam, vbabka, rppt, surenb,
mhocko, npache, ziy, baolin.wang, ryan.roberts, daichaobing,
wangkefeng.wang, gutierrez.asier, zengheng4, kasong, corbet,
skhan, linux-doc, linux-kselftest, lianux.mm, lianux.wang,
kunwu.chan
In-Reply-To: <20260720030327.80153-1-lianux.mm@gmail.com>
Implement DAMOS_SPLIT for the vaddr and fvaddr operations sets. For
each large folio in the target region whose order exceeds the scheme's
order, split it down using split_folio_to_order() -- the same helper
used by truncation, migration, ksm and shmem. No new core-mm code or
exported symbols are introduced.
split_folio_to_order() must not run under the page table lock, so the
folio is pinned (folio_trylock + folio_get), the walk is ended to drop
the page table lock, and the split runs while holding only mmap_read_lock.
The scheme's operations-layer filters are honoured, and the amount of
memory that passed those filters is reported via sz_filter_passed.
Co-developed-by: Kunwu Chan <kunwu.chan@linux.dev>
Signed-off-by: Kunwu Chan <kunwu.chan@linux.dev>
Signed-off-by: Lian Wang (Processmission) <lianux.mm@gmail.com>
---
mm/damon/vaddr.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 108 insertions(+)
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index d10b8042adb5..a8607448584d 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -6,6 +6,7 @@
#define pr_fmt(fmt) "damon-va: " fmt
#include <linux/highmem.h>
+#include <linux/huge_mm.h>
#include <linux/hugetlb.h>
#include <linux/mman.h>
#include <linux/mmu_notifier.h>
@@ -896,6 +897,111 @@ static unsigned long damos_va_stat(struct damon_target *target,
return 0;
}
+/*
+ * damos_va_split() - Split large folios in a region down to @target_order
+ * using the existing split_folio_to_order().
+ *
+ * Locking: folio_walk_start() returns the folio with the page table lock
+ * held. split_folio_to_order() must not run under the page table lock,
+ * so we pin the folio (reference + lock), end the walk to drop the page
+ * table lock, and split while holding only mmap_read_lock.
+ * split_folio_to_order() returns -EBUSY for a raced or pinned folio;
+ * we skip such folios.
+ */
+static unsigned long damos_va_split(struct damon_target *target,
+ struct damon_region *r, struct damos *s,
+ unsigned long *sz_filter_passed)
+{
+ unsigned int target_order = s->order;
+ unsigned long addr = ALIGN_DOWN(r->ar.start, PAGE_SIZE);
+ unsigned long end = r->ar.end;
+ unsigned long applied = 0;
+ struct mm_struct *mm;
+
+ mm = damon_get_mm(target);
+ if (!mm)
+ return 0;
+
+ while (addr < end) {
+ struct vm_area_struct *vma;
+ struct folio *folio;
+ struct folio_walk fw;
+ unsigned long folio_sz = 0;
+
+ if (mmap_read_lock_killable(mm))
+ break;
+
+ vma = vma_lookup(mm, addr);
+ if (!vma) {
+ /* Skip the gap to the next VMA, if any. */
+ vma = find_vma(mm, addr);
+ mmap_read_unlock(mm);
+ if (!vma || vma->vm_start >= end)
+ break;
+ addr = vma->vm_start;
+ continue;
+ }
+
+ /* Folios in these VMAs are not our business. */
+ if (vma->vm_flags & (VM_HUGETLB | VM_MIXEDMAP)) {
+ addr = vma->vm_end;
+ mmap_read_unlock(mm);
+ continue;
+ }
+
+ folio = folio_walk_start(&fw, vma, addr, 0);
+ if (!folio) {
+ mmap_read_unlock(mm);
+ addr += PAGE_SIZE;
+ continue;
+ }
+
+ folio_sz = folio_size(folio);
+
+ /*
+ * For file-backed folios, @target_order may be below the
+ * filesystem's minimum folio order (mapping_min_folio_order()).
+ * split_folio_to_order() will simply fail in that case and
+ * we skip the folio. This is a safe no-op; future work can
+ * adjust target_order upward like split_huge_pages_in_pid()
+ * does when a specific filesystem needs it.
+ */
+
+ /* Honour the scheme's operations-layer filters. */
+ if (damos_ops_has_filter(s)) {
+ if (damos_va_filter_out(s, folio, vma, addr,
+ fw.level == FW_LEVEL_PTE ? fw.ptep : NULL,
+ fw.level == FW_LEVEL_PMD ? fw.pmdp : NULL)) {
+ folio_walk_end(&fw, vma);
+ mmap_read_unlock(mm);
+ goto next;
+ }
+ *sz_filter_passed += folio_sz;
+ }
+
+ if (folio_order(folio) > target_order && folio_trylock(folio)) {
+ folio_get(folio);
+ /* Drop the page table lock before splitting. */
+ folio_walk_end(&fw, vma);
+
+ if (!split_folio_to_order(folio, target_order))
+ applied += folio_sz;
+
+ folio_unlock(folio);
+ folio_put(folio);
+ } else {
+ folio_walk_end(&fw, vma);
+ }
+ mmap_read_unlock(mm);
+next:
+ addr = ALIGN_DOWN(addr, folio_sz) + folio_sz;
+ cond_resched();
+ }
+
+ mmput(mm);
+ return applied;
+}
+
static unsigned long damon_va_apply_scheme(struct damon_ctx *ctx,
struct damon_target *t, struct damon_region *r,
struct damos *scheme, unsigned long *sz_filter_passed)
@@ -926,6 +1032,8 @@ static unsigned long damon_va_apply_scheme(struct damon_ctx *ctx,
return damos_va_migrate(t, r, scheme, sz_filter_passed);
case DAMOS_STAT:
return damos_va_stat(t, r, scheme, sz_filter_passed);
+ case DAMOS_SPLIT:
+ return damos_va_split(t, r, scheme, sz_filter_passed);
default:
/*
* DAMOS actions that are not yet supported by 'vaddr'.
^ permalink raw reply related
* [RFC PATCH v3 1/3] mm/damon: introduce DAMOS_SPLIT action
From: Lian Wang @ 2026-07-20 3:03 UTC (permalink / raw)
To: damon, linux-mm
Cc: sj, akpm, linux-kernel, david, ljs, liam, vbabka, rppt, surenb,
mhocko, npache, ziy, baolin.wang, ryan.roberts, daichaobing,
wangkefeng.wang, gutierrez.asier, zengheng4, kasong, corbet,
skhan, linux-doc, linux-kselftest, lianux.mm, lianux.wang,
kunwu.chan
In-Reply-To: <20260720030327.80153-1-lianux.mm@gmail.com>
Add a new DAMOS action, DAMOS_SPLIT, that splits large folios in the
target region down to a smaller order. A new per-scheme field and sysfs
file, target_order, selects the split target: 0 for order-0 base pages,
or 2..HPAGE_PMD_ORDER-1 for a smaller mTHP order (order-1 is rejected at
store time since anonymous THPs cannot be split to order-1).
Unlike DAMOS_COLLAPSE which builds large folios, splitting only dismantles
them; it allocates nothing, copies nothing, and no THP admission policy
applies. khugepaged deliberately refuses to collapse a folio to a
smaller order, leaving the shrink direction to callers like this one.
This patch adds the DAMOS_SPLIT action, the ``order`` field (placed in
the existing union alongside target_nid and migrate_dests), its sysfs
interface, and documentation. The vaddr operations set handler is added
in the next patch.
Link: https://lore.kernel.org/20260605161422.213817-9-npache@redhat.com/
("mm/khugepaged: skip collapsing mTHP to smaller orders")
Co-developed-by: Kunwu Chan <kunwu.chan@linux.dev>
Signed-off-by: Kunwu Chan <kunwu.chan@linux.dev>
Signed-off-by: Lian Wang (Processmission) <lianux.mm@gmail.com>
---
.../ABI/testing/sysfs-kernel-mm-damon | 7 +++
Documentation/mm/damon/design.rst | 5 ++
include/linux/damon.h | 15 ++++--
mm/damon/core.c | 2 +
mm/damon/sysfs-schemes.c | 48 +++++++++++++++++++
tools/testing/selftests/damon/sysfs.py | 11 +++--
6 files changed, 79 insertions(+), 9 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-damon b/Documentation/ABI/testing/sysfs-kernel-mm-damon
index 907a504fb64c..ee8e260a631c 100644
--- a/Documentation/ABI/testing/sysfs-kernel-mm-damon
+++ b/Documentation/ABI/testing/sysfs-kernel-mm-damon
@@ -265,6 +265,13 @@ Contact: SJ Park <sj@kernel.org>
Description: Action's target NUMA node id. Supported by only relevant
actions.
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/schemes/<S>/target_order
+Date: Jul 2026
+Contact: SJ Park <sj@kernel.org>
+Description: Target folio order for the ``split`` action. Large folios in
+ the target region are split down to this order. Valid values
+ are 0 (order-0 base pages) and 2..HPAGE_PMD_ORDER-1.
+
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/schemes/<S>/apply_interval_us
Date: Sep 2023
Contact: SJ Park <sj@kernel.org>
diff --git a/Documentation/mm/damon/design.rst b/Documentation/mm/damon/design.rst
index aed6cb1cf483..87a801954b1b 100644
--- a/Documentation/mm/damon/design.rst
+++ b/Documentation/mm/damon/design.rst
@@ -549,6 +549,11 @@ that supports each action are as below.
Supported by ``vaddr`` and ``fvaddr`` operations set. When
TRANSPARENT_HUGEPAGE is disabled, the application of the action will just
fail.
+ - ``split``: Split each large folio in the region down to the order specified
+ by the scheme's ``target_order`` (``0`` for order-0 base pages, or
+ ``2..HPAGE_PMD_ORDER-1``). Supported by ``vaddr`` and ``fvaddr`` operations
+ set. When TRANSPARENT_HUGEPAGE is disabled, the application of the action
+ will just fail.
- ``lru_prio``: Prioritize the region on its LRU lists.
Supported by ``paddr`` operations set.
- ``lru_deprio``: Deprioritize the region on its LRU lists.
diff --git a/include/linux/damon.h b/include/linux/damon.h
index f69442a9d431..88b61b900a36 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -110,6 +110,7 @@ struct damon_target {
* @DAMOS_HUGEPAGE: Call ``madvise()`` for the region with MADV_HUGEPAGE.
* @DAMOS_NOHUGEPAGE: Call ``madvise()`` for the region with MADV_NOHUGEPAGE.
* @DAMOS_COLLAPSE: Call ``madvise()`` for the region with MADV_COLLAPSE.
+ * @DAMOS_SPLIT: Split each large folio in the region to a smaller order.
* @DAMOS_LRU_PRIO: Prioritize the region on its LRU lists.
* @DAMOS_LRU_DEPRIO: Deprioritize the region on its LRU lists.
* @DAMOS_MIGRATE_HOT: Migrate the regions prioritizing warmer regions.
@@ -130,6 +131,7 @@ enum damos_action {
DAMOS_HUGEPAGE,
DAMOS_NOHUGEPAGE,
DAMOS_COLLAPSE,
+ DAMOS_SPLIT,
DAMOS_LRU_PRIO,
DAMOS_LRU_DEPRIO,
DAMOS_MIGRATE_HOT,
@@ -582,10 +584,15 @@ struct damos {
struct damos_quota quota;
struct damos_watermarks wmarks;
union {
- struct {
- int target_nid;
- struct damos_migrate_dests migrate_dests;
- };
+ int target_nid;
+ struct damos_migrate_dests migrate_dests;
+ /*
+ * @order: target folio order for DAMOS_SPLIT.
+ * Split large folios down to this order. 0 for
+ * order-0 base pages, 2..HPAGE_PMD_ORDER-1 for
+ * smaller mTHP. Order-1 is rejected.
+ */
+ unsigned int order;
};
struct list_head core_filters;
struct list_head ops_filters;
diff --git a/mm/damon/core.c b/mm/damon/core.c
index 806a67d02a6e..394c62455ad5 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -693,6 +693,7 @@ struct damos *damon_new_scheme(struct damos_access_pattern *pattern,
return NULL;
scheme->pattern = *pattern;
scheme->action = action;
+ scheme->order = 0;
scheme->apply_interval_us = apply_interval_us;
/*
* next_apply_sis will be set when kdamond starts. While kdamond is
@@ -1446,6 +1447,7 @@ static int damos_commit(struct damos *dst, struct damos *src)
dst->pattern = src->pattern;
dst->action = src->action;
+ dst->order = src->order;
dst->apply_interval_us = src->apply_interval_us;
err = damos_commit_quota(&dst->quota, &src->quota);
diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c
index 32f495a96b17..725ed81ccf6c 100644
--- a/mm/damon/sysfs-schemes.c
+++ b/mm/damon/sysfs-schemes.c
@@ -4,7 +4,9 @@
*/
#include <linux/slab.h>
+#include <linux/mm.h>
#include <linux/numa.h>
+#include <linux/huge_mm.h>
#include "sysfs-common.h"
@@ -2260,6 +2262,7 @@ struct damon_sysfs_scheme {
struct damon_sysfs_stats *stats;
struct damon_sysfs_scheme_regions *tried_regions;
int target_nid;
+ unsigned int target_order;
struct damos_sysfs_dests *dests;
};
@@ -2293,6 +2296,10 @@ static struct damos_sysfs_action_name damos_sysfs_action_names[] = {
.action = DAMOS_COLLAPSE,
.name = "collapse",
},
+ {
+ .action = DAMOS_SPLIT,
+ .name = "split",
+ },
{
.action = DAMOS_LRU_PRIO,
.name = "lru_prio",
@@ -2326,6 +2333,7 @@ static struct damon_sysfs_scheme *damon_sysfs_scheme_alloc(
scheme->action = action;
scheme->apply_interval_us = apply_interval_us;
scheme->target_nid = NUMA_NO_NODE;
+ scheme->target_order = 0;
return scheme;
}
@@ -2645,6 +2653,40 @@ static ssize_t target_nid_store(struct kobject *kobj,
return err ? err : count;
}
+static ssize_t target_order_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_scheme *scheme = container_of(kobj,
+ struct damon_sysfs_scheme, kobj);
+
+ return sysfs_emit(buf, "%u\n", scheme->target_order);
+}
+
+static ssize_t target_order_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_scheme *scheme = container_of(kobj,
+ struct damon_sysfs_scheme, kobj);
+ unsigned int val;
+ int err;
+
+ err = kstrtouint(buf, 0, &val);
+ if (err)
+ return err;
+
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+ /* Valid split targets: 0 (order-0 base pages) or 2..HPAGE_PMD_ORDER-1. */
+ if (val != 0 && (val < 2 || val >= HPAGE_PMD_ORDER))
+ return -EINVAL;
+#else
+ if (val != 0)
+ return -EINVAL;
+#endif
+
+ scheme->target_order = val;
+ return count;
+}
+
static void damon_sysfs_scheme_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_scheme, kobj));
@@ -2659,10 +2701,14 @@ static struct kobj_attribute damon_sysfs_scheme_apply_interval_us_attr =
static struct kobj_attribute damon_sysfs_scheme_target_nid_attr =
__ATTR_RW_MODE(target_nid, 0600);
+static struct kobj_attribute damon_sysfs_scheme_target_order_attr =
+ __ATTR_RW_MODE(target_order, 0600);
+
static struct attribute *damon_sysfs_scheme_attrs[] = {
&damon_sysfs_scheme_action_attr.attr,
&damon_sysfs_scheme_apply_interval_us_attr.attr,
&damon_sysfs_scheme_target_nid_attr.attr,
+ &damon_sysfs_scheme_target_order_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_scheme);
@@ -3011,6 +3057,8 @@ static struct damos *damon_sysfs_mk_scheme(
if (!scheme)
return NULL;
+ scheme->order = sysfs_scheme->target_order;
+
err = damos_sysfs_add_quota_score(sysfs_quotas->goals, &scheme->quota);
if (err) {
damon_destroy_scheme(scheme);
diff --git a/tools/testing/selftests/damon/sysfs.py b/tools/testing/selftests/damon/sysfs.py
index 3ffa054b6386..49d3c319dfd8 100755
--- a/tools/testing/selftests/damon/sysfs.py
+++ b/tools/testing/selftests/damon/sysfs.py
@@ -131,11 +131,12 @@ def assert_scheme_committed(scheme, dump):
'hugepage': 3,
'nohugepage': 4,
'collapse': 5,
- 'lru_prio': 6,
- 'lru_deprio': 7,
- 'migrate_hot': 8,
- 'migrate_cold': 9,
- 'stat': 10,
+ 'split': 6,
+ 'lru_prio': 7,
+ 'lru_deprio': 8,
+ 'migrate_hot': 9,
+ 'migrate_cold': 10,
+ 'stat': 11,
}
assert_true(dump['action'] == action_val[scheme.action], 'action', dump)
assert_true(dump['apply_interval_us'] == scheme. apply_interval_us,
^ permalink raw reply related
* [RFC PATCH v3 0/3] mm/damon: introduce DAMOS_SPLIT action
From: Lian Wang @ 2026-07-20 3:03 UTC (permalink / raw)
To: damon, linux-mm
Cc: sj, akpm, linux-kernel, david, ljs, liam, vbabka, rppt, surenb,
mhocko, npache, ziy, baolin.wang, ryan.roberts, daichaobing,
wangkefeng.wang, gutierrez.asier, zengheng4, kasong, corbet,
skhan, linux-doc, linux-kselftest, lianux.mm, lianux.wang,
kunwu.chan
DAMOS_SPLIT splits large folios in a target region down to a
configured target order, using the existing split_folio_to_order().
No new core-mm code or exported symbols are introduced.
Based on mm-unstable at 61cccb8363fc ("mm/swap, PM: hibernate:
atomically replace hibernation pin").
Different addresses within a PMD-mapped folio resolve to the same
PMD Accessed bit. Accesses to a small part of the folio can
therefore coarsen DAMON's observed hot set relative to the actual
working set.
DAMOS already provides promotion actions (HUGEPAGE, COLLAPSE) but
has no corresponding demotion action. DAMOS_SPLIT fills this gap.
It is a mechanism, not a policy -- it does not decide which folios
to split. Selection is left to DAMON's existing access patterns,
filters, and future probe/PMU signals.
target_order selects the split target: 0 for order-0 base pages,
or a supported smaller mTHP order. Both anonymous and file-backed
folios are supported. The locking follows split_huge_pages_in_pid()
in mm/huge_memory.c.
Tests
=====
damos_split.py (VM + Kunpeng 920):
anon THP -> order-0 split: PASS
sangfor_exp.py (Kunpeng 920, tmpfs, 4096 MiB):
- Created a 4 GiB PMD-mapped tmpfs workload.
- Applied DAMOS_SPLIT with target_order=0.
- ShmemPmdMapped dropped from 4194304 KiB to 0 in every round.
- Repeated for five rounds without functional failures.
The functional selftest (damos_split.py) is included in this series.
Additional experiment scripts and raw results are available on
request. Performance characterization using masim [1] and KMB [2]
is in progress.
[1] https://github.com/sjp38/masim
[2] https://gitee.com/OpenCloudOS/kernel-multi-bench
Open questions
==============
- Selection policy: this series keeps folio selection outside the
action and relies on DAMOS access patterns, filters, and quotas.
Is this the appropriate layering for future probe-based signals?
- Hysteresis: khugepaged may re-collapse a just-split folio.
Should cooldown live in DAMON policy or khugepaged?
- File-backed folios: adjust target_order upward to filesystem
minimum, or keep current "fail and skip"?
Beyond the action API itself, feedback on real workloads that need
proactive large-folio demotion is particularly welcome. Follow-up
work will evaluate candidate selection signals, including DAMON
probes and hardware-assisted sampling, as well as target-order
selection and split/collapse hysteresis. Those policies are
intentionally kept outside this series.
Changes since v2 [3]
====================
- Split-only series (collapse deferred).
- Dropped SPE feedback (mechanism/policy separation).
- DAMOS_MTHP_SPLIT -> DAMOS_SPLIT.
- order field in existing union (no struct size increase).
- Added functional selftest (damos_split.py).
- checkpatch: 0 errors, 0 warnings.
[3] https://lore.kernel.org/20260701123000.00000-1-lianux.mm@gmail.com/
Lian Wang (Processmission) (3):
mm/damon: introduce DAMOS_SPLIT action
mm/damon/vaddr: implement DAMOS_SPLIT handler
selftests/damon: add functional test for DAMOS_SPLIT
.../ABI/testing/sysfs-kernel-mm-damon | 7 ++
Documentation/mm/damon/design.rst | 5 +
include/linux/damon.h | 15 ++-
mm/damon/core.c | 2 +
mm/damon/sysfs-schemes.c | 48 +++++++
mm/damon/vaddr.c | 90 +++++++++++++
tools/testing/selftests/damon/Makefile | 1 +
tools/testing/selftests/damon/_damon_sysfs.py | 9 +-
tools/testing/selftests/damon/damos_split.py | 99 +++++++++++++++
tools/testing/selftests/damon/sysfs.py | 11 +-
10 files changed, 279 insertions(+), 8 deletions(-)
^ permalink raw reply
* [PATCH]docs: ja_JP: Add 1.Intro.rst Japanese Ver
From: dayodayo @ 2026-07-20 2:20 UTC (permalink / raw)
To: akiyks, corbet; +Cc: linux-doc
From d4614a0754863767726edf4ae1dba3f058c30d54 Mon Sep 17 00:00:00 2001
From: Sakamoto Yuto <sakadayo1210@gmail.com>
Date: Mon, 20 Jul 2026 09:29:46 +0900
Subject: [PATCH] Translate Documentation\process\1.Intro.rst into Japanese to
Documentation\translations\ja_JP\1.Intro.rst
Signed-off-by: Sakamoto Yuto <dayodayo1410@gmail.com>
---
.../translations/ja_JP/process/1.Intro.rst | 176 ++++++++++++++++++
1 file changed, 176 insertions(+)
create mode 100644 Documentation/translations/ja_JP/process/1.Intro.rst
diff --git a/Documentation/translations/ja_JP/process/1.Intro.rst
b/Documentation/translations/ja_JP/process/1.Intro.rst
new file mode 100644
index 000000000000..a3d0a12fdf75
--- /dev/null
+++ b/Documentation/translations/ja_JP/process/1.Intro.rst
@@ -0,0 +1,176 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+.. Originally contributed by sakadayo
+
+はじめに
+========
+
+.. note:: 【訳註】
+ この文書は、
+ Documentation/process/howto.rst
+ の翻訳です。
+ 免責条項については、
+ :ref:`免責条項の抄訳 <translations_ja_JP_disclaimer>` および、
+ :ref:`Disclaimer (英語版) <translations_disclaimer>` を参照してください。
+
+概要
+----
+
+この章の以下の部分では、カーネル開発プロセスの範疇と開発者やその企業が直面し得る問題に関して、説明を行います。
+カーネルコードは公式(メインライン)カーネルにマージされるべき理由は数多く存在します。
+ユーザーへの自動提供、多様な形態でのコミュニティ支援、かつカーネル開発の方向性に影響を与える等が、例として挙げられます。
+Linuxカーネルに貢献するコードは、必ずGPL互換ライセンスの下、公開してください。
+
+:ref:`development_process` は開発プロセス、カーネルのリリース周期、マージウィンドウの仕組みの三つに関して、説明されています。
+パッチの開発、評価、マージ周期の段階が、説明されています。
+これまで、ツールやメーリングリストに関しての質問や議論が、いくつかありました。
+カーネル開発をしてみたい開発者は、最初の練習としてバグの発見と修正をおすすめします。
+
+:ref:`development_early_stage`はできる限り早期段階での開発者のプロジェクト策定への関与について説明しています。
+
+:ref:`development_coding`はコーディングプロセスに関してであり、他の開発者が遭遇した落とし穴について議論されています。
+パッチに係るいくつかの要件が説明され、そしてパッチの正確性の確認を手助けするツールについて紹介されています。
+
+:ref:`development_posting`はレビューの為のパッチを投稿するプロセスについて、説明しています。
+開発コミュニティにあなたのパッチを真剣に受け取ってもらうには、パッチは適切な構成で記述され、且つ正しい場所に必ず送信されなければなりません。
+このアドバイスに従って頂ければ、あなたの努力は最良の形で受け入れられる事でしょう。
+
+:ref:`development_followthrough`はパッチの投稿後、何が起こるかについて説明しています。作業は評価される迄を以て作業と言うのです。
+レビュアーと共に作業する事は開発プロセスに於いて重要であり、この章ではこの大事な段階に於いて如何に問題を回避するかについていくつかヒントを提供します。
+開発者はメインラインにパッチが統合された時点で作業が完了したと思わない様、注意が必要です。
+
+:ref:`development_advancedtopics`はいくつかの「高度な」トピックを紹介しています。Gitを用いたパッチ管理や他の開発者が投稿したパッチの評価等が、例として挙げられます。
+
+:ref:`development_conclusion`は、カーネル開発者に対する更なる詳細情報へのリンクを含んだ、ドキュメントの締めくくりです。
+
+この文書の内容
+-------------
+
+八百万行を超えるコード、各リリースには千を超えるコントリビューター数を誇るLinuxカーネルは最も巨大、かつ最も活発なフリーソフトウェアプロジェクトです。
+1991年の細やかな始まりから、このカーネルはポケットサイズの音楽プレーヤーからデスクトップPCや最大のスパコンまで、ありとあらゆるシステムで使われる最高のOSコンポーネントへ進化しました。
+このソフトウェアは堅牢、効率、拡張性を兼ね備える、難局へのソリューションとなります。
+
+成長と普及に伴い、年々開発への参加を希望する開発者や会社は増えています。
+ハードウェアベンダーは、Linuxでの自社製品のサポートを確立し、Linuxユーザーにとって自社製品を魅力的なものたらしめたいと考えています。
+組み込みシステムベンダーは、自社の組み込みシステム製品の構成要素として、Linuxがより高性能であり、かつ目前のタスクに対して最適である事を求めています。
+ソフトウェア開発会社やその販売代理店等は、Linuxカーネルの機能、性能、信頼性に明確に興味関心を抱いています。
+エンドユーザーもまた、Linuxを自分のニーズに適したLinuxとする為、Linuxをより良いものとしたいと思うものと考えられます。
+
+Linuxの最も魅力ある特徴の一つは、開発者がアクセス可能である事にあります。
+要求される技術さえあれば、誰でもLinuxの開発に参加してLinuxを改良し、開発の方向性に影響を与える事が出来ます。
+企業製品等では、この様な開放性を提供する事は出来ません。これはフリーソフトウェアならではの特徴です。
+しかし、むしろカーネルは他のほとんどのフリーソフトウェアと比べ、なおさらオープンであると言えるでしょう。
+典型的な三か月間のカーネル開発周期では、千を超える開発者に百を超える企業(会社に属さない場合もある)が、開発に従事する可能性があるのです。
+
+開発コミュニティと協力する事は、特段難しい事ではありません。
+それに関わらず、しかしながらも多くの潜在的なコントリビューターが開発の際に、困難を経験しています。
+開発コミュニティは、毎日数千行のコードが変更される環境下であろうと円滑に機能する(かつ高品質なカーネルを生み出す)、コミュニティならではの開発方法を発展させて来ました。
+したがって、Linuxカーネル開発の手法やプロセスが企業等の開発の手法やプロセスと異なる事は、それほど驚くべき事では無いのです。
+
+カーネル開発のプロセスは奇妙で難解に思えるかも知れないものの、しかしその背景には然るべき正当な理由と確かな経験があります。
+開発コミュニティの方法を理解しない開発者(または悪い事にそれを無視したり回避しようとする開発者)は、必ずや不満の残る経験が残る事となりましょう。
+開発コミュニティは学習を目指す人々には親切に接しますが、開発プロセスに興味を持たず人の話を聞かない様な開発者にはほとんど時間を割きません。
+
+この文書を読み、上記の様な不快な経験を回避される事を願います。
+ここには膨大な資料が存在しますが、しかしそれを読み解く努力は瞬く間にに報われることでしょう。
+我らが開発コミュニティは、常にカーネルの改善に貢献してくれる開発者を求めています。以下の文章は、あなた自身或いはあなたの部下等が開発コミュニティに参入するのに役立つ事でしょう。
+
+クレジット
+----------
+
+この文書は、Jonathan Corbet (corbet@lwm.net)により記述されました。
+そしてJohannes Berg, James Berry, Alex Chiang, Roland Dreier, Randy
Dunlap, Jake Edge, Jiri Kosina, Matt Mackall, Arthur Marsh, Amanda
McPherson, Andrew Morton, Andrew Price, Tsugikazu Shibata, Jochen
Voßからのコメントにより、改善されて来ました。
+日本語への翻訳: Sakamoto Yuto(dayodayo1410@gmail.com)
+
+このプロジェクトはLinux財団により支援されました。この取り組みを理解し実現にご尽力されたAmanda McPherson氏に感謝いたします。
+
+コードをメインラインに取り込む事の重要性
+-------------------------------------
+
+いくつかの会社及び開発者は、時折なぜわざわざ開発コミュニティで作業する仕方を学び、コードをカーネルのメインライン(Linus
TorvaldsによってメンテナンスされていてLinuxディストリビュータの基盤として使われる)に組み込むべきなのか、不思議に思う事があるでしょう。
+短期的に見れば、コードをLinuxに組み込む事は面倒なコストの様に思われます。コードをLinuxに組み込まず、ただ使用者を直接サポートする方が簡単であるとも見えるでしょう。
+しかし実の所、コードを分離しておく事は、結局は無駄な出費と成り果てる事となります。
+ツリー外コードの弊害を明らかにする為に、カーネル開発プロセスに関連する側面をいくつか挙げましょう。これらの大半はこの文書内の後半にてより詳しく説明されます。
+以下の事例を考えてみて下さい。
+
+- カーネルのメインラインにマージされたコードは、全てのLinuxユーザーが使用可能です。
+ これは有効化されている全てのディストリビューションでも、それが自動的に提供される事となります。
+ ドライバディスク、ダウンロード、その他複数のディストリビューションの複数のバージョンに対応させる手間から解放され、開発者にとってもユーザーにとっても、全てがスムーズに動作します。
+ メインラインへのマージにより、多くのディストリビューションへの対応の問題が解決されます。
+
+- カーネル開発者達はユーザー空間への安定的なインターフェースの維持に努めていますが、カーネル内部のAPIは常に変化しています。
+ 安定した内部インターフェースが存在しないのは、設計上の意図的な決定であり、それによりいつでも改善が可能になる事で結果としてより高性能なコードに繋がります。
+ しかし、この方針の結果、例外無くツリー外コードには新しいカーネルに対応させる為のメンテナンスが必要になります。
+ ツリー外コードのメンテナンスには、コードの動作を確保するのみでも相当な作業量が必要となります。
+
+ 一方、メインライン内のコードに関しては、「API変更を行った開発者は、変更により影響を及ぼしたり動作しなくなるコードも修正せねばならない」という単純な規則が存在する為、この様な作業は必要ありません。
+ その為、メインラインにマージされたコードは、大幅にメンテナンスコストが低いです。
+
+- 更に、カーネル内にあるコードは他の開発者によって改善される事がよくあります。
+ ユーザーコミュニティや顧客に製品の改善を任せる事で、驚くべき結果が得られる可能性があります。
+
+- カーネルコードはマージ前後の両方において、評価されます。
+ どれだけ元の開発者の技術や能力が優れていたとて、レビューは必ず元の開発者の思いもしない改善点を見つけ出します。
+ レビューでは、よく深刻なバグやセキュリティ上の問題が発見される事があります。
+ 特にクローズドソースソフトにこの事実はよく当てはまる為、その様なコードは外部開発者のレビューからより改善されます。
+ ツリー外コードは低品質なコードです。
+
+- 開発プロセスへの参加は、カーネル開発の方向性に影響を及ぼす方法であります。
+ 傍観者として不満を論うユーザーの声もよく開発者達に届きますが、活動的に開発に貢献する開発者がより強い発言力と自分達の需要に合ったLinuxカーネルを実現する変更を実装する力を有する事実は明らかでしょう。
+
+- コードを個別に管理する場合、第三者が類似した機能の異なる実装を提供する可能性と、あなたはいつも隣合わせです。
+ その様な事態が発生した場合、コードのマージは非常に困難になり、時には拒絶されるかも知れません。
+ もしそうなってしまえば、以下の様な好ましくない事態に直面します。
+ (1)ツリー外非標準機能を無期限にメンテナンスする事になる
+ (2)コードを放棄し、ツリー内のバージョンにユーザーを移行させる
+
+- コードの貢献は、プロセス全体を機能させる根本的行動です。
+ あなたのコードを提供する事で、新機能をカーネルに追加する事ができ、かつ外部のカーネル開発者にとって役立つ機能や例を提供できます。
+ もしあなたがLinux向けコードを開発した事があるのなら(またはそうしようと考えたか)、あなたはこのプラットフォームの継続的な成功に関心を持っているものと思われます。
+ コードの貢献は、その成功を確定させる為の最も良い道の一つです。
+
+ここで挙げられる理由の全ては、独自のバイナリのみの形式で配布されるものを含む全てのツリー外カーネルコードに言えます。
+更バイナリのみのカーネルコード配布を考える前に、他にも考慮すべき以下の要素が存在します。
+
+- 独自のカーネルモジュール配布に係る法的問題は、特に複雑です。少なからず多くのカーネルの著作権を保有する者はこれらのモジュールはカーネルから派生した製品であり、それらの配布はGNU一般公衆ライセンス(詳細は後述)に違反すると考えられます。
+ 著者は弁護士では無いものですから、この文章の内容は法的助言と捉えられるべきではありません。
+ クローズドソースモジュールの法的地位に関しては、裁判所によってのみ決定されます。
+ しかし、いずれにせよそれらのモジュールについてまわる不確実性は存在します。
+
+- バイナリのみのモジュールはカーネルのデバグ問題をより難解にする為、殆どのカーネル開発者はそれを試みようとさえしないでしょう。
+ したがってバイナリのみのモジュールを配布する事は、ユーザーにコミュニティからのサポートを享受させる事を阻害し得ます。
+
+- バイナリのみのモジュールの配布者にとっても、サポートはより困難となります。
+ 誰かが必ずサポートしたいカーネルバージョンや配布毎にバージョンを用意する必要があるからです。
+ 十分な網羅性を提供する為には一つのモジュールに対し数十ものビルドが必要であり、そしてユーザーはカーネルをアップグレードする度、個別にあなたのモジュールをアップグレードせねばならないでしょう。
+
+- コードレビューに関する上記の内容は、クローズドソースコードに特に適用できます。
+ このコードは全て公開されていない為、それはコミュニティからのレビューを受けていないでしょうし、間違いなく重大な問題が存在するでしょう。
+
+特に組み込みシステムのメーカーは、固定されたカーネルバージョンでリリース後に更なる開発が不要な製品を出荷したい思いに基いて、この章で述べられた事を無視したくなるかも知れません。
+この主張は広範なコードレビューの価値やユーザーが製品に機能を追加できる事の価値を見落としており、かつこれらの製品も寿命は限られており、その後に新しいバージョンをリリースせねばならないのです。
+その時点では、コードをメインラインに統合し適切なメンテナンスを受けられているベンダーの方が、より早く新しい製品を準備する事が出来るでしょう。
+
+ライセンス
+----------
+
+コードは、多数のライセンス下でLinux Kernelにコントリビュートされている。
+しかし、全てのコードがカーネルディストリビューション等の全体を対象とするversion 2 of the GNU General
Public License (GPLv2)に従っていなければならなりません。
+実際には、それは全てのコントリビュートはGPLv2に従うか(補足事項でGPLの最新のバージョン下での配布が許可される)、またはthe
three-clause BSD licenseに従う必要がある事を意味しています。
+
+カーネルに貢献するコードに関して、著作権譲渡は必須ではありませんし要求もされません。
+全てのメインラインにマージされるコードは元の所有権を保持しており、その結果としてこのカーネルは今や数千人の所有者を抱えています。
+
+この所有構造の意味する所は、いかなるカーネルのライセンス変更の試みも必ずや失敗するという事です。
+極めて稀な実際のシナリオとして、著作権保持者全員から許可を得る(あるいはカーネルから開発者達のコードが削除される)場合があります。
+だからといってそれは稀なシナリオであって、特にGPLv3への移行の見通しはありません。
+
+全てのカーネルにコントリビュートするコードは、正真正銘の無料である事が不可欠です。
+その為、身元不明あるいは匿名のコントリビューターはこれを受け付けません。
+全てのコントリビューターは、そのコードに署名し、GPLライセンス下でそのコードが配布され得る事を承認する必要があります。
+所有者によってライセンスされていないコード、著作権関係の法的問題等を引き起こす危険性のあるコード(適切な保護措置の欠如したリバースエンジニアリング等に由来する様なコード)はコントリビュート出来ません。
+
+著作権関係問題の悩みに関する質問は、Linuxの開発メーリングリストではよくある事です。
+この様な質問は通常多くの返答が来ますが、回答してくれている人たちは弁護士では無く、法的助言を出来ない事にご留意ください。
+Linuxソースコードに関する法的質問がある場合は、この分野を理解している弁護士に聞くのが一番です。
+Linuxの開発メーリングリストの様な技術的なメーリングリストでの返答に頼り切りになる事は、リスキーです。
\ No newline at end of file
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2 0/2] mm/zswap: Fixes and improves the zswap global shrinker
From: Hao Jia @ 2026-07-20 1:26 UTC (permalink / raw)
To: Andrew Morton, Yosry Ahmed
Cc: tj, hannes, shakeel.butt, mhocko, mkoutny, nphamcs,
chengming.zhou, muchun.song, roman.gushchin, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <20260717214031.8b22e9a37ea3b0c564b466f0@linux-foundation.org>
On 2026/7/18 12:40, Andrew Morton wrote:
> On Fri, 17 Jul 2026 18:28:04 -0700 Yosry Ahmed <yosry@kernel.org> wrote:
>
>>>
>>> The [1/2] changelog lacks a description of how the flaw impacts users.
>>> Please describe this fully and maintain that info within the
>>> changelogging. This info helps -stable maintainers and others
>>> understand why we're proposing a backport and helps myself and others
>>> with timing decisions.
>>
>> The first line in the changelog should be sufficient imo: "Zswap
>> writeback on hitting the pool limit is broken when memory cgroup is
>> disabled"
>
> "broken"? Perhaps this means "fails to occur".
>
> But what is the userspace-visible impact? IOW, why are we proposing a
> backport?
>
Perhaps the first paragraph of the commit1 message could be modified as
follows? I have added a description of the issues that occur without
this patch.
Zswap writeback on hitting the pool limit fails to occur when memory
cgroup is disabled, because mem_cgroup_iter() always returns NULL.
Therefore, the global shrinker shrink_worker() always takes the !memcg
branch. After MAX_RECLAIM_RETRIES empty walks, the worker simply gives
up, so it fails to write back anything. As a result, once the pool
reaches the zswap limit, every subsequent zswap shrink work run is a
no-op. This leads to zswap store failures, forcing pages to bypass zswap
and be written directly to the backing swap device, which can trigger
issues such as LRU inversion.
Thanks,
Hao
^ permalink raw reply
* [PATCH v13 32/32] KVM: arm64: selftests: Add SME to set_id_regs test
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
Add coverage of the SME ID registers to set_id_regs, ID_AA64PFR1_EL1.SME
becomes writable and we add ID_AA64SMFR0_EL1 and it's subfields.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
tools/testing/selftests/kvm/arm64/set_id_regs.c | 30 +++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/set_id_regs.c b/tools/testing/selftests/kvm/arm64/set_id_regs.c
index da9349bf31ab..26db507f6e73 100644
--- a/tools/testing/selftests/kvm/arm64/set_id_regs.c
+++ b/tools/testing/selftests/kvm/arm64/set_id_regs.c
@@ -155,6 +155,7 @@ static const struct reg_ftr_bits ftr_id_aa64pfr0_el1[] = {
static const struct reg_ftr_bits ftr_id_aa64pfr1_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, DF2, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, CSV2_frac, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, SME, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, SSBS, ID_AA64PFR1_EL1_SSBS_NI),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, BT, 0),
REG_FTR_END,
@@ -212,6 +213,33 @@ static const struct reg_ftr_bits ftr_id_aa64mmfr3_el1[] = {
REG_FTR_END,
};
+static const struct reg_ftr_bits ftr_id_aa64smfr0_el1[] = {
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, FA64, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, LUTv2, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, SMEver, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, I16I64, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, F64F64, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, I16I32, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, B16B16, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, F16F16, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, F8F16, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, F8F32, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, I8I32, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, F16F32, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, B16F32, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, BI32I32, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, F32F32, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, SF8FMA, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, SF8DP4, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, SF8DP2, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, SBitPerm, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, AES, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, SFEXPA, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, STMOP, 0),
+ REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64SMFR0_EL1, SMOP4, 0),
+ REG_FTR_END,
+};
+
static const struct reg_ftr_bits ftr_id_aa64zfr0_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, F64MM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, F32MM, 0),
@@ -244,6 +272,7 @@ static struct test_feature_reg test_regs[] = {
TEST_REG(SYS_ID_AA64MMFR1_EL1, ftr_id_aa64mmfr1_el1),
TEST_REG(SYS_ID_AA64MMFR2_EL1, ftr_id_aa64mmfr2_el1),
TEST_REG(SYS_ID_AA64MMFR3_EL1, ftr_id_aa64mmfr3_el1),
+ TEST_REG(SYS_ID_AA64SMFR0_EL1, ftr_id_aa64smfr0_el1),
TEST_REG(SYS_ID_AA64ZFR0_EL1, ftr_id_aa64zfr0_el1),
};
@@ -263,6 +292,7 @@ static void guest_code(void)
GUEST_REG_SYNC(SYS_ID_AA64MMFR1_EL1);
GUEST_REG_SYNC(SYS_ID_AA64MMFR2_EL1);
GUEST_REG_SYNC(SYS_ID_AA64MMFR3_EL1);
+ GUEST_REG_SYNC(SYS_ID_AA64SMFR0_EL1);
GUEST_REG_SYNC(SYS_ID_AA64ZFR0_EL1);
GUEST_REG_SYNC(SYS_MPIDR_EL1);
GUEST_REG_SYNC(SYS_CLIDR_EL1);
--
2.47.3
^ permalink raw reply related
* [PATCH v13 31/32] KVM: arm64: selftests: Add SME system registers to get-reg-list
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
SME adds a number of new system registers, update get-reg-list to check for
them based on the visibility of SME.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
tools/testing/selftests/kvm/arm64/get-reg-list.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/kvm/arm64/get-reg-list.c b/tools/testing/selftests/kvm/arm64/get-reg-list.c
index 0a3a94c4cca1..876c4719e2e2 100644
--- a/tools/testing/selftests/kvm/arm64/get-reg-list.c
+++ b/tools/testing/selftests/kvm/arm64/get-reg-list.c
@@ -61,7 +61,13 @@ static struct feature_id_reg feat_id_regs[] = {
REG_FEAT(HFGITR2_EL2, ID_AA64MMFR0_EL1, FGT, FGT2),
REG_FEAT(HDFGRTR2_EL2, ID_AA64MMFR0_EL1, FGT, FGT2),
REG_FEAT(HDFGWTR2_EL2, ID_AA64MMFR0_EL1, FGT, FGT2),
- REG_FEAT(ZCR_EL2, ID_AA64PFR0_EL1, SVE, IMP),
+ REG_FEAT(SMCR_EL1, ID_AA64PFR1_EL1, SME, IMP),
+ REG_FEAT(SMCR_EL2, ID_AA64PFR1_EL1, SME, IMP),
+ REG_FEAT(SMIDR_EL1, ID_AA64PFR1_EL1, SME, IMP),
+ REG_FEAT(SMPRI_EL1, ID_AA64PFR1_EL1, SME, IMP),
+ REG_FEAT(SMPRIMAP_EL2, ID_AA64PFR1_EL1, SME, IMP),
+ REG_FEAT(TPIDR2_EL0, ID_AA64PFR1_EL1, SME, IMP),
+ REG_FEAT(SVCR, ID_AA64PFR1_EL1, SME, IMP),
REG_FEAT(SCTLR2_EL1, ID_AA64MMFR3_EL1, SCTLRX, IMP),
REG_FEAT(SCTLR2_EL2, ID_AA64MMFR3_EL1, SCTLRX, IMP),
REG_FEAT(VDISR_EL2, ID_AA64PFR0_EL1, RAS, IMP),
@@ -367,6 +373,7 @@ static __u64 base_regs[] = {
ARM64_SYS_REG(3, 0, 0, 0, 0), /* MIDR_EL1 */
ARM64_SYS_REG(3, 0, 0, 0, 6), /* REVIDR_EL1 */
ARM64_SYS_REG(3, 1, 0, 0, 1), /* CLIDR_EL1 */
+ ARM64_SYS_REG(3, 1, 0, 0, 6), /* SMIDR_EL1 */
ARM64_SYS_REG(3, 1, 0, 0, 7), /* AIDR_EL1 */
ARM64_SYS_REG(3, 3, 0, 0, 1), /* CTR_EL0 */
ARM64_SYS_REG(2, 0, 0, 0, 4),
@@ -498,6 +505,8 @@ static __u64 base_regs[] = {
ARM64_SYS_REG(3, 0, 1, 0, 1), /* ACTLR_EL1 */
ARM64_SYS_REG(3, 0, 1, 0, 2), /* CPACR_EL1 */
KVM_ARM64_SYS_REG(SYS_SCTLR2_EL1),
+ ARM64_SYS_REG(3, 0, 1, 2, 4), /* SMPRI_EL1 */
+ ARM64_SYS_REG(3, 0, 1, 2, 6), /* SMCR_EL1 */
ARM64_SYS_REG(3, 0, 2, 0, 0), /* TTBR0_EL1 */
ARM64_SYS_REG(3, 0, 2, 0, 1), /* TTBR1_EL1 */
ARM64_SYS_REG(3, 0, 2, 0, 2), /* TCR_EL1 */
@@ -518,9 +527,11 @@ static __u64 base_regs[] = {
ARM64_SYS_REG(3, 0, 13, 0, 4), /* TPIDR_EL1 */
ARM64_SYS_REG(3, 0, 14, 1, 0), /* CNTKCTL_EL1 */
ARM64_SYS_REG(3, 2, 0, 0, 0), /* CSSELR_EL1 */
+ ARM64_SYS_REG(3, 3, 4, 2, 2), /* SVCR */
ARM64_SYS_REG(3, 3, 10, 2, 4), /* POR_EL0 */
ARM64_SYS_REG(3, 3, 13, 0, 2), /* TPIDR_EL0 */
ARM64_SYS_REG(3, 3, 13, 0, 3), /* TPIDRRO_EL0 */
+ ARM64_SYS_REG(3, 3, 13, 0, 5), /* TPIDR2_EL0 */
ARM64_SYS_REG(3, 3, 14, 0, 1), /* CNTPCT_EL0 */
ARM64_SYS_REG(3, 3, 14, 2, 1), /* CNTP_CTL_EL0 */
ARM64_SYS_REG(3, 3, 14, 2, 2), /* CNTP_CVAL_EL0 */
@@ -730,6 +741,8 @@ static __u64 el2_regs[] = {
SYS_REG(HFGITR_EL2),
SYS_REG(HACR_EL2),
SYS_REG(ZCR_EL2),
+ SYS_REG(SMPRIMAP_EL2),
+ SYS_REG(SMCR_EL2),
SYS_REG(HCRX_EL2),
SYS_REG(TTBR0_EL2),
SYS_REG(TTBR1_EL2),
--
2.47.3
^ permalink raw reply related
* [PATCH v13 30/32] KVM: arm64: selftests: Skip impossible invalid value tests
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
The set_id_regs test currently assumes that there will always be invalid
values available in bitfields for it to generate but this may not be the
case if the architecture has defined meanings for every possible value for
the bitfield. An assert added in commit bf09ee918053e ("KVM: arm64:
selftests: Remove ARM64_FEATURE_FIELD_BITS and its last user") refuses to
run for single bit fields which will show the issue most readily but there
is no reason wider ones can't show the same issue.
Rework the tests for invalid value to check if an invalid value can be
generated and skip the test if not, removing the assert.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
tools/testing/selftests/kvm/arm64/set_id_regs.c | 64 +++++++++++++++++++++----
1 file changed, 54 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/set_id_regs.c b/tools/testing/selftests/kvm/arm64/set_id_regs.c
index 9eceac84e297..da9349bf31ab 100644
--- a/tools/testing/selftests/kvm/arm64/set_id_regs.c
+++ b/tools/testing/selftests/kvm/arm64/set_id_regs.c
@@ -327,54 +327,95 @@ u64 get_safe_value(const struct reg_ftr_bits *ftr_bits, u64 ftr)
}
/* Return an invalid value to a given ftr_bits an ftr value */
-u64 get_invalid_value(const struct reg_ftr_bits *ftr_bits, u64 ftr)
+u64 get_invalid_value(const struct reg_ftr_bits *ftr_bits, u64 ftr, bool *skip)
{
u64 ftr_max = ftr_bits->mask >> ftr_bits->shift;
- TEST_ASSERT(ftr_max > 1, "This test doesn't support single bit features");
+ *skip = false;
if (ftr_bits->sign == FTR_UNSIGNED) {
switch (ftr_bits->type) {
case FTR_EXACT:
ftr = max((u64)ftr_bits->safe_val + 1, ftr + 1);
+ if (ftr >= ftr_max)
+ *skip = true;
break;
case FTR_LOWER_SAFE:
+ if (ftr == ftr_max)
+ *skip = true;
ftr++;
break;
case FTR_HIGHER_SAFE:
+ if (ftr == 0)
+ *skip = true;
ftr--;
break;
case FTR_HIGHER_OR_ZERO_SAFE:
- if (ftr == 0)
+ switch (ftr) {
+ case 0:
ftr = ftr_max;
- else
+ break;
+ case 1:
+ *skip = true;
+ break;
+ default:
ftr--;
+ break;
+ }
break;
default:
+ *skip = true;
break;
}
} else if (ftr != ftr_max) {
switch (ftr_bits->type) {
case FTR_EXACT:
ftr = max((u64)ftr_bits->safe_val + 1, ftr + 1);
+ if (ftr >= ftr_max)
+ *skip = true;
break;
case FTR_LOWER_SAFE:
ftr++;
break;
case FTR_HIGHER_SAFE:
- ftr--;
+ /* FIXME: "need to check for the actual highest." */
+ if (ftr == 0 || ftr == ftr_max)
+ *skip = true;
+ else
+ ftr--;
break;
case FTR_HIGHER_OR_ZERO_SAFE:
- if (ftr == 0)
- ftr = ftr_max - 1;
- else
+ switch (ftr) {
+ case 0:
+ if (ftr_max > 1)
+ ftr = ftr_max - 1;
+ else
+ *skip = true;
+ break;
+ case 1:
+ *skip = true;
+ break;
+ default:
ftr--;
+ break;
+ }
break;
default:
+ *skip = true;
break;
}
} else {
- ftr = 0;
+ switch (ftr_bits->type) {
+ case FTR_LOWER_SAFE:
+ if (ftr == 0)
+ *skip = true;
+ else
+ ftr = 0;
+ break;
+ default:
+ *skip = true;
+ break;
+ }
}
return ftr;
@@ -409,12 +450,15 @@ static void test_reg_set_fail(struct kvm_vcpu *vcpu, u64 reg,
u8 shift = ftr_bits->shift;
u64 mask = ftr_bits->mask;
u64 val, old_val, ftr;
+ bool skip;
int r;
val = vcpu_get_reg(vcpu, reg);
ftr = (val & mask) >> shift;
- ftr = get_invalid_value(ftr_bits, ftr);
+ ftr = get_invalid_value(ftr_bits, ftr, &skip);
+ if (skip)
+ return;
old_val = val;
ftr <<= shift;
--
2.47.3
^ permalink raw reply related
* [PATCH v13 29/32] KVM: arm64: selftests: Remove spurious check for single bit safe values
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
get_safe_value() currently asserts that bitfields it is generating a safe
value for must be more than one bit wide but in actual fact it should
always be possible to generate a safe value to write to a bitfield even if
it is just the current value and the function correctly handles that.
Remove the assert.
Fixes: bf09ee918053e ("KVM: arm64: selftests: Remove ARM64_FEATURE_FIELD_BITS and its last user")
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
tools/testing/selftests/kvm/arm64/set_id_regs.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/set_id_regs.c b/tools/testing/selftests/kvm/arm64/set_id_regs.c
index 7429a1055df5..9eceac84e297 100644
--- a/tools/testing/selftests/kvm/arm64/set_id_regs.c
+++ b/tools/testing/selftests/kvm/arm64/set_id_regs.c
@@ -279,8 +279,6 @@ u64 get_safe_value(const struct reg_ftr_bits *ftr_bits, u64 ftr)
{
u64 ftr_max = ftr_bits->mask >> ftr_bits->shift;
- TEST_ASSERT(ftr_max > 1, "This test doesn't support single bit features");
-
if (ftr_bits->sign == FTR_UNSIGNED) {
switch (ftr_bits->type) {
case FTR_EXACT:
--
2.47.3
^ permalink raw reply related
* [PATCH v13 28/32] KVM: arm64: Provide interface for configuring and enabling SME for guests
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
Since SME requires configuration of a vector length in order to know the
size of both the streaming mode SVE state and ZA array we implement a
capability for it and require that it be enabled and finalized before
the SME specific state can be accessed, similarly to SVE.
Due to the overlap with sizing the SVE state we finalise both SVE and
SME with a single finalization, preventing any further changes to the
SVE and SME configuration once KVM_ARM_VCPU_VEC (an alias for _VCPU_SVE)
has been finalised. This is not a thing of great elegance but it ensures
that we never have a state where one of SVE or SME is finalised and the
other not, avoiding complexity.
Since unlike SVE there is no architecturally manadated vector length
which must be supported by all PEs we detect the case where the feature
is supported but there is no shared VL and hide the feature.
SME is supported for normal guests only.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/asm/kvm_host.h | 8 ++-
arch/arm64/include/uapi/asm/kvm.h | 1 +
arch/arm64/kvm/arm.c | 10 ++++
arch/arm64/kvm/hyp/nvhe/pkvm.c | 81 ++++++++++++++++++++-----
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 6 ++
arch/arm64/kvm/reset.c | 119 +++++++++++++++++++++++++++++++------
include/uapi/linux/kvm.h | 1 +
7 files changed, 192 insertions(+), 34 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 7ebe7b6cc96e..ab395b6d5c95 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -39,7 +39,7 @@
#define KVM_MAX_VCPUS VGIC_V3_MAX_CPUS
-#define KVM_VCPU_MAX_FEATURES 9
+#define KVM_VCPU_MAX_FEATURES 10
#define KVM_VCPU_VALID_FEATURES (BIT(KVM_VCPU_MAX_FEATURES) - 1)
#define KVM_REQ_SLEEP \
@@ -81,6 +81,7 @@ extern unsigned int __ro_after_init kvm_max_vl[ARM64_VEC_MAX];
extern unsigned int __ro_after_init kvm_host_max_vl[ARM64_VEC_MAX];
int __init kvm_arm_init_sve(void);
+int __init kvm_arm_init_sme(void);
u32 __attribute_const__ kvm_target_cpu(void);
void kvm_reset_vcpu(struct kvm_vcpu *vcpu);
@@ -1804,4 +1805,9 @@ static __always_inline enum fgt_group_id __fgt_reg_to_group_id(enum vcpu_sysreg
long kvm_get_cap_for_kvm_ioctl(unsigned int ioctl, long *ext);
+static inline bool system_supports_sme_virt(void)
+{
+ return system_supports_sme() && sme_max_virtualisable_vl();
+}
+
#endif /* __ARM64_KVM_HOST_H__ */
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index deccb034fce3..db240ba5555f 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -106,6 +106,7 @@ struct kvm_regs {
#define KVM_ARM_VCPU_PTRAUTH_GENERIC 6 /* VCPU uses generic authentication */
#define KVM_ARM_VCPU_HAS_EL2 7 /* Support nested virtualization */
#define KVM_ARM_VCPU_HAS_EL2_E2H0 8 /* Limit NV support to E2H RES0 */
+#define KVM_ARM_VCPU_SME 9 /* enable SME for this CPU */
/*
* An alias for _SVE since we finalize VL configuration for both SVE and SME
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index d43cfdcaee64..1b30470abde8 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -463,6 +463,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_ARM_SVE:
r = system_supports_sve();
break;
+ case KVM_CAP_ARM_SME:
+ r = system_supports_sme_virt();
+ break;
case KVM_CAP_ARM_PTRAUTH_ADDRESS:
case KVM_CAP_ARM_PTRAUTH_GENERIC:
r = kvm_has_full_ptr_auth();
@@ -1569,6 +1572,9 @@ static unsigned long system_supported_vcpu_features(void)
if (!system_supports_sve())
clear_bit(KVM_ARM_VCPU_SVE, &features);
+ if (!system_supports_sme_virt())
+ clear_bit(KVM_ARM_VCPU_SME, &features);
+
if (!kvm_has_full_ptr_auth()) {
clear_bit(KVM_ARM_VCPU_PTRAUTH_ADDRESS, &features);
clear_bit(KVM_ARM_VCPU_PTRAUTH_GENERIC, &features);
@@ -3041,6 +3047,10 @@ static __init int kvm_arm_init(void)
if (err)
return err;
+ err = kvm_arm_init_sme();
+ if (err)
+ return err;
+
err = kvm_arm_vmid_alloc_init();
if (err) {
kvm_err("Failed to initialize VMID allocator.\n");
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 620f3395ea4e..ed9ce39ee92f 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -150,10 +150,6 @@ static int pkvm_check_pvm_cpu_features(struct kvm_vcpu *vcpu)
!kvm_has_feat(kvm, ID_AA64PFR0_EL1, AdvSIMD, IMP))
return -EINVAL;
- /* No SME support in KVM right now. Check to catch if it changes. */
- if (kvm_has_feat(kvm, ID_AA64PFR1_EL1, SME, IMP))
- return -EINVAL;
-
return 0;
}
@@ -386,6 +382,11 @@ static void pkvm_init_features_from_host(struct pkvm_hyp_vm *hyp_vm, const struc
kvm->arch.flags |= host_arch_flags & BIT(KVM_ARCH_FLAG_GUEST_HAS_SVE);
}
+ if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_SME)) {
+ set_bit(KVM_ARM_VCPU_SME, allowed_features);
+ kvm->arch.flags |= host_arch_flags & BIT(KVM_ARCH_FLAG_GUEST_HAS_SME);
+ }
+
bitmap_and(kvm->arch.vcpu_features, host_kvm->arch.vcpu_features,
allowed_features, KVM_VCPU_MAX_FEATURES);
}
@@ -400,7 +401,8 @@ static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu)
{
void *sve_state;
- if (!vcpu_has_feature(&hyp_vcpu->vcpu, KVM_ARM_VCPU_SVE))
+ if (!vcpu_has_feature(&hyp_vcpu->vcpu, KVM_ARM_VCPU_SVE) &&
+ !vcpu_has_feature(&hyp_vcpu->vcpu, KVM_ARM_VCPU_SME))
return;
sve_state = hyp_vcpu->vcpu.arch.sve_state;
@@ -408,6 +410,18 @@ static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu)
sve_state + vcpu_sve_state_size(&hyp_vcpu->vcpu));
}
+static void unpin_host_sme_state(struct pkvm_hyp_vcpu *hyp_vcpu)
+{
+ void *sme_state;
+
+ if (!vcpu_has_feature(&hyp_vcpu->vcpu, KVM_ARM_VCPU_SME))
+ return;
+
+ sme_state = hyp_vcpu->vcpu.arch.sme_state;
+ hyp_unpin_shared_mem(sme_state,
+ sme_state + vcpu_sme_state_size(&hyp_vcpu->vcpu));
+}
+
static void unpin_host_vcpus(struct pkvm_hyp_vcpu *hyp_vcpus[],
unsigned int nr_vcpus)
{
@@ -421,6 +435,7 @@ static void unpin_host_vcpus(struct pkvm_hyp_vcpu *hyp_vcpus[],
unpin_host_vcpu(hyp_vcpu->host_vcpu);
unpin_host_sve_state(hyp_vcpu);
+ unpin_host_sme_state(hyp_vcpu);
}
}
@@ -447,23 +462,35 @@ static void init_pkvm_hyp_vm(struct kvm *host_kvm, struct pkvm_hyp_vm *hyp_vm,
mmu->pgt = &hyp_vm->pgt;
}
-static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *host_vcpu)
+static int pkvm_vcpu_init_vec(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *host_vcpu)
{
struct kvm_vcpu *vcpu = &hyp_vcpu->vcpu;
- unsigned int sve_max_vl;
- size_t sve_state_size;
- void *sve_state;
+ unsigned int sve_max_vl, sme_max_vl;
+ size_t sve_state_size, sme_state_size;
+ void *sve_state, *sme_state;
int ret = 0;
- if (!vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE)) {
+ if (!vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE) &&
+ !vcpu_has_feature(vcpu, KVM_ARM_VCPU_SME)) {
vcpu_clear_flag(vcpu, VCPU_VEC_FINALIZED);
return 0;
}
/* Limit guest vector length to the maximum supported by the host. */
- sve_max_vl = min(READ_ONCE(host_vcpu->arch.max_vl[ARM64_VEC_SVE]),
- kvm_host_max_vl[ARM64_VEC_SVE]);
- sve_state_size = sve_state_size_from_vl(sve_max_vl);
+ if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE))
+ sve_max_vl = min(READ_ONCE(host_vcpu->arch.max_vl[ARM64_VEC_SVE]),
+ kvm_host_max_vl[ARM64_VEC_SVE]);
+ else
+ sve_max_vl = 0;
+
+ if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SME))
+ sme_max_vl = min(READ_ONCE(host_vcpu->arch.max_vl[ARM64_VEC_SME]),
+ kvm_host_max_vl[ARM64_VEC_SME]);
+ else
+ sme_max_vl = 0;
+
+ /* We need SVE storage for the larger of normal or streaming mode */
+ sve_state_size = sve_state_size_from_vl(max(sve_max_vl, sme_max_vl));
sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state));
if (!sve_state || !sve_state_size) {
@@ -475,12 +502,37 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h
if (ret)
goto err;
+ if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SME)) {
+ /* Space for ZT0 is always allocated to simplify the code. */
+ sme_state_size = sme_state_size_from_vl(sme_max_vl,
+ system_supports_sme2());
+ sme_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sme_state));
+
+ if (!sme_state || !sme_state_size) {
+ ret = -EINVAL;
+ goto err_sve_mapped;
+ }
+
+ ret = hyp_pin_shared_mem(sme_state, sme_state + sme_state_size);
+ if (ret)
+ goto err_sve_mapped;
+ } else {
+ sme_state = NULL;
+ }
+
vcpu->arch.sve_state = sve_state;
vcpu->arch.max_vl[ARM64_VEC_SVE] = sve_max_vl;
+ vcpu->arch.sme_state = sme_state;
+ vcpu->arch.max_vl[ARM64_VEC_SME] = sme_max_vl;
+
return 0;
+
+err_sve_mapped:
+ hyp_unpin_shared_mem(sve_state, sve_state + sve_state_size);
err:
clear_bit(KVM_ARM_VCPU_SVE, vcpu->kvm->arch.vcpu_features);
+ clear_bit(KVM_ARM_VCPU_SME, vcpu->kvm->arch.vcpu_features);
return ret;
}
@@ -540,7 +592,7 @@ static int init_pkvm_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu,
if (ret)
goto done;
- ret = pkvm_vcpu_init_sve(hyp_vcpu, host_vcpu);
+ ret = pkvm_vcpu_init_vec(hyp_vcpu, host_vcpu);
done:
if (ret)
unpin_host_vcpu(host_vcpu);
@@ -926,6 +978,7 @@ int __pkvm_init_vcpu(pkvm_handle_t handle, struct kvm_vcpu *host_vcpu,
if (ret) {
unpin_host_vcpu(host_vcpu);
unpin_host_sve_state(hyp_vcpu);
+ unpin_host_sme_state(hyp_vcpu);
}
unlock:
hyp_spin_unlock(&vm_table_lock);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 8c3fbb413a06..e2d8a28be8f5 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -67,6 +67,11 @@ static bool vm_has_ptrauth(const struct kvm *kvm)
kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_PTRAUTH_GENERIC);
}
+static bool vm_has_sme(const struct kvm *kvm)
+{
+ return system_supports_sme() && kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_SME);
+}
+
static bool vm_has_sve(const struct kvm *kvm)
{
return system_supports_sve() && kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_SVE);
@@ -103,6 +108,7 @@ static const struct pvm_ftr_bits pvmid_aa64pfr0[] = {
};
static const struct pvm_ftr_bits pvmid_aa64pfr1[] = {
+ MAX_FEAT_FUNC(ID_AA64PFR1_EL1, SME, SME2, vm_has_sme),
MAX_FEAT(ID_AA64PFR1_EL1, BT, IMP),
MAX_FEAT(ID_AA64PFR1_EL1, SSBS, SSBS2),
MAX_FEAT_ENUM(ID_AA64PFR1_EL1, MTE_frac, NI),
diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c
index fee01c38fa13..c0c605ff22bd 100644
--- a/arch/arm64/kvm/reset.c
+++ b/arch/arm64/kvm/reset.c
@@ -76,6 +76,31 @@ int __init kvm_arm_init_sve(void)
return 0;
}
+int __init kvm_arm_init_sme(void)
+{
+ if (system_supports_sme()) {
+ kvm_host_max_vl[ARM64_VEC_SME] = sme_max_vl();
+ kvm_nvhe_sym(kvm_host_max_vl[ARM64_VEC_SME]) = kvm_host_max_vl[ARM64_VEC_SME];
+ }
+
+ if (system_supports_sme_virt()) {
+ kvm_max_vl[ARM64_VEC_SME] = sme_max_virtualisable_vl();
+
+ if (WARN_ON(kvm_max_vl[ARM64_VEC_SME] > VL_ARCH_MAX))
+ kvm_max_vl[ARM64_VEC_SME] = VL_ARCH_MAX;
+
+ /*
+ * Don't even try to make use of vector lengths that
+ * aren't available on all CPUs, for now:
+ */
+ if (kvm_max_vl[ARM64_VEC_SME] < sme_max_vl())
+ pr_warn("KVM: SME vector length for guests limited to %u bytes\n",
+ kvm_max_vl[ARM64_VEC_SME]);
+ }
+
+ return 0;
+}
+
static void kvm_vcpu_enable_sve(struct kvm_vcpu *vcpu)
{
vcpu->arch.max_vl[ARM64_VEC_SVE] = kvm_max_vl[ARM64_VEC_SVE];
@@ -88,42 +113,90 @@ static void kvm_vcpu_enable_sve(struct kvm_vcpu *vcpu)
set_bit(KVM_ARCH_FLAG_GUEST_HAS_SVE, &vcpu->kvm->arch.flags);
}
+static void kvm_vcpu_enable_sme(struct kvm_vcpu *vcpu)
+{
+ vcpu->arch.max_vl[ARM64_VEC_SME] = kvm_max_vl[ARM64_VEC_SME];
+
+ /*
+ * Userspace can still customize the vector lengths by writing
+ * KVM_REG_ARM64_SME_VLS. Allocation is deferred until
+ * kvm_arm_vcpu_finalize(), which freezes the configuration.
+ */
+ set_bit(KVM_ARCH_FLAG_GUEST_HAS_SME, &vcpu->kvm->arch.flags);
+}
+
/*
- * Finalize vcpu's maximum SVE vector length, allocating
- * vcpu->arch.sve_state as necessary.
+ * Finalize vcpu's maximum vector lengths, allocating
+ * vcpu->arch.sve_state and vcpu->arch.sme_state as necessary.
*/
static int kvm_vcpu_finalize_vec(struct kvm_vcpu *vcpu)
{
- void *buf;
+ void *sve_state, *sme_state;
unsigned int vl;
- size_t reg_sz;
int ret;
- vl = vcpu->arch.max_vl[ARM64_VEC_SVE];
-
/*
* Responsibility for these properties is shared between
* kvm_arm_init_sve(), kvm_vcpu_enable_sve() and
* set_sve_vls(). Double-check here just to be sure:
*/
- if (WARN_ON(!sve_vl_valid(vl) || vl > sve_max_virtualisable_vl() ||
- vl > VL_ARCH_MAX))
- return -EIO;
+ if (vcpu_has_sve(vcpu)) {
+ vl = vcpu->arch.max_vl[ARM64_VEC_SVE];
+ if (WARN_ON(!sve_vl_valid(vl) ||
+ vl > sve_max_virtualisable_vl() ||
+ vl > VL_ARCH_MAX))
+ return -EIO;
+ } else {
+ vcpu->arch.max_vl[ARM64_VEC_SVE] = 0;
+ }
- reg_sz = vcpu_sve_state_size(vcpu);
- buf = kzalloc(reg_sz, GFP_KERNEL_ACCOUNT);
- if (!buf)
+ /* Similarly for SME */
+ if (vcpu_has_sme(vcpu)) {
+ vl = vcpu->arch.max_vl[ARM64_VEC_SME];
+ if (WARN_ON(!sve_vl_valid(vl) ||
+ vl > sme_max_virtualisable_vl() ||
+ vl > VL_ARCH_MAX))
+ return -EIO;
+ } else {
+ vcpu->arch.max_vl[ARM64_VEC_SME] = 0;
+ }
+
+ sve_state = kzalloc(vcpu_sve_state_size(vcpu), GFP_KERNEL_ACCOUNT);
+ if (!sve_state)
return -ENOMEM;
- ret = kvm_share_hyp(buf, buf + reg_sz);
- if (ret) {
- kfree(buf);
- return ret;
+ ret = kvm_share_hyp(sve_state, sve_state + vcpu_sve_state_size(vcpu));
+ if (ret)
+ goto err_sve_alloc;
+
+ if (vcpu_has_sme(vcpu)) {
+ sme_state = kzalloc(vcpu_sme_state_size(vcpu),
+ GFP_KERNEL_ACCOUNT);
+ if (!sme_state) {
+ ret = -ENOMEM;
+ goto err_sve_map;
+ }
+
+ ret = kvm_share_hyp(sme_state,
+ sme_state + vcpu_sme_state_size(vcpu));
+ if (ret)
+ goto err_sme_alloc;
+ } else {
+ sme_state = NULL;
}
-
- vcpu->arch.sve_state = buf;
+
+ vcpu->arch.sve_state = sve_state;
+ vcpu->arch.sme_state = sme_state;
vcpu_set_flag(vcpu, VCPU_VEC_FINALIZED);
return 0;
+
+err_sme_alloc:
+ kfree(sme_state);
+err_sve_map:
+ kvm_unshare_hyp(sve_state, sve_state + vcpu_sve_state_size(vcpu));
+err_sve_alloc:
+ kfree(sve_state);
+ return ret;
}
int kvm_arm_vcpu_finalize(struct kvm_vcpu *vcpu, int feature)
@@ -153,20 +226,26 @@ bool kvm_arm_vcpu_is_finalized(struct kvm_vcpu *vcpu)
void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu)
{
void *sve_state = vcpu->arch.sve_state;
+ void *sme_state = vcpu->arch.sme_state;
kvm_unshare_hyp(vcpu, vcpu + 1);
if (sve_state)
kvm_unshare_hyp(sve_state, sve_state + vcpu_sve_state_size(vcpu));
kfree(sve_state);
free_page((unsigned long)vcpu->arch.ctxt.vncr_array);
+ if (sme_state)
+ kvm_unshare_hyp(sme_state, sme_state + vcpu_sme_state_size(vcpu));
+ kfree(sme_state);
kfree(vcpu->arch.vncr_tlb);
kfree(vcpu->arch.ccsidr);
}
static void kvm_vcpu_reset_vec(struct kvm_vcpu *vcpu)
{
- if (vcpu_has_sve(vcpu))
+ if (vcpu_has_sve(vcpu) || vcpu_has_sme(vcpu))
memset(vcpu->arch.sve_state, 0, vcpu_sve_state_size(vcpu));
+ if (vcpu_has_sme(vcpu))
+ memset(vcpu->arch.sme_state, 0, vcpu_sme_state_size(vcpu));
}
/**
@@ -206,6 +285,8 @@ void kvm_reset_vcpu(struct kvm_vcpu *vcpu)
if (!kvm_arm_vcpu_vec_finalized(vcpu)) {
if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE))
kvm_vcpu_enable_sve(vcpu);
+ if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_SME))
+ kvm_vcpu_enable_sme(vcpu);
} else {
kvm_vcpu_reset_vec(vcpu);
}
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 419011097fa8..9291538489ac 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -997,6 +997,7 @@ struct kvm_enable_cap {
#define KVM_CAP_S390_KEYOP 247
#define KVM_CAP_S390_VSIE_ESAMODE 248
#define KVM_CAP_S390_HPAGE_2G 249
+#define KVM_CAP_ARM_SME 250
struct kvm_irq_routing_irqchip {
__u32 irqchip;
--
2.47.3
^ permalink raw reply related
* [PATCH v13 27/32] KVM: arm64: Expose SME to nested guests
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
With support for context switching SME state in place allow access to SME
in nested guests.
The SME floating point state is handled along with all the other floating
point state, SME specific floating point exceptions are directed into the
same handlers as other floating point exceptions with NV specific handling
for the vector lengths already in place.
TPIDR2_EL0 is context switched along with the other TPIDRs as part of the
main guest register context switch.
SME priority support is currently masked from all guests including nested
ones.
Reviewed-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/asm/kvm_nested.h | 2 ++
arch/arm64/kvm/nested.c | 3 +--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h
index cdf3e8422ea1..9df3cea39b09 100644
--- a/arch/arm64/include/asm/kvm_nested.h
+++ b/arch/arm64/include/asm/kvm_nested.h
@@ -42,6 +42,8 @@ static inline u64 translate_cptr_el2_to_cpacr_el1(u64 cptr_el2)
cpacr_el1 |= CPACR_EL1_FPEN;
if (!(cptr_el2 & CPTR_EL2_TZ))
cpacr_el1 |= CPACR_EL1_ZEN;
+ if (!(cptr_el2 & CPTR_EL2_TSM))
+ cpacr_el1 |= CPACR_EL1_SMEN;
cpacr_el1 |= cptr_el2 & (CPTR_EL2_TCPAC | CPTR_EL2_TAM);
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index 7de9b7f8f90a..cc80d4c8ec20 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1630,14 +1630,13 @@ u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)
break;
case SYS_ID_AA64PFR1_EL1:
- /* Only support BTI, SSBS, CSV2_frac */
+ /* Only support BTI, SME, SSBS, CSV2_frac */
val &= ~(ID_AA64PFR1_EL1_PFAR |
ID_AA64PFR1_EL1_MTEX |
ID_AA64PFR1_EL1_THE |
ID_AA64PFR1_EL1_GCS |
ID_AA64PFR1_EL1_MTE_frac |
ID_AA64PFR1_EL1_NMI |
- ID_AA64PFR1_EL1_SME |
ID_AA64PFR1_EL1_RES0 |
ID_AA64PFR1_EL1_MPAM_frac |
ID_AA64PFR1_EL1_MTE);
--
2.47.3
^ permalink raw reply related
* [PATCH v13 26/32] KVM: arm64: Handle SME exceptions
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
The access control for SME follows the same structure as for the base FP
and SVE extensions, with control being via CPACR_ELx.SMEN and CPTR_EL2.TSM
mirroring the equivalent FPSIMD and SVE controls in those registers. Add
handling for these controls and exceptions mirroring the existing handling
for FPSIMD and SVE, plus handling for emulation of SMCR_EL2.EZT0.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/kvm/handle_exit.c | 19 +++++++++++++++++++
arch/arm64/kvm/hyp/include/hyp/switch.h | 11 ++++++-----
arch/arm64/kvm/hyp/nvhe/switch.c | 2 ++
arch/arm64/kvm/hyp/vhe/switch.c | 1 +
4 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kvm/handle_exit.c b/arch/arm64/kvm/handle_exit.c
index f674cd25d894..24d54e977a31 100644
--- a/arch/arm64/kvm/handle_exit.c
+++ b/arch/arm64/kvm/handle_exit.c
@@ -233,6 +233,24 @@ static int handle_sve(struct kvm_vcpu *vcpu)
return 1;
}
+static int handle_sme(struct kvm_vcpu *vcpu)
+{
+ u64 esr = kvm_vcpu_get_esr(vcpu);
+
+ if (guest_hyp_sme_traps_enabled(vcpu))
+ return kvm_inject_nested_sync(vcpu, esr);
+
+ /* Reinject SME EZT0 traps that were enabled by the guest. */
+ if (vcpu_has_sme2(vcpu) &&
+ ESR_ELx_EC(esr) == ESR_ELx_EC_SME &&
+ ESR_ELx_SME_ISS_SMTC(esr) == ESR_ELx_SME_ISS_SMTC_ZT_DISABLED &&
+ !(__vcpu_sys_reg(vcpu, SMCR_EL2) & SMCR_ELx_EZT0))
+ return kvm_inject_nested_sync(vcpu, esr);
+
+ kvm_inject_undefined(vcpu);
+ return 1;
+}
+
/*
* Two possibilities to handle a trapping ptrauth instruction:
*
@@ -386,6 +404,7 @@ static exit_handle_fn arm_exit_handlers[] = {
[ESR_ELx_EC_SVC64] = handle_svc,
[ESR_ELx_EC_SYS64] = kvm_handle_sys_reg,
[ESR_ELx_EC_SVE] = handle_sve,
+ [ESR_ELx_EC_SME] = handle_sme,
[ESR_ELx_EC_ERET] = kvm_handle_eret,
[ESR_ELx_EC_IABT_LOW] = kvm_handle_guest_abort,
[ESR_ELx_EC_DABT_LOW] = kvm_handle_guest_abort,
diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h
index 2eabffb640e9..5987b550ad34 100644
--- a/arch/arm64/kvm/hyp/include/hyp/switch.h
+++ b/arch/arm64/kvm/hyp/include/hyp/switch.h
@@ -67,11 +67,8 @@ static inline void __activate_cptr_traps_nvhe(struct kvm_vcpu *vcpu)
{
u64 val = CPTR_NVHE_EL2_RES1 | CPTR_EL2_TAM | CPTR_EL2_TTA;
- /*
- * Always trap SME since it's not supported in KVM.
- * TSM is RES1 if SME isn't implemented.
- */
- val |= CPTR_EL2_TSM;
+ if (!vcpu_has_sme(vcpu) || !guest_owns_fp_regs())
+ val |= CPTR_EL2_TSM;
if (!vcpu_has_sve(vcpu) || !guest_owns_fp_regs())
val |= CPTR_EL2_TZ;
@@ -99,6 +96,8 @@ static inline void __activate_cptr_traps_vhe(struct kvm_vcpu *vcpu)
val |= CPACR_EL1_FPEN;
if (vcpu_has_sve(vcpu))
val |= CPACR_EL1_ZEN;
+ if (vcpu_has_sme(vcpu))
+ val |= CPACR_EL1_SMEN;
}
if (!vcpu_has_nv(vcpu))
@@ -140,6 +139,8 @@ static inline void __activate_cptr_traps_vhe(struct kvm_vcpu *vcpu)
val &= ~CPACR_EL1_FPEN;
if (!(SYS_FIELD_GET(CPACR_EL1, ZEN, cptr) & BIT(0)))
val &= ~CPACR_EL1_ZEN;
+ if (!(SYS_FIELD_GET(CPACR_EL1, SMEN, cptr) & BIT(0)))
+ val &= ~CPACR_EL1_SMEN;
if (kvm_has_feat(vcpu->kvm, ID_AA64MMFR3_EL1, S1POE, IMP))
val |= cptr & CPACR_EL1_E0POE;
diff --git a/arch/arm64/kvm/hyp/nvhe/switch.c b/arch/arm64/kvm/hyp/nvhe/switch.c
index 7318e3e6a5f3..2d5029dbfb00 100644
--- a/arch/arm64/kvm/hyp/nvhe/switch.c
+++ b/arch/arm64/kvm/hyp/nvhe/switch.c
@@ -196,6 +196,7 @@ static const exit_handler_fn hyp_exit_handlers[] = {
[ESR_ELx_EC_CP15_32] = kvm_hyp_handle_cp15_32,
[ESR_ELx_EC_SYS64] = kvm_hyp_handle_sysreg,
[ESR_ELx_EC_SVE] = kvm_hyp_handle_fpsimd,
+ [ESR_ELx_EC_SME] = kvm_hyp_handle_fpsimd,
[ESR_ELx_EC_FP_ASIMD] = kvm_hyp_handle_fpsimd,
[ESR_ELx_EC_IABT_LOW] = kvm_hyp_handle_iabt_low,
[ESR_ELx_EC_DABT_LOW] = kvm_hyp_handle_dabt_low,
@@ -208,6 +209,7 @@ static const exit_handler_fn pvm_exit_handlers[] = {
[ESR_ELx_EC_HVC64] = kvm_handle_pvm_hvc64,
[ESR_ELx_EC_SYS64] = kvm_handle_pvm_sys64,
[ESR_ELx_EC_SVE] = kvm_handle_pvm_restricted,
+ [ESR_ELx_EC_SME] = kvm_handle_pvm_restricted,
[ESR_ELx_EC_FP_ASIMD] = kvm_hyp_handle_fpsimd,
[ESR_ELx_EC_IABT_LOW] = kvm_hyp_handle_iabt_low,
[ESR_ELx_EC_DABT_LOW] = kvm_hyp_handle_dabt_low,
diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c
index 625fe81a20b1..2931a1a1fe90 100644
--- a/arch/arm64/kvm/hyp/vhe/switch.c
+++ b/arch/arm64/kvm/hyp/vhe/switch.c
@@ -495,6 +495,7 @@ static const exit_handler_fn hyp_exit_handlers[] = {
[0 ... ESR_ELx_EC_MAX] = NULL,
[ESR_ELx_EC_CP15_32] = kvm_hyp_handle_cp15_32,
[ESR_ELx_EC_SYS64] = kvm_hyp_handle_sysreg_vhe,
+ [ESR_ELx_EC_SME] = kvm_hyp_handle_fpsimd,
[ESR_ELx_EC_SVE] = kvm_hyp_handle_fpsimd,
[ESR_ELx_EC_FP_ASIMD] = kvm_hyp_handle_fpsimd,
[ESR_ELx_EC_IABT_LOW] = kvm_hyp_handle_iabt_low,
--
2.47.3
^ permalink raw reply related
* [PATCH v13 25/32] KVM: arm64: Context switch SME state for guests
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
If the guest has SME state we need to context switch that state, provide
support for that for normal guests.
SME has three sets of registers, ZA, ZT (only present for SME2) and also
streaming SVE which replaces the standard floating point registers when
active. The first two are fairly straightforward, they are accessible only
when PSTATE.ZA is set and we can reuse the assembly from the host to save
and load them from a single contiguous buffer. When PSTATE.ZA is not set
then these registers are inaccessible, when the guest enables PSTATE.ZA
all bits will be set to 0 by that and nothing is required on restore.
Streaming mode is slightly more complicated, when enabled via PSTATE.SM it
provides a version of the SVE registers using the SME vector length and may
optionally omit the FFR register. SME may also be present without SVE. The
register state is stored in sve_state as for non-streaming SVE mode, we
make an initial selection of registers to update based on the guest SVE
support and then override this when loading SVCR if streaming mode is
enabled.
A further complication is that when the hardware is in streaming mode
guest operations that are invalid in in streaming mode will generate SME
exceptions. There are also subfeature exceptions for SME2 controlled via
SMCR which generate distinct exception codes. In many situations these
exceptions are routed directly to the lower ELs with no opportunity for
the hypervisor to intercept. So that guests do not see unexpected
exception types due to the actual hardware configuration not being what
the guest configured we update the SMCRs and SVCR even if the guest does
not own the registers.
For nested guests we also need to forward exceptions generated as the
result of SMCR_EL2.EZT0 being disabled in the guest.
Since in order to avoid duplication with SME we now restore the register
state outside of the SVE specific restore function we need to move the
restore of the effective VL for nested guests to a separate restore
function run after loading the floating point register state, along with
the similar handling required for SME.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/asm/kvm_host.h | 25 ++--
arch/arm64/kvm/fpsimd.c | 26 ++++-
arch/arm64/kvm/hyp/include/hyp/switch.h | 197 ++++++++++++++++++++++++++++++--
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 131 ++++++++++++++++++---
4 files changed, 337 insertions(+), 42 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index cafc43776ac3..7ebe7b6cc96e 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1126,6 +1126,9 @@ struct kvm_vcpu_arch {
#define vcpu_sve_zcr_elx(vcpu) \
(unlikely(is_hyp_ctxt(vcpu)) ? ZCR_EL2 : ZCR_EL1)
+#define vcpu_sme_smcr_elx(vcpu) \
+ (unlikely(is_hyp_ctxt(vcpu)) ? SMCR_EL2 : SMCR_EL1)
+
#define sve_state_size_from_vl(sve_max_vl) ({ \
size_t __size_ret; \
unsigned int __vq; \
@@ -1140,10 +1143,20 @@ struct kvm_vcpu_arch {
__size_ret; \
})
-#define vcpu_sve_state_size(vcpu) sve_state_size_from_vl(vcpu_sve_max_vl(vcpu))
+#define vcpu_sve_state_size(vcpu) sve_state_size_from_vl(vcpu_max_vl(vcpu))
#define vcpu_sme_state(vcpu) (kern_hyp_va((vcpu)->arch.sme_state))
+/*
+ * Always provide space for ZT0 to avoid ordering requirements with ID
+ * register writes and vector finalization.
+ */
+#define vcpu_sme_state_size(vcpu) ({ \
+ unsigned long __vl; \
+ __vl = (vcpu)->arch.max_vl[ARM64_VEC_SME]; \
+ sme_state_size_from_vl(__vl, system_supports_sme2()); \
+})
+
#define sme_state_size_from_vl(vl, sme2) ({ \
size_t __size_ret; \
unsigned int __vq; \
@@ -1160,16 +1173,6 @@ struct kvm_vcpu_arch {
__size_ret; \
})
-/*
- * Always provide space for ZT0 to avoid ordering requirements with ID
- * register writes and vector finalization.
- */
-#define vcpu_sme_state_size(vcpu) ({ \
- unsigned long __vl; \
- __vl = (vcpu)->arch.max_vl[ARM64_VEC_SME]; \
- sme_state_size_from_vl(__vl, system_supports_sme2()); \
-})
-
#define KVM_GUESTDBG_VALID_MASK (KVM_GUESTDBG_ENABLE | \
KVM_GUESTDBG_USE_SW_BP | \
KVM_GUESTDBG_USE_HW | \
diff --git a/arch/arm64/kvm/fpsimd.c b/arch/arm64/kvm/fpsimd.c
index 567dd43970c5..bb0bf8d81522 100644
--- a/arch/arm64/kvm/fpsimd.c
+++ b/arch/arm64/kvm/fpsimd.c
@@ -83,19 +83,24 @@ void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu)
WARN_ON_ONCE(!irqs_disabled());
if (guest_owns_fp_regs()) {
- /*
- * Currently we do not support SME guests so SVCR is
- * always 0 and we just need a variable to point to.
- */
fp_state.st = &vcpu->arch.ctxt.fp_regs;
fp_state.sve_state = vcpu->arch.sve_state;
fp_state.zcr = vcpu_sve_max_vq(vcpu) - 1;
- fp_state.smcr = 0;
- fp_state.sme_state = NULL;
+ fp_state.smcr = vcpu_sme_max_vq(vcpu) - 1;
+ fp_state.sme_state = vcpu->arch.sme_state;
fp_state.svcr = __ctxt_sys_reg(&vcpu->arch.ctxt, SVCR);
fp_state.fpmr = __ctxt_sys_reg(&vcpu->arch.ctxt, FPMR);
fp_state.fp_type = &vcpu->arch.fp_type;
+ if (kvm_has_fa64(vcpu->kvm))
+ fp_state.smcr |= SMCR_ELx_FA64;
+ if (kvm_has_sme2(vcpu->kvm))
+ fp_state.smcr |= SMCR_ELx_EZT0;
+
+ /*
+ * For SME only guests fpsimd_save() will override the
+ * state selection if we are in streaming mode.
+ */
if (vcpu_has_sve(vcpu))
fp_state.to_save = FP_STATE_SVE;
else
@@ -104,6 +109,15 @@ void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu)
fpsimd_bind_state_to_cpu(&fp_state);
clear_thread_flag(TIF_FOREIGN_FPSTATE);
+ } else {
+ /*
+ * We might have enabled SME to configure traps but
+ * insist the host doesn't run the hypervisor with SME
+ * enabled, ensure it's disabled again.
+ */
+ if (system_supports_sme()) {
+ sme_smstop();
+ }
}
}
diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h
index ce586110c226..2eabffb640e9 100644
--- a/arch/arm64/kvm/hyp/include/hyp/switch.h
+++ b/arch/arm64/kvm/hyp/include/hyp/switch.h
@@ -462,6 +462,44 @@ static inline bool kvm_hyp_handle_mops(struct kvm_vcpu *vcpu, u64 *exit_code)
return true;
}
+/* The SMCR_EL2 for non-nested guests */
+static inline u64 __hyp_smcr_el2(struct kvm_vcpu *vcpu)
+{
+ u64 smcr_el2;
+
+ /*
+ * The vCPU's saved SVE state layout always matches the max VL of the
+ * vCPU.
+ */
+ smcr_el2 = vcpu_sme_max_vq(vcpu) - 1;
+ if (vcpu_has_fa64(vcpu))
+ smcr_el2 |= SMCR_ELx_FA64;
+ if (vcpu_has_sme2(vcpu))
+ smcr_el2 |= SMCR_ELx_EZT0;
+
+ return smcr_el2;
+}
+
+static inline void __hyp_sme_restore_guest(struct kvm_vcpu *vcpu,
+ bool *restore_sve,
+ bool *restore_ffr)
+{
+ /* EZT0 and FA64 are not self synchronizing */
+ sysreg_cond_update_s(SYS_SMCR_EL2, __hyp_smcr_el2(vcpu));
+ isb();
+
+ if (vcpu_in_streaming_mode(vcpu)) {
+ *restore_sve = true;
+ *restore_ffr = vcpu_has_fa64(vcpu);
+ }
+
+ if (vcpu_za_enabled(vcpu))
+ sme_load_state(kern_hyp_va(vcpu->arch.sme_state),
+ vcpu_has_sme2(vcpu));
+
+ write_sysreg_el1(__vcpu_sys_reg(vcpu, vcpu_sme_smcr_elx(vcpu)), SYS_SMCR);
+}
+
static inline void __hyp_sve_restore_guest(struct kvm_vcpu *vcpu)
{
u64 zcr_el2 = vcpu_sve_max_vq(vcpu) - 1;
@@ -471,20 +509,45 @@ static inline void __hyp_sve_restore_guest(struct kvm_vcpu *vcpu)
* vCPU. Start off with the max VL so we can load the SVE state.
*/
sve_cond_update_zcr_vq(zcr_el2, SYS_ZCR_EL2);
- sve_load_state(kern_hyp_va(vcpu->arch.sve_state), true);
- fpsimd_load_common(&vcpu->arch.ctxt.fp_regs);
+ write_sysreg_el1(__vcpu_sys_reg(vcpu, vcpu_sve_zcr_elx(vcpu)), SYS_ZCR);
+}
+
+static inline u64 __hyp_smcr_el2_nested(struct kvm_vcpu *vcpu)
+{
+ u64 max_len = vcpu_sme_max_vq(vcpu) - 1;
+ u64 smcr_el2 = SYS_FIELD_GET(SMCR_ELx, LEN,
+ __vcpu_sys_reg(vcpu, SMCR_EL2));
+
+ smcr_el2 = min(max_len, smcr_el2);
+
+ if (vcpu_has_fa64(vcpu))
+ smcr_el2 |= __vcpu_sys_reg(vcpu, SMCR_EL2) & SMCR_ELx_FA64;
+ if (vcpu_has_sme2(vcpu))
+ smcr_el2 |= __vcpu_sys_reg(vcpu, SMCR_EL2) & SMCR_ELx_EZT0;
+
+ return smcr_el2;
+}
+
+static inline void __hyp_nv_restore_guest_vls(struct kvm_vcpu *vcpu)
+{
+ if (!is_nested_ctxt(vcpu))
+ return;
/*
* The effective VL for a VM could differ from the max VL when running a
* nested guest, as the guest hypervisor could select a smaller VL. Slap
* that into hardware before wrapping up.
*/
- if (is_nested_ctxt(vcpu)) {
+
+ if (vcpu_has_sve(vcpu)) {
+ u64 zcr_el2 = vcpu_sve_max_vq(vcpu) - 1;
+
zcr_el2 = min(zcr_el2, __vcpu_sys_reg(vcpu, ZCR_EL2));
sve_cond_update_zcr_vq(zcr_el2, SYS_ZCR_EL2);
}
- write_sysreg_el1(__vcpu_sys_reg(vcpu, vcpu_sve_zcr_elx(vcpu)), SYS_ZCR);
+ if (vcpu_has_sme(vcpu))
+ sysreg_cond_update_s(SYS_SMCR_EL2, __hyp_smcr_el2_nested(vcpu));
}
static inline void __hyp_sve_save_host(void)
@@ -498,10 +561,38 @@ static inline void __hyp_sve_save_host(void)
fpsimd_save_common(&hctxt->fp_regs);
}
+static inline void kvm_sme_configure_traps(struct kvm_vcpu *vcpu)
+{
+ u64 smcr_el1, smcr_el2, svcr;
+
+ if (!vcpu_has_sme(vcpu))
+ return;
+
+ if (is_nested_ctxt(vcpu))
+ smcr_el2 = __hyp_smcr_el2_nested(vcpu);
+ else
+ smcr_el2 = __hyp_smcr_el2(vcpu);
+ sysreg_cond_update_s(SYS_SMCR_EL2, smcr_el2);
+
+ smcr_el1 = __vcpu_sys_reg(vcpu, vcpu_sme_smcr_elx(vcpu));
+ write_sysreg_el1(smcr_el1, SYS_SMCR);
+
+ svcr = __vcpu_sys_reg(vcpu, SVCR);
+ write_sysreg_s(svcr, SYS_SVCR);
+}
+
static inline void fpsimd_lazy_switch_to_guest(struct kvm_vcpu *vcpu)
{
u64 zcr_el1, zcr_el2;
+ /*
+ * We always load the SME control registers that affect traps
+ * since if they are not configured as expected by the guest
+ * then it may have exceptions that it does not expect
+ * directly delivered.
+ */
+ kvm_sme_configure_traps(vcpu);
+
if (!guest_owns_fp_regs())
return;
@@ -555,8 +646,59 @@ static inline void sve_lazy_switch_to_host(struct kvm_vcpu *vcpu)
}
}
+static inline void sme_lazy_switch_to_host(struct kvm_vcpu *vcpu)
+{
+ u64 smcr_el1, smcr_el2;
+
+ if (!vcpu_has_sme(vcpu))
+ return;
+
+ /*
+ * __deactivate_cptr_traps() disabled traps, but there hasn't
+ * necessarily been a context synchronization event yet.
+ */
+ isb();
+
+ smcr_el1 = read_sysreg_el1(SYS_SMCR);
+ __vcpu_assign_sys_reg(vcpu, vcpu_sme_smcr_elx(vcpu), smcr_el1);
+
+ smcr_el2 = 0;
+ if (system_supports_fa64())
+ smcr_el2 |= SMCR_ELx_FA64;
+ if (system_supports_sme2())
+ smcr_el2 |= SMCR_ELx_EZT0;
+
+ /*
+ * The guest's state is always saved using the guest's max VL.
+ * Ensure that the host has the guest's max VL active such
+ * that the host can save the guest's state lazily, but don't
+ * artificially restrict the host to the guest's max VL.
+ */
+ if (has_vhe()) {
+ smcr_el2 |= vcpu_sme_max_vq(vcpu) - 1;
+ write_sysreg_el2(smcr_el2, SYS_SMCR);
+ /* Guest might not have had EZT0 or FA64 */
+ isb();
+ } else {
+ smcr_el1 = smcr_el2;
+ smcr_el2 |= sve_vq_from_vl(kvm_host_max_vl[ARM64_VEC_SME]) - 1;
+ write_sysreg_el2(smcr_el2, SYS_SMCR);
+
+ smcr_el1 |= vcpu_sme_max_vq(vcpu) - 1;
+ write_sysreg_el1(smcr_el1, SYS_SMCR);
+ }
+
+ __vcpu_assign_sys_reg(vcpu, SVCR, read_sysreg_s(SYS_SVCR));
+}
+
static inline void fpsimd_lazy_switch_to_host(struct kvm_vcpu *vcpu)
{
+ /*
+ * We always load the control registers for the guest so we
+ * always restore state for the host.
+ */
+ sme_lazy_switch_to_host(vcpu);
+
if (!guest_owns_fp_regs())
return;
@@ -567,6 +709,16 @@ static void kvm_hyp_save_fpsimd_host(struct kvm_vcpu *vcpu)
{
struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
+ /*
+ * The hypervisor refuses to run if streaming mode or ZA is
+ * enabled, we only need to save SMCR_EL1 for SME. For pKVM
+ * we will restore this, reset SMCR_EL2 to a fixed value and
+ * disable streaming mode and ZA to avoid any state being
+ * leaked.
+ */
+ if (system_supports_sme())
+ ctxt_sys_reg(hctxt, SMCR_EL1) = read_sysreg_el1(SYS_SMCR);
+
/*
* Non-protected kvm relies on the host restoring its sve state.
* Protected kvm restores the host's sve state as not to reveal that
@@ -591,14 +743,17 @@ static void kvm_hyp_save_fpsimd_host(struct kvm_vcpu *vcpu)
*/
static inline bool kvm_hyp_handle_fpsimd(struct kvm_vcpu *vcpu, u64 *exit_code)
{
- bool sve_guest;
- u8 esr_ec;
+ bool restore_sve, restore_ffr;
+ bool sve_guest, sme_guest;
+ u8 esr_ec, esr_iss_smtc;
if (!system_supports_fpsimd())
return false;
sve_guest = vcpu_has_sve(vcpu);
+ sme_guest = vcpu_has_sme(vcpu);
esr_ec = kvm_vcpu_trap_get_class(vcpu);
+ esr_iss_smtc = ESR_ELx_SME_ISS_SMTC((kvm_vcpu_get_esr(vcpu)));
/* Only handle traps the vCPU can support here: */
switch (esr_ec) {
@@ -613,6 +768,20 @@ static inline bool kvm_hyp_handle_fpsimd(struct kvm_vcpu *vcpu, u64 *exit_code)
if (guest_hyp_sve_traps_enabled(vcpu))
return false;
break;
+ case ESR_ELx_EC_SME:
+ if (!sme_guest)
+ return false;
+ if (guest_hyp_sme_traps_enabled(vcpu))
+ return false;
+ if (esr_iss_smtc == ESR_ELx_SME_ISS_SMTC_ZT_DISABLED) {
+ if (!kvm_has_sme2(kern_hyp_va(vcpu->kvm)))
+ return false;
+
+ if (vcpu_has_nv(vcpu) &&
+ !(__vcpu_sys_reg(vcpu, SMCR_EL2) & SMCR_ELx_EZT0))
+ return false;
+ }
+ break;
default:
return false;
}
@@ -628,10 +797,22 @@ static inline bool kvm_hyp_handle_fpsimd(struct kvm_vcpu *vcpu, u64 *exit_code)
kvm_hyp_save_fpsimd_host(vcpu);
/* Restore the guest state */
+
+ /* These may be overridden for a SME guest */
+ restore_sve = sve_guest;
+ restore_ffr = sve_guest;
+
if (sve_guest)
__hyp_sve_restore_guest(vcpu);
- else
+ if (sme_guest)
+ __hyp_sme_restore_guest(vcpu, &restore_sve, &restore_ffr);
+
+ if (restore_sve) {
+ sve_load_state(kern_hyp_va(vcpu->arch.sve_state), restore_ffr);
+ fpsimd_load_common(&vcpu->arch.ctxt.fp_regs);
+ } else {
fpsimd_load_state(&vcpu->arch.ctxt.fp_regs);
+ }
if (kvm_has_fpmr(kern_hyp_va(vcpu->kvm)))
write_sysreg_s(__vcpu_sys_reg(vcpu, FPMR), SYS_FPMR);
@@ -640,6 +821,8 @@ static inline bool kvm_hyp_handle_fpsimd(struct kvm_vcpu *vcpu, u64 *exit_code)
if (!(read_sysreg(hcr_el2) & HCR_RW))
write_sysreg(__vcpu_sys_reg(vcpu, FPEXC32_EL2), fpexc32_el2);
+ __hyp_nv_restore_guest_vls(vcpu);
+
*host_data_ptr(fp_owner) = FP_STATE_GUEST_OWNED;
/*
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 14e24e257dcc..33fd3bcb112a 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -31,16 +31,28 @@ void __kvm_hyp_host_forward_smc(struct kvm_cpu_context *host_ctxt);
static void __hyp_sve_save_guest(struct kvm_vcpu *vcpu)
{
- __vcpu_assign_sys_reg(vcpu, ZCR_EL1, read_sysreg_el1(SYS_ZCR));
- /*
- * On saving/restoring guest sve state, always use the maximum VL for
- * the guest. The layout of the data when saving the sve state depends
- * on the VL, so use a consistent (i.e., the maximum) guest VL.
- */
- sve_cond_update_zcr_vq(vcpu_sve_max_vq(vcpu) - 1, SYS_ZCR_EL2);
- sve_save_state(kern_hyp_va(vcpu->arch.sve_state), true);
+ bool save_ffr = !vcpu_in_streaming_mode(vcpu) || vcpu_has_fa64(vcpu);
+
+ if (vcpu_has_sve(vcpu)) {
+ __vcpu_assign_sys_reg(vcpu, ZCR_EL1, read_sysreg_el1(SYS_ZCR));
+
+ /*
+ * On saving/restoring guest sve state, always use the
+ * maximum VL for the guest. The layout of the data
+ * when saving the sve state depends on the VL, so use
+ * a consistent (i.e., the maximum) guest VL.
+ */
+ sve_cond_update_zcr_vq(vcpu_sve_max_vq(vcpu) - 1, SYS_ZCR_EL2);
+ }
+
+ /* Ensure ZCR/SMCR updates for VL are seen */
+ isb();
+ sve_save_state(kern_hyp_va(vcpu->arch.sve_state), save_ffr);
fpsimd_save_common(&vcpu->arch.ctxt.fp_regs);
- write_sysreg_s(sve_vq_from_vl(kvm_host_max_vl[ARM64_VEC_SVE]) - 1, SYS_ZCR_EL2);
+
+ if (system_supports_sve())
+ write_sysreg_s(sve_vq_from_vl(kvm_host_max_vl[ARM64_VEC_SVE]) - 1,
+ SYS_ZCR_EL2);
}
static void __hyp_sve_restore_host(void)
@@ -63,9 +75,76 @@ static void __hyp_sve_restore_host(void)
write_sysreg_el1(ctxt_sys_reg(hctxt, ZCR_EL1), SYS_ZCR);
}
-static void fpsimd_sve_flush(void)
+static void __hyp_sme_save_guest(struct kvm_vcpu *vcpu)
{
- *host_data_ptr(fp_owner) = FP_STATE_HOST_OWNED;
+ unsigned long smcr_el2;
+
+ __vcpu_assign_sys_reg(vcpu, SMCR_EL1, read_sysreg_el1(SYS_SMCR));
+ __vcpu_assign_sys_reg(vcpu, SVCR, read_sysreg_s(SYS_SVCR));
+
+ /*
+ * On saving/restoring guest sve state, always use the maximum VL for
+ * the guest. The layout of the data when saving the sve state depends
+ * on the VL, so use a consistent (i.e., the maximum) guest VL.
+ *
+ * We restore the FA64 and SME2 enables for the host since we
+ * will always restore the host configuration so if host and
+ * guest VLs are the same we might suppress an update.
+ */
+ smcr_el2 = vcpu_sme_max_vq(vcpu) - 1;
+ if (system_supports_fa64())
+ smcr_el2 |= SMCR_ELx_FA64;
+ if (system_supports_sme2())
+ smcr_el2 |= SMCR_ELx_EZT0;
+ sysreg_cond_update_s(SYS_SMCR_EL2, smcr_el2);
+
+ if (vcpu_za_enabled(vcpu)) {
+ isb();
+ sme_save_state(vcpu_sme_state(vcpu), vcpu_has_sme2(vcpu));
+ }
+}
+
+static void __hyp_sme_restore_host(void)
+{
+ struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
+ u64 smcr_el2;
+
+ /*
+ * The hypervisor refuses to run if we are in streaming mode
+ * or have ZA enabled so there is no SME specific state to
+ * restore other than the system registers.
+ *
+ * Note that this constrains the PE to the maximum shared VL
+ * that was discovered, if we wish to use larger VLs this will
+ * need to be revisited.
+ */
+ smcr_el2 = sve_vq_from_vl(kvm_host_max_vl[ARM64_VEC_SME]) - 1;
+ if (system_supports_fa64())
+ smcr_el2 |= SMCR_ELx_FA64;
+ if (system_supports_sme2())
+ smcr_el2 |= SMCR_ELx_EZT0;
+ sysreg_cond_update_s(SYS_SMCR_EL2, smcr_el2);
+
+ write_sysreg_el1(ctxt_sys_reg(hctxt, SMCR_EL1), SYS_SMCR);
+ sme_smstop();
+}
+
+static void fpsimd_sve_flush(struct kvm_vcpu *vcpu)
+{
+ /*
+ * If the guest has SME then we need to restore the trap
+ * controls in SMCR and mode in SVCR in order to ensure that
+ * traps generated directly to EL1 have the correct types,
+ * otherwise we can defer until we load the guest state.
+ */
+ if (vcpu_has_sme(vcpu)) {
+ kvm_hyp_save_fpsimd_host(vcpu);
+ kvm_sme_configure_traps(vcpu);
+
+ *host_data_ptr(fp_owner) = FP_STATE_FREE;
+ } else {
+ *host_data_ptr(fp_owner) = FP_STATE_HOST_OWNED;
+ }
}
static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
@@ -73,8 +152,18 @@ static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
bool has_fpmr;
- if (!guest_owns_fp_regs())
+ if (!guest_owns_fp_regs()) {
+ /*
+ * We always at least partially configure SME for the
+ * guest due to traps.
+ */
+ if (vcpu_has_sme(vcpu)) {
+ /* Ensure __deactivate_cptr_traps() is visible */
+ isb();
+ __hyp_sme_restore_host();
+ }
return;
+ }
/*
* Traps have been disabled by __deactivate_cptr_traps(), but there
@@ -82,7 +171,10 @@ static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
*/
isb();
- if (vcpu_has_sve(vcpu))
+ if (vcpu_has_sme(vcpu))
+ __hyp_sme_save_guest(vcpu);
+
+ if (vcpu_has_sve(vcpu) || vcpu_in_streaming_mode(vcpu))
__hyp_sve_save_guest(vcpu);
else
fpsimd_save_state(&vcpu->arch.ctxt.fp_regs);
@@ -91,6 +183,9 @@ static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
if (has_fpmr)
__vcpu_assign_sys_reg(vcpu, FPMR, read_sysreg_s(SYS_FPMR));
+ if (system_supports_sme())
+ __hyp_sme_restore_host();
+
if (system_supports_sve())
__hyp_sve_restore_host();
else
@@ -128,11 +223,12 @@ static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
{
struct kvm_vcpu *host_vcpu = hyp_vcpu->host_vcpu;
- fpsimd_sve_flush();
flush_debug_state(hyp_vcpu);
hyp_vcpu->vcpu.arch.ctxt = host_vcpu->arch.ctxt;
+ fpsimd_sve_flush(&hyp_vcpu->vcpu);
+
/* __hyp_running_vcpu must be NULL in a guest context. */
hyp_vcpu->vcpu.arch.ctxt.__hyp_running_vcpu = NULL;
@@ -223,10 +319,9 @@ static void handle___kvm_vcpu_run(struct kvm_cpu_context *host_ctxt)
struct pkvm_hyp_vcpu *hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
/*
- * KVM (and pKVM) doesn't support SME guests for now, and
- * ensures that SME features aren't enabled in pstate when
- * loading a vcpu. Therefore, if SME features enabled the host
- * is misbehaving.
+ * KVM (and pKVM) refuses to run if PSTATE.{SM,ZA} are
+ * enabled. Therefore, if SME features enabled the
+ * host is misbehaving.
*/
if (unlikely(system_supports_sme() && read_sysreg_s(SYS_SVCR))) {
ret = -EINVAL;
--
2.47.3
^ permalink raw reply related
* [PATCH v13 24/32] KVM: arm64: Expose SME specific state to userspace
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
SME introduces two new registers, the ZA matrix register and the ZT0 LUT
register. Both of these registers are only accessible when PSTATE.ZA is
set and ZT0 is only present if SME2 is enabled for the guest. Provide
support for configuring these from VMMs.
The ZA matrix is a single SVL*SVL register which is available when
PSTATE.ZA is set. We follow the pattern established by the architecture
itself and expose this to userspace as a series of horizontal SVE vectors
with the streaming mode vector length, using the format already established
for the SVE vectors themselves.
ZT0 is a single register with a refreshingly fixed size 512 bit register
which is like ZA accessible only when PSTATE.ZA is set. Add support for it
to the userspace API.
As is done in the architecture for both ZA and ZT0 the value will be
reset to 0 whenever PSTATE.ZA changes from 0 to 1 and the registers are
inaccessible when PSTATE.ZA is 0.
While there is currently only one ZT register the naming as ZT0 and the
instruction encoding clearly leave room for future extensions adding more
ZT registers. This encoding can readily support such an extension if one is
introduced.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/uapi/asm/kvm.h | 20 ++++
arch/arm64/kvm/guest.c | 186 +++++++++++++++++++++++++++++++++++++-
2 files changed, 204 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 15d53300914b..deccb034fce3 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -357,6 +357,26 @@ struct kvm_arm_counter_offset {
/* SME registers */
#define KVM_REG_ARM64_SME (0x17 << KVM_REG_ARM_COPROC_SHIFT)
+#define KVM_ARM64_SME_VQ_MIN __SVE_VQ_MIN
+#define KVM_ARM64_SME_VQ_MAX 16
+
+/* ZA and ZTn occupy blocks at the following offsets within this range: */
+#define KVM_REG_ARM64_SME_ZA_BASE 0
+#define KVM_REG_ARM64_SME_ZT_BASE 0x600
+
+#define KVM_ARM64_SME_MAX_ZAHREG (__SVE_VQ_BYTES * KVM_ARM64_SME_VQ_MAX)
+
+#define KVM_REG_ARM64_SME_ZAHREG(n, i) \
+ (KVM_REG_ARM64 | KVM_REG_ARM64_SME | KVM_REG_ARM64_SME_ZA_BASE | \
+ KVM_REG_SIZE_U2048 | \
+ (((n) & (KVM_ARM64_SME_MAX_ZAHREG - 1)) << 5) | \
+ ((i) & (KVM_ARM64_SVE_MAX_SLICES - 1)))
+
+#define KVM_REG_ARM64_SME_ZTREG_SIZE (512 / 8)
+#define KVM_REG_ARM64_SME_ZTREG(n) \
+ (KVM_REG_ARM64 | KVM_REG_ARM64_SME | KVM_REG_ARM64_SME_ZT_BASE | \
+ KVM_REG_SIZE_U512 | (n))
+
/* Vector lengths pseudo-register: */
#define KVM_REG_ARM64_SME_VLS (KVM_REG_ARM64 | KVM_REG_ARM64_SME | \
KVM_REG_SIZE_U512 | 0xfffe)
diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
index 110cc7f7527a..1b85f0383628 100644
--- a/arch/arm64/kvm/guest.c
+++ b/arch/arm64/kvm/guest.c
@@ -598,22 +598,133 @@ static int set_sme_vls(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
return set_vec_vls(ARM64_VEC_SME, vcpu, reg);
}
+#define ZAH_REG_SLICE_SHIFT 0
+#define ZAH_REG_SLICE_BITS 5
+#define ZAH_REG_ID_SHIFT (ZAH_REG_SLICE_SHIFT + ZAH_REG_SLICE_BITS)
+#define ZAH_REG_ID_BITS 8
+
+#define ZAH_REG_SLICE_MASK \
+ GENMASK(ZAH_REG_SLICE_SHIFT + ZAH_REG_SLICE_BITS - 1, \
+ ZAH_REG_SLICE_SHIFT)
+#define ZAH_REG_ID_MASK \
+ GENMASK(ZAH_REG_ID_SHIFT + ZAH_REG_ID_BITS - 1, ZAH_REG_ID_SHIFT)
+
+/*
+ * Validate SME register ID and get sanitised bounds for user/kernel SME
+ * register copy
+ */
+static int sme_reg_to_region(struct vec_state_reg_region *region,
+ struct kvm_vcpu *vcpu,
+ const struct kvm_one_reg *reg)
+{
+ /* reg ID ranges for ZA.H[n] registers */
+ unsigned int vq = vcpu_sme_max_vq(vcpu);
+ const u64 za_h_max = vq * __SVE_VQ_BYTES;
+ const u64 zah_id_min = KVM_REG_ARM64_SME_ZAHREG(0, 0);
+ const u64 zah_id_max = KVM_REG_ARM64_SME_ZAHREG(za_h_max - 1,
+ SVE_NUM_SLICES - 1);
+ unsigned int reg_num;
+
+ unsigned int reqoffset, reqlen; /* User-requested offset and length */
+ unsigned int maxlen; /* Maximum permitted length */
+
+ size_t sme_state_size;
+
+ reg_num = (reg->id & ZAH_REG_ID_MASK) >> ZAH_REG_ID_SHIFT;
+
+ if (reg->id >= zah_id_min && reg->id <= zah_id_max) {
+ if (!vcpu_has_sme(vcpu) || (reg->id & ZAH_REG_SLICE_MASK) > 0)
+ return -ENOENT;
+
+ if (!vcpu_za_enabled(vcpu))
+ return -EBUSY;
+
+ /* ZA is exposed as SVE vectors ZA.H[n] */
+ reqoffset = ZA_SIG_ZAV_OFFSET(vq, reg_num) -
+ ZA_SIG_REGS_OFFSET;
+ reqlen = KVM_SVE_ZREG_SIZE;
+ maxlen = SVE_SIG_ZREG_SIZE(vq);
+ } else if (reg->id == KVM_REG_ARM64_SME_ZTREG(0)) {
+ if (!kvm_has_feat(vcpu->kvm, ID_AA64PFR1_EL1, SME, SME2))
+ return -ENOENT;
+
+ if (!vcpu_za_enabled(vcpu))
+ return -EBUSY;
+
+ /* ZT0 is stored after ZA */
+ reqoffset = ZA_SIG_REGS_SIZE(vq);
+ reqlen = KVM_REG_ARM64_SME_ZTREG_SIZE;
+ maxlen = KVM_REG_ARM64_SME_ZTREG_SIZE;
+ } else {
+ return -EINVAL;
+ }
+
+ sme_state_size = vcpu_sme_state_size(vcpu);
+ if (WARN_ON(!sme_state_size))
+ return -EINVAL;
+
+ region->koffset = array_index_nospec(reqoffset, sme_state_size);
+ region->klen = min(maxlen, reqlen);
+ region->upad = reqlen - region->klen;
+
+ return 0;
+}
+
+/*
+ * ZA is exposed as an array of horizontal vectors with the same
+ * format as SVE, mirroring the architecture's LDR ZA[Wv, offs], [Xn]
+ * instruction.
+ */
+
static int get_sme_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
{
+ int ret;
+ struct vec_state_reg_region region;
+ char __user *uptr = (char __user *)reg->addr;
+
/* Handle the KVM_REG_ARM64_SME_VLS pseudo-reg as a special case: */
if (reg->id == KVM_REG_ARM64_SME_VLS)
return get_sme_vls(vcpu, reg);
- return -EINVAL;
+ /* Try to interpret reg ID as an architectural SME register... */
+ ret = sme_reg_to_region(®ion, vcpu, reg);
+ if (ret)
+ return ret;
+
+ if (!kvm_arm_vcpu_vec_finalized(vcpu))
+ return -EPERM;
+
+ if (copy_to_user(uptr, (void *)vcpu->arch.sme_state + region.koffset,
+ region.klen) ||
+ clear_user(uptr + region.klen, region.upad))
+ return -EFAULT;
+
+ return 0;
}
static int set_sme_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
{
+ int ret;
+ struct vec_state_reg_region region;
+ char __user *uptr = (char __user *)reg->addr;
+
/* Handle the KVM_REG_ARM64_SME_VLS pseudo-reg as a special case: */
if (reg->id == KVM_REG_ARM64_SME_VLS)
return set_sme_vls(vcpu, reg);
- return -EINVAL;
+ /* Try to interpret reg ID as an architectural SME register... */
+ ret = sme_reg_to_region(®ion, vcpu, reg);
+ if (ret)
+ return ret;
+
+ if (!kvm_arm_vcpu_vec_finalized(vcpu))
+ return -EPERM;
+
+ if (copy_from_user((void *)vcpu->arch.sme_state + region.koffset, uptr,
+ region.klen))
+ return -EFAULT;
+
+ return 0;
}
int kvm_arch_vcpu_ioctl_get_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs)
@@ -694,6 +805,27 @@ static unsigned long num_sve_regs(const struct kvm_vcpu *vcpu)
return ret;
}
+static unsigned long num_sme_regs(const struct kvm_vcpu *vcpu)
+{
+ const unsigned int slices = vcpu_sve_slices(vcpu);
+ int regs;
+
+ if (!vcpu_has_sme(vcpu))
+ return 0;
+
+ /* Policed by KVM_GET_REG_LIST: */
+ WARN_ON(!kvm_arm_vcpu_vec_finalized(vcpu));
+
+ /* KVM_REG_ARM64_SME_VLS */
+ regs = 1;
+
+ /* ZA, and ZT0 if SME2 */
+ if (vcpu_za_enabled(vcpu))
+ regs += (slices * vcpu_sme_max_vl(vcpu)) + vcpu_has_sme2(vcpu);
+
+ return regs;
+}
+
static int copy_sve_reg_indices(const struct kvm_vcpu *vcpu,
u64 __user *uindices)
{
@@ -745,6 +877,50 @@ static int copy_sve_reg_indices(const struct kvm_vcpu *vcpu,
return num_regs;
}
+static int copy_sme_reg_indices(const struct kvm_vcpu *vcpu,
+ u64 __user *uindices)
+{
+ const unsigned int slices = vcpu_sve_slices(vcpu);
+ u64 reg;
+ unsigned int i, n;
+ int num_regs = 0;
+
+ if (!vcpu_has_sme(vcpu))
+ return 0;
+
+ /* Policed by KVM_GET_REG_LIST: */
+ WARN_ON(!kvm_arm_vcpu_vec_finalized(vcpu));
+
+ /*
+ * Enumerate this first, so that userspace can save/restore in
+ * the order reported by KVM_GET_REG_LIST:
+ */
+ reg = KVM_REG_ARM64_SME_VLS;
+ if (put_user(reg, uindices++))
+ return -EFAULT;
+ ++num_regs;
+
+ if (vcpu_za_enabled(vcpu)) {
+ for (i = 0; i < slices; i++) {
+ for (n = 0; n < vcpu_sme_max_vl(vcpu); n++) {
+ reg = KVM_REG_ARM64_SME_ZAHREG(n, i);
+ if (put_user(reg, uindices++))
+ return -EFAULT;
+ num_regs++;
+ }
+ }
+
+ if (vcpu_has_sme2(vcpu)) {
+ reg = KVM_REG_ARM64_SME_ZTREG(0);
+ if (put_user(reg, uindices++))
+ return -EFAULT;
+ num_regs++;
+ }
+ }
+
+ return num_regs;
+}
+
/**
* kvm_arm_num_regs - how many registers do we present via KVM_GET_ONE_REG
* @vcpu: the vCPU pointer
@@ -757,6 +933,7 @@ unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu)
res += num_core_regs(vcpu);
res += num_sve_regs(vcpu);
+ res += num_sme_regs(vcpu);
res += kvm_arm_num_sys_reg_descs(vcpu);
res += kvm_arm_get_fw_num_regs(vcpu);
@@ -784,6 +961,11 @@ int kvm_arm_copy_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices)
return ret;
uindices += ret;
+ ret = copy_sme_reg_indices(vcpu, uindices);
+ if (ret < 0)
+ return ret;
+ uindices += ret;
+
ret = kvm_arm_copy_fw_reg_indices(vcpu, uindices);
if (ret < 0)
return ret;
--
2.47.3
^ permalink raw reply related
* [PATCH v13 23/32] KVM: arm64: Flush register state on writes to SVCR.SM and SVCR.ZA
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
Writes to the physical SVCR.SM and SVCR.ZA change the state of PSTATE.SM
and PSTATE.ZA, causing other floating point state to reset. Emulate this
behaviour for writes done via the KVM userspace ABI.
Setting PSTATE.ZA to 1 causes ZA and ZT0 to be reset to 0, these are stored
in sme_state. Setting PSTATE.ZA to 0 causes ZA and ZT0 to become inaccessible
so no reset is needed.
Any change in PSTATE.SM causes the V, Z, P, FFR and FPMR registers to be
reset to 0 and FPSR to be reset to 0x800009f.
Rather than introduce a requirement that the vector configuration be
finalised before writing to SVCR we check for this before updating the
SVE and SME specific state, when finalisation happens they will be
allocated with an initial state of 0.
Similarly in order to avoid ordering requirements between finalisation
and writes to the ID registers we always allocate space for ZT0 if the
hardware supports it, this is 512 bits per vCPU. The overwhelming
majority of practical systems with SME are expected to want use SME2,
there is very little practical reason to disable it other than for
feature testing, and the additional complexity seems more likely to lead
to bugs than deliver practical benefits.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/asm/kvm_host.h | 28 ++++++++++++++++++++++++++++
arch/arm64/include/asm/sysreg.h | 2 ++
arch/arm64/kvm/sys_regs.c | 30 +++++++++++++++++++++++++++++-
3 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 6574c286f50f..cafc43776ac3 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1142,6 +1142,34 @@ struct kvm_vcpu_arch {
#define vcpu_sve_state_size(vcpu) sve_state_size_from_vl(vcpu_sve_max_vl(vcpu))
+#define vcpu_sme_state(vcpu) (kern_hyp_va((vcpu)->arch.sme_state))
+
+#define sme_state_size_from_vl(vl, sme2) ({ \
+ size_t __size_ret; \
+ unsigned int __vq; \
+ \
+ if (WARN_ON(!sve_vl_valid(vl))) { \
+ __size_ret = 0; \
+ } else { \
+ __vq = sve_vq_from_vl(vl); \
+ __size_ret = ZA_SIG_REGS_SIZE(__vq); \
+ if (sme2) \
+ __size_ret += ZT_SIG_REG_BYTES; \
+ } \
+ \
+ __size_ret; \
+})
+
+/*
+ * Always provide space for ZT0 to avoid ordering requirements with ID
+ * register writes and vector finalization.
+ */
+#define vcpu_sme_state_size(vcpu) ({ \
+ unsigned long __vl; \
+ __vl = (vcpu)->arch.max_vl[ARM64_VEC_SME]; \
+ sme_state_size_from_vl(__vl, system_supports_sme2()); \
+})
+
#define KVM_GUESTDBG_VALID_MASK (KVM_GUESTDBG_ENABLE | \
KVM_GUESTDBG_USE_SW_BP | \
KVM_GUESTDBG_USE_HW | \
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index 4b96449e0ffa..b434320de1a7 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -1108,6 +1108,8 @@
#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
+#define FPSR_RESET_VALUE 0x800009f
+
#ifdef __ASSEMBLER__
.macro mrs_s, rt, sreg
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 827502e3249a..f55a8d65cfc2 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1015,6 +1015,34 @@ static unsigned int hidden_visibility(const struct kvm_vcpu *vcpu,
return REG_HIDDEN;
}
+static int set_svcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd,
+ u64 val)
+{
+ u64 old = __vcpu_sys_reg(vcpu, rd->reg);
+
+ if (val & SVCR_RES0)
+ return -EINVAL;
+
+ if ((val & SVCR_ZA) && !(old & SVCR_ZA) &&
+ kvm_arm_vcpu_vec_finalized(vcpu))
+ memset(vcpu->arch.sme_state, 0, vcpu_sme_state_size(vcpu));
+
+ if ((val & SVCR_SM) != (old & SVCR_SM)) {
+ memset(vcpu->arch.ctxt.fp_regs.vregs, 0,
+ sizeof(vcpu->arch.ctxt.fp_regs.vregs));
+
+ if (kvm_arm_vcpu_vec_finalized(vcpu))
+ memset(vcpu->arch.sve_state, 0,
+ vcpu_sve_state_size(vcpu));
+
+ __vcpu_assign_sys_reg(vcpu, FPMR, 0);
+ vcpu->arch.ctxt.fp_regs.fpsr = FPSR_RESET_VALUE;
+ }
+
+ __vcpu_assign_sys_reg(vcpu, rd->reg, val);
+ return 0;
+}
+
static unsigned int pmu_visibility(const struct kvm_vcpu *vcpu,
const struct sys_reg_desc *r)
{
@@ -3616,7 +3644,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
CTR_EL0_DminLine_MASK |
CTR_EL0_L1Ip_MASK |
CTR_EL0_IminLine_MASK),
- { SYS_DESC(SYS_SVCR), undef_access, reset_val, SVCR, 0, .visibility = sme_visibility },
+ { SYS_DESC(SYS_SVCR), undef_access, reset_val, SVCR, 0, .visibility = sme_visibility, .set_user = set_svcr },
{ SYS_DESC(SYS_FPMR), undef_access, reset_val, FPMR, 0, .visibility = fp8_visibility },
{ PMU_SYS_REG(PMCR_EL0), .access = access_pmcr, .reset = reset_pmcr,
--
2.47.3
^ permalink raw reply related
* [PATCH v13 22/32] KVM: arm64: Support userspace access to streaming mode Z and P registers
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
SME introduces a mode called streaming mode where the Z, P and optionally
FFR registers can be accessed using the SVE instructions but with the SME
vector length. Reflect this in the ABI for accessing the guest registers by
making the vector length for the vcpu reflect the vector length that would
be seen by the guest were it running, using the SME vector length when the
guest is configured for streaming mode.
Since SME may be present without SVE we also update the existing checks for
access to the Z, P and V registers to check for either SVE or streaming
mode. When not in streaming mode the guest floating point state may be
accessed via the V registers.
Any VMM that supports SME must be aware of the need to configure streaming
mode prior to writing the floating point registers that this creates.
Since this makes the set of registers that are visible depend on the
current state of the vCPU also add processing of PSCI reset requests to
register enumeration similarly to register reads and writes.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/kvm/arm.c | 9 ++++++
arch/arm64/kvm/guest.c | 83 +++++++++++++++++++++++++++++++++++++++-----------
2 files changed, 74 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 50adfff75be8..d43cfdcaee64 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1882,6 +1882,15 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
r = -EFAULT;
if (copy_from_user(®_list, user_list, sizeof(reg_list)))
break;
+
+ /*
+ * We could owe a reset due to PSCI. Handle the pending reset
+ * here since for SME the set of registers varies depending
+ * on vCPU state.
+ */
+ if (kvm_check_request(KVM_REQ_VCPU_RESET, vcpu))
+ kvm_reset_vcpu(vcpu);
+
n = reg_list.n;
reg_list.n = kvm_arm_num_regs(vcpu);
if (copy_to_user(user_list, ®_list, sizeof(reg_list)))
diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
index 48e6b500f531..110cc7f7527a 100644
--- a/arch/arm64/kvm/guest.c
+++ b/arch/arm64/kvm/guest.c
@@ -73,6 +73,19 @@ static u64 core_reg_offset_from_id(u64 id)
return id & ~(KVM_REG_ARCH_MASK | KVM_REG_SIZE_MASK | KVM_REG_ARM_CORE);
}
+static bool vcpu_has_sve_regs(const struct kvm_vcpu *vcpu)
+{
+ return vcpu_has_sve(vcpu) || vcpu_in_streaming_mode(vcpu);
+}
+
+static bool vcpu_ffr_enabled(const struct kvm_vcpu *vcpu)
+{
+ if (vcpu_in_streaming_mode(vcpu))
+ return vcpu_has_fa64(vcpu);
+ else
+ return vcpu_has_sve(vcpu);
+}
+
static int core_reg_size_from_offset(const struct kvm_vcpu *vcpu, u64 off)
{
int size;
@@ -110,9 +123,10 @@ static int core_reg_size_from_offset(const struct kvm_vcpu *vcpu, u64 off)
/*
* The KVM_REG_ARM64_SVE regs must be used instead of
* KVM_REG_ARM_CORE for accessing the FPSIMD V-registers on
- * SVE-enabled vcpus:
+ * SVE-enabled vcpus or when a SME enabled vcpu is in
+ * streaming mode:
*/
- if (vcpu_has_sve(vcpu) && core_reg_offset_is_vreg(off))
+ if (vcpu_has_sve_regs(vcpu) && core_reg_offset_is_vreg(off))
return -EINVAL;
return size;
@@ -423,6 +437,24 @@ struct vec_state_reg_region {
unsigned int upad; /* extra trailing padding in user memory */
};
+/*
+ * We represent the Z and P registers to userspace using either the
+ * SVE or SME vector length, depending on which features the guest has
+ * and if the guest is in streaming mode.
+ */
+static unsigned int vcpu_sve_cur_vq(struct kvm_vcpu *vcpu)
+{
+ unsigned int vq = 0;
+
+ if (vcpu_has_sve(vcpu))
+ vq = vcpu_sve_max_vq(vcpu);
+
+ if (vcpu_in_streaming_mode(vcpu))
+ vq = vcpu_sme_max_vq(vcpu);
+
+ return vq;
+}
+
/*
* Validate SVE register ID and get sanitised bounds for user/kernel SVE
* register copy
@@ -460,20 +492,25 @@ static int sve_reg_to_region(struct vec_state_reg_region *region,
reg_num = (reg->id & SVE_REG_ID_MASK) >> SVE_REG_ID_SHIFT;
if (reg->id >= zreg_id_min && reg->id <= zreg_id_max) {
- if (!vcpu_has_sve(vcpu) || (reg->id & SVE_REG_SLICE_MASK) > 0)
+ if (!vcpu_has_sve_regs(vcpu) || (reg->id & SVE_REG_SLICE_MASK) > 0)
return -ENOENT;
- vq = vcpu_sve_max_vq(vcpu);
+ vq = vcpu_sve_cur_vq(vcpu);
reqoffset = SVE_SIG_ZREG_OFFSET(vq, reg_num) -
SVE_SIG_REGS_OFFSET;
reqlen = KVM_SVE_ZREG_SIZE;
maxlen = SVE_SIG_ZREG_SIZE(vq);
} else if (reg->id >= preg_id_min && reg->id <= preg_id_max) {
- if (!vcpu_has_sve(vcpu) || (reg->id & SVE_REG_SLICE_MASK) > 0)
+ if (!vcpu_has_sve_regs(vcpu) || (reg->id & SVE_REG_SLICE_MASK) > 0)
return -ENOENT;
- vq = vcpu_sve_max_vq(vcpu);
+ if (!vcpu_ffr_enabled(vcpu) &&
+ (reg->id >= KVM_REG_ARM64_SVE_FFR(0)) &&
+ (reg->id <= KVM_REG_ARM64_SVE_FFR(SVE_NUM_SLICES - 1)))
+ return -ENOENT;
+
+ vq = vcpu_sve_cur_vq(vcpu);
reqoffset = SVE_SIG_PREG_OFFSET(vq, reg_num) -
SVE_SIG_REGS_OFFSET;
@@ -640,15 +677,21 @@ static unsigned long num_core_regs(const struct kvm_vcpu *vcpu)
static unsigned long num_sve_regs(const struct kvm_vcpu *vcpu)
{
const unsigned int slices = vcpu_sve_slices(vcpu);
+ int regs, ret;
- if (!vcpu_has_sve(vcpu))
+ if (!vcpu_has_sve(vcpu) && !vcpu_in_streaming_mode(vcpu))
return 0;
/* Policed by KVM_GET_REG_LIST: */
WARN_ON(!kvm_arm_vcpu_vec_finalized(vcpu));
- return slices * (SVE_NUM_PREGS + SVE_NUM_ZREGS + 1 /* FFR */)
- + 1; /* KVM_REG_ARM64_SVE_VLS */
+ regs = SVE_NUM_PREGS + SVE_NUM_ZREGS;
+ if (vcpu_ffr_enabled(vcpu))
+ regs++; /* FFR */
+ ret = regs * slices;
+ if (vcpu_has_sve(vcpu))
+ ret++; /* KVM_REG_ARM64_SVE_VLS */
+ return ret;
}
static int copy_sve_reg_indices(const struct kvm_vcpu *vcpu,
@@ -659,7 +702,7 @@ static int copy_sve_reg_indices(const struct kvm_vcpu *vcpu,
unsigned int i, n;
int num_regs = 0;
- if (!vcpu_has_sve(vcpu))
+ if (!vcpu_has_sve_regs(vcpu))
return 0;
/* Policed by KVM_GET_REG_LIST: */
@@ -669,10 +712,12 @@ static int copy_sve_reg_indices(const struct kvm_vcpu *vcpu,
* Enumerate this first, so that userspace can save/restore in
* the order reported by KVM_GET_REG_LIST:
*/
- reg = KVM_REG_ARM64_SVE_VLS;
- if (put_user(reg, uindices++))
- return -EFAULT;
- ++num_regs;
+ if (vcpu_has_sve(vcpu)) {
+ reg = KVM_REG_ARM64_SVE_VLS;
+ if (put_user(reg, uindices++))
+ return -EFAULT;
+ ++num_regs;
+ }
for (i = 0; i < slices; i++) {
for (n = 0; n < SVE_NUM_ZREGS; n++) {
@@ -689,10 +734,12 @@ static int copy_sve_reg_indices(const struct kvm_vcpu *vcpu,
num_regs++;
}
- reg = KVM_REG_ARM64_SVE_FFR(i);
- if (put_user(reg, uindices++))
- return -EFAULT;
- num_regs++;
+ if (vcpu_ffr_enabled(vcpu)) {
+ reg = KVM_REG_ARM64_SVE_FFR(i);
+ if (put_user(reg, uindices++))
+ return -EFAULT;
+ num_regs++;
+ }
}
return num_regs;
--
2.47.3
^ permalink raw reply related
* [PATCH v13 21/32] KVM: arm64: Support SME priority registers
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
SME has optional support for configuring the relative priorities of PEs
in systems where they share a single SME hardware block, known as a
SMCU. Currently we do not have any support for this in Linux and will
also hide it from KVM guests, pending experience with practical
implementations. The interface for configuring priority support is via
two new system registers, these registers are always defined when SME is
available.
The register SMPRI_EL1 allows control of SME execution priorities. Since
we disable SME priority support for guests this register is RES0, define
it as such with no storage allocated and enable fine grained traps for
SMPRI_EL1 to ensure that guests can't write to it even if the hardware
supports priorities. Since the register should be readable with fixed
contents we only trap writes, not reads. Since there is no host support
for using priorities the register currently left with a value of 0 by
the host so we do not need to update the value for guests.
There is also an EL2 register SMPRIMAP_EL2 for virtualisation of
priorities which is VNCR mapped, this is RES0 when priority
configuration is not supported but has no register specific traps
available. When saving state from a nested guest we overwrite any value
the guest stored.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/asm/kvm_host.h | 5 +++++
arch/arm64/include/asm/vncr_mapping.h | 1 +
arch/arm64/kvm/config.c | 7 +++++--
arch/arm64/kvm/hyp/vhe/sysreg-sr.c | 7 +++++++
arch/arm64/kvm/nested.c | 7 +++++++
arch/arm64/kvm/sys_regs.c | 25 ++++++++++++++++++++++++-
6 files changed, 49 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index e8c2907aacd2..6574c286f50f 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -558,6 +558,7 @@ enum vcpu_sysreg {
VNCR(CPACR_EL1),/* Coprocessor Access Control */
VNCR(ZCR_EL1), /* SVE Control */
VNCR(SMCR_EL1), /* SME Control */
+ VNCR(SMPRIMAP_EL2), /* Streaming Mode Priority Mapping Register */
VNCR(TTBR0_EL1),/* Translation Table Base Register 0 */
VNCR(TTBR1_EL1),/* Translation Table Base Register 1 */
VNCR(TCR_EL1), /* Translation Control Register */
@@ -1691,6 +1692,10 @@ void kvm_set_vm_id_reg(struct kvm *kvm, u32 reg, u64 val);
(system_supports_sme2() && \
kvm_has_feat((k), ID_AA64PFR1_EL1, SME, SME2))
+#define kvm_has_smps(k) \
+ (kvm_has_sme(k) && \
+ (kvm_read_vm_id_reg(k, SYS_SMIDR_EL1) & SMIDR_EL1_SMPS))
+
#ifdef __KVM_NVHE_HYPERVISOR__
#define vcpu_has_sme2(vcpu) kvm_has_sme2(kern_hyp_va((vcpu)->kvm))
#define vcpu_has_fa64(vcpu) kvm_has_fa64(kern_hyp_va((vcpu)->kvm))
diff --git a/arch/arm64/include/asm/vncr_mapping.h b/arch/arm64/include/asm/vncr_mapping.h
index c3bf92ac52d4..f6152fbbfe03 100644
--- a/arch/arm64/include/asm/vncr_mapping.h
+++ b/arch/arm64/include/asm/vncr_mapping.h
@@ -45,6 +45,7 @@
#define VNCR_ZCR_EL1 0x1E0
#define VNCR_HAFGRTR_EL2 0x1E8
#define VNCR_SMCR_EL1 0x1F0
+#define VNCR_SMPRIMAP_EL2 0x1F8
#define VNCR_TTBR0_EL1 0x200
#define VNCR_TTBR1_EL1 0x210
#define VNCR_FAR_EL1 0x220
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index cb6f3ea556c2..bc7f29a48399 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -281,8 +281,7 @@ static bool feat_anerr(struct kvm *kvm)
static bool feat_sme_smps(struct kvm *kvm)
{
- return (kvm_has_feat(kvm, FEAT_SME) &&
- (kvm_read_vm_id_reg(kvm, SYS_SMIDR_EL1) & SMIDR_EL1_SMPS));
+ return kvm_has_smps(kvm);
}
static bool feat_spe_fds(struct kvm *kvm)
@@ -1677,6 +1676,10 @@ static void __compute_hfgwtr(struct kvm_vcpu *vcpu)
if (cpus_have_final_cap(ARM64_WORKAROUND_AMPERE_AC03_CPU_38))
*vcpu_fgt(vcpu, HFGWTR_EL2) |= HFGWTR_EL2_TCR_EL1;
+
+ /* Emulate RES0 for SMPRI_EL1 until we support priorities */
+ if (cpus_have_final_cap(ARM64_SME))
+ *vcpu_fgt(vcpu, HFGWTR_EL2) &= ~HFGWTR_EL2_nSMPRI_EL1;
}
static void __compute_hdfgwtr(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/kvm/hyp/vhe/sysreg-sr.c b/arch/arm64/kvm/hyp/vhe/sysreg-sr.c
index be685b63e8cf..0fe7153eab08 100644
--- a/arch/arm64/kvm/hyp/vhe/sysreg-sr.c
+++ b/arch/arm64/kvm/hyp/vhe/sysreg-sr.c
@@ -80,6 +80,13 @@ static void __sysreg_save_vel2_state(struct kvm_vcpu *vcpu)
if (ctxt_has_sctlr2(&vcpu->arch.ctxt))
__vcpu_assign_sys_reg(vcpu, SCTLR2_EL2, read_sysreg_el1(SYS_SCTLR2));
+
+ /*
+ * We block SME priorities so SMPRIMAP_EL2 is RES0, however we
+ * do not have traps to block access so the guest might have
+ * updated the state, overwrite anything there.
+ */
+ __vcpu_assign_sys_reg(vcpu, SMPRIMAP_EL2, 0);
}
static void __sysreg_restore_vel2_state(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index 50e25ab9b604..7de9b7f8f90a 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1925,6 +1925,13 @@ int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)
resx.res1 = SMCR_ELx_RES1;
set_sysreg_masks(kvm, SMCR_EL2, resx);
+ /* SMPRMAP_EL2 - RES0 with SME but without priority mapping */
+ if (!kvm_has_smps(kvm)) {
+ resx.res0 = GENMASK_ULL(63, 0);
+ resx.res1 = 0;
+ set_sysreg_masks(kvm, SMPRIMAP_EL2, resx);
+ }
+
out:
for (enum vcpu_sysreg sr = __SANITISED_REG_START__; sr < NR_SYS_REGS; sr++)
__vcpu_rmw_sys_reg(vcpu, sr, |=, 0);
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 9a993e76da18..827502e3249a 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -779,6 +779,15 @@ static bool trap_raz_wi(struct kvm_vcpu *vcpu,
return read_zero(vcpu, p);
}
+static int set_res0(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd,
+ u64 val)
+{
+ if (val)
+ return -EINVAL;
+
+ return 0;
+}
+
/*
* ARMv8.1 mandates at least a trivial LORegion implementation, where all the
* RW registers are RES0 (which we can implement as RAZ/WI). On an ARMv8.0
@@ -2054,6 +2063,15 @@ static unsigned int fp8_visibility(const struct kvm_vcpu *vcpu,
return REG_HIDDEN;
}
+static unsigned int sme_raz_visibility(const struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *rd)
+{
+ if (vcpu_has_sme(vcpu))
+ return REG_RAZ;
+
+ return REG_HIDDEN;
+}
+
static u64 sanitise_id_aa64pfr0_el1(const struct kvm_vcpu *vcpu, u64 val)
{
if (!vcpu_has_sve(vcpu))
@@ -3450,7 +3468,10 @@ static const struct sys_reg_desc sys_reg_descs[] = {
{ SYS_DESC(SYS_ZCR_EL1), NULL, reset_val, ZCR_EL1, 0, .visibility = sve_visibility },
{ SYS_DESC(SYS_TRFCR_EL1), undef_access },
- { SYS_DESC(SYS_SMPRI_EL1), undef_access },
+
+ { SYS_DESC(SYS_SMPRI_EL1), .access = trap_raz_wi, .set_user = set_res0,
+ .get_user = get_raz_reg, .val = 0, .visibility = sme_raz_visibility },
+
{ SYS_DESC(SYS_SMCR_EL1), NULL, reset_val, SMCR_EL1, 0, .visibility = sme_visibility },
{ SYS_DESC(SYS_TTBR0_EL1), access_vm_reg, reset_unknown, TTBR0_EL1 },
{ SYS_DESC(SYS_TTBR1_EL1), access_vm_reg, reset_unknown, TTBR1_EL1 },
@@ -3827,6 +3848,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
EL2_REG_VNCR(HCRX_EL2, reset_val, 0),
+ EL2_REG_FILTERED(SMPRIMAP_EL2, access_rw, reset_val, 0,
+ sme_el2_visibility),
EL2_REG_FILTERED(SMCR_EL2, access_smcr_el2, reset_val, 0,
sme_el2_visibility),
--
2.47.3
^ permalink raw reply related
* [PATCH v13 20/32] KVM: arm64: Support SME identification registers for guests
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
The primary register for identifying SME is ID_AA64PFR1_EL1.SME. This
is hidden from guests unless SME is enabled by the VMM.
When it is visible it is writable and can be used to control the
availability of SME2. In order to avoid any confusion in the KVM
implementation we force ID_AA64PFR1_EL1.SME to advertise SME when SME is
enabled for the guest.
There is also a new register ID_AA64SMFR0_EL1 which we make writable,
forcing it to all bits 0 if SME is disabled. This includes the field
SMEver giving the SME version, userspace is responsible for ensuring
the value is consistent with ID_AA64PFR1_EL1.SME. It also includes
FA64, a separately enableable extension which provides the full FPSIMD
and SVE instruction set including FFR in streaming mode. Userspace can
control the availability of FA64 by writing to this field. The other
features enumerated there only add new instructions, there are no
architectural controls for these.
There is a further identification register SMIDR_EL1 which provides a
basic description of the SME microarchitecture, in a manner similar to
MIDR_EL1 for the PE. It also describes support for priority management
and a basic affinity description for shared SME units, plus some RES0
space. We do not support priority management for guests so this is
hidden from guests, along with any fields defined in future.
As for MIDR_EL1 and REVIDR_EL1 we expose the implementer and revision
information to guests with the raw value from the CPU we are running on,
this may present issues for asymmetric systems or for migration as it
does for the existing registers.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/asm/kvm_host.h | 3 +++
arch/arm64/kvm/config.c | 8 +-----
arch/arm64/kvm/hyp/nvhe/pkvm.c | 4 ++-
arch/arm64/kvm/sys_regs.c | 55 ++++++++++++++++++++++++++++++++++-----
4 files changed, 56 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index da7e572822a1..e8c2907aacd2 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -410,6 +410,7 @@ struct kvm_arch {
u64 revidr_el1;
u64 aidr_el1;
u64 ctr_el0;
+ u64 smidr_el1;
/* Masks for VNCR-backed and general EL2 sysregs */
struct kvm_sysreg_masks *sysreg_masks;
@@ -1585,6 +1586,8 @@ static inline u64 *__vm_id_reg(struct kvm_arch *ka, u32 reg)
return &ka->revidr_el1;
case SYS_AIDR_EL1:
return &ka->aidr_el1;
+ case SYS_SMIDR_EL1:
+ return &ka->smidr_el1;
default:
WARN_ON_ONCE(1);
return NULL;
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 0622162b089e..cb6f3ea556c2 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -281,14 +281,8 @@ static bool feat_anerr(struct kvm *kvm)
static bool feat_sme_smps(struct kvm *kvm)
{
- /*
- * Revisit this if KVM ever supports SME -- this really should
- * look at the guest's view of SMIDR_EL1. Funnily enough, this
- * is not captured in the JSON file, but only as a note in the
- * ARM ARM.
- */
return (kvm_has_feat(kvm, FEAT_SME) &&
- (read_sysreg_s(SYS_SMIDR_EL1) & SMIDR_EL1_SMPS));
+ (kvm_read_vm_id_reg(kvm, SYS_SMIDR_EL1) & SMIDR_EL1_SMPS));
}
static bool feat_spe_fds(struct kvm *kvm)
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index d49f7f327adf..620f3395ea4e 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -357,8 +357,10 @@ static void pkvm_init_features_from_host(struct pkvm_hyp_vm *hyp_vm, const struc
host_kvm->arch.vcpu_features,
KVM_VCPU_MAX_FEATURES);
- if (test_bit(KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS, &host_arch_flags))
+ if (test_bit(KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS, &host_arch_flags)) {
hyp_vm->kvm.arch.midr_el1 = host_kvm->arch.midr_el1;
+ hyp_vm->kvm.arch.smidr_el1 = host_kvm->arch.smidr_el1;
+ }
return;
}
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 8f19caac6008..9a993e76da18 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1951,6 +1951,7 @@ static inline bool is_vm_ftr_id_reg(u32 id)
case SYS_MIDR_EL1:
case SYS_REVIDR_EL1:
case SYS_AIDR_EL1:
+ case SYS_SMIDR_EL1:
return true;
default:
return (sys_reg_Op0(id) == 3 && sys_reg_Op1(id) == 0 &&
@@ -1979,7 +1980,11 @@ static unsigned int id_visibility(const struct kvm_vcpu *vcpu,
switch (id) {
case SYS_ID_AA64ZFR0_EL1:
- if (!vcpu_has_sve(vcpu))
+ if (!vcpu_has_sve(vcpu) && !vcpu_has_sme(vcpu))
+ return REG_RAZ;
+ break;
+ case SYS_ID_AA64SMFR0_EL1:
+ if (!vcpu_has_sme(vcpu))
return REG_RAZ;
break;
}
@@ -2101,7 +2106,9 @@ static u64 sanitise_id_aa64pfr1_el1(const struct kvm_vcpu *vcpu, u64 val)
SYS_FIELD_GET(ID_AA64PFR0_EL1, RAS, pfr0) == ID_AA64PFR0_EL1_RAS_IMP))
val &= ~ID_AA64PFR1_EL1_RAS_frac;
- val &= ~ID_AA64PFR1_EL1_SME;
+ if (!kvm_has_sme(vcpu->kvm))
+ val &= ~ID_AA64PFR1_EL1_SME;
+
val &= ~ID_AA64PFR1_EL1_RNDR_trap;
val &= ~ID_AA64PFR1_EL1_NMI;
val &= ~ID_AA64PFR1_EL1_GCS;
@@ -2316,6 +2323,15 @@ static int set_id_aa64pfr1_el1(struct kvm_vcpu *vcpu,
user_val |= hw_val & ID_AA64PFR1_EL1_MTE_frac_MASK;
}
+ /*
+ * Prevent userspace removing SME from the ID registers while
+ * leaving the capability enabled, avoiding potential bugs.
+ */
+ if (kvm_has_sme(vcpu->kvm) &&
+ SYS_FIELD_GET(ID_AA64PFR1_EL1, SME, user_val) == 0)
+ user_val |= SYS_FIELD_PREP(ID_AA64PFR1_EL1, SME,
+ ID_AA64PFR1_EL1_SME_IMP);
+
return set_id_reg(vcpu, rd, user_val);
}
@@ -3119,8 +3135,11 @@ static bool access_imp_id_reg(struct kvm_vcpu *vcpu,
return access_id_reg(vcpu, p, r);
/*
- * Otherwise, fall back to the old behavior of returning the value of
- * the current CPU.
+ * Otherwise, fall back to the old behavior of returning the
+ * value of the current CPU for REVIDR_EL1 and AIDR_EL1, or
+ * use whatever the sanitised reset value we have is for other
+ * registers not exposed prior to writability support for
+ * these registers.
*/
switch (reg_to_encoding(r)) {
case SYS_REVIDR_EL1:
@@ -3129,6 +3148,9 @@ static bool access_imp_id_reg(struct kvm_vcpu *vcpu,
case SYS_AIDR_EL1:
p->regval = read_sysreg(aidr_el1);
break;
+ case SYS_SMIDR_EL1:
+ p->regval = read_id_reg(vcpu, r);
+ break;
default:
WARN_ON_ONCE(1);
}
@@ -3139,12 +3161,15 @@ static bool access_imp_id_reg(struct kvm_vcpu *vcpu,
static u64 __ro_after_init boot_cpu_midr_val;
static u64 __ro_after_init boot_cpu_revidr_val;
static u64 __ro_after_init boot_cpu_aidr_val;
+static u64 __ro_after_init boot_cpu_smidr_val;
static void init_imp_id_regs(void)
{
boot_cpu_midr_val = read_sysreg(midr_el1);
boot_cpu_revidr_val = read_sysreg(revidr_el1);
boot_cpu_aidr_val = read_sysreg(aidr_el1);
+ if (system_supports_sme())
+ boot_cpu_smidr_val = read_sysreg_s(SYS_SMIDR_EL1);
}
static u64 reset_imp_id_reg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
@@ -3156,6 +3181,8 @@ static u64 reset_imp_id_reg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
return boot_cpu_revidr_val;
case SYS_AIDR_EL1:
return boot_cpu_aidr_val;
+ case SYS_SMIDR_EL1:
+ return boot_cpu_smidr_val & r->val;
default:
KVM_BUG_ON(1, vcpu->kvm);
return 0;
@@ -3204,6 +3231,16 @@ static int set_imp_id_reg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
.val = mask, \
}
+#define IMPLEMENTATION_ID_FILTERED(reg, mask, reg_visibility) { \
+ SYS_DESC(SYS_##reg), \
+ .access = access_imp_id_reg, \
+ .get_user = get_id_reg, \
+ .set_user = set_imp_id_reg, \
+ .reset = reset_imp_id_reg, \
+ .visibility = reg_visibility, \
+ .val = mask, \
+ }
+
static u64 reset_mdcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
{
__vcpu_assign_sys_reg(vcpu, r->reg, vcpu->kvm->arch.nr_pmu_counters);
@@ -3320,7 +3357,6 @@ static const struct sys_reg_desc sys_reg_descs[] = {
ID_AA64PFR1_EL1_MTE_frac |
ID_AA64PFR1_EL1_NMI |
ID_AA64PFR1_EL1_RNDR_trap |
- ID_AA64PFR1_EL1_SME |
ID_AA64PFR1_EL1_RES0 |
ID_AA64PFR1_EL1_MPAM_frac |
ID_AA64PFR1_EL1_MTE)),
@@ -3331,7 +3367,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
ID_AA64PFR2_EL1_GCIE)),
ID_UNALLOCATED(4,3),
ID_WRITABLE(ID_AA64ZFR0_EL1, ~ID_AA64ZFR0_EL1_RES0),
- ID_HIDDEN(ID_AA64SMFR0_EL1),
+ ID_WRITABLE(ID_AA64SMFR0_EL1, ~ID_AA64SMFR0_EL1_RES0),
ID_UNALLOCATED(4,6),
ID_WRITABLE(ID_AA64FPFR0_EL1, ~ID_AA64FPFR0_EL1_RES0),
@@ -3544,6 +3580,13 @@ static const struct sys_reg_desc sys_reg_descs[] = {
{ SYS_DESC(SYS_CCSIDR_EL1), access_ccsidr },
{ SYS_DESC(SYS_CLIDR_EL1), access_clidr, reset_clidr, CLIDR_EL1,
.set_user = set_clidr, .val = ~CLIDR_EL1_RES0 },
+ IMPLEMENTATION_ID_FILTERED(SMIDR_EL1,
+ (SMIDR_EL1_NSMC | SMIDR_EL1_HIP |
+ SMIDR_EL1_AFFINITY2 |
+ SMIDR_EL1_IMPLEMENTER |
+ SMIDR_EL1_REVISION | SMIDR_EL1_SH |
+ SMIDR_EL1_AFFINITY),
+ sme_visibility),
IMPLEMENTATION_ID(AIDR_EL1, GENMASK_ULL(63, 0)),
{ SYS_DESC(SYS_CSSELR_EL1), access_csselr, reset_unknown, CSSELR_EL1 },
ID_FILTERED(CTR_EL0, ctr_el0,
--
2.47.3
^ permalink raw reply related
* [PATCH v13 19/32] KVM: arm64: Support TPIDR2_EL0
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
SME adds a new thread ID register, TPIDR2_EL0. This is used in userspace
for delayed saving of the ZA state but in terms of the architecture is
not really connected to SME other than being part of FEAT_SME. It has an
independent fine grained trap and the runtime connection with the rest
of SME is purely software defined.
Expose the register as a system register if the guest supports SME,
context switching it along with the other EL0 TPIDRs.
Reviewed-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h | 12 ++++++++++++
arch/arm64/kvm/sys_regs.c | 6 +++++-
3 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 796b6e3a50f7..da7e572822a1 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -460,6 +460,7 @@ enum vcpu_sysreg {
CSSELR_EL1, /* Cache Size Selection Register */
TPIDR_EL0, /* Thread ID, User R/W */
TPIDRRO_EL0, /* Thread ID, User R/O */
+ TPIDR2_EL0, /* Thread ID, Register 2 */
TPIDR_EL1, /* Thread ID, Privileged */
CNTKCTL_EL1, /* Timer Control Register (EL1) */
PAR_EL1, /* Physical Address Register */
diff --git a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
index 5624fd705ae3..0fd4092e4f25 100644
--- a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
+++ b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
@@ -88,6 +88,14 @@ static inline bool ctxt_has_sctlr2(struct kvm_cpu_context *ctxt)
return kvm_has_sctlr2(kern_hyp_va(vcpu->kvm));
}
+static inline bool ctxt_has_sme(struct kvm_cpu_context *ctxt)
+{
+ struct kvm_vcpu *vcpu;
+
+ vcpu = ctxt_to_vcpu(ctxt);
+ return kvm_has_sme(kern_hyp_va(vcpu->kvm));
+}
+
static inline bool ctxt_is_guest(struct kvm_cpu_context *ctxt)
{
return host_data_ptr(host_ctxt) != ctxt;
@@ -127,6 +135,8 @@ static inline void __sysreg_save_user_state(struct kvm_cpu_context *ctxt)
{
ctxt_sys_reg(ctxt, TPIDR_EL0) = read_sysreg(tpidr_el0);
ctxt_sys_reg(ctxt, TPIDRRO_EL0) = read_sysreg(tpidrro_el0);
+ if (ctxt_has_sme(ctxt))
+ ctxt_sys_reg(ctxt, TPIDR2_EL0) = read_sysreg_s(SYS_TPIDR2_EL0);
}
static inline void __sysreg_save_el1_state(struct kvm_cpu_context *ctxt)
@@ -204,6 +214,8 @@ static inline void __sysreg_restore_user_state(struct kvm_cpu_context *ctxt)
{
write_sysreg(ctxt_sys_reg(ctxt, TPIDR_EL0), tpidr_el0);
write_sysreg(ctxt_sys_reg(ctxt, TPIDRRO_EL0), tpidrro_el0);
+ if (ctxt_has_sme(ctxt))
+ write_sysreg_s(ctxt_sys_reg(ctxt, TPIDR2_EL0), SYS_TPIDR2_EL0);
}
static inline void __sysreg_restore_el1_state(struct kvm_cpu_context *ctxt,
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 24bbe30c075a..8f19caac6008 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -131,6 +131,7 @@ static enum sr_loc_attr locate_direct_register(const struct kvm_vcpu *vcpu,
case TPIDR_EL0:
case TPIDRRO_EL0:
+ case TPIDR2_EL0:
case TPIDR_EL1:
case PAR_EL1:
case DACR32_EL2:
@@ -246,6 +247,7 @@ static u64 read_sr_from_cpu(enum vcpu_sysreg reg)
case SCTLR2_EL1: val = read_sysreg_s(SYS_SCTLR2_EL12); break;
case TPIDR_EL0: val = read_sysreg_s(SYS_TPIDR_EL0); break;
case TPIDRRO_EL0: val = read_sysreg_s(SYS_TPIDRRO_EL0); break;
+ case TPIDR2_EL0: val = read_sysreg_s(SYS_TPIDR2_EL0); break;
case TPIDR_EL1: val = read_sysreg_s(SYS_TPIDR_EL1); break;
case PAR_EL1: val = read_sysreg_par(); break;
case DACR32_EL2: val = read_sysreg_s(SYS_DACR32_EL2); break;
@@ -285,6 +287,7 @@ static void write_sr_to_cpu(enum vcpu_sysreg reg, u64 val)
case SCTLR2_EL1: write_sysreg_s(val, SYS_SCTLR2_EL12); break;
case TPIDR_EL0: write_sysreg_s(val, SYS_TPIDR_EL0); break;
case TPIDRRO_EL0: write_sysreg_s(val, SYS_TPIDRRO_EL0); break;
+ case TPIDR2_EL0: write_sysreg_s(val, SYS_TPIDR2_EL0); break;
case TPIDR_EL1: write_sysreg_s(val, SYS_TPIDR_EL1); break;
case PAR_EL1: write_sysreg_s(val, SYS_PAR_EL1); break;
case DACR32_EL2: write_sysreg_s(val, SYS_DACR32_EL2); break;
@@ -3598,7 +3601,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
.visibility = s1poe_visibility },
{ SYS_DESC(SYS_TPIDR_EL0), NULL, reset_unknown, TPIDR_EL0 },
{ SYS_DESC(SYS_TPIDRRO_EL0), NULL, reset_unknown, TPIDRRO_EL0 },
- { SYS_DESC(SYS_TPIDR2_EL0), undef_access },
+ { SYS_DESC(SYS_TPIDR2_EL0), NULL, reset_unknown, TPIDR2_EL0,
+ .visibility = sme_visibility },
{ SYS_DESC(SYS_SCXTNUM_EL0), undef_access },
--
2.47.3
^ permalink raw reply related
* [PATCH v13 18/32] KVM: arm64: Support SME control registers
From: Mark Brown @ 2026-07-19 23:07 UTC (permalink / raw)
To: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Oliver Upton
Cc: Dave Martin, Fuad Tabba, Mark Rutland, Ben Horgan,
Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger,
Mark Brown
In-Reply-To: <20260720-kvm-arm64-sme-v13-0-d9abd3ffa245@kernel.org>
SME is configured by the system registers SMCR_EL1 and SMCR_EL2, add
definitions and userspace access for them. These control the SME vector
length in a manner similar to that for SVE and also have feature enable
bits for SME2 and FA64. A subsequent patch will add management of them
for guests as part of the general floating point context switch, as is
done for the equivalent SVE registers.
Signed-off-by: Mark Brown <broonie@kernel.org>
---
arch/arm64/include/asm/kvm_emulate.h | 15 +++++++++++++++
arch/arm64/include/asm/kvm_host.h | 2 ++
arch/arm64/include/asm/vncr_mapping.h | 1 +
arch/arm64/kvm/nested.c | 5 +++++
arch/arm64/kvm/sys_regs.c | 31 ++++++++++++++++++++++++++++++-
5 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 994afbf479fc..b5dc8a4c320a 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -90,6 +90,15 @@ static inline void kvm_inject_nested_sve_trap(struct kvm_vcpu *vcpu)
kvm_inject_nested_sync(vcpu, esr);
}
+static inline void kvm_inject_nested_sme_trap(struct kvm_vcpu *vcpu,
+ u64 smtc)
+{
+ u64 esr = FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_SME) |
+ ESR_ELx_IL | smtc;
+
+ kvm_inject_nested_sync(vcpu, esr);
+}
+
#if defined(__KVM_VHE_HYPERVISOR__) || defined(__KVM_NVHE_HYPERVISOR__)
static __always_inline bool vcpu_el1_is_32bit(struct kvm_vcpu *vcpu)
{
@@ -689,4 +698,10 @@ static inline void vcpu_set_hcrx(struct kvm_vcpu *vcpu)
vcpu->arch.hcrx_el2 |= HCRX_EL2_EnASR;
}
}
+
+static inline bool guest_hyp_sme_traps_enabled(const struct kvm_vcpu *vcpu)
+{
+ return __guest_hyp_cptr_xen_trap_enabled(vcpu, SMEN);
+}
+
#endif /* __ARM64_KVM_EMULATE_H__ */
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 858341eb1e8f..796b6e3a50f7 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -546,6 +546,7 @@ enum vcpu_sysreg {
MDCR_EL2, /* Monitor Debug Configuration Register (EL2) */
CNTHCTL_EL2, /* Counter-timer Hypervisor Control register */
ZCR_EL2, /* SVE Control Register (EL2) */
+ SMCR_EL2, /* SME Control Register (EL2) */
/* Any VNCR-capable reg goes after this point */
MARKER(__VNCR_START__),
@@ -554,6 +555,7 @@ enum vcpu_sysreg {
VNCR(ACTLR_EL1),/* Auxiliary Control Register */
VNCR(CPACR_EL1),/* Coprocessor Access Control */
VNCR(ZCR_EL1), /* SVE Control */
+ VNCR(SMCR_EL1), /* SME Control */
VNCR(TTBR0_EL1),/* Translation Table Base Register 0 */
VNCR(TTBR1_EL1),/* Translation Table Base Register 1 */
VNCR(TCR_EL1), /* Translation Control Register */
diff --git a/arch/arm64/include/asm/vncr_mapping.h b/arch/arm64/include/asm/vncr_mapping.h
index 14366d35ce82..c3bf92ac52d4 100644
--- a/arch/arm64/include/asm/vncr_mapping.h
+++ b/arch/arm64/include/asm/vncr_mapping.h
@@ -44,6 +44,7 @@
#define VNCR_HDFGWTR_EL2 0x1D8
#define VNCR_ZCR_EL1 0x1E0
#define VNCR_HAFGRTR_EL2 0x1E8
+#define VNCR_SMCR_EL1 0x1F0
#define VNCR_TTBR0_EL1 0x200
#define VNCR_TTBR1_EL1 0x210
#define VNCR_FAR_EL1 0x220
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index fb54f6dad995..50e25ab9b604 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1920,6 +1920,11 @@ int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)
resx.res1 = ZCR_ELx_RES1;
set_sysreg_masks(kvm, ZCR_EL2, resx);
+ /* SMCR_EL2 - bits 8:4 are RAZ/WI so treat them as RES0 */
+ resx.res0 = SMCR_ELx_RES0 | GENMASK_ULL(8, 4);
+ resx.res1 = SMCR_ELx_RES1;
+ set_sysreg_masks(kvm, SMCR_EL2, resx);
+
out:
for (enum vcpu_sysreg sr = __SANITISED_REG_START__; sr < NR_SYS_REGS; sr++)
__vcpu_rmw_sys_reg(vcpu, sr, |=, 0);
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index ba8a3ed8f5ff..24bbe30c075a 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -121,6 +121,7 @@ static enum sr_loc_attr locate_direct_register(const struct kvm_vcpu *vcpu,
case ELR_EL1:
case SPSR_EL1:
case ZCR_EL1:
+ case SMCR_EL1:
case SCTLR2_EL1:
/*
* EL1 registers which have an ELx2 mapping are loaded if
@@ -241,6 +242,7 @@ static u64 read_sr_from_cpu(enum vcpu_sysreg reg)
case ELR_EL1: val = read_sysreg_s(SYS_ELR_EL12); break;
case SPSR_EL1: val = read_sysreg_s(SYS_SPSR_EL12); break;
case ZCR_EL1: val = read_sysreg_s(SYS_ZCR_EL12); break;
+ case SMCR_EL1: val = read_sysreg_s(SYS_SMCR_EL12); break;
case SCTLR2_EL1: val = read_sysreg_s(SYS_SCTLR2_EL12); break;
case TPIDR_EL0: val = read_sysreg_s(SYS_TPIDR_EL0); break;
case TPIDRRO_EL0: val = read_sysreg_s(SYS_TPIDRRO_EL0); break;
@@ -279,6 +281,7 @@ static void write_sr_to_cpu(enum vcpu_sysreg reg, u64 val)
case ELR_EL1: write_sysreg_s(val, SYS_ELR_EL12); break;
case SPSR_EL1: write_sysreg_s(val, SYS_SPSR_EL12); break;
case ZCR_EL1: write_sysreg_s(val, SYS_ZCR_EL12); break;
+ case SMCR_EL1: write_sysreg_s(val, SYS_SMCR_EL12); break;
case SCTLR2_EL1: write_sysreg_s(val, SYS_SCTLR2_EL12); break;
case TPIDR_EL0: write_sysreg_s(val, SYS_TPIDR_EL0); break;
case TPIDRRO_EL0: write_sysreg_s(val, SYS_TPIDRRO_EL0); break;
@@ -2830,6 +2833,12 @@ static unsigned int sve_el2_visibility(const struct kvm_vcpu *vcpu,
return __el2_visibility(vcpu, rd, sve_visibility);
}
+static unsigned int sme_el2_visibility(const struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *rd)
+{
+ return __el2_visibility(vcpu, rd, sme_visibility);
+}
+
static unsigned int vncr_el2_visibility(const struct kvm_vcpu *vcpu,
const struct sys_reg_desc *rd)
{
@@ -2872,6 +2881,23 @@ static bool access_zcr_el2(struct kvm_vcpu *vcpu,
return true;
}
+static bool access_smcr_el2(struct kvm_vcpu *vcpu,
+ struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ if (guest_hyp_sme_traps_enabled(vcpu)) {
+ kvm_inject_nested_sme_trap(vcpu, ESR_ELx_SME_ISS_SMTC_SME_DISABLED);
+ return false;
+ }
+
+ if (!p->is_write)
+ p->regval = __vcpu_sys_reg(vcpu, SMCR_EL2);
+ else
+ __vcpu_assign_sys_reg(vcpu, SMCR_EL2, p->regval);
+
+ return true;
+}
+
static bool access_gic_vtr(struct kvm_vcpu *vcpu,
struct sys_reg_params *p,
const struct sys_reg_desc *r)
@@ -3386,7 +3412,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
{ SYS_DESC(SYS_ZCR_EL1), NULL, reset_val, ZCR_EL1, 0, .visibility = sve_visibility },
{ SYS_DESC(SYS_TRFCR_EL1), undef_access },
{ SYS_DESC(SYS_SMPRI_EL1), undef_access },
- { SYS_DESC(SYS_SMCR_EL1), undef_access },
+ { SYS_DESC(SYS_SMCR_EL1), NULL, reset_val, SMCR_EL1, 0, .visibility = sme_visibility },
{ SYS_DESC(SYS_TTBR0_EL1), access_vm_reg, reset_unknown, TTBR0_EL1 },
{ SYS_DESC(SYS_TTBR1_EL1), access_vm_reg, reset_unknown, TTBR1_EL1 },
{ SYS_DESC(SYS_TCR_EL1), access_vm_reg, reset_val, TCR_EL1, 0 },
@@ -3754,6 +3780,9 @@ static const struct sys_reg_desc sys_reg_descs[] = {
EL2_REG_VNCR(HCRX_EL2, reset_val, 0),
+ EL2_REG_FILTERED(SMCR_EL2, access_smcr_el2, reset_val, 0,
+ sme_el2_visibility),
+
EL2_REG(TTBR0_EL2, access_rw, reset_val, 0),
EL2_REG(TTBR1_EL2, access_rw, reset_val, 0),
EL2_REG(TCR_EL2, access_rw, reset_val, TCR_EL2_RES1),
--
2.47.3
^ 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