* [RFC PATCH v1 1/8] misc/arm-cla: Add driver skeleton and documentation
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
@ 2026-07-17 10:47 ` Ryan Roberts
2026-07-17 10:47 ` [RFC PATCH v1 2/8] misc/arm-cla: Add launch operation helpers Ryan Roberts
` (7 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 10:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: Ryan Roberts, linux-kernel, linux-arm-kernel, dri-devel,
linux-doc
From: Jean-Philippe Brucker <jpb@kernel.org>
Add the initial Kconfig and build-system plumbing for the Arm Core Local
Accelerator driver.
Introduce the common driver header and register definitions used by
later CLA support. The definitions cover the CLA MMIO frame, launch
response and status fields, standard accelerator registers, launch
opcodes, error codes and memory translation context state.
Add documentation describing the CLA programming model, its CPU-local
MMIO access rules, userspace assignment model, domain grouping and
expected boot state.
Co-developed-by: Ryan Roberts <ryan.roberts@arm.com>
Signed-off-by: Ryan Roberts <ryan.roberts@arm.com>
Signed-off-by: Jean-Philippe Brucker <jpb@kernel.org>
---
Documentation/misc-devices/arm-cla.rst | 206 +++++++++++++++++++++++++
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/arm-cla/Kconfig | 10 ++
drivers/misc/arm-cla/Makefile | 1 +
drivers/misc/arm-cla/arm-cla-regs.h | 177 +++++++++++++++++++++
drivers/misc/arm-cla/arm-cla.h | 38 +++++
7 files changed, 434 insertions(+)
create mode 100644 Documentation/misc-devices/arm-cla.rst
create mode 100644 drivers/misc/arm-cla/Kconfig
create mode 100644 drivers/misc/arm-cla/Makefile
create mode 100644 drivers/misc/arm-cla/arm-cla-regs.h
create mode 100644 drivers/misc/arm-cla/arm-cla.h
diff --git a/Documentation/misc-devices/arm-cla.rst b/Documentation/misc-devices/arm-cla.rst
new file mode 100644
index 000000000000..dfe2ab4a10a0
--- /dev/null
+++ b/Documentation/misc-devices/arm-cla.rst
@@ -0,0 +1,206 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+================================
+Arm Core Local Accelerator (CLA)
+================================
+
+Arm CLA is an interface local to a CPU for programming accelerators that access
+memory via the CPU's MMU:
+
+ ┌───────┬───────┬───────┐ ┌───────┐
+ │ CPU │ MMU │ CLA │ │ Accel │─┐
+ │ │ │ │ │ │ │
+ │ ---MMIO--> <--> │ │
+ │ │ │ │ │ │ │
+ │ │ ,-----DMA-----> │ │
+ └───────┴────|──┴───────┘ └───────┘ │
+ ┌──────────v────────┐ └───────┘
+ │ Caches and memory │
+ └───────────────────┘
+
+
+Hardware
+========
+
+The CLA supports up to 8 attached accelerators, which are accessed by
+programming the CLA's MMIO registers. Operations are launched to an accelerator
+and are polled for completion. CLA does not raise interrupts.
+
+ CPU CLA Accel
+ |--- write DATA[7:0] -->| |
+ |--- write LAUNCH ----->|---- launch ---->|
+ |<--- poll LRESP -------| |
+ | | |
+ |<--- poll STATUS ------|<--- complete ---|
+
+Each operation can take a 512-bit payload in the DATA registers. After handling
+a LAUNCH write, CLA indicates the launch status in the LRESP register. A further
+operation can only be launched after LRESP indicates completion of the previous
+launch.
+
+Some operations continue to run asynchronously on the accelerator after launch
+completion. In this case progress is tracked by polling the STATUS register.
+When the CLA updates the STATUS register, it also raises an event which will
+wake an in-progress WFE (wait for event) instruction on the local CPU.
+
+The CLA's MMIO registers are not accessible from remote CPUs. Although each CLA
+has a unique physical address, accesses from remote CPUs are read as zero and
+write ignored.
+
+The CLA registers are accessible from four different Privilege Level (PL)
+frames, with usage inteded to map to EL0 - EL3. The PLxCTRL registers may be
+written via a higher PL frame to suppress access to accelerators via a lower PL
+frame.
+
+The CPU and CLA share an MMU, although FEAT_TTCNP (common not private) is
+implemented, allowing both CPU and CLA to independently opt into and out of
+sharing TLB entries at runtime. TLB invalidation is performed via the CPU TLBI
+instructions; any TLBI instruction that targets the CLA's local CPU will also
+implicitly target the CLA.
+
+CLA has its own set of Memory Translation Context (MTC) registers, distinct from
+the CPU. A PL can set the MTC registers corresponding to an equivalent CPU
+Exception Level (EL) (eg. TCR_EL2 configurable only from the PL2 and PL3 MMIO
+frame).
+
+Faults during address translation are reported by the accelerator in its
+registers and in STATUS. While polling for work completion, software fixes up
+the faults and notifies the accelerator with RESOLVE operations.
+
+Accesses to the MMIO registers must be aligned 64-bit loads and stores, and the
+registers are mapped with Device-nGnRE attribute. Invalid accesses (unaligned,
+atomic, badly sized, etc) to the MMIO frame are either read as zero and write
+ignored, or cause a data abort, depending on the platform. Invalid access will
+never cause an SError.
+
+
+Inter-Accelerator Communication
+-------------------------------
+
+On some platforms, multiple accelerators, each attached to a separate CLA within
+a cluster, are also directly connected to each other via a shared bus to
+accelerate cooperation between accelerators. The accelerators sharing a bus
+cannot be isolated from each other. When collaborative operations are launched
+on each of the participating accelerators, they synchronize over the bus,
+stalling until all are ready.
+
+
+Intended SW Usage Model
+=======================
+
+CLA is designed for its PL0 MMIO frame to be mapped into user space and for user
+space to directly launch accelerator operations and poll for completion. It has
+been observed that for some use cases, the operation execution time is small and
+a trip through the kernel would consume a significant amount of the CPU budget
+for preparing the next operation leading to a significant reduction in bandwidth
+through the accelerator.
+
+User space software is expected to create a thread to drive each CLA it is
+using, and for each thread to be pinned to the CLA's local CPU.
+
+Software should rely on WFE (wait for event) to reduce power consumption when
+polling STATUS.
+
+The CLA is intended to be configured, by privileged software via PL1 and/or PL2,
+so that it shares virtual addresses with the process to which it is assigned
+(SVA). In practice this means configuring the CLA's MTC to point to the same
+page tables and use the same ASID (and VMID if relevant) as the process to which
+it is assigned. This ensures the architectural TLB invalidations also correctly
+target the CLA's TLB entries.
+
+We investigated the possibility of having the CLA driver allocate private page
+tables, private ASIDs/VMIDs and implement an MMU notifier for invalidation, but
+that suffers from 2 issues; there is a possibility of over-invalidation since
+the ASID and VMID spaces overlap with the CPU's (minor), and when issuing a
+TLBI, VMID is implicitly taken from CPU's VTTBR_EL2.VMID, which won't match the
+CLA's private VMID - therefore, for a virtualization scenario, TLB invalidation
+becomes impossible (major).
+
+Because the CLA has its own MTC registers, it is correct and safe for it to be
+executing an operation on behalf of user process A, while its local CPU is
+executing a thread from user process B.
+
+
+Difficulties for software to deal with
+--------------------------------------
+
+Although each CLA is attached to a single CPU, not all CPUs have a CLA, and CLAs
+may have a different set of accelerators attached. Users need to probe around to
+find a suitable accelerator and bind their process to it.
+
+Since remote CPUs can't access the CLA, dealing with CPU hotplug migrating tasks
+is challenging. And virtualization breaks down if the hypervisor cannot
+guarantee that a vCPU is pinned to a CPU.
+
+Saving and restoring the internal state of the accelerator is an optional
+feature. Current platforms only support it when the accelerator is idle, so
+preempting an accelerator causes work cancellation. Software must carefully
+consider how to balance forward-progress guarantees with preemption latency.
+
+
+Driver
+======
+
+Booting
+-------
+
+A host expects to be booted with CLAs in the following state:
+- All attached accelerators have STATUS_IDLE set.
+- PL2CTRL: AVAIL all enabled (no accelerator request made at runtime).
+ DBG at the firmware's discretion, preferably all enabled.
+- TSCTRLOWNER.PL, TSOFFOWNER.PL, PMUOWNER.PL all EL2.
+
+A guest expects to be booted with CLAs in the following state:
+- All attached accelerators have STATUS_IDLE set.
+- PL1CTRL: AVAIL all enabled (no accelerator request made at runtime).
+ DBG at the hypervisor's discretion, preferably all enabled.
+
+Linux discovers the base address of each CLA device in the firmware tables and
+creates platform devices. cla_dev behaves mostly like a regular platform device,
+but it can only be accessed from one specific CPU. CPU hotplug notifiers probe
+and teardown the CLA device.
+
+CLAs may be grouped into domains. The topology is described in the firmware
+tables, and the driver creates cla_domain objects containing one or more
+cla_dev.
+
+
+Assignment to userspace
+-----------------------
+
+A char device provides enumeration and mmap abilities to userspace. The user
+task queries the driver to find a suitable set of CLAs, and mmaps their
+registers. For each mapped domain, the driver creates a cla_ctx context. When
+the user accesses the registers, the driver's fault handler queues the cla_ctx
+and waits for assignment. A context reassignment switches the whole cla_domain,
+by calling each CPU in the domain to switch the CLA context. A time slice is
+given to each context before being deassigned.
+
+In order to access the registers, the task must be bound to the CLA's CPU with
+sched_setaffinity(). Accesses from remote CPUs are ignored. If the CPU gets
+offlined, the task is migrated to an online CPU, and the driver disables the
+offlined CLA. User space may choose to use a mechanism such as restartable
+sequences to be notified when its task has been migrated away from its intended
+CLA.
+
+The accelerators access memory using the CPU's MMU. When assigning a context,
+the driver sets up the CLA's Memory Translation Context (MTC) with the page
+directory (TTBR), address space ID (ASID) and configuration (TCR) of the
+context's mm. When receiving translation faults from the accelerator, the user
+space task accesses the faulting address from the CPU to trigger the fault to be
+fixed up by the kernel before launching a RESOLVE operation to the accelerator.
+
+
+Power management
+----------------
+
+When a CPU enters a deep low-power state, then depending on the platform, the
+attached CLA and accelerators might not retain their state. In that case the
+firmware is expected to save the CLA and accelerator states before entering CPU
+low-power state, and restore them after exiting. The CLA driver does not expect
+to notice a CPU deep idle. However some accelerators do not support context
+saving, in which case userspace will notice from the STATUS register that the
+work was canceled during deep idle. Given that userspace would be polling
+STATUS, the CPU is unlikely to enter deep idle while the CLA is running. To
+ensure forward-progress the admin can disable deep idle states (see
+Documentation/admin-guide/pm/cpuidle.rst).
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 390256ed91f4..a2491b5da224 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -651,4 +651,5 @@ source "drivers/misc/mchp_pci1xxxx/Kconfig"
source "drivers/misc/keba/Kconfig"
source "drivers/misc/amd-sbi/Kconfig"
source "drivers/misc/rp1/Kconfig"
+source "drivers/misc/arm-cla/Kconfig"
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index fed47c7672b9..51d878822c1f 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -74,3 +74,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o
obj-y += keba/
obj-y += amd-sbi/
obj-$(CONFIG_MISC_RP1) += rp1/
+obj-$(CONFIG_ARM_CLA) += arm-cla/
diff --git a/drivers/misc/arm-cla/Kconfig b/drivers/misc/arm-cla/Kconfig
new file mode 100644
index 000000000000..f8436a12c1e6
--- /dev/null
+++ b/drivers/misc/arm-cla/Kconfig
@@ -0,0 +1,10 @@
+config ARM_CLA
+ tristate "Arm Core Local Accelerator"
+ default n
+ depends on ARM64
+ help
+ Arm Core Local Accelerator (CLA) is coupled with a specific CPU and
+ provides an interface to enumerate and access attached hardware
+ accelerators. It is only accessible from its associated CPU.
+
+ If unsure, say N.
diff --git a/drivers/misc/arm-cla/Makefile b/drivers/misc/arm-cla/Makefile
new file mode 100644
index 000000000000..a4e40e534e6a
--- /dev/null
+++ b/drivers/misc/arm-cla/Makefile
@@ -0,0 +1 @@
+# SPDX-License-Identifier: GPL-2.0-only
diff --git a/drivers/misc/arm-cla/arm-cla-regs.h b/drivers/misc/arm-cla/arm-cla-regs.h
new file mode 100644
index 000000000000..fcd187ecb777
--- /dev/null
+++ b/drivers/misc/arm-cla/arm-cla-regs.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Arm CLA driver - register definitions
+ *
+ * Copyright 2026 Arm Limited.
+ */
+#ifndef _ARM_CLA_REGS_H_
+#define _ARM_CLA_REGS_H_
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+
+/* Registers */
+#define CLA_REG_DATA(i) (0x00 + (8 * (i)))
+#define CLA_REG_LAUNCH 0x40
+#define CLA_REG_LRESP 0x48
+#define CLA_REG_PL0CTRL 0x60
+#define CLA_REG_PL1CTRL 0x68
+#define CLA_REG_PL2CTRL 0x70
+#define CLA_REG_EVENT 0x78
+#define CLA_REG_STATUS(i) (0x80 + (8 * (i)))
+#define CLA_REG_CLAAIDR 0xC0
+
+#define CLA_LAUNCH_OP GENMASK(3, 0)
+#define CLA_LAUNCH_NDATA_M1 GENMASK(6, 4)
+#define CLA_LAUNCH_SEQ BIT(7)
+#define CLA_LAUNCH_ACCID GENMASK(10, 8)
+#define CLA_LAUNCH_REGIDX GENMASK(63, 32)
+
+#define CLA_LRESP_PENDING BIT(0)
+#define CLA_LRESP_CODE GENMASK(2, 1)
+#define CLA_LRESP_ERRCODE GENMASK(7, 3)
+#define CLA_LRESP_DATANZ BIT(15)
+
+#define CLA_PLxCTRL_AVAIL BIT(0)
+#define CLA_PLxCTRL_DBGPERM GENMASK(3, 1)
+#define CLA_PLxCTRL_PREP(accid, v) ((u64)(v) << (8 * (accid)))
+
+#define CLA_STATUS_AVAIL BIT(0)
+#define CLA_STATUS_DMB BIT(1)
+#define CLA_STATUS_EABORT BIT(2)
+#define CLA_STATUS_IDLE BIT(4)
+#define CLA_STATUS_READY BIT(5)
+#define CLA_STATUS_FAULT BIT(6)
+#define CLA_STATUS_EXCEPT BIT(7)
+#define CLA_STATUS_SRMODE BIT(8)
+#define CLA_STATUS_USER GENMASK(63, 16)
+
+/* Some useful values for sanity checks */
+#define CLA_STATUS_STATE_IDLE (CLA_STATUS_AVAIL | \
+ CLA_STATUS_IDLE | \
+ CLA_STATUS_READY)
+#define CLA_STATUS_STATE_SRMODE (CLA_STATUS_AVAIL | \
+ CLA_STATUS_IDLE | \
+ CLA_STATUS_READY | \
+ CLA_STATUS_SRMODE)
+/* Bits we care about when checking the state */
+#define CLA_STATUS_STATE_MASK (CLA_STATUS_AVAIL | \
+ CLA_STATUS_EABORT | \
+ CLA_STATUS_IDLE | \
+ CLA_STATUS_READY | \
+ CLA_STATUS_FAULT | \
+ CLA_STATUS_EXCEPT | \
+ CLA_STATUS_SRMODE)
+
+/* Standard accelerator registers */
+#define CLA_REG_IIDR 0x0000
+#define CLA_REG_DEVARCH 0x0001
+#define CLA_REG_REVIDR 0x0002
+#define CLA_REG_IASSIZE 0x0003
+#define CLA_REG_ACAP 0x0004
+#define CLA_REG_FSARV 0x001f
+#define CLA_REG_FSAR(n) (0x0020 + (n))
+#define CLA_REG_TSCTRLOWNER 0x00c0
+#define CLA_REG_TSCTRL 0x00c8
+#define CLA_REG_TSOFFOWNER 0x00d0
+#define CLA_REG_TSVOFF 0x00d8
+#define CLA_REG_TSPOFF 0x00d9
+#define CLA_REG_PMUOWNER 0x0100
+#define CLA_REG_PMURESET 0x0108
+#define CLA_REG_PMUCTRL 0x0110
+#define CLA_REG_PMUSNAP 0x0111
+#define CLA_REG_PMUEVT(n) (0x0120 + (n))
+#define CLA_REG_PMUCNT(n) (0x0140 + (n))
+#define CLA_REG_PMUSCNT(n) (0x0160 + (n))
+#define CLA_REG_IASn 0x8000
+
+#define CLA_IIDR_PRODUCTID GENMASK(31, 20)
+#define CLA_IIDR_VARIANT GENMASK(19, 16)
+#define CLA_IIDR_REVISION GENMASK(15, 12)
+#define CLA_IIDR_IMPLEMENTER GENMASK(11, 0)
+
+#define CLA_DEVARCH_ARCHITECT GENMASK(31, 21)
+#define CLA_DEVARCH_PRESENT BIT(20)
+#define CLA_DEVARCH_REVISION GENMASK(19, 16)
+#define CLA_DEVARCH_ARCHID GENMASK(15, 0)
+
+#define CLA_REVIDR_REVISION GENMASK(31, 0)
+
+#define CLA_ACAP_SROP BIT(0)
+#define CLA_ACAP_REGSTATE BIT(1)
+#define CLA_ACAP_PMUCNTS GENMASK(4, 2)
+#define CLA_ACAP_TS BIT(5)
+
+#define CLA_FSAR_READ BIT(0)
+#define CLA_FSAR_WRITE BIT(1)
+#define CLA_FSAR_ADDR GENMASK(63, 6)
+
+#define CLA_TSCTRLOWNER_PL GENMASK(1, 0)
+#define CLA_TSCTRL_TS GENMASK(1, 0)
+#define CLA_TSOFFOWNER_PL GENMASK(1, 0)
+
+#define CLA_TSCTRL_ZERO 0
+#define CLA_TSCTRL_VIRTUAL 1
+#define CLA_TSCTRL_GUESTPHYSICAL 2
+#define CLA_TSCTRL_PHYSICAL 3
+
+#define CLA_PMUCTRL_EN BIT(0)
+#define CLA_PMUOWNER_PL GENMASK(1, 0)
+
+/* LAUNCH operations */
+#define CLA_LAUNCH_OP_RESET 0
+#define CLA_LAUNCH_OP_CMD 1
+#define CLA_LAUNCH_OP_CMDNR 2
+#define CLA_LAUNCH_OP_ENTERSR 4
+#define CLA_LAUNCH_OP_EXITSR 5
+#define CLA_LAUNCH_OP_SAVE 6
+#define CLA_LAUNCH_OP_RESTORE 7
+#define CLA_LAUNCH_OP_RESOLVE 9
+#define CLA_LAUNCH_OP_REGREAD 10
+#define CLA_LAUNCH_OP_REGWRITE 11
+#define CLA_LAUNCH_OP_SETCTX 12
+#define CLA_LAUNCH_OP_GETCTX 13
+
+/* Return codes */
+#define CLA_LRESP_OK 0
+#define CLA_LRESP_UNAVAIL 1
+#define CLA_LRESP_BUSY 2
+#define CLA_LRESP_ERROR 3
+
+#define CLA_ERRCODE_CSINT 0
+#define CLA_ERRCODE_CSOF 1
+#define CLA_ERRCODE_NOTIDLE 2
+#define CLA_ERRCODE_PERM 3
+#define CLA_ERRCODE_NOACC 4
+#define CLA_ERRCODE_INVAL 5
+#define CLA_ERRCODE_RESET 6
+
+/* Memory translation context */
+#define CLA_MTC_REGIDX_PL1 0
+#define CLA_MTC_REGIDX_PL2 64
+#define CLA_MTC_PL_SIZE 64
+
+/* Common register offsets */
+#define CLA_MTC_PSTATE 0
+#define CLA_MTC_TTBR0 1
+#define CLA_MTC_TTBR1 2
+#define CLA_MTC_TCR 3
+#define CLA_MTC_SCTLR 4
+#define CLA_MTC_MAIR 5
+#define CLA_MTC_TCR2 8
+
+/* EL2 specific register offsets */
+#define CLA_MTC_HCR_EL2 80
+#define CLA_MTC_VTTBR_EL2 81
+#define CLA_MTC_VTCR_EL2 82
+
+#define CLA_MTC_PSTATE_EL GENMASK(1, 0)
+#define CLA_MTC_PSTATE_PAN BIT(2)
+
+#define CLA_SRSTATE_0_SROP BIT(0)
+#define CLA_SRSTATE_0_REGSTATE GENMASK(15, 1)
+#define CLA_SRSTATE_1_STATUS GENMASK(63, 0)
+#define CLA_SRSTATE_2_SRACTIVE GENMASK(1, 0)
+#define CLA_SRSTATE_2_ADDR_MASK GENMASK(63, 6)
+
+#endif /* _ARM_CLA_REGS_H_ */
diff --git a/drivers/misc/arm-cla/arm-cla.h b/drivers/misc/arm-cla/arm-cla.h
new file mode 100644
index 000000000000..f265d7b60268
--- /dev/null
+++ b/drivers/misc/arm-cla/arm-cla.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Arm CLA driver - internal definitions
+ *
+ * Copyright 2026 Arm Limited.
+ */
+#ifndef _ARM_CLA_H_
+#define _ARM_CLA_H_
+
+#include <linux/types.h>
+
+#include "arm-cla-regs.h"
+
+/* Number of accelerators per CLA */
+#define CLA_NUM_ACC 8
+#define CLA_NUM_DATA_REGS 8
+#define CLA_SRSTATE_LEN 8
+
+/**
+ * struct cla_dev - CLA device
+ *
+ * Immutable state:
+ * @cpu: The CPU this CLA is attached to.
+ * @dev: The platform device.
+ */
+struct cla_dev {
+ unsigned int cpu;
+ struct device *dev;
+};
+
+#define cla_dbg(dev, fmt, ...) \
+ dev_dbg((dev)->dev, "[%u] " fmt, (dev)->cpu, ##__VA_ARGS__)
+#define cla_info(dev, fmt, ...) \
+ dev_info((dev)->dev, "[%u] " fmt, (dev)->cpu, ##__VA_ARGS__)
+#define cla_err(dev, fmt, ...) \
+ dev_err((dev)->dev, "[%u] " fmt, (dev)->cpu, ##__VA_ARGS__)
+
+#endif /* _ARM_CLA_H_ */
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v1 2/8] misc/arm-cla: Add launch operation helpers
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
2026-07-17 10:47 ` [RFC PATCH v1 1/8] misc/arm-cla: Add driver skeleton and documentation Ryan Roberts
@ 2026-07-17 10:47 ` Ryan Roberts
2026-07-17 12:16 ` Arnd Bergmann
2026-07-17 10:47 ` [RFC PATCH v1 3/8] misc/arm-cla: Probe firmware-described devices Ryan Roberts
` (6 subsequent siblings)
8 siblings, 1 reply; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 10:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: Ryan Roberts, linux-kernel, linux-arm-kernel, dri-devel,
linux-doc
From: Jean-Philippe Brucker <jpb@kernel.org>
CLA commands are issued by writing optional payload registers,
programming the LAUNCH register and polling LRESP until the hardware
accepts or rejects the operation.
Add a common launch helper that performs this sequence on the CLA's
local CPU, waits for LRESP completion and translates launch response
codes into Linux errors.
Build accelerator reset and register read and write support on top of
it. The register read and write helpers split larger accesses into
multiple launch operations when an access crosses an eight-register
window.
Signed-off-by: Jean-Philippe Brucker <jpb@kernel.org>
---
drivers/misc/arm-cla/Makefile | 6 +
drivers/misc/arm-cla/arm-cla.h | 21 +++
drivers/misc/arm-cla/cla-init.c | 12 ++
drivers/misc/arm-cla/cla-ops.c | 228 ++++++++++++++++++++++++++++++++
4 files changed, 267 insertions(+)
create mode 100644 drivers/misc/arm-cla/cla-init.c
create mode 100644 drivers/misc/arm-cla/cla-ops.c
diff --git a/drivers/misc/arm-cla/Makefile b/drivers/misc/arm-cla/Makefile
index a4e40e534e6a..3fa8567c8b3e 100644
--- a/drivers/misc/arm-cla/Makefile
+++ b/drivers/misc/arm-cla/Makefile
@@ -1 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
+
+arm-cla-y := \
+ cla-init.o \
+ cla-ops.o
+
+obj-$(CONFIG_ARM_CLA) += arm-cla.o
diff --git a/drivers/misc/arm-cla/arm-cla.h b/drivers/misc/arm-cla/arm-cla.h
index f265d7b60268..9294b71929f1 100644
--- a/drivers/misc/arm-cla/arm-cla.h
+++ b/drivers/misc/arm-cla/arm-cla.h
@@ -7,6 +7,8 @@
#ifndef _ARM_CLA_H_
#define _ARM_CLA_H_
+#include <linux/device.h>
+#include <linux/io.h>
#include <linux/types.h>
#include "arm-cla-regs.h"
@@ -21,10 +23,12 @@
*
* Immutable state:
* @cpu: The CPU this CLA is attached to.
+ * @regs: Registers accessed by the kernel.
* @dev: The platform device.
*/
struct cla_dev {
unsigned int cpu;
+ void __iomem *regs;
struct device *dev;
};
@@ -35,4 +39,21 @@ struct cla_dev {
#define cla_err(dev, fmt, ...) \
dev_err((dev)->dev, "[%u] " fmt, (dev)->cpu, ##__VA_ARGS__)
+static inline u64 cla_reg_read(struct cla_dev *dev, off_t reg)
+{
+ return readq_relaxed(dev->regs + reg);
+}
+
+static inline void cla_reg_write(struct cla_dev *dev, off_t reg, u64 val)
+{
+ return writeq_relaxed(val, dev->regs + reg);
+}
+
+int cla_op_wait_lresp(struct cla_dev *dev, u64 *lresp);
+int cla_op_reset(struct cla_dev *dev, unsigned int accid);
+int cla_op_regread(struct cla_dev *dev, unsigned int accid, unsigned int regidx,
+ size_t nregs, u64 *regs);
+int cla_op_regwrite(struct cla_dev *dev, unsigned int accid,
+ unsigned int regidx, size_t nregs, u64 *regs);
+
#endif /* _ARM_CLA_H_ */
diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-init.c
new file mode 100644
index 000000000000..3d1f47592842
--- /dev/null
+++ b/drivers/misc/arm-cla/cla-init.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Arm CLA driver - probing and initialization
+ *
+ * Copyright 2026 Arm Limited.
+ */
+
+#include <linux/module.h>
+
+MODULE_DESCRIPTION("Arm Core Local Accelerator");
+MODULE_AUTHOR("Arm Limited");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/arm-cla/cla-ops.c b/drivers/misc/arm-cla/cla-ops.c
new file mode 100644
index 000000000000..d594344a2eb4
--- /dev/null
+++ b/drivers/misc/arm-cla/cla-ops.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Arm CLA driver - Launch operations
+ *
+ * Copyright 2026 Arm Limited.
+ */
+
+#include <linux/iopoll.h>
+
+#include "arm-cla.h"
+
+/* Time to wait between two LRESP reads */
+#define CLA_LRESP_DELAY_US 1
+
+/*
+ * Time to wait for LRESP_PENDING to clear. Commands like REGREAD should
+ * complete in a few cycles, but ENTERSR and RESET may need to clean up
+ * some very large states depending on the work interrupted, and may need
+ * 1us or more.
+ */
+#define CLA_LRESP_TIMEOUT_US 100
+
+enum cla_launch_data_mode {
+ CLA_DATA_NONE,
+ CLA_DATA_IN,
+ CLA_DATA_OUT,
+};
+
+struct cla_launch {
+ u8 op; /* opcode */
+ u8 ndata_m1; /* Data size minus 1 */
+ u8 accid; /* Accelerator ID */
+ bool seq; /* part of compound cmd */
+ u32 regidx; /* Register index */
+
+ enum cla_launch_data_mode data_mode;
+ u64 *data; /* In/out data */
+
+ u8 errcode; /* Output error code */
+};
+
+/**
+ * cla_op_launch - Launch operation.
+ * @dev: CLA device.
+ * @launch: LAUNCH settings.
+ *
+ * 1. If data_mode is %CLA_DATA_IN, write DATA registers.
+ * 2. Launch the operation, and wait for the response.
+ * 3. If data_mode is %CLA_DATA_OUT, read DATA registers into @launch->data.
+ *
+ * Return:
+ * * %0 - Success.
+ * * %-ETIMEDOUT - LAUNCH timed out (possibly no CLA at this address).
+ * Unless LRESP_PENDING eventually clears, this is
+ * unrecoverable.
+ * * %-ENODEV - Accelerator not available.
+ * * %-EBUSY - Accelerator is busy.
+ * * %-EIO - LAUNCH error. @launch->errcode contains the LRESP
+ * error code.
+ */
+static int cla_op_launch(struct cla_dev *dev, struct cla_launch *launch)
+{
+ int i;
+ int ret;
+ u64 lresp;
+
+ if (WARN_ON(smp_processor_id() != dev->cpu))
+ return -EINVAL;
+
+ if (launch->data_mode == CLA_DATA_IN)
+ for (i = 0; i < launch->ndata_m1 + 1; i++)
+ cla_reg_write(dev, CLA_REG_DATA(i), launch->data[i]);
+
+ /*
+ * No barrier needed because accesses use Device-nGnRE, within the same
+ * memory-mapped peripheral, so accesses arrive at the endpoint in
+ * program order.
+ */
+ cla_reg_write(dev, CLA_REG_LAUNCH,
+ FIELD_PREP(CLA_LAUNCH_OP, launch->op) |
+ FIELD_PREP(CLA_LAUNCH_NDATA_M1, launch->ndata_m1) |
+ FIELD_PREP(CLA_LAUNCH_ACCID, launch->accid) |
+ FIELD_PREP(CLA_LAUNCH_SEQ, launch->seq) |
+ FIELD_PREP(CLA_LAUNCH_REGIDX, launch->regidx));
+
+ ret = cla_op_wait_lresp(dev, &lresp);
+ if (ret) {
+ cla_err(dev, "launch failed with %d\n", ret);
+ return ret;
+ }
+
+ switch (FIELD_GET(CLA_LRESP_CODE, lresp)) {
+ case CLA_LRESP_OK:
+ break;
+ case CLA_LRESP_UNAVAIL:
+ return -ENODEV;
+ case CLA_LRESP_BUSY:
+ return -EBUSY;
+ case CLA_LRESP_ERROR:
+ launch->errcode = FIELD_GET(CLA_LRESP_ERRCODE, lresp);
+ return -EIO;
+ }
+
+ if (launch->data_mode == CLA_DATA_OUT)
+ for (i = 0; i < launch->ndata_m1 + 1; i++)
+ launch->data[i] = cla_reg_read(dev, CLA_REG_DATA(i));
+
+ return 0;
+}
+
+/**
+ * cla_op_wait_lresp - Wait for any LAUNCH op to complete.
+ * @dev: CLA device.
+ * @lresp: last LRESP value read.
+ *
+ * Return: 0 on success, -ETIMEDOUT in case of timeout.
+ */
+int cla_op_wait_lresp(struct cla_dev *dev, u64 *lresp)
+{
+ return readq_relaxed_poll_timeout_atomic(dev->regs + CLA_REG_LRESP,
+ *lresp, FIELD_GET(CLA_LRESP_PENDING, *lresp) == 0,
+ CLA_LRESP_DELAY_US, CLA_LRESP_TIMEOUT_US);
+}
+
+/**
+ * cla_op_reset - Launch RESET operation for this accelerator.
+ * @dev: CLA device.
+ * @accid: accelerator ID.
+ *
+ * Return: 0 on success, 1 if there is no accelerator with this ID, or an error.
+ */
+int cla_op_reset(struct cla_dev *dev, unsigned int accid)
+{
+ int ret;
+ struct cla_launch launch = {
+ .op = CLA_LAUNCH_OP_RESET,
+ .accid = accid,
+ };
+
+ ret = cla_op_launch(dev, &launch);
+ if (ret == -EIO && launch.errcode == CLA_ERRCODE_NOACC)
+ return 1;
+ return ret;
+}
+
+static int cla_op_access_reg(struct cla_dev *dev, u8 op,
+ enum cla_launch_data_mode data_mode,
+ unsigned int accid, unsigned int regidx,
+ size_t nregs, u64 *regs)
+{
+ int ret = -EINVAL;
+ unsigned long max_regidx;
+ struct cla_launch launch = {
+ .op = op,
+ .accid = accid,
+ .data_mode = data_mode,
+ };
+
+ switch (op) {
+ case CLA_LAUNCH_OP_REGREAD:
+ case CLA_LAUNCH_OP_REGWRITE:
+ max_regidx = 0x100000000;
+ break;
+ default:
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ if (WARN_ON(regidx + nregs > max_regidx))
+ return -EINVAL;
+
+ /* 1 to 8 registers accessed at a time, within the same 8-reg group */
+ while (nregs > 0) {
+ unsigned int reg_group = ALIGN_DOWN(regidx, 8);
+ unsigned int max_reg = min(regidx + nregs, reg_group + 8);
+ unsigned int ndata = max_reg - regidx;
+
+ launch.ndata_m1 = ndata - 1;
+ launch.regidx = regidx;
+ launch.data = regs;
+
+ ret = cla_op_launch(dev, &launch);
+ if (ret)
+ break;
+
+ regidx += ndata;
+ regs += ndata;
+ nregs -= ndata;
+ }
+
+ return ret;
+}
+
+/**
+ * cla_op_regread - Launch REGREAD operations.
+ * @dev: CLA device.
+ * @accid: accelerator ID.
+ * @regidx: first register index.
+ * @nregs: number of registers. Can be greater than 8 (accessed with multiple
+ * REGREAD operations).
+ * @regs: array of length @nregs.
+ *
+ * Return: 0 on success, or an error.
+ */
+int cla_op_regread(struct cla_dev *dev, unsigned int accid,
+ unsigned int regidx, size_t nregs, u64 *regs)
+{
+ return cla_op_access_reg(dev, CLA_LAUNCH_OP_REGREAD, CLA_DATA_OUT,
+ accid, regidx, nregs, regs);
+}
+
+/**
+ * cla_op_regwrite - Launch REGWRITE operations.
+ * @dev: CLA device.
+ * @accid: accelerator ID.
+ * @regidx: first register index.
+ * @nregs: number of registers. Can be greater than 8 (accessed with multiple
+ * REGWRITE operations).
+ * @regs: array of length @nregs.
+ *
+ * Return: 0 on success, or an error.
+ */
+int cla_op_regwrite(struct cla_dev *dev, unsigned int accid,
+ unsigned int regidx, size_t nregs, u64 *regs)
+{
+ return cla_op_access_reg(dev, CLA_LAUNCH_OP_REGWRITE, CLA_DATA_IN,
+ accid, regidx, nregs, regs);
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [RFC PATCH v1 2/8] misc/arm-cla: Add launch operation helpers
2026-07-17 10:47 ` [RFC PATCH v1 2/8] misc/arm-cla: Add launch operation helpers Ryan Roberts
@ 2026-07-17 12:16 ` Arnd Bergmann
0 siblings, 0 replies; 16+ messages in thread
From: Arnd Bergmann @ 2026-07-17 12:16 UTC (permalink / raw)
To: Ryan Roberts, Greg Kroah-Hartman, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: linux-kernel, linux-arm-kernel, dri-devel, linux-doc
On Fri, Jul 17, 2026, at 12:47, Ryan Roberts wrote:
> From: Jean-Philippe Brucker <jpb@kernel.org>
>
> CLA commands are issued by writing optional payload registers,
> programming the LAUNCH register and polling LRESP until the hardware
> accepts or rejects the operation.
>
> Add a common launch helper that performs this sequence on the CLA's
> local CPU, waits for LRESP completion and translates launch response
> codes into Linux errors.
>
> Build accelerator reset and register read and write support on top of
> it. The register read and write helpers split larger accesses into
> multiple launch operations when an access crosses an eight-register
> window.
I'm a bit confused by the MMIO register access ordering, if this is
not a normal AXI attached device with a DMA master, I think it
would make sense to better document what it is.
> +static inline u64 cla_reg_read(struct cla_dev *dev, off_t reg)
> +{
> + return readq_relaxed(dev->regs + reg);
> +}
> +
> +static inline void cla_reg_write(struct cla_dev *dev, off_t reg, u64
> val)
> +{
> + return writeq_relaxed(val, dev->regs + reg);
> +}
For regular devices that have a DMA master, you cannot use
the relaxed operations by default since they do not serialize
against DMA transfers.
To do this properly, you'd have to define separate cla_reg_read()
and cla_reg_read_relaxed() helpers and then use them as needed,
ideally with a comment for each relaxed instance to explain why
that one is both performance critical and safe.
If for some reason this accelerator is not a DMA master (e.g.
because it is implemented through CPU microcode and accesses
the memory through the CPU's own load/store unit), that should
be documented here to explain that you are relying on
implementation defined behavior outside of the normal driver
and memory model.
> + /*
> + * No barrier needed because accesses use Device-nGnRE, within the
> same
> + * memory-mapped peripheral, so accesses arrive at the endpoint in
> + * program order.
> + */
This comment in turn looks completely useless, as that is true
for any MMIO device. The only barriers that you'd normally need
here on sane architectures (not Alpha) are to serialize MMIO
against DMA.
> +
> + if (launch->data_mode == CLA_DATA_OUT)
> + for (i = 0; i < launch->ndata_m1 + 1; i++)
> + launch->data[i] = cla_reg_read(dev, CLA_REG_DATA(i));
Instead of the open-coded loop, maybe this can be built
on top of __iowrite32_copy()
> +/**
> + * cla_op_wait_lresp - Wait for any LAUNCH op to complete.
> +int cla_op_wait_lresp(struct cla_dev *dev, u64 *lresp)
> +{
> + return readq_relaxed_poll_timeout_atomic(dev->regs + CLA_REG_LRESP,
> + *lresp, FIELD_GET(CLA_LRESP_PENDING, *lresp) == 0,
> + CLA_LRESP_DELAY_US, CLA_LRESP_TIMEOUT_US);
Similarly, the readq_relaxed_poll_timeout_atomic() specifically
does not wait for DMA, so you may need separate helpers for
devices that can do DMA and readq_poll_timeout_atomic() vs devices
that never access memory and can use the relaxed version.
You may also need a non-atomic version, as blocking the CPU
for 100µs is not great for realtime workloads.
Arnd
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v1 3/8] misc/arm-cla: Probe firmware-described devices
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
2026-07-17 10:47 ` [RFC PATCH v1 1/8] misc/arm-cla: Add driver skeleton and documentation Ryan Roberts
2026-07-17 10:47 ` [RFC PATCH v1 2/8] misc/arm-cla: Add launch operation helpers Ryan Roberts
@ 2026-07-17 10:47 ` Ryan Roberts
2026-07-17 12:25 ` Arnd Bergmann
2026-07-17 10:47 ` [RFC PATCH v1 4/8] misc/arm-cla: Initialize devices on CPU bringup Ryan Roberts
` (5 subsequent siblings)
8 siblings, 1 reply; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 10:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: Ryan Roberts, linux-kernel, linux-arm-kernel, dri-devel,
linux-doc
From: Jean-Philippe Brucker <jpb@kernel.org>
Discover CLA devices from firmware and instantiate the driver with
platform_driver_probe(), since the set of devices is fixed at boot. Map
the MMIO frame, select the kernel-accessible privilege-level frame and
associate each CLA with the CPU named by firmware.
Add the topology code used to group CLAs into domains. Domains represent
the smallest unit that can be assigned independently, because CLAs in
the same domain cannot be isolated from each other. Build lookup tables
indexed by CPU and by mmap page offset for later fast access.
Register CPU hotplug callbacks so that hardware setup and teardown can
run on the CPU local to each CLA, which is the only CPU allowed to
access that CLA's registers.
Co-developed-by: Ryan Roberts <ryan.roberts@arm.com>
Signed-off-by: Ryan Roberts <ryan.roberts@arm.com>
Signed-off-by: Jean-Philippe Brucker <jpb@kernel.org>
---
drivers/misc/arm-cla/Makefile | 3 +-
drivers/misc/arm-cla/arm-cla.h | 46 ++++++
drivers/misc/arm-cla/cla-init.c | 211 ++++++++++++++++++++++++++++
drivers/misc/arm-cla/cla-topology.c | 178 +++++++++++++++++++++++
4 files changed, 437 insertions(+), 1 deletion(-)
create mode 100644 drivers/misc/arm-cla/cla-topology.c
diff --git a/drivers/misc/arm-cla/Makefile b/drivers/misc/arm-cla/Makefile
index 3fa8567c8b3e..84ed1a054a21 100644
--- a/drivers/misc/arm-cla/Makefile
+++ b/drivers/misc/arm-cla/Makefile
@@ -2,6 +2,7 @@
arm-cla-y := \
cla-init.o \
- cla-ops.o
+ cla-ops.o \
+ cla-topology.o
obj-$(CONFIG_ARM_CLA) += arm-cla.o
diff --git a/drivers/misc/arm-cla/arm-cla.h b/drivers/misc/arm-cla/arm-cla.h
index 9294b71929f1..1853daa200e4 100644
--- a/drivers/misc/arm-cla/arm-cla.h
+++ b/drivers/misc/arm-cla/arm-cla.h
@@ -18,6 +18,8 @@
#define CLA_NUM_DATA_REGS 8
#define CLA_SRSTATE_LEN 8
+struct cla_domain;
+
/**
* struct cla_dev - CLA device
*
@@ -25,13 +27,41 @@
* @cpu: The CPU this CLA is attached to.
* @regs: Registers accessed by the kernel.
* @dev: The platform device.
+ * @pfn: Page of registers assigned to user.
+ * @pg_offset: Mmap offset of this device.
+ * @domain: The domain this CLA belongs to.
*/
struct cla_dev {
unsigned int cpu;
void __iomem *regs;
struct device *dev;
+ unsigned long pfn;
+ unsigned long pg_offset;
+ struct cla_domain *domain;
+};
+
+/**
+ * struct cla_domain - Collection of cla_dev
+ *
+ * Immutable state:
+ * @id: Domain identifier, from FW or generated.
+ * @pg_offset: Mmap offset of the first device.
+ * @nr_devs: Number of devices in the domain.
+ * @devs: Devices.
+ */
+struct cla_domain {
+ unsigned int id;
+ unsigned long pg_offset;
+ unsigned int nr_devs;
+ struct cla_dev **devs;
};
+extern struct xarray cla_domains;
+extern unsigned int cla_nr_domains;
+extern struct cla_dev **cla_lut_cpu;
+extern struct cla_dev **cla_lut_pg;
+extern unsigned int cla_nr_devs;
+
#define cla_dbg(dev, fmt, ...) \
dev_dbg((dev)->dev, "[%u] " fmt, (dev)->cpu, ##__VA_ARGS__)
#define cla_info(dev, fmt, ...) \
@@ -39,6 +69,13 @@ struct cla_dev {
#define cla_err(dev, fmt, ...) \
dev_err((dev)->dev, "[%u] " fmt, (dev)->cpu, ##__VA_ARGS__)
+#define CLA_REG_SIZE SZ_64K
+#define CLA_FRAME_SIZE (4 * CLA_REG_SIZE)
+
+/* Return the registers corresponding to this privilege level */
+#define cla_get_regs(base, pl) \
+ ((typeof(base))((uintptr_t)(base) + (pl) * CLA_REG_SIZE))
+
static inline u64 cla_reg_read(struct cla_dev *dev, off_t reg)
{
return readq_relaxed(dev->regs + reg);
@@ -49,6 +86,15 @@ static inline void cla_reg_write(struct cla_dev *dev, off_t reg, u64 val)
return writeq_relaxed(val, dev->regs + reg);
}
+/*
+ * If we're at EL2, use PL2. If we're a guest or nVHE host, use PL1.
+ */
+#define cla_kernel_pl (is_kernel_in_hyp_mode() ? 2 : 1)
+
+struct cla_domain *cla_dev_domain_get(struct cla_dev *dev);
+int cla_domains_finalise(void);
+void cla_domains_free(void);
+
int cla_op_wait_lresp(struct cla_dev *dev, u64 *lresp);
int cla_op_reset(struct cla_dev *dev, unsigned int accid);
int cla_op_regread(struct cla_dev *dev, unsigned int accid, unsigned int regidx,
diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-init.c
index 3d1f47592842..731ca08c9b79 100644
--- a/drivers/misc/arm-cla/cla-init.c
+++ b/drivers/misc/arm-cla/cla-init.c
@@ -5,7 +5,218 @@
* Copyright 2026 Arm Limited.
*/
+#include <linux/cpuhotplug.h>
#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/smp.h>
+
+#include <asm/virt.h>
+
+#include "arm-cla.h"
+
+static int cla_cpuhp_state = -1;
+
+static int cla_dev_setup(unsigned int cpu)
+{
+ struct cla_dev *dev;
+
+ dev = cla_lut_cpu[cpu];
+ if (!dev)
+ return 0;
+
+ if (WARN_ON(smp_processor_id() != cpu || dev->cpu != cpu))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int cla_dev_teardown(unsigned int cpu)
+{
+ struct cla_dev *dev;
+
+ /*
+ * Careful what we return here, the teardown path isn't really allowed
+ * to fail (BUG_ON in kernel/cpu.c)
+ */
+ dev = cla_lut_cpu[cpu];
+ if (!dev)
+ return 0;
+
+ return 0;
+}
+
+static int cla_of_to_cpu(struct device_node *of_node)
+{
+ int cpu;
+ int ret;
+ u32 cpu_phandle;
+ struct device_node *cpu_node;
+
+ if (!of_node)
+ return -ENODEV;
+
+ ret = of_property_read_u32(of_node, "cpu", &cpu_phandle);
+ if (WARN_ON(ret))
+ return -EINVAL;
+
+ cpu_node = of_find_node_by_phandle(cpu_phandle);
+ if (WARN_ON(!cpu_node))
+ return -EINVAL;
+
+ cpu = of_cpu_node_to_id(cpu_node);
+ of_node_put(cpu_node);
+
+ return cpu;
+}
+
+static struct cla_dev *cla_dev_alloc(struct device *parent, int cpu,
+ void __iomem *regs, phys_addr_t base)
+{
+ struct cla_dev *dev;
+
+ dev = devm_kzalloc(parent, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return ERR_PTR(-ENOMEM);
+
+ dev->pfn = __phys_to_pfn(base);
+ dev->regs = cla_get_regs(regs, cla_kernel_pl);
+ dev->cpu = cpu;
+ dev->dev = parent;
+
+ /* Attempt to find device domain, or allocate a new one */
+ dev->domain = cla_dev_domain_get(dev);
+ if (IS_ERR(dev->domain))
+ return ERR_CAST(dev->domain);
+
+ cla_nr_devs++;
+
+ return dev;
+}
+
+static int cla_probe(struct platform_device *pdev)
+{
+ int cpu;
+ void __iomem *reg;
+ size_t reg_size;
+ struct cla_dev *dev;
+ struct resource *res;
+
+ /*
+ * TODO: the firmware maps this as well to access PL3, and the guest
+ * will map PL1 and PL0. Avoid TLB attr mismatches by only mapping what
+ * we need.
+ */
+ reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(reg)) {
+ dev_err(&pdev->dev, "could not map CLA registers\n");
+ return PTR_ERR(reg);
+ }
+
+ if (!IS_ALIGNED(res->start, SZ_256K)) {
+ dev_err(&pdev->dev, "invalid CLA registers alignment\n");
+ return -EINVAL;
+ }
+
+ reg_size = resource_size(res);
+ if (reg_size <= CLA_FRAME_SIZE) {
+ /* A single CLA. We need information about its CPU. */
+ cpu = cla_of_to_cpu(pdev->dev.of_node);
+ if (cpu < 0)
+ return cpu;
+
+ /*
+ * As a guest we may not get PL3 or PL2. Tolerate CLAs smaller
+ * than 4*regs.
+ */
+ if (reg_size < (cla_kernel_pl + 1) * CLA_REG_SIZE)
+ return -ENXIO;
+
+ dev = cla_dev_alloc(&pdev->dev, cpu, reg, res->start);
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ dev_dbg(&pdev->dev, "CLA found %pa size 0x%llx\n", &res->start,
+ resource_size(res));
+
+ } else {
+ dev_err(&pdev->dev, "unexpected CLA registers size\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id cla_of_match[] = {
+ {.compatible = "arm,cla",},
+ {},
+};
+MODULE_DEVICE_TABLE(of, cla_of_match);
+
+static struct platform_driver cla_driver = {
+ .driver = {
+ .name = "arm-cla",
+ .of_match_table = cla_of_match,
+ },
+ .probe = cla_probe,
+};
+
+static int __init cla_module_init(void)
+{
+ int ret;
+
+ /*
+ * CPUs may be hotplugged, but all CLAs are described by firmware so the
+ * probe can be synchronous. This only sets up the resources, and CPUHP
+ * callbacks will do the actual peeking and poking.
+ *
+ * This returns an error when no CLA is present.
+ */
+ ret = platform_driver_probe(&cla_driver, cla_probe);
+ if (ret) {
+ if (ret != -ENODEV)
+ pr_err("arm-cla: probe failed with %d\n", ret);
+ /* Some domains may have been created during probe */
+ goto err_domains_free;
+ }
+
+ ret = cla_domains_finalise();
+ if (ret) {
+ pr_err("arm-cla: failed to finalise domains: %d", ret);
+ goto err_domains_free;
+ }
+
+ /*
+ * Each CPU initializes their own CLA. CPUHP uses a pair of smp_mb()
+ * when calling the startup callback, ensuring that cla_dev_setup()
+ * reads fully initialized cla_lut_cpu and cla_dev structures.
+ */
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "arm-cla",
+ cla_dev_setup, cla_dev_teardown);
+ if (ret < 0) {
+ pr_err("arm-cla: failed to setup cpuhp: %d", ret);
+ goto err_driver_unregister;
+ }
+ cla_cpuhp_state = ret;
+
+ return 0;
+
+err_driver_unregister:
+ platform_driver_unregister(&cla_driver);
+err_domains_free:
+ cla_domains_free();
+ return ret;
+}
+
+static void __exit cla_module_exit(void)
+{
+ cpuhp_remove_state(cla_cpuhp_state);
+ platform_driver_unregister(&cla_driver);
+ cla_domains_free();
+}
+
+module_init(cla_module_init);
+module_exit(cla_module_exit);
MODULE_DESCRIPTION("Arm Core Local Accelerator");
MODULE_AUTHOR("Arm Limited");
diff --git a/drivers/misc/arm-cla/cla-topology.c b/drivers/misc/arm-cla/cla-topology.c
new file mode 100644
index 000000000000..402c228e197c
--- /dev/null
+++ b/drivers/misc/arm-cla/cla-topology.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm CLA driver - device topology initialization
+ *
+ * A CLA domain is a group of devices that work together and cannot be isolated
+ * from each other. They are owned by a single user at a time.
+ *
+ * Copyright 2026 Arm Limited.
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+
+#include "arm-cla.h"
+
+DEFINE_XARRAY_ALLOC(cla_domains);
+unsigned int cla_nr_domains;
+
+/*
+ * Some CPUs may not have a CLA. So cla_lut_cpu (indexed by CPU) may be sparse,
+ * but cla_lut_pg (indexed by page offset in mmap'ed file) is a contiguous array
+ * of all devices, of size cla_nr_devs, sorted by domain.
+ */
+struct cla_dev **cla_lut_cpu;
+struct cla_dev **cla_lut_pg;
+unsigned int cla_nr_devs;
+
+static struct cla_domain *cla_domain_alloc(struct cla_dev *dev, unsigned int id)
+{
+ int ret;
+ struct cla_domain *domain;
+
+ domain = kzalloc_obj(*domain);
+ if (!domain)
+ return ERR_PTR(-ENOMEM);
+
+ domain->id = id;
+ ret = xa_insert(&cla_domains, id, domain, GFP_KERNEL);
+ if (ret < 0)
+ goto err_free_domain;
+
+ domain->nr_devs = 1;
+ domain->devs = kzalloc_obj(*domain->devs);
+ if (!domain->devs) {
+ ret = -ENOMEM;
+ goto err_free_id;
+ }
+ domain->devs[0] = dev;
+
+ return domain;
+
+err_free_id:
+ xa_erase(&cla_domains, domain->id);
+err_free_domain:
+ kfree(domain);
+ return ERR_PTR(ret);
+}
+
+/**
+ * cla_dev_domain_get - get or create a CLA domain for a device
+ * @dev: CLA device
+ *
+ * Return: CLA domain pointer on success, or an ERR_PTR() on failure.
+ */
+struct cla_domain *cla_dev_domain_get(struct cla_dev *dev)
+{
+ int ret;
+ unsigned int domain_id;
+ struct cla_domain *domain;
+
+ /* Domain ID is provided by firmware */
+ ret = of_property_read_u32(dev->dev->of_node, "domain", &domain_id);
+ if (WARN_ON(ret))
+ return ERR_PTR(-EINVAL);
+
+ domain = xa_load(&cla_domains, domain_id);
+ if (domain) {
+ domain->nr_devs++;
+ domain->devs = krealloc_array(domain->devs, domain->nr_devs,
+ sizeof(*domain->devs), GFP_KERNEL);
+ if (!domain->devs)
+ return ERR_PTR(-ENOMEM);
+ domain->devs[domain->nr_devs - 1] = dev;
+ return domain;
+ }
+
+ domain = cla_domain_alloc(dev, domain_id);
+ if (IS_ERR(domain))
+ return domain;
+
+ cla_nr_domains = max(domain_id + 1, cla_nr_domains);
+
+ return domain;
+}
+
+/**
+ * cla_domains_finalise - build CLA device lookup tables
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int __init cla_domains_finalise(void)
+{
+ int ret = -ENOMEM;
+ unsigned int i, j;
+ unsigned int pg_offset = 0;
+
+ cla_lut_cpu = kzalloc_objs(*cla_lut_cpu, nr_cpu_ids);
+ if (!cla_lut_cpu)
+ goto err_free;
+
+ cla_lut_pg = kzalloc_objs(*cla_lut_pg, cla_nr_devs);
+ if (!cla_lut_pg)
+ goto err_free;
+
+ ret = -EINVAL;
+
+ /* Populate the lookup tables. */
+ for (i = 0; i < cla_nr_domains; i++) {
+ struct cla_domain *domain = xa_load(&cla_domains, i);
+
+ /* The user API requires sequential domain IDs */
+ if (WARN_ON(!domain))
+ goto err_free;
+
+ domain->pg_offset = pg_offset;
+
+ for (j = 0; j < domain->nr_devs; j++) {
+ struct cla_dev *dev = domain->devs[j];
+
+ if (WARN_ON(dev->cpu >= nr_cpu_ids) ||
+ WARN_ON(pg_offset >= cla_nr_devs))
+ goto err_free;
+ WARN_ON(cla_lut_cpu[dev->cpu]);
+ WARN_ON(cla_lut_pg[pg_offset]);
+
+ cla_lut_cpu[dev->cpu] = dev;
+ cla_lut_pg[pg_offset] = dev;
+ dev->pg_offset = pg_offset;
+ pg_offset++;
+ }
+ }
+
+ return 0;
+
+err_free:
+ kfree(cla_lut_cpu);
+ kfree(cla_lut_pg);
+ cla_lut_cpu = NULL;
+ cla_lut_pg = NULL;
+ return ret;
+}
+
+static void cla_domain_free(struct cla_domain *domain)
+{
+ kfree(domain->devs);
+ xa_erase(&cla_domains, domain->id);
+ kfree(domain);
+}
+
+/**
+ * cla_domains_free - free CLA domains and lookup tables
+ */
+void cla_domains_free(void)
+{
+ unsigned long id;
+ struct cla_domain *domain;
+
+ kfree(cla_lut_cpu);
+ kfree(cla_lut_pg);
+ cla_lut_cpu = NULL;
+ cla_lut_pg = NULL;
+ xa_for_each(&cla_domains, id, domain)
+ cla_domain_free(domain);
+ cla_nr_domains = 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [RFC PATCH v1 3/8] misc/arm-cla: Probe firmware-described devices
2026-07-17 10:47 ` [RFC PATCH v1 3/8] misc/arm-cla: Probe firmware-described devices Ryan Roberts
@ 2026-07-17 12:25 ` Arnd Bergmann
2026-07-17 12:36 ` Ryan Roberts
0 siblings, 1 reply; 16+ messages in thread
From: Arnd Bergmann @ 2026-07-17 12:25 UTC (permalink / raw)
To: Ryan Roberts, Greg Kroah-Hartman, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: linux-kernel, linux-arm-kernel, dri-devel, linux-doc
On Fri, Jul 17, 2026, at 12:47, Ryan Roberts wrote:
> +
> +static const struct of_device_id cla_of_match[] = {
> + {.compatible = "arm,cla",},
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, cla_of_match);
This is missing a device tree binding, and probably needs to versioned
in some form.
Arnd
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [RFC PATCH v1 3/8] misc/arm-cla: Probe firmware-described devices
2026-07-17 12:25 ` Arnd Bergmann
@ 2026-07-17 12:36 ` Ryan Roberts
0 siblings, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 12:36 UTC (permalink / raw)
To: Arnd Bergmann, Greg Kroah-Hartman, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: linux-kernel, linux-arm-kernel, dri-devel, linux-doc
On 17/07/2026 13:25, Arnd Bergmann wrote:
> On Fri, Jul 17, 2026, at 12:47, Ryan Roberts wrote:
>> +
>> +static const struct of_device_id cla_of_match[] = {
>> + {.compatible = "arm,cla",},
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(of, cla_of_match);
>
> This is missing a device tree binding, and probably needs to versioned
> in some form.
Given the aims of the RFC, I didn't think this would be a blocker for now - of
course it's on the list for if/when we get further along the road.
Thanks,
Ryan
>
> Arnd
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v1 4/8] misc/arm-cla: Initialize devices on CPU bringup
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
` (2 preceding siblings ...)
2026-07-17 10:47 ` [RFC PATCH v1 3/8] misc/arm-cla: Probe firmware-described devices Ryan Roberts
@ 2026-07-17 10:47 ` Ryan Roberts
2026-07-17 10:47 ` [RFC PATCH v1 5/8] misc/arm-cla: Accelerator context save and restore Ryan Roberts
` (4 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 10:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: Ryan Roberts, linux-kernel, linux-arm-kernel, dri-devel,
linux-doc
From: Jean-Philippe Brucker <jpb@kernel.org>
CLA registers are only accessible from the CPU local to the CLA, so
perform device initialization from the CPU hotplug online callback.
Clear the DATA registers, reset each attached accelerator and verify
that it returns to the expected idle state. Record the set of usable
accelerators and expose only those through PL0CTRL, and through PL1CTRL
when the kernel is running at EL2.
Reset optional timestamp and PMU controls where available so userspace
starts from a predictable state, but do not fail probing if those
optional controls cannot be programmed.
On CPU teardown, reset all known accelerators and mark the device broken
if reset fails, since the hotplug teardown path cannot safely fail.
Signed-off-by: Jean-Philippe Brucker <jpb@kernel.org>
---
drivers/misc/arm-cla/arm-cla.h | 14 +++
drivers/misc/arm-cla/cla-init.c | 178 ++++++++++++++++++++++++++++++++
drivers/misc/arm-cla/cla-ops.c | 19 ++++
3 files changed, 211 insertions(+)
diff --git a/drivers/misc/arm-cla/arm-cla.h b/drivers/misc/arm-cla/arm-cla.h
index 1853daa200e4..57d0acd01d3a 100644
--- a/drivers/misc/arm-cla/arm-cla.h
+++ b/drivers/misc/arm-cla/arm-cla.h
@@ -28,16 +28,25 @@ struct cla_domain;
* @regs: Registers accessed by the kernel.
* @dev: The platform device.
* @pfn: Page of registers assigned to user.
+ * @accelerators: Available accelerators.
* @pg_offset: Mmap offset of this device.
* @domain: The domain this CLA belongs to.
+ *
+ * Mutable, only accessed under @lock:
+ * @lock: Protects the following members.
+ * @broken: Hardware failure.
*/
struct cla_dev {
unsigned int cpu;
void __iomem *regs;
struct device *dev;
unsigned long pfn;
+ u8 accelerators;
unsigned long pg_offset;
struct cla_domain *domain;
+
+ struct mutex lock;
+ bool broken;
};
/**
@@ -62,6 +71,10 @@ extern struct cla_dev **cla_lut_cpu;
extern struct cla_dev **cla_lut_pg;
extern unsigned int cla_nr_devs;
+#define cla_for_each_accid(dev, accid) \
+ for ((accid) = 0; (accid) < CLA_NUM_ACC; (accid)++) \
+ for_each_if((dev)->accelerators & BIT(accid))
+
#define cla_dbg(dev, fmt, ...) \
dev_dbg((dev)->dev, "[%u] " fmt, (dev)->cpu, ##__VA_ARGS__)
#define cla_info(dev, fmt, ...) \
@@ -97,6 +110,7 @@ void cla_domains_free(void);
int cla_op_wait_lresp(struct cla_dev *dev, u64 *lresp);
int cla_op_reset(struct cla_dev *dev, unsigned int accid);
+int cla_op_reset_all(struct cla_dev *dev);
int cla_op_regread(struct cla_dev *dev, unsigned int accid, unsigned int regidx,
size_t nregs, u64 *regs);
int cla_op_regwrite(struct cla_dev *dev, unsigned int accid,
diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-init.c
index 731ca08c9b79..ba7552a2d886 100644
--- a/drivers/misc/arm-cla/cla-init.c
+++ b/drivers/misc/arm-cla/cla-init.c
@@ -17,18 +17,192 @@
static int cla_cpuhp_state = -1;
+static int cla_reset_ts(struct cla_dev *dev, unsigned int accid)
+{
+ int ret;
+ u64 reg;
+
+ ret = cla_op_regread(dev, accid, CLA_REG_ACAP, 1, ®);
+ if (ret)
+ return ret;
+ if (!FIELD_GET(CLA_ACAP_TS, reg))
+ return 0;
+
+ /*
+ * Disable TS control from userspace, provide TS from CNTP. If we do
+ * have to provide a timer to userspace or a virtual offset to a guest,
+ * we'll need to make sure we have access to both TSCTRLOWNER and
+ * TSOFFOWNER. For now best effort.
+ */
+ reg = FIELD_PREP(CLA_TSCTRLOWNER_PL, cla_kernel_pl);
+ ret = cla_op_regwrite(dev, accid, CLA_REG_TSCTRLOWNER, 1, ®);
+ if (!ret) {
+ reg = FIELD_PREP(CLA_TSCTRL_TS, CLA_TSCTRL_PHYSICAL);
+ ret = cla_op_regwrite(dev, accid, CLA_REG_TSCTRL, 1, ®);
+ if (ret)
+ return ret;
+ }
+
+ reg = FIELD_PREP(CLA_TSOFFOWNER_PL, cla_kernel_pl);
+ ret = cla_op_regread(dev, accid, CLA_REG_TSOFFOWNER, 1, ®);
+ if (!ret) {
+ reg = 0;
+ ret = cla_op_regwrite(dev, accid, CLA_REG_TSVOFF, 1, ®);
+ if (ret)
+ return ret;
+ ret = cla_op_regwrite(dev, accid, CLA_REG_TSPOFF, 1, ®);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int cla_reset_pmu(struct cla_dev *dev, unsigned int accid)
+{
+ int ret;
+ u64 reg;
+
+ /* Disable PMU access */
+ reg = FIELD_PREP(CLA_PMUOWNER_PL, cla_kernel_pl);
+ ret = cla_op_regwrite(dev, accid, CLA_REG_PMUOWNER, 1, ®);
+ if (!ret) {
+ reg = 0;
+ ret = cla_op_regwrite(dev, accid, CLA_REG_PMURESET, 1, ®);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * Return: 0 on success, 1 if the accelerator is not attached or not usable, or
+ * an error
+ */
+static int cla_dev_setup_accel(struct cla_dev *dev, unsigned int accid)
+{
+ u64 status;
+ int ret;
+
+ /*
+ * Probe and reset. Return 1 if no accelerator is attached, happy days.
+ * If the accelerator is unavailable (masked by higher PL with PLxCTRL),
+ * return an error. Individual accelerators cannot be owned by a higher
+ * PL, since the MTC is shared between all accelerators attached to this
+ * CLA.
+ *
+ * Some accelerators will be masked due to returning 1 further down this
+ * function. If we end up with no dev->accelerators because of that we
+ * won't setup the MTC, but as long as this reset succeeds, the
+ * accelerator is not issuing memory transactions.
+ */
+ ret = cla_op_reset(dev, accid);
+ if (ret)
+ return ret;
+
+ status = cla_reg_read(dev, CLA_REG_STATUS(accid));
+ if ((status & CLA_STATUS_STATE_MASK) != CLA_STATUS_STATE_IDLE) {
+ cla_err(dev, "unexpected status 0x%llx for accelerator %d\n",
+ status, accid);
+ return -EIO;
+ }
+
+ /*
+ * The following are nice to have, but the accelerator should work
+ * without them.
+ */
+ ret = cla_reset_ts(dev, accid);
+ if (ret)
+ cla_err(dev, "[%u] could not reset TS: %d\n", accid, ret);
+
+ ret = cla_reset_pmu(dev, accid);
+ if (ret)
+ cla_err(dev, "[%u] could not reset PMU: %d\n", accid, ret);
+
+ return 0;
+}
+
+/* Clean the device before releasing it */
+static void cla_dev_reinit(struct cla_dev *dev)
+{
+ int i;
+ bool broken;
+
+ mutex_lock(&dev->lock);
+ broken = dev->broken;
+ mutex_unlock(&dev->lock);
+ if (broken)
+ return;
+
+ if (WARN_ON(cla_op_reset_all(dev))) {
+ mutex_lock(&dev->lock);
+ dev->broken = true;
+ mutex_unlock(&dev->lock);
+ return;
+ }
+
+ if (is_kernel_in_hyp_mode())
+ cla_reg_write(dev, CLA_REG_PL1CTRL, ~0ULL);
+ cla_reg_write(dev, CLA_REG_PL0CTRL, ~0ULL);
+ for (i = 0; i < CLA_NUM_DATA_REGS; i++)
+ cla_reg_write(dev, CLA_REG_DATA(i), 0);
+ cla_reg_write(dev, CLA_REG_LRESP, 0);
+}
+
static int cla_dev_setup(unsigned int cpu)
{
+ int i;
+ int ret;
+ bool broken;
+ unsigned int accid;
struct cla_dev *dev;
+ u64 plxctrl_val = 0;
dev = cla_lut_cpu[cpu];
if (!dev)
return 0;
+ mutex_lock(&dev->lock);
+ broken = dev->broken;
+ mutex_unlock(&dev->lock);
+ if (broken)
+ return 0;
+
if (WARN_ON(smp_processor_id() != cpu || dev->cpu != cpu))
return -EINVAL;
+ /* Clear DATA and LRESP_DATANZ */
+ for (i = 0; i < CLA_NUM_DATA_REGS; i++)
+ cla_reg_write(dev, CLA_REG_DATA(i), 0);
+
+ /*
+ * Reset all accelerators. We restrict PLxCTRL to the accelerators that
+ * are attached and well behaved.
+ */
+ for (accid = 0; accid < CLA_NUM_ACC; accid++) {
+ ret = cla_dev_setup_accel(dev, accid);
+ if (ret > 0)
+ continue;
+ else if (ret < 0)
+ goto err;
+
+ dev->accelerators |= (1 << accid);
+ plxctrl_val |= CLA_PLxCTRL_PREP(accid,
+ FIELD_PREP(CLA_PLxCTRL_AVAIL, 1));
+ }
+
+ if (is_kernel_in_hyp_mode())
+ cla_reg_write(dev, CLA_REG_PL1CTRL, plxctrl_val);
+
+ cla_reg_write(dev, CLA_REG_PL0CTRL, plxctrl_val);
+
+ if (dev->accelerators)
+ cla_info(dev, "available accelerators: 0x%02x\n",
+ dev->accelerators);
+
return 0;
+err:
+ cla_dev_reinit(dev);
+ return ret;
}
static int cla_dev_teardown(unsigned int cpu)
@@ -43,6 +217,8 @@ static int cla_dev_teardown(unsigned int cpu)
if (!dev)
return 0;
+ cla_dev_reinit(dev);
+
return 0;
}
@@ -84,6 +260,8 @@ static struct cla_dev *cla_dev_alloc(struct device *parent, int cpu,
dev->cpu = cpu;
dev->dev = parent;
+ mutex_init(&dev->lock);
+
/* Attempt to find device domain, or allocate a new one */
dev->domain = cla_dev_domain_get(dev);
if (IS_ERR(dev->domain))
diff --git a/drivers/misc/arm-cla/cla-ops.c b/drivers/misc/arm-cla/cla-ops.c
index d594344a2eb4..f9e5570e89c1 100644
--- a/drivers/misc/arm-cla/cla-ops.c
+++ b/drivers/misc/arm-cla/cla-ops.c
@@ -143,6 +143,25 @@ int cla_op_reset(struct cla_dev *dev, unsigned int accid)
return ret;
}
+/**
+ * cla_op_reset_all - Reset all attached accelerators
+ * @dev: CLA device.
+ *
+ * Return: 0 on success, or an error
+ */
+int cla_op_reset_all(struct cla_dev *dev)
+{
+ int ret;
+ unsigned int accid;
+
+ cla_for_each_accid(dev, accid) {
+ ret = cla_op_reset(dev, accid);
+ if (ret)
+ return ret < 0 ? ret : -ENODEV;
+ }
+ return 0;
+}
+
static int cla_op_access_reg(struct cla_dev *dev, u8 op,
enum cla_launch_data_mode data_mode,
unsigned int accid, unsigned int regidx,
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v1 5/8] misc/arm-cla: Accelerator context save and restore
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
` (3 preceding siblings ...)
2026-07-17 10:47 ` [RFC PATCH v1 4/8] misc/arm-cla: Initialize devices on CPU bringup Ryan Roberts
@ 2026-07-17 10:47 ` Ryan Roberts
2026-07-17 10:47 ` [RFC PATCH v1 6/8] misc/arm-cla: Set up memory translation context Ryan Roberts
` (3 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 10:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: Ryan Roberts, linux-kernel, linux-arm-kernel, dri-devel,
linux-doc
From: Jean-Philippe Brucker <jpb@kernel.org>
Add helpers to save and restore CLA context for later domain switching.
The saved state includes DATA registers, LRESP, per-accelerator SRSTATE
and any implementation-defined internal accelerator state exposed
through the IAS register window.
Use ENTERSR to stop each accelerator and collect SRSTATE, then read the
advertised internal register state with REGREAD. Restore by entering SR
mode from a reset, idle accelerator, writing the saved internal state
with REGWRITE and completing the transition with EXITSR.
Reject accelerators that require SAVE/RESTORE operations during setup,
since that path needs a DMA buffer for state storage. We don't have any
accelerators that support this anyway. Allocate per-device save areas
for all CLAs in a domain, sized from the combined IASSIZE discovered at
setup time.
Signed-off-by: Jean-Philippe Brucker <jpb@kernel.org>
---
drivers/misc/arm-cla/Makefile | 1 +
drivers/misc/arm-cla/arm-cla.h | 27 ++++
drivers/misc/arm-cla/cla-init.c | 22 +++
drivers/misc/arm-cla/cla-ops.c | 43 ++++++
drivers/misc/arm-cla/cla-regs.c | 263 ++++++++++++++++++++++++++++++++
5 files changed, 356 insertions(+)
create mode 100644 drivers/misc/arm-cla/cla-regs.c
diff --git a/drivers/misc/arm-cla/Makefile b/drivers/misc/arm-cla/Makefile
index 84ed1a054a21..c1b14155454d 100644
--- a/drivers/misc/arm-cla/Makefile
+++ b/drivers/misc/arm-cla/Makefile
@@ -3,6 +3,7 @@
arm-cla-y := \
cla-init.o \
cla-ops.o \
+ cla-regs.o \
cla-topology.o
obj-$(CONFIG_ARM_CLA) += arm-cla.o
diff --git a/drivers/misc/arm-cla/arm-cla.h b/drivers/misc/arm-cla/arm-cla.h
index 57d0acd01d3a..f1c6d509c3fe 100644
--- a/drivers/misc/arm-cla/arm-cla.h
+++ b/drivers/misc/arm-cla/arm-cla.h
@@ -30,6 +30,7 @@ struct cla_domain;
* @pfn: Page of registers assigned to user.
* @accelerators: Available accelerators.
* @pg_offset: Mmap offset of this device.
+ * @iassizes: Combined regstate of all accels.
* @domain: The domain this CLA belongs to.
*
* Mutable, only accessed under @lock:
@@ -43,6 +44,7 @@ struct cla_dev {
unsigned long pfn;
u8 accelerators;
unsigned long pg_offset;
+ unsigned long iassizes;
struct cla_domain *domain;
struct mutex lock;
@@ -65,6 +67,23 @@ struct cla_domain {
struct cla_dev **devs;
};
+/**
+ * struct cla_regs - Saved CLA register state
+ *
+ * @data: DATA registers.
+ * @lresp: LRESP register.
+ * @accel_valid: Accelerator state has been saved once.
+ * @srstate: Save/restore state for each accelerator.
+ * @regstate: Internal accelerator state.
+ */
+struct cla_regs {
+ u64 data[CLA_NUM_DATA_REGS];
+ u64 lresp;
+ bool accel_valid;
+ u64 srstate[CLA_NUM_ACC][CLA_SRSTATE_LEN];
+ u64 regstate[];
+};
+
extern struct xarray cla_domains;
extern unsigned int cla_nr_domains;
extern struct cla_dev **cla_lut_cpu;
@@ -115,5 +134,13 @@ int cla_op_regread(struct cla_dev *dev, unsigned int accid, unsigned int regidx,
size_t nregs, u64 *regs);
int cla_op_regwrite(struct cla_dev *dev, unsigned int accid,
unsigned int regidx, size_t nregs, u64 *regs);
+int cla_op_entersr(struct cla_dev *dev, unsigned int accid, u64 *srstate);
+int cla_op_exitsr(struct cla_dev *dev, unsigned int accid, u64 *srstate);
+
+int cla_regs_switch_out(struct cla_dev *dev, struct cla_regs *regs,
+ bool save_regs);
+int cla_regs_switch_in(struct cla_dev *dev, struct cla_regs *regs);
+struct cla_regs **cla_regs_alloc_domain(struct cla_domain *domain);
+void cla_regs_free_domain(struct cla_domain *domain, struct cla_regs **regs);
#endif /* _ARM_CLA_H_ */
diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-init.c
index ba7552a2d886..189ab2a139f1 100644
--- a/drivers/misc/arm-cla/cla-init.c
+++ b/drivers/misc/arm-cla/cla-init.c
@@ -81,6 +81,8 @@ static int cla_reset_pmu(struct cla_dev *dev, unsigned int accid)
static int cla_dev_setup_accel(struct cla_dev *dev, unsigned int accid)
{
u64 status;
+ u64 iassize;
+ u64 acap;
int ret;
/*
@@ -106,6 +108,26 @@ static int cla_dev_setup_accel(struct cla_dev *dev, unsigned int accid)
return -EIO;
}
+ /*
+ * We don't support SROP (SAVE and RESTORE ops), only context switching
+ * with REGREAD and REGWRITE. SROP would require finding a DMA buffer
+ * where to save state, ideally in userspace process to avoid kernel
+ * DMA. It's complicated and no implementation needs it at the moment.
+ */
+ ret = cla_op_regread(dev, accid, CLA_REG_ACAP, 1, &acap);
+ if (ret)
+ return ret;
+ if (FIELD_GET(CLA_ACAP_SROP, acap)) {
+ cla_err(dev, "[%u] SROP not supported\n", accid);
+ return 1;
+ }
+
+ ret = cla_op_regread(dev, accid, CLA_REG_IASSIZE, 1, &iassize);
+ if (ret)
+ return ret;
+ if (FIELD_GET(CLA_ACAP_REGSTATE, acap) && iassize)
+ dev->iassizes += iassize;
+
/*
* The following are nice to have, but the accelerator should work
* without them.
diff --git a/drivers/misc/arm-cla/cla-ops.c b/drivers/misc/arm-cla/cla-ops.c
index f9e5570e89c1..7ddb973927e9 100644
--- a/drivers/misc/arm-cla/cla-ops.c
+++ b/drivers/misc/arm-cla/cla-ops.c
@@ -245,3 +245,46 @@ int cla_op_regwrite(struct cla_dev *dev, unsigned int accid,
return cla_op_access_reg(dev, CLA_LAUNCH_OP_REGWRITE, CLA_DATA_IN,
accid, regidx, nregs, regs);
}
+
+/**
+ * cla_op_entersr - launch ENTERSR operation.
+ * @dev: CLA device.
+ * @accid: accelerator ID.
+ * @srstate: 64-byte save/restore state written by CLA. May be NULL if the
+ * SRSTATE isn't needed.
+ *
+ * Return: 0 on success, or an error.
+ */
+int cla_op_entersr(struct cla_dev *dev, unsigned int accid, u64 *srstate)
+{
+ struct cla_launch launch = {
+ .op = CLA_LAUNCH_OP_ENTERSR,
+ .ndata_m1 = 7,
+ .accid = accid,
+ .data_mode = srstate ? CLA_DATA_OUT : CLA_DATA_NONE,
+ .data = srstate,
+ };
+
+ return cla_op_launch(dev, &launch);
+}
+
+/**
+ * cla_op_exitsr - launch EXITSR operation.
+ * @dev: CLA device.
+ * @accid: accelerator ID.
+ * @srstate: 64-byte save/restore state written to CLA.
+ *
+ * Return: 0 on success, or an error.
+ */
+int cla_op_exitsr(struct cla_dev *dev, unsigned int accid, u64 *srstate)
+{
+ struct cla_launch launch = {
+ .op = CLA_LAUNCH_OP_EXITSR,
+ .ndata_m1 = 7,
+ .accid = accid,
+ .data_mode = CLA_DATA_IN,
+ .data = srstate,
+ };
+
+ return cla_op_launch(dev, &launch);
+}
diff --git a/drivers/misc/arm-cla/cla-regs.c b/drivers/misc/arm-cla/cla-regs.c
new file mode 100644
index 000000000000..7507796cd551
--- /dev/null
+++ b/drivers/misc/arm-cla/cla-regs.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Arm CLA driver - save/restore accelerator context
+ *
+ * Copyright 2026 Arm Limited.
+ */
+
+#include <linux/io.h>
+
+#include "arm-cla.h"
+
+static int cla_regs_save_accel(struct cla_dev *dev, unsigned int accid,
+ struct cla_regs *regs, off_t *regstate_off)
+{
+ int ret;
+ u64 status;
+ size_t regstate_size;
+ u64 *srstate = regs->srstate[accid];
+
+ ret = cla_op_entersr(dev, accid, srstate);
+ if (ret) {
+ /*
+ * Note that we don't expect the accelerator to return an error.
+ * Implementations that don't support context switch cancel the
+ * work and write an error in SRSTATE_STATUS, but don't fail
+ * ENTERSR.
+ */
+ return ret;
+ }
+
+ status = cla_reg_read(dev, CLA_REG_STATUS(accid));
+ if ((status & CLA_STATUS_STATE_MASK) != CLA_STATUS_STATE_SRMODE) {
+ cla_err(dev, "unexpected SR status 0x%llx\n", status);
+ return -EIO;
+ }
+
+ /*
+ * A device that supports SROP fails device probe. Others should not
+ * report SROP here, we don't support it.
+ */
+ WARN_ON(FIELD_GET(CLA_SRSTATE_0_SROP, srstate[0]));
+
+ regstate_size = FIELD_GET(CLA_SRSTATE_0_REGSTATE, srstate[0]);
+ if (WARN_ON(*regstate_off + regstate_size * 8 > dev->iassizes))
+ return -ENOSPC;
+
+ if (regstate_size) {
+ ret = cla_op_regread(dev, accid, CLA_REG_IASn, regstate_size,
+ regs->regstate + *regstate_off);
+ if (ret) {
+ cla_err(dev, "failed to save regstate: %d\n", ret);
+ return ret;
+ }
+ }
+
+ *regstate_off += regstate_size;
+
+ return 0;
+}
+
+/*
+ * Errors are very unlikely, but if they happen the device is left in SRMODE.
+ */
+static int cla_regs_restore_accel(struct cla_dev *dev, unsigned int accid,
+ struct cla_regs *regs, off_t *regstate_off)
+{
+ int ret;
+ u64 status;
+ size_t regstate_size;
+ u64 *srstate = regs->srstate[accid];
+
+ /*
+ * The accelerator was reset and isn't in SRMODE. Later we could support
+ * coming directly from cla_regs_save_accel() in SRMODE, but at the
+ * moment we always need a RESET.
+ */
+ status = cla_reg_read(dev, CLA_REG_STATUS(accid));
+ if ((status & CLA_STATUS_STATE_MASK) != CLA_STATUS_STATE_IDLE) {
+ cla_err(dev, "unexpected status 0x%llx\n", status);
+ return -EIO;
+ }
+
+ ret = cla_op_entersr(dev, accid, NULL);
+ if (ret)
+ return ret;
+
+ WARN_ON(FIELD_GET(CLA_SRSTATE_0_SROP, srstate[0]));
+
+ regstate_size = FIELD_GET(CLA_SRSTATE_0_REGSTATE, srstate[0]);
+ if (WARN_ON(*regstate_off + regstate_size * 8 > dev->iassizes))
+ return -ENOSPC;
+
+ if (regstate_size) {
+ ret = cla_op_regwrite(dev, accid, CLA_REG_IASn, regstate_size,
+ regs->regstate + *regstate_off);
+ if (ret) {
+ cla_err(dev, "failed to restore regstate: %d\n", ret);
+ return ret;
+ }
+ }
+
+ *regstate_off += regstate_size;
+
+ return cla_op_exitsr(dev, accid, srstate);
+}
+
+/**
+ * cla_regs_switch_out - Save CLA context and reset all accelerators
+ * @dev: CLA device
+ * @regs: CLA register state to save
+ * @save_regs: whether to save the DATA and accelerator state
+ *
+ * When this completes, all accelerators are idle. If this fails, some
+ * accelerators may still be running.
+ *
+ * Return: 0 on success, or an error
+ */
+int cla_regs_switch_out(struct cla_dev *dev, struct cla_regs *regs,
+ bool save_regs)
+{
+ int i;
+ int ret;
+ unsigned int accid;
+ off_t regstate_off = 0;
+
+ /*
+ * When we interrupt the user process in the middle of launching a
+ * command, we have to wait for LRESP_PENDING to clear before we can
+ * launch a new command or save the DATA registers.
+ */
+ ret = cla_op_wait_lresp(dev, ®s->lresp);
+ if (ret)
+ return ret;
+
+ if (save_regs) {
+ for (i = 0; i < CLA_NUM_DATA_REGS; i++)
+ regs->data[i] = cla_reg_read(dev, CLA_REG_DATA(i));
+
+ cla_for_each_accid(dev, accid) {
+ ret = cla_regs_save_accel(dev, accid, regs, ®state_off);
+ if (ret)
+ return ret;
+ }
+
+ regs->accel_valid = true;
+ }
+
+ /*
+ * "If the accelerator is non idle when ENTERSR is launched, then a
+ * RESET operation is required after the internal accelerator state has
+ * been saved"
+ *
+ * However some accelerators keep stale internal state and caches even
+ * after job completion, so always RESET for now.
+ */
+ return cla_op_reset_all(dev);
+}
+
+/**
+ * cla_regs_switch_in - Restore CLA context
+ * @dev: CLA device
+ * @regs: CLA register state to restore
+ *
+ * Restore the DATA and LRESP registers, and accelerator state if one has been
+ * saved. This function is called with all accelerators idle and no trace of
+ * previous work.
+ *
+ * Return: 0 on success, or an error
+ */
+int cla_regs_switch_in(struct cla_dev *dev, struct cla_regs *regs)
+{
+ int i;
+ int ret;
+ unsigned int accid;
+ off_t regstate_off = 0;
+
+ cla_for_each_accid(dev, accid) {
+ if (regs->accel_valid) {
+ ret = cla_regs_restore_accel(dev, accid, regs, ®state_off);
+ if (ret)
+ return ret;
+ /*
+ * At this point we must not read STATUS because that
+ * would clear EVENT. Userspace always gets a spurious
+ * EVENT on restore, because we have no way to
+ * save/restore EVENT and userspace missing an event
+ * would be worse than getting a spurious one.
+ */
+ } else {
+ /*
+ * If this context has never been scheduled, then we
+ * just clean the CLA regs. Also clear EVENT by reading
+ * STATUS, to provide a pristine context.
+ */
+ cla_reg_read(dev, CLA_REG_STATUS(accid));
+ }
+ }
+
+ for (i = 0; i < CLA_NUM_DATA_REGS; i++)
+ cla_reg_write(dev, CLA_REG_DATA(i), regs->data[i]);
+ cla_reg_write(dev, CLA_REG_LRESP, regs->lresp);
+
+ return 0;
+}
+
+/**
+ * cla_regs_alloc_domain - Allocate register state for a CLA domain
+ * @domain: CLA domain
+ *
+ * Allocate register state to save and restore every device in @domain.
+ *
+ * Return: an array of register state pointers on success, %NULL on failure.
+ */
+struct cla_regs **cla_regs_alloc_domain(struct cla_domain *domain)
+{
+ int i;
+ size_t size;
+ struct cla_regs **regs_ptrs;
+
+ regs_ptrs = kmalloc_objs(*regs_ptrs, domain->nr_devs,
+ GFP_KERNEL_ACCOUNT);
+ if (!regs_ptrs)
+ return NULL;
+
+ for (i = 0; i < domain->nr_devs; i++) {
+ struct cla_regs *regs;
+ struct cla_dev *dev = domain->devs[i];
+
+ /*
+ * The regs structures are only ever accessed from the CLA
+ * device's CPU, so try to allocate them on the right NUMA node
+ */
+ size = sizeof(*regs) + dev->iassizes;
+ regs = kvzalloc_node(size, GFP_KERNEL_ACCOUNT,
+ cpu_to_node(dev->cpu));
+ if (!regs)
+ goto err_free;
+
+ regs_ptrs[i] = regs;
+ }
+
+ return regs_ptrs;
+
+err_free:
+ for (i--; i >= 0; i--)
+ kvfree(regs_ptrs[i]);
+ kfree(regs_ptrs);
+ return NULL;
+}
+
+/**
+ * cla_regs_free_domain - Free register state for a CLA domain
+ * @domain: CLA domain
+ * @regs: array of register state pointers to free
+ */
+void cla_regs_free_domain(struct cla_domain *domain, struct cla_regs **regs)
+{
+ int i;
+
+ for (i = 0; i < domain->nr_devs; i++)
+ kvfree(regs[i]);
+ kfree(regs);
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v1 6/8] misc/arm-cla: Set up memory translation context
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
` (4 preceding siblings ...)
2026-07-17 10:47 ` [RFC PATCH v1 5/8] misc/arm-cla: Accelerator context save and restore Ryan Roberts
@ 2026-07-17 10:47 ` Ryan Roberts
2026-07-17 10:47 ` [RFC PATCH v1 7/8] misc/arm-cla: Manage domain contexts Ryan Roberts
` (2 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 10:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: Ryan Roberts, linux-kernel, linux-arm-kernel, dri-devel,
linux-doc
CLA accelerators use a Memory Translation Context to describe the
page-table and memory-system configuration used for DMA through the CPU
MMU. Set up the kernel-owned MTC during CLA bringup so later context
switches only need to replace the user TTBR0 value.
Populate the MTC from the CPU EL1 configuration, including SCTLR, MAIR,
TCR and TCR2 where present. Configure the accelerator to run as EL0, put
the ASID in TTBR0 and disable TTBR1 translations because the driver only
installs user contexts in the TTBR0 range. When running at EL2, also
program the host HCR state.
Add SETCTX and GETCTX launch helpers for MTC register access. Use the
first available accelerator ID for these operations, since the MTC is
shared by the CLA but the operation still requires a valid accelerator
ID.
Clear the MTC on CPU teardown, and add helpers to install and uninstall
a user translation context by updating TTBR0 with the process page
table, ASID and CnP state.
Co-developed-by: Jean-Philippe Brucker <jpb@kernel.org>
Signed-off-by: Jean-Philippe Brucker <jpb@kernel.org>
Signed-off-by: Ryan Roberts <ryan.roberts@arm.com>
---
drivers/misc/arm-cla/Makefile | 1 +
drivers/misc/arm-cla/arm-cla.h | 9 +++
drivers/misc/arm-cla/cla-init.c | 10 ++-
drivers/misc/arm-cla/cla-mtc.c | 139 ++++++++++++++++++++++++++++++++
drivers/misc/arm-cla/cla-ops.c | 52 ++++++++++++
5 files changed, 209 insertions(+), 2 deletions(-)
create mode 100644 drivers/misc/arm-cla/cla-mtc.c
diff --git a/drivers/misc/arm-cla/Makefile b/drivers/misc/arm-cla/Makefile
index c1b14155454d..df3ad7e4a1b4 100644
--- a/drivers/misc/arm-cla/Makefile
+++ b/drivers/misc/arm-cla/Makefile
@@ -2,6 +2,7 @@
arm-cla-y := \
cla-init.o \
+ cla-mtc.o \
cla-ops.o \
cla-regs.o \
cla-topology.o
diff --git a/drivers/misc/arm-cla/arm-cla.h b/drivers/misc/arm-cla/arm-cla.h
index f1c6d509c3fe..ffdb82e5e09c 100644
--- a/drivers/misc/arm-cla/arm-cla.h
+++ b/drivers/misc/arm-cla/arm-cla.h
@@ -134,6 +134,10 @@ int cla_op_regread(struct cla_dev *dev, unsigned int accid, unsigned int regidx,
size_t nregs, u64 *regs);
int cla_op_regwrite(struct cla_dev *dev, unsigned int accid,
unsigned int regidx, size_t nregs, u64 *regs);
+int cla_op_setctx(struct cla_dev *dev, unsigned int regidx, size_t nregs,
+ u64 *regs);
+int cla_op_getctx(struct cla_dev *dev, unsigned int regidx, size_t nregs,
+ u64 *regs);
int cla_op_entersr(struct cla_dev *dev, unsigned int accid, u64 *srstate);
int cla_op_exitsr(struct cla_dev *dev, unsigned int accid, u64 *srstate);
@@ -143,4 +147,9 @@ int cla_regs_switch_in(struct cla_dev *dev, struct cla_regs *regs);
struct cla_regs **cla_regs_alloc_domain(struct cla_domain *domain);
void cla_regs_free_domain(struct cla_domain *domain, struct cla_regs **regs);
+int cla_mtc_setup(struct cla_dev *dev);
+int cla_mtc_clear(struct cla_dev *dev);
+int cla_mtc_install(struct cla_dev *dev, pgd_t *pgd, unsigned long asid);
+int cla_mtc_uninstall(struct cla_dev *dev);
+
#endif /* _ARM_CLA_H_ */
diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-init.c
index 189ab2a139f1..0451d97bc95b 100644
--- a/drivers/misc/arm-cla/cla-init.c
+++ b/drivers/misc/arm-cla/cla-init.c
@@ -155,7 +155,8 @@ static void cla_dev_reinit(struct cla_dev *dev)
if (broken)
return;
- if (WARN_ON(cla_op_reset_all(dev))) {
+ if (WARN_ON(cla_op_reset_all(dev)) ||
+ WARN_ON(cla_mtc_clear(dev))) {
mutex_lock(&dev->lock);
dev->broken = true;
mutex_unlock(&dev->lock);
@@ -217,9 +218,14 @@ static int cla_dev_setup(unsigned int cpu)
cla_reg_write(dev, CLA_REG_PL0CTRL, plxctrl_val);
- if (dev->accelerators)
+ if (dev->accelerators) {
+ ret = cla_mtc_setup(dev);
+ if (ret)
+ goto err;
+
cla_info(dev, "available accelerators: 0x%02x\n",
dev->accelerators);
+ }
return 0;
err:
diff --git a/drivers/misc/arm-cla/cla-mtc.c b/drivers/misc/arm-cla/cla-mtc.c
new file mode 100644
index 000000000000..0f786d5061da
--- /dev/null
+++ b/drivers/misc/arm-cla/cla-mtc.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Arm CLA driver - memory translation context
+ *
+ * Copyright 2026 Arm Limited.
+ */
+
+#include <asm/kvm_arm.h>
+
+#include "arm-cla.h"
+
+#define cla_kernel_mtc \
+ (cla_kernel_pl == 2 ? CLA_MTC_REGIDX_PL2 : CLA_MTC_REGIDX_PL1)
+
+/**
+ * cla_mtc_setup - initialize the memory translation context
+ * @dev: CLA device
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int cla_mtc_setup(struct cla_dev *dev)
+{
+ int ret;
+ u64 tcr;
+ u64 regs[2 * CLA_MTC_PL_SIZE] = {};
+ u64 *kregs = ®s[cla_kernel_mtc];
+ size_t regs_size = cla_kernel_pl * CLA_MTC_PL_SIZE;
+ u64 reserved_ttbr = phys_to_ttbr(page_to_phys(ZERO_PAGE(0)));
+
+ /* The accelerator always runs as EL0 */
+ kregs[CLA_MTC_PSTATE] = FIELD_PREP(CLA_MTC_PSTATE_EL, 0) |
+ FIELD_PREP(CLA_MTC_PSTATE_PAN,
+ system_uses_hw_pan());
+ kregs[CLA_MTC_TTBR0] = reserved_ttbr;
+ kregs[CLA_MTC_TTBR1] = reserved_ttbr;
+ kregs[CLA_MTC_SCTLR] = read_sysreg(sctlr_el1);
+ kregs[CLA_MTC_MAIR] = read_sysreg(mair_el1);
+
+ if (cpus_have_final_cap(ARM64_HAS_TCR2))
+ kregs[CLA_MTC_TCR2] = read_sysreg_s(SYS_TCR2_EL1);
+
+ tcr = read_sysreg(tcr_el1);
+ /*
+ * Put ASID in TTBR0, so that we only have one reg to update when
+ * switching context.
+ */
+ FIELD_MODIFY(TCR_EL1_A1, &tcr, 0);
+ /*
+ * We never map anything in the TTBR1 VA range so explicitly disable
+ * translations via TTBR1.
+ */
+ FIELD_MODIFY(TCR_EL1_EPD1_MASK, &tcr, 1);
+ kregs[CLA_MTC_TCR] = tcr;
+
+ if (cla_kernel_pl == 2) {
+ /* E2H and TGE */
+ regs[CLA_MTC_HCR_EL2] = HCR_HOST_VHE_FLAGS;
+ }
+
+ /*
+ * All accelerators are idle, meaning there can't be any memory
+ * transactions happening. So it is safe to setup the MTC in any order.
+ * i.e. there is no issue with setting SCTLR.M=1 before we have
+ * configured MAIR or TCR2.
+ */
+ ret = cla_op_setctx(dev, CLA_MTC_REGIDX_PL1, regs_size, regs);
+ if (ret) {
+ WARN_ON(cla_mtc_clear(dev));
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * cla_mtc_clear - clear the memory translation context
+ * @dev: CLA device
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int cla_mtc_clear(struct cla_dev *dev)
+{
+ /* Don't set the RES1 bit in VTCR_EL2 because in MTC they are RAZ/WI. */
+ u64 regs[2 * CLA_MTC_PL_SIZE] = {};
+ size_t regs_size = cla_kernel_pl * CLA_MTC_PL_SIZE;
+
+ /* SETCTX fails if there is no ACCID */
+ if (!dev->accelerators)
+ return 0;
+
+ /*
+ * All accelerators are idle, meaning there can't be any memory
+ * transactions happening. So it is safe to clear the MTC in any order.
+ * i.e. there is no issue with clearing TTBRx before setting SCTLR.M=0.
+ */
+ return cla_op_setctx(dev, CLA_MTC_REGIDX_PL1, regs_size, regs);
+}
+
+/**
+ * cla_mtc_install - enable a user translation context
+ * @dev: CLA device
+ * @pgd: page table to install
+ * @asid: ASID to use with the page table
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int cla_mtc_install(struct cla_dev *dev, pgd_t *pgd, unsigned long asid)
+{
+ u64 ttbr0;
+
+ /* SETCTX fails if there is no ACCID */
+ if (!dev->accelerators)
+ return 0;
+
+ ttbr0 = phys_to_ttbr(virt_to_phys(pgd)) |
+ FIELD_PREP(TTBRx_EL1_ASID_MASK, asid);
+
+ if (system_supports_cnp())
+ ttbr0 |= TTBRx_EL1_CnP;
+
+ return cla_op_setctx(dev, cla_kernel_mtc + CLA_MTC_TTBR0, 1, &ttbr0);
+}
+
+/**
+ * cla_mtc_uninstall - disable the user translation context
+ * @dev: CLA device
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int cla_mtc_uninstall(struct cla_dev *dev)
+{
+ u64 ttbr0 = phys_to_ttbr(page_to_phys(ZERO_PAGE(0)));
+
+ /* SETCTX fails if there is no ACCID */
+ if (!dev->accelerators)
+ return 0;
+
+ return cla_op_setctx(dev, cla_kernel_mtc + CLA_MTC_TTBR0, 1, &ttbr0);
+}
diff --git a/drivers/misc/arm-cla/cla-ops.c b/drivers/misc/arm-cla/cla-ops.c
index 7ddb973927e9..4d3f4f453e0b 100644
--- a/drivers/misc/arm-cla/cla-ops.c
+++ b/drivers/misc/arm-cla/cla-ops.c
@@ -180,6 +180,10 @@ static int cla_op_access_reg(struct cla_dev *dev, u8 op,
case CLA_LAUNCH_OP_REGWRITE:
max_regidx = 0x100000000;
break;
+ case CLA_LAUNCH_OP_SETCTX:
+ case CLA_LAUNCH_OP_GETCTX:
+ max_regidx = 0x80;
+ break;
default:
WARN_ON(1);
return -EINVAL;
@@ -246,6 +250,54 @@ int cla_op_regwrite(struct cla_dev *dev, unsigned int accid,
accid, regidx, nregs, regs);
}
+/**
+ * cla_op_setctx - launch SETCTX operation.
+ * @dev: CLA device.
+ * @regidx: first MTC register index.
+ * @nregs: number of registers to set. Can be greater than 8 (accessed with
+ * multiple SETCTX operations).
+ * @regs: register content, array of length @nregs.
+ *
+ * Return: 0 on success, or an error.
+ */
+int cla_op_setctx(struct cla_dev *dev, unsigned int regidx, size_t nregs,
+ u64 *regs)
+{
+ /*
+ * Even though the MTC is shared between all accelerators, accid must be
+ * valid. If no accelerator is attached, the op fails.
+ */
+ if (!dev->accelerators)
+ return -ENODEV;
+
+ return cla_op_access_reg(dev, CLA_LAUNCH_OP_SETCTX, CLA_DATA_IN,
+ __ffs(dev->accelerators), regidx, nregs, regs);
+}
+
+/**
+ * cla_op_getctx - launch GETCTX operation.
+ * @dev: CLA device.
+ * @regidx: first MTC register index.
+ * @nregs: number of registers to set. Can be greater than 8 (accessed with
+ * multiple GETCTX operations).
+ * @regs: register content, array of length @nregs.
+ *
+ * Return: 0 on success, or an error.
+ */
+int cla_op_getctx(struct cla_dev *dev, unsigned int regidx, size_t nregs,
+ u64 *regs)
+{
+ /*
+ * Even though the MTC is shared between all accelerators, accid must be
+ * valid. If no accelerator is attached, the op fails.
+ */
+ if (!dev->accelerators)
+ return -ENODEV;
+
+ return cla_op_access_reg(dev, CLA_LAUNCH_OP_GETCTX, CLA_DATA_OUT,
+ __ffs(dev->accelerators), regidx, nregs, regs);
+}
+
/**
* cla_op_entersr - launch ENTERSR operation.
* @dev: CLA device.
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v1 7/8] misc/arm-cla: Manage domain contexts
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
` (5 preceding siblings ...)
2026-07-17 10:47 ` [RFC PATCH v1 6/8] misc/arm-cla: Set up memory translation context Ryan Roberts
@ 2026-07-17 10:47 ` Ryan Roberts
2026-07-17 10:47 ` [RFC PATCH v1 8/8] misc/arm-cla: Add userspace interface Ryan Roberts
2026-07-17 11:33 ` [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Will Deacon
8 siblings, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 10:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: Ryan Roberts, linux-kernel, linux-arm-kernel, dri-devel,
linux-doc
A CLA domain cannot be used by multiple address spaces concurrently
because its devices share a memory translation context and may
communicate without isolation. Represent each user of a domain with a
context keyed by the mm_struct and device file description, and track
contexts in a per-domain hash table.
Add a domain scheduler that queues contexts for assignment and gives
each one a fixed time slice. Before switching, revoke the outgoing
context's mappings from all VMAs for the device file. Then save its
accelerator state and remove its MTC, before installing the incoming
context's MTC and restoring the incoming accelerator state.
Run hardware operations through CPU-bound kthread workers, since each
CLA can only be accessed from its local CPU. Coordinate the per-device
workers with a domain worker so that every CLA in the domain is switched
as a single assignment unit.
Defer context deassignment and destruction when the final mapping
disappears to avoid taking mmap_lock in the wrong order. Keep the mm and
file alive until the context has been removed from hardware and
reclaimed.
Treat a failure on any device as a failure of the whole domain. Stop
further assignments and wake all waiters when a domain becomes unusable.
Co-developed-by: Jean-Philippe Brucker <jpb@kernel.org>
Signed-off-by: Jean-Philippe Brucker <jpb@kernel.org>
Signed-off-by: Ryan Roberts <ryan.roberts@arm.com>
---
drivers/misc/arm-cla/Makefile | 2 +
drivers/misc/arm-cla/arm-cla.h | 128 ++++++++
drivers/misc/arm-cla/cla-ctx.c | 142 +++++++++
drivers/misc/arm-cla/cla-init.c | 48 +++
drivers/misc/arm-cla/cla-sched.c | 474 ++++++++++++++++++++++++++++
drivers/misc/arm-cla/cla-topology.c | 9 +
6 files changed, 803 insertions(+)
create mode 100644 drivers/misc/arm-cla/cla-ctx.c
create mode 100644 drivers/misc/arm-cla/cla-sched.c
diff --git a/drivers/misc/arm-cla/Makefile b/drivers/misc/arm-cla/Makefile
index df3ad7e4a1b4..a92c3594e5c9 100644
--- a/drivers/misc/arm-cla/Makefile
+++ b/drivers/misc/arm-cla/Makefile
@@ -2,9 +2,11 @@
arm-cla-y := \
cla-init.o \
+ cla-ctx.o \
cla-mtc.o \
cla-ops.o \
cla-regs.o \
+ cla-sched.o \
cla-topology.o
obj-$(CONFIG_ARM_CLA) += arm-cla.o
diff --git a/drivers/misc/arm-cla/arm-cla.h b/drivers/misc/arm-cla/arm-cla.h
index ffdb82e5e09c..041d4a8d7e91 100644
--- a/drivers/misc/arm-cla/arm-cla.h
+++ b/drivers/misc/arm-cla/arm-cla.h
@@ -7,9 +7,21 @@
#ifndef _ARM_CLA_H_
#define _ARM_CLA_H_
+#include <linux/atomic.h>
+#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
+#include <linux/kref.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/mm_types.h>
+#include <linux/mutex.h>
+#include <linux/rhashtable-types.h>
+#include <linux/rwsem.h>
#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
#include "arm-cla-regs.h"
@@ -18,8 +30,21 @@
#define CLA_NUM_DATA_REGS 8
#define CLA_SRSTATE_LEN 8
+/* Quantum of CLA assignment */
+#define CLA_SLICE_MS 100
+
struct cla_domain;
+struct cla_call_on_cpu {
+ int ret;
+ struct {
+ struct cla_ctx *prev_ctx;
+ struct cla_ctx *next_ctx;
+ unsigned int ctx_id;
+ } sched;
+ struct kthread_work switch_ctx;
+};
+
/**
* struct cla_dev - CLA device
*
@@ -36,6 +61,9 @@ struct cla_domain;
* Mutable, only accessed under @lock:
* @lock: Protects the following members.
* @broken: Hardware failure.
+ * @worker: CPU-bound worker to communicate with CLA.
+ * @worker_sem: Serialize running @call against @worker destruction.
+ * @call: Scheduling work.
*/
struct cla_dev {
unsigned int cpu;
@@ -49,22 +77,50 @@ struct cla_dev {
struct mutex lock;
bool broken;
+ struct kthread_worker *worker;
+ struct rw_semaphore worker_sem;
+ struct cla_call_on_cpu call;
};
/**
* struct cla_domain - Collection of cla_dev
*
+ * The whole domain is assigned to a single cla_ctx at a time.
+ *
* Immutable state:
* @id: Domain identifier, from FW or generated.
* @pg_offset: Mmap offset of the first device.
* @nr_devs: Number of devices in the domain.
* @devs: Devices.
+ *
+ * Mutable, only accessed under @lock:
+ * @lock: Protects the following members.
+ * @ctxs: All live contexts, keyed on mm_struct and file ptr.
+ * @queued_ctxs: Queue of contexts waiting for assignment.
+ * @dying_ctxs: Contexts waiting for reclaim.
+ * @worker: Kthread worker to coordinate reassignment.
+ * @reassign: Delayed work that switches contexts with time slicing.
+ * @reclaim: Work to release and free contexts (after reassignment).
+ *
+ * Mutable, some reads outside the lock:
+ * @broken: Hardware failure in any device in the domain.
+ * @assigned_ctx: Context to which domain is currently assigned.
*/
struct cla_domain {
unsigned int id;
unsigned long pg_offset;
unsigned int nr_devs;
struct cla_dev **devs;
+
+ struct mutex lock;
+ bool broken;
+ struct rhashtable ctxs;
+ struct list_head queued_ctxs;
+ struct list_head dying_ctxs;
+ struct cla_ctx *assigned_ctx;
+ struct kthread_worker *worker;
+ struct kthread_delayed_work reassign;
+ struct kthread_delayed_work reclaim;
};
/**
@@ -84,6 +140,46 @@ struct cla_regs {
u64 regstate[];
};
+struct cla_ctx_key {
+ struct mm_struct *mm;
+ struct file *file;
+};
+
+/**
+ * struct cla_ctx - Domain context
+ *
+ * Immutable state:
+ * @domain: The domain of this context.
+ * @key: Key in cla_domain::ctxs hashtable.
+ * @node: Node in cla_domain::ctxs hashtable.
+ *
+ * Mutable, protected by domain::lock:
+ * @refcnt: Current users of this context.
+ * @queue_node: Node in cla_domain::queued_ctxs or
+ * cla_domain::dying_ctxs.
+ * @waitq: Faulting threads sleep until assignment.
+ *
+ * Mutable, protected by domain::lock, some reads outside the lock:
+ * @mapped: Number of VMAs mapping the context.
+ *
+ * Mutable, written only by domain::reassign and dev::switch_ctx:
+ * @regs: State for each device in domain.
+ * @asid: Pinned ASID of live context.
+ */
+struct cla_ctx {
+ struct kref refcnt;
+ struct rhash_head node;
+
+ struct cla_domain *domain;
+ struct cla_ctx_key key;
+
+ refcount_t mapped;
+ struct list_head queue_node;
+ struct cla_regs **regs;
+ wait_queue_head_t waitq;
+ unsigned int asid;
+};
+
extern struct xarray cla_domains;
extern unsigned int cla_nr_domains;
extern struct cla_dev **cla_lut_cpu;
@@ -126,6 +222,38 @@ static inline void cla_reg_write(struct cla_dev *dev, off_t reg, u64 val)
struct cla_domain *cla_dev_domain_get(struct cla_dev *dev);
int cla_domains_finalise(void);
void cla_domains_free(void);
+int cla_domain_sched_init(struct cla_domain *domain);
+void cla_domain_sched_exit(struct cla_domain *domain);
+void cla_domain_set_broken(struct cla_domain *domain);
+struct cla_ctx *cla_domain_lookup_ctx(struct cla_domain *domain,
+ struct mm_struct *mm, struct file *file);
+int cla_domain_insert_ctx(struct cla_domain *domain, struct cla_ctx *ctx);
+void cla_domain_remove_ctx(struct cla_domain *domain, struct cla_ctx *ctx);
+void cla_domain_schedule_reassignment(struct cla_domain *domain,
+ unsigned long ms);
+void cla_domain_schedule_reclaim(struct cla_domain *domain);
+struct cla_ctx *cla_ctx_map(struct cla_domain *domain, struct mm_struct *mm,
+ struct file *file);
+void cla_ctx_unmap(struct cla_domain *domain, struct mm_struct *mm,
+ struct file *file);
+void cla_ctx_free(struct kref *ref);
+
+static inline void cla_ctx_get(struct cla_ctx *ctx)
+{
+ kref_get(&ctx->refcnt);
+}
+
+static inline void cla_ctx_put(struct cla_ctx *ctx)
+{
+ kref_put(&ctx->refcnt, cla_ctx_free);
+}
+
+static inline bool cla_ctx_is_dying(struct cla_ctx *ctx)
+{
+ return refcount_read(&ctx->mapped) == 0;
+}
+
+void cla_dev_switch_ctx(struct kthread_work *work);
int cla_op_wait_lresp(struct cla_dev *dev, u64 *lresp);
int cla_op_reset(struct cla_dev *dev, unsigned int accid);
diff --git a/drivers/misc/arm-cla/cla-ctx.c b/drivers/misc/arm-cla/cla-ctx.c
new file mode 100644
index 000000000000..db2bf5b7f106
--- /dev/null
+++ b/drivers/misc/arm-cla/cla-ctx.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/err.h>
+#include <linux/file.h>
+#include <linux/gfp.h>
+#include <linux/sched/mm.h>
+#include <linux/slab.h>
+
+#include "arm-cla.h"
+
+/**
+ * cla_ctx_map - Map a process and file to a CLA domain context
+ * @domain: CLA domain
+ * @mm: process address space
+ * @file: CLA device file
+ *
+ * Reuse an existing context for the same @mm and @file, or allocate and insert
+ * a new context into @domain. The context mapping count is incremented before
+ * returning.
+ *
+ * Return: the mapped context on success, or an error pointer on failure
+ */
+struct cla_ctx *cla_ctx_map(struct cla_domain *domain, struct mm_struct *mm,
+ struct file *file)
+{
+ struct cla_ctx *ctx;
+ int ret = -ENOMEM;
+
+ mutex_lock(&domain->lock);
+ if (domain->broken) {
+ ret = -EIO;
+ goto err_unlock;
+ }
+
+ ctx = cla_domain_lookup_ctx(domain, mm, file);
+ if (ctx) {
+ refcount_inc(&ctx->mapped);
+ mutex_unlock(&domain->lock);
+ return ctx;
+ }
+
+ ctx = kzalloc_obj(*ctx, GFP_KERNEL_ACCOUNT);
+ if (!ctx)
+ goto err_unlock;
+
+ kref_init(&ctx->refcnt);
+ refcount_set(&ctx->mapped, 1);
+ ctx->domain = domain;
+ ctx->regs = cla_regs_alloc_domain(domain);
+ if (!ctx->regs)
+ goto err_free;
+
+ mmgrab(mm);
+ ctx->key.mm = mm;
+ get_file(file);
+ ctx->key.file = file;
+ INIT_LIST_HEAD(&ctx->queue_node);
+ init_waitqueue_head(&ctx->waitq);
+
+ ret = cla_domain_insert_ctx(domain, ctx);
+ if (ret)
+ goto err_mmdrop;
+ mutex_unlock(&domain->lock);
+
+ return ctx;
+
+err_mmdrop:
+ mmdrop(ctx->key.mm);
+ fput(ctx->key.file);
+ cla_regs_free_domain(domain, ctx->regs);
+err_free:
+ kfree(ctx);
+err_unlock:
+ mutex_unlock(&domain->lock);
+ return ERR_PTR(ret);
+}
+
+/**
+ * cla_ctx_unmap - Unmap a process and file from a CLA domain context
+ * @domain: CLA domain
+ * @mm: process address space
+ * @file: CLA device file
+ *
+ * Drop a context mapping. When the final mapping is removed, wake waiters,
+ * remove the context from @domain, and schedule it for deassignment and
+ * reclaim.
+ */
+void cla_ctx_unmap(struct cla_domain *domain, struct mm_struct *mm,
+ struct file *file)
+{
+ struct cla_ctx *ctx;
+
+ mutex_lock(&domain->lock);
+ ctx = cla_domain_lookup_ctx(domain, mm, file);
+ WARN_ON(!ctx);
+
+ if (!refcount_dec_and_test(&ctx->mapped)) {
+ mutex_unlock(&domain->lock);
+ return;
+ }
+
+ /* Notify waiters that their context is dying. */
+ wake_up_all(&ctx->waitq);
+ cla_domain_remove_ctx(domain, ctx);
+
+ /*
+ * Since we're holding the mmap_lock for writing, and reassignment work
+ * may be waiting to grab it for reading, we cannot wait for
+ * reassignment here. The context holds a reference to mm_count, so the
+ * mm_struct or pgd are not going away until cla_ctx_free().
+ * If the mm is exiting, the accelerators will now access memory through
+ * an empty pgd and fault silently.
+ *
+ * Schedule deassignment followed by reclaim. If reassignment is
+ * currently running, it reschedules itself once it re-takes the lock:
+ * - After deassigning this context, schedule reassignment as usual. If
+ * no more contexts are queued, the following reassignment is a NOP.
+ * - After assigning this context, insert a deassignment before reclaim.
+ */
+ if (domain->assigned_ctx == ctx)
+ cla_domain_schedule_reassignment(domain, 0);
+ list_move(&ctx->queue_node, &domain->dying_ctxs);
+ cla_domain_schedule_reclaim(domain);
+ mutex_unlock(&domain->lock);
+}
+
+/**
+ * cla_ctx_free - Free a CLA domain context
+ * @ref: context reference counter
+ *
+ * Release resources held by a dying context after final reference is dropped.
+ */
+void cla_ctx_free(struct kref *ref)
+{
+ struct cla_ctx *ctx = container_of(ref, struct cla_ctx, refcnt);
+
+ WARN_ON(!cla_ctx_is_dying(ctx));
+ mmdrop(ctx->key.mm);
+ fput(ctx->key.file);
+ cla_regs_free_domain(ctx->domain, ctx->regs);
+ kfree(ctx);
+}
diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-init.c
index 0451d97bc95b..5335c55568ad 100644
--- a/drivers/misc/arm-cla/cla-init.c
+++ b/drivers/misc/arm-cla/cla-init.c
@@ -160,6 +160,7 @@ static void cla_dev_reinit(struct cla_dev *dev)
mutex_lock(&dev->lock);
dev->broken = true;
mutex_unlock(&dev->lock);
+ cla_domain_set_broken(dev->domain);
return;
}
@@ -171,6 +172,46 @@ static void cla_dev_reinit(struct cla_dev *dev)
cla_reg_write(dev, CLA_REG_LRESP, 0);
}
+static int cla_dev_worker_init(struct cla_dev *dev, int cpu)
+{
+ struct kthread_worker *worker;
+
+ worker = kthread_run_worker_on_cpu(cpu, 0, "cla-dev-worker/%u");
+ if (IS_ERR(worker))
+ return PTR_ERR(worker);
+
+ mutex_lock(&dev->lock);
+ WARN_ON(dev->worker);
+ dev->worker = worker;
+ mutex_unlock(&dev->lock);
+
+ return 0;
+}
+
+static void cla_dev_worker_destroy(struct cla_dev *dev)
+{
+ struct kthread_worker *worker;
+
+ /*
+ * Mark the worker as NULL, which prevents any new work from being
+ * queued to it. Then destroy it, which will flush any pending work.
+ * worker_sem guarantees lifetime of worker when flushing work in other
+ * paths. We must reinit the work so that work->worker is not dangling
+ * after releasing worker_sem.
+ */
+ mutex_lock(&dev->lock);
+ worker = dev->worker;
+ dev->worker = NULL;
+ mutex_unlock(&dev->lock);
+
+ if (worker) {
+ down_write(&dev->worker_sem);
+ kthread_destroy_worker(worker);
+ kthread_init_work(&dev->call.switch_ctx, cla_dev_switch_ctx);
+ up_write(&dev->worker_sem);
+ }
+}
+
static int cla_dev_setup(unsigned int cpu)
{
int i;
@@ -227,6 +268,10 @@ static int cla_dev_setup(unsigned int cpu)
dev->accelerators);
}
+ ret = cla_dev_worker_init(dev, cpu);
+ if (ret)
+ goto err;
+
return 0;
err:
cla_dev_reinit(dev);
@@ -245,6 +290,7 @@ static int cla_dev_teardown(unsigned int cpu)
if (!dev)
return 0;
+ cla_dev_worker_destroy(dev);
cla_dev_reinit(dev);
return 0;
@@ -289,6 +335,8 @@ static struct cla_dev *cla_dev_alloc(struct device *parent, int cpu,
dev->dev = parent;
mutex_init(&dev->lock);
+ init_rwsem(&dev->worker_sem);
+ kthread_init_work(&dev->call.switch_ctx, cla_dev_switch_ctx);
/* Attempt to find device domain, or allocate a new one */
dev->domain = cla_dev_domain_get(dev);
diff --git a/drivers/misc/arm-cla/cla-sched.c b/drivers/misc/arm-cla/cla-sched.c
new file mode 100644
index 000000000000..18b54cc386b7
--- /dev/null
+++ b/drivers/misc/arm-cla/cla-sched.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm CLA driver - domain scheduling
+ *
+ * Copyright 2026 Arm Limited.
+ */
+
+#include <linux/rhashtable.h>
+#include <linux/mmu_context.h>
+
+#include "arm-cla.h"
+
+static const struct rhashtable_params ctxs_rht_params = {
+ .head_offset = offsetof(struct cla_ctx, node),
+ .key_offset = offsetof(struct cla_ctx, key),
+ .key_len = sizeof(((struct cla_ctx *)0)->key),
+ .automatic_shrinking = true,
+};
+
+static int __cla_domain_switch_ctx(struct cla_domain *domain,
+ struct cla_ctx *prev_ctx,
+ struct cla_ctx *next_ctx)
+{
+ int ret = 0;
+ unsigned int i;
+ struct cla_dev *dev;
+ struct cla_call_on_cpu *call;
+
+ if (!prev_ctx && !next_ctx)
+ return 0;
+
+ for (i = 0; i < domain->nr_devs; i++) {
+ dev = domain->devs[i];
+ call = &dev->call;
+
+ call->sched.prev_ctx = prev_ctx;
+ call->sched.next_ctx = next_ctx;
+ call->sched.ctx_id = i;
+ call->ret = 0;
+
+ mutex_lock(&dev->lock);
+ if (dev->worker && !dev->broken)
+ kthread_queue_work(dev->worker, &call->switch_ctx);
+ mutex_unlock(&dev->lock);
+ }
+
+ for (i = 0; i < domain->nr_devs; i++) {
+ dev = domain->devs[i];
+ call = &dev->call;
+
+ down_read(&dev->worker_sem);
+ kthread_flush_work(&call->switch_ctx);
+ up_read(&dev->worker_sem);
+
+ /*
+ * kthread worker and kthread_flush_work() both take the worker
+ * lock to respectively write and read the current_work,
+ * providing the desired memory ordering for ret.
+ */
+ if (call->ret)
+ ret = call->ret;
+ }
+
+ return ret;
+}
+
+static int cla_domain_switch_ctx(struct cla_domain *domain,
+ struct cla_ctx *prev_ctx,
+ struct cla_ctx *next_ctx)
+{
+ int ret;
+
+ /* Remove prev_ctx from all devices in the domain */
+ ret = __cla_domain_switch_ctx(domain, prev_ctx, NULL);
+ if (ret) {
+ mmgrab(prev_ctx->key.mm);
+ goto err_kill_domain;
+ }
+
+ if (prev_ctx)
+ arm64_mm_context_put(prev_ctx->key.mm);
+ if (next_ctx) {
+ next_ctx->asid = arm64_mm_context_get(next_ctx->key.mm);
+ if (next_ctx->asid == 0) {
+ pr_err("cla: out of pinned ASIDs\n");
+ ret = -ENOSPC;
+ goto err_kill_domain;
+ }
+ }
+
+ /* Install next_ctx into all devices in the domain */
+ ret = __cla_domain_switch_ctx(domain, NULL, next_ctx);
+ if (ret) {
+ mmgrab(next_ctx->key.mm);
+ goto err_kill_domain;
+ }
+ return 0;
+
+err_kill_domain:
+ /* Hardware failure, the domain is dead */
+ cla_domain_set_broken(domain);
+ return ret;
+}
+
+/*
+ * This should only be called within the context of the device's worker, and
+ * so is bound to the cla's local cpu. The worker ensures serialization of
+ * invocations for the same cla. cla_dev_switch_ctx() may be running
+ * concurrently on other cpus for other clas in the same domain.
+ */
+static int __cla_dev_switch_ctx(struct cla_dev *dev)
+{
+ struct cla_ctx *prev_ctx, *next_ctx;
+ unsigned long ctx_id;
+ int ret;
+
+ if (WARN_ON(smp_processor_id() != dev->cpu))
+ return -EINVAL;
+
+ prev_ctx = dev->call.sched.prev_ctx;
+ next_ctx = dev->call.sched.next_ctx;
+ ctx_id = dev->call.sched.ctx_id;
+
+ /* Save state for outgoing ctx. */
+ if (prev_ctx) {
+ /*
+ * Since we're not holding the domain lock during this check, we
+ * may be saving a dying context, but it's only an optimisation.
+ */
+ bool do_save = !cla_ctx_is_dying(prev_ctx);
+
+ ret = cla_regs_switch_out(dev, prev_ctx->regs[ctx_id], do_save);
+ if (ret) {
+ cla_err(dev, "failed to remove regs: %d\n", ret);
+ return ret;
+ }
+
+ ret = cla_mtc_uninstall(dev);
+ if (ret) {
+ cla_err(dev, "failed to remove mm: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* Restore state for incoming ctx. */
+ if (next_ctx) {
+ ret = cla_mtc_install(dev, next_ctx->key.mm->pgd, next_ctx->asid);
+ if (ret) {
+ cla_err(dev, "failed to setup mm: %d\n", ret);
+ return ret;
+ }
+
+ ret = cla_regs_switch_in(dev, next_ctx->regs[ctx_id]);
+ if (ret) {
+ cla_err(dev, "failed to setup regs: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void cla_dev_switch_ctx(struct kthread_work *work)
+{
+ struct cla_call_on_cpu *call =
+ container_of(work, struct cla_call_on_cpu, switch_ctx);
+ struct cla_dev *dev = container_of(call, struct cla_dev, call);
+
+ call->ret = __cla_dev_switch_ctx(dev);
+ if (call->ret) {
+ mutex_lock(&dev->lock);
+ dev->broken = true;
+ mutex_unlock(&dev->lock);
+ }
+}
+
+static inline struct cla_ctx *cla_domain_get_next_ctx(struct cla_domain *domain)
+{
+ return list_first_entry_or_null(&domain->queued_ctxs, struct cla_ctx,
+ queue_node);
+}
+
+static inline struct cla_ctx *cla_domain_get_prev_ctx(struct cla_domain *domain,
+ struct cla_ctx *next_ctx)
+{
+ if (domain->assigned_ctx && (next_ctx || cla_ctx_is_dying(domain->assigned_ctx)))
+ return domain->assigned_ctx;
+ return NULL;
+}
+
+static void cla_vma_zap_domain(struct vm_area_struct *vma, struct cla_domain *domain)
+{
+ unsigned long vma_start = vma->vm_pgoff;
+ unsigned long vma_end = vma_start + vma_pages(vma);
+ unsigned long domain_start = domain->pg_offset;
+ unsigned long domain_end = domain_start + domain->nr_devs;
+ unsigned long start = max(vma_start, domain_start);
+ unsigned long end = min(vma_end, domain_end);
+ unsigned long addr;
+ unsigned long size;
+
+ if (start >= end)
+ return;
+
+ addr = vma->vm_start + ((start - vma_start) << PAGE_SHIFT);
+ size = (end - start) << PAGE_SHIFT;
+ zap_special_vma_range(vma, addr, size);
+}
+
+static void cla_domain_zap(struct cla_domain *domain, struct cla_ctx *ctx)
+{
+ struct vm_area_struct *vma;
+ struct vma_iterator vmi;
+
+ /*
+ * If the context is dying it has already been unmapped, no need to zap
+ * it here.
+ */
+ if (!ctx || cla_ctx_is_dying(ctx))
+ return;
+
+ /*
+ * Iterate over vmas in prev_ctx's mm, looking for vmas that map
+ * prev_ctx's file. Zap any portions of those vmas that map the domain.
+ */
+ mmap_read_lock(ctx->key.mm);
+ vma_iter_init(&vmi, ctx->key.mm, 0);
+ for_each_vma(vmi, vma) {
+ if (vma->vm_file != ctx->key.file)
+ continue;
+
+ cla_vma_zap_domain(vma, domain);
+ }
+ mmap_read_unlock(ctx->key.mm);
+}
+
+static void cla_domain_reassign(struct kthread_work *work)
+{
+ struct cla_ctx invalid_ctx;
+ struct cla_domain *domain;
+ struct cla_ctx *prev_ctx;
+ struct cla_ctx *next_ctx;
+ int ret;
+
+ domain = container_of(work, struct cla_domain, reassign.work);
+
+ mutex_lock(&domain->lock);
+ if (domain->broken)
+ goto out_unlock;
+
+ /* Figure out prev_ctx and next_ctx. NULL indicates don't [un]assign. */
+ next_ctx = cla_domain_get_next_ctx(domain);
+ prev_ctx = cla_domain_get_prev_ctx(domain, next_ctx);
+
+ if (!prev_ctx && !next_ctx)
+ goto out_unlock;
+
+ /*
+ * We need to grab mmap_lock, but can't do so while holding the domain
+ * lock (since cla_vma_fault() grabs the domain lock while holding
+ * mmap_lock). We are the only thread that modifies assigned_ctx, but
+ * the queue may change and contexts may be killed by cla_ctx_unmap().
+ *
+ * No user is allowed to access CLA while we are reassigning. Disable
+ * assigned_ctx before zapping so cla_vma_fault() won't re-map the
+ * domain during reassignment. A NULL assigned_ctx causes
+ * cla_vma_fault() to immediately schedule a reassignment; use a
+ * reserved context to make it wait.
+ */
+ WRITE_ONCE(domain->assigned_ctx, &invalid_ctx);
+ mutex_unlock(&domain->lock);
+
+ /* Prevent prev_ctx from accessing any device in the domain. */
+ cla_domain_zap(domain, prev_ctx);
+
+ /* Do per-device reassignment work and wait for it to complete. */
+ ret = cla_domain_switch_ctx(domain, prev_ctx, next_ctx);
+ if (ret)
+ next_ctx = NULL;
+
+ mutex_lock(&domain->lock);
+ WRITE_ONCE(domain->assigned_ctx, next_ctx);
+
+ /*
+ * Remove next_ctx from the queue and wake up all waiters so that the
+ * fault handler can map the domain into the newly assigned process.
+ * assigned_ctx must have been updated prior to waking the waiters.
+ *
+ * If next_ctx was killed while we were assigning it, insert a
+ * deassignment before the upcoming reclaim.
+ */
+ if (next_ctx) {
+ if (cla_ctx_is_dying(next_ctx)) {
+ cla_domain_schedule_reassignment(domain, 0);
+ cla_domain_schedule_reclaim(domain);
+ goto out_unlock;
+ }
+ list_del_init(&next_ctx->queue_node);
+ wake_up_all(&next_ctx->waitq);
+ }
+
+ /* If there are more queued ctxs, schedule the next reassignment. */
+ if (!list_empty(&domain->queued_ctxs))
+ cla_domain_schedule_reassignment(domain, CLA_SLICE_MS);
+
+out_unlock:
+ mutex_unlock(&domain->lock);
+}
+
+static void cla_domain_reclaim(struct kthread_work *work)
+{
+ struct cla_domain *domain;
+ struct cla_ctx *ctx, *next;
+
+ domain = container_of(work, struct cla_domain, reclaim.work);
+
+ mutex_lock(&domain->lock);
+ list_for_each_entry_safe(ctx, next, &domain->dying_ctxs, queue_node) {
+ list_del_init(&ctx->queue_node);
+ WARN_ON(domain->assigned_ctx == ctx);
+ cla_ctx_put(ctx);
+ }
+ mutex_unlock(&domain->lock);
+}
+
+/**
+ * cla_domain_sched_init - Initialize scheduling for a CLA domain
+ * @domain: CLA domain
+ *
+ * Initialize domain context table, scheduling queues, worker, and work items.
+ *
+ * Return: 0 on success, or an error
+ */
+int cla_domain_sched_init(struct cla_domain *domain)
+{
+ int ret;
+
+ INIT_LIST_HEAD(&domain->queued_ctxs);
+ INIT_LIST_HEAD(&domain->dying_ctxs);
+ WRITE_ONCE(domain->assigned_ctx, NULL);
+
+ ret = rhashtable_init(&domain->ctxs, &ctxs_rht_params);
+ if (ret)
+ return ret;
+
+ domain->worker = kthread_run_worker(0, "cla-domain-worker");
+ if (IS_ERR(domain->worker)) {
+ rhashtable_destroy(&domain->ctxs);
+ return PTR_ERR(domain->worker);
+ }
+ kthread_init_delayed_work(&domain->reassign, cla_domain_reassign);
+ kthread_init_delayed_work(&domain->reclaim, cla_domain_reclaim);
+
+ return 0;
+}
+
+/**
+ * cla_domain_sched_exit - Tear down scheduling for a CLA domain
+ * @domain: CLA domain
+ *
+ * Cancel pending reassignment work, flush reclaim work, destroy the domain
+ * worker, and destroy the context table.
+ */
+void cla_domain_sched_exit(struct cla_domain *domain)
+{
+ WARN_ON(!list_empty(&domain->queued_ctxs));
+ WARN_ON(READ_ONCE(domain->assigned_ctx));
+
+ kthread_cancel_delayed_work_sync(&domain->reassign);
+ /* Since reclaim is always queued with 0 delay, it gets flushed here. */
+ kthread_destroy_worker(domain->worker);
+ rhashtable_destroy(&domain->ctxs);
+}
+
+/**
+ * cla_domain_set_broken - Mark a CLA domain as broken
+ * @domain: CLA domain
+ *
+ * Prevent further reassignment work and wake contexts waiting for assignment.
+ */
+void cla_domain_set_broken(struct cla_domain *domain)
+{
+ struct cla_ctx *ctx;
+
+ mutex_lock(&domain->lock);
+ WRITE_ONCE(domain->broken, true);
+ /* Notify any waiter that their context isn't getting reassigned */
+ list_for_each_entry(ctx, &domain->queued_ctxs, queue_node)
+ wake_up_all(&ctx->waitq);
+ mutex_unlock(&domain->lock);
+}
+
+/**
+ * cla_domain_lookup_ctx - Look up a context in a CLA domain
+ * @domain: CLA domain
+ * @mm: process address space
+ * @file: CLA device file
+ *
+ * The caller must hold the domain lock.
+ *
+ * Return: the matching context, or %NULL if no context was found
+ */
+struct cla_ctx *cla_domain_lookup_ctx(struct cla_domain *domain,
+ struct mm_struct *mm, struct file *file)
+{
+ struct cla_ctx_key key = {
+ .mm = mm,
+ .file = file,
+ };
+
+ lockdep_assert_held(&domain->lock);
+ return rhashtable_lookup_fast(&domain->ctxs, &key, ctxs_rht_params);
+}
+
+/**
+ * cla_domain_insert_ctx - Insert a context into a CLA domain
+ * @domain: CLA domain
+ * @ctx: context to insert
+ *
+ * The caller must hold the domain lock.
+ *
+ * Return: 0 on success, or an error
+ */
+int cla_domain_insert_ctx(struct cla_domain *domain, struct cla_ctx *ctx)
+{
+ lockdep_assert_held(&domain->lock);
+ return rhashtable_insert_fast(&domain->ctxs, &ctx->node, ctxs_rht_params);
+}
+
+/**
+ * cla_domain_remove_ctx - Remove a context from a CLA domain
+ * @domain: CLA domain
+ * @ctx: context to remove
+ *
+ * The caller must hold the domain lock.
+ */
+void cla_domain_remove_ctx(struct cla_domain *domain, struct cla_ctx *ctx)
+{
+ lockdep_assert_held(&domain->lock);
+ rhashtable_remove_fast(&domain->ctxs, &ctx->node, ctxs_rht_params);
+}
+
+/**
+ * cla_domain_schedule_reassignment - Schedule domain reassignment
+ * @domain: CLA domain
+ * @ms: delay in milliseconds
+ *
+ * The caller must hold the domain lock. Nop if the domain is broken.
+ */
+void cla_domain_schedule_reassignment(struct cla_domain *domain, unsigned long ms)
+{
+ lockdep_assert_held(&domain->lock);
+ if (!domain->broken)
+ kthread_mod_delayed_work(domain->worker, &domain->reassign,
+ msecs_to_jiffies(ms));
+}
+
+/**
+ * cla_domain_schedule_reclaim - Schedule reclaim of dying contexts
+ * @domain: CLA domain
+ *
+ * The caller must hold the domain lock. Reclaim is queued without delay after
+ * any work already pending on the domain worker.
+ */
+void cla_domain_schedule_reclaim(struct cla_domain *domain)
+{
+ lockdep_assert_held(&domain->lock);
+ /*
+ * Reclaim is always queued with 0 delay. It is a delayed work only so
+ * we can easily move the work to the back of the queue (after
+ * reassign). Doing so is tricky with non-delayed work.
+ */
+ kthread_mod_delayed_work(domain->worker, &domain->reclaim, 0);
+}
diff --git a/drivers/misc/arm-cla/cla-topology.c b/drivers/misc/arm-cla/cla-topology.c
index 402c228e197c..ca416b610cb1 100644
--- a/drivers/misc/arm-cla/cla-topology.c
+++ b/drivers/misc/arm-cla/cla-topology.c
@@ -37,6 +37,8 @@ static struct cla_domain *cla_domain_alloc(struct cla_dev *dev, unsigned int id)
if (!domain)
return ERR_PTR(-ENOMEM);
+ mutex_init(&domain->lock);
+
domain->id = id;
ret = xa_insert(&cla_domains, id, domain, GFP_KERNEL);
if (ret < 0)
@@ -50,8 +52,14 @@ static struct cla_domain *cla_domain_alloc(struct cla_dev *dev, unsigned int id)
}
domain->devs[0] = dev;
+ ret = cla_domain_sched_init(domain);
+ if (ret)
+ goto err_free_devs;
+
return domain;
+err_free_devs:
+ kfree(domain->devs);
err_free_id:
xa_erase(&cla_domains, domain->id);
err_free_domain:
@@ -155,6 +163,7 @@ int __init cla_domains_finalise(void)
static void cla_domain_free(struct cla_domain *domain)
{
+ cla_domain_sched_exit(domain);
kfree(domain->devs);
xa_erase(&cla_domains, domain->id);
kfree(domain);
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [RFC PATCH v1 8/8] misc/arm-cla: Add userspace interface
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
` (6 preceding siblings ...)
2026-07-17 10:47 ` [RFC PATCH v1 7/8] misc/arm-cla: Manage domain contexts Ryan Roberts
@ 2026-07-17 10:47 ` Ryan Roberts
2026-07-17 11:33 ` [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Will Deacon
8 siblings, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 10:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: Ryan Roberts, linux-kernel, linux-arm-kernel, dri-devel,
linux-doc
Expose CLA devices through a character device so userspace can enumerate
the available hardware and map accelerator register frames.
Define version 1 of the CLA UAPI with a GET_PARAM ioctl. Report device
topology, CPU affinity, domain membership, mmap offsets, architecture
version and attached accelerator masks, together with the IIDR, DEVARCH
and REVIDR of each accelerator.
CLA registers can only be read from the CPU local to the device, while
enumeration may occur on any CPU. Validate the supported CLA
architecture version during device setup and cache the CLA and
accelerator identification registers for later ioctl queries.
Support shared read-write mmap of one or more CLA register pages. Create
a context for every domain covered by the mapping and resolve faults
only while that context owns the domain. Queue unassigned contexts with
the domain scheduler, drop mmap_lock while waiting for assignment and
retry the fault after the context is woken.
Co-developed-by: Jean-Philippe Brucker <jpb@kernel.org>
Signed-off-by: Jean-Philippe Brucker <jpb@kernel.org>
Signed-off-by: Ryan Roberts <ryan.roberts@arm.com>
---
drivers/misc/arm-cla/Makefile | 3 +-
drivers/misc/arm-cla/arm-cla-regs.h | 2 +
drivers/misc/arm-cla/arm-cla.h | 13 ++
drivers/misc/arm-cla/cla-init.c | 26 +++
drivers/misc/arm-cla/cla-user.c | 351 ++++++++++++++++++++++++++++
include/uapi/linux/arm-cla.h | 207 ++++++++++++++++
6 files changed, 601 insertions(+), 1 deletion(-)
create mode 100644 drivers/misc/arm-cla/cla-user.c
create mode 100644 include/uapi/linux/arm-cla.h
diff --git a/drivers/misc/arm-cla/Makefile b/drivers/misc/arm-cla/Makefile
index a92c3594e5c9..f848c608e8f1 100644
--- a/drivers/misc/arm-cla/Makefile
+++ b/drivers/misc/arm-cla/Makefile
@@ -7,6 +7,7 @@ arm-cla-y := \
cla-ops.o \
cla-regs.o \
cla-sched.o \
- cla-topology.o
+ cla-topology.o \
+ cla-user.o
obj-$(CONFIG_ARM_CLA) += arm-cla.o
diff --git a/drivers/misc/arm-cla/arm-cla-regs.h b/drivers/misc/arm-cla/arm-cla-regs.h
index fcd187ecb777..338b9e78b465 100644
--- a/drivers/misc/arm-cla/arm-cla-regs.h
+++ b/drivers/misc/arm-cla/arm-cla-regs.h
@@ -63,6 +63,8 @@
CLA_STATUS_EXCEPT | \
CLA_STATUS_SRMODE)
+#define CLA_AAIDR_1_0 (1ULL << 8)
+
/* Standard accelerator registers */
#define CLA_REG_IIDR 0x0000
#define CLA_REG_DEVARCH 0x0001
diff --git a/drivers/misc/arm-cla/arm-cla.h b/drivers/misc/arm-cla/arm-cla.h
index 041d4a8d7e91..3dd783d0b1cc 100644
--- a/drivers/misc/arm-cla/arm-cla.h
+++ b/drivers/misc/arm-cla/arm-cla.h
@@ -45,6 +45,12 @@ struct cla_call_on_cpu {
struct kthread_work switch_ctx;
};
+struct cla_accel_desc {
+ u64 iidr;
+ u64 devarch;
+ u64 revidr;
+};
+
/**
* struct cla_dev - CLA device
*
@@ -57,6 +63,8 @@ struct cla_call_on_cpu {
* @pg_offset: Mmap offset of this device.
* @iassizes: Combined regstate of all accels.
* @domain: The domain this CLA belongs to.
+ * @aidr: CLA Architecture ID.
+ * @accel_descs: Accelerator ID registers.
*
* Mutable, only accessed under @lock:
* @lock: Protects the following members.
@@ -74,6 +82,8 @@ struct cla_dev {
unsigned long pg_offset;
unsigned long iassizes;
struct cla_domain *domain;
+ u64 aidr;
+ struct cla_accel_desc accel_descs[CLA_NUM_ACC];
struct mutex lock;
bool broken;
@@ -280,4 +290,7 @@ int cla_mtc_clear(struct cla_dev *dev);
int cla_mtc_install(struct cla_dev *dev, pgd_t *pgd, unsigned long asid);
int cla_mtc_uninstall(struct cla_dev *dev);
+int cla_user_init(void);
+void cla_user_exit(void);
+
#endif /* _ARM_CLA_H_ */
diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-init.c
index 5335c55568ad..090efc6cc9b4 100644
--- a/drivers/misc/arm-cla/cla-init.c
+++ b/drivers/misc/arm-cla/cla-init.c
@@ -80,6 +80,7 @@ static int cla_reset_pmu(struct cla_dev *dev, unsigned int accid)
*/
static int cla_dev_setup_accel(struct cla_dev *dev, unsigned int accid)
{
+ struct cla_accel_desc *desc = &dev->accel_descs[accid];
u64 status;
u64 iassize;
u64 acap;
@@ -122,6 +123,20 @@ static int cla_dev_setup_accel(struct cla_dev *dev, unsigned int accid)
return 1;
}
+ /*
+ * Cache some standard accelerator registers that user space may query
+ * via ioctl from a remote CPU.
+ */
+ ret = cla_op_regread(dev, accid, CLA_REG_IIDR, 1, &desc->iidr);
+ if (ret)
+ return ret;
+ ret = cla_op_regread(dev, accid, CLA_REG_DEVARCH, 1, &desc->devarch);
+ if (ret)
+ return ret;
+ ret = cla_op_regread(dev, accid, CLA_REG_REVIDR, 1, &desc->revidr);
+ if (ret)
+ return ret;
+
ret = cla_op_regread(dev, accid, CLA_REG_IASSIZE, 1, &iassize);
if (ret)
return ret;
@@ -234,6 +249,10 @@ static int cla_dev_setup(unsigned int cpu)
if (WARN_ON(smp_processor_id() != cpu || dev->cpu != cpu))
return -EINVAL;
+ dev->aidr = cla_reg_read(dev, CLA_REG_CLAAIDR);
+ if (dev->aidr != CLA_AAIDR_1_0)
+ return -EPROTONOSUPPORT;
+
/* Clear DATA and LRESP_DATANZ */
for (i = 0; i < CLA_NUM_DATA_REGS; i++)
cla_reg_write(dev, CLA_REG_DATA(i), 0);
@@ -453,8 +472,14 @@ static int __init cla_module_init(void)
}
cla_cpuhp_state = ret;
+ ret = cla_user_init();
+ if (ret)
+ goto err_cpuhp_remove;
+
return 0;
+err_cpuhp_remove:
+ cpuhp_remove_state(cla_cpuhp_state);
err_driver_unregister:
platform_driver_unregister(&cla_driver);
err_domains_free:
@@ -464,6 +489,7 @@ static int __init cla_module_init(void)
static void __exit cla_module_exit(void)
{
+ cla_user_exit();
cpuhp_remove_state(cla_cpuhp_state);
platform_driver_unregister(&cla_driver);
cla_domains_free();
diff --git a/drivers/misc/arm-cla/cla-user.c b/drivers/misc/arm-cla/cla-user.c
new file mode 100644
index 000000000000..415328a653b7
--- /dev/null
+++ b/drivers/misc/arm-cla/cla-user.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm CLA driver - userspace interface
+ *
+ * Copyright 2026 Arm Limited.
+ */
+
+#include <linux/cdev.h>
+#include <linux/cpu.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include "arm-cla.h"
+#include <uapi/linux/arm-cla.h>
+
+static struct class *cla_class;
+static dev_t cla_devt;
+
+#define dev_nospec(idx) array_index_nospec(idx, cla_nr_devs)
+#define accel_nospec(idx) array_index_nospec(idx, CLA_NUM_ACC)
+
+#define cla_for_each_mapped_domain(_vma, _domain) \
+ for (unsigned long __pg = (_vma)->vm_pgoff; \
+ __pg < (_vma)->vm_pgoff + vma_pages(_vma) && \
+ ((_domain) = cla_lut_pg[dev_nospec(__pg)]->domain); \
+ __pg = (_domain)->pg_offset + (_domain)->nr_devs)
+
+static long cla_ioctl_validate_param(struct arm_cla_param *param)
+{
+ int accel_id;
+ int dev_id;
+
+ switch (param->param) {
+ case ARM_CLA_PARAM_UABI_VERSION:
+ case ARM_CLA_PARAM_DEV_NR:
+ if (param->index != 0)
+ return -EINVAL;
+ break;
+ case ARM_CLA_PARAM_DEV_CPU_ID:
+ case ARM_CLA_PARAM_DEV_DOMAIN_ID:
+ case ARM_CLA_PARAM_DEV_PGOFF:
+ case ARM_CLA_PARAM_DEV_AIDR:
+ case ARM_CLA_PARAM_DEV_ACCELS:
+ if (param->index >= cla_nr_devs)
+ return -EINVAL;
+ break;
+ case ARM_CLA_PARAM_ACCEL_IIDR:
+ case ARM_CLA_PARAM_ACCEL_DEVARCH:
+ case ARM_CLA_PARAM_ACCEL_REVIDR:
+ dev_id = ARM_CLA_PARAM_INDEX_DEV(param->index);
+ accel_id = ARM_CLA_PARAM_INDEX_ACCEL(param->index);
+ if (dev_id >= cla_nr_devs || accel_id >= CLA_NUM_ACC)
+ return -EINVAL;
+ dev_id = dev_nospec(dev_id);
+ if ((cla_lut_pg[dev_id]->accelerators & BIT(accel_id)) == 0)
+ return -ENODEV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static long cla_ioctl_get_param(unsigned long arg)
+{
+ struct arm_cla_param __user *uparam = (void __user *)arg;
+ struct arm_cla_param param;
+ int accel_id;
+ int dev_id;
+ int ret;
+
+ if (copy_from_user(¶m, uparam, sizeof(param)))
+ return -EFAULT;
+
+ ret = cla_ioctl_validate_param(¶m);
+ if (ret)
+ return ret;
+
+ dev_id = dev_nospec(ARM_CLA_PARAM_INDEX_DEV(param.index));
+ accel_id = accel_nospec(ARM_CLA_PARAM_INDEX_ACCEL(param.index));
+
+ switch (param.param) {
+ case ARM_CLA_PARAM_UABI_VERSION:
+ param.value = ARM_CLA_UABI_VERSION;
+ break;
+ case ARM_CLA_PARAM_DEV_NR:
+ param.value = cla_nr_devs;
+ break;
+ case ARM_CLA_PARAM_DEV_CPU_ID:
+ param.value = cla_lut_pg[dev_id]->cpu;
+ break;
+ case ARM_CLA_PARAM_DEV_DOMAIN_ID:
+ param.value = cla_lut_pg[dev_id]->domain->id;
+ break;
+ case ARM_CLA_PARAM_DEV_PGOFF:
+ param.value = cla_lut_pg[dev_id]->pg_offset;
+ break;
+ case ARM_CLA_PARAM_DEV_AIDR:
+ param.value = cla_lut_pg[dev_id]->aidr;
+ break;
+ case ARM_CLA_PARAM_DEV_ACCELS:
+ param.value = cla_lut_pg[dev_id]->accelerators;
+ break;
+ case ARM_CLA_PARAM_ACCEL_IIDR:
+ param.value = cla_lut_pg[dev_id]->accel_descs[accel_id].iidr;
+ break;
+ case ARM_CLA_PARAM_ACCEL_DEVARCH:
+ param.value = cla_lut_pg[dev_id]->accel_descs[accel_id].devarch;
+ break;
+ case ARM_CLA_PARAM_ACCEL_REVIDR:
+ param.value = cla_lut_pg[dev_id]->accel_descs[accel_id].revidr;
+ break;
+ }
+
+ if (copy_to_user(uparam, ¶m, sizeof(param)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static void cla_vma_open(struct vm_area_struct *vma)
+{
+ /*
+ * A vma previously created with cla_file_mmap() has been duplicated
+ * within the same mm (most likely due to mremap). While this could also
+ * be called for duplication into a new mm (via fork), we set
+ * VM_DONTCOPY on the original mmap, so this will never happen. So the
+ * contexts covered by this new vma already exist.
+ */
+ struct cla_domain *domain;
+
+ cla_for_each_mapped_domain(vma, domain)
+ cla_ctx_map(domain, vma->vm_mm, vma->vm_file);
+}
+
+static void cla_vma_close(struct vm_area_struct *vma)
+{
+ struct cla_domain *domain;
+
+ /* On munmap() or exit_mmap(), kill the context. */
+ cla_for_each_mapped_domain(vma, domain)
+ cla_ctx_unmap(domain, vma->vm_mm, vma->vm_file);
+}
+
+static vm_fault_t cla_vma_fault(struct vm_fault *vmf)
+{
+ struct vm_area_struct *vma = vmf->vma;
+ struct cla_domain *domain;
+ struct cla_dev *dev;
+ struct cla_ctx *ctx;
+ unsigned long pg;
+
+ /*
+ * Reassignment needs mmap_lock, so we cannot wait while holding it.
+ * Luckily, this flag is only missing in some exotic cases which do not
+ * apply for this VM_IO | VM_PFNMAP vma. i.e. GUP without
+ * FOLL_UNLOCKABLE or FOLL_NOWAIT or fixup_user_fault(unlocked=NULL),
+ * used by futex fault-in path.
+ */
+ if (WARN_ON(!(vmf->flags & FAULT_FLAG_ALLOW_RETRY)))
+ return VM_FAULT_SIGBUS;
+
+ pg = vma->vm_pgoff + ((vmf->address - vma->vm_start) >> PAGE_SHIFT);
+ dev = cla_lut_pg[pg];
+ domain = dev->domain;
+
+ mutex_lock(&domain->lock);
+ ctx = cla_domain_lookup_ctx(domain, vma->vm_mm, vma->vm_file);
+ if (WARN_ON(!ctx) || cla_ctx_is_dying(ctx) || domain->broken) {
+ mutex_unlock(&domain->lock);
+ return domain->broken ? VM_FAULT_SIGBUS : VM_FAULT_SIGSEGV;
+ }
+
+ /* If our ctx is the assigned one, map the device into memory. */
+ if (domain->assigned_ctx == ctx) {
+ vm_fault_t ret;
+
+ ret = vmf_insert_pfn(vma, vmf->address, dev->pfn);
+ mutex_unlock(&domain->lock);
+ return ret;
+ }
+
+ /* Enqueue if not already, starting current assignee's time slice. */
+ if (list_empty(&ctx->queue_node)) {
+ bool was_empty = list_empty(&domain->queued_ctxs);
+ unsigned long delay;
+
+ list_add_tail(&ctx->queue_node, &domain->queued_ctxs);
+ if (was_empty) {
+ delay = domain->assigned_ctx ? CLA_SLICE_MS : 0;
+ cla_domain_schedule_reassignment(domain, delay);
+ }
+ }
+
+ mutex_unlock(&domain->lock);
+
+ /* If waiting is not permitted, return indicating we need to retry. */
+ if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT)
+ return VM_FAULT_RETRY;
+
+ /*
+ * Drop the fault lock prior to sleeping and return VM_FAULT_RETRY when
+ * we wake up to rerun the fault. We must get the ctx while sleeping to
+ * prevent it from being freed while we are asleep on the contained wait
+ * queue.
+ */
+ cla_ctx_get(ctx);
+ release_fault_lock(vmf);
+ wait_event_interruptible(ctx->waitq,
+ READ_ONCE(domain->assigned_ctx) == ctx ||
+ cla_ctx_is_dying(ctx) ||
+ READ_ONCE(domain->broken));
+ cla_ctx_put(ctx);
+ return VM_FAULT_RETRY;
+}
+
+static int cla_vma_may_split(struct vm_area_struct *vma, unsigned long addr)
+{
+ /*
+ * Forbid splitting cla mappings to prevent refcount leaks.
+ * cla_file_mmap()/cla_vma_open() track a context per domain mapped by
+ * the vma. If there were fewer domains mapped by the vma at
+ * cla_vma_close() then contexts would get leaked.
+ */
+ return -EINVAL;
+}
+
+static const struct vm_operations_struct cla_vma_ops = {
+ .open = cla_vma_open,
+ .close = cla_vma_close,
+ .fault = cla_vma_fault,
+ .may_split = cla_vma_may_split,
+};
+
+static int cla_file_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ unsigned long nr_pages = vma_pages(vma);
+ struct cla_domain *rollback;
+ struct cla_domain *domain;
+ struct cla_ctx *ctx;
+
+ /* Ensure the requested mapping is within range. */
+ if (!nr_pages)
+ return -EINVAL;
+ if (vma->vm_pgoff >= cla_nr_devs)
+ return -EINVAL;
+ if (nr_pages > cla_nr_devs - vma->vm_pgoff)
+ return -EINVAL;
+
+ /* Only allow shared RW mappings. Nothing else makes sense for CLA. */
+ if (!(vma->vm_flags & VM_SHARED))
+ return -EINVAL;
+ if (!(vma->vm_flags & VM_READ))
+ return -EINVAL;
+ if (!(vma->vm_flags & VM_WRITE))
+ return -EINVAL;
+ if (vma->vm_flags & VM_EXEC)
+ return -EINVAL;
+
+ /*
+ * Iterate over each domain covered by the vma and get-or-alloc its
+ * context. If any fails, we need to rollback with a put.
+ */
+ cla_for_each_mapped_domain(vma, domain) {
+ ctx = cla_ctx_map(domain, vma->vm_mm, vma->vm_file);
+ if (IS_ERR(ctx)) {
+ cla_for_each_mapped_domain(vma, rollback) {
+ if (rollback == domain)
+ return PTR_ERR(ctx);
+ cla_ctx_unmap(rollback, vma->vm_mm,
+ vma->vm_file);
+ }
+ }
+ }
+
+ vm_flags_mod(vma, VM_DONTCOPY | VM_DONTDUMP | VM_DONTEXPAND |
+ VM_IO | VM_PFNMAP, VM_MAYEXEC);
+ vma->vm_page_prot = pgprot_device(vma->vm_page_prot);
+ vma->vm_ops = &cla_vma_ops;
+
+ return 0;
+}
+
+static long cla_file_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ switch (cmd) {
+ case ARM_CLA_IOCTL_GET_PARAM:
+ return cla_ioctl_get_param(arg);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations cla_fops = {
+ .owner = THIS_MODULE,
+ .mmap = cla_file_mmap,
+ .unlocked_ioctl = cla_file_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = cla_file_ioctl,
+#endif
+};
+
+static char *cla_devnode(const struct device *dev, umode_t *mode)
+{
+ if (mode)
+ *mode = 0666;
+ return NULL;
+}
+
+int __init cla_user_init(void)
+{
+ int ret;
+
+ cla_class = class_create("arm-cla");
+ if (IS_ERR(cla_class))
+ return PTR_ERR(cla_class);
+ cla_class->devnode = cla_devnode;
+
+ ret = register_chrdev(0, KBUILD_MODNAME, &cla_fops);
+ if (ret < 0)
+ goto err_class_destroy;
+
+ cla_devt = MKDEV(ret, 0);
+
+ if (IS_ERR(device_create(cla_class, NULL, cla_devt, NULL, "cla"))) {
+ ret = -ENODEV;
+ goto err_unregister_chrdev;
+ }
+
+ return 0;
+
+err_unregister_chrdev:
+ unregister_chrdev(MAJOR(cla_devt), KBUILD_MODNAME);
+err_class_destroy:
+ class_destroy(cla_class);
+ return ret;
+}
+
+void __exit cla_user_exit(void)
+{
+ device_destroy(cla_class, cla_devt);
+ unregister_chrdev(MAJOR(cla_devt), KBUILD_MODNAME);
+ class_destroy(cla_class);
+}
diff --git a/include/uapi/linux/arm-cla.h b/include/uapi/linux/arm-cla.h
new file mode 100644
index 000000000000..6357fdb9df8a
--- /dev/null
+++ b/include/uapi/linux/arm-cla.h
@@ -0,0 +1,207 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * UAPI definitions for the CLA character device.
+ */
+
+#ifndef _UAPI_LINUX_CLA_H
+#define _UAPI_LINUX_CLA_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define ARM_CLA_UABI_VERSION 1
+#define ARM_CLA_IOC_MAGIC 'C'
+
+/**
+ * define ARM_CLA_IOCTL_GET_PARAM - Get the value of a parameter.
+ *
+ * ioctl command whose argument is a pointer to &struct arm_cla_param.
+ * &arm_cla_param->param and &arm_cla_param->index are input parameters.
+ * &arm_cla_param->value is an output parameter.
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - Unrecognised param or param does not support GET
+ * * Return code as defined for param
+ */
+#define ARM_CLA_IOCTL_GET_PARAM \
+ _IOWR(ARM_CLA_IOC_MAGIC, 0x00, struct arm_cla_param)
+
+/**
+ * define ARM_CLA_PARAM_UABI_VERSION - CLA driver UABI version (RO).
+ *
+ * &arm_cla_param->index must be 0.
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - index not 0
+ */
+#define ARM_CLA_PARAM_UABI_VERSION 0
+
+/**
+ * define ARM_CLA_PARAM_DEV_NR - Number of attached CLA devices (RO).
+ *
+ * &arm_cla_param->index must be 0.
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - index not 0
+ */
+#define ARM_CLA_PARAM_DEV_NR 1
+
+/**
+ * define ARM_CLA_PARAM_DEV_CPU_ID - CPU to which the CLA is attached (RO).
+ *
+ * &arm_cla_param->index is the dev_id in range (0, DEV_NR - 1).
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - dev_id (index) not in range
+ */
+#define ARM_CLA_PARAM_DEV_CPU_ID 2
+
+/**
+ * define ARM_CLA_PARAM_DEV_DOMAIN_ID - Domain to which the CLA belongs (RO).
+ *
+ * &arm_cla_param->index is the dev_id in range (0, DEV_NR - 1).
+ *
+ * A CLA domain contains a set of CLA devices whose accelerators can communicate
+ * with each other. All CLA devices within a CLA domain are atomically assigned
+ * to a single context at a time.
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - dev_id (index) not in range
+ */
+#define ARM_CLA_PARAM_DEV_DOMAIN_ID 3
+
+/**
+ * define ARM_CLA_PARAM_DEV_PGOFF - CLA's ``mmap()`` page offset (RO).
+ *
+ * &arm_cla_param->index is the dev_id in range (0, DEV_NR - 1).
+ *
+ * The page offset at which the selected CLA device's register page is exposed
+ * through ``mmap()``.
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - dev_id (index) not in range
+ */
+#define ARM_CLA_PARAM_DEV_PGOFF 4
+
+/**
+ * define ARM_CLA_PARAM_DEV_AIDR - CLA Architecture Identification Register
+ * (RO).
+ *
+ * &arm_cla_param->index is the dev_id in range (0, DEV_NR - 1).
+ *
+ * The CLA Architecture Identification Register is used to obtain version
+ * information of the CLA Programming model.
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - dev_id (index) not in range
+ */
+#define ARM_CLA_PARAM_DEV_AIDR 5
+
+/**
+ * define ARM_CLA_PARAM_DEV_ACCELS - Mask of CLA's attached accelerators (RO).
+ *
+ * &arm_cla_param->index is the dev_id in range (0, DEV_NR - 1).
+ *
+ * A bitmask describing which accelerator slots are attached on the selected CLA
+ * device. A CLA may have up to 8 attached accelerators, each with an accel_id
+ * in the range (0, 7), each with a corresponding bit in the bitmask.
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - dev_id (index) not in range
+ */
+#define ARM_CLA_PARAM_DEV_ACCELS 6
+
+/**
+ * define ARM_CLA_PARAM_ACCEL_IIDR - Accelerator IIDR register (RO).
+ *
+ * &arm_cla_param->index is ARM_CLA_PARAM_ACCEL_INDEX(accel_id, dev_id).
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - dev_id not in range
+ * * %-EINVAL - accel_id not in range
+ * * %-ENODEV - accel_id not attached to CLA
+ */
+#define ARM_CLA_PARAM_ACCEL_IIDR 7
+
+/**
+ * define ARM_CLA_PARAM_ACCEL_DEVARCH - Accelerator DEVARCH register (RO).
+ *
+ * &arm_cla_param->index is ARM_CLA_PARAM_ACCEL_INDEX(accel_id, dev_id).
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - dev_id not in range
+ * * %-EINVAL - accel_id not in range
+ * * %-ENODEV - accel_id not attached to CLA
+ */
+#define ARM_CLA_PARAM_ACCEL_DEVARCH 8
+
+/**
+ * define ARM_CLA_PARAM_ACCEL_REVIDR - Accelerator REVIDR register (RO).
+ *
+ * &arm_cla_param->index is ARM_CLA_PARAM_ACCEL_INDEX(accel_id, dev_id).
+ *
+ * Return:
+ * * %0 - OK
+ * * %-EINVAL - dev_id not in range
+ * * %-EINVAL - accel_id not in range
+ * * %-ENODEV - accel_id not attached to CLA
+ */
+#define ARM_CLA_PARAM_ACCEL_REVIDR 9
+
+/**
+ * ARM_CLA_PARAM_ACCEL_INDEX() - encode index for specific accelerator.
+ * @accel_id: accelerator id in the range (0, 7).
+ * @dev_id: CLA device id in the range (0, DEV_NR - 1).
+ *
+ * Return: encoded index for use in &arm_cla_param->index.
+ */
+#define ARM_CLA_PARAM_ACCEL_INDEX(accel_id, dev_id) \
+ (((accel_id) << 24) | (dev_id))
+
+/**
+ * ARM_CLA_PARAM_INDEX_ACCEL() - extract accel_id from index.
+ * @index: Encoded index as returned by ARM_CLA_PARAM_ACCEL_INDEX()
+ *
+ * Return: extracted accel_id.
+ */
+#define ARM_CLA_PARAM_INDEX_ACCEL(index) (((index) >> 24) & 0xff)
+
+/**
+ * ARM_CLA_PARAM_INDEX_DEV() - extract dev_id from index.
+ * @index: Encoded index as returned by ARM_CLA_PARAM_ACCEL_INDEX()
+ *
+ * Return: extracted dev_id.
+ */
+#define ARM_CLA_PARAM_INDEX_DEV(index) ((index) & 0xffffff)
+
+/**
+ * struct arm_cla_param - Get/Set CLA parameters.
+ */
+struct arm_cla_param {
+ /**
+ * @param: Param selector - One of the ARM_CLA_PARAM_* values.
+ */
+ __u64 param;
+
+ /**
+ * @index: Index for params that have multiple instances.
+ */
+ __u64 index;
+
+ /**
+ * @value: Param value.
+ */
+ __u64 value;
+};
+
+#endif /* _UAPI_LINUX_CLA_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver
2026-07-17 10:47 [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Ryan Roberts
` (7 preceding siblings ...)
2026-07-17 10:47 ` [RFC PATCH v1 8/8] misc/arm-cla: Add userspace interface Ryan Roberts
@ 2026-07-17 11:33 ` Will Deacon
2026-07-17 12:09 ` Marc Zyngier
2026-07-17 12:30 ` Ryan Roberts
8 siblings, 2 replies; 16+ messages in thread
From: Will Deacon @ 2026-07-17 11:33 UTC (permalink / raw)
To: Ryan Roberts
Cc: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Mark Rutland,
Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet, linux-kernel,
linux-arm-kernel, dri-devel, linux-doc, maz, oupton, hch, jgg
Hi Ryan,
I haven't bothered to look at the code (looks like Sashiko is having fun
with that), but I'm going to jump at this bit:
On Fri, Jul 17, 2026 at 11:47:44AM +0100, Ryan Roberts wrote:
> * User space availability: The kernel driver exposes the capabilities of the
> hardware to user space. Arm plans to open source a user space driver, but does
> not yet have any committed date. I'd like to understand if the availability of
> this component will be a prerequisite for upstream acceptance of the kernel
> driver; either way, I'm hoping we can at least progress with some discussion
> in its absence.
From my perspective, I'm not particularly interested in having code in
the upstream kernel tree that we can't meaningfully exercise or benefit
from. I also think that the incentive for Arm to open source the
user-space driver practically disappears if we merge the kernel part
first. So, at the moment, this just looks like a burden to me, especially
as it appears to create a brand new, device-specific UAPI for what is
ostensibly a form of SVA - something which the community is actively
working on already.
Relatedly, is there a spec and/or fastmodel/qemu (sorry...) support for
this?
> I'm deliberately constraining the scope to bare-metal support for now.
> Virtualization is something we are considering (and have prototyped), but plan
> to post a separate RFC for that as follow-up, once we have agreement on
> direction for the bare-metal driver.
I'd actually like to see what the virtualisation part looks like first
because doing it as a bolt-on later feels like the wrong approach. The
structure you have at the moment is remarkably clean, given the
architectural/CPU interactions (this thing even apparently builds as a
module, nice!), but I'm unsure how far you can push the separation once
you need to start hacking at KVM. Maybe the MMU notifiers are enough,
but I can't tell.
Will
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver
2026-07-17 11:33 ` [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Will Deacon
@ 2026-07-17 12:09 ` Marc Zyngier
2026-07-17 12:33 ` Ryan Roberts
2026-07-17 12:30 ` Ryan Roberts
1 sibling, 1 reply; 16+ messages in thread
From: Marc Zyngier @ 2026-07-17 12:09 UTC (permalink / raw)
To: Ryan Roberts, Will Deacon
Cc: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Mark Rutland,
Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet, linux-kernel,
linux-arm-kernel, dri-devel, linux-doc, oupton, hch, jgg
Thanks Will for roping me in.
On Fri, 17 Jul 2026 12:33:17 +0100,
Will Deacon <will@kernel.org> wrote:
>
> > I'm deliberately constraining the scope to bare-metal support for now.
> > Virtualization is something we are considering (and have prototyped), but plan
> > to post a separate RFC for that as follow-up, once we have agreement on
> > direction for the bare-metal driver.
>
> I'd actually like to see what the virtualisation part looks like first
> because doing it as a bolt-on later feels like the wrong approach. The
> structure you have at the moment is remarkably clean, given the
> architectural/CPU interactions (this thing even apparently builds as a
> module, nice!), but I'm unsure how far you can push the separation once
> you need to start hacking at KVM. Maybe the MMU notifiers are enough,
> but I can't tell.
+1.
Virtualisation cannot be a "bolt on the side" exercise. It is an
integral part of the arm64 tree, particularly for memory management
and scheduling, all of which have a direct impact on KVM.
I don't think we can really evaluate anything here without looking at
the full picture.
Thanks,
M.
--
Jazz isn't dead. It just smells funny.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver
2026-07-17 12:09 ` Marc Zyngier
@ 2026-07-17 12:33 ` Ryan Roberts
0 siblings, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 12:33 UTC (permalink / raw)
To: Marc Zyngier, Will Deacon
Cc: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Mark Rutland,
Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet, linux-kernel,
linux-arm-kernel, dri-devel, linux-doc, oupton, hch, jgg
On 17/07/2026 13:09, Marc Zyngier wrote:
> Thanks Will for roping me in.
Sorry - I intentionally didn't include you because last time we spoke you said
you were only really interested in discussions on the virt side of things and I
didn't want to spam your inbox. Perhaps the wrong decision...
>
> On Fri, 17 Jul 2026 12:33:17 +0100,
> Will Deacon <will@kernel.org> wrote:
>>
>>> I'm deliberately constraining the scope to bare-metal support for now.
>>> Virtualization is something we are considering (and have prototyped), but plan
>>> to post a separate RFC for that as follow-up, once we have agreement on
>>> direction for the bare-metal driver.
>>
>> I'd actually like to see what the virtualisation part looks like first
>> because doing it as a bolt-on later feels like the wrong approach. The
>> structure you have at the moment is remarkably clean, given the
>> architectural/CPU interactions (this thing even apparently builds as a
>> module, nice!), but I'm unsure how far you can push the separation once
>> you need to start hacking at KVM. Maybe the MMU notifiers are enough,
>> but I can't tell.
>
> +1.
>
> Virtualisation cannot be a "bolt on the side" exercise. It is an
> integral part of the arm64 tree, particularly for memory management
> and scheduling, all of which have a direct impact on KVM.
>
> I don't think we can really evaluate anything here without looking at
> the full picture.
OK understood - as per reply to Will, we'll prioritise doing a version with virt
support.
Thanks,
Ryan
>
> Thanks,
>
> M.
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver
2026-07-17 11:33 ` [RFC PATCH v1 0/8] Arm Core Local Accelerator Driver Will Deacon
2026-07-17 12:09 ` Marc Zyngier
@ 2026-07-17 12:30 ` Ryan Roberts
1 sibling, 0 replies; 16+ messages in thread
From: Ryan Roberts @ 2026-07-17 12:30 UTC (permalink / raw)
To: Will Deacon
Cc: Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Mark Rutland,
Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet, linux-kernel,
linux-arm-kernel, dri-devel, linux-doc, maz, oupton, hch, jgg
Hi Will,
On 17/07/2026 12:33, Will Deacon wrote:
> Hi Ryan,
>
> I haven't bothered to look at the code (looks like Sashiko is having fun
> with that), but I'm going to jump at this bit:
Thanks for the quick reply!
JP and I had a quick glance at the Sashiko feedback; looks like some useful
stuff, and a few false alarms. Nothing cricial though. Not that it matters too
much for this stage of discussion.
>
> On Fri, Jul 17, 2026 at 11:47:44AM +0100, Ryan Roberts wrote:
>> * User space availability: The kernel driver exposes the capabilities of the
>> hardware to user space. Arm plans to open source a user space driver, but does
>> not yet have any committed date. I'd like to understand if the availability of
>> this component will be a prerequisite for upstream acceptance of the kernel
>> driver; either way, I'm hoping we can at least progress with some discussion
>> in its absence.
>
> From my perspective, I'm not particularly interested in having code in
> the upstream kernel tree that we can't meaningfully exercise or benefit
> from. I also think that the incentive for Arm to open source the
> user-space driver practically disappears if we merge the kernel part
> first. So, at the moment, this just looks like a burden to me, especially
Understood. Now that I have such a clear statement, hopefully I can use that to
work internally to get commitments and dates to build a proper plan. What I'm
trying to get out of this RFC though, is "is having an open source user space
the main blocker, or are there other significant challenges here too?".
> as it appears to create a brand new, device-specific UAPI for what is
> ostensibly a form of SVA - something which the community is actively
> working on already.
>
> Relatedly, is there a spec and/or fastmodel/qemu (sorry...) support for
> this?
They both exist internally of course. There is a plan to publish the spec, but I
don't think we will converge on a timeframe until after the summer now. I'll
follow up regarding fastmodel.
>
>> I'm deliberately constraining the scope to bare-metal support for now.
>> Virtualization is something we are considering (and have prototyped), but plan
>> to post a separate RFC for that as follow-up, once we have agreement on
>> direction for the bare-metal driver.
>
> I'd actually like to see what the virtualisation part looks like first
> because doing it as a bolt-on later feels like the wrong approach. The
> structure you have at the moment is remarkably clean, given the
> architectural/CPU interactions (this thing even apparently builds as a
> module, nice!), but I'm unsure how far you can push the separation once
> you need to start hacking at KVM. Maybe the MMU notifiers are enough,
> but I can't tell.
The virtualization implementation is not as advanced as bare-metal and JP can
probably comment on it in more detail, but the intention is to expose the HW
using VFIO-MDEV (mediated device framework). We will need exported functions
from (the arm64 part of) KVM to grab the S2 pgdir and to get/put the VMID - we
would not be using MMU notifiers but instead sharing the pgtable and VMID and
piggybacking the CPU's TBLI operations. My current expectation is that it can be
made to work without deep KVM integration and still as a module, but let's see
where we get to with the code. (I have spoken with Marc about this briefly - I
would say he doesn't love it, but agreed to look at the code when we have it).
But I think I'm hearing that to get into a deeper discussion we will need to
post virt support? In which case, I'll prioritise getting something together.
I'm about to be on sabbatical for 4 weeks though, so probably won't be until
~early Sept.
Thanks,
Ryan
>
> Will
^ permalink raw reply [flat|nested] 16+ messages in thread