From nobody Sat Jul 25 05:27:22 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 43D633EDE4A; Fri, 17 Jul 2026 10:48:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285304; cv=none; b=AgtuBq5/Hr/k9H8qArVhBeCDO1H80uF/dcc392iiiIjVUrf7lYh2Ya/HOmm4+89GsbVrlzHldfDPOf3kfLU49L3m+oS7zTo7XFFjORG1KgVewZfoF26NGfZa62kp5YhJgLIB01oBVytfpCvoJplIo0u0Tuk0J2jVfukf3ybJukU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285304; c=relaxed/simple; bh=TeAz4Dzo5i5zwMqiutYZMxKoMBz4QgCafIr4aJksZ34=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=e8rPrGMkQs12T2eJOju2hGIr4S80UPEwNKFT/qQs51fUkWh08gAZT+/t8HYDqLYa3Yah+YM6EUaobB1R+keWomVLZ6fAXjtzcB7H3ziRx0Db8sP/YFzbKkuHnPriWA7Q+8JgJHlNDqWR8BQT6KbEwdcgP0CL6Wwk8s0o6fnVeYI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=TcMtqnsE; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="TcMtqnsE" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 5ECAF1477; Fri, 17 Jul 2026 03:48:17 -0700 (PDT) Received: from e125769.cambridge.arm.com (e125769.cambridge.arm.com [10.2.198.107]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id A5E3B3F7D8; Fri, 17 Jul 2026 03:48:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1784285301; bh=TeAz4Dzo5i5zwMqiutYZMxKoMBz4QgCafIr4aJksZ34=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TcMtqnsEeF8L1fzjX/J8XzhNYUhDx/eyZ880WwEX50dQO2HkjiVCNzhCtXuhSgeJ+ R+tpEXdS46VbTDZ9AUZIq5Ogeat9MEEORCBOmWMswLAAldqHXdu41HOCdY0rOcbsYg hblwCrpxYZzc/rBDycKHvzp5KrgjvpiqfttFBOTY= From: Ryan Roberts To: Greg Kroah-Hartman , Arnd Bergmann , Catalin Marinas , Will Deacon , Mark Rutland , Jean-Philippe Brucker , Oded Gabbay , Jonathan Corbet Cc: Ryan Roberts , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org Subject: [RFC PATCH v1 1/8] misc/arm-cla: Add driver skeleton and documentation Date: Fri, 17 Jul 2026 11:47:45 +0100 Message-ID: <20260717104759.123203-2-ryan.roberts@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260717104759.123203-1-ryan.roberts@arm.com> References: <20260717104759.123203-1-ryan.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Jean-Philippe Brucker 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 Signed-off-by: Ryan Roberts Signed-off-by: Jean-Philippe Brucker --- 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-de= vices/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 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D +Arm Core Local Accelerator (CLA) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D + +Arm CLA is an interface local to a CPU for programming accelerators that a= ccess +memory via the CPU's MMU: + + =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=AC=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=AC=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=90 =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=90 + =E2=94=82 CPU =E2=94=82 MMU =E2=94=82 CLA =E2=94=82 = =E2=94=82 Accel =E2=94=82=E2=94=80=E2=94=90 + =E2=94=82 =E2=94=82 =E2=94=82 =E2=94=82 = =E2=94=82 =E2=94=82 =E2=94=82 + =E2=94=82 ---MMIO--> <--> =E2=94=82 =E2=94= =82 + =E2=94=82 =E2=94=82 =E2=94=82 =E2=94=82 = =E2=94=82 =E2=94=82 =E2=94=82 + =E2=94=82 =E2=94=82 ,-----DMA-----> =E2=94=82= =E2=94=82 + =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=B4=E2=94=80=E2=94=80=E2=94=80=E2=94=80|=E2=94=80=E2=94= =80=E2=94=B4=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=98 =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=98 =E2=94=82 + =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80v=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=90 =E2=94=94=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=98 + =E2=94=82 Caches and memory =E2=94=82 + =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=98 + + +Hardware +=3D=3D=3D=3D=3D=3D=3D=3D + +The CLA supports up to 8 attached accelerators, which are accessed by +programming the CLA's MMIO registers. Operations are launched to an accele= rator +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 han= dling +a LAUNCH write, CLA indicates the launch status in the LRESP register. A f= urther +operation can only be launched after LRESP indicates completion of the pre= vious +launch. + +Some operations continue to run asynchronously on the accelerator after la= unch +completion. In this case progress is tracked by polling the STATUS registe= r. +When the CLA updates the STATUS register, it also raises an event which wi= ll +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 eac= h 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 low= er 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 a= lso +implicitly target the CLA. + +CLA has its own set of Memory Translation Context (MTC) registers, distinc= t 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 M= MIO +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, an= d the +registers are mapped with Device-nGnRE attribute. Invalid accesses (unalig= ned, +atomic, badly sized, etc) to the MMIO frame are either read as zero and wr= ite +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 laun= ched +on each of the participating accelerators, they synchronize over the bus, +stalling until all are ready. + + +Intended SW Usage Model +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +CLA is designed for its PL0 MMIO frame to be mapped into user space and fo= r user +space to directly launch accelerator operations and poll for completion. I= t has +been observed that for some use cases, the operation execution time is sma= ll and +a trip through the kernel would consume a significant amount of the CPU bu= dget +for preparing the next operation leading to a significant reduction in ban= dwidth +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 w= hen +polling STATUS. + +The CLA is intended to be configured, by privileged software via PL1 and/o= r PL2, +so that it shares virtual addresses with the process to which it is assign= ed +(SVA). In practice this means configuring the CLA's MTC to point to the sa= me +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 corr= ectly +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 si= nce +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 matc= h the +CLA's private VMID - therefore, for a virtualization scenario, TLB invalid= ation +becomes impossible (major). + +Because the CLA has its own MTC registers, it is correct and safe for it t= o 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, an= d CLAs +may have a different set of accelerators attached. Users need to probe aro= und 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 latenc= y. + + +Driver +=3D=3D=3D=3D=3D=3D + +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 d= evice, +but it can only be accessed from one specific CPU. CPU hotplug notifiers p= robe +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 us= er +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. W= hen +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_do= main, +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 t= he +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 int= ended +CLA. + +The accelerators access memory using the CPU's MMU. When assigning a conte= xt, +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 acceler= ator. + + +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 t= he +firmware is expected to save the CLA and accelerator states before enterin= g CPU +low-power state, and restore them after exiting. The CLA driver does not e= xpect +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) +=3D lan966x-pci.o obj-y +=3D keba/ obj-y +=3D amd-sbi/ obj-$(CONFIG_MISC_RP1) +=3D rp1/ +obj-$(CONFIG_ARM_CLA) +=3D 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 +#include + +/* 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 + +#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_ */ --=20 2.43.0 From nobody Sat Jul 25 05:27:22 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 40EAA3ED5A7; Fri, 17 Jul 2026 10:48:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285310; cv=none; b=XwRJrO0wYeXTkfJc7XISEDJjAQI8oQa4MMbewh3/0XrO82Z2jzrb6jepxlwOySkaqez7IWEpB4PZBPIjXVjQSmQ/H7NLUk15SJbPLtWudNLSna4+PinzHTKnmgP0IthLMw2sWtjTEBCtNPplADZRiqo7JjcFjjF68r6Vnab9dxE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285310; c=relaxed/simple; bh=M3lQjBH3mo1sMqyWrYmtwxYC4XzZifrPrCwjDq5ZBXc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MhfR4HET7oafSEVZXOyx77wtn/anIvWLDB4np6VZh0nfIGf7ZnmGkRvykBXLcX98IK55rUgag3oLrtfEAy0PvOx/lt9PiiADnoJ99cgO91AJovbGkxd3JLvU1ChYRKePUVNat/+n5u7L6Z6NFT7+zlCX9P8N6bDidXSnGOEenl4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=cJgfiMyz; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="cJgfiMyz" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 65F2C1476; Fri, 17 Jul 2026 03:48:19 -0700 (PDT) Received: from e125769.cambridge.arm.com (e125769.cambridge.arm.com [10.2.198.107]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id CB3313F7D8; Fri, 17 Jul 2026 03:48:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1784285303; bh=M3lQjBH3mo1sMqyWrYmtwxYC4XzZifrPrCwjDq5ZBXc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cJgfiMyzr4hAsdGyqLrDgeDsXQcFIC1Bp+XaYgE5uSear1jwwNWNqgC0X9ZAe+iHz us2qcoPEpHV03mVh//mr7qig09B0iwNvNjSyT3WMLwCUu0yKZ5NsWQ6MWGiG/7vOIA R7tbHF4pvvlvjVb4No9CRXRkltgmfyqsQXBBGYY8= From: Ryan Roberts To: Greg Kroah-Hartman , Arnd Bergmann , Catalin Marinas , Will Deacon , Mark Rutland , Jean-Philippe Brucker , Oded Gabbay , Jonathan Corbet Cc: Ryan Roberts , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org Subject: [RFC PATCH v1 2/8] misc/arm-cla: Add launch operation helpers Date: Fri, 17 Jul 2026 11:47:46 +0100 Message-ID: <20260717104759.123203-3-ryan.roberts@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260717104759.123203-1-ryan.roberts@arm.com> References: <20260717104759.123203-1-ryan.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Jean-Philippe Brucker 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 --- 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 :=3D \ + cla-init.o \ + cla-ops.o + +obj-$(CONFIG_ARM_CLA) +=3D 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_ =20 +#include +#include #include =20 #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; }; =20 @@ -35,4 +39,21 @@ struct cla_dev { #define cla_err(dev, fmt, ...) \ dev_err((dev)->dev, "[%u] " fmt, (dev)->cpu, ##__VA_ARGS__) =20 +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 r= egidx, + 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-ini= t.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 + +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 + +#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->dat= a. + * + * 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() !=3D dev->cpu)) + return -EINVAL; + + if (launch->data_mode =3D=3D CLA_DATA_IN) + for (i =3D 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 =3D 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 =3D FIELD_GET(CLA_LRESP_ERRCODE, lresp); + return -EIO; + } + + if (launch->data_mode =3D=3D CLA_DATA_OUT) + for (i =3D 0; i < launch->ndata_m1 + 1; i++) + launch->data[i] =3D 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) =3D=3D 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 =3D { + .op =3D CLA_LAUNCH_OP_RESET, + .accid =3D accid, + }; + + ret =3D cla_op_launch(dev, &launch); + if (ret =3D=3D -EIO && launch.errcode =3D=3D 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 =3D -EINVAL; + unsigned long max_regidx; + struct cla_launch launch =3D { + .op =3D op, + .accid =3D accid, + .data_mode =3D data_mode, + }; + + switch (op) { + case CLA_LAUNCH_OP_REGREAD: + case CLA_LAUNCH_OP_REGWRITE: + max_regidx =3D 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 =3D ALIGN_DOWN(regidx, 8); + unsigned int max_reg =3D min(regidx + nregs, reg_group + 8); + unsigned int ndata =3D max_reg - regidx; + + launch.ndata_m1 =3D ndata - 1; + launch.regidx =3D regidx; + launch.data =3D regs; + + ret =3D cla_op_launch(dev, &launch); + if (ret) + break; + + regidx +=3D ndata; + regs +=3D ndata; + nregs -=3D 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 multi= ple + * 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 multi= ple + * 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); +} --=20 2.43.0 From nobody Sat Jul 25 05:27:22 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 63BD53CE083; Fri, 17 Jul 2026 10:48:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285310; cv=none; b=T5KaKzrCjA7TpBw/19QDPGut/Ngpx606nDtWoz3sHaJf9sH1+/TT6bmDgWhFN1GXO1DtayyZW7s4Zsr5XYiTrMUmGULOFZuDOLtYXzTKUvm/SK3uMT8Ltc38NEpDZmpyxyJ0m9tsnHo4Xe9Tiyqp80nCvvNwVrR6qQjO/rDgkkc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285310; c=relaxed/simple; bh=VryYZLyCRAnwMcaOIAx6JrVaT4F3efUezY/4MUedNvg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GukZNVz9DXWxJU3qCLDbU47dtICzMtE6Lp55eJ2/ICopke6wPTRs/7N3GErof55Z9Nkl2g4q2fTkoCAQWaroEP4F+gK2RrsFYg2a5Ed7pvNp5Xzs9SiM1IP3KIoRS27Q+aX+QGVJaR1VXG0k8KJXOe0g0LA8qotSSNcEosENVEA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=C5IvS6yA; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="C5IvS6yA" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 8EFB91477; Fri, 17 Jul 2026 03:48:21 -0700 (PDT) Received: from e125769.cambridge.arm.com (e125769.cambridge.arm.com [10.2.198.107]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id D47543F7D8; Fri, 17 Jul 2026 03:48:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1784285305; bh=VryYZLyCRAnwMcaOIAx6JrVaT4F3efUezY/4MUedNvg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=C5IvS6yA+kuEGk1LOdTwqhdaYiIj/0x0Th+FNJwdHilzi8nWIOtc7JnH1d9hYyb0b LaWYVGNz/sWtpL+zXGfC9YYPV/KqwZmZoXz9aEnzMqANcttCojWWMxNbRzbCqalU0Y HQIfKPJeUpuHQIfuQ7j4j9WvSyFrAhJPvrlxAE+g= From: Ryan Roberts To: Greg Kroah-Hartman , Arnd Bergmann , Catalin Marinas , Will Deacon , Mark Rutland , Jean-Philippe Brucker , Oded Gabbay , Jonathan Corbet Cc: Ryan Roberts , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org Subject: [RFC PATCH v1 3/8] misc/arm-cla: Probe firmware-described devices Date: Fri, 17 Jul 2026 11:47:47 +0100 Message-ID: <20260717104759.123203-4-ryan.roberts@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260717104759.123203-1-ryan.roberts@arm.com> References: <20260717104759.123203-1-ryan.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Jean-Philippe Brucker 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 Signed-off-by: Ryan Roberts Signed-off-by: Jean-Philippe Brucker --- 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 @@ =20 arm-cla-y :=3D \ cla-init.o \ - cla-ops.o + cla-ops.o \ + cla-topology.o =20 obj-$(CONFIG_ARM_CLA) +=3D 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 =20 +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; }; =20 +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__) =20 +#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, of= f_t reg, u64 val) return writeq_relaxed(val, dev->regs + reg); } =20 +/* + * 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 r= egidx, diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-ini= t.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. */ =20 +#include #include +#include +#include +#include + +#include + +#include "arm-cla.h" + +static int cla_cpuhp_state =3D -1; + +static int cla_dev_setup(unsigned int cpu) +{ + struct cla_dev *dev; + + dev =3D cla_lut_cpu[cpu]; + if (!dev) + return 0; + + if (WARN_ON(smp_processor_id() !=3D cpu || dev->cpu !=3D 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 =3D 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 =3D of_property_read_u32(of_node, "cpu", &cpu_phandle); + if (WARN_ON(ret)) + return -EINVAL; + + cpu_node =3D of_find_node_by_phandle(cpu_phandle); + if (WARN_ON(!cpu_node)) + return -EINVAL; + + cpu =3D 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 =3D devm_kzalloc(parent, sizeof(*dev), GFP_KERNEL); + if (!dev) + return ERR_PTR(-ENOMEM); + + dev->pfn =3D __phys_to_pfn(base); + dev->regs =3D cla_get_regs(regs, cla_kernel_pl); + dev->cpu =3D cpu; + dev->dev =3D parent; + + /* Attempt to find device domain, or allocate a new one */ + dev->domain =3D 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 =3D 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 =3D resource_size(res); + if (reg_size <=3D CLA_FRAME_SIZE) { + /* A single CLA. We need information about its CPU. */ + cpu =3D 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 =3D 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[] =3D { + {.compatible =3D "arm,cla",}, + {}, +}; +MODULE_DEVICE_TABLE(of, cla_of_match); + +static struct platform_driver cla_driver =3D { + .driver =3D { + .name =3D "arm-cla", + .of_match_table =3D cla_of_match, + }, + .probe =3D 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 =3D platform_driver_probe(&cla_driver, cla_probe); + if (ret) { + if (ret !=3D -ENODEV) + pr_err("arm-cla: probe failed with %d\n", ret); + /* Some domains may have been created during probe */ + goto err_domains_free; + } + + ret =3D 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 =3D 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 =3D 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); =20 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 iso= lated + * from each other. They are owned by a single user at a time. + * + * Copyright 2026 Arm Limited. + */ + +#include +#include +#include +#include +#include + +#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 sp= arse, + * 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 i= nt id) +{ + int ret; + struct cla_domain *domain; + + domain =3D kzalloc_obj(*domain); + if (!domain) + return ERR_PTR(-ENOMEM); + + domain->id =3D id; + ret =3D xa_insert(&cla_domains, id, domain, GFP_KERNEL); + if (ret < 0) + goto err_free_domain; + + domain->nr_devs =3D 1; + domain->devs =3D kzalloc_obj(*domain->devs); + if (!domain->devs) { + ret =3D -ENOMEM; + goto err_free_id; + } + domain->devs[0] =3D 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 =3D of_property_read_u32(dev->dev->of_node, "domain", &domain_id); + if (WARN_ON(ret)) + return ERR_PTR(-EINVAL); + + domain =3D xa_load(&cla_domains, domain_id); + if (domain) { + domain->nr_devs++; + domain->devs =3D 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] =3D dev; + return domain; + } + + domain =3D cla_domain_alloc(dev, domain_id); + if (IS_ERR(domain)) + return domain; + + cla_nr_domains =3D 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 =3D -ENOMEM; + unsigned int i, j; + unsigned int pg_offset =3D 0; + + cla_lut_cpu =3D kzalloc_objs(*cla_lut_cpu, nr_cpu_ids); + if (!cla_lut_cpu) + goto err_free; + + cla_lut_pg =3D kzalloc_objs(*cla_lut_pg, cla_nr_devs); + if (!cla_lut_pg) + goto err_free; + + ret =3D -EINVAL; + + /* Populate the lookup tables. */ + for (i =3D 0; i < cla_nr_domains; i++) { + struct cla_domain *domain =3D xa_load(&cla_domains, i); + + /* The user API requires sequential domain IDs */ + if (WARN_ON(!domain)) + goto err_free; + + domain->pg_offset =3D pg_offset; + + for (j =3D 0; j < domain->nr_devs; j++) { + struct cla_dev *dev =3D domain->devs[j]; + + if (WARN_ON(dev->cpu >=3D nr_cpu_ids) || + WARN_ON(pg_offset >=3D 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] =3D dev; + cla_lut_pg[pg_offset] =3D dev; + dev->pg_offset =3D pg_offset; + pg_offset++; + } + } + + return 0; + +err_free: + kfree(cla_lut_cpu); + kfree(cla_lut_pg); + cla_lut_cpu =3D NULL; + cla_lut_pg =3D 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 =3D NULL; + cla_lut_pg =3D NULL; + xa_for_each(&cla_domains, id, domain) + cla_domain_free(domain); + cla_nr_domains =3D 0; +} --=20 2.43.0 From nobody Sat Jul 25 05:27:22 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 63C6E3DDB02; Fri, 17 Jul 2026 10:48:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285311; cv=none; b=UUO/8p/fR4hO4OKB1SgFjkY8d8wFEwCTQcnxcGurKvuYhRi4NBub8ohOGYCrEo6BhqWkDOOcffKXC88W/D9/B3Zu1Xh+oa8pKMBoyl2mDyzTgj8xJtIdjkfwm0OsuhjwsO0l/9HP0g+odP1DlXqSO3t6Ho3ikYjbHXcydoTPmP0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285311; c=relaxed/simple; bh=hL8zKEfkHR6cwYhCGYTeqo96eLtnIhcZah/Vhkb5Kyw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ILnX8jc6wFvFFiWRuPq0MayT8Da3qJe9jsh1/78VO0bJom6ShBB5kYHj46SgFso85Kzz1Bpm+YCnJk6E7G/HV2k9+IoMRDYSxbmqae4qSjDNZ3YqeuWVv9hSSI6G3Ro21zZ8LXfQYx11gscNXrkRomL045jYs2dBJWM/tqqd2dI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=QEEiw3Uf; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="QEEiw3Uf" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 99A3C169E; Fri, 17 Jul 2026 03:48:23 -0700 (PDT) Received: from e125769.cambridge.arm.com (e125769.cambridge.arm.com [10.2.198.107]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 05CCF3F7D8; Fri, 17 Jul 2026 03:48:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1784285307; bh=hL8zKEfkHR6cwYhCGYTeqo96eLtnIhcZah/Vhkb5Kyw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QEEiw3Uf+kHm/zL1nFZBxldILQjzxBFtjhfH7EA92tF9OyM93+HNtLYZ9vPDFTQfZ UnECFi4Mu3zS9ouFkdwePVIYiEmBC6juhEOfM0DrXtx7xq3o5DWIyZ+iC6rUoj9Th3 yj2UICSyOOTN2JPHcBs/PsQ8B3E1oy19hU4DDskw= From: Ryan Roberts To: Greg Kroah-Hartman , Arnd Bergmann , Catalin Marinas , Will Deacon , Mark Rutland , Jean-Philippe Brucker , Oded Gabbay , Jonathan Corbet Cc: Ryan Roberts , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org Subject: [RFC PATCH v1 4/8] misc/arm-cla: Initialize devices on CPU bringup Date: Fri, 17 Jul 2026 11:47:48 +0100 Message-ID: <20260717104759.123203-5-ryan.roberts@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260717104759.123203-1-ryan.roberts@arm.com> References: <20260717104759.123203-1-ryan.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Jean-Philippe Brucker 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 --- 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; }; =20 /** @@ -62,6 +71,10 @@ extern struct cla_dev **cla_lut_cpu; extern struct cla_dev **cla_lut_pg; extern unsigned int cla_nr_devs; =20 +#define cla_for_each_accid(dev, accid) \ + for ((accid) =3D 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); =20 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 r= egidx, 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-ini= t.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 @@ =20 static int cla_cpuhp_state =3D -1; =20 +static int cla_reset_ts(struct cla_dev *dev, unsigned int accid) +{ + int ret; + u64 reg; + + ret =3D 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 =3D FIELD_PREP(CLA_TSCTRLOWNER_PL, cla_kernel_pl); + ret =3D cla_op_regwrite(dev, accid, CLA_REG_TSCTRLOWNER, 1, ®); + if (!ret) { + reg =3D FIELD_PREP(CLA_TSCTRL_TS, CLA_TSCTRL_PHYSICAL); + ret =3D cla_op_regwrite(dev, accid, CLA_REG_TSCTRL, 1, ®); + if (ret) + return ret; + } + + reg =3D FIELD_PREP(CLA_TSOFFOWNER_PL, cla_kernel_pl); + ret =3D cla_op_regread(dev, accid, CLA_REG_TSOFFOWNER, 1, ®); + if (!ret) { + reg =3D 0; + ret =3D cla_op_regwrite(dev, accid, CLA_REG_TSVOFF, 1, ®); + if (ret) + return ret; + ret =3D 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 =3D FIELD_PREP(CLA_PMUOWNER_PL, cla_kernel_pl); + ret =3D cla_op_regwrite(dev, accid, CLA_REG_PMUOWNER, 1, ®); + if (!ret) { + reg =3D 0; + ret =3D 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 usabl= e, 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 =3D cla_op_reset(dev, accid); + if (ret) + return ret; + + status =3D cla_reg_read(dev, CLA_REG_STATUS(accid)); + if ((status & CLA_STATUS_STATE_MASK) !=3D 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 =3D cla_reset_ts(dev, accid); + if (ret) + cla_err(dev, "[%u] could not reset TS: %d\n", accid, ret); + + ret =3D 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 =3D dev->broken; + mutex_unlock(&dev->lock); + if (broken) + return; + + if (WARN_ON(cla_op_reset_all(dev))) { + mutex_lock(&dev->lock); + dev->broken =3D 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 =3D 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 =3D 0; =20 dev =3D cla_lut_cpu[cpu]; if (!dev) return 0; =20 + mutex_lock(&dev->lock); + broken =3D dev->broken; + mutex_unlock(&dev->lock); + if (broken) + return 0; + if (WARN_ON(smp_processor_id() !=3D cpu || dev->cpu !=3D cpu)) return -EINVAL; =20 + /* Clear DATA and LRESP_DATANZ */ + for (i =3D 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 =3D 0; accid < CLA_NUM_ACC; accid++) { + ret =3D cla_dev_setup_accel(dev, accid); + if (ret > 0) + continue; + else if (ret < 0) + goto err; + + dev->accelerators |=3D (1 << accid); + plxctrl_val |=3D 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; } =20 static int cla_dev_teardown(unsigned int cpu) @@ -43,6 +217,8 @@ static int cla_dev_teardown(unsigned int cpu) if (!dev) return 0; =20 + cla_dev_reinit(dev); + return 0; } =20 @@ -84,6 +260,8 @@ static struct cla_dev *cla_dev_alloc(struct device *pare= nt, int cpu, dev->cpu =3D cpu; dev->dev =3D parent; =20 + mutex_init(&dev->lock); + /* Attempt to find device domain, or allocate a new one */ dev->domain =3D 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 acc= id) return ret; } =20 +/** + * 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 =3D 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, --=20 2.43.0 From nobody Sat Jul 25 05:27:22 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 682223EC691; Fri, 17 Jul 2026 10:48:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285316; cv=none; b=n2H+ouGo/HkJPhIgAPsKxdsjIf5OQqiOZVQazdwCY5UfjjbJytRhljPZQZhAe1ZrySpN8K2oJ4tAAF4ePvVawFZ7uxGrRhFxCPaFw/waLpf8WIxccDkmuDgGTT5XzJNi/sxeRF1HRb+atEM0l6a3cq2WivXIAwwsHyiDCBEwRT0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285316; c=relaxed/simple; bh=h0EEXQNHC9ihxehacPaDoP+3zl/YwSV/oonbudN70L0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KHUasZGCa7wGkCoHIFIOZ427hfJRI0dpw0tnyJcJy1HwkS3r908DzaJDHRZmzntm6g+lds7/jjCKfF6lSaPjf2YPwQJz4Ze22xgPSNGWFcoVQkwdDTIQdu/Qi+PQn3CQjvqr/jXgXI2KXciem8MRQXh3RPUD0a4gZHTtya/JMUU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=qKWqs1tY; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="qKWqs1tY" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id A08D316A3; Fri, 17 Jul 2026 03:48:25 -0700 (PDT) Received: from e125769.cambridge.arm.com (e125769.cambridge.arm.com [10.2.198.107]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 0ECF73F7D8; Fri, 17 Jul 2026 03:48:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1784285309; bh=h0EEXQNHC9ihxehacPaDoP+3zl/YwSV/oonbudN70L0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qKWqs1tY4kGLvj0gph/UT0cDasG2iUSxFJk9CnGa6FFPiPJoH5EQy29uMxr97/1d/ wEkd91gR/EF+z4DZE8nzCyw0QSKBraxhHKuDDKMFQn/7KoXTjwo5aZLdmmtK91VrRq 3jSY0+QxsYnyBPyggY6M0LbPWGM2YQ/m1zNBRos4= From: Ryan Roberts To: Greg Kroah-Hartman , Arnd Bergmann , Catalin Marinas , Will Deacon , Mark Rutland , Jean-Philippe Brucker , Oded Gabbay , Jonathan Corbet Cc: Ryan Roberts , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org Subject: [RFC PATCH v1 5/8] misc/arm-cla: Accelerator context save and restore Date: Fri, 17 Jul 2026 11:47:49 +0100 Message-ID: <20260717104759.123203-6-ryan.roberts@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260717104759.123203-1-ryan.roberts@arm.com> References: <20260717104759.123203-1-ryan.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Jean-Philippe Brucker 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 --- 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 :=3D \ cla-init.o \ cla-ops.o \ + cla-regs.o \ cla-topology.o =20 obj-$(CONFIG_ARM_CLA) +=3D 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; =20 struct mutex lock; @@ -65,6 +67,23 @@ struct cla_domain { struct cla_dev **devs; }; =20 +/** + * 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 a= ccid, 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 **reg= s); =20 #endif /* _ARM_CLA_H_ */ diff --git a/drivers/misc/arm-cla/cla-init.c b/drivers/misc/arm-cla/cla-ini= t.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 in= t accid) static int cla_dev_setup_accel(struct cla_dev *dev, unsigned int accid) { u64 status; + u64 iassize; + u64 acap; int ret; =20 /* @@ -106,6 +108,26 @@ static int cla_dev_setup_accel(struct cla_dev *dev, un= signed int accid) return -EIO; } =20 + /* + * 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 =3D 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 =3D cla_op_regread(dev, accid, CLA_REG_IASSIZE, 1, &iassize); + if (ret) + return ret; + if (FIELD_GET(CLA_ACAP_REGSTATE, acap) && iassize) + dev->iassizes +=3D 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 =3D { + .op =3D CLA_LAUNCH_OP_ENTERSR, + .ndata_m1 =3D 7, + .accid =3D accid, + .data_mode =3D srstate ? CLA_DATA_OUT : CLA_DATA_NONE, + .data =3D 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 =3D { + .op =3D CLA_LAUNCH_OP_EXITSR, + .ndata_m1 =3D 7, + .accid =3D accid, + .data_mode =3D CLA_DATA_IN, + .data =3D srstate, + }; + + return cla_op_launch(dev, &launch); +} diff --git a/drivers/misc/arm-cla/cla-regs.c b/drivers/misc/arm-cla/cla-reg= s.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 + +#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 =3D regs->srstate[accid]; + + ret =3D 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 =3D cla_reg_read(dev, CLA_REG_STATUS(accid)); + if ((status & CLA_STATUS_STATE_MASK) !=3D 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 =3D FIELD_GET(CLA_SRSTATE_0_REGSTATE, srstate[0]); + if (WARN_ON(*regstate_off + regstate_size * 8 > dev->iassizes)) + return -ENOSPC; + + if (regstate_size) { + ret =3D 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 +=3D regstate_size; + + return 0; +} + +/* + * Errors are very unlikely, but if they happen the device is left in SRMO= DE. + */ +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 =3D 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 =3D cla_reg_read(dev, CLA_REG_STATUS(accid)); + if ((status & CLA_STATUS_STATE_MASK) !=3D CLA_STATUS_STATE_IDLE) { + cla_err(dev, "unexpected status 0x%llx\n", status); + return -EIO; + } + + ret =3D cla_op_entersr(dev, accid, NULL); + if (ret) + return ret; + + WARN_ON(FIELD_GET(CLA_SRSTATE_0_SROP, srstate[0])); + + regstate_size =3D FIELD_GET(CLA_SRSTATE_0_REGSTATE, srstate[0]); + if (WARN_ON(*regstate_off + regstate_size * 8 > dev->iassizes)) + return -ENOSPC; + + if (regstate_size) { + ret =3D 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 +=3D 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 =3D 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 =3D cla_op_wait_lresp(dev, ®s->lresp); + if (ret) + return ret; + + if (save_regs) { + for (i =3D 0; i < CLA_NUM_DATA_REGS; i++) + regs->data[i] =3D cla_reg_read(dev, CLA_REG_DATA(i)); + + cla_for_each_accid(dev, accid) { + ret =3D cla_regs_save_accel(dev, accid, regs, ®state_off); + if (ret) + return ret; + } + + regs->accel_valid =3D 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 =3D 0; + + cla_for_each_accid(dev, accid) { + if (regs->accel_valid) { + ret =3D 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 =3D 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 failur= e. + */ +struct cla_regs **cla_regs_alloc_domain(struct cla_domain *domain) +{ + int i; + size_t size; + struct cla_regs **regs_ptrs; + + regs_ptrs =3D kmalloc_objs(*regs_ptrs, domain->nr_devs, + GFP_KERNEL_ACCOUNT); + if (!regs_ptrs) + return NULL; + + for (i =3D 0; i < domain->nr_devs; i++) { + struct cla_regs *regs; + struct cla_dev *dev =3D 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 =3D sizeof(*regs) + dev->iassizes; + regs =3D kvzalloc_node(size, GFP_KERNEL_ACCOUNT, + cpu_to_node(dev->cpu)); + if (!regs) + goto err_free; + + regs_ptrs[i] =3D regs; + } + + return regs_ptrs; + +err_free: + for (i--; i >=3D 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 **reg= s) +{ + int i; + + for (i =3D 0; i < domain->nr_devs; i++) + kvfree(regs[i]); + kfree(regs); +} --=20 2.43.0 From nobody Sat Jul 25 05:27:22 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 2CEB93EE1C6; Fri, 17 Jul 2026 10:48:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285316; cv=none; b=YMBlQuKdH0L2QRMuqdQS0cf0yBicliH1CWWWUByIrcUUzIltIoAhvUqYM7UqNH4F5gsWZmrbyQjC7/RsAemMFWZaom+9rfMQKWE3YV0i4FiOKnIq3+1uvdYmVaxr/zXshvsSqxlKqg1LujuR0lEYm0p+iM4HOlmhCW+El692YSk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285316; c=relaxed/simple; bh=chWPSehmlJZgltcURJ8jpDXdqkFNEE2jEXjuTj8IlJA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=IfNWx96ewr2G7BQveLwEoV7rt78HqFwd0ou2u4+fCcUiqwg7eY9/6xpuWnFqYPuEQtqk+Ni/j+zyU2jNoZsWi21ozu+OCv9K+BnioZjqm1zFB/JuveNq3t7ysh+BgOesQ0NRw6MezO9dcDmYVmqEEcudaFF6NrtCcJIBSYnhLTg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=tGxXA+uw; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="tGxXA+uw" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id A817A1476; Fri, 17 Jul 2026 03:48:27 -0700 (PDT) Received: from e125769.cambridge.arm.com (e125769.cambridge.arm.com [10.2.198.107]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 17A223F7D8; Fri, 17 Jul 2026 03:48:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1784285311; bh=chWPSehmlJZgltcURJ8jpDXdqkFNEE2jEXjuTj8IlJA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tGxXA+uwTwe+5IAPkv+RgC+rlgm1nBtbXuXBFFCmD9qJF9O7rphbetW8TsZwVMROT LNGYwVotfKxroDH7XHuriM0/UUIF5adE4tH8Tq6oQHVJCrTj5zQn9kpfNFqQSnFHaQ Zo3YmzS2jq5lBf267YwscGarVCu0P8UiQSuvhq24= From: Ryan Roberts To: Greg Kroah-Hartman , Arnd Bergmann , Catalin Marinas , Will Deacon , Mark Rutland , Jean-Philippe Brucker , Oded Gabbay , Jonathan Corbet Cc: Ryan Roberts , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org Subject: [RFC PATCH v1 6/8] misc/arm-cla: Set up memory translation context Date: Fri, 17 Jul 2026 11:47:50 +0100 Message-ID: <20260717104759.123203-7-ryan.roberts@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260717104759.123203-1-ryan.roberts@arm.com> References: <20260717104759.123203-1-ryan.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Jean-Philippe Brucker Signed-off-by: Ryan Roberts --- 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 @@ =20 arm-cla-y :=3D \ 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 a= ccid, 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); =20 @@ -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 **reg= s); =20 +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-ini= t.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; =20 - 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 =3D true; mutex_unlock(&dev->lock); @@ -217,9 +218,14 @@ static int cla_dev_setup(unsigned int cpu) =20 cla_reg_write(dev, CLA_REG_PL0CTRL, plxctrl_val); =20 - if (dev->accelerators) + if (dev->accelerators) { + ret =3D cla_mtc_setup(dev); + if (ret) + goto err; + cla_info(dev, "available accelerators: 0x%02x\n", dev->accelerators); + } =20 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 + +#include "arm-cla.h" + +#define cla_kernel_mtc \ + (cla_kernel_pl =3D=3D 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] =3D {}; + u64 *kregs =3D ®s[cla_kernel_mtc]; + size_t regs_size =3D cla_kernel_pl * CLA_MTC_PL_SIZE; + u64 reserved_ttbr =3D phys_to_ttbr(page_to_phys(ZERO_PAGE(0))); + + /* The accelerator always runs as EL0 */ + kregs[CLA_MTC_PSTATE] =3D FIELD_PREP(CLA_MTC_PSTATE_EL, 0) | + FIELD_PREP(CLA_MTC_PSTATE_PAN, + system_uses_hw_pan()); + kregs[CLA_MTC_TTBR0] =3D reserved_ttbr; + kregs[CLA_MTC_TTBR1] =3D reserved_ttbr; + kregs[CLA_MTC_SCTLR] =3D read_sysreg(sctlr_el1); + kregs[CLA_MTC_MAIR] =3D read_sysreg(mair_el1); + + if (cpus_have_final_cap(ARM64_HAS_TCR2)) + kregs[CLA_MTC_TCR2] =3D read_sysreg_s(SYS_TCR2_EL1); + + tcr =3D 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] =3D tcr; + + if (cla_kernel_pl =3D=3D 2) { + /* E2H and TGE */ + regs[CLA_MTC_HCR_EL2] =3D 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=3D1 before we have + * configured MAIR or TCR2. + */ + ret =3D 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] =3D {}; + size_t regs_size =3D 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=3D0. + */ + 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 =3D phys_to_ttbr(virt_to_phys(pgd)) | + FIELD_PREP(TTBRx_EL1_ASID_MASK, asid); + + if (system_supports_cnp()) + ttbr0 |=3D 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 =3D 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 o= p, case CLA_LAUNCH_OP_REGWRITE: max_regidx =3D 0x100000000; break; + case CLA_LAUNCH_OP_SETCTX: + case CLA_LAUNCH_OP_GETCTX: + max_regidx =3D 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); } =20 +/** + * 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. --=20 2.43.0 From nobody Sat Jul 25 05:27:22 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id AB1183EE1C5; Fri, 17 Jul 2026 10:48:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285317; cv=none; b=JeZ0mWTVqzIwEZYZm3J35YJMoOR+vVAzcUsag3FteR+RUaiNioOV2O/pS128UL4kd8MUIdvcNiHWckp1CmrrVwK4solFJEW2PCEJyNPk6iDOE2nchQONIYR2IOvPhTffOs8tNgQfDGmyhioOdiRt3r7Kdoi3ENHPaV2IVD/39Z0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285317; c=relaxed/simple; bh=9cPPT0hhQ4cO7VIRLqsU2E5h9uEzkzPUVVwnMnUldOs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pC5nIJL2GERcs+TYy9uEsUGfcwZ1abomDo/aHSmN3ODB2GetY9ksVe1zTkkYzQKJBbLr5OjUNL0Z5RCamm+nELXkKNj2iz1xBgn/sMW9qv2DEdZ7v48g9o6/RGpLw4cDs4hl8Z5vRAbJexH6ikePeBRRBNgosW4FCIyQohTUK3w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=NItu6mby; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="NItu6mby" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id D7A27169E; Fri, 17 Jul 2026 03:48:29 -0700 (PDT) Received: from e125769.cambridge.arm.com (e125769.cambridge.arm.com [10.2.198.107]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 2171A3F7D8; Fri, 17 Jul 2026 03:48:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1784285314; bh=9cPPT0hhQ4cO7VIRLqsU2E5h9uEzkzPUVVwnMnUldOs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NItu6mbyTmXQIaqf6zBOV+8styqpg93BBRgFxoCtT51N2/2uc9g/YkIi5mGqcgpSn DX/1k3E1W/5oFTAb7wXEUG2gs2iPAt58JbxFfJjni23EAa+kX7F+xKPCjvLEtetgJN PQpQj8/p6lKLJrAgq2CuDwWP3IndZZWXnLF70kzE= From: Ryan Roberts To: Greg Kroah-Hartman , Arnd Bergmann , Catalin Marinas , Will Deacon , Mark Rutland , Jean-Philippe Brucker , Oded Gabbay , Jonathan Corbet Cc: Ryan Roberts , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org Subject: [RFC PATCH v1 7/8] misc/arm-cla: Manage domain contexts Date: Fri, 17 Jul 2026 11:47:51 +0100 Message-ID: <20260717104759.123203-8-ryan.roberts@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260717104759.123203-1-ryan.roberts@arm.com> References: <20260717104759.123203-1-ryan.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Jean-Philippe Brucker Signed-off-by: Ryan Roberts --- 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 @@ =20 arm-cla-y :=3D \ cla-init.o \ + cla-ctx.o \ cla-mtc.o \ cla-ops.o \ cla-regs.o \ + cla-sched.o \ cla-topology.o =20 obj-$(CONFIG_ARM_CLA) +=3D 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_ =20 +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include =20 #include "arm-cla-regs.h" =20 @@ -18,8 +30,21 @@ #define CLA_NUM_DATA_REGS 8 #define CLA_SRSTATE_LEN 8 =20 +/* Quantum of CLA assignment */ +#define CLA_SLICE_MS 100 + struct cla_domain; =20 +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 { =20 struct mutex lock; bool broken; + struct kthread_worker *worker; + struct rw_semaphore worker_sem; + struct cla_call_on_cpu call; }; =20 /** * 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; }; =20 /** @@ -84,6 +140,46 @@ struct cla_regs { u64 regstate[]; }; =20 +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 *m= m, + 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) =3D=3D 0; +} + +void cla_dev_switch_ctx(struct kthread_work *work); =20 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 +#include +#include +#include +#include + +#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 i= nsert + * a new context into @domain. The context mapping count is incremented be= fore + * 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 *m= m, + struct file *file) +{ + struct cla_ctx *ctx; + int ret =3D -ENOMEM; + + mutex_lock(&domain->lock); + if (domain->broken) { + ret =3D -EIO; + goto err_unlock; + } + + ctx =3D cla_domain_lookup_ctx(domain, mm, file); + if (ctx) { + refcount_inc(&ctx->mapped); + mutex_unlock(&domain->lock); + return ctx; + } + + ctx =3D kzalloc_obj(*ctx, GFP_KERNEL_ACCOUNT); + if (!ctx) + goto err_unlock; + + kref_init(&ctx->refcnt); + refcount_set(&ctx->mapped, 1); + ctx->domain =3D domain; + ctx->regs =3D cla_regs_alloc_domain(domain); + if (!ctx->regs) + goto err_free; + + mmgrab(mm); + ctx->key.mm =3D mm; + get_file(file); + ctx->key.file =3D file; + INIT_LIST_HEAD(&ctx->queue_node); + init_waitqueue_head(&ctx->waitq); + + ret =3D 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 =3D 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 =3D=3D 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 drop= ped. + */ +void cla_ctx_free(struct kref *ref) +{ + struct cla_ctx *ctx =3D 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-ini= t.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 =3D true; mutex_unlock(&dev->lock); + cla_domain_set_broken(dev->domain); return; } =20 @@ -171,6 +172,46 @@ static void cla_dev_reinit(struct cla_dev *dev) cla_reg_write(dev, CLA_REG_LRESP, 0); } =20 +static int cla_dev_worker_init(struct cla_dev *dev, int cpu) +{ + struct kthread_worker *worker; + + worker =3D 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 =3D 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 =3D dev->worker; + dev->worker =3D 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); } =20 + ret =3D 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; =20 + cla_dev_worker_destroy(dev); cla_dev_reinit(dev); =20 return 0; @@ -289,6 +335,8 @@ static struct cla_dev *cla_dev_alloc(struct device *par= ent, int cpu, dev->dev =3D parent; =20 mutex_init(&dev->lock); + init_rwsem(&dev->worker_sem); + kthread_init_work(&dev->call.switch_ctx, cla_dev_switch_ctx); =20 /* Attempt to find device domain, or allocate a new one */ dev->domain =3D cla_dev_domain_get(dev); diff --git a/drivers/misc/arm-cla/cla-sched.c b/drivers/misc/arm-cla/cla-sc= hed.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 +#include + +#include "arm-cla.h" + +static const struct rhashtable_params ctxs_rht_params =3D { + .head_offset =3D offsetof(struct cla_ctx, node), + .key_offset =3D offsetof(struct cla_ctx, key), + .key_len =3D sizeof(((struct cla_ctx *)0)->key), + .automatic_shrinking =3D true, +}; + +static int __cla_domain_switch_ctx(struct cla_domain *domain, + struct cla_ctx *prev_ctx, + struct cla_ctx *next_ctx) +{ + int ret =3D 0; + unsigned int i; + struct cla_dev *dev; + struct cla_call_on_cpu *call; + + if (!prev_ctx && !next_ctx) + return 0; + + for (i =3D 0; i < domain->nr_devs; i++) { + dev =3D domain->devs[i]; + call =3D &dev->call; + + call->sched.prev_ctx =3D prev_ctx; + call->sched.next_ctx =3D next_ctx; + call->sched.ctx_id =3D i; + call->ret =3D 0; + + mutex_lock(&dev->lock); + if (dev->worker && !dev->broken) + kthread_queue_work(dev->worker, &call->switch_ctx); + mutex_unlock(&dev->lock); + } + + for (i =3D 0; i < domain->nr_devs; i++) { + dev =3D domain->devs[i]; + call =3D &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 =3D 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 =3D __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 =3D arm64_mm_context_get(next_ctx->key.mm); + if (next_ctx->asid =3D=3D 0) { + pr_err("cla: out of pinned ASIDs\n"); + ret =3D -ENOSPC; + goto err_kill_domain; + } + } + + /* Install next_ctx into all devices in the domain */ + ret =3D __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, a= nd + * 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() !=3D dev->cpu)) + return -EINVAL; + + prev_ctx =3D dev->call.sched.prev_ctx; + next_ctx =3D dev->call.sched.next_ctx; + ctx_id =3D 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 =3D !cla_ctx_is_dying(prev_ctx); + + ret =3D 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 =3D 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 =3D 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 =3D 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 =3D + container_of(work, struct cla_call_on_cpu, switch_ctx); + struct cla_dev *dev =3D container_of(call, struct cla_dev, call); + + call->ret =3D __cla_dev_switch_ctx(dev); + if (call->ret) { + mutex_lock(&dev->lock); + dev->broken =3D true; + mutex_unlock(&dev->lock); + } +} + +static inline struct cla_ctx *cla_domain_get_next_ctx(struct cla_domain *d= omain) +{ + 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 *d= omain, + struct cla_ctx *next_ctx) +{ + if (domain->assigned_ctx && (next_ctx || cla_ctx_is_dying(domain->assigne= d_ctx))) + return domain->assigned_ctx; + return NULL; +} + +static void cla_vma_zap_domain(struct vm_area_struct *vma, struct cla_doma= in *domain) +{ + unsigned long vma_start =3D vma->vm_pgoff; + unsigned long vma_end =3D vma_start + vma_pages(vma); + unsigned long domain_start =3D domain->pg_offset; + unsigned long domain_end =3D domain_start + domain->nr_devs; + unsigned long start =3D max(vma_start, domain_start); + unsigned long end =3D min(vma_end, domain_end); + unsigned long addr; + unsigned long size; + + if (start >=3D end) + return; + + addr =3D vma->vm_start + ((start - vma_start) << PAGE_SHIFT); + size =3D (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 !=3D 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 =3D 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 =3D cla_domain_get_next_ctx(domain); + prev_ctx =3D 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 =3D cla_domain_switch_ctx(domain, prev_ctx, next_ctx); + if (ret) + next_ctx =3D 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 =3D 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 =3D=3D 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 it= ems. + * + * 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 =3D rhashtable_init(&domain->ctxs, &ctxs_rht_params); + if (ret) + return ret; + + domain->worker =3D 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 assignm= ent. + */ +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 =3D { + .mm =3D mm, + .file =3D 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 a= fter + * 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); =20 + mutex_init(&domain->lock); + domain->id =3D id; ret =3D xa_insert(&cla_domains, id, domain, GFP_KERNEL); if (ret < 0) @@ -50,8 +52,14 @@ static struct cla_domain *cla_domain_alloc(struct cla_de= v *dev, unsigned int id) } domain->devs[0] =3D dev; =20 + ret =3D cla_domain_sched_init(domain); + if (ret) + goto err_free_devs; + return domain; =20 +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) =20 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); --=20 2.43.0 From nobody Sat Jul 25 05:27:22 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 69A323EB108; Fri, 17 Jul 2026 10:48:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285320; cv=none; b=N2dJWeYTESUfQtS4xlKie2wN/Ab9TfNObx6NPRON3JlHQA/qhALjurE0ZnlPyzlQZCDWZkpbfYsTw/j1mfOUKQGMnBCthARBhvHPhpsl9FOFkGuh/4ddAdndLP+q44+CueFZEPuJgqtdNbJGA1x6gpcZlxwsGXTKgeGHwoPmLV0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784285320; c=relaxed/simple; bh=wqXkAtcd1JkcLZE+TF60/UvHHNNYyFX6YQhnrddF4eA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=p2gdQq7ksvbzjKf2FFFe5izSizzuOpgNTmRpQBDM25s0rIVsdr0SgzV7lTVE6E32poGN+ySXyoJSOEUnYQ5LF+g53Tg0vSw7GmJodAG9bgPmGR4f9+YpW/3YYwSzKvI3JE0b+Vzh6ESi4hIHhYvS4eM+HuCEFdrZtpHPuzfMCXk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=rTbxGfMD; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="rTbxGfMD" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id E217916A3; Fri, 17 Jul 2026 03:48:31 -0700 (PDT) Received: from e125769.cambridge.arm.com (e125769.cambridge.arm.com [10.2.198.107]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 504B73F7D8; Fri, 17 Jul 2026 03:48:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1784285316; bh=wqXkAtcd1JkcLZE+TF60/UvHHNNYyFX6YQhnrddF4eA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rTbxGfMDL/rlkadUaC3iwvE7T5tcrxAET84ATpQlHdqxJQ4fYxk3qdUMTLXbvVOw7 I78h5jxDf3H6V7trXu6GBCd/Aj5coetUxV9K9cyIdOkr9cKw0Fz3/ucJYvX2ZKsyof QL4We9oSHBl0epIPno/MH3B7vSMk/UMGHbsqVIM0= From: Ryan Roberts To: Greg Kroah-Hartman , Arnd Bergmann , Catalin Marinas , Will Deacon , Mark Rutland , Jean-Philippe Brucker , Oded Gabbay , Jonathan Corbet Cc: Ryan Roberts , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org Subject: [RFC PATCH v1 8/8] misc/arm-cla: Add userspace interface Date: Fri, 17 Jul 2026 11:47:52 +0100 Message-ID: <20260717104759.123203-9-ryan.roberts@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260717104759.123203-1-ryan.roberts@arm.com> References: <20260717104759.123203-1-ryan.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Jean-Philippe Brucker Signed-off-by: Ryan Roberts --- 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 :=3D \ cla-ops.o \ cla-regs.o \ cla-sched.o \ - cla-topology.o + cla-topology.o \ + cla-user.o =20 obj-$(CONFIG_ARM_CLA) +=3D 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) =20 +#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; }; =20 +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]; =20 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); =20 +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-ini= t.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 in= t accid) */ static int cla_dev_setup_accel(struct cla_dev *dev, unsigned int accid) { + struct cla_accel_desc *desc =3D &dev->accel_descs[accid]; u64 status; u64 iassize; u64 acap; @@ -122,6 +123,20 @@ static int cla_dev_setup_accel(struct cla_dev *dev, un= signed int accid) return 1; } =20 + /* + * Cache some standard accelerator registers that user space may query + * via ioctl from a remote CPU. + */ + ret =3D cla_op_regread(dev, accid, CLA_REG_IIDR, 1, &desc->iidr); + if (ret) + return ret; + ret =3D cla_op_regread(dev, accid, CLA_REG_DEVARCH, 1, &desc->devarch); + if (ret) + return ret; + ret =3D cla_op_regread(dev, accid, CLA_REG_REVIDR, 1, &desc->revidr); + if (ret) + return ret; + ret =3D 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() !=3D cpu || dev->cpu !=3D cpu)) return -EINVAL; =20 + dev->aidr =3D cla_reg_read(dev, CLA_REG_CLAAIDR); + if (dev->aidr !=3D CLA_AAIDR_1_0) + return -EPROTONOSUPPORT; + /* Clear DATA and LRESP_DATANZ */ for (i =3D 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 =3D ret; =20 + ret =3D cla_user_init(); + if (ret) + goto err_cpuhp_remove; + return 0; =20 +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) =20 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-use= r.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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arm-cla.h" +#include + +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 =3D (_vma)->vm_pgoff; \ + __pg < (_vma)->vm_pgoff + vma_pages(_vma) && \ + ((_domain) =3D cla_lut_pg[dev_nospec(__pg)]->domain); \ + __pg =3D (_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 !=3D 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 >=3D 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 =3D ARM_CLA_PARAM_INDEX_DEV(param->index); + accel_id =3D ARM_CLA_PARAM_INDEX_ACCEL(param->index); + if (dev_id >=3D cla_nr_devs || accel_id >=3D CLA_NUM_ACC) + return -EINVAL; + dev_id =3D dev_nospec(dev_id); + if ((cla_lut_pg[dev_id]->accelerators & BIT(accel_id)) =3D=3D 0) + return -ENODEV; + break; + default: + return -EINVAL; + } + + return 0; +} + +static long cla_ioctl_get_param(unsigned long arg) +{ + struct arm_cla_param __user *uparam =3D (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 =3D cla_ioctl_validate_param(¶m); + if (ret) + return ret; + + dev_id =3D dev_nospec(ARM_CLA_PARAM_INDEX_DEV(param.index)); + accel_id =3D accel_nospec(ARM_CLA_PARAM_INDEX_ACCEL(param.index)); + + switch (param.param) { + case ARM_CLA_PARAM_UABI_VERSION: + param.value =3D ARM_CLA_UABI_VERSION; + break; + case ARM_CLA_PARAM_DEV_NR: + param.value =3D cla_nr_devs; + break; + case ARM_CLA_PARAM_DEV_CPU_ID: + param.value =3D cla_lut_pg[dev_id]->cpu; + break; + case ARM_CLA_PARAM_DEV_DOMAIN_ID: + param.value =3D cla_lut_pg[dev_id]->domain->id; + break; + case ARM_CLA_PARAM_DEV_PGOFF: + param.value =3D cla_lut_pg[dev_id]->pg_offset; + break; + case ARM_CLA_PARAM_DEV_AIDR: + param.value =3D cla_lut_pg[dev_id]->aidr; + break; + case ARM_CLA_PARAM_DEV_ACCELS: + param.value =3D cla_lut_pg[dev_id]->accelerators; + break; + case ARM_CLA_PARAM_ACCEL_IIDR: + param.value =3D cla_lut_pg[dev_id]->accel_descs[accel_id].iidr; + break; + case ARM_CLA_PARAM_ACCEL_DEVARCH: + param.value =3D cla_lut_pg[dev_id]->accel_descs[accel_id].devarch; + break; + case ARM_CLA_PARAM_ACCEL_REVIDR: + param.value =3D 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 =3D 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=3DNULL), + * used by futex fault-in path. + */ + if (WARN_ON(!(vmf->flags & FAULT_FLAG_ALLOW_RETRY))) + return VM_FAULT_SIGBUS; + + pg =3D vma->vm_pgoff + ((vmf->address - vma->vm_start) >> PAGE_SHIFT); + dev =3D cla_lut_pg[pg]; + domain =3D dev->domain; + + mutex_lock(&domain->lock); + ctx =3D 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 =3D=3D ctx) { + vm_fault_t ret; + + ret =3D 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 =3D list_empty(&domain->queued_ctxs); + unsigned long delay; + + list_add_tail(&ctx->queue_node, &domain->queued_ctxs); + if (was_empty) { + delay =3D 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) =3D=3D 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 add= r) +{ + /* + * 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 =3D { + .open =3D cla_vma_open, + .close =3D cla_vma_close, + .fault =3D cla_vma_fault, + .may_split =3D cla_vma_may_split, +}; + +static int cla_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long nr_pages =3D 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 >=3D 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 =3D cla_ctx_map(domain, vma->vm_mm, vma->vm_file); + if (IS_ERR(ctx)) { + cla_for_each_mapped_domain(vma, rollback) { + if (rollback =3D=3D 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 =3D pgprot_device(vma->vm_page_prot); + vma->vm_ops =3D &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 =3D { + .owner =3D THIS_MODULE, + .mmap =3D cla_file_mmap, + .unlocked_ioctl =3D cla_file_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl =3D cla_file_ioctl, +#endif +}; + +static char *cla_devnode(const struct device *dev, umode_t *mode) +{ + if (mode) + *mode =3D 0666; + return NULL; +} + +int __init cla_user_init(void) +{ + int ret; + + cla_class =3D class_create("arm-cla"); + if (IS_ERR(cla_class)) + return PTR_ERR(cla_class); + cla_class->devnode =3D cla_devnode; + + ret =3D register_chrdev(0, KBUILD_MODNAME, &cla_fops); + if (ret < 0) + goto err_class_destroy; + + cla_devt =3D MKDEV(ret, 0); + + if (IS_ERR(device_create(cla_class, NULL, cla_devt, NULL, "cla"))) { + ret =3D -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 +#include + +#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 (R= O). + * + * &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 commu= nicate + * with each other. All CLA devices within a CLA domain are atomically ass= igned + * 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 exp= osed + * 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 select= ed CLA + * device. A CLA may have up to 8 attached accelerators, each with an acce= l_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 */ --=20 2.43.0