Merge tag 'drm-misc-next-2025-10-28' of https://gitlab.freedesktop.org/drm/misc/kernel into drm-next

drm-misc-next for v6.19-rc1:

UAPI Changes:

Cross-subsystem Changes:
- Update DT bindings for renesas and powervr-rogue.
- Update MAINTAINERS email and add spsc_queue.

Core Changes:
- Allow ttm page protection flags on risc-v.
- Move freeing of drm client memory to driver.

Driver Changes:
- Assorted small fixes and updates to qaic, ivpu, st7571-i2c, gud,
  amdxdna.
- Allow configuration of vkms' display through configfs.
- Add Arm Ethos-U65/U85 accel driver.

Signed-off-by: Simona Vetter <simona.vetter@ffwll.ch>
From: Maarten Lankhorst <dev@lankhorst.se>
Link: https://patch.msgid.link/32b43261-3c99-49d9-92ee-615ada1d01e8@lankhorst.se
This commit is contained in:
Simona Vetter
2025-10-31 18:47:16 +01:00
59 changed files with 3553 additions and 127 deletions

View File

@@ -173,6 +173,7 @@ Carlos Bilbao <carlos.bilbao@kernel.org> <bilbao@vt.edu>
Changbin Du <changbin.du@intel.com> <changbin.du@gmail.com>
Chao Yu <chao@kernel.org> <chao2.yu@samsung.com>
Chao Yu <chao@kernel.org> <yuchao0@huawei.com>
Chen-Yu Tsai <wens@kernel.org> <wens@csie.org>
Chester Lin <chester62515@gmail.com> <clin@suse.com>
Chris Chiu <chris.chiu@canonical.com> <chiu@endlessm.com>
Chris Chiu <chris.chiu@canonical.com> <chiu@endlessos.org>

View File

@@ -14,6 +14,9 @@ description: |
R-Car Gen4 SoCs. The encoder can operate in either DSI or CSI-2 mode, with up
to four data lanes.
allOf:
- $ref: /schemas/display/dsi-controller.yaml#
properties:
compatible:
enum:
@@ -80,14 +83,14 @@ required:
- resets
- ports
additionalProperties: false
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/clock/r8a779a0-cpg-mssr.h>
#include <dt-bindings/power/r8a779a0-sysc.h>
dsi0: dsi-encoder@fed80000 {
dsi@fed80000 {
compatible = "renesas,r8a779a0-dsi-csi2-tx";
reg = <0xfed80000 0x10000>;
power-domains = <&sysc R8A779A0_PD_ALWAYS_ON>;
@@ -117,4 +120,50 @@ examples:
};
};
};
- |
#include <dt-bindings/clock/r8a779g0-cpg-mssr.h>
#include <dt-bindings/power/r8a779g0-sysc.h>
dsi@fed80000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "renesas,r8a779g0-dsi-csi2-tx";
reg = <0xfed80000 0x10000>;
clocks = <&cpg CPG_MOD 415>,
<&cpg CPG_CORE R8A779G0_CLK_DSIEXT>,
<&cpg CPG_CORE R8A779G0_CLK_DSIREF>;
clock-names = "fck", "dsi", "pll";
power-domains = <&sysc R8A779G0_PD_ALWAYS_ON>;
resets = <&cpg 415>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
};
port@1 {
reg = <1>;
dsi0port1_out: endpoint {
remote-endpoint = <&panel_in>;
data-lanes = <1 2>;
};
};
};
panel@0 {
reg = <0>;
compatible = "raspberrypi,dsi-7inch";
port {
panel_in: endpoint {
remote-endpoint = <&dsi0port1_out>;
};
};
};
};
...

View File

@@ -13,6 +13,12 @@ maintainers:
properties:
compatible:
oneOf:
- items:
- enum:
- renesas,r8a7796-gpu
- renesas,r8a77961-gpu
- const: img,img-gx6250
- const: img,img-rogue
- items:
- enum:
- ti,am62-gpu
@@ -82,6 +88,33 @@ required:
additionalProperties: false
allOf:
- if:
properties:
compatible:
contains:
enum:
- ti,am62-gpu
- ti,j721s2-gpu
then:
properties:
clocks:
maxItems: 1
- if:
properties:
compatible:
contains:
enum:
- img,img-gx6250
- thead,th1520-gpu
then:
properties:
clocks:
minItems: 3
clock-names:
minItems: 3
- if:
properties:
compatible:
@@ -90,14 +123,30 @@ allOf:
then:
properties:
power-domains:
items:
- description: Power domain A
maxItems: 1
power-domain-names:
maxItems: 1
required:
- power-domains
- power-domain-names
- if:
properties:
compatible:
contains:
enum:
- img,img-gx6250
- img,img-bxs-4-64
then:
properties:
power-domains:
minItems: 2
power-domain-names:
minItems: 2
required:
- power-domains
- power-domain-names
- if:
properties:
compatible:
@@ -105,10 +154,6 @@ allOf:
const: thead,th1520-gpu
then:
properties:
clocks:
minItems: 3
clock-names:
minItems: 3
power-domains:
items:
- description: The single, unified power domain for the GPU on the
@@ -117,35 +162,6 @@ allOf:
required:
- power-domains
- if:
properties:
compatible:
contains:
const: img,img-bxs-4-64
then:
properties:
power-domains:
items:
- description: Power domain A
- description: Power domain B
power-domain-names:
minItems: 2
required:
- power-domains
- power-domain-names
- if:
properties:
compatible:
contains:
enum:
- ti,am62-gpu
- ti,j721s2-gpu
then:
properties:
clocks:
maxItems: 1
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>

View File

@@ -0,0 +1,79 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/npu/arm,ethos.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Arm Ethos U65/U85
maintainers:
- Rob Herring <robh@kernel.org>
description: >
The Arm Ethos-U NPUs are designed for IoT inference applications. The NPUs
can accelerate 8-bit and 16-bit integer quantized networks:
Transformer networks (U85 only)
Convolutional Neural Networks (CNN)
Recurrent Neural Networks (RNN)
Further documentation is available here:
U65 TRM: https://developer.arm.com/documentation/102023/
U85 TRM: https://developer.arm.com/documentation/102685/
properties:
compatible:
oneOf:
- items:
- enum:
- fsl,imx93-npu
- const: arm,ethos-u65
- items:
- {}
- const: arm,ethos-u85
reg:
maxItems: 1
interrupts:
maxItems: 1
clocks:
maxItems: 2
clock-names:
items:
- const: core
- const: apb
power-domains:
maxItems: 1
sram:
maxItems: 1
required:
- compatible
- reg
- interrupts
- clocks
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/imx93-clock.h>
npu@4a900000 {
compatible = "fsl,imx93-npu", "arm,ethos-u65";
reg = <0x4a900000 0x1000>;
interrupts = <GIC_SPI 178 IRQ_TYPE_LEVEL_HIGH>;
power-domains = <&mlmix>;
clocks = <&clk IMX93_CLK_ML>, <&clk IMX93_CLK_ML_APB>;
clock-names = "core", "apb";
sram = <&sram>;
};
...

View File

@@ -51,6 +51,97 @@ To disable the driver, use ::
sudo modprobe -r vkms
Configuring With Configfs
=========================
It is possible to create and configure multiple VKMS instances via configfs.
Start by mounting configfs and loading VKMS::
sudo mount -t configfs none /config
sudo modprobe vkms
Once VKMS is loaded, ``/config/vkms`` is created automatically. Each directory
under ``/config/vkms`` represents a VKMS instance, create a new one::
sudo mkdir /config/vkms/my-vkms
By default, the instance is disabled::
cat /config/vkms/my-vkms/enabled
0
And directories are created for each configurable item of the display pipeline::
tree /config/vkms/my-vkms
├── connectors
├── crtcs
├── enabled
├── encoders
└── planes
To add items to the display pipeline, create one or more directories under the
available paths.
Start by creating one or more planes::
sudo mkdir /config/vkms/my-vkms/planes/plane0
Planes have 1 configurable attribute:
- type: Plane type: 0 overlay, 1 primary, 2 cursor (same values as those
exposed by the "type" property of a plane)
Continue by creating one or more CRTCs::
sudo mkdir /config/vkms/my-vkms/crtcs/crtc0
CRTCs have 1 configurable attribute:
- writeback: Enable or disable writeback connector support by writing 1 or 0
Next, create one or more encoders::
sudo mkdir /config/vkms/my-vkms/encoders/encoder0
Last but not least, create one or more connectors::
sudo mkdir /config/vkms/my-vkms/connectors/connector0
Connectors have 1 configurable attribute:
- status: Connection status: 1 connected, 2 disconnected, 3 unknown (same values
as those exposed by the "status" property of a connector)
To finish the configuration, link the different pipeline items::
sudo ln -s /config/vkms/my-vkms/crtcs/crtc0 /config/vkms/my-vkms/planes/plane0/possible_crtcs
sudo ln -s /config/vkms/my-vkms/crtcs/crtc0 /config/vkms/my-vkms/encoders/encoder0/possible_crtcs
sudo ln -s /config/vkms/my-vkms/encoders/encoder0 /config/vkms/my-vkms/connectors/connector0/possible_encoders
Since at least one primary plane is required, make sure to set the right type::
echo "1" | sudo tee /config/vkms/my-vkms/planes/plane0/type
Once you are done configuring the VKMS instance, enable it::
echo "1" | sudo tee /config/vkms/my-vkms/enabled
Finally, you can remove the VKMS instance disabling it::
echo "0" | sudo tee /config/vkms/my-vkms/enabled
And removing the top level directory and its subdirectories::
sudo rm /config/vkms/my-vkms/planes/*/possible_crtcs/*
sudo rm /config/vkms/my-vkms/encoders/*/possible_crtcs/*
sudo rm /config/vkms/my-vkms/connectors/*/possible_encoders/*
sudo rmdir /config/vkms/my-vkms/planes/*
sudo rmdir /config/vkms/my-vkms/crtcs/*
sudo rmdir /config/vkms/my-vkms/encoders/*
sudo rmdir /config/vkms/my-vkms/connectors/*
sudo rmdir /config/vkms/my-vkms
Testing With IGT
================
@@ -147,21 +238,14 @@ Runtime Configuration
---------------------
We want to be able to reconfigure vkms instance without having to reload the
module. Use/Test-cases:
module through configfs. Use/Test-cases:
- Hotplug/hotremove connectors on the fly (to be able to test DP MST handling
of compositors).
- Configure planes/crtcs/connectors (we'd need some code to have more than 1 of
them first).
- Change output configuration: Plug/unplug screens, change EDID, allow changing
the refresh rate.
The currently proposed solution is to expose vkms configuration through
configfs. All existing module options should be supported through configfs
too.
Writeback support
-----------------

View File

@@ -2017,6 +2017,15 @@ F: arch/arm64/include/asm/arch_timer.h
F: drivers/clocksource/arm_arch_timer.c
F: drivers/clocksource/arm_arch_timer_mmio.c
ARM ETHOS-U NPU DRIVER
M: Rob Herring (Arm) <robh@kernel.org>
M: Tomeu Vizoso <tomeu@tomeuvizoso.net>
L: dri-devel@lists.freedesktop.org
S: Supported
T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
F: drivers/accel/ethosu/
F: include/uapi/drm/ethosu_accel.h
ARM GENERIC INTERRUPT CONTROLLER DRIVERS
M: Marc Zyngier <maz@kernel.org>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
@@ -2299,7 +2308,7 @@ S: Maintained
F: drivers/clk/sunxi/
ARM/Allwinner sunXi SoC support
M: Chen-Yu Tsai <wens@csie.org>
M: Chen-Yu Tsai <wens@kernel.org>
M: Jernej Skrabec <jernej.skrabec@gmail.com>
M: Samuel Holland <samuel@sholland.org>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
@@ -7639,7 +7648,7 @@ F: drivers/accel/
F: include/drm/drm_accel.h
DRM DRIVER FOR ALLWINNER DE2 AND DE3 ENGINE
M: Chen-Yu Tsai <wens@csie.org>
M: Chen-Yu Tsai <wens@kernel.org>
R: Jernej Skrabec <jernej.skrabec@gmail.com>
L: dri-devel@lists.freedesktop.org
S: Supported
@@ -8253,7 +8262,7 @@ F: drivers/gpu/nova-core/
F: rust/kernel/drm/
DRM DRIVERS FOR ALLWINNER A10
M: Chen-Yu Tsai <wens@csie.org>
M: Chen-Yu Tsai <wens@kernel.org>
L: dri-devel@lists.freedesktop.org
S: Supported
T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
@@ -8580,6 +8589,7 @@ S: Supported
T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
F: drivers/gpu/drm/scheduler/
F: include/drm/gpu_scheduler.h
F: include/drm/spsc_queue.h
DRM GPUVM
M: Danilo Krummrich <dakr@kernel.org>
@@ -27707,7 +27717,7 @@ F: drivers/acpi/pmic/intel_pmic_xpower.c
N: axp288
X-POWERS MULTIFUNCTION PMIC DEVICE DRIVERS
M: Chen-Yu Tsai <wens@csie.org>
M: Chen-Yu Tsai <wens@kernel.org>
L: linux-kernel@vger.kernel.org
S: Maintained
N: axp[128]

View File

@@ -25,6 +25,7 @@ menuconfig DRM_ACCEL
and debugfs).
source "drivers/accel/amdxdna/Kconfig"
source "drivers/accel/ethosu/Kconfig"
source "drivers/accel/habanalabs/Kconfig"
source "drivers/accel/ivpu/Kconfig"
source "drivers/accel/qaic/Kconfig"

View File

@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_ACCEL_AMDXDNA) += amdxdna/
obj-$(CONFIG_DRM_ACCEL_ARM_ETHOSU) += ethosu/
obj-$(CONFIG_DRM_ACCEL_HABANALABS) += habanalabs/
obj-$(CONFIG_DRM_ACCEL_IVPU) += ivpu/
obj-$(CONFIG_DRM_ACCEL_QAIC) += qaic/

View File

@@ -879,7 +879,7 @@ int aie2_hwctx_sync_debug_bo(struct amdxdna_hwctx *hwctx, u32 debug_bo_hdl)
aie2_cmd_wait(hwctx, seq);
if (cmd.result) {
XDNA_ERR(xdna, "Response failure 0x%x", cmd.result);
return ret;
return -EINVAL;
}
return 0;

View File

@@ -822,7 +822,7 @@ static int aie2_get_hwctx_status(struct amdxdna_client *client,
}
args->buffer_size -= (u32)(array_args.buffer - args->buffer);
return ret;
return 0;
}
static int aie2_get_info(struct amdxdna_client *client, struct amdxdna_drm_get_info *args)
@@ -904,7 +904,7 @@ static int aie2_query_ctx_status_array(struct amdxdna_client *client,
args->num_element = (u32)((array_args.buffer - args->buffer) /
args->element_size);
return ret;
return 0;
}
static int aie2_get_array(struct amdxdna_client *client,

View File

@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_ACCEL_ARM_ETHOSU
tristate "Arm Ethos-U65/U85 NPU"
depends on HAS_IOMEM
depends on DRM_ACCEL
select DRM_GEM_DMA_HELPER
select DRM_SCHED
select GENERIC_ALLOCATOR
help
Enables driver for Arm Ethos-U65/U85 NPUs

View File

@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_ACCEL_ARM_ETHOSU) := ethosu.o
ethosu-y += ethosu_drv.o ethosu_gem.o ethosu_job.o

View File

@@ -0,0 +1,197 @@
/* SPDX-License-Identifier: GPL-2.0-only or MIT */
/* Copyright 2025 Arm, Ltd. */
#ifndef __ETHOSU_DEVICE_H__
#define __ETHOSU_DEVICE_H__
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/types.h>
#include <drm/drm_device.h>
#include <drm/gpu_scheduler.h>
#include <drm/ethosu_accel.h>
struct clk;
struct gen_pool;
#define NPU_REG_ID 0x0000
#define NPU_REG_STATUS 0x0004
#define NPU_REG_CMD 0x0008
#define NPU_REG_RESET 0x000c
#define NPU_REG_QBASE 0x0010
#define NPU_REG_QBASE_HI 0x0014
#define NPU_REG_QREAD 0x0018
#define NPU_REG_QCONFIG 0x001c
#define NPU_REG_QSIZE 0x0020
#define NPU_REG_PROT 0x0024
#define NPU_REG_CONFIG 0x0028
#define NPU_REG_REGIONCFG 0x003c
#define NPU_REG_AXILIMIT0 0x0040 // U65
#define NPU_REG_AXILIMIT1 0x0044 // U65
#define NPU_REG_AXILIMIT2 0x0048 // U65
#define NPU_REG_AXILIMIT3 0x004c // U65
#define NPU_REG_MEM_ATTR0 0x0040 // U85
#define NPU_REG_MEM_ATTR1 0x0044 // U85
#define NPU_REG_MEM_ATTR2 0x0048 // U85
#define NPU_REG_MEM_ATTR3 0x004c // U85
#define NPU_REG_AXI_SRAM 0x0050 // U85
#define NPU_REG_AXI_EXT 0x0054 // U85
#define NPU_REG_BASEP(x) (0x0080 + (x) * 8)
#define NPU_REG_BASEP_HI(x) (0x0084 + (x) * 8)
#define NPU_BASEP_REGION_MAX 8
#define ID_ARCH_MAJOR_MASK GENMASK(31, 28)
#define ID_ARCH_MINOR_MASK GENMASK(27, 20)
#define ID_ARCH_PATCH_MASK GENMASK(19, 16)
#define ID_VER_MAJOR_MASK GENMASK(11, 8)
#define ID_VER_MINOR_MASK GENMASK(7, 4)
#define CONFIG_MACS_PER_CC_MASK GENMASK(3, 0)
#define CONFIG_CMD_STREAM_VER_MASK GENMASK(7, 4)
#define STATUS_STATE_RUNNING BIT(0)
#define STATUS_IRQ_RAISED BIT(1)
#define STATUS_BUS_STATUS BIT(2)
#define STATUS_RESET_STATUS BIT(3)
#define STATUS_CMD_PARSE_ERR BIT(4)
#define STATUS_CMD_END_REACHED BIT(5)
#define CMD_CLEAR_IRQ BIT(1)
#define CMD_TRANSITION_TO_RUN BIT(0)
#define RESET_PENDING_CSL BIT(1)
#define RESET_PENDING_CPL BIT(0)
#define PROT_ACTIVE_CSL BIT(1)
enum ethosu_cmds {
NPU_OP_CONV = 0x2,
NPU_OP_DEPTHWISE = 0x3,
NPU_OP_POOL = 0x5,
NPU_OP_ELEMENTWISE = 0x6,
NPU_OP_RESIZE = 0x7, // U85 only
NPU_OP_DMA_START = 0x10,
NPU_SET_IFM_PAD_TOP = 0x100,
NPU_SET_IFM_PAD_LEFT = 0x101,
NPU_SET_IFM_PAD_RIGHT = 0x102,
NPU_SET_IFM_PAD_BOTTOM = 0x103,
NPU_SET_IFM_DEPTH_M1 = 0x104,
NPU_SET_IFM_PRECISION = 0x105,
NPU_SET_IFM_BROADCAST = 0x108,
NPU_SET_IFM_WIDTH0_M1 = 0x10a,
NPU_SET_IFM_HEIGHT0_M1 = 0x10b,
NPU_SET_IFM_HEIGHT1_M1 = 0x10c,
NPU_SET_IFM_REGION = 0x10f,
NPU_SET_OFM_WIDTH_M1 = 0x111,
NPU_SET_OFM_HEIGHT_M1 = 0x112,
NPU_SET_OFM_DEPTH_M1 = 0x113,
NPU_SET_OFM_PRECISION = 0x114,
NPU_SET_OFM_WIDTH0_M1 = 0x11a,
NPU_SET_OFM_HEIGHT0_M1 = 0x11b,
NPU_SET_OFM_HEIGHT1_M1 = 0x11c,
NPU_SET_OFM_REGION = 0x11f,
NPU_SET_KERNEL_WIDTH_M1 = 0x120,
NPU_SET_KERNEL_HEIGHT_M1 = 0x121,
NPU_SET_KERNEL_STRIDE = 0x122,
NPU_SET_WEIGHT_REGION = 0x128,
NPU_SET_SCALE_REGION = 0x129,
NPU_SET_DMA0_SRC_REGION = 0x130,
NPU_SET_DMA0_DST_REGION = 0x131,
NPU_SET_DMA0_SIZE0 = 0x132,
NPU_SET_DMA0_SIZE1 = 0x133,
NPU_SET_IFM2_BROADCAST = 0x180,
NPU_SET_IFM2_PRECISION = 0x185,
NPU_SET_IFM2_WIDTH0_M1 = 0x18a,
NPU_SET_IFM2_HEIGHT0_M1 = 0x18b,
NPU_SET_IFM2_HEIGHT1_M1 = 0x18c,
NPU_SET_IFM2_REGION = 0x18f,
NPU_SET_IFM_BASE0 = 0x4000,
NPU_SET_IFM_BASE1 = 0x4001,
NPU_SET_IFM_BASE2 = 0x4002,
NPU_SET_IFM_BASE3 = 0x4003,
NPU_SET_IFM_STRIDE_X = 0x4004,
NPU_SET_IFM_STRIDE_Y = 0x4005,
NPU_SET_IFM_STRIDE_C = 0x4006,
NPU_SET_OFM_BASE0 = 0x4010,
NPU_SET_OFM_BASE1 = 0x4011,
NPU_SET_OFM_BASE2 = 0x4012,
NPU_SET_OFM_BASE3 = 0x4013,
NPU_SET_OFM_STRIDE_X = 0x4014,
NPU_SET_OFM_STRIDE_Y = 0x4015,
NPU_SET_OFM_STRIDE_C = 0x4016,
NPU_SET_WEIGHT_BASE = 0x4020,
NPU_SET_WEIGHT_LENGTH = 0x4021,
NPU_SET_SCALE_BASE = 0x4022,
NPU_SET_SCALE_LENGTH = 0x4023,
NPU_SET_DMA0_SRC = 0x4030,
NPU_SET_DMA0_DST = 0x4031,
NPU_SET_DMA0_LEN = 0x4032,
NPU_SET_DMA0_SRC_STRIDE0 = 0x4033,
NPU_SET_DMA0_SRC_STRIDE1 = 0x4034,
NPU_SET_DMA0_DST_STRIDE0 = 0x4035,
NPU_SET_DMA0_DST_STRIDE1 = 0x4036,
NPU_SET_IFM2_BASE0 = 0x4080,
NPU_SET_IFM2_BASE1 = 0x4081,
NPU_SET_IFM2_BASE2 = 0x4082,
NPU_SET_IFM2_BASE3 = 0x4083,
NPU_SET_IFM2_STRIDE_X = 0x4084,
NPU_SET_IFM2_STRIDE_Y = 0x4085,
NPU_SET_IFM2_STRIDE_C = 0x4086,
NPU_SET_WEIGHT1_BASE = 0x4090,
NPU_SET_WEIGHT1_LENGTH = 0x4091,
NPU_SET_SCALE1_BASE = 0x4092,
NPU_SET_WEIGHT2_BASE = 0x4092,
NPU_SET_SCALE1_LENGTH = 0x4093,
NPU_SET_WEIGHT2_LENGTH = 0x4093,
NPU_SET_WEIGHT3_BASE = 0x4094,
NPU_SET_WEIGHT3_LENGTH = 0x4095,
};
#define ETHOSU_SRAM_REGION 2 /* Matching Vela compiler */
/**
* struct ethosu_device - Ethosu device
*/
struct ethosu_device {
/** @base: Base drm_device. */
struct drm_device base;
/** @iomem: CPU mapping of the registers. */
void __iomem *regs;
void __iomem *sram;
struct gen_pool *srampool;
dma_addr_t sramphys;
struct clk_bulk_data *clks;
int num_clks;
int irq;
struct drm_ethosu_npu_info npu_info;
struct ethosu_job *in_flight_job;
/* For in_flight_job and ethosu_job_hw_submit() */
struct mutex job_lock;
/* For dma_fence */
spinlock_t fence_lock;
struct drm_gpu_scheduler sched;
/* For ethosu_job_do_push() */
struct mutex sched_lock;
u64 fence_context;
u64 emit_seqno;
};
#define to_ethosu_device(drm_dev) \
((struct ethosu_device *)container_of(drm_dev, struct ethosu_device, base))
static inline bool ethosu_is_u65(const struct ethosu_device *ethosudev)
{
return FIELD_GET(ID_ARCH_MAJOR_MASK, ethosudev->npu_info.id) == 1;
}
#endif

View File

@@ -0,0 +1,403 @@
// SPDX-License-Identifier: GPL-2.0-only or MIT
// Copyright (C) 2025 Arm, Ltd.
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/genalloc.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_drv.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_utils.h>
#include <drm/drm_gem.h>
#include <drm/drm_accel.h>
#include <drm/ethosu_accel.h>
#include "ethosu_drv.h"
#include "ethosu_device.h"
#include "ethosu_gem.h"
#include "ethosu_job.h"
static int ethosu_ioctl_dev_query(struct drm_device *ddev, void *data,
struct drm_file *file)
{
struct ethosu_device *ethosudev = to_ethosu_device(ddev);
struct drm_ethosu_dev_query *args = data;
if (!args->pointer) {
switch (args->type) {
case DRM_ETHOSU_DEV_QUERY_NPU_INFO:
args->size = sizeof(ethosudev->npu_info);
return 0;
default:
return -EINVAL;
}
}
switch (args->type) {
case DRM_ETHOSU_DEV_QUERY_NPU_INFO:
if (args->size < offsetofend(struct drm_ethosu_npu_info, sram_size))
return -EINVAL;
return copy_struct_to_user(u64_to_user_ptr(args->pointer),
args->size,
&ethosudev->npu_info,
sizeof(ethosudev->npu_info), NULL);
default:
return -EINVAL;
}
}
#define ETHOSU_BO_FLAGS DRM_ETHOSU_BO_NO_MMAP
static int ethosu_ioctl_bo_create(struct drm_device *ddev, void *data,
struct drm_file *file)
{
struct drm_ethosu_bo_create *args = data;
int cookie, ret;
if (!drm_dev_enter(ddev, &cookie))
return -ENODEV;
if (!args->size || (args->flags & ~ETHOSU_BO_FLAGS)) {
ret = -EINVAL;
goto out_dev_exit;
}
ret = ethosu_gem_create_with_handle(file, ddev, &args->size,
args->flags, &args->handle);
out_dev_exit:
drm_dev_exit(cookie);
return ret;
}
static int ethosu_ioctl_bo_wait(struct drm_device *ddev, void *data,
struct drm_file *file)
{
struct drm_ethosu_bo_wait *args = data;
int cookie, ret;
unsigned long timeout = drm_timeout_abs_to_jiffies(args->timeout_ns);
if (args->pad)
return -EINVAL;
if (!drm_dev_enter(ddev, &cookie))
return -ENODEV;
ret = drm_gem_dma_resv_wait(file, args->handle, true, timeout);
drm_dev_exit(cookie);
return ret;
}
static int ethosu_ioctl_bo_mmap_offset(struct drm_device *ddev, void *data,
struct drm_file *file)
{
struct drm_ethosu_bo_mmap_offset *args = data;
struct drm_gem_object *obj;
if (args->pad)
return -EINVAL;
obj = drm_gem_object_lookup(file, args->handle);
if (!obj)
return -ENOENT;
args->offset = drm_vma_node_offset_addr(&obj->vma_node);
drm_gem_object_put(obj);
return 0;
}
static int ethosu_ioctl_cmdstream_bo_create(struct drm_device *ddev, void *data,
struct drm_file *file)
{
struct drm_ethosu_cmdstream_bo_create *args = data;
int cookie, ret;
if (!drm_dev_enter(ddev, &cookie))
return -ENODEV;
if (!args->size || !args->data || args->pad || args->flags) {
ret = -EINVAL;
goto out_dev_exit;
}
args->flags |= DRM_ETHOSU_BO_NO_MMAP;
ret = ethosu_gem_cmdstream_create(file, ddev, args->size, args->data,
args->flags, &args->handle);
out_dev_exit:
drm_dev_exit(cookie);
return ret;
}
static int ethosu_open(struct drm_device *ddev, struct drm_file *file)
{
int ret = 0;
if (!try_module_get(THIS_MODULE))
return -EINVAL;
struct ethosu_file_priv __free(kfree) *priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
ret = -ENOMEM;
goto err_put_mod;
}
priv->edev = to_ethosu_device(ddev);
ret = ethosu_job_open(priv);
if (ret)
goto err_put_mod;
file->driver_priv = no_free_ptr(priv);
return 0;
err_put_mod:
module_put(THIS_MODULE);
return ret;
}
static void ethosu_postclose(struct drm_device *ddev, struct drm_file *file)
{
ethosu_job_close(file->driver_priv);
kfree(file->driver_priv);
module_put(THIS_MODULE);
}
static const struct drm_ioctl_desc ethosu_drm_driver_ioctls[] = {
#define ETHOSU_IOCTL(n, func, flags) \
DRM_IOCTL_DEF_DRV(ETHOSU_##n, ethosu_ioctl_##func, flags)
ETHOSU_IOCTL(DEV_QUERY, dev_query, 0),
ETHOSU_IOCTL(BO_CREATE, bo_create, 0),
ETHOSU_IOCTL(BO_WAIT, bo_wait, 0),
ETHOSU_IOCTL(BO_MMAP_OFFSET, bo_mmap_offset, 0),
ETHOSU_IOCTL(CMDSTREAM_BO_CREATE, cmdstream_bo_create, 0),
ETHOSU_IOCTL(SUBMIT, submit, 0),
};
DEFINE_DRM_ACCEL_FOPS(ethosu_drm_driver_fops);
/*
* Ethosu driver version:
* - 1.0 - initial interface
*/
static const struct drm_driver ethosu_drm_driver = {
.driver_features = DRIVER_COMPUTE_ACCEL | DRIVER_GEM,
.open = ethosu_open,
.postclose = ethosu_postclose,
.ioctls = ethosu_drm_driver_ioctls,
.num_ioctls = ARRAY_SIZE(ethosu_drm_driver_ioctls),
.fops = &ethosu_drm_driver_fops,
.name = "ethosu",
.desc = "Arm Ethos-U Accel driver",
.major = 1,
.minor = 0,
.gem_create_object = ethosu_gem_create_object,
};
#define U65_DRAM_AXI_LIMIT_CFG 0x1f3f0002
#define U65_SRAM_AXI_LIMIT_CFG 0x1f3f00b0
#define U85_AXI_EXT_CFG 0x00021f3f
#define U85_AXI_SRAM_CFG 0x00021f3f
#define U85_MEM_ATTR0_CFG 0x00000000
#define U85_MEM_ATTR2_CFG 0x000000b7
static int ethosu_reset(struct ethosu_device *ethosudev)
{
int ret;
u32 reg;
writel_relaxed(RESET_PENDING_CSL, ethosudev->regs + NPU_REG_RESET);
ret = readl_poll_timeout(ethosudev->regs + NPU_REG_STATUS, reg,
!FIELD_GET(STATUS_RESET_STATUS, reg),
USEC_PER_MSEC, USEC_PER_SEC);
if (ret)
return ret;
if (!FIELD_GET(PROT_ACTIVE_CSL, readl_relaxed(ethosudev->regs + NPU_REG_PROT))) {
dev_warn(ethosudev->base.dev, "Could not reset to non-secure mode (PROT = %x)\n",
readl_relaxed(ethosudev->regs + NPU_REG_PROT));
}
/*
* Assign region 2 (SRAM) to AXI M0 (AXILIMIT0),
* everything else to AXI M1 (AXILIMIT2)
*/
writel_relaxed(0x0000aa8a, ethosudev->regs + NPU_REG_REGIONCFG);
if (ethosu_is_u65(ethosudev)) {
writel_relaxed(U65_SRAM_AXI_LIMIT_CFG, ethosudev->regs + NPU_REG_AXILIMIT0);
writel_relaxed(U65_DRAM_AXI_LIMIT_CFG, ethosudev->regs + NPU_REG_AXILIMIT2);
} else {
writel_relaxed(U85_AXI_SRAM_CFG, ethosudev->regs + NPU_REG_AXI_SRAM);
writel_relaxed(U85_AXI_EXT_CFG, ethosudev->regs + NPU_REG_AXI_EXT);
writel_relaxed(U85_MEM_ATTR0_CFG, ethosudev->regs + NPU_REG_MEM_ATTR0); // SRAM
writel_relaxed(U85_MEM_ATTR2_CFG, ethosudev->regs + NPU_REG_MEM_ATTR2); // DRAM
}
if (ethosudev->sram)
memset_io(ethosudev->sram, 0, ethosudev->npu_info.sram_size);
return 0;
}
static int ethosu_device_resume(struct device *dev)
{
struct ethosu_device *ethosudev = dev_get_drvdata(dev);
int ret;
ret = clk_bulk_prepare_enable(ethosudev->num_clks, ethosudev->clks);
if (ret)
return ret;
ret = ethosu_reset(ethosudev);
if (!ret)
return 0;
clk_bulk_disable_unprepare(ethosudev->num_clks, ethosudev->clks);
return ret;
}
static int ethosu_device_suspend(struct device *dev)
{
struct ethosu_device *ethosudev = dev_get_drvdata(dev);
clk_bulk_disable_unprepare(ethosudev->num_clks, ethosudev->clks);
return 0;
}
static int ethosu_sram_init(struct ethosu_device *ethosudev)
{
ethosudev->npu_info.sram_size = 0;
ethosudev->srampool = of_gen_pool_get(ethosudev->base.dev->of_node, "sram", 0);
if (!ethosudev->srampool)
return 0;
ethosudev->npu_info.sram_size = gen_pool_size(ethosudev->srampool);
ethosudev->sram = (void __iomem *)gen_pool_dma_alloc(ethosudev->srampool,
ethosudev->npu_info.sram_size,
&ethosudev->sramphys);
if (!ethosudev->sram) {
dev_err(ethosudev->base.dev, "failed to allocate from SRAM pool\n");
return -ENOMEM;
}
return 0;
}
static int ethosu_init(struct ethosu_device *ethosudev)
{
int ret;
u32 id, config;
ret = ethosu_device_resume(ethosudev->base.dev);
if (ret)
return ret;
pm_runtime_set_autosuspend_delay(ethosudev->base.dev, 50);
pm_runtime_use_autosuspend(ethosudev->base.dev);
ret = devm_pm_runtime_set_active_enabled(ethosudev->base.dev);
if (ret)
return ret;
pm_runtime_get_noresume(ethosudev->base.dev);
ethosudev->npu_info.id = id = readl_relaxed(ethosudev->regs + NPU_REG_ID);
ethosudev->npu_info.config = config = readl_relaxed(ethosudev->regs + NPU_REG_CONFIG);
ethosu_sram_init(ethosudev);
dev_info(ethosudev->base.dev,
"Ethos-U NPU, arch v%ld.%ld.%ld, rev r%ldp%ld, cmd stream ver%ld, %d MACs, %dKB SRAM\n",
FIELD_GET(ID_ARCH_MAJOR_MASK, id),
FIELD_GET(ID_ARCH_MINOR_MASK, id),
FIELD_GET(ID_ARCH_PATCH_MASK, id),
FIELD_GET(ID_VER_MAJOR_MASK, id),
FIELD_GET(ID_VER_MINOR_MASK, id),
FIELD_GET(CONFIG_CMD_STREAM_VER_MASK, config),
1 << FIELD_GET(CONFIG_MACS_PER_CC_MASK, config),
ethosudev->npu_info.sram_size / 1024);
return 0;
}
static int ethosu_probe(struct platform_device *pdev)
{
int ret;
struct ethosu_device *ethosudev;
ethosudev = devm_drm_dev_alloc(&pdev->dev, &ethosu_drm_driver,
struct ethosu_device, base);
if (IS_ERR(ethosudev))
return -ENOMEM;
platform_set_drvdata(pdev, ethosudev);
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40));
ethosudev->regs = devm_platform_ioremap_resource(pdev, 0);
ethosudev->num_clks = devm_clk_bulk_get_all(&pdev->dev, &ethosudev->clks);
if (ethosudev->num_clks < 0)
return ethosudev->num_clks;
ret = ethosu_job_init(ethosudev);
if (ret)
return ret;
ret = ethosu_init(ethosudev);
if (ret)
return ret;
ret = drm_dev_register(&ethosudev->base, 0);
if (ret)
pm_runtime_dont_use_autosuspend(ethosudev->base.dev);
pm_runtime_put_autosuspend(ethosudev->base.dev);
return ret;
}
static void ethosu_remove(struct platform_device *pdev)
{
struct ethosu_device *ethosudev = dev_get_drvdata(&pdev->dev);
drm_dev_unregister(&ethosudev->base);
ethosu_job_fini(ethosudev);
if (ethosudev->sram)
gen_pool_free(ethosudev->srampool, (unsigned long)ethosudev->sram,
ethosudev->npu_info.sram_size);
}
static const struct of_device_id dt_match[] = {
{ .compatible = "arm,ethos-u65" },
{ .compatible = "arm,ethos-u85" },
{}
};
MODULE_DEVICE_TABLE(of, dt_match);
static DEFINE_RUNTIME_DEV_PM_OPS(ethosu_pm_ops,
ethosu_device_suspend,
ethosu_device_resume,
NULL);
static struct platform_driver ethosu_driver = {
.probe = ethosu_probe,
.remove = ethosu_remove,
.driver = {
.name = "ethosu",
.pm = pm_ptr(&ethosu_pm_ops),
.of_match_table = dt_match,
},
};
module_platform_driver(ethosu_driver);
MODULE_AUTHOR("Rob Herring <robh@kernel.org>");
MODULE_DESCRIPTION("Arm Ethos-U Accel Driver");
MODULE_LICENSE("Dual MIT/GPL");

View File

@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
/* Copyright 2025 Arm, Ltd. */
#ifndef __ETHOSU_DRV_H__
#define __ETHOSU_DRV_H__
#include <drm/gpu_scheduler.h>
struct ethosu_device;
struct ethosu_file_priv {
struct ethosu_device *edev;
struct drm_sched_entity sched_entity;
};
#endif

View File

@@ -0,0 +1,704 @@
// SPDX-License-Identifier: GPL-2.0-only or MIT
/* Copyright 2025 Arm, Ltd. */
#include <linux/err.h>
#include <linux/slab.h>
#include <drm/ethosu_accel.h>
#include "ethosu_device.h"
#include "ethosu_gem.h"
static void ethosu_gem_free_object(struct drm_gem_object *obj)
{
struct ethosu_gem_object *bo = to_ethosu_bo(obj);
kfree(bo->info);
drm_gem_free_mmap_offset(&bo->base.base);
drm_gem_dma_free(&bo->base);
}
static int ethosu_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
{
struct ethosu_gem_object *bo = to_ethosu_bo(obj);
/* Don't allow mmap on objects that have the NO_MMAP flag set. */
if (bo->flags & DRM_ETHOSU_BO_NO_MMAP)
return -EINVAL;
return drm_gem_dma_object_mmap(obj, vma);
}
static const struct drm_gem_object_funcs ethosu_gem_funcs = {
.free = ethosu_gem_free_object,
.print_info = drm_gem_dma_object_print_info,
.get_sg_table = drm_gem_dma_object_get_sg_table,
.vmap = drm_gem_dma_object_vmap,
.mmap = ethosu_gem_mmap,
.vm_ops = &drm_gem_dma_vm_ops,
};
/**
* ethosu_gem_create_object - Implementation of driver->gem_create_object.
* @ddev: DRM device
* @size: Size in bytes of the memory the object will reference
*
* This lets the GEM helpers allocate object structs for us, and keep
* our BO stats correct.
*/
struct drm_gem_object *ethosu_gem_create_object(struct drm_device *ddev, size_t size)
{
struct ethosu_gem_object *obj;
obj = kzalloc(sizeof(*obj), GFP_KERNEL);
if (!obj)
return ERR_PTR(-ENOMEM);
obj->base.base.funcs = &ethosu_gem_funcs;
return &obj->base.base;
}
/**
* ethosu_gem_create_with_handle() - Create a GEM object and attach it to a handle.
* @file: DRM file.
* @ddev: DRM device.
* @size: Size of the GEM object to allocate.
* @flags: Combination of drm_ethosu_bo_flags flags.
* @handle: Pointer holding the handle pointing to the new GEM object.
*
* Return: Zero on success
*/
int ethosu_gem_create_with_handle(struct drm_file *file,
struct drm_device *ddev,
u64 *size, u32 flags, u32 *handle)
{
struct drm_gem_dma_object *mem;
struct ethosu_gem_object *bo;
int ret;
mem = drm_gem_dma_create(ddev, *size);
if (IS_ERR(mem))
return PTR_ERR(mem);
bo = to_ethosu_bo(&mem->base);
bo->flags = flags;
/*
* Allocate an id of idr table where the obj is registered
* and handle has the id what user can see.
*/
ret = drm_gem_handle_create(file, &mem->base, handle);
if (!ret)
*size = bo->base.base.size;
/* drop reference from allocate - handle holds it now. */
drm_gem_object_put(&mem->base);
return ret;
}
struct dma {
s8 region;
u64 len;
u64 offset;
s64 stride[2];
};
struct dma_state {
u16 size0;
u16 size1;
s8 mode;
struct dma src;
struct dma dst;
};
struct buffer {
u64 base;
u32 length;
s8 region;
};
struct feat_matrix {
u64 base[4];
s64 stride_x;
s64 stride_y;
s64 stride_c;
s8 region;
u8 broadcast;
u16 stride_kernel;
u16 precision;
u16 depth;
u16 width;
u16 width0;
u16 height[3];
u8 pad_top;
u8 pad_left;
u8 pad_bottom;
u8 pad_right;
};
struct cmd_state {
struct dma_state dma;
struct buffer scale[2];
struct buffer weight[4];
struct feat_matrix ofm;
struct feat_matrix ifm;
struct feat_matrix ifm2;
};
static void cmd_state_init(struct cmd_state *st)
{
/* Initialize to all 1s to detect missing setup */
memset(st, 0xff, sizeof(*st));
}
static u64 cmd_to_addr(u32 *cmd)
{
return ((u64)((cmd[0] & 0xff0000) << 16)) | cmd[1];
}
static u64 dma_length(struct ethosu_validated_cmdstream_info *info,
struct dma_state *dma_st, struct dma *dma)
{
s8 mode = dma_st->mode;
u64 len = dma->len;
if (mode >= 1) {
len += dma->stride[0];
len *= dma_st->size0;
}
if (mode == 2) {
len += dma->stride[1];
len *= dma_st->size1;
}
if (dma->region >= 0)
info->region_size[dma->region] = max(info->region_size[dma->region],
len + dma->offset);
return len;
}
static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info,
struct feat_matrix *fm,
u32 x, u32 y, u32 c)
{
u32 element_size, storage = fm->precision >> 14;
int tile = 0;
u64 addr;
if (fm->region < 0)
return U64_MAX;
switch (storage) {
case 0:
if (x >= fm->width0 + 1) {
x -= fm->width0 + 1;
tile += 1;
}
if (y >= fm->height[tile] + 1) {
y -= fm->height[tile] + 1;
tile += 2;
}
break;
case 1:
if (y >= fm->height[1] + 1) {
y -= fm->height[1] + 1;
tile = 2;
} else if (y >= fm->height[0] + 1) {
y -= fm->height[0] + 1;
tile = 1;
}
break;
}
if (fm->base[tile] == U64_MAX)
return U64_MAX;
addr = fm->base[tile] + y * fm->stride_y;
switch ((fm->precision >> 6) & 0x3) { // format
case 0: //nhwc:
addr += x * fm->stride_x + c;
break;
case 1: //nhcwb16:
element_size = BIT((fm->precision >> 1) & 0x3);
addr += (c / 16) * fm->stride_c + (16 * x + (c & 0xf)) * element_size;
break;
}
info->region_size[fm->region] = max(info->region_size[fm->region], addr + 1);
return addr;
}
static int calc_sizes(struct drm_device *ddev,
struct ethosu_validated_cmdstream_info *info,
u16 op, struct cmd_state *st,
bool ifm, bool ifm2, bool weight, bool scale)
{
u64 len;
if (ifm) {
if (st->ifm.stride_kernel == U16_MAX)
return -EINVAL;
u32 stride_y = ((st->ifm.stride_kernel >> 8) & 0x2) +
((st->ifm.stride_kernel >> 1) & 0x1) + 1;
u32 stride_x = ((st->ifm.stride_kernel >> 5) & 0x2) +
(st->ifm.stride_kernel & 0x1) + 1;
u32 ifm_height = st->ofm.height[2] * stride_y +
st->ifm.height[2] - (st->ifm.pad_top + st->ifm.pad_bottom);
u32 ifm_width = st->ofm.width * stride_x +
st->ifm.width - (st->ifm.pad_left + st->ifm.pad_right);
len = feat_matrix_length(info, &st->ifm, ifm_width,
ifm_height, st->ifm.depth);
dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n",
op, st->ifm.region, st->ifm.base[0], len);
if (len == U64_MAX)
return -EINVAL;
}
if (ifm2) {
len = feat_matrix_length(info, &st->ifm2, st->ifm.depth,
0, st->ofm.depth);
dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n",
op, st->ifm2.region, st->ifm2.base[0], len);
if (len == U64_MAX)
return -EINVAL;
}
if (weight) {
dev_dbg(ddev->dev, "op %d: W:%d:0x%llx-0x%llx\n",
op, st->weight[0].region, st->weight[0].base,
st->weight[0].base + st->weight[0].length - 1);
if (st->weight[0].region < 0 || st->weight[0].base == U64_MAX ||
st->weight[0].length == U32_MAX)
return -EINVAL;
info->region_size[st->weight[0].region] =
max(info->region_size[st->weight[0].region],
st->weight[0].base + st->weight[0].length);
}
if (scale) {
dev_dbg(ddev->dev, "op %d: S:%d:0x%llx-0x%llx\n",
op, st->scale[0].region, st->scale[0].base,
st->scale[0].base + st->scale[0].length - 1);
if (st->scale[0].region < 0 || st->scale[0].base == U64_MAX ||
st->scale[0].length == U32_MAX)
return -EINVAL;
info->region_size[st->scale[0].region] =
max(info->region_size[st->scale[0].region],
st->scale[0].base + st->scale[0].length);
}
len = feat_matrix_length(info, &st->ofm, st->ofm.width,
st->ofm.height[2], st->ofm.depth);
dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
op, st->ofm.region, st->ofm.base[0], len);
if (len == U64_MAX)
return -EINVAL;
info->output_region[st->ofm.region] = true;
return 0;
}
static int calc_sizes_elemwise(struct drm_device *ddev,
struct ethosu_validated_cmdstream_info *info,
u16 op, struct cmd_state *st,
bool ifm, bool ifm2)
{
u32 height, width, depth;
u64 len;
if (ifm) {
height = st->ifm.broadcast & 0x1 ? 0 : st->ofm.height[2];
width = st->ifm.broadcast & 0x2 ? 0 : st->ofm.width;
depth = st->ifm.broadcast & 0x4 ? 0 : st->ofm.depth;
len = feat_matrix_length(info, &st->ifm, width,
height, depth);
dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n",
op, st->ifm.region, st->ifm.base[0], len);
if (len == U64_MAX)
return -EINVAL;
}
if (ifm2) {
height = st->ifm2.broadcast & 0x1 ? 0 : st->ofm.height[2];
width = st->ifm2.broadcast & 0x2 ? 0 : st->ofm.width;
depth = st->ifm2.broadcast & 0x4 ? 0 : st->ofm.depth;
len = feat_matrix_length(info, &st->ifm2, width,
height, depth);
dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n",
op, st->ifm2.region, st->ifm2.base[0], len);
if (len == U64_MAX)
return -EINVAL;
}
len = feat_matrix_length(info, &st->ofm, st->ofm.width,
st->ofm.height[2], st->ofm.depth);
dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
op, st->ofm.region, st->ofm.base[0], len);
if (len == U64_MAX)
return -EINVAL;
info->output_region[st->ofm.region] = true;
return 0;
}
static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
u32 __user *ucmds,
struct ethosu_gem_object *bo,
u32 size)
{
struct ethosu_validated_cmdstream_info __free(kfree) *info = kzalloc(sizeof(*info), GFP_KERNEL);
struct ethosu_device *edev = to_ethosu_device(ddev);
u32 *bocmds = bo->base.vaddr;
struct cmd_state st;
int i, ret;
if (!info)
return -ENOMEM;
info->cmd_size = size;
cmd_state_init(&st);
for (i = 0; i < size / 4; i++) {
bool use_ifm, use_ifm2, use_scale;
u64 dstlen, srclen;
u16 cmd, param;
u32 cmds[2];
u64 addr;
if (get_user(cmds[0], ucmds++))
return -EFAULT;
bocmds[i] = cmds[0];
cmd = cmds[0];
param = cmds[0] >> 16;
if (cmd & 0x4000) {
if (get_user(cmds[1], ucmds++))
return -EFAULT;
i++;
bocmds[i] = cmds[1];
addr = cmd_to_addr(cmds);
}
switch (cmd) {
case NPU_OP_DMA_START:
srclen = dma_length(info, &st.dma, &st.dma.src);
dstlen = dma_length(info, &st.dma, &st.dma.dst);
if (st.dma.dst.region >= 0)
info->output_region[st.dma.dst.region] = true;
dev_dbg(ddev->dev, "cmd: DMA SRC:%d:0x%llx+0x%llx DST:%d:0x%llx+0x%llx\n",
st.dma.src.region, st.dma.src.offset, srclen,
st.dma.dst.region, st.dma.dst.offset, dstlen);
break;
case NPU_OP_CONV:
case NPU_OP_DEPTHWISE:
use_ifm2 = param & 0x1; // weights_ifm2
use_scale = !(st.ofm.precision & 0x100);
ret = calc_sizes(ddev, info, cmd, &st, true, use_ifm2,
!use_ifm2, use_scale);
if (ret)
return ret;
break;
case NPU_OP_POOL:
use_ifm = param != 0x4; // pooling mode
use_scale = !(st.ofm.precision & 0x100);
ret = calc_sizes(ddev, info, cmd, &st, use_ifm, false,
false, use_scale);
if (ret)
return ret;
break;
case NPU_OP_ELEMENTWISE:
use_ifm2 = !((st.ifm2.broadcast == 8) || (param == 5) ||
(param == 6) || (param == 7) || (param == 0x24));
use_ifm = st.ifm.broadcast != 8;
ret = calc_sizes_elemwise(ddev, info, cmd, &st, use_ifm, use_ifm2);
if (ret)
return ret;
break;
case NPU_OP_RESIZE: // U85 only
WARN_ON(1); // TODO
break;
case NPU_SET_KERNEL_WIDTH_M1:
st.ifm.width = param;
break;
case NPU_SET_KERNEL_HEIGHT_M1:
st.ifm.height[2] = param;
break;
case NPU_SET_KERNEL_STRIDE:
st.ifm.stride_kernel = param;
break;
case NPU_SET_IFM_PAD_TOP:
st.ifm.pad_top = param & 0x7f;
break;
case NPU_SET_IFM_PAD_LEFT:
st.ifm.pad_left = param & 0x7f;
break;
case NPU_SET_IFM_PAD_RIGHT:
st.ifm.pad_right = param & 0xff;
break;
case NPU_SET_IFM_PAD_BOTTOM:
st.ifm.pad_bottom = param & 0xff;
break;
case NPU_SET_IFM_DEPTH_M1:
st.ifm.depth = param;
break;
case NPU_SET_IFM_PRECISION:
st.ifm.precision = param;
break;
case NPU_SET_IFM_BROADCAST:
st.ifm.broadcast = param;
break;
case NPU_SET_IFM_REGION:
st.ifm.region = param & 0x7f;
break;
case NPU_SET_IFM_WIDTH0_M1:
st.ifm.width0 = param;
break;
case NPU_SET_IFM_HEIGHT0_M1:
st.ifm.height[0] = param;
break;
case NPU_SET_IFM_HEIGHT1_M1:
st.ifm.height[1] = param;
break;
case NPU_SET_IFM_BASE0:
case NPU_SET_IFM_BASE1:
case NPU_SET_IFM_BASE2:
case NPU_SET_IFM_BASE3:
st.ifm.base[cmd & 0x3] = addr;
break;
case NPU_SET_IFM_STRIDE_X:
st.ifm.stride_x = addr;
break;
case NPU_SET_IFM_STRIDE_Y:
st.ifm.stride_y = addr;
break;
case NPU_SET_IFM_STRIDE_C:
st.ifm.stride_c = addr;
break;
case NPU_SET_OFM_WIDTH_M1:
st.ofm.width = param;
break;
case NPU_SET_OFM_HEIGHT_M1:
st.ofm.height[2] = param;
break;
case NPU_SET_OFM_DEPTH_M1:
st.ofm.depth = param;
break;
case NPU_SET_OFM_PRECISION:
st.ofm.precision = param;
break;
case NPU_SET_OFM_REGION:
st.ofm.region = param & 0x7;
break;
case NPU_SET_OFM_WIDTH0_M1:
st.ofm.width0 = param;
break;
case NPU_SET_OFM_HEIGHT0_M1:
st.ofm.height[0] = param;
break;
case NPU_SET_OFM_HEIGHT1_M1:
st.ofm.height[1] = param;
break;
case NPU_SET_OFM_BASE0:
case NPU_SET_OFM_BASE1:
case NPU_SET_OFM_BASE2:
case NPU_SET_OFM_BASE3:
st.ofm.base[cmd & 0x3] = addr;
break;
case NPU_SET_OFM_STRIDE_X:
st.ofm.stride_x = addr;
break;
case NPU_SET_OFM_STRIDE_Y:
st.ofm.stride_y = addr;
break;
case NPU_SET_OFM_STRIDE_C:
st.ofm.stride_c = addr;
break;
case NPU_SET_IFM2_BROADCAST:
st.ifm2.broadcast = param;
break;
case NPU_SET_IFM2_PRECISION:
st.ifm2.precision = param;
break;
case NPU_SET_IFM2_REGION:
st.ifm2.region = param & 0x7;
break;
case NPU_SET_IFM2_WIDTH0_M1:
st.ifm2.width0 = param;
break;
case NPU_SET_IFM2_HEIGHT0_M1:
st.ifm2.height[0] = param;
break;
case NPU_SET_IFM2_HEIGHT1_M1:
st.ifm2.height[1] = param;
break;
case NPU_SET_IFM2_BASE0:
case NPU_SET_IFM2_BASE1:
case NPU_SET_IFM2_BASE2:
case NPU_SET_IFM2_BASE3:
st.ifm2.base[cmd & 0x3] = addr;
break;
case NPU_SET_IFM2_STRIDE_X:
st.ifm2.stride_x = addr;
break;
case NPU_SET_IFM2_STRIDE_Y:
st.ifm2.stride_y = addr;
break;
case NPU_SET_IFM2_STRIDE_C:
st.ifm2.stride_c = addr;
break;
case NPU_SET_WEIGHT_REGION:
st.weight[0].region = param & 0x7;
break;
case NPU_SET_SCALE_REGION:
st.scale[0].region = param & 0x7;
break;
case NPU_SET_WEIGHT_BASE:
st.weight[0].base = addr;
break;
case NPU_SET_WEIGHT_LENGTH:
st.weight[0].length = cmds[1];
break;
case NPU_SET_SCALE_BASE:
st.scale[0].base = addr;
break;
case NPU_SET_SCALE_LENGTH:
st.scale[0].length = cmds[1];
break;
case NPU_SET_WEIGHT1_BASE:
st.weight[1].base = addr;
break;
case NPU_SET_WEIGHT1_LENGTH:
st.weight[1].length = cmds[1];
break;
case NPU_SET_SCALE1_BASE: // NPU_SET_WEIGHT2_BASE (U85)
if (ethosu_is_u65(edev))
st.scale[1].base = addr;
else
st.weight[2].base = addr;
break;
case NPU_SET_SCALE1_LENGTH: // NPU_SET_WEIGHT2_LENGTH (U85)
if (ethosu_is_u65(edev))
st.scale[1].length = cmds[1];
else
st.weight[1].length = cmds[1];
break;
case NPU_SET_WEIGHT3_BASE:
st.weight[3].base = addr;
break;
case NPU_SET_WEIGHT3_LENGTH:
st.weight[3].length = cmds[1];
break;
case NPU_SET_DMA0_SRC_REGION:
if (param & 0x100)
st.dma.src.region = -1;
else
st.dma.src.region = param & 0x7;
st.dma.mode = (param >> 9) & 0x3;
break;
case NPU_SET_DMA0_DST_REGION:
if (param & 0x100)
st.dma.dst.region = -1;
else
st.dma.dst.region = param & 0x7;
break;
case NPU_SET_DMA0_SIZE0:
st.dma.size0 = param;
break;
case NPU_SET_DMA0_SIZE1:
st.dma.size1 = param;
break;
case NPU_SET_DMA0_SRC_STRIDE0:
st.dma.src.stride[0] = ((s64)addr << 24) >> 24;
break;
case NPU_SET_DMA0_SRC_STRIDE1:
st.dma.src.stride[1] = ((s64)addr << 24) >> 24;
break;
case NPU_SET_DMA0_DST_STRIDE0:
st.dma.dst.stride[0] = ((s64)addr << 24) >> 24;
break;
case NPU_SET_DMA0_DST_STRIDE1:
st.dma.dst.stride[1] = ((s64)addr << 24) >> 24;
break;
case NPU_SET_DMA0_SRC:
st.dma.src.offset = addr;
break;
case NPU_SET_DMA0_DST:
st.dma.dst.offset = addr;
break;
case NPU_SET_DMA0_LEN:
st.dma.src.len = st.dma.dst.len = addr;
break;
default:
break;
}
}
for (i = 0; i < NPU_BASEP_REGION_MAX; i++) {
if (!info->region_size[i])
continue;
dev_dbg(ddev->dev, "region %d max size: 0x%llx\n",
i, info->region_size[i]);
}
bo->info = no_free_ptr(info);
return 0;
}
/**
* ethosu_gem_cmdstream_create() - Create a GEM object and attach it to a handle.
* @file: DRM file.
* @ddev: DRM device.
* @exclusive_vm: Exclusive VM. Not NULL if the GEM object can't be shared.
* @size: Size of the GEM object to allocate.
* @flags: Combination of drm_ethosu_bo_flags flags.
* @handle: Pointer holding the handle pointing to the new GEM object.
*
* Return: Zero on success
*/
int ethosu_gem_cmdstream_create(struct drm_file *file,
struct drm_device *ddev,
u32 size, u64 data, u32 flags, u32 *handle)
{
int ret;
struct drm_gem_dma_object *mem;
struct ethosu_gem_object *bo;
mem = drm_gem_dma_create(ddev, size);
if (IS_ERR(mem))
return PTR_ERR(mem);
bo = to_ethosu_bo(&mem->base);
bo->flags = flags;
ret = ethosu_gem_cmdstream_copy_and_validate(ddev,
(void __user *)(uintptr_t)data,
bo, size);
if (ret)
goto fail;
/*
* Allocate an id of idr table where the obj is registered
* and handle has the id what user can see.
*/
ret = drm_gem_handle_create(file, &mem->base, handle);
fail:
/* drop reference from allocate - handle holds it now. */
drm_gem_object_put(&mem->base);
return ret;
}

View File

@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2025 Arm, Ltd. */
#ifndef __ETHOSU_GEM_H__
#define __ETHOSU_GEM_H__
#include "ethosu_device.h"
#include <drm/drm_gem_dma_helper.h>
struct ethosu_validated_cmdstream_info {
u32 cmd_size;
u64 region_size[NPU_BASEP_REGION_MAX];
bool output_region[NPU_BASEP_REGION_MAX];
};
/**
* struct ethosu_gem_object - Driver specific GEM object.
*/
struct ethosu_gem_object {
/** @base: Inherit from drm_gem_shmem_object. */
struct drm_gem_dma_object base;
struct ethosu_validated_cmdstream_info *info;
/** @flags: Combination of drm_ethosu_bo_flags flags. */
u32 flags;
};
static inline
struct ethosu_gem_object *to_ethosu_bo(struct drm_gem_object *obj)
{
return container_of(to_drm_gem_dma_obj(obj), struct ethosu_gem_object, base);
}
struct drm_gem_object *ethosu_gem_create_object(struct drm_device *ddev,
size_t size);
int ethosu_gem_create_with_handle(struct drm_file *file,
struct drm_device *ddev,
u64 *size, u32 flags, uint32_t *handle);
int ethosu_gem_cmdstream_create(struct drm_file *file,
struct drm_device *ddev,
u32 size, u64 data, u32 flags, u32 *handle);
#endif /* __ETHOSU_GEM_H__ */

View File

@@ -0,0 +1,496 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
/* Copyright 2025 Arm, Ltd. */
#include <linux/bitfield.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_file.h>
#include <drm/drm_gem.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/ethosu_accel.h>
#include "ethosu_device.h"
#include "ethosu_drv.h"
#include "ethosu_gem.h"
#include "ethosu_job.h"
#define JOB_TIMEOUT_MS 500
static struct ethosu_job *to_ethosu_job(struct drm_sched_job *sched_job)
{
return container_of(sched_job, struct ethosu_job, base);
}
static const char *ethosu_fence_get_driver_name(struct dma_fence *fence)
{
return "ethosu";
}
static const char *ethosu_fence_get_timeline_name(struct dma_fence *fence)
{
return "ethosu-npu";
}
static const struct dma_fence_ops ethosu_fence_ops = {
.get_driver_name = ethosu_fence_get_driver_name,
.get_timeline_name = ethosu_fence_get_timeline_name,
};
static void ethosu_job_hw_submit(struct ethosu_device *dev, struct ethosu_job *job)
{
struct drm_gem_dma_object *cmd_bo = to_drm_gem_dma_obj(job->cmd_bo);
struct ethosu_validated_cmdstream_info *cmd_info = to_ethosu_bo(job->cmd_bo)->info;
for (int i = 0; i < job->region_cnt; i++) {
struct drm_gem_dma_object *bo;
int region = job->region_bo_num[i];
bo = to_drm_gem_dma_obj(job->region_bo[i]);
writel_relaxed(lower_32_bits(bo->dma_addr), dev->regs + NPU_REG_BASEP(region));
writel_relaxed(upper_32_bits(bo->dma_addr), dev->regs + NPU_REG_BASEP_HI(region));
dev_dbg(dev->base.dev, "Region %d base addr = %pad\n", region, &bo->dma_addr);
}
if (job->sram_size) {
writel_relaxed(lower_32_bits(dev->sramphys),
dev->regs + NPU_REG_BASEP(ETHOSU_SRAM_REGION));
writel_relaxed(upper_32_bits(dev->sramphys),
dev->regs + NPU_REG_BASEP_HI(ETHOSU_SRAM_REGION));
dev_dbg(dev->base.dev, "Region %d base addr = %pad (SRAM)\n",
ETHOSU_SRAM_REGION, &dev->sramphys);
}
writel_relaxed(lower_32_bits(cmd_bo->dma_addr), dev->regs + NPU_REG_QBASE);
writel_relaxed(upper_32_bits(cmd_bo->dma_addr), dev->regs + NPU_REG_QBASE_HI);
writel_relaxed(cmd_info->cmd_size, dev->regs + NPU_REG_QSIZE);
writel(CMD_TRANSITION_TO_RUN, dev->regs + NPU_REG_CMD);
dev_dbg(dev->base.dev,
"Submitted cmd at %pad to core\n", &cmd_bo->dma_addr);
}
static int ethosu_acquire_object_fences(struct ethosu_job *job)
{
int i, ret;
struct drm_gem_object **bos = job->region_bo;
struct ethosu_validated_cmdstream_info *info = to_ethosu_bo(job->cmd_bo)->info;
for (i = 0; i < job->region_cnt; i++) {
bool is_write;
if (!bos[i])
break;
ret = dma_resv_reserve_fences(bos[i]->resv, 1);
if (ret)
return ret;
is_write = info->output_region[job->region_bo_num[i]];
ret = drm_sched_job_add_implicit_dependencies(&job->base, bos[i],
is_write);
if (ret)
return ret;
}
return 0;
}
static void ethosu_attach_object_fences(struct ethosu_job *job)
{
int i;
struct dma_fence *fence = job->inference_done_fence;
struct drm_gem_object **bos = job->region_bo;
struct ethosu_validated_cmdstream_info *info = to_ethosu_bo(job->cmd_bo)->info;
for (i = 0; i < job->region_cnt; i++)
if (info->output_region[job->region_bo_num[i]])
dma_resv_add_fence(bos[i]->resv, fence, DMA_RESV_USAGE_WRITE);
}
static int ethosu_job_push(struct ethosu_job *job)
{
struct ww_acquire_ctx acquire_ctx;
int ret;
ret = drm_gem_lock_reservations(job->region_bo, job->region_cnt, &acquire_ctx);
if (ret)
return ret;
ret = ethosu_acquire_object_fences(job);
if (ret)
goto out;
ret = pm_runtime_resume_and_get(job->dev->base.dev);
if (!ret) {
guard(mutex)(&job->dev->sched_lock);
drm_sched_job_arm(&job->base);
job->inference_done_fence = dma_fence_get(&job->base.s_fence->finished);
kref_get(&job->refcount); /* put by scheduler job completion */
drm_sched_entity_push_job(&job->base);
ethosu_attach_object_fences(job);
}
out:
drm_gem_unlock_reservations(job->region_bo, job->region_cnt, &acquire_ctx);
return ret;
}
static void ethosu_job_cleanup(struct kref *ref)
{
struct ethosu_job *job = container_of(ref, struct ethosu_job,
refcount);
unsigned int i;
pm_runtime_put_autosuspend(job->dev->base.dev);
dma_fence_put(job->done_fence);
dma_fence_put(job->inference_done_fence);
for (i = 0; i < job->region_cnt; i++)
drm_gem_object_put(job->region_bo[i]);
drm_gem_object_put(job->cmd_bo);
kfree(job);
}
static void ethosu_job_put(struct ethosu_job *job)
{
kref_put(&job->refcount, ethosu_job_cleanup);
}
static void ethosu_job_free(struct drm_sched_job *sched_job)
{
struct ethosu_job *job = to_ethosu_job(sched_job);
drm_sched_job_cleanup(sched_job);
ethosu_job_put(job);
}
static struct dma_fence *ethosu_job_run(struct drm_sched_job *sched_job)
{
struct ethosu_job *job = to_ethosu_job(sched_job);
struct ethosu_device *dev = job->dev;
struct dma_fence *fence = job->done_fence;
if (unlikely(job->base.s_fence->finished.error))
return NULL;
dma_fence_init(fence, &ethosu_fence_ops, &dev->fence_lock,
dev->fence_context, ++dev->emit_seqno);
dma_fence_get(fence);
scoped_guard(mutex, &dev->job_lock) {
dev->in_flight_job = job;
ethosu_job_hw_submit(dev, job);
}
return fence;
}
static void ethosu_job_handle_irq(struct ethosu_device *dev)
{
u32 status = readl_relaxed(dev->regs + NPU_REG_STATUS);
if (status & (STATUS_BUS_STATUS | STATUS_CMD_PARSE_ERR)) {
dev_err(dev->base.dev, "Error IRQ - %x\n", status);
drm_sched_fault(&dev->sched);
return;
}
scoped_guard(mutex, &dev->job_lock) {
if (dev->in_flight_job) {
dma_fence_signal(dev->in_flight_job->done_fence);
dev->in_flight_job = NULL;
}
}
}
static irqreturn_t ethosu_job_irq_handler_thread(int irq, void *data)
{
struct ethosu_device *dev = data;
ethosu_job_handle_irq(dev);
return IRQ_HANDLED;
}
static irqreturn_t ethosu_job_irq_handler(int irq, void *data)
{
struct ethosu_device *dev = data;
u32 status = readl_relaxed(dev->regs + NPU_REG_STATUS);
if (!(status & STATUS_IRQ_RAISED))
return IRQ_NONE;
writel_relaxed(CMD_CLEAR_IRQ, dev->regs + NPU_REG_CMD);
return IRQ_WAKE_THREAD;
}
static enum drm_gpu_sched_stat ethosu_job_timedout(struct drm_sched_job *bad)
{
struct ethosu_job *job = to_ethosu_job(bad);
struct ethosu_device *dev = job->dev;
bool running;
u32 *bocmds = to_drm_gem_dma_obj(job->cmd_bo)->vaddr;
u32 cmdaddr;
cmdaddr = readl_relaxed(dev->regs + NPU_REG_QREAD);
running = FIELD_GET(STATUS_STATE_RUNNING, readl_relaxed(dev->regs + NPU_REG_STATUS));
if (running) {
int ret;
u32 reg;
ret = readl_relaxed_poll_timeout(dev->regs + NPU_REG_QREAD,
reg,
reg != cmdaddr,
USEC_PER_MSEC, 100 * USEC_PER_MSEC);
/* If still running and progress is being made, just return */
if (!ret)
return DRM_GPU_SCHED_STAT_NO_HANG;
}
dev_err(dev->base.dev, "NPU sched timed out: NPU %s, cmdstream offset 0x%x: 0x%x\n",
running ? "running" : "stopped",
cmdaddr, bocmds[cmdaddr / 4]);
drm_sched_stop(&dev->sched, bad);
scoped_guard(mutex, &dev->job_lock)
dev->in_flight_job = NULL;
/* Proceed with reset now. */
pm_runtime_force_suspend(dev->base.dev);
pm_runtime_force_resume(dev->base.dev);
/* Restart the scheduler */
drm_sched_start(&dev->sched, 0);
return DRM_GPU_SCHED_STAT_RESET;
}
static const struct drm_sched_backend_ops ethosu_sched_ops = {
.run_job = ethosu_job_run,
.timedout_job = ethosu_job_timedout,
.free_job = ethosu_job_free
};
int ethosu_job_init(struct ethosu_device *edev)
{
struct device *dev = edev->base.dev;
struct drm_sched_init_args args = {
.ops = &ethosu_sched_ops,
.num_rqs = DRM_SCHED_PRIORITY_COUNT,
.credit_limit = 1,
.timeout = msecs_to_jiffies(JOB_TIMEOUT_MS),
.name = dev_name(dev),
.dev = dev,
};
int ret;
spin_lock_init(&edev->fence_lock);
ret = devm_mutex_init(dev, &edev->job_lock);
if (ret)
return ret;
ret = devm_mutex_init(dev, &edev->sched_lock);
if (ret)
return ret;
edev->irq = platform_get_irq(to_platform_device(dev), 0);
if (edev->irq < 0)
return edev->irq;
ret = devm_request_threaded_irq(dev, edev->irq,
ethosu_job_irq_handler,
ethosu_job_irq_handler_thread,
IRQF_SHARED, KBUILD_MODNAME,
edev);
if (ret) {
dev_err(dev, "failed to request irq\n");
return ret;
}
edev->fence_context = dma_fence_context_alloc(1);
ret = drm_sched_init(&edev->sched, &args);
if (ret) {
dev_err(dev, "Failed to create scheduler: %d\n", ret);
goto err_sched;
}
return 0;
err_sched:
drm_sched_fini(&edev->sched);
return ret;
}
void ethosu_job_fini(struct ethosu_device *dev)
{
drm_sched_fini(&dev->sched);
}
int ethosu_job_open(struct ethosu_file_priv *ethosu_priv)
{
struct ethosu_device *dev = ethosu_priv->edev;
struct drm_gpu_scheduler *sched = &dev->sched;
int ret;
ret = drm_sched_entity_init(&ethosu_priv->sched_entity,
DRM_SCHED_PRIORITY_NORMAL,
&sched, 1, NULL);
return WARN_ON(ret);
}
void ethosu_job_close(struct ethosu_file_priv *ethosu_priv)
{
struct drm_sched_entity *entity = &ethosu_priv->sched_entity;
drm_sched_entity_destroy(entity);
}
static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file,
struct drm_ethosu_job *job)
{
struct ethosu_device *edev = to_ethosu_device(dev);
struct ethosu_file_priv *file_priv = file->driver_priv;
struct ethosu_job *ejob = NULL;
struct ethosu_validated_cmdstream_info *cmd_info;
int ret = 0;
/* BO region 2 is reserved if SRAM is used */
if (job->region_bo_handles[ETHOSU_SRAM_REGION] && job->sram_size)
return -EINVAL;
if (edev->npu_info.sram_size < job->sram_size)
return -EINVAL;
ejob = kzalloc(sizeof(*ejob), GFP_KERNEL);
if (!ejob)
return -ENOMEM;
kref_init(&ejob->refcount);
ejob->dev = edev;
ejob->sram_size = job->sram_size;
ejob->done_fence = kzalloc(sizeof(*ejob->done_fence), GFP_KERNEL);
if (!ejob->done_fence) {
ret = -ENOMEM;
goto out_cleanup_job;
}
ret = drm_sched_job_init(&ejob->base,
&file_priv->sched_entity,
1, NULL, file->client_id);
if (ret)
goto out_put_job;
ejob->cmd_bo = drm_gem_object_lookup(file, job->cmd_bo);
if (!ejob->cmd_bo) {
ret = -ENOENT;
goto out_cleanup_job;
}
cmd_info = to_ethosu_bo(ejob->cmd_bo)->info;
if (!cmd_info) {
ret = -EINVAL;
goto out_cleanup_job;
}
for (int i = 0; i < NPU_BASEP_REGION_MAX; i++) {
struct drm_gem_object *gem;
/* Can only omit a BO handle if the region is not used or used for SRAM */
if (!job->region_bo_handles[i] &&
(!cmd_info->region_size[i] || (i == ETHOSU_SRAM_REGION && job->sram_size)))
continue;
if (job->region_bo_handles[i] && !cmd_info->region_size[i]) {
dev_err(dev->dev,
"Cmdstream BO handle %d set for unused region %d\n",
job->region_bo_handles[i], i);
ret = -EINVAL;
goto out_cleanup_job;
}
gem = drm_gem_object_lookup(file, job->region_bo_handles[i]);
if (!gem) {
dev_err(dev->dev,
"Invalid BO handle %d for region %d\n",
job->region_bo_handles[i], i);
ret = -ENOENT;
goto out_cleanup_job;
}
ejob->region_bo[ejob->region_cnt] = gem;
ejob->region_bo_num[ejob->region_cnt] = i;
ejob->region_cnt++;
if (to_ethosu_bo(gem)->info) {
dev_err(dev->dev,
"Cmdstream BO handle %d used for region %d\n",
job->region_bo_handles[i], i);
ret = -EINVAL;
goto out_cleanup_job;
}
/* Verify the command stream doesn't have accesses outside the BO */
if (cmd_info->region_size[i] > gem->size) {
dev_err(dev->dev,
"cmd stream region %d size greater than BO size (%llu > %zu)\n",
i, cmd_info->region_size[i], gem->size);
ret = -EOVERFLOW;
goto out_cleanup_job;
}
}
ret = ethosu_job_push(ejob);
out_cleanup_job:
if (ret)
drm_sched_job_cleanup(&ejob->base);
out_put_job:
ethosu_job_put(ejob);
return ret;
}
int ethosu_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_ethosu_submit *args = data;
int ret = 0;
unsigned int i = 0;
if (args->pad) {
drm_dbg(dev, "Reserved field in drm_ethosu_submit struct should be 0.\n");
return -EINVAL;
}
struct drm_ethosu_job __free(kvfree) *jobs =
kvmalloc_array(args->job_count, sizeof(*jobs), GFP_KERNEL);
if (!jobs)
return -ENOMEM;
if (copy_from_user(jobs,
(void __user *)(uintptr_t)args->jobs,
args->job_count * sizeof(*jobs))) {
drm_dbg(dev, "Failed to copy incoming job array\n");
return -EFAULT;
}
for (i = 0; i < args->job_count; i++) {
ret = ethosu_ioctl_submit_job(dev, file, &jobs[i]);
if (ret)
return ret;
}
return 0;
}

View File

@@ -0,0 +1,40 @@
/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
/* Copyright 2025 Arm, Ltd. */
#ifndef __ETHOSU_JOB_H__
#define __ETHOSU_JOB_H__
#include <linux/kref.h>
#include <drm/gpu_scheduler.h>
struct ethosu_device;
struct ethosu_file_priv;
struct ethosu_job {
struct drm_sched_job base;
struct ethosu_device *dev;
struct drm_gem_object *cmd_bo;
struct drm_gem_object *region_bo[NPU_BASEP_REGION_MAX];
u8 region_bo_num[NPU_BASEP_REGION_MAX];
u8 region_cnt;
u32 sram_size;
/* Fence to be signaled by drm-sched once its done with the job */
struct dma_fence *inference_done_fence;
/* Fence to be signaled by IRQ handler when the job is complete. */
struct dma_fence *done_fence;
struct kref refcount;
};
int ethosu_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *file);
int ethosu_job_init(struct ethosu_device *dev);
void ethosu_job_fini(struct ethosu_device *dev);
int ethosu_job_open(struct ethosu_file_priv *ethosu_priv);
void ethosu_job_close(struct ethosu_file_priv *ethosu_priv);
#endif

View File

@@ -707,6 +707,7 @@ static struct pci_device_id ivpu_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_LNL) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PTL_P) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_WCL) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_NVL) },
{ }
};
MODULE_DEVICE_TABLE(pci, ivpu_pci_ids);

View File

@@ -27,6 +27,7 @@
#define PCI_DEVICE_ID_LNL 0x643e
#define PCI_DEVICE_ID_PTL_P 0xb03e
#define PCI_DEVICE_ID_WCL 0xfd3e
#define PCI_DEVICE_ID_NVL 0xd71d
#define IVPU_HW_IP_37XX 37
#define IVPU_HW_IP_40XX 40
@@ -245,6 +246,8 @@ static inline int ivpu_hw_ip_gen(struct ivpu_device *vdev)
case PCI_DEVICE_ID_PTL_P:
case PCI_DEVICE_ID_WCL:
return IVPU_HW_IP_50XX;
case PCI_DEVICE_ID_NVL:
return IVPU_HW_IP_60XX;
default:
dump_stack();
ivpu_err(vdev, "Unknown NPU IP generation\n");
@@ -261,6 +264,7 @@ static inline int ivpu_hw_btrs_gen(struct ivpu_device *vdev)
case PCI_DEVICE_ID_LNL:
case PCI_DEVICE_ID_PTL_P:
case PCI_DEVICE_ID_WCL:
case PCI_DEVICE_ID_NVL:
return IVPU_HW_BTRS_LNL;
default:
dump_stack();

View File

@@ -56,12 +56,14 @@ static struct {
{ IVPU_HW_IP_40XX, "intel/vpu/vpu_40xx_v0.0.bin" },
{ IVPU_HW_IP_50XX, "intel/vpu/vpu_50xx_v1.bin" },
{ IVPU_HW_IP_50XX, "intel/vpu/vpu_50xx_v0.0.bin" },
{ IVPU_HW_IP_60XX, "intel/vpu/vpu_60xx_v1.bin" },
};
/* Production fw_names from the table above */
MODULE_FIRMWARE("intel/vpu/vpu_37xx_v1.bin");
MODULE_FIRMWARE("intel/vpu/vpu_40xx_v1.bin");
MODULE_FIRMWARE("intel/vpu/vpu_50xx_v1.bin");
MODULE_FIRMWARE("intel/vpu/vpu_60xx_v1.bin");
static int ivpu_fw_request(struct ivpu_device *vdev)
{

View File

@@ -691,6 +691,13 @@ static void pwr_island_delay_set(struct ivpu_device *vdev)
status = high ? 46 : 3;
break;
case PCI_DEVICE_ID_NVL:
post = high ? 198 : 17;
post1 = 0;
post2 = high ? 198 : 17;
status = 0;
break;
default:
dump_stack();
ivpu_err(vdev, "Unknown device ID\n");
@@ -889,6 +896,9 @@ static int soc_cpu_drive_40xx(struct ivpu_device *vdev, bool enable)
static int soc_cpu_enable(struct ivpu_device *vdev)
{
if (ivpu_hw_ip_gen(vdev) >= IVPU_HW_IP_60XX)
return 0;
return soc_cpu_drive_40xx(vdev, true);
}

View File

@@ -31,7 +31,7 @@
#define MANAGE_MAGIC_NUMBER ((__force __le32)0x43494151) /* "QAIC" in little endian */
#define QAIC_DBC_Q_GAP SZ_256
#define QAIC_DBC_Q_BUF_ALIGN SZ_4K
#define QAIC_MANAGE_EXT_MSG_LENGTH SZ_64K /* Max DMA message length */
#define QAIC_MANAGE_WIRE_MSG_LENGTH SZ_64K /* Max DMA message length */
#define QAIC_WRAPPER_MAX_SIZE SZ_4K
#define QAIC_MHI_RETRY_WAIT_MS 100
#define QAIC_MHI_RETRY_MAX 20
@@ -368,7 +368,7 @@ static int encode_passthrough(struct qaic_device *qdev, void *trans, struct wrap
if (in_trans->hdr.len % 8 != 0)
return -EINVAL;
if (size_add(msg_hdr_len, in_trans->hdr.len) > QAIC_MANAGE_EXT_MSG_LENGTH)
if (size_add(msg_hdr_len, in_trans->hdr.len) > QAIC_MANAGE_WIRE_MSG_LENGTH)
return -ENOSPC;
trans_wrapper = add_wrapper(wrappers,
@@ -496,7 +496,7 @@ static int encode_addr_size_pairs(struct dma_xfer *xfer, struct wrapper_list *wr
nents = sgt->nents;
nents_dma = nents;
*size = QAIC_MANAGE_EXT_MSG_LENGTH - msg_hdr_len - sizeof(**out_trans);
*size = QAIC_MANAGE_WIRE_MSG_LENGTH - msg_hdr_len - sizeof(**out_trans);
for_each_sgtable_dma_sg(sgt, sg, i) {
*size -= sizeof(*asp);
/* Save 1K for possible follow-up transactions. */
@@ -577,7 +577,7 @@ static int encode_dma(struct qaic_device *qdev, void *trans, struct wrapper_list
/* There should be enough space to hold at least one ASP entry. */
if (size_add(msg_hdr_len, sizeof(*out_trans) + sizeof(struct wire_addr_size_pair)) >
QAIC_MANAGE_EXT_MSG_LENGTH)
QAIC_MANAGE_WIRE_MSG_LENGTH)
return -ENOMEM;
xfer = kmalloc(sizeof(*xfer), GFP_KERNEL);
@@ -646,7 +646,7 @@ static int encode_activate(struct qaic_device *qdev, void *trans, struct wrapper
msg = &wrapper->msg;
msg_hdr_len = le32_to_cpu(msg->hdr.len);
if (size_add(msg_hdr_len, sizeof(*out_trans)) > QAIC_MANAGE_MAX_MSG_LENGTH)
if (size_add(msg_hdr_len, sizeof(*out_trans)) > QAIC_MANAGE_WIRE_MSG_LENGTH)
return -ENOSPC;
if (!in_trans->queue_size)
@@ -731,7 +731,7 @@ static int encode_status(struct qaic_device *qdev, void *trans, struct wrapper_l
msg = &wrapper->msg;
msg_hdr_len = le32_to_cpu(msg->hdr.len);
if (size_add(msg_hdr_len, in_trans->hdr.len) > QAIC_MANAGE_MAX_MSG_LENGTH)
if (size_add(msg_hdr_len, in_trans->hdr.len) > QAIC_MANAGE_WIRE_MSG_LENGTH)
return -ENOSPC;
trans_wrapper = add_wrapper(wrappers, sizeof(*trans_wrapper));
@@ -1054,7 +1054,7 @@ static void *msg_xfer(struct qaic_device *qdev, struct wrapper_list *wrappers, u
init_completion(&elem.xfer_done);
if (likely(!qdev->cntl_lost_buf)) {
/*
* The max size of request to device is QAIC_MANAGE_EXT_MSG_LENGTH.
* The max size of request to device is QAIC_MANAGE_WIRE_MSG_LENGTH.
* The max size of response from device is QAIC_MANAGE_MAX_MSG_LENGTH.
*/
out_buf = kmalloc(QAIC_MANAGE_MAX_MSG_LENGTH, GFP_KERNEL);

View File

@@ -1959,7 +1959,7 @@ int disable_dbc(struct qaic_device *qdev, u32 dbc_id, struct qaic_user *usr)
* enable_dbc - Enable the DBC. DBCs are disabled by removing the context of
* user. Add user context back to DBC to enable it. This function trusts the
* DBC ID passed and expects the DBC to be disabled.
* @qdev: Qranium device handle
* @qdev: qaic device handle
* @dbc_id: ID of the DBC
* @usr: User context
*/

View File

@@ -198,6 +198,7 @@ static const char * const aic200_image_table[] = {
[23] = "qcom/aic200/aop.mbn",
[32] = "qcom/aic200/tz.mbn",
[33] = "qcom/aic200/hypvm.mbn",
[38] = "qcom/aic200/xbl_config.elf",
[39] = "qcom/aic200/aic200_abl.elf",
[40] = "qcom/aic200/apdp.mbn",
[41] = "qcom/aic200/devcfg.mbn",
@@ -206,6 +207,7 @@ static const char * const aic200_image_table[] = {
[49] = "qcom/aic200/shrm.elf",
[50] = "qcom/aic200/cpucp.elf",
[51] = "qcom/aic200/aop_devcfg.mbn",
[54] = "qcom/aic200/qupv3fw.elf",
[57] = "qcom/aic200/cpucp_dtbs.elf",
[62] = "qcom/aic200/uefi_dtbs.elf",
[63] = "qcom/aic200/xbl_ac_config.mbn",
@@ -217,7 +219,8 @@ static const char * const aic200_image_table[] = {
[69] = "qcom/aic200/dcd.mbn",
[73] = "qcom/aic200/gearvm.mbn",
[74] = "qcom/aic200/sti.bin",
[75] = "qcom/aic200/pvs.bin",
[76] = "qcom/aic200/tz_qti_config.mbn",
[78] = "qcom/aic200/pvs.bin",
};
static bool is_streaming(struct sahara_context *context)

View File

@@ -28,8 +28,6 @@ static void armada_fbdev_fb_destroy(struct fb_info *info)
fbh->fb->funcs->destroy(fbh->fb);
drm_client_release(&fbh->client);
drm_fb_helper_unprepare(fbh);
kfree(fbh);
}
static const struct fb_ops armada_fb_ops = {

View File

@@ -13,16 +13,28 @@
* struct drm_client_funcs
*/
static void drm_fbdev_client_free(struct drm_client_dev *client)
{
struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
static void drm_fbdev_client_unregister(struct drm_client_dev *client)
{
struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
if (fb_helper->info) {
/*
* Fully probed framebuffer device
*/
drm_fb_helper_unregister_info(fb_helper);
} else {
/*
* Partially initialized client, no framebuffer device yet
*/
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
}
@@ -82,6 +94,7 @@ static int drm_fbdev_client_resume(struct drm_client_dev *client)
static const struct drm_client_funcs drm_fbdev_client_funcs = {
.owner = THIS_MODULE,
.free = drm_fbdev_client_free,
.unregister = drm_fbdev_client_unregister,
.restore = drm_fbdev_client_restore,
.hotplug = drm_fbdev_client_hotplug,

View File

@@ -293,19 +293,26 @@ static void drm_log_free_scanout(struct drm_client_dev *client)
}
}
static void drm_log_client_unregister(struct drm_client_dev *client)
static void drm_log_client_free(struct drm_client_dev *client)
{
struct drm_log *dlog = client_to_drm_log(client);
struct drm_device *dev = client->dev;
kfree(dlog);
drm_dbg(dev, "Unregistered with drm log\n");
}
static void drm_log_client_unregister(struct drm_client_dev *client)
{
struct drm_log *dlog = client_to_drm_log(client);
unregister_console(&dlog->con);
mutex_lock(&dlog->lock);
drm_log_free_scanout(client);
drm_client_release(client);
mutex_unlock(&dlog->lock);
kfree(dlog);
drm_dbg(dev, "Unregistered with drm log\n");
drm_client_release(client);
}
static int drm_log_client_hotplug(struct drm_client_dev *client)
@@ -339,6 +346,7 @@ static int drm_log_client_resume(struct drm_client_dev *client)
static const struct drm_client_funcs drm_log_client_funcs = {
.owner = THIS_MODULE,
.free = drm_log_client_free,
.unregister = drm_log_client_unregister,
.hotplug = drm_log_client_hotplug,
.suspend = drm_log_client_suspend,

View File

@@ -168,6 +168,10 @@ void drm_client_release(struct drm_client_dev *client)
drm_client_modeset_free(client);
drm_client_close(client);
if (client->funcs && client->funcs->free)
client->funcs->free(client);
drm_dev_put(dev);
}
EXPORT_SYMBOL(drm_client_release);

View File

@@ -39,12 +39,13 @@ void drm_client_dev_unregister(struct drm_device *dev)
mutex_lock(&dev->clientlist_mutex);
list_for_each_entry_safe(client, tmp, &dev->clientlist, list) {
list_del(&client->list);
if (client->funcs && client->funcs->unregister) {
/*
* Unregistering consumes and frees the client.
*/
if (client->funcs && client->funcs->unregister)
client->funcs->unregister(client);
} else {
else
drm_client_release(client);
kfree(client);
}
}
mutex_unlock(&dev->clientlist_mutex);
}

View File

@@ -57,8 +57,6 @@ static void drm_fbdev_dma_fb_destroy(struct fb_info *info)
drm_client_buffer_vunmap(fb_helper->buffer);
drm_client_framebuffer_delete(fb_helper->buffer);
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
static const struct fb_ops drm_fbdev_dma_fb_ops = {
@@ -92,8 +90,6 @@ static void drm_fbdev_dma_shadowed_fb_destroy(struct fb_info *info)
drm_client_buffer_vunmap(fb_helper->buffer);
drm_client_framebuffer_delete(fb_helper->buffer);
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
static const struct fb_ops drm_fbdev_dma_shadowed_fb_ops = {

View File

@@ -65,8 +65,6 @@ static void drm_fbdev_shmem_fb_destroy(struct fb_info *info)
drm_client_buffer_vunmap(fb_helper->buffer);
drm_client_framebuffer_delete(fb_helper->buffer);
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
static const struct fb_ops drm_fbdev_shmem_fb_ops = {

View File

@@ -53,8 +53,6 @@ static void drm_fbdev_ttm_fb_destroy(struct fb_info *info)
drm_client_framebuffer_delete(fb_helper->buffer);
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
static const struct fb_ops drm_fbdev_ttm_fb_ops = {

View File

@@ -334,8 +334,6 @@ void drm_gem_reset_shadow_plane(struct drm_plane *plane)
}
shadow_plane_state = kzalloc(sizeof(*shadow_plane_state), GFP_KERNEL);
if (!shadow_plane_state)
return;
__drm_gem_reset_shadow_plane(plane, shadow_plane_state);
}
EXPORT_SYMBOL(drm_gem_reset_shadow_plane);

View File

@@ -42,8 +42,6 @@ static void exynos_drm_fb_destroy(struct fb_info *info)
drm_framebuffer_remove(fb);
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
static const struct fb_ops exynos_drm_fb_ops = {

View File

@@ -84,9 +84,6 @@ static void psb_fbdev_fb_destroy(struct fb_info *info)
drm_gem_object_put(obj);
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
static const struct fb_ops psb_fbdev_fb_ops = {

View File

@@ -249,7 +249,7 @@ int gud_usb_set_u8(struct gud_device *gdrm, u8 request, u8 val)
return gud_usb_set(gdrm, request, 0, &val, sizeof(val));
}
static int gud_get_properties(struct gud_device *gdrm)
static int gud_plane_add_properties(struct gud_device *gdrm)
{
struct gud_property_req *properties;
unsigned int i, num_properties;
@@ -463,10 +463,6 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
return PTR_ERR(gdrm);
drm = &gdrm->drm;
drm->mode_config.funcs = &gud_mode_config_funcs;
ret = drmm_mode_config_init(drm);
if (ret)
return ret;
gdrm->flags = le32_to_cpu(desc.flags);
gdrm->compression = desc.compression & GUD_COMPRESSION_LZ4;
@@ -483,11 +479,28 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
if (ret)
return ret;
usb_set_intfdata(intf, gdrm);
dma_dev = usb_intf_get_dma_device(intf);
if (dma_dev) {
drm_dev_set_dma_dev(drm, dma_dev);
put_device(dma_dev);
} else {
dev_warn(dev, "buffer sharing not supported"); /* not an error */
}
/* Mode config init */
ret = drmm_mode_config_init(drm);
if (ret)
return ret;
drm->mode_config.min_width = le32_to_cpu(desc.min_width);
drm->mode_config.max_width = le32_to_cpu(desc.max_width);
drm->mode_config.min_height = le32_to_cpu(desc.min_height);
drm->mode_config.max_height = le32_to_cpu(desc.max_height);
drm->mode_config.funcs = &gud_mode_config_funcs;
/* Format init */
formats_dev = devm_kmalloc(dev, GUD_FORMATS_MAX_NUM, GFP_KERNEL);
/* Add room for emulated XRGB8888 */
formats = devm_kmalloc_array(dev, GUD_FORMATS_MAX_NUM + 1, sizeof(*formats), GFP_KERNEL);
@@ -587,6 +600,7 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
return -ENOMEM;
}
/* Pipeline init */
ret = drm_universal_plane_init(drm, &gdrm->plane, 0,
&gud_plane_funcs,
formats, num_formats,
@@ -598,12 +612,9 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
drm_plane_helper_add(&gdrm->plane, &gud_plane_helper_funcs);
drm_plane_enable_fb_damage_clips(&gdrm->plane);
devm_kfree(dev, formats);
devm_kfree(dev, formats_dev);
ret = gud_get_properties(gdrm);
ret = gud_plane_add_properties(gdrm);
if (ret) {
dev_err(dev, "Failed to get properties (error=%d)\n", ret);
dev_err(dev, "Failed to add properties (error=%d)\n", ret);
return ret;
}
@@ -621,16 +632,7 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
}
drm_mode_config_reset(drm);
usb_set_intfdata(intf, gdrm);
dma_dev = usb_intf_get_dma_device(intf);
if (dma_dev) {
drm_dev_set_dma_dev(drm, dma_dev);
put_device(dma_dev);
} else {
dev_warn(dev, "buffer sharing not supported"); /* not an error */
}
drm_kms_helper_poll_init(drm);
drm_debugfs_add_file(drm, "stats", gud_stats_debugfs, NULL);
@@ -638,7 +640,8 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
if (ret)
return ret;
drm_kms_helper_poll_init(drm);
devm_kfree(dev, formats);
devm_kfree(dev, formats_dev);
drm_client_setup(drm, NULL);

View File

@@ -146,8 +146,6 @@ static void intel_fbdev_fb_destroy(struct fb_info *info)
drm_framebuffer_remove(fb_helper->fb);
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
__diag_push();

View File

@@ -52,8 +52,6 @@ static void msm_fbdev_fb_destroy(struct fb_info *info)
drm_framebuffer_remove(fb);
drm_client_release(&helper->client);
drm_fb_helper_unprepare(helper);
kfree(helper);
}
static const struct fb_ops msm_fb_ops = {

View File

@@ -103,8 +103,6 @@ static void omap_fbdev_fb_destroy(struct fb_info *info)
drm_framebuffer_remove(fb);
drm_client_release(&helper->client);
drm_fb_helper_unprepare(helper);
kfree(helper);
}
/*

View File

@@ -184,8 +184,6 @@ static void radeon_fbdev_fb_destroy(struct fb_info *info)
radeon_fbdev_destroy_pinned_object(gobj);
drm_client_release(&fb_helper->client);
drm_fb_helper_unprepare(fb_helper);
kfree(fb_helper);
}
static const struct fb_ops radeon_fbdev_fb_ops = {

View File

@@ -322,7 +322,7 @@ static void st7571_prepare_buffer_grayscale(struct st7571_device *st7571,
size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 4;
memcpy(st7571->hwbuf, vmap->vaddr, size);
break;
};
}
}
static int st7571_fb_update_rect_monochrome(struct drm_framebuffer *fb, struct drm_rect *rect)

View File

@@ -258,7 +258,7 @@ int drm_sysfb_plane_helper_begin_fb_access(struct drm_plane *plane,
ret = -EINVAL;
crtc_state = drm_atomic_get_crtc_state(plane_state->state, plane_state->crtc);
crtc_state = drm_atomic_get_new_crtc_state(plane_state->state, plane_state->crtc);
if (drm_WARN_ON_ONCE(dev, !crtc_state))
goto err_drm_gem_end_shadow_fb_access;
sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state);

View File

@@ -53,8 +53,6 @@ static void tegra_fbdev_fb_destroy(struct fb_info *info)
drm_framebuffer_remove(fb);
drm_client_release(&helper->client);
drm_fb_helper_unprepare(helper);
kfree(helper);
}
static const struct fb_ops tegra_fb_ops = {

View File

@@ -74,7 +74,8 @@ pgprot_t ttm_prot_from_caching(enum ttm_caching caching, pgprot_t tmp)
#endif /* CONFIG_UML */
#endif /* __i386__ || __x86_64__ */
#if defined(__ia64__) || defined(__arm__) || defined(__aarch64__) || \
defined(__powerpc__) || defined(__mips__) || defined(__loongarch__)
defined(__powerpc__) || defined(__mips__) || defined(__loongarch__) || \
defined(__riscv)
if (caching == ttm_write_combined)
tmp = pgprot_writecombine(tmp);
else

View File

@@ -7,6 +7,7 @@ config DRM_VKMS
select DRM_KMS_HELPER
select DRM_GEM_SHMEM_HELPER
select CRC32
select CONFIGFS_FS
default n
help
Virtual Kernel Mode-Setting (VKMS) is used for testing or for

View File

@@ -8,7 +8,8 @@ vkms-y := \
vkms_composer.o \
vkms_writeback.o \
vkms_connector.o \
vkms_config.o
vkms_config.o \
vkms_configfs.o
obj-$(CONFIG_DRM_VKMS) += vkms.o
obj-$(CONFIG_DRM_VKMS_KUNIT_TEST) += tests/

View File

@@ -957,6 +957,29 @@ static void vkms_config_test_connector_get_possible_encoders(struct kunit *test)
vkms_config_destroy(config);
}
static void vkms_config_test_connector_status(struct kunit *test)
{
struct vkms_config *config;
struct vkms_config_connector *connector_cfg;
enum drm_connector_status status;
config = vkms_config_create("test");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config);
connector_cfg = vkms_config_create_connector(config);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector_cfg);
status = vkms_config_connector_get_status(connector_cfg);
KUNIT_EXPECT_EQ(test, status, connector_status_connected);
vkms_config_connector_set_status(connector_cfg,
connector_status_disconnected);
status = vkms_config_connector_get_status(connector_cfg);
KUNIT_EXPECT_EQ(test, status, connector_status_disconnected);
vkms_config_destroy(config);
}
static struct kunit_case vkms_config_test_cases[] = {
KUNIT_CASE(vkms_config_test_empty_config),
KUNIT_CASE_PARAM(vkms_config_test_default_config,
@@ -978,6 +1001,7 @@ static struct kunit_case vkms_config_test_cases[] = {
KUNIT_CASE(vkms_config_test_plane_get_possible_crtcs),
KUNIT_CASE(vkms_config_test_encoder_get_possible_crtcs),
KUNIT_CASE(vkms_config_test_connector_get_possible_encoders),
KUNIT_CASE(vkms_config_test_connector_status),
{}
};

View File

@@ -361,8 +361,11 @@ static int vkms_config_show(struct seq_file *m, void *data)
vkms_config_for_each_encoder(vkmsdev->config, encoder_cfg)
seq_puts(m, "encoder\n");
vkms_config_for_each_connector(vkmsdev->config, connector_cfg)
seq_puts(m, "connector\n");
vkms_config_for_each_connector(vkmsdev->config, connector_cfg) {
seq_puts(m, "connector:\n");
seq_printf(m, "\tstatus=%d\n",
vkms_config_connector_get_status(connector_cfg));
}
return 0;
}
@@ -588,6 +591,7 @@ struct vkms_config_connector *vkms_config_create_connector(struct vkms_config *c
return ERR_PTR(-ENOMEM);
connector_cfg->config = config;
connector_cfg->status = connector_status_connected;
xa_init_flags(&connector_cfg->possible_encoders, XA_FLAGS_ALLOC);
list_add_tail(&connector_cfg->link, &config->connectors);

View File

@@ -7,6 +7,8 @@
#include <linux/types.h>
#include <linux/xarray.h>
#include <drm/drm_connector.h>
#include "vkms_drv.h"
/**
@@ -99,6 +101,7 @@ struct vkms_config_encoder {
*
* @link: Link to the others connector in vkms_config
* @config: The vkms_config this connector belongs to
* @status: Status (connected, disconnected...) of the connector
* @possible_encoders: Array of encoders that can be used with this connector
* @connector: Internal usage. This pointer should never be considered as valid.
* It can be used to store a temporary reference to a VKMS connector
@@ -109,6 +112,7 @@ struct vkms_config_connector {
struct list_head link;
struct vkms_config *config;
enum drm_connector_status status;
struct xarray possible_encoders;
/* Internal usage */
@@ -434,4 +438,26 @@ int __must_check vkms_config_connector_attach_encoder(struct vkms_config_connect
void vkms_config_connector_detach_encoder(struct vkms_config_connector *connector_cfg,
struct vkms_config_encoder *encoder_cfg);
/**
* vkms_config_connector_get_status() - Return the status of the connector
* @connector_cfg: Connector to get the status from
*/
static inline enum drm_connector_status
vkms_config_connector_get_status(struct vkms_config_connector *connector_cfg)
{
return connector_cfg->status;
}
/**
* vkms_config_connector_set_status() - Set the status of the connector
* @connector_cfg: Connector to set the status to
* @status: New connector status
*/
static inline void
vkms_config_connector_set_status(struct vkms_config_connector *connector_cfg,
enum drm_connector_status status)
{
connector_cfg->status = status;
}
#endif /* _VKMS_CONFIG_H_ */

View File

@@ -0,0 +1,833 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/cleanup.h>
#include <linux/configfs.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include "vkms_drv.h"
#include "vkms_config.h"
#include "vkms_configfs.h"
#include "vkms_connector.h"
/* To avoid registering configfs more than once or unregistering on error */
static bool is_configfs_registered;
/**
* struct vkms_configfs_device - Configfs representation of a VKMS device
*
* @group: Top level configuration group that represents a VKMS device.
* Initialized when a new directory is created under "/config/vkms/"
* @planes_group: Default subgroup of @group at "/config/vkms/planes"
* @crtcs_group: Default subgroup of @group at "/config/vkms/crtcs"
* @encoders_group: Default subgroup of @group at "/config/vkms/encoders"
* @connectors_group: Default subgroup of @group at "/config/vkms/connectors"
* @lock: Lock used to project concurrent access to the configuration attributes
* @config: Protected by @lock. Configuration of the VKMS device
* @enabled: Protected by @lock. The device is created or destroyed when this
* option changes
*/
struct vkms_configfs_device {
struct config_group group;
struct config_group planes_group;
struct config_group crtcs_group;
struct config_group encoders_group;
struct config_group connectors_group;
struct mutex lock;
struct vkms_config *config;
bool enabled;
};
/**
* struct vkms_configfs_plane - Configfs representation of a plane
*
* @group: Top level configuration group that represents a plane.
* Initialized when a new directory is created under "/config/vkms/planes"
* @possible_crtcs_group: Default subgroup of @group at "plane/possible_crtcs"
* @dev: The vkms_configfs_device this plane belongs to
* @config: Configuration of the VKMS plane
*/
struct vkms_configfs_plane {
struct config_group group;
struct config_group possible_crtcs_group;
struct vkms_configfs_device *dev;
struct vkms_config_plane *config;
};
/**
* struct vkms_configfs_crtc - Configfs representation of a CRTC
*
* @group: Top level configuration group that represents a CRTC.
* Initialized when a new directory is created under "/config/vkms/crtcs"
* @dev: The vkms_configfs_device this CRTC belongs to
* @config: Configuration of the VKMS CRTC
*/
struct vkms_configfs_crtc {
struct config_group group;
struct vkms_configfs_device *dev;
struct vkms_config_crtc *config;
};
/**
* struct vkms_configfs_encoder - Configfs representation of a encoder
*
* @group: Top level configuration group that represents a encoder.
* Initialized when a new directory is created under "/config/vkms/encoders"
* @possible_crtcs_group: Default subgroup of @group at "encoder/possible_crtcs"
* @dev: The vkms_configfs_device this encoder belongs to
* @config: Configuration of the VKMS encoder
*/
struct vkms_configfs_encoder {
struct config_group group;
struct config_group possible_crtcs_group;
struct vkms_configfs_device *dev;
struct vkms_config_encoder *config;
};
/**
* struct vkms_configfs_connector - Configfs representation of a connector
*
* @group: Top level configuration group that represents a connector.
* Initialized when a new directory is created under "/config/vkms/connectors"
* @possible_encoders_group: Default subgroup of @group at
* "connector/possible_encoders"
* @dev: The vkms_configfs_device this connector belongs to
* @config: Configuration of the VKMS connector
*/
struct vkms_configfs_connector {
struct config_group group;
struct config_group possible_encoders_group;
struct vkms_configfs_device *dev;
struct vkms_config_connector *config;
};
#define device_item_to_vkms_configfs_device(item) \
container_of(to_config_group((item)), struct vkms_configfs_device, \
group)
#define child_group_to_vkms_configfs_device(group) \
device_item_to_vkms_configfs_device((&(group)->cg_item)->ci_parent)
#define plane_item_to_vkms_configfs_plane(item) \
container_of(to_config_group((item)), struct vkms_configfs_plane, group)
#define plane_possible_crtcs_item_to_vkms_configfs_plane(item) \
container_of(to_config_group((item)), struct vkms_configfs_plane, \
possible_crtcs_group)
#define crtc_item_to_vkms_configfs_crtc(item) \
container_of(to_config_group((item)), struct vkms_configfs_crtc, group)
#define encoder_item_to_vkms_configfs_encoder(item) \
container_of(to_config_group((item)), struct vkms_configfs_encoder, \
group)
#define encoder_possible_crtcs_item_to_vkms_configfs_encoder(item) \
container_of(to_config_group((item)), struct vkms_configfs_encoder, \
possible_crtcs_group)
#define connector_item_to_vkms_configfs_connector(item) \
container_of(to_config_group((item)), struct vkms_configfs_connector, \
group)
#define connector_possible_encoders_item_to_vkms_configfs_connector(item) \
container_of(to_config_group((item)), struct vkms_configfs_connector, \
possible_encoders_group)
static ssize_t crtc_writeback_show(struct config_item *item, char *page)
{
struct vkms_configfs_crtc *crtc;
bool writeback;
crtc = crtc_item_to_vkms_configfs_crtc(item);
scoped_guard(mutex, &crtc->dev->lock)
writeback = vkms_config_crtc_get_writeback(crtc->config);
return sprintf(page, "%d\n", writeback);
}
static ssize_t crtc_writeback_store(struct config_item *item, const char *page,
size_t count)
{
struct vkms_configfs_crtc *crtc;
bool writeback;
crtc = crtc_item_to_vkms_configfs_crtc(item);
if (kstrtobool(page, &writeback))
return -EINVAL;
scoped_guard(mutex, &crtc->dev->lock) {
if (crtc->dev->enabled)
return -EBUSY;
vkms_config_crtc_set_writeback(crtc->config, writeback);
}
return (ssize_t)count;
}
CONFIGFS_ATTR(crtc_, writeback);
static struct configfs_attribute *crtc_item_attrs[] = {
&crtc_attr_writeback,
NULL,
};
static void crtc_release(struct config_item *item)
{
struct vkms_configfs_crtc *crtc;
struct mutex *lock;
crtc = crtc_item_to_vkms_configfs_crtc(item);
lock = &crtc->dev->lock;
scoped_guard(mutex, lock) {
vkms_config_destroy_crtc(crtc->dev->config, crtc->config);
kfree(crtc);
}
}
static struct configfs_item_operations crtc_item_operations = {
.release = &crtc_release,
};
static const struct config_item_type crtc_item_type = {
.ct_attrs = crtc_item_attrs,
.ct_item_ops = &crtc_item_operations,
.ct_owner = THIS_MODULE,
};
static struct config_group *make_crtc_group(struct config_group *group,
const char *name)
{
struct vkms_configfs_device *dev;
struct vkms_configfs_crtc *crtc;
dev = child_group_to_vkms_configfs_device(group);
scoped_guard(mutex, &dev->lock) {
if (dev->enabled)
return ERR_PTR(-EBUSY);
crtc = kzalloc(sizeof(*crtc), GFP_KERNEL);
if (!crtc)
return ERR_PTR(-ENOMEM);
crtc->dev = dev;
crtc->config = vkms_config_create_crtc(dev->config);
if (IS_ERR(crtc->config)) {
kfree(crtc);
return ERR_CAST(crtc->config);
}
config_group_init_type_name(&crtc->group, name, &crtc_item_type);
}
return &crtc->group;
}
static struct configfs_group_operations crtcs_group_operations = {
.make_group = &make_crtc_group,
};
static const struct config_item_type crtc_group_type = {
.ct_group_ops = &crtcs_group_operations,
.ct_owner = THIS_MODULE,
};
static int plane_possible_crtcs_allow_link(struct config_item *src,
struct config_item *target)
{
struct vkms_configfs_plane *plane;
struct vkms_configfs_crtc *crtc;
int ret;
if (target->ci_type != &crtc_item_type)
return -EINVAL;
plane = plane_possible_crtcs_item_to_vkms_configfs_plane(src);
crtc = crtc_item_to_vkms_configfs_crtc(target);
scoped_guard(mutex, &plane->dev->lock) {
if (plane->dev->enabled)
return -EBUSY;
ret = vkms_config_plane_attach_crtc(plane->config, crtc->config);
}
return ret;
}
static void plane_possible_crtcs_drop_link(struct config_item *src,
struct config_item *target)
{
struct vkms_configfs_plane *plane;
struct vkms_configfs_crtc *crtc;
plane = plane_possible_crtcs_item_to_vkms_configfs_plane(src);
crtc = crtc_item_to_vkms_configfs_crtc(target);
scoped_guard(mutex, &plane->dev->lock)
vkms_config_plane_detach_crtc(plane->config, crtc->config);
}
static struct configfs_item_operations plane_possible_crtcs_item_operations = {
.allow_link = plane_possible_crtcs_allow_link,
.drop_link = plane_possible_crtcs_drop_link,
};
static const struct config_item_type plane_possible_crtcs_group_type = {
.ct_item_ops = &plane_possible_crtcs_item_operations,
.ct_owner = THIS_MODULE,
};
static ssize_t plane_type_show(struct config_item *item, char *page)
{
struct vkms_configfs_plane *plane;
enum drm_plane_type type;
plane = plane_item_to_vkms_configfs_plane(item);
scoped_guard(mutex, &plane->dev->lock)
type = vkms_config_plane_get_type(plane->config);
return sprintf(page, "%u", type);
}
static ssize_t plane_type_store(struct config_item *item, const char *page,
size_t count)
{
struct vkms_configfs_plane *plane;
enum drm_plane_type type;
plane = plane_item_to_vkms_configfs_plane(item);
if (kstrtouint(page, 10, &type))
return -EINVAL;
if (type != DRM_PLANE_TYPE_OVERLAY && type != DRM_PLANE_TYPE_PRIMARY &&
type != DRM_PLANE_TYPE_CURSOR)
return -EINVAL;
scoped_guard(mutex, &plane->dev->lock) {
if (plane->dev->enabled)
return -EBUSY;
vkms_config_plane_set_type(plane->config, type);
}
return (ssize_t)count;
}
CONFIGFS_ATTR(plane_, type);
static struct configfs_attribute *plane_item_attrs[] = {
&plane_attr_type,
NULL,
};
static void plane_release(struct config_item *item)
{
struct vkms_configfs_plane *plane;
struct mutex *lock;
plane = plane_item_to_vkms_configfs_plane(item);
lock = &plane->dev->lock;
scoped_guard(mutex, lock) {
vkms_config_destroy_plane(plane->config);
kfree(plane);
}
}
static struct configfs_item_operations plane_item_operations = {
.release = &plane_release,
};
static const struct config_item_type plane_item_type = {
.ct_attrs = plane_item_attrs,
.ct_item_ops = &plane_item_operations,
.ct_owner = THIS_MODULE,
};
static struct config_group *make_plane_group(struct config_group *group,
const char *name)
{
struct vkms_configfs_device *dev;
struct vkms_configfs_plane *plane;
dev = child_group_to_vkms_configfs_device(group);
scoped_guard(mutex, &dev->lock) {
if (dev->enabled)
return ERR_PTR(-EBUSY);
plane = kzalloc(sizeof(*plane), GFP_KERNEL);
if (!plane)
return ERR_PTR(-ENOMEM);
plane->dev = dev;
plane->config = vkms_config_create_plane(dev->config);
if (IS_ERR(plane->config)) {
kfree(plane);
return ERR_CAST(plane->config);
}
config_group_init_type_name(&plane->group, name, &plane_item_type);
config_group_init_type_name(&plane->possible_crtcs_group,
"possible_crtcs",
&plane_possible_crtcs_group_type);
configfs_add_default_group(&plane->possible_crtcs_group,
&plane->group);
}
return &plane->group;
}
static struct configfs_group_operations planes_group_operations = {
.make_group = &make_plane_group,
};
static const struct config_item_type plane_group_type = {
.ct_group_ops = &planes_group_operations,
.ct_owner = THIS_MODULE,
};
static int encoder_possible_crtcs_allow_link(struct config_item *src,
struct config_item *target)
{
struct vkms_configfs_encoder *encoder;
struct vkms_configfs_crtc *crtc;
int ret;
if (target->ci_type != &crtc_item_type)
return -EINVAL;
encoder = encoder_possible_crtcs_item_to_vkms_configfs_encoder(src);
crtc = crtc_item_to_vkms_configfs_crtc(target);
scoped_guard(mutex, &encoder->dev->lock) {
if (encoder->dev->enabled)
return -EBUSY;
ret = vkms_config_encoder_attach_crtc(encoder->config, crtc->config);
}
return ret;
}
static void encoder_possible_crtcs_drop_link(struct config_item *src,
struct config_item *target)
{
struct vkms_configfs_encoder *encoder;
struct vkms_configfs_crtc *crtc;
encoder = encoder_possible_crtcs_item_to_vkms_configfs_encoder(src);
crtc = crtc_item_to_vkms_configfs_crtc(target);
scoped_guard(mutex, &encoder->dev->lock)
vkms_config_encoder_detach_crtc(encoder->config, crtc->config);
}
static struct configfs_item_operations encoder_possible_crtcs_item_operations = {
.allow_link = encoder_possible_crtcs_allow_link,
.drop_link = encoder_possible_crtcs_drop_link,
};
static const struct config_item_type encoder_possible_crtcs_group_type = {
.ct_item_ops = &encoder_possible_crtcs_item_operations,
.ct_owner = THIS_MODULE,
};
static void encoder_release(struct config_item *item)
{
struct vkms_configfs_encoder *encoder;
struct mutex *lock;
encoder = encoder_item_to_vkms_configfs_encoder(item);
lock = &encoder->dev->lock;
scoped_guard(mutex, lock) {
vkms_config_destroy_encoder(encoder->dev->config, encoder->config);
kfree(encoder);
}
}
static struct configfs_item_operations encoder_item_operations = {
.release = &encoder_release,
};
static const struct config_item_type encoder_item_type = {
.ct_item_ops = &encoder_item_operations,
.ct_owner = THIS_MODULE,
};
static struct config_group *make_encoder_group(struct config_group *group,
const char *name)
{
struct vkms_configfs_device *dev;
struct vkms_configfs_encoder *encoder;
dev = child_group_to_vkms_configfs_device(group);
scoped_guard(mutex, &dev->lock) {
if (dev->enabled)
return ERR_PTR(-EBUSY);
encoder = kzalloc(sizeof(*encoder), GFP_KERNEL);
if (!encoder)
return ERR_PTR(-ENOMEM);
encoder->dev = dev;
encoder->config = vkms_config_create_encoder(dev->config);
if (IS_ERR(encoder->config)) {
kfree(encoder);
return ERR_CAST(encoder->config);
}
config_group_init_type_name(&encoder->group, name,
&encoder_item_type);
config_group_init_type_name(&encoder->possible_crtcs_group,
"possible_crtcs",
&encoder_possible_crtcs_group_type);
configfs_add_default_group(&encoder->possible_crtcs_group,
&encoder->group);
}
return &encoder->group;
}
static struct configfs_group_operations encoders_group_operations = {
.make_group = &make_encoder_group,
};
static const struct config_item_type encoder_group_type = {
.ct_group_ops = &encoders_group_operations,
.ct_owner = THIS_MODULE,
};
static ssize_t connector_status_show(struct config_item *item, char *page)
{
struct vkms_configfs_connector *connector;
enum drm_connector_status status;
connector = connector_item_to_vkms_configfs_connector(item);
scoped_guard(mutex, &connector->dev->lock)
status = vkms_config_connector_get_status(connector->config);
return sprintf(page, "%u", status);
}
static ssize_t connector_status_store(struct config_item *item,
const char *page, size_t count)
{
struct vkms_configfs_connector *connector;
enum drm_connector_status status;
connector = connector_item_to_vkms_configfs_connector(item);
if (kstrtouint(page, 10, &status))
return -EINVAL;
if (status != connector_status_connected &&
status != connector_status_disconnected &&
status != connector_status_unknown)
return -EINVAL;
scoped_guard(mutex, &connector->dev->lock) {
vkms_config_connector_set_status(connector->config, status);
if (connector->dev->enabled)
vkms_trigger_connector_hotplug(connector->dev->config->dev);
}
return (ssize_t)count;
}
CONFIGFS_ATTR(connector_, status);
static struct configfs_attribute *connector_item_attrs[] = {
&connector_attr_status,
NULL,
};
static void connector_release(struct config_item *item)
{
struct vkms_configfs_connector *connector;
struct mutex *lock;
connector = connector_item_to_vkms_configfs_connector(item);
lock = &connector->dev->lock;
scoped_guard(mutex, lock) {
vkms_config_destroy_connector(connector->config);
kfree(connector);
}
}
static struct configfs_item_operations connector_item_operations = {
.release = &connector_release,
};
static const struct config_item_type connector_item_type = {
.ct_attrs = connector_item_attrs,
.ct_item_ops = &connector_item_operations,
.ct_owner = THIS_MODULE,
};
static int connector_possible_encoders_allow_link(struct config_item *src,
struct config_item *target)
{
struct vkms_configfs_connector *connector;
struct vkms_configfs_encoder *encoder;
int ret;
if (target->ci_type != &encoder_item_type)
return -EINVAL;
connector = connector_possible_encoders_item_to_vkms_configfs_connector(src);
encoder = encoder_item_to_vkms_configfs_encoder(target);
scoped_guard(mutex, &connector->dev->lock) {
if (connector->dev->enabled)
return -EBUSY;
ret = vkms_config_connector_attach_encoder(connector->config,
encoder->config);
}
return ret;
}
static void connector_possible_encoders_drop_link(struct config_item *src,
struct config_item *target)
{
struct vkms_configfs_connector *connector;
struct vkms_configfs_encoder *encoder;
connector = connector_possible_encoders_item_to_vkms_configfs_connector(src);
encoder = encoder_item_to_vkms_configfs_encoder(target);
scoped_guard(mutex, &connector->dev->lock) {
vkms_config_connector_detach_encoder(connector->config,
encoder->config);
}
}
static struct configfs_item_operations connector_possible_encoders_item_operations = {
.allow_link = connector_possible_encoders_allow_link,
.drop_link = connector_possible_encoders_drop_link,
};
static const struct config_item_type connector_possible_encoders_group_type = {
.ct_item_ops = &connector_possible_encoders_item_operations,
.ct_owner = THIS_MODULE,
};
static struct config_group *make_connector_group(struct config_group *group,
const char *name)
{
struct vkms_configfs_device *dev;
struct vkms_configfs_connector *connector;
dev = child_group_to_vkms_configfs_device(group);
scoped_guard(mutex, &dev->lock) {
if (dev->enabled)
return ERR_PTR(-EBUSY);
connector = kzalloc(sizeof(*connector), GFP_KERNEL);
if (!connector)
return ERR_PTR(-ENOMEM);
connector->dev = dev;
connector->config = vkms_config_create_connector(dev->config);
if (IS_ERR(connector->config)) {
kfree(connector);
return ERR_CAST(connector->config);
}
config_group_init_type_name(&connector->group, name,
&connector_item_type);
config_group_init_type_name(&connector->possible_encoders_group,
"possible_encoders",
&connector_possible_encoders_group_type);
configfs_add_default_group(&connector->possible_encoders_group,
&connector->group);
}
return &connector->group;
}
static struct configfs_group_operations connectors_group_operations = {
.make_group = &make_connector_group,
};
static const struct config_item_type connector_group_type = {
.ct_group_ops = &connectors_group_operations,
.ct_owner = THIS_MODULE,
};
static ssize_t device_enabled_show(struct config_item *item, char *page)
{
struct vkms_configfs_device *dev;
bool enabled;
dev = device_item_to_vkms_configfs_device(item);
scoped_guard(mutex, &dev->lock)
enabled = dev->enabled;
return sprintf(page, "%d\n", enabled);
}
static ssize_t device_enabled_store(struct config_item *item, const char *page,
size_t count)
{
struct vkms_configfs_device *dev;
bool enabled;
int ret = 0;
dev = device_item_to_vkms_configfs_device(item);
if (kstrtobool(page, &enabled))
return -EINVAL;
scoped_guard(mutex, &dev->lock) {
if (!dev->enabled && enabled) {
if (!vkms_config_is_valid(dev->config))
return -EINVAL;
ret = vkms_create(dev->config);
if (ret)
return ret;
} else if (dev->enabled && !enabled) {
vkms_destroy(dev->config);
}
dev->enabled = enabled;
}
return (ssize_t)count;
}
CONFIGFS_ATTR(device_, enabled);
static struct configfs_attribute *device_item_attrs[] = {
&device_attr_enabled,
NULL,
};
static void device_release(struct config_item *item)
{
struct vkms_configfs_device *dev;
dev = device_item_to_vkms_configfs_device(item);
if (dev->enabled)
vkms_destroy(dev->config);
mutex_destroy(&dev->lock);
vkms_config_destroy(dev->config);
kfree(dev);
}
static struct configfs_item_operations device_item_operations = {
.release = &device_release,
};
static const struct config_item_type device_item_type = {
.ct_attrs = device_item_attrs,
.ct_item_ops = &device_item_operations,
.ct_owner = THIS_MODULE,
};
static struct config_group *make_device_group(struct config_group *group,
const char *name)
{
struct vkms_configfs_device *dev;
if (strcmp(name, DEFAULT_DEVICE_NAME) == 0)
return ERR_PTR(-EINVAL);
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return ERR_PTR(-ENOMEM);
dev->config = vkms_config_create(name);
if (IS_ERR(dev->config)) {
kfree(dev);
return ERR_CAST(dev->config);
}
config_group_init_type_name(&dev->group, name, &device_item_type);
mutex_init(&dev->lock);
config_group_init_type_name(&dev->planes_group, "planes",
&plane_group_type);
configfs_add_default_group(&dev->planes_group, &dev->group);
config_group_init_type_name(&dev->crtcs_group, "crtcs",
&crtc_group_type);
configfs_add_default_group(&dev->crtcs_group, &dev->group);
config_group_init_type_name(&dev->encoders_group, "encoders",
&encoder_group_type);
configfs_add_default_group(&dev->encoders_group, &dev->group);
config_group_init_type_name(&dev->connectors_group, "connectors",
&connector_group_type);
configfs_add_default_group(&dev->connectors_group, &dev->group);
return &dev->group;
}
static struct configfs_group_operations device_group_ops = {
.make_group = &make_device_group,
};
static const struct config_item_type device_group_type = {
.ct_group_ops = &device_group_ops,
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem vkms_subsys = {
.su_group = {
.cg_item = {
.ci_name = "vkms",
.ci_type = &device_group_type,
},
},
.su_mutex = __MUTEX_INITIALIZER(vkms_subsys.su_mutex),
};
int vkms_configfs_register(void)
{
int ret;
if (is_configfs_registered)
return 0;
config_group_init(&vkms_subsys.su_group);
ret = configfs_register_subsystem(&vkms_subsys);
is_configfs_registered = ret == 0;
return ret;
}
void vkms_configfs_unregister(void)
{
if (is_configfs_registered)
configfs_unregister_subsystem(&vkms_subsys);
}

View File

@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef _VKMS_CONFIGFS_H_
#define _VKMS_CONFIGFS_H_
int vkms_configfs_register(void);
void vkms_configfs_unregister(void);
#endif /* _VKMS_CONFIGFS_H_ */

View File

@@ -5,9 +5,37 @@
#include <drm/drm_managed.h>
#include <drm/drm_probe_helper.h>
#include "vkms_config.h"
#include "vkms_connector.h"
static enum drm_connector_status vkms_connector_detect(struct drm_connector *connector,
bool force)
{
struct drm_device *dev = connector->dev;
struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
struct vkms_connector *vkms_connector;
enum drm_connector_status status;
struct vkms_config_connector *connector_cfg;
vkms_connector = drm_connector_to_vkms_connector(connector);
/*
* The connector configuration might not exist if its configfs directory
* was deleted. Therefore, use the configuration if present or keep the
* current status if we can not access it anymore.
*/
status = connector->status;
vkms_config_for_each_connector(vkmsdev->config, connector_cfg) {
if (connector_cfg->connector == vkms_connector)
status = vkms_config_connector_get_status(connector_cfg);
}
return status;
}
static const struct drm_connector_funcs vkms_connector_funcs = {
.detect = vkms_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
@@ -59,3 +87,10 @@ struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev)
return connector;
}
void vkms_trigger_connector_hotplug(struct vkms_device *vkmsdev)
{
struct drm_device *dev = &vkmsdev->drm;
drm_kms_helper_hotplug_event(dev);
}

View File

@@ -5,6 +5,9 @@
#include "vkms_drv.h"
#define drm_connector_to_vkms_connector(target) \
container_of(target, struct vkms_connector, base)
/**
* struct vkms_connector - VKMS custom type wrapping around the DRM connector
*
@@ -23,4 +26,10 @@ struct vkms_connector {
*/
struct vkms_connector *vkms_connector_init(struct vkms_device *vkmsdev);
/**
* vkms_trigger_connector_hotplug() - Update the device's connectors status
* @vkmsdev: VKMS device to update
*/
void vkms_trigger_connector_hotplug(struct vkms_device *vkmsdev);
#endif /* _VKMS_CONNECTOR_H_ */

View File

@@ -28,6 +28,7 @@
#include <drm/drm_vblank.h>
#include "vkms_config.h"
#include "vkms_configfs.h"
#include "vkms_drv.h"
#define DRIVER_NAME "vkms"
@@ -49,6 +50,10 @@ static bool enable_overlay;
module_param_named(enable_overlay, enable_overlay, bool, 0444);
MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
static bool create_default_dev = true;
module_param_named(create_default_dev, create_default_dev, bool, 0444);
MODULE_PARM_DESC(create_default_dev, "Create or not the default VKMS device");
DEFINE_DRM_GEM_FOPS(vkms_driver_fops);
static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
@@ -146,7 +151,7 @@ static int vkms_modeset_init(struct vkms_device *vkmsdev)
return vkms_output_init(vkmsdev);
}
static int vkms_create(struct vkms_config *config)
int vkms_create(struct vkms_config *config)
{
int ret;
struct faux_device *fdev;
@@ -214,6 +219,13 @@ static int __init vkms_init(void)
int ret;
struct vkms_config *config;
ret = vkms_configfs_register();
if (ret)
return ret;
if (!create_default_dev)
return 0;
config = vkms_config_default_create(enable_cursor, enable_writeback, enable_overlay);
if (IS_ERR(config))
return PTR_ERR(config);
@@ -229,7 +241,7 @@ static int __init vkms_init(void)
return 0;
}
static void vkms_destroy(struct vkms_config *config)
void vkms_destroy(struct vkms_config *config)
{
struct faux_device *fdev;
@@ -250,6 +262,8 @@ static void vkms_destroy(struct vkms_config *config)
static void __exit vkms_exit(void)
{
vkms_configfs_unregister();
if (!default_config)
return;

View File

@@ -256,6 +256,26 @@ struct vkms_device {
#define to_vkms_plane_state(target)\
container_of(target, struct vkms_plane_state, base.base)
/**
* vkms_create() - Create a device from a configuration
* @config: Config used to configure the new device
*
* A pointer to the created vkms_device is stored in @config
*
* Returns:
* 0 on success or an error.
*/
int vkms_create(struct vkms_config *config);
/**
* vkms_destroy() - Destroy a device
* @config: Config from which the device was created
*
* The device is completely removed, but the @config is not freed. It can be
* reused or destroyed with vkms_config_destroy().
*/
void vkms_destroy(struct vkms_config *config);
/**
* vkms_crtc_init() - Initialize a CRTC for VKMS
* @dev: DRM device associated with the VKMS buffer

View File

@@ -28,6 +28,16 @@ struct drm_client_funcs {
*/
struct module *owner;
/**
* @free:
*
* Called when the client gets unregistered. Implementations should
* release all client-specific data and free the memory.
*
* This callback is optional.
*/
void (*free)(struct drm_client_dev *client);
/**
* @unregister:
*

View File

@@ -0,0 +1,261 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (C) 2025 Arm, Ltd. */
#ifndef _ETHOSU_DRM_H_
#define _ETHOSU_DRM_H_
#include "drm.h"
#if defined(__cplusplus)
extern "C" {
#endif
/**
* DOC: IOCTL IDs
*
* enum drm_ethosu_ioctl_id - IOCTL IDs
*
* Place new ioctls at the end, don't re-order, don't replace or remove entries.
*
* These IDs are not meant to be used directly. Use the DRM_IOCTL_ETHOSU_xxx
* definitions instead.
*/
enum drm_ethosu_ioctl_id {
/** @DRM_ETHOSU_DEV_QUERY: Query device information. */
DRM_ETHOSU_DEV_QUERY = 0,
/** @DRM_ETHOSU_BO_CREATE: Create a buffer object. */
DRM_ETHOSU_BO_CREATE,
/** @DRM_ETHOSU_BO_WAIT: Wait on a buffer object's fence. */
DRM_ETHOSU_BO_WAIT,
/**
* @DRM_ETHOSU_BO_MMAP_OFFSET: Get the file offset to pass to
* mmap to map a GEM object.
*/
DRM_ETHOSU_BO_MMAP_OFFSET,
/**
* @DRM_ETHOSU_CMDSTREAM_BO_CREATE: Create a command stream buffer
* object.
*/
DRM_ETHOSU_CMDSTREAM_BO_CREATE,
/** @DRM_ETHOSU_SUBMIT: Submit a job and BOs to run. */
DRM_ETHOSU_SUBMIT,
};
/**
* DOC: IOCTL arguments
*/
/**
* enum drm_ethosu_dev_query_type - Query type
*
* Place new types at the end, don't re-order, don't remove or replace.
*/
enum drm_ethosu_dev_query_type {
/** @DRM_ETHOSU_DEV_QUERY_NPU_INFO: Query NPU information. */
DRM_ETHOSU_DEV_QUERY_NPU_INFO = 0,
};
/**
* struct drm_ethosu_gpu_info - NPU information
*
* Structure grouping all queryable information relating to the NPU.
*/
struct drm_ethosu_npu_info {
/** @id : NPU ID. */
__u32 id;
#define DRM_ETHOSU_ARCH_MAJOR(x) ((x) >> 28)
#define DRM_ETHOSU_ARCH_MINOR(x) (((x) >> 20) & 0xff)
#define DRM_ETHOSU_ARCH_PATCH(x) (((x) >> 16) & 0xf)
#define DRM_ETHOSU_PRODUCT_MAJOR(x) (((x) >> 12) & 0xf)
#define DRM_ETHOSU_VERSION_MAJOR(x) (((x) >> 8) & 0xf)
#define DRM_ETHOSU_VERSION_MINOR(x) (((x) >> 4) & 0xff)
#define DRM_ETHOSU_VERSION_STATUS(x) ((x) & 0xf)
/** @gpu_rev: GPU revision. */
__u32 config;
__u32 sram_size;
};
/**
* struct drm_ethosu_dev_query - Arguments passed to DRM_ETHOSU_IOCTL_DEV_QUERY
*/
struct drm_ethosu_dev_query {
/** @type: the query type (see drm_ethosu_dev_query_type). */
__u32 type;
/**
* @size: size of the type being queried.
*
* If pointer is NULL, size is updated by the driver to provide the
* output structure size. If pointer is not NULL, the driver will
* only copy min(size, actual_structure_size) bytes to the pointer,
* and update the size accordingly. This allows us to extend query
* types without breaking userspace.
*/
__u32 size;
/**
* @pointer: user pointer to a query type struct.
*
* Pointer can be NULL, in which case, nothing is copied, but the
* actual structure size is returned. If not NULL, it must point to
* a location that's large enough to hold size bytes.
*/
__u64 pointer;
};
/**
* enum drm_ethosu_bo_flags - Buffer object flags, passed at creation time.
*/
enum drm_ethosu_bo_flags {
/**
* @DRM_ETHOSU_BO_NO_MMAP: The buffer object will never be CPU-mapped
* in userspace.
*/
DRM_ETHOSU_BO_NO_MMAP = (1 << 0),
};
/**
* struct drm_ethosu_bo_create - Arguments passed to DRM_IOCTL_ETHOSU_BO_CREATE.
*/
struct drm_ethosu_bo_create {
/**
* @size: Requested size for the object
*
* The (page-aligned) allocated size for the object will be returned.
*/
__u64 size;
/**
* @flags: Flags. Must be a combination of drm_ethosu_bo_flags flags.
*/
__u32 flags;
/**
* @handle: Returned handle for the object.
*
* Object handles are nonzero.
*/
__u32 handle;
};
/**
* struct drm_ethosu_bo_mmap_offset - Arguments passed to DRM_IOCTL_ETHOSU_BO_MMAP_OFFSET.
*/
struct drm_ethosu_bo_mmap_offset {
/** @handle: Handle of the object we want an mmap offset for. */
__u32 handle;
/** @pad: MBZ. */
__u32 pad;
/** @offset: The fake offset to use for subsequent mmap calls. */
__u64 offset;
};
/**
* struct drm_ethosu_wait_bo - ioctl argument for waiting for
* completion of the last DRM_ETHOSU_SUBMIT on a BO.
*
* This is useful for cases where multiple processes might be
* rendering to a BO and you want to wait for all rendering to be
* completed.
*/
struct drm_ethosu_bo_wait {
__u32 handle;
__u32 pad;
__s64 timeout_ns; /* absolute */
};
struct drm_ethosu_cmdstream_bo_create {
/* Size of the data argument. */
__u32 size;
/* Flags, currently must be 0. */
__u32 flags;
/* Pointer to the data. */
__u64 data;
/** Returned GEM handle for the BO. */
__u32 handle;
/* Pad, must be 0. */
__u32 pad;
};
/**
* struct drm_ethosu_job - A job to be run on the NPU
*
* The kernel will schedule the execution of this job taking into account its
* dependencies with other jobs. All tasks in the same job will be executed
* sequentially on the same core, to benefit from memory residency in SRAM.
*/
struct drm_ethosu_job {
/** Input: BO handle for cmdstream. */
__u32 cmd_bo;
/** Input: Amount of SRAM to use. */
__u32 sram_size;
#define ETHOSU_MAX_REGIONS 8
/** Input: Array of BO handles for each region. */
__u32 region_bo_handles[ETHOSU_MAX_REGIONS];
};
/**
* struct drm_ethosu_submit - ioctl argument for submitting commands to the NPU.
*
* The kernel will schedule the execution of these jobs in dependency order.
*/
struct drm_ethosu_submit {
/** Input: Pointer to an array of struct drm_ethosu_job. */
__u64 jobs;
/** Input: Number of jobs passed in. */
__u32 job_count;
/** Reserved, must be zero. */
__u32 pad;
};
/**
* DRM_IOCTL_ETHOSU() - Build a ethosu IOCTL number
* @__access: Access type. Must be R, W or RW.
* @__id: One of the DRM_ETHOSU_xxx id.
* @__type: Suffix of the type being passed to the IOCTL.
*
* Don't use this macro directly, use the DRM_IOCTL_ETHOSU_xxx
* values instead.
*
* Return: An IOCTL number to be passed to ioctl() from userspace.
*/
#define DRM_IOCTL_ETHOSU(__access, __id, __type) \
DRM_IO ## __access(DRM_COMMAND_BASE + DRM_ETHOSU_ ## __id, \
struct drm_ethosu_ ## __type)
enum {
DRM_IOCTL_ETHOSU_DEV_QUERY =
DRM_IOCTL_ETHOSU(WR, DEV_QUERY, dev_query),
DRM_IOCTL_ETHOSU_BO_CREATE =
DRM_IOCTL_ETHOSU(WR, BO_CREATE, bo_create),
DRM_IOCTL_ETHOSU_BO_WAIT =
DRM_IOCTL_ETHOSU(WR, BO_WAIT, bo_wait),
DRM_IOCTL_ETHOSU_BO_MMAP_OFFSET =
DRM_IOCTL_ETHOSU(WR, BO_MMAP_OFFSET, bo_mmap_offset),
DRM_IOCTL_ETHOSU_CMDSTREAM_BO_CREATE =
DRM_IOCTL_ETHOSU(WR, CMDSTREAM_BO_CREATE, cmdstream_bo_create),
DRM_IOCTL_ETHOSU_SUBMIT =
DRM_IOCTL_ETHOSU(WR, SUBMIT, submit),
};
#if defined(__cplusplus)
}
#endif
#endif /* _ETHOSU_DRM_H_ */