mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
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:
1
.mailmap
1
.mailmap
@@ -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>
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
||||
|
||||
@@ -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>
|
||||
|
||||
79
Documentation/devicetree/bindings/npu/arm,ethos.yaml
Normal file
79
Documentation/devicetree/bindings/npu/arm,ethos.yaml
Normal 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>;
|
||||
};
|
||||
...
|
||||
@@ -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
|
||||
-----------------
|
||||
|
||||
|
||||
18
MAINTAINERS
18
MAINTAINERS
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
11
drivers/accel/ethosu/Kconfig
Normal file
11
drivers/accel/ethosu/Kconfig
Normal 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
|
||||
4
drivers/accel/ethosu/Makefile
Normal file
4
drivers/accel/ethosu/Makefile
Normal 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
|
||||
197
drivers/accel/ethosu/ethosu_device.h
Normal file
197
drivers/accel/ethosu/ethosu_device.h
Normal 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
|
||||
403
drivers/accel/ethosu/ethosu_drv.c
Normal file
403
drivers/accel/ethosu/ethosu_drv.c
Normal 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,
|
||||
ðosudev->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 = ðosu_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,
|
||||
ðosudev->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, ðosu_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, ðosudev->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(ðosudev->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(ðosudev->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(ðosu_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");
|
||||
15
drivers/accel/ethosu/ethosu_drv.h
Normal file
15
drivers/accel/ethosu/ethosu_drv.h
Normal 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
|
||||
704
drivers/accel/ethosu/ethosu_gem.c
Normal file
704
drivers/accel/ethosu/ethosu_gem.c
Normal 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 = ðosu_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;
|
||||
}
|
||||
46
drivers/accel/ethosu/ethosu_gem.h
Normal file
46
drivers/accel/ethosu/ethosu_gem.h
Normal 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__ */
|
||||
496
drivers/accel/ethosu/ethosu_job.c
Normal file
496
drivers/accel/ethosu/ethosu_job.c
Normal 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, ðosu_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 = ðosu_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(ðosu_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 = ðosu_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;
|
||||
}
|
||||
40
drivers/accel/ethosu/ethosu_job.h
Normal file
40
drivers/accel/ethosu/ethosu_job.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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),
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_ */
|
||||
|
||||
833
drivers/gpu/drm/vkms/vkms_configfs.c
Normal file
833
drivers/gpu/drm/vkms/vkms_configfs.c
Normal 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);
|
||||
}
|
||||
8
drivers/gpu/drm/vkms/vkms_configfs.h
Normal file
8
drivers/gpu/drm/vkms/vkms_configfs.h
Normal 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_ */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_ */
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
*
|
||||
|
||||
261
include/uapi/drm/ethosu_accel.h
Normal file
261
include/uapi/drm/ethosu_accel.h
Normal 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_ */
|
||||
Reference in New Issue
Block a user