Merge drm-misc-next-2025-08-21 into drm-rust-next
We need the DRM Rust changes that went into drm-misc before the existence of the drm-rust tree in here as well. Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
2
.mailmap
2
.mailmap
@@ -164,6 +164,8 @@ Casey Connolly <casey.connolly@linaro.org> <caleb@connolly.tech>
|
||||
Casey Connolly <casey.connolly@linaro.org> <caleb@postmarketos.org>
|
||||
Can Guo <quic_cang@quicinc.com> <cang@codeaurora.org>
|
||||
Carl Huang <quic_cjhuang@quicinc.com> <cjhuang@codeaurora.org>
|
||||
Carl Vanderlip <carl.vanderlip@oss.qualcomm.com> <carlv@codeaurora.org>
|
||||
Carl Vanderlip <carl.vanderlip@oss.qualcomm.com> <quic_carlv@quicinc.com>
|
||||
Carlos Bilbao <carlos.bilbao@kernel.org> <carlos.bilbao@amd.com>
|
||||
Carlos Bilbao <carlos.bilbao@kernel.org> <carlos.bilbao.osdev@gmail.com>
|
||||
Carlos Bilbao <carlos.bilbao@kernel.org> <bilbao@vt.edu>
|
||||
|
||||
@@ -731,7 +731,7 @@ Contact: linux-block@vger.kernel.org
|
||||
Description:
|
||||
[RW] If the device is registered for writeback throttling, then
|
||||
this file shows the target minimum read latency. If this latency
|
||||
is exceeded in a given window of time (see wb_window_usec), then
|
||||
is exceeded in a given window of time (see curr_win_nsec), then
|
||||
the writeback throttling will start scaling back writes. Writing
|
||||
a value of '0' to this file disables the feature. Writing a
|
||||
value of '-1' to this file resets the value to the default
|
||||
|
||||
@@ -10,6 +10,7 @@ Compute Accelerators
|
||||
introduction
|
||||
amdxdna/index
|
||||
qaic/index
|
||||
rocket/index
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
|
||||
19
Documentation/accel/rocket/index.rst
Normal file
19
Documentation/accel/rocket/index.rst
Normal file
@@ -0,0 +1,19 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
=====================================
|
||||
accel/rocket Rockchip NPU driver
|
||||
=====================================
|
||||
|
||||
The accel/rocket driver supports the Neural Processing Units (NPUs) inside some
|
||||
Rockchip SoCs such as the RK3588. Rockchip calls it RKNN and sometimes RKNPU.
|
||||
|
||||
The hardware is described in chapter 36 in the RK3588 TRM.
|
||||
|
||||
This driver just powers the hardware on and off, allocates and maps buffers to
|
||||
the device and submits jobs to the frontend unit. Everything else is done in
|
||||
userspace, as a Gallium driver (also called rocket) that is part of the Mesa3D
|
||||
project.
|
||||
|
||||
Hardware currently supported:
|
||||
|
||||
* RK3588
|
||||
@@ -79,7 +79,7 @@ zone_capacity_mb Device zone capacity (must always be equal to or lower than
|
||||
the zone size. Default: zone size.
|
||||
conv_zones Total number of conventioanl zones starting from sector 0.
|
||||
Default: 8.
|
||||
base_dir Path to the base directoy where to create the directory
|
||||
base_dir Path to the base directory where to create the directory
|
||||
containing the zone files of the device.
|
||||
Default=/var/local/zloop.
|
||||
The device directory containing the zone files is always
|
||||
|
||||
@@ -214,7 +214,7 @@ Spectre_v1 X
|
||||
Spectre_v2 X X
|
||||
Spectre_v2_user X X * (Note 1)
|
||||
SRBDS X X X X
|
||||
SRSO X X
|
||||
SRSO X X X X
|
||||
SSB (Note 4)
|
||||
TAA X X X X * (Note 2)
|
||||
TSA X X X X
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/bridge/solomon,ssd2825.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Solomon SSD2825 RGB to MIPI-DSI bridge
|
||||
|
||||
maintainers:
|
||||
- Svyatoslav Ryhel <clamor95@gmail.com>
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/spi/spi-peripheral-props.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: solomon,ssd2825
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
reset-gpios: true
|
||||
|
||||
dvdd-supply:
|
||||
description: Regulator for 1.2V digital power supply.
|
||||
|
||||
avdd-supply:
|
||||
description: Regulator for 1.2V analog power supply.
|
||||
|
||||
vddio-supply:
|
||||
description: Regulator for 1.8V IO power supply.
|
||||
|
||||
spi-max-frequency:
|
||||
maximum: 1000000
|
||||
|
||||
spi-cpha: true
|
||||
spi-cpol: true
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
description: Reference TX_CLK used before PLL is locked.
|
||||
|
||||
solomon,hs-zero-delay-ns:
|
||||
description:
|
||||
HS zero delay period
|
||||
minimum: 0
|
||||
maximum: 1700
|
||||
default: 133
|
||||
|
||||
solomon,hs-prep-delay-ns:
|
||||
description:
|
||||
HS prep delay period
|
||||
minimum: 0
|
||||
maximum: 1728
|
||||
default: 40
|
||||
|
||||
ports:
|
||||
$ref: /schemas/graph.yaml#/properties/ports
|
||||
|
||||
properties:
|
||||
port@0:
|
||||
$ref: /schemas/graph.yaml#/$defs/port-base
|
||||
unevaluatedProperties: false
|
||||
description:
|
||||
Video port for RGB input
|
||||
|
||||
properties:
|
||||
endpoint:
|
||||
$ref: /schemas/graph.yaml#/$defs/endpoint-base
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
bus-width:
|
||||
enum: [ 16, 18, 24 ]
|
||||
|
||||
port@1:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
description:
|
||||
Video port for DSI output (panel or connector)
|
||||
|
||||
required:
|
||||
- port@0
|
||||
- port@1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- ports
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dsi@2 {
|
||||
compatible = "solomon,ssd2825";
|
||||
reg = <2>;
|
||||
|
||||
spi-max-frequency = <1000000>;
|
||||
|
||||
spi-cpha;
|
||||
spi-cpol;
|
||||
|
||||
reset-gpios = <&gpio 114 GPIO_ACTIVE_LOW>;
|
||||
|
||||
dvdd-supply = <&vdd_1v2>;
|
||||
avdd-supply = <&vdd_1v2>;
|
||||
vddio-supply = <&vdd_1v8_io>;
|
||||
|
||||
solomon,hs-zero-delay-ns = <300>;
|
||||
solomon,hs-prep-delay-ns = <65>;
|
||||
|
||||
clocks = <&ssd2825_tx_clk>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
|
||||
bridge_input: endpoint {
|
||||
remote-endpoint = <&dpi_output>;
|
||||
bus-width = <24>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
|
||||
bridge_output: endpoint {
|
||||
remote-endpoint = <&panel_input>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/bridge/waveshare,dsi2dpi.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Waveshare MIPI-DSI to DPI Converter bridge
|
||||
|
||||
maintainers:
|
||||
- Joseph Guo <qijian.guo@nxp.com>
|
||||
|
||||
description:
|
||||
Waveshare bridge board is part of Waveshare panel which converts DSI to DPI.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: waveshare,dsi2dpi
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
description: base I2C address of the device
|
||||
|
||||
power-supply: true
|
||||
|
||||
ports:
|
||||
$ref: /schemas/graph.yaml#/properties/ports
|
||||
|
||||
properties:
|
||||
port@0:
|
||||
$ref: /schemas/graph.yaml#/$defs/port-base
|
||||
unevaluatedProperties: false
|
||||
description:
|
||||
Video port for MIPI DSI input
|
||||
|
||||
properties:
|
||||
endpoint:
|
||||
$ref: /schemas/media/video-interfaces.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
data-lanes:
|
||||
description: array of physical DSI data lane indexes.
|
||||
items:
|
||||
- const: 1
|
||||
- const: 2
|
||||
|
||||
required:
|
||||
- data-lanes
|
||||
|
||||
port@1:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
description:
|
||||
Video port for MIPI DPI output panel.
|
||||
|
||||
required:
|
||||
- port@0
|
||||
- port@1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- ports
|
||||
- power-supply
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
bridge@45 {
|
||||
compatible = "waveshare,dsi2dpi";
|
||||
reg = <0x45>;
|
||||
power-supply = <®_3p3v>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
|
||||
waveshare_from_dsim: endpoint {
|
||||
data-lanes = <1 2>;
|
||||
remote-endpoint = <&dsim_to_waveshare>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
|
||||
waveshare_to_panel: endpoint {
|
||||
remote-endpoint = <&panel_to_waveshare>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
@@ -0,0 +1,60 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/hydis,hv101hd1.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Hydis HV101HD1 DSI Display Panel
|
||||
|
||||
maintainers:
|
||||
- Svyatoslav Ryhel <clamor95@gmail.com>
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: hydis,hv101hd1
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply: true
|
||||
vio-supply: true
|
||||
|
||||
backlight: true
|
||||
port: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- vdd-supply
|
||||
- vio-supply
|
||||
- backlight
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
dsi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
panel@0 {
|
||||
compatible = "hydis,hv101hd1";
|
||||
reg = <0>;
|
||||
|
||||
vdd-supply = <&vdd_lcd>;
|
||||
vio-supply = <&vddio_lcd>;
|
||||
|
||||
backlight = <&backlight>;
|
||||
|
||||
port {
|
||||
panel_in: endpoint {
|
||||
remote-endpoint = <&dsi_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
||||
@@ -236,6 +236,8 @@ properties:
|
||||
- okaya,rs800480t-7x0gp
|
||||
# Olimex 4.3" TFT LCD panel
|
||||
- olimex,lcd-olinuxino-43-ts
|
||||
# Olimex 5.0" TFT LCD panel
|
||||
- olimex,lcd-olinuxino-5-cts
|
||||
# On Tat Industrial Company 5" DPI TFT panel.
|
||||
- ontat,kd50g21-40nt-a1
|
||||
# On Tat Industrial Company 7" DPI TFT panel.
|
||||
@@ -321,6 +323,10 @@ properties:
|
||||
- vivax,tpc9150-panel
|
||||
# VXT 800x480 color TFT LCD panel
|
||||
- vxt,vl050-8048nt-c01
|
||||
# Waveshare 13.3" FHD (1920x1080) LCD panel
|
||||
- waveshare,13.3inch-panel
|
||||
# Waveshare 7.0" WSVGA (1024x600) LCD panel
|
||||
- waveshare,7.0inch-c-panel
|
||||
# Winstar Display Corporation 3.5" QVGA (320x240) TFT LCD panel
|
||||
- winstar,wf35ltiacd
|
||||
# Yes Optoelectronics YTC700TLAG-05-201C 7" TFT LCD panel
|
||||
|
||||
@@ -21,6 +21,10 @@ properties:
|
||||
- enum:
|
||||
# Samsung 13" 3K (2880×1920 pixels) eDP AMOLED panel
|
||||
- samsung,atna30dw01
|
||||
# Samsung 14" FHD+ (1920x1200 pixels) eDP AMOLED panel
|
||||
- samsung,atna40ct06
|
||||
# Samsung 14" WQXGA+ (2880x1800 pixels) eDP AMOLED panel
|
||||
- samsung,atna40cu11
|
||||
# Samsung 14" WQXGA+ (2880×1800 pixels) eDP AMOLED panel
|
||||
- samsung,atna40yk20
|
||||
# Samsung 14.5" WQXGA+ (2880x1800 pixels) eDP AMOLED panel
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/samsung,s6e8aa5x01-ams561ra01.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Samsung AMS561RA01 panel with S6E8AA5X01 controller
|
||||
|
||||
maintainers:
|
||||
- Kaustabh Chakraborty <kauschluss@disroot.org>
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: samsung,s6e8aa5x01-ams561ra01
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply:
|
||||
description: core voltage supply
|
||||
|
||||
vci-supply:
|
||||
description: voltage supply for analog circuits
|
||||
|
||||
reset-gpios: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
dsi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
panel@0 {
|
||||
compatible = "samsung,s6e8aa5x01-ams561ra01";
|
||||
reg = <0>;
|
||||
|
||||
vdd-supply = <&panel_vdd_reg>;
|
||||
vci-supply = <&panel_vci_reg>;
|
||||
|
||||
reset-gpios = <&gpd3 4 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
@@ -62,11 +62,13 @@ properties:
|
||||
items:
|
||||
- description: GMAC main clock
|
||||
- description: Peripheral registers interface clock
|
||||
- description: APB glue registers interface clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: stmmaceth
|
||||
- const: pclk
|
||||
- const: apb
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
@@ -88,8 +90,8 @@ examples:
|
||||
compatible = "thead,th1520-gmac", "snps,dwmac-3.70a";
|
||||
reg = <0xe7070000 0x2000>, <0xec003000 0x1000>;
|
||||
reg-names = "dwmac", "apb";
|
||||
clocks = <&clk 1>, <&clk 2>;
|
||||
clock-names = "stmmaceth", "pclk";
|
||||
clocks = <&clk 1>, <&clk 2>, <&clk 3>;
|
||||
clock-names = "stmmaceth", "pclk", "apb";
|
||||
interrupts = <66>;
|
||||
interrupt-names = "macirq";
|
||||
phy-mode = "rgmii-id";
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/npu/rockchip,rk3588-rknn-core.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Neural Processing Unit IP from Rockchip
|
||||
|
||||
maintainers:
|
||||
- Tomeu Vizoso <tomeu@tomeuvizoso.net>
|
||||
|
||||
description:
|
||||
Rockchip IP for accelerating inference of neural networks.
|
||||
|
||||
There is to be a node per each NPU core in the SoC, and each core should reference all the
|
||||
resources that it needs to function, such as clocks, power domains, and resets.
|
||||
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: '^npu@[a-f0-9]+$'
|
||||
|
||||
compatible:
|
||||
enum:
|
||||
- rockchip,rk3588-rknn-core
|
||||
|
||||
reg:
|
||||
maxItems: 3
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: pc # Program Control-related registers
|
||||
- const: cna # Convolution Neural Network Accelerator registers
|
||||
- const: core # Main NPU core processing unit registers
|
||||
|
||||
clocks:
|
||||
maxItems: 4
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: aclk
|
||||
- const: hclk
|
||||
- const: npu
|
||||
- const: pclk
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
iommus:
|
||||
maxItems: 1
|
||||
|
||||
npu-supply: true
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 2
|
||||
|
||||
reset-names:
|
||||
items:
|
||||
- const: srst_a
|
||||
- const: srst_h
|
||||
|
||||
sram-supply: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- clocks
|
||||
- clock-names
|
||||
- interrupts
|
||||
- iommus
|
||||
- power-domains
|
||||
- resets
|
||||
- reset-names
|
||||
- npu-supply
|
||||
- sram-supply
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/rockchip,rk3588-cru.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/power/rk3588-power.h>
|
||||
#include <dt-bindings/reset/rockchip,rk3588-cru.h>
|
||||
|
||||
bus {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
npu@fdab0000 {
|
||||
compatible = "rockchip,rk3588-rknn-core";
|
||||
reg = <0x0 0xfdab0000 0x0 0x1000>,
|
||||
<0x0 0xfdab1000 0x0 0x1000>,
|
||||
<0x0 0xfdab3000 0x0 0x1000>;
|
||||
reg-names = "pc", "cna", "core";
|
||||
clocks = <&cru ACLK_NPU0>, <&cru HCLK_NPU0>,
|
||||
<&scmi_clk SCMI_CLK_NPU>, <&cru PCLK_NPU_ROOT>;
|
||||
clock-names = "aclk", "hclk", "npu", "pclk";
|
||||
interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH 0>;
|
||||
iommus = <&rknn_mmu_0>;
|
||||
npu-supply = <&vdd_npu_s0>;
|
||||
power-domains = <&power RK3588_PD_NPUTOP>;
|
||||
resets = <&cru SRST_A_RKNN0>, <&cru SRST_H_RKNN0>;
|
||||
reset-names = "srst_a", "srst_h";
|
||||
sram-supply = <&vdd_npu_mem_s0>;
|
||||
};
|
||||
};
|
||||
...
|
||||
@@ -497,19 +497,19 @@ Contact: Douglas Anderson <dianders@chromium.org>
|
||||
|
||||
Level: Intermediate
|
||||
|
||||
Transition away from using mipi_dsi_*_write_seq()
|
||||
-------------------------------------------------
|
||||
Transition away from using deprecated MIPI DSI functions
|
||||
--------------------------------------------------------
|
||||
|
||||
The macros mipi_dsi_generic_write_seq() and mipi_dsi_dcs_write_seq() are
|
||||
non-intuitive because, if there are errors, they return out of the *caller's*
|
||||
function. We should move all callers to use mipi_dsi_generic_write_seq_multi()
|
||||
and mipi_dsi_dcs_write_seq_multi() macros instead.
|
||||
There are many functions defined in ``drm_mipi_dsi.c`` which have been
|
||||
deprecated. Each deprecated function was deprecated in favor of its `multi`
|
||||
variant (e.g. `mipi_dsi_generic_write()` and `mipi_dsi_generic_write_multi()`).
|
||||
The `multi` variant of a function includes improved error handling and logic
|
||||
which makes it more convenient to make several calls in a row, as most MIPI
|
||||
drivers do.
|
||||
|
||||
Once all callers are transitioned, the macros and the functions that they call,
|
||||
mipi_dsi_generic_write_chatty() and mipi_dsi_dcs_write_buffer_chatty(), can
|
||||
probably be removed. Alternatively, if people feel like the _multi() variants
|
||||
are overkill for some use cases, we could keep the mipi_dsi_*_write_seq()
|
||||
variants but change them not to return out of the caller.
|
||||
Drivers should be updated to use undeprecated functions. Once all usages of the
|
||||
deprecated MIPI DSI functions have been removed, their definitions may be
|
||||
removed from ``drm_mipi_dsi.c``.
|
||||
|
||||
Contact: Douglas Anderson <dianders@chromium.org>
|
||||
|
||||
|
||||
@@ -1420,7 +1420,7 @@ udp_hash_entries - INTEGER
|
||||
A negative value means the networking namespace does not own its
|
||||
hash buckets and shares the initial networking namespace's one.
|
||||
|
||||
udp_child_ehash_entries - INTEGER
|
||||
udp_child_hash_entries - INTEGER
|
||||
Control the number of hash buckets for UDP sockets in the child
|
||||
networking namespace, which must be set before clone() or unshare().
|
||||
|
||||
|
||||
23
MAINTAINERS
23
MAINTAINERS
@@ -7494,12 +7494,23 @@ F: include/linux/power/smartreflex.h
|
||||
DRM ACCEL DRIVERS FOR INTEL VPU
|
||||
M: Jacek Lawrynowicz <jacek.lawrynowicz@linux.intel.com>
|
||||
M: Maciej Falkowski <maciej.falkowski@linux.intel.com>
|
||||
M: Karol Wachowski <karol.wachowski@linux.intel.com>
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
S: Supported
|
||||
T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
|
||||
F: drivers/accel/ivpu/
|
||||
F: include/uapi/drm/ivpu_accel.h
|
||||
|
||||
DRM ACCEL DRIVER FOR ROCKCHIP NPU
|
||||
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: Documentation/accel/rocket/
|
||||
F: Documentation/devicetree/bindings/npu/rockchip,rknn-core.yaml
|
||||
F: drivers/accel/rocket/
|
||||
F: include/uapi/drm/rocket_accel.h
|
||||
|
||||
DRM COMPUTE ACCELERATORS DRIVERS AND FRAMEWORK
|
||||
M: Oded Gabbay <ogabbay@kernel.org>
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
@@ -11442,6 +11453,7 @@ F: drivers/tty/hvc/
|
||||
HUNG TASK DETECTOR
|
||||
M: Andrew Morton <akpm@linux-foundation.org>
|
||||
R: Lance Yang <lance.yang@linux.dev>
|
||||
R: Masami Hiramatsu <mhiramat@kernel.org>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Maintained
|
||||
F: include/linux/hung_task.h
|
||||
@@ -12587,10 +12599,9 @@ S: Supported
|
||||
F: drivers/cpufreq/intel_pstate.c
|
||||
|
||||
INTEL PTP DFL ToD DRIVER
|
||||
M: Tianfei Zhang <tianfei.zhang@intel.com>
|
||||
L: linux-fpga@vger.kernel.org
|
||||
L: netdev@vger.kernel.org
|
||||
S: Maintained
|
||||
S: Orphan
|
||||
F: drivers/ptp/ptp_dfl_tod.c
|
||||
|
||||
INTEL QUADRATURE ENCODER PERIPHERAL DRIVER
|
||||
@@ -12728,9 +12739,8 @@ S: Maintained
|
||||
F: drivers/platform/x86/intel/wmi/thunderbolt.c
|
||||
|
||||
INTEL WWAN IOSM DRIVER
|
||||
M: M Chetan Kumar <m.chetan.kumar@intel.com>
|
||||
L: netdev@vger.kernel.org
|
||||
S: Maintained
|
||||
S: Orphan
|
||||
F: drivers/net/wwan/iosm/
|
||||
|
||||
INTEL(R) FLEXIBLE RETURN AND EVENT DELIVERY
|
||||
@@ -13690,7 +13700,6 @@ F: scripts/Makefile.kmsan
|
||||
|
||||
KPROBES
|
||||
M: Naveen N Rao <naveen@kernel.org>
|
||||
M: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>
|
||||
M: "David S. Miller" <davem@davemloft.net>
|
||||
M: Masami Hiramatsu <mhiramat@kernel.org>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
@@ -15678,7 +15687,6 @@ MEDIATEK T7XX 5G WWAN MODEM DRIVER
|
||||
M: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
|
||||
R: Chiranjeevi Rapolu <chiranjeevi.rapolu@linux.intel.com>
|
||||
R: Liu Haijun <haijun.liu@mediatek.com>
|
||||
R: M Chetan Kumar <m.chetan.kumar@linux.intel.com>
|
||||
R: Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||||
L: netdev@vger.kernel.org
|
||||
S: Supported
|
||||
@@ -17455,6 +17463,7 @@ F: drivers/net/ethernet/neterion/
|
||||
NETFILTER
|
||||
M: Pablo Neira Ayuso <pablo@netfilter.org>
|
||||
M: Jozsef Kadlecsik <kadlec@netfilter.org>
|
||||
M: Florian Westphal <fw@strlen.de>
|
||||
L: netfilter-devel@vger.kernel.org
|
||||
L: coreteam@netfilter.org
|
||||
S: Maintained
|
||||
@@ -20667,7 +20676,7 @@ F: include/dt-bindings/clock/qcom,*
|
||||
|
||||
QUALCOMM CLOUD AI (QAIC) DRIVER
|
||||
M: Jeff Hugo <jeff.hugo@oss.qualcomm.com>
|
||||
R: Carl Vanderlip <quic_carlv@quicinc.com>
|
||||
R: Carl Vanderlip <carl.vanderlip@oss.qualcomm.com>
|
||||
L: linux-arm-msm@vger.kernel.org
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
S: Supported
|
||||
|
||||
2
Makefile
2
Makefile
@@ -2,7 +2,7 @@
|
||||
VERSION = 6
|
||||
PATCHLEVEL = 17
|
||||
SUBLEVEL = 0
|
||||
EXTRAVERSION = -rc1
|
||||
EXTRAVERSION = -rc2
|
||||
NAME = Baby Opossum Posse
|
||||
|
||||
# *DOCUMENTATION*
|
||||
|
||||
@@ -297,8 +297,9 @@
|
||||
reg-names = "dwmac", "apb";
|
||||
interrupts = <67 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "macirq";
|
||||
clocks = <&clk CLK_GMAC_AXI>, <&clk CLK_GMAC1>;
|
||||
clock-names = "stmmaceth", "pclk";
|
||||
clocks = <&clk CLK_GMAC_AXI>, <&clk CLK_GMAC1>,
|
||||
<&clk CLK_PERISYS_APB4_HCLK>;
|
||||
clock-names = "stmmaceth", "pclk", "apb";
|
||||
snps,pbl = <32>;
|
||||
snps,fixed-burst;
|
||||
snps,multicast-filter-bins = <64>;
|
||||
@@ -319,8 +320,9 @@
|
||||
reg-names = "dwmac", "apb";
|
||||
interrupts = <66 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "macirq";
|
||||
clocks = <&clk CLK_GMAC_AXI>, <&clk CLK_GMAC0>;
|
||||
clock-names = "stmmaceth", "pclk";
|
||||
clocks = <&clk CLK_GMAC_AXI>, <&clk CLK_GMAC0>,
|
||||
<&clk CLK_PERISYS_APB4_HCLK>;
|
||||
clock-names = "stmmaceth", "pclk", "apb";
|
||||
snps,pbl = <32>;
|
||||
snps,fixed-burst;
|
||||
snps,multicast-filter-bins = <64>;
|
||||
|
||||
@@ -106,5 +106,18 @@ void get_cpuflags(void)
|
||||
cpuid(0x80000001, &ignored, &ignored, &cpu.flags[6],
|
||||
&cpu.flags[1]);
|
||||
}
|
||||
|
||||
if (max_amd_level >= 0x8000001f) {
|
||||
u32 ebx;
|
||||
|
||||
/*
|
||||
* The X86_FEATURE_COHERENCY_SFW_NO feature bit is in
|
||||
* the virtualization flags entry (word 8) and set by
|
||||
* scattered.c, so the bit needs to be explicitly set.
|
||||
*/
|
||||
cpuid(0x8000001f, &ignored, &ebx, &ignored, &ignored);
|
||||
if (ebx & BIT(31))
|
||||
set_bit(X86_FEATURE_COHERENCY_SFW_NO, cpu.flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,6 +785,7 @@ static void __head svsm_pval_4k_page(unsigned long paddr, bool validate)
|
||||
pc->entry[0].page_size = RMP_PG_SIZE_4K;
|
||||
pc->entry[0].action = validate;
|
||||
pc->entry[0].ignore_cf = 0;
|
||||
pc->entry[0].rsvd = 0;
|
||||
pc->entry[0].pfn = paddr >> PAGE_SHIFT;
|
||||
|
||||
/* Protocol 0, Call ID 1 */
|
||||
@@ -810,6 +811,13 @@ static void __head pvalidate_4k_page(unsigned long vaddr, unsigned long paddr,
|
||||
if (ret)
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PVALIDATE);
|
||||
}
|
||||
|
||||
/*
|
||||
* If validating memory (making it private) and affected by the
|
||||
* cache-coherency vulnerability, perform the cache eviction mitigation.
|
||||
*/
|
||||
if (validate && !has_cpuflag(X86_FEATURE_COHERENCY_SFW_NO))
|
||||
sev_evict_cache((void *)vaddr, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -227,6 +227,7 @@ static u64 svsm_build_ca_from_pfn_range(u64 pfn, u64 pfn_end, bool action,
|
||||
pe->page_size = RMP_PG_SIZE_4K;
|
||||
pe->action = action;
|
||||
pe->ignore_cf = 0;
|
||||
pe->rsvd = 0;
|
||||
pe->pfn = pfn;
|
||||
|
||||
pe++;
|
||||
@@ -257,6 +258,7 @@ static int svsm_build_ca_from_psc_desc(struct snp_psc_desc *desc, unsigned int d
|
||||
pe->page_size = e->pagesize ? RMP_PG_SIZE_2M : RMP_PG_SIZE_4K;
|
||||
pe->action = e->operation == SNP_PAGE_STATE_PRIVATE;
|
||||
pe->ignore_cf = 0;
|
||||
pe->rsvd = 0;
|
||||
pe->pfn = e->gfn;
|
||||
|
||||
pe++;
|
||||
@@ -358,10 +360,31 @@ static void svsm_pval_pages(struct snp_psc_desc *desc)
|
||||
|
||||
static void pvalidate_pages(struct snp_psc_desc *desc)
|
||||
{
|
||||
struct psc_entry *e;
|
||||
unsigned int i;
|
||||
|
||||
if (snp_vmpl)
|
||||
svsm_pval_pages(desc);
|
||||
else
|
||||
pval_pages(desc);
|
||||
|
||||
/*
|
||||
* If not affected by the cache-coherency vulnerability there is no need
|
||||
* to perform the cache eviction mitigation.
|
||||
*/
|
||||
if (cpu_feature_enabled(X86_FEATURE_COHERENCY_SFW_NO))
|
||||
return;
|
||||
|
||||
for (i = 0; i <= desc->hdr.end_entry; i++) {
|
||||
e = &desc->entries[i];
|
||||
|
||||
/*
|
||||
* If validating memory (making it private) perform the cache
|
||||
* eviction mitigation.
|
||||
*/
|
||||
if (e->operation == SNP_PAGE_STATE_PRIVATE)
|
||||
sev_evict_cache(pfn_to_kaddr(e->gfn), e->pagesize ? 512 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
static int vmgexit_psc(struct ghcb *ghcb, struct snp_psc_desc *desc)
|
||||
|
||||
@@ -371,29 +371,30 @@ static enum es_result __vc_handle_msr_caa(struct pt_regs *regs, bool write)
|
||||
* executing with Secure TSC enabled, so special handling is required for
|
||||
* accesses of MSR_IA32_TSC and MSR_AMD64_GUEST_TSC_FREQ.
|
||||
*/
|
||||
static enum es_result __vc_handle_secure_tsc_msrs(struct pt_regs *regs, bool write)
|
||||
static enum es_result __vc_handle_secure_tsc_msrs(struct es_em_ctxt *ctxt, bool write)
|
||||
{
|
||||
struct pt_regs *regs = ctxt->regs;
|
||||
u64 tsc;
|
||||
|
||||
/*
|
||||
* GUEST_TSC_FREQ should not be intercepted when Secure TSC is enabled.
|
||||
* Terminate the SNP guest when the interception is enabled.
|
||||
* Writing to MSR_IA32_TSC can cause subsequent reads of the TSC to
|
||||
* return undefined values, and GUEST_TSC_FREQ is read-only. Generate
|
||||
* a #GP on all writes.
|
||||
*/
|
||||
if (write) {
|
||||
ctxt->fi.vector = X86_TRAP_GP;
|
||||
ctxt->fi.error_code = 0;
|
||||
return ES_EXCEPTION;
|
||||
}
|
||||
|
||||
/*
|
||||
* GUEST_TSC_FREQ read should not be intercepted when Secure TSC is
|
||||
* enabled. Terminate the guest if a read is attempted.
|
||||
*/
|
||||
if (regs->cx == MSR_AMD64_GUEST_TSC_FREQ)
|
||||
return ES_VMM_ERROR;
|
||||
|
||||
/*
|
||||
* Writes: Writing to MSR_IA32_TSC can cause subsequent reads of the TSC
|
||||
* to return undefined values, so ignore all writes.
|
||||
*
|
||||
* Reads: Reads of MSR_IA32_TSC should return the current TSC value, use
|
||||
* the value returned by rdtsc_ordered().
|
||||
*/
|
||||
if (write) {
|
||||
WARN_ONCE(1, "TSC MSR writes are verboten!\n");
|
||||
return ES_OK;
|
||||
}
|
||||
|
||||
/* Reads of MSR_IA32_TSC should return the current TSC value. */
|
||||
tsc = rdtsc_ordered();
|
||||
regs->ax = lower_32_bits(tsc);
|
||||
regs->dx = upper_32_bits(tsc);
|
||||
@@ -416,7 +417,7 @@ static enum es_result vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
|
||||
case MSR_IA32_TSC:
|
||||
case MSR_AMD64_GUEST_TSC_FREQ:
|
||||
if (sev_status & MSR_AMD64_SNP_SECURE_TSC)
|
||||
return __vc_handle_secure_tsc_msrs(regs, write);
|
||||
return __vc_handle_secure_tsc_msrs(ctxt, write);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -218,6 +218,7 @@
|
||||
#define X86_FEATURE_FLEXPRIORITY ( 8*32+ 1) /* "flexpriority" Intel FlexPriority */
|
||||
#define X86_FEATURE_EPT ( 8*32+ 2) /* "ept" Intel Extended Page Table */
|
||||
#define X86_FEATURE_VPID ( 8*32+ 3) /* "vpid" Intel Virtual Processor ID */
|
||||
#define X86_FEATURE_COHERENCY_SFW_NO ( 8*32+ 4) /* SNP cache coherency software work around not needed */
|
||||
|
||||
#define X86_FEATURE_VMMCALL ( 8*32+15) /* "vmmcall" Prefer VMMCALL to VMCALL */
|
||||
#define X86_FEATURE_XENPV ( 8*32+16) /* Xen paravirtual guest */
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#ifndef _ASM_X86_CPUID_H
|
||||
#define _ASM_X86_CPUID_H
|
||||
|
||||
#include <asm/cpuid/api.h>
|
||||
|
||||
#endif /* _ASM_X86_CPUID_H */
|
||||
@@ -619,6 +619,24 @@ int rmp_make_shared(u64 pfn, enum pg_level level);
|
||||
void snp_leak_pages(u64 pfn, unsigned int npages);
|
||||
void kdump_sev_callback(void);
|
||||
void snp_fixup_e820_tables(void);
|
||||
|
||||
static inline void sev_evict_cache(void *va, int npages)
|
||||
{
|
||||
volatile u8 val __always_unused;
|
||||
u8 *bytes = va;
|
||||
int page_idx;
|
||||
|
||||
/*
|
||||
* For SEV guests, a read from the first/last cache-lines of a 4K page
|
||||
* using the guest key is sufficient to cause a flush of all cache-lines
|
||||
* associated with that 4K page without incurring all the overhead of a
|
||||
* full CLFLUSH sequence.
|
||||
*/
|
||||
for (page_idx = 0; page_idx < npages; page_idx++) {
|
||||
val = bytes[page_idx * PAGE_SIZE];
|
||||
val = bytes[page_idx * PAGE_SIZE + PAGE_SIZE - 1];
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline bool snp_probe_rmptable_info(void) { return false; }
|
||||
static inline int snp_rmptable_init(void) { return -ENOSYS; }
|
||||
@@ -634,6 +652,7 @@ static inline int rmp_make_shared(u64 pfn, enum pg_level level) { return -ENODEV
|
||||
static inline void snp_leak_pages(u64 pfn, unsigned int npages) {}
|
||||
static inline void kdump_sev_callback(void) { }
|
||||
static inline void snp_fixup_e820_tables(void) {}
|
||||
static inline void sev_evict_cache(void *va, int npages) {}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -386,7 +386,6 @@ static bool __init should_mitigate_vuln(unsigned int bug)
|
||||
|
||||
case X86_BUG_SPECTRE_V2:
|
||||
case X86_BUG_RETBLEED:
|
||||
case X86_BUG_SRSO:
|
||||
case X86_BUG_L1TF:
|
||||
case X86_BUG_ITS:
|
||||
return cpu_attack_vector_mitigated(CPU_MITIGATE_USER_KERNEL) ||
|
||||
@@ -3184,8 +3183,18 @@ static void __init srso_select_mitigation(void)
|
||||
}
|
||||
|
||||
if (srso_mitigation == SRSO_MITIGATION_AUTO) {
|
||||
if (should_mitigate_vuln(X86_BUG_SRSO)) {
|
||||
/*
|
||||
* Use safe-RET if user->kernel or guest->host protection is
|
||||
* required. Otherwise the 'microcode' mitigation is sufficient
|
||||
* to protect the user->user and guest->guest vectors.
|
||||
*/
|
||||
if (cpu_attack_vector_mitigated(CPU_MITIGATE_GUEST_HOST) ||
|
||||
(cpu_attack_vector_mitigated(CPU_MITIGATE_USER_KERNEL) &&
|
||||
!boot_cpu_has(X86_FEATURE_SRSO_USER_KERNEL_NO))) {
|
||||
srso_mitigation = SRSO_MITIGATION_SAFE_RET;
|
||||
} else if (cpu_attack_vector_mitigated(CPU_MITIGATE_USER_USER) ||
|
||||
cpu_attack_vector_mitigated(CPU_MITIGATE_GUEST_GUEST)) {
|
||||
srso_mitigation = SRSO_MITIGATION_MICROCODE;
|
||||
} else {
|
||||
srso_mitigation = SRSO_MITIGATION_NONE;
|
||||
return;
|
||||
|
||||
@@ -48,6 +48,7 @@ static const struct cpuid_bit cpuid_bits[] = {
|
||||
{ X86_FEATURE_PROC_FEEDBACK, CPUID_EDX, 11, 0x80000007, 0 },
|
||||
{ X86_FEATURE_AMD_FAST_CPPC, CPUID_EDX, 15, 0x80000007, 0 },
|
||||
{ X86_FEATURE_MBA, CPUID_EBX, 6, 0x80000008, 0 },
|
||||
{ X86_FEATURE_COHERENCY_SFW_NO, CPUID_EBX, 31, 0x8000001f, 0 },
|
||||
{ X86_FEATURE_SMBA, CPUID_EBX, 2, 0x80000020, 0 },
|
||||
{ X86_FEATURE_BMEC, CPUID_EBX, 3, 0x80000020, 0 },
|
||||
{ X86_FEATURE_TSA_SQ_NO, CPUID_ECX, 1, 0x80000021, 0 },
|
||||
|
||||
@@ -1881,19 +1881,20 @@ long fpu_xstate_prctl(int option, unsigned long arg2)
|
||||
#ifdef CONFIG_PROC_PID_ARCH_STATUS
|
||||
/*
|
||||
* Report the amount of time elapsed in millisecond since last AVX512
|
||||
* use in the task.
|
||||
* use in the task. Report -1 if no AVX-512 usage.
|
||||
*/
|
||||
static void avx512_status(struct seq_file *m, struct task_struct *task)
|
||||
{
|
||||
unsigned long timestamp = READ_ONCE(x86_task_fpu(task)->avx512_timestamp);
|
||||
long delta;
|
||||
unsigned long timestamp;
|
||||
long delta = -1;
|
||||
|
||||
if (!timestamp) {
|
||||
/*
|
||||
* Report -1 if no AVX512 usage
|
||||
*/
|
||||
delta = -1;
|
||||
} else {
|
||||
/* AVX-512 usage is not tracked for kernel threads. Don't report anything. */
|
||||
if (task->flags & (PF_KTHREAD | PF_USER_WORKER))
|
||||
return;
|
||||
|
||||
timestamp = READ_ONCE(x86_task_fpu(task)->avx512_timestamp);
|
||||
|
||||
if (timestamp) {
|
||||
delta = (long)(jiffies - timestamp);
|
||||
/*
|
||||
* Cap to LONG_MAX if time difference > LONG_MAX
|
||||
|
||||
@@ -5847,8 +5847,7 @@ static struct bfq_queue *bfq_get_queue(struct bfq_data *bfqd,
|
||||
goto out;
|
||||
}
|
||||
|
||||
bfqq = kmem_cache_alloc_node(bfq_pool,
|
||||
GFP_NOWAIT | __GFP_ZERO | __GFP_NOWARN,
|
||||
bfqq = kmem_cache_alloc_node(bfq_pool, GFP_NOWAIT | __GFP_ZERO,
|
||||
bfqd->queue->node);
|
||||
|
||||
if (bfqq) {
|
||||
|
||||
@@ -394,7 +394,7 @@ static struct blkcg_gq *blkg_create(struct blkcg *blkcg, struct gendisk *disk,
|
||||
|
||||
/* allocate */
|
||||
if (!new_blkg) {
|
||||
new_blkg = blkg_alloc(blkcg, disk, GFP_NOWAIT | __GFP_NOWARN);
|
||||
new_blkg = blkg_alloc(blkcg, disk, GFP_NOWAIT);
|
||||
if (unlikely(!new_blkg)) {
|
||||
ret = -ENOMEM;
|
||||
goto err_put_css;
|
||||
@@ -1467,7 +1467,7 @@ blkcg_css_alloc(struct cgroup_subsys_state *parent_css)
|
||||
|
||||
spin_lock_init(&blkcg->lock);
|
||||
refcount_set(&blkcg->online_pin, 1);
|
||||
INIT_RADIX_TREE(&blkcg->blkg_tree, GFP_NOWAIT | __GFP_NOWARN);
|
||||
INIT_RADIX_TREE(&blkcg->blkg_tree, GFP_NOWAIT);
|
||||
INIT_HLIST_HEAD(&blkcg->blkg_list);
|
||||
#ifdef CONFIG_CGROUP_WRITEBACK
|
||||
INIT_LIST_HEAD(&blkcg->cgwb_list);
|
||||
@@ -1630,7 +1630,7 @@ retry:
|
||||
pd_prealloc = NULL;
|
||||
} else {
|
||||
pd = pol->pd_alloc_fn(disk, blkg->blkcg,
|
||||
GFP_NOWAIT | __GFP_NOWARN);
|
||||
GFP_NOWAIT);
|
||||
}
|
||||
|
||||
if (!pd) {
|
||||
|
||||
@@ -847,7 +847,7 @@ static void blk_queue_release(struct kobject *kobj)
|
||||
/* nothing to do here, all data is associated with the parent gendisk */
|
||||
}
|
||||
|
||||
static const struct kobj_type blk_queue_ktype = {
|
||||
const struct kobj_type blk_queue_ktype = {
|
||||
.default_groups = blk_queue_attr_groups,
|
||||
.sysfs_ops = &queue_sysfs_ops,
|
||||
.release = blk_queue_release,
|
||||
@@ -875,15 +875,14 @@ int blk_register_queue(struct gendisk *disk)
|
||||
struct request_queue *q = disk->queue;
|
||||
int ret;
|
||||
|
||||
kobject_init(&disk->queue_kobj, &blk_queue_ktype);
|
||||
ret = kobject_add(&disk->queue_kobj, &disk_to_dev(disk)->kobj, "queue");
|
||||
if (ret < 0)
|
||||
goto out_put_queue_kobj;
|
||||
return ret;
|
||||
|
||||
if (queue_is_mq(q)) {
|
||||
ret = blk_mq_sysfs_register(disk);
|
||||
if (ret)
|
||||
goto out_put_queue_kobj;
|
||||
goto out_del_queue_kobj;
|
||||
}
|
||||
mutex_lock(&q->sysfs_lock);
|
||||
|
||||
@@ -903,9 +902,9 @@ int blk_register_queue(struct gendisk *disk)
|
||||
|
||||
if (queue_is_mq(q))
|
||||
elevator_set_default(q);
|
||||
wbt_enable_default(disk);
|
||||
|
||||
blk_queue_flag_set(QUEUE_FLAG_REGISTERED, q);
|
||||
wbt_enable_default(disk);
|
||||
|
||||
/* Now everything is ready and send out KOBJ_ADD uevent */
|
||||
kobject_uevent(&disk->queue_kobj, KOBJ_ADD);
|
||||
@@ -934,8 +933,8 @@ out_debugfs_remove:
|
||||
mutex_unlock(&q->sysfs_lock);
|
||||
if (queue_is_mq(q))
|
||||
blk_mq_sysfs_unregister(disk);
|
||||
out_put_queue_kobj:
|
||||
kobject_put(&disk->queue_kobj);
|
||||
out_del_queue_kobj:
|
||||
kobject_del(&disk->queue_kobj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -986,5 +985,4 @@ void blk_unregister_queue(struct gendisk *disk)
|
||||
elevator_set_none(q);
|
||||
|
||||
blk_debugfs_remove(disk);
|
||||
kobject_put(&disk->queue_kobj);
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ struct rq_wb {
|
||||
u64 sync_issue;
|
||||
void *sync_cookie;
|
||||
|
||||
unsigned long last_issue; /* last non-throttled issue */
|
||||
unsigned long last_comp; /* last non-throttled comp */
|
||||
unsigned long last_issue; /* issue time of last read rq */
|
||||
unsigned long last_comp; /* completion time of last read rq */
|
||||
unsigned long min_lat_nsec;
|
||||
struct rq_qos rqos;
|
||||
struct rq_wait rq_wait[WBT_NUM_RWQ];
|
||||
@@ -248,13 +248,14 @@ static void wbt_done(struct rq_qos *rqos, struct request *rq)
|
||||
struct rq_wb *rwb = RQWB(rqos);
|
||||
|
||||
if (!wbt_is_tracked(rq)) {
|
||||
if (rwb->sync_cookie == rq) {
|
||||
rwb->sync_issue = 0;
|
||||
rwb->sync_cookie = NULL;
|
||||
}
|
||||
if (wbt_is_read(rq)) {
|
||||
if (rwb->sync_cookie == rq) {
|
||||
rwb->sync_issue = 0;
|
||||
rwb->sync_cookie = NULL;
|
||||
}
|
||||
|
||||
if (wbt_is_read(rq))
|
||||
wb_timestamp(rwb, &rwb->last_comp);
|
||||
}
|
||||
} else {
|
||||
WARN_ON_ONCE(rq == rwb->sync_cookie);
|
||||
__wbt_done(rqos, wbt_flags(rq));
|
||||
|
||||
@@ -29,6 +29,7 @@ struct elevator_tags;
|
||||
/* Max future timer expiry for timeouts */
|
||||
#define BLK_MAX_TIMEOUT (5 * HZ)
|
||||
|
||||
extern const struct kobj_type blk_queue_ktype;
|
||||
extern struct dentry *blk_debugfs_root;
|
||||
|
||||
struct blk_flush_queue {
|
||||
|
||||
@@ -1303,6 +1303,7 @@ static void disk_release(struct device *dev)
|
||||
disk_free_zone_resources(disk);
|
||||
xa_destroy(&disk->part_tbl);
|
||||
|
||||
kobject_put(&disk->queue_kobj);
|
||||
disk->queue->disk = NULL;
|
||||
blk_put_queue(disk->queue);
|
||||
|
||||
@@ -1486,6 +1487,7 @@ struct gendisk *__alloc_disk_node(struct request_queue *q, int node_id,
|
||||
INIT_LIST_HEAD(&disk->slave_bdevs);
|
||||
#endif
|
||||
mutex_init(&disk->rqos_state_mutex);
|
||||
kobject_init(&disk->queue_kobj, &blk_queue_ktype);
|
||||
return disk;
|
||||
|
||||
out_erase_part0:
|
||||
|
||||
@@ -28,5 +28,6 @@ source "drivers/accel/amdxdna/Kconfig"
|
||||
source "drivers/accel/habanalabs/Kconfig"
|
||||
source "drivers/accel/ivpu/Kconfig"
|
||||
source "drivers/accel/qaic/Kconfig"
|
||||
source "drivers/accel/rocket/Kconfig"
|
||||
|
||||
endif
|
||||
|
||||
@@ -4,3 +4,4 @@ obj-$(CONFIG_DRM_ACCEL_AMDXDNA) += amdxdna/
|
||||
obj-$(CONFIG_DRM_ACCEL_HABANALABS) += habanalabs/
|
||||
obj-$(CONFIG_DRM_ACCEL_IVPU) += ivpu/
|
||||
obj-$(CONFIG_DRM_ACCEL_QAIC) += qaic/
|
||||
obj-$(CONFIG_DRM_ACCEL_ROCKET) += rocket/
|
||||
@@ -15,6 +15,7 @@ amdxdna-y := \
|
||||
amdxdna_mailbox_helper.o \
|
||||
amdxdna_pci_drv.o \
|
||||
amdxdna_sysfs.o \
|
||||
amdxdna_ubuf.o \
|
||||
npu1_regs.o \
|
||||
npu2_regs.o \
|
||||
npu4_regs.o \
|
||||
|
||||
@@ -46,6 +46,17 @@ static void aie2_job_put(struct amdxdna_sched_job *job)
|
||||
kref_put(&job->refcnt, aie2_job_release);
|
||||
}
|
||||
|
||||
static void aie2_hwctx_status_shift_stop(struct amdxdna_hwctx *hwctx)
|
||||
{
|
||||
hwctx->old_status = hwctx->status;
|
||||
hwctx->status = HWCTX_STAT_STOP;
|
||||
}
|
||||
|
||||
static void aie2_hwctx_status_restore(struct amdxdna_hwctx *hwctx)
|
||||
{
|
||||
hwctx->status = hwctx->old_status;
|
||||
}
|
||||
|
||||
/* The bad_job is used in aie2_sched_job_timedout, otherwise, set it to NULL */
|
||||
static void aie2_hwctx_stop(struct amdxdna_dev *xdna, struct amdxdna_hwctx *hwctx,
|
||||
struct drm_sched_job *bad_job)
|
||||
@@ -89,25 +100,6 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void aie2_restart_ctx(struct amdxdna_client *client)
|
||||
{
|
||||
struct amdxdna_dev *xdna = client->xdna;
|
||||
struct amdxdna_hwctx *hwctx;
|
||||
unsigned long hwctx_id;
|
||||
|
||||
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
||||
mutex_lock(&client->hwctx_lock);
|
||||
amdxdna_for_each_hwctx(client, hwctx_id, hwctx) {
|
||||
if (hwctx->status != HWCTX_STAT_STOP)
|
||||
continue;
|
||||
|
||||
hwctx->status = hwctx->old_status;
|
||||
XDNA_DBG(xdna, "Resetting %s", hwctx->name);
|
||||
aie2_hwctx_restart(xdna, hwctx);
|
||||
}
|
||||
mutex_unlock(&client->hwctx_lock);
|
||||
}
|
||||
|
||||
static struct dma_fence *aie2_cmd_get_out_fence(struct amdxdna_hwctx *hwctx, u64 seq)
|
||||
{
|
||||
struct dma_fence *fence, *out_fence = NULL;
|
||||
@@ -141,34 +133,49 @@ static void aie2_hwctx_wait_for_idle(struct amdxdna_hwctx *hwctx)
|
||||
dma_fence_put(fence);
|
||||
}
|
||||
|
||||
void aie2_hwctx_suspend(struct amdxdna_hwctx *hwctx)
|
||||
static int aie2_hwctx_suspend_cb(struct amdxdna_hwctx *hwctx, void *arg)
|
||||
{
|
||||
struct amdxdna_dev *xdna = hwctx->client->xdna;
|
||||
|
||||
aie2_hwctx_wait_for_idle(hwctx);
|
||||
aie2_hwctx_stop(xdna, hwctx, NULL);
|
||||
aie2_hwctx_status_shift_stop(hwctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void aie2_hwctx_suspend(struct amdxdna_client *client)
|
||||
{
|
||||
struct amdxdna_dev *xdna = client->xdna;
|
||||
|
||||
/*
|
||||
* Command timeout is unlikely. But if it happens, it doesn't
|
||||
* break the system. aie2_hwctx_stop() will destroy mailbox
|
||||
* and abort all commands.
|
||||
*/
|
||||
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
||||
aie2_hwctx_wait_for_idle(hwctx);
|
||||
aie2_hwctx_stop(xdna, hwctx, NULL);
|
||||
hwctx->old_status = hwctx->status;
|
||||
hwctx->status = HWCTX_STAT_STOP;
|
||||
amdxdna_hwctx_walk(client, NULL, aie2_hwctx_suspend_cb);
|
||||
}
|
||||
|
||||
void aie2_hwctx_resume(struct amdxdna_hwctx *hwctx)
|
||||
static int aie2_hwctx_resume_cb(struct amdxdna_hwctx *hwctx, void *arg)
|
||||
{
|
||||
struct amdxdna_dev *xdna = hwctx->client->xdna;
|
||||
|
||||
aie2_hwctx_status_restore(hwctx);
|
||||
return aie2_hwctx_restart(xdna, hwctx);
|
||||
}
|
||||
|
||||
int aie2_hwctx_resume(struct amdxdna_client *client)
|
||||
{
|
||||
struct amdxdna_dev *xdna = client->xdna;
|
||||
|
||||
/*
|
||||
* The resume path cannot guarantee that mailbox channel can be
|
||||
* regenerated. If this happen, when submit message to this
|
||||
* mailbox channel, error will return.
|
||||
*/
|
||||
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
||||
hwctx->status = hwctx->old_status;
|
||||
aie2_hwctx_restart(xdna, hwctx);
|
||||
return amdxdna_hwctx_walk(client, NULL, aie2_hwctx_resume_cb);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -290,18 +290,25 @@ int aie2_map_host_buf(struct amdxdna_dev_hdl *ndev, u32 context_id, u64 addr, u6
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amdxdna_hwctx_col_map(struct amdxdna_hwctx *hwctx, void *arg)
|
||||
{
|
||||
u32 *bitmap = arg;
|
||||
|
||||
*bitmap |= GENMASK(hwctx->start_col + hwctx->num_col - 1, hwctx->start_col);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int aie2_query_status(struct amdxdna_dev_hdl *ndev, char __user *buf,
|
||||
u32 size, u32 *cols_filled)
|
||||
{
|
||||
DECLARE_AIE2_MSG(aie_column_info, MSG_OP_QUERY_COL_STATUS);
|
||||
struct amdxdna_dev *xdna = ndev->xdna;
|
||||
struct amdxdna_client *client;
|
||||
struct amdxdna_hwctx *hwctx;
|
||||
unsigned long hwctx_id;
|
||||
dma_addr_t dma_addr;
|
||||
u32 aie_bitmap = 0;
|
||||
u8 *buff_addr;
|
||||
int ret, idx;
|
||||
int ret;
|
||||
|
||||
buff_addr = dma_alloc_noncoherent(xdna->ddev.dev, size, &dma_addr,
|
||||
DMA_FROM_DEVICE, GFP_KERNEL);
|
||||
@@ -309,12 +316,8 @@ int aie2_query_status(struct amdxdna_dev_hdl *ndev, char __user *buf,
|
||||
return -ENOMEM;
|
||||
|
||||
/* Go through each hardware context and mark the AIE columns that are active */
|
||||
list_for_each_entry(client, &xdna->client_list, node) {
|
||||
idx = srcu_read_lock(&client->hwctx_srcu);
|
||||
amdxdna_for_each_hwctx(client, hwctx_id, hwctx)
|
||||
aie_bitmap |= amdxdna_hwctx_col_map(hwctx);
|
||||
srcu_read_unlock(&client->hwctx_srcu, idx);
|
||||
}
|
||||
list_for_each_entry(client, &xdna->client_list, node)
|
||||
amdxdna_hwctx_walk(client, &aie_bitmap, amdxdna_hwctx_col_map);
|
||||
|
||||
*cols_filled = 0;
|
||||
req.dump_buff_addr = dma_addr;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <drm/drm_managed.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/gpu_scheduler.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/iommu.h>
|
||||
@@ -440,6 +441,40 @@ disable_dev:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aie2_hw_suspend(struct amdxdna_dev *xdna)
|
||||
{
|
||||
struct amdxdna_client *client;
|
||||
|
||||
guard(mutex)(&xdna->dev_lock);
|
||||
list_for_each_entry(client, &xdna->client_list, node)
|
||||
aie2_hwctx_suspend(client);
|
||||
|
||||
aie2_hw_stop(xdna);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aie2_hw_resume(struct amdxdna_dev *xdna)
|
||||
{
|
||||
struct amdxdna_client *client;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&xdna->dev_lock);
|
||||
ret = aie2_hw_start(xdna);
|
||||
if (ret) {
|
||||
XDNA_ERR(xdna, "Start hardware failed, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
list_for_each_entry(client, &xdna->client_list, node) {
|
||||
ret = aie2_hwctx_resume(client);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aie2_init(struct amdxdna_dev *xdna)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(xdna->ddev.dev);
|
||||
@@ -520,14 +555,14 @@ static int aie2_init(struct amdxdna_dev *xdna)
|
||||
if (!ndev->psp_hdl) {
|
||||
XDNA_ERR(xdna, "failed to create psp");
|
||||
ret = -ENOMEM;
|
||||
goto free_irq;
|
||||
goto release_fw;
|
||||
}
|
||||
xdna->dev_handle = ndev;
|
||||
|
||||
ret = aie2_hw_start(xdna);
|
||||
if (ret) {
|
||||
XDNA_ERR(xdna, "start npu failed, ret %d", ret);
|
||||
goto free_irq;
|
||||
goto release_fw;
|
||||
}
|
||||
|
||||
ret = aie2_mgmt_fw_query(ndev);
|
||||
@@ -578,8 +613,6 @@ async_event_free:
|
||||
aie2_error_async_events_free(ndev);
|
||||
stop_hw:
|
||||
aie2_hw_stop(xdna);
|
||||
free_irq:
|
||||
pci_free_irq_vectors(pdev);
|
||||
release_fw:
|
||||
release_firmware(fw);
|
||||
|
||||
@@ -588,12 +621,10 @@ release_fw:
|
||||
|
||||
static void aie2_fini(struct amdxdna_dev *xdna)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(xdna->ddev.dev);
|
||||
struct amdxdna_dev_hdl *ndev = xdna->dev_handle;
|
||||
|
||||
aie2_hw_stop(xdna);
|
||||
aie2_error_async_events_free(ndev);
|
||||
pci_free_irq_vectors(pdev);
|
||||
}
|
||||
|
||||
static int aie2_get_aie_status(struct amdxdna_client *client,
|
||||
@@ -752,65 +783,56 @@ static int aie2_get_clock_metadata(struct amdxdna_client *client,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aie2_get_hwctx_status(struct amdxdna_client *client,
|
||||
struct amdxdna_drm_get_info *args)
|
||||
static int aie2_hwctx_status_cb(struct amdxdna_hwctx *hwctx, void *arg)
|
||||
{
|
||||
struct amdxdna_drm_query_hwctx __user *buf;
|
||||
struct amdxdna_dev *xdna = client->xdna;
|
||||
struct amdxdna_drm_query_hwctx *tmp;
|
||||
struct amdxdna_client *tmp_client;
|
||||
struct amdxdna_hwctx *hwctx;
|
||||
unsigned long hwctx_id;
|
||||
bool overflow = false;
|
||||
u32 req_bytes = 0;
|
||||
u32 hw_i = 0;
|
||||
int ret = 0;
|
||||
int idx;
|
||||
struct amdxdna_drm_query_hwctx __user *buf, *tmp __free(kfree) = NULL;
|
||||
struct amdxdna_drm_get_info *get_info_args = arg;
|
||||
|
||||
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
||||
if (get_info_args->buffer_size < sizeof(*tmp))
|
||||
return -EINVAL;
|
||||
|
||||
tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
|
||||
if (!tmp)
|
||||
return -ENOMEM;
|
||||
|
||||
buf = u64_to_user_ptr(args->buffer);
|
||||
tmp->pid = hwctx->client->pid;
|
||||
tmp->context_id = hwctx->id;
|
||||
tmp->start_col = hwctx->start_col;
|
||||
tmp->num_col = hwctx->num_col;
|
||||
tmp->command_submissions = hwctx->priv->seq;
|
||||
tmp->command_completions = hwctx->priv->completed;
|
||||
|
||||
buf = u64_to_user_ptr(get_info_args->buffer);
|
||||
|
||||
if (copy_to_user(buf, tmp, sizeof(*tmp)))
|
||||
return -EFAULT;
|
||||
|
||||
get_info_args->buffer += sizeof(*tmp);
|
||||
get_info_args->buffer_size -= sizeof(*tmp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aie2_get_hwctx_status(struct amdxdna_client *client,
|
||||
struct amdxdna_drm_get_info *args)
|
||||
{
|
||||
struct amdxdna_dev *xdna = client->xdna;
|
||||
struct amdxdna_drm_get_info info_args;
|
||||
struct amdxdna_client *tmp_client;
|
||||
int ret;
|
||||
|
||||
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
||||
|
||||
info_args.buffer = args->buffer;
|
||||
info_args.buffer_size = args->buffer_size;
|
||||
|
||||
list_for_each_entry(tmp_client, &xdna->client_list, node) {
|
||||
idx = srcu_read_lock(&tmp_client->hwctx_srcu);
|
||||
amdxdna_for_each_hwctx(tmp_client, hwctx_id, hwctx) {
|
||||
req_bytes += sizeof(*tmp);
|
||||
if (args->buffer_size < req_bytes) {
|
||||
/* Continue iterating to get the required size */
|
||||
overflow = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(tmp, 0, sizeof(*tmp));
|
||||
tmp->pid = tmp_client->pid;
|
||||
tmp->context_id = hwctx->id;
|
||||
tmp->start_col = hwctx->start_col;
|
||||
tmp->num_col = hwctx->num_col;
|
||||
tmp->command_submissions = hwctx->priv->seq;
|
||||
tmp->command_completions = hwctx->priv->completed;
|
||||
|
||||
if (copy_to_user(&buf[hw_i], tmp, sizeof(*tmp))) {
|
||||
ret = -EFAULT;
|
||||
srcu_read_unlock(&tmp_client->hwctx_srcu, idx);
|
||||
goto out;
|
||||
}
|
||||
hw_i++;
|
||||
}
|
||||
srcu_read_unlock(&tmp_client->hwctx_srcu, idx);
|
||||
ret = amdxdna_hwctx_walk(tmp_client, &info_args, aie2_hwctx_status_cb);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
if (overflow) {
|
||||
XDNA_ERR(xdna, "Invalid buffer size. Given: %u Need: %u.",
|
||||
args->buffer_size, req_bytes);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(tmp);
|
||||
args->buffer_size = req_bytes;
|
||||
args->buffer_size = (u32)(info_args.buffer - args->buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -905,8 +927,8 @@ static int aie2_set_state(struct amdxdna_client *client,
|
||||
const struct amdxdna_dev_ops aie2_ops = {
|
||||
.init = aie2_init,
|
||||
.fini = aie2_fini,
|
||||
.resume = aie2_hw_start,
|
||||
.suspend = aie2_hw_stop,
|
||||
.resume = aie2_hw_resume,
|
||||
.suspend = aie2_hw_suspend,
|
||||
.get_aie_info = aie2_get_info,
|
||||
.set_aie_state = aie2_set_state,
|
||||
.hwctx_init = aie2_hwctx_init,
|
||||
@@ -914,6 +936,4 @@ const struct amdxdna_dev_ops aie2_ops = {
|
||||
.hwctx_config = aie2_hwctx_config,
|
||||
.cmd_submit = aie2_cmd_submit,
|
||||
.hmm_invalidate = aie2_hmm_invalidate,
|
||||
.hwctx_suspend = aie2_hwctx_suspend,
|
||||
.hwctx_resume = aie2_hwctx_resume,
|
||||
};
|
||||
|
||||
@@ -288,10 +288,9 @@ int aie2_sync_bo(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job,
|
||||
int aie2_hwctx_init(struct amdxdna_hwctx *hwctx);
|
||||
void aie2_hwctx_fini(struct amdxdna_hwctx *hwctx);
|
||||
int aie2_hwctx_config(struct amdxdna_hwctx *hwctx, u32 type, u64 value, void *buf, u32 size);
|
||||
void aie2_hwctx_suspend(struct amdxdna_hwctx *hwctx);
|
||||
void aie2_hwctx_resume(struct amdxdna_hwctx *hwctx);
|
||||
void aie2_hwctx_suspend(struct amdxdna_client *client);
|
||||
int aie2_hwctx_resume(struct amdxdna_client *client);
|
||||
int aie2_cmd_submit(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job, u64 *seq);
|
||||
void aie2_hmm_invalidate(struct amdxdna_gem_obj *abo, unsigned long cur_seq);
|
||||
void aie2_restart_ctx(struct amdxdna_client *client);
|
||||
|
||||
#endif /* _AIE2_PCI_H_ */
|
||||
|
||||
@@ -60,32 +60,6 @@ static struct dma_fence *amdxdna_fence_create(struct amdxdna_hwctx *hwctx)
|
||||
return &fence->base;
|
||||
}
|
||||
|
||||
void amdxdna_hwctx_suspend(struct amdxdna_client *client)
|
||||
{
|
||||
struct amdxdna_dev *xdna = client->xdna;
|
||||
struct amdxdna_hwctx *hwctx;
|
||||
unsigned long hwctx_id;
|
||||
|
||||
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
||||
mutex_lock(&client->hwctx_lock);
|
||||
amdxdna_for_each_hwctx(client, hwctx_id, hwctx)
|
||||
xdna->dev_info->ops->hwctx_suspend(hwctx);
|
||||
mutex_unlock(&client->hwctx_lock);
|
||||
}
|
||||
|
||||
void amdxdna_hwctx_resume(struct amdxdna_client *client)
|
||||
{
|
||||
struct amdxdna_dev *xdna = client->xdna;
|
||||
struct amdxdna_hwctx *hwctx;
|
||||
unsigned long hwctx_id;
|
||||
|
||||
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
||||
mutex_lock(&client->hwctx_lock);
|
||||
amdxdna_for_each_hwctx(client, hwctx_id, hwctx)
|
||||
xdna->dev_info->ops->hwctx_resume(hwctx);
|
||||
mutex_unlock(&client->hwctx_lock);
|
||||
}
|
||||
|
||||
static void amdxdna_hwctx_destroy_rcu(struct amdxdna_hwctx *hwctx,
|
||||
struct srcu_struct *ss)
|
||||
{
|
||||
@@ -94,14 +68,30 @@ static void amdxdna_hwctx_destroy_rcu(struct amdxdna_hwctx *hwctx,
|
||||
synchronize_srcu(ss);
|
||||
|
||||
/* At this point, user is not able to submit new commands */
|
||||
mutex_lock(&xdna->dev_lock);
|
||||
xdna->dev_info->ops->hwctx_fini(hwctx);
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
|
||||
kfree(hwctx->name);
|
||||
kfree(hwctx);
|
||||
}
|
||||
|
||||
int amdxdna_hwctx_walk(struct amdxdna_client *client, void *arg,
|
||||
int (*walk)(struct amdxdna_hwctx *hwctx, void *arg))
|
||||
{
|
||||
struct amdxdna_hwctx *hwctx;
|
||||
unsigned long hwctx_id;
|
||||
int ret = 0, idx;
|
||||
|
||||
idx = srcu_read_lock(&client->hwctx_srcu);
|
||||
amdxdna_for_each_hwctx(client, hwctx_id, hwctx) {
|
||||
ret = walk(hwctx, arg);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
srcu_read_unlock(&client->hwctx_srcu, idx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *amdxdna_cmd_get_payload(struct amdxdna_gem_obj *abo, u32 *size)
|
||||
{
|
||||
struct amdxdna_cmd *cmd = abo->mem.kva;
|
||||
@@ -152,16 +142,12 @@ void amdxdna_hwctx_remove_all(struct amdxdna_client *client)
|
||||
struct amdxdna_hwctx *hwctx;
|
||||
unsigned long hwctx_id;
|
||||
|
||||
mutex_lock(&client->hwctx_lock);
|
||||
amdxdna_for_each_hwctx(client, hwctx_id, hwctx) {
|
||||
XDNA_DBG(client->xdna, "PID %d close HW context %d",
|
||||
client->pid, hwctx->id);
|
||||
xa_erase(&client->hwctx_xa, hwctx->id);
|
||||
mutex_unlock(&client->hwctx_lock);
|
||||
amdxdna_hwctx_destroy_rcu(hwctx, &client->hwctx_srcu);
|
||||
mutex_lock(&client->hwctx_lock);
|
||||
}
|
||||
mutex_unlock(&client->hwctx_lock);
|
||||
}
|
||||
|
||||
int amdxdna_drm_create_hwctx_ioctl(struct drm_device *dev, void *data, struct drm_file *filp)
|
||||
@@ -251,6 +237,7 @@ int amdxdna_drm_destroy_hwctx_ioctl(struct drm_device *dev, void *data, struct d
|
||||
if (!drm_dev_enter(dev, &idx))
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&xdna->dev_lock);
|
||||
hwctx = xa_erase(&client->hwctx_xa, args->handle);
|
||||
if (!hwctx) {
|
||||
ret = -EINVAL;
|
||||
@@ -267,6 +254,7 @@ int amdxdna_drm_destroy_hwctx_ioctl(struct drm_device *dev, void *data, struct d
|
||||
|
||||
XDNA_DBG(xdna, "PID %d destroyed HW context %d", client->pid, args->handle);
|
||||
out:
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
drm_dev_exit(idx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -139,16 +139,10 @@ amdxdna_cmd_get_state(struct amdxdna_gem_obj *abo)
|
||||
void *amdxdna_cmd_get_payload(struct amdxdna_gem_obj *abo, u32 *size);
|
||||
int amdxdna_cmd_get_cu_idx(struct amdxdna_gem_obj *abo);
|
||||
|
||||
static inline u32 amdxdna_hwctx_col_map(struct amdxdna_hwctx *hwctx)
|
||||
{
|
||||
return GENMASK(hwctx->start_col + hwctx->num_col - 1,
|
||||
hwctx->start_col);
|
||||
}
|
||||
|
||||
void amdxdna_sched_job_cleanup(struct amdxdna_sched_job *job);
|
||||
void amdxdna_hwctx_remove_all(struct amdxdna_client *client);
|
||||
void amdxdna_hwctx_suspend(struct amdxdna_client *client);
|
||||
void amdxdna_hwctx_resume(struct amdxdna_client *client);
|
||||
int amdxdna_hwctx_walk(struct amdxdna_client *client, void *arg,
|
||||
int (*walk)(struct amdxdna_hwctx *hwctx, void *arg));
|
||||
|
||||
int amdxdna_cmd_submit(struct amdxdna_client *client,
|
||||
u32 cmd_bo_hdls, u32 *arg_bo_hdls, u32 arg_bo_cnt,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "amdxdna_ctx.h"
|
||||
#include "amdxdna_gem.h"
|
||||
#include "amdxdna_pci_drv.h"
|
||||
#include "amdxdna_ubuf.h"
|
||||
|
||||
#define XDNA_MAX_CMD_BO_SIZE SZ_32K
|
||||
|
||||
@@ -296,7 +297,7 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo,
|
||||
|
||||
vma->vm_private_data = NULL;
|
||||
vma->vm_ops = NULL;
|
||||
ret = dma_buf_mmap(to_gobj(abo)->dma_buf, vma, 0);
|
||||
ret = dma_buf_mmap(abo->dma_buf, vma, 0);
|
||||
if (ret) {
|
||||
XDNA_ERR(xdna, "Failed to mmap dma buf %d", ret);
|
||||
return ret;
|
||||
@@ -391,10 +392,47 @@ static const struct dma_buf_ops amdxdna_dmabuf_ops = {
|
||||
.vunmap = drm_gem_dmabuf_vunmap,
|
||||
};
|
||||
|
||||
static int amdxdna_gem_obj_vmap(struct drm_gem_object *obj, struct iosys_map *map)
|
||||
{
|
||||
struct amdxdna_gem_obj *abo = to_xdna_obj(obj);
|
||||
|
||||
iosys_map_clear(map);
|
||||
|
||||
dma_resv_assert_held(obj->resv);
|
||||
|
||||
if (is_import_bo(abo))
|
||||
dma_buf_vmap(abo->dma_buf, map);
|
||||
else
|
||||
drm_gem_shmem_object_vmap(obj, map);
|
||||
|
||||
if (!map->vaddr)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amdxdna_gem_obj_vunmap(struct drm_gem_object *obj, struct iosys_map *map)
|
||||
{
|
||||
struct amdxdna_gem_obj *abo = to_xdna_obj(obj);
|
||||
|
||||
dma_resv_assert_held(obj->resv);
|
||||
|
||||
if (is_import_bo(abo))
|
||||
dma_buf_vunmap(abo->dma_buf, map);
|
||||
else
|
||||
drm_gem_shmem_object_vunmap(obj, map);
|
||||
}
|
||||
|
||||
static struct dma_buf *amdxdna_gem_prime_export(struct drm_gem_object *gobj, int flags)
|
||||
{
|
||||
struct amdxdna_gem_obj *abo = to_xdna_obj(gobj);
|
||||
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
|
||||
|
||||
if (abo->dma_buf) {
|
||||
get_dma_buf(abo->dma_buf);
|
||||
return abo->dma_buf;
|
||||
}
|
||||
|
||||
exp_info.ops = &amdxdna_dmabuf_ops;
|
||||
exp_info.size = gobj->size;
|
||||
exp_info.flags = flags;
|
||||
@@ -451,8 +489,8 @@ static const struct drm_gem_object_funcs amdxdna_gem_shmem_funcs = {
|
||||
.pin = drm_gem_shmem_object_pin,
|
||||
.unpin = drm_gem_shmem_object_unpin,
|
||||
.get_sg_table = drm_gem_shmem_object_get_sg_table,
|
||||
.vmap = drm_gem_shmem_object_vmap,
|
||||
.vunmap = drm_gem_shmem_object_vunmap,
|
||||
.vmap = amdxdna_gem_obj_vmap,
|
||||
.vunmap = amdxdna_gem_obj_vunmap,
|
||||
.mmap = amdxdna_gem_obj_mmap,
|
||||
.vm_ops = &drm_gem_shmem_vm_ops,
|
||||
.export = amdxdna_gem_prime_export,
|
||||
@@ -494,6 +532,68 @@ amdxdna_gem_create_object_cb(struct drm_device *dev, size_t size)
|
||||
return to_gobj(abo);
|
||||
}
|
||||
|
||||
static struct amdxdna_gem_obj *
|
||||
amdxdna_gem_create_shmem_object(struct drm_device *dev, size_t size)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = drm_gem_shmem_create(dev, size);
|
||||
|
||||
if (IS_ERR(shmem))
|
||||
return ERR_CAST(shmem);
|
||||
|
||||
shmem->map_wc = false;
|
||||
return to_xdna_obj(&shmem->base);
|
||||
}
|
||||
|
||||
static struct amdxdna_gem_obj *
|
||||
amdxdna_gem_create_ubuf_object(struct drm_device *dev, struct amdxdna_drm_create_bo *args)
|
||||
{
|
||||
struct amdxdna_dev *xdna = to_xdna_dev(dev);
|
||||
enum amdxdna_ubuf_flag flags = 0;
|
||||
struct amdxdna_drm_va_tbl va_tbl;
|
||||
struct drm_gem_object *gobj;
|
||||
struct dma_buf *dma_buf;
|
||||
|
||||
if (copy_from_user(&va_tbl, u64_to_user_ptr(args->vaddr), sizeof(va_tbl))) {
|
||||
XDNA_DBG(xdna, "Access va table failed");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
if (va_tbl.num_entries) {
|
||||
if (args->type == AMDXDNA_BO_CMD)
|
||||
flags |= AMDXDNA_UBUF_FLAG_MAP_DMA;
|
||||
|
||||
dma_buf = amdxdna_get_ubuf(dev, flags, va_tbl.num_entries,
|
||||
u64_to_user_ptr(args->vaddr + sizeof(va_tbl)));
|
||||
} else {
|
||||
dma_buf = dma_buf_get(va_tbl.dmabuf_fd);
|
||||
}
|
||||
|
||||
if (IS_ERR(dma_buf))
|
||||
return ERR_CAST(dma_buf);
|
||||
|
||||
gobj = amdxdna_gem_prime_import(dev, dma_buf);
|
||||
if (IS_ERR(gobj)) {
|
||||
dma_buf_put(dma_buf);
|
||||
return ERR_CAST(gobj);
|
||||
}
|
||||
|
||||
dma_buf_put(dma_buf);
|
||||
|
||||
return to_xdna_obj(gobj);
|
||||
}
|
||||
|
||||
static struct amdxdna_gem_obj *
|
||||
amdxdna_gem_create_object(struct drm_device *dev,
|
||||
struct amdxdna_drm_create_bo *args)
|
||||
{
|
||||
size_t aligned_sz = PAGE_ALIGN(args->size);
|
||||
|
||||
if (args->vaddr)
|
||||
return amdxdna_gem_create_ubuf_object(dev, args);
|
||||
|
||||
return amdxdna_gem_create_shmem_object(dev, aligned_sz);
|
||||
}
|
||||
|
||||
struct drm_gem_object *
|
||||
amdxdna_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf)
|
||||
{
|
||||
@@ -545,16 +645,12 @@ amdxdna_drm_alloc_shmem(struct drm_device *dev,
|
||||
struct drm_file *filp)
|
||||
{
|
||||
struct amdxdna_client *client = filp->driver_priv;
|
||||
struct drm_gem_shmem_object *shmem;
|
||||
struct amdxdna_gem_obj *abo;
|
||||
|
||||
shmem = drm_gem_shmem_create(dev, args->size);
|
||||
if (IS_ERR(shmem))
|
||||
return ERR_CAST(shmem);
|
||||
abo = amdxdna_gem_create_object(dev, args);
|
||||
if (IS_ERR(abo))
|
||||
return ERR_CAST(abo);
|
||||
|
||||
shmem->map_wc = false;
|
||||
|
||||
abo = to_xdna_obj(&shmem->base);
|
||||
abo->client = client;
|
||||
abo->type = AMDXDNA_BO_SHMEM;
|
||||
|
||||
@@ -569,7 +665,6 @@ amdxdna_drm_create_dev_heap(struct drm_device *dev,
|
||||
struct amdxdna_client *client = filp->driver_priv;
|
||||
struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL);
|
||||
struct amdxdna_dev *xdna = to_xdna_dev(dev);
|
||||
struct drm_gem_shmem_object *shmem;
|
||||
struct amdxdna_gem_obj *abo;
|
||||
int ret;
|
||||
|
||||
@@ -586,14 +681,12 @@ amdxdna_drm_create_dev_heap(struct drm_device *dev,
|
||||
goto mm_unlock;
|
||||
}
|
||||
|
||||
shmem = drm_gem_shmem_create(dev, args->size);
|
||||
if (IS_ERR(shmem)) {
|
||||
ret = PTR_ERR(shmem);
|
||||
abo = amdxdna_gem_create_object(dev, args);
|
||||
if (IS_ERR(abo)) {
|
||||
ret = PTR_ERR(abo);
|
||||
goto mm_unlock;
|
||||
}
|
||||
|
||||
shmem->map_wc = false;
|
||||
abo = to_xdna_obj(&shmem->base);
|
||||
abo->type = AMDXDNA_BO_DEV_HEAP;
|
||||
abo->client = client;
|
||||
abo->mem.dev_addr = client->xdna->dev_info->dev_mem_base;
|
||||
@@ -657,7 +750,6 @@ amdxdna_drm_create_cmd_bo(struct drm_device *dev,
|
||||
{
|
||||
struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL);
|
||||
struct amdxdna_dev *xdna = to_xdna_dev(dev);
|
||||
struct drm_gem_shmem_object *shmem;
|
||||
struct amdxdna_gem_obj *abo;
|
||||
int ret;
|
||||
|
||||
@@ -671,12 +763,9 @@ amdxdna_drm_create_cmd_bo(struct drm_device *dev,
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
shmem = drm_gem_shmem_create(dev, args->size);
|
||||
if (IS_ERR(shmem))
|
||||
return ERR_CAST(shmem);
|
||||
|
||||
shmem->map_wc = false;
|
||||
abo = to_xdna_obj(&shmem->base);
|
||||
abo = amdxdna_gem_create_object(dev, args);
|
||||
if (IS_ERR(abo))
|
||||
return ERR_CAST(abo);
|
||||
|
||||
abo->type = AMDXDNA_BO_CMD;
|
||||
abo->client = filp->driver_priv;
|
||||
@@ -691,7 +780,7 @@ amdxdna_drm_create_cmd_bo(struct drm_device *dev,
|
||||
return abo;
|
||||
|
||||
release_obj:
|
||||
drm_gem_shmem_free(shmem);
|
||||
drm_gem_object_put(to_gobj(abo));
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
@@ -702,7 +791,7 @@ int amdxdna_drm_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_f
|
||||
struct amdxdna_gem_obj *abo;
|
||||
int ret;
|
||||
|
||||
if (args->flags || args->vaddr || !args->size)
|
||||
if (args->flags)
|
||||
return -EINVAL;
|
||||
|
||||
XDNA_DBG(xdna, "BO arg type %d vaddr 0x%llx size 0x%llx flags 0x%llx",
|
||||
|
||||
@@ -81,7 +81,6 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp)
|
||||
ret = -ENODEV;
|
||||
goto unbind_sva;
|
||||
}
|
||||
mutex_init(&client->hwctx_lock);
|
||||
init_srcu_struct(&client->hwctx_srcu);
|
||||
xa_init_flags(&client->hwctx_xa, XA_FLAGS_ALLOC);
|
||||
mutex_init(&client->mm_lock);
|
||||
@@ -116,7 +115,6 @@ static void amdxdna_drm_close(struct drm_device *ddev, struct drm_file *filp)
|
||||
|
||||
xa_destroy(&client->hwctx_xa);
|
||||
cleanup_srcu_struct(&client->hwctx_srcu);
|
||||
mutex_destroy(&client->hwctx_lock);
|
||||
mutex_destroy(&client->mm_lock);
|
||||
if (client->dev_heap)
|
||||
drm_gem_object_put(to_gobj(client->dev_heap));
|
||||
@@ -142,8 +140,8 @@ static int amdxdna_flush(struct file *f, fl_owner_t id)
|
||||
|
||||
mutex_lock(&xdna->dev_lock);
|
||||
list_del_init(&client->node);
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
amdxdna_hwctx_remove_all(client);
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
|
||||
drm_dev_exit(idx);
|
||||
return 0;
|
||||
@@ -330,11 +328,8 @@ static void amdxdna_remove(struct pci_dev *pdev)
|
||||
struct amdxdna_client, node);
|
||||
while (client) {
|
||||
list_del_init(&client->node);
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
|
||||
amdxdna_hwctx_remove_all(client);
|
||||
|
||||
mutex_lock(&xdna->dev_lock);
|
||||
client = list_first_entry_or_null(&xdna->client_list,
|
||||
struct amdxdna_client, node);
|
||||
}
|
||||
@@ -343,89 +338,29 @@ static void amdxdna_remove(struct pci_dev *pdev)
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
}
|
||||
|
||||
static int amdxdna_dev_suspend_nolock(struct amdxdna_dev *xdna)
|
||||
{
|
||||
if (xdna->dev_info->ops->suspend)
|
||||
xdna->dev_info->ops->suspend(xdna);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amdxdna_dev_resume_nolock(struct amdxdna_dev *xdna)
|
||||
{
|
||||
if (xdna->dev_info->ops->resume)
|
||||
return xdna->dev_info->ops->resume(xdna);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amdxdna_pmops_suspend(struct device *dev)
|
||||
{
|
||||
struct amdxdna_dev *xdna = pci_get_drvdata(to_pci_dev(dev));
|
||||
struct amdxdna_client *client;
|
||||
|
||||
mutex_lock(&xdna->dev_lock);
|
||||
list_for_each_entry(client, &xdna->client_list, node)
|
||||
amdxdna_hwctx_suspend(client);
|
||||
if (!xdna->dev_info->ops->suspend)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
amdxdna_dev_suspend_nolock(xdna);
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
|
||||
return 0;
|
||||
return xdna->dev_info->ops->suspend(xdna);
|
||||
}
|
||||
|
||||
static int amdxdna_pmops_resume(struct device *dev)
|
||||
{
|
||||
struct amdxdna_dev *xdna = pci_get_drvdata(to_pci_dev(dev));
|
||||
struct amdxdna_client *client;
|
||||
int ret;
|
||||
|
||||
XDNA_INFO(xdna, "firmware resuming...");
|
||||
mutex_lock(&xdna->dev_lock);
|
||||
ret = amdxdna_dev_resume_nolock(xdna);
|
||||
if (ret) {
|
||||
XDNA_ERR(xdna, "resume NPU firmware failed");
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
return ret;
|
||||
}
|
||||
if (!xdna->dev_info->ops->resume)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
XDNA_INFO(xdna, "hardware context resuming...");
|
||||
list_for_each_entry(client, &xdna->client_list, node)
|
||||
amdxdna_hwctx_resume(client);
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amdxdna_rpmops_suspend(struct device *dev)
|
||||
{
|
||||
struct amdxdna_dev *xdna = pci_get_drvdata(to_pci_dev(dev));
|
||||
int ret;
|
||||
|
||||
mutex_lock(&xdna->dev_lock);
|
||||
ret = amdxdna_dev_suspend_nolock(xdna);
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
|
||||
XDNA_DBG(xdna, "Runtime suspend done ret: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int amdxdna_rpmops_resume(struct device *dev)
|
||||
{
|
||||
struct amdxdna_dev *xdna = pci_get_drvdata(to_pci_dev(dev));
|
||||
int ret;
|
||||
|
||||
mutex_lock(&xdna->dev_lock);
|
||||
ret = amdxdna_dev_resume_nolock(xdna);
|
||||
mutex_unlock(&xdna->dev_lock);
|
||||
|
||||
XDNA_DBG(xdna, "Runtime resume done ret: %d", ret);
|
||||
return ret;
|
||||
return xdna->dev_info->ops->resume(xdna);
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops amdxdna_pm_ops = {
|
||||
SYSTEM_SLEEP_PM_OPS(amdxdna_pmops_suspend, amdxdna_pmops_resume)
|
||||
RUNTIME_PM_OPS(amdxdna_rpmops_suspend, amdxdna_rpmops_resume, NULL)
|
||||
RUNTIME_PM_OPS(amdxdna_pmops_suspend, amdxdna_pmops_resume, NULL)
|
||||
};
|
||||
|
||||
static struct pci_driver amdxdna_pci_driver = {
|
||||
|
||||
@@ -50,13 +50,11 @@ struct amdxdna_dev_ops {
|
||||
int (*init)(struct amdxdna_dev *xdna);
|
||||
void (*fini)(struct amdxdna_dev *xdna);
|
||||
int (*resume)(struct amdxdna_dev *xdna);
|
||||
void (*suspend)(struct amdxdna_dev *xdna);
|
||||
int (*suspend)(struct amdxdna_dev *xdna);
|
||||
int (*hwctx_init)(struct amdxdna_hwctx *hwctx);
|
||||
void (*hwctx_fini)(struct amdxdna_hwctx *hwctx);
|
||||
int (*hwctx_config)(struct amdxdna_hwctx *hwctx, u32 type, u64 value, void *buf, u32 size);
|
||||
void (*hmm_invalidate)(struct amdxdna_gem_obj *abo, unsigned long cur_seq);
|
||||
void (*hwctx_suspend)(struct amdxdna_hwctx *hwctx);
|
||||
void (*hwctx_resume)(struct amdxdna_hwctx *hwctx);
|
||||
int (*cmd_submit)(struct amdxdna_hwctx *hwctx, struct amdxdna_sched_job *job, u64 *seq);
|
||||
int (*get_aie_info)(struct amdxdna_client *client, struct amdxdna_drm_get_info *args);
|
||||
int (*set_aie_state)(struct amdxdna_client *client, struct amdxdna_drm_set_state *args);
|
||||
@@ -118,8 +116,6 @@ struct amdxdna_device_id {
|
||||
struct amdxdna_client {
|
||||
struct list_head node;
|
||||
pid_t pid;
|
||||
struct mutex hwctx_lock; /* protect hwctx */
|
||||
/* do NOT wait this srcu when hwctx_lock is held */
|
||||
struct srcu_struct hwctx_srcu;
|
||||
struct xarray hwctx_xa;
|
||||
u32 next_hwctxid;
|
||||
|
||||
232
drivers/accel/amdxdna/amdxdna_ubuf.c
Normal file
232
drivers/accel/amdxdna/amdxdna_ubuf.c
Normal file
@@ -0,0 +1,232 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2025, Advanced Micro Devices, Inc.
|
||||
*/
|
||||
|
||||
#include <drm/amdxdna_accel.h>
|
||||
#include <drm/drm_device.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include "amdxdna_pci_drv.h"
|
||||
#include "amdxdna_ubuf.h"
|
||||
|
||||
struct amdxdna_ubuf_priv {
|
||||
struct page **pages;
|
||||
u64 nr_pages;
|
||||
enum amdxdna_ubuf_flag flags;
|
||||
struct mm_struct *mm;
|
||||
};
|
||||
|
||||
static struct sg_table *amdxdna_ubuf_map(struct dma_buf_attachment *attach,
|
||||
enum dma_data_direction direction)
|
||||
{
|
||||
struct amdxdna_ubuf_priv *ubuf = attach->dmabuf->priv;
|
||||
struct sg_table *sg;
|
||||
int ret;
|
||||
|
||||
sg = kzalloc(sizeof(*sg), GFP_KERNEL);
|
||||
if (!sg)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = sg_alloc_table_from_pages(sg, ubuf->pages, ubuf->nr_pages, 0,
|
||||
ubuf->nr_pages << PAGE_SHIFT, GFP_KERNEL);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
if (ubuf->flags & AMDXDNA_UBUF_FLAG_MAP_DMA) {
|
||||
ret = dma_map_sgtable(attach->dev, sg, direction, 0);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return sg;
|
||||
}
|
||||
|
||||
static void amdxdna_ubuf_unmap(struct dma_buf_attachment *attach,
|
||||
struct sg_table *sg,
|
||||
enum dma_data_direction direction)
|
||||
{
|
||||
struct amdxdna_ubuf_priv *ubuf = attach->dmabuf->priv;
|
||||
|
||||
if (ubuf->flags & AMDXDNA_UBUF_FLAG_MAP_DMA)
|
||||
dma_unmap_sgtable(attach->dev, sg, direction, 0);
|
||||
|
||||
sg_free_table(sg);
|
||||
kfree(sg);
|
||||
}
|
||||
|
||||
static void amdxdna_ubuf_release(struct dma_buf *dbuf)
|
||||
{
|
||||
struct amdxdna_ubuf_priv *ubuf = dbuf->priv;
|
||||
|
||||
unpin_user_pages(ubuf->pages, ubuf->nr_pages);
|
||||
kvfree(ubuf->pages);
|
||||
atomic64_sub(ubuf->nr_pages, &ubuf->mm->pinned_vm);
|
||||
mmdrop(ubuf->mm);
|
||||
kfree(ubuf);
|
||||
}
|
||||
|
||||
static vm_fault_t amdxdna_ubuf_vm_fault(struct vm_fault *vmf)
|
||||
{
|
||||
struct vm_area_struct *vma = vmf->vma;
|
||||
struct amdxdna_ubuf_priv *ubuf;
|
||||
unsigned long pfn;
|
||||
pgoff_t pgoff;
|
||||
|
||||
ubuf = vma->vm_private_data;
|
||||
pgoff = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
|
||||
|
||||
pfn = page_to_pfn(ubuf->pages[pgoff]);
|
||||
return vmf_insert_pfn(vma, vmf->address, pfn);
|
||||
}
|
||||
|
||||
static const struct vm_operations_struct amdxdna_ubuf_vm_ops = {
|
||||
.fault = amdxdna_ubuf_vm_fault,
|
||||
};
|
||||
|
||||
static int amdxdna_ubuf_mmap(struct dma_buf *dbuf, struct vm_area_struct *vma)
|
||||
{
|
||||
struct amdxdna_ubuf_priv *ubuf = dbuf->priv;
|
||||
|
||||
vma->vm_ops = &amdxdna_ubuf_vm_ops;
|
||||
vma->vm_private_data = ubuf;
|
||||
vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amdxdna_ubuf_vmap(struct dma_buf *dbuf, struct iosys_map *map)
|
||||
{
|
||||
struct amdxdna_ubuf_priv *ubuf = dbuf->priv;
|
||||
void *kva;
|
||||
|
||||
kva = vmap(ubuf->pages, ubuf->nr_pages, VM_MAP, PAGE_KERNEL);
|
||||
if (!kva)
|
||||
return -EINVAL;
|
||||
|
||||
iosys_map_set_vaddr(map, kva);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amdxdna_ubuf_vunmap(struct dma_buf *dbuf, struct iosys_map *map)
|
||||
{
|
||||
vunmap(map->vaddr);
|
||||
}
|
||||
|
||||
static const struct dma_buf_ops amdxdna_ubuf_dmabuf_ops = {
|
||||
.map_dma_buf = amdxdna_ubuf_map,
|
||||
.unmap_dma_buf = amdxdna_ubuf_unmap,
|
||||
.release = amdxdna_ubuf_release,
|
||||
.mmap = amdxdna_ubuf_mmap,
|
||||
.vmap = amdxdna_ubuf_vmap,
|
||||
.vunmap = amdxdna_ubuf_vunmap,
|
||||
};
|
||||
|
||||
struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev,
|
||||
enum amdxdna_ubuf_flag flags,
|
||||
u32 num_entries, void __user *va_entries)
|
||||
{
|
||||
struct amdxdna_dev *xdna = to_xdna_dev(dev);
|
||||
unsigned long lock_limit, new_pinned;
|
||||
struct amdxdna_drm_va_entry *va_ent;
|
||||
struct amdxdna_ubuf_priv *ubuf;
|
||||
u32 npages, start = 0;
|
||||
struct dma_buf *dbuf;
|
||||
int i, ret;
|
||||
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
|
||||
|
||||
if (!can_do_mlock())
|
||||
return ERR_PTR(-EPERM);
|
||||
|
||||
ubuf = kzalloc(sizeof(*ubuf), GFP_KERNEL);
|
||||
if (!ubuf)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ubuf->flags = flags;
|
||||
ubuf->mm = current->mm;
|
||||
mmgrab(ubuf->mm);
|
||||
|
||||
va_ent = kvcalloc(num_entries, sizeof(*va_ent), GFP_KERNEL);
|
||||
if (!va_ent) {
|
||||
ret = -ENOMEM;
|
||||
goto free_ubuf;
|
||||
}
|
||||
|
||||
if (copy_from_user(va_ent, va_entries, sizeof(*va_ent) * num_entries)) {
|
||||
XDNA_DBG(xdna, "Access va entries failed");
|
||||
ret = -EINVAL;
|
||||
goto free_ent;
|
||||
}
|
||||
|
||||
for (i = 0, exp_info.size = 0; i < num_entries; i++) {
|
||||
if (!IS_ALIGNED(va_ent[i].vaddr, PAGE_SIZE) ||
|
||||
!IS_ALIGNED(va_ent[i].len, PAGE_SIZE)) {
|
||||
XDNA_ERR(xdna, "Invalid address or len %llx, %llx",
|
||||
va_ent[i].vaddr, va_ent[i].len);
|
||||
ret = -EINVAL;
|
||||
goto free_ent;
|
||||
}
|
||||
|
||||
exp_info.size += va_ent[i].len;
|
||||
}
|
||||
|
||||
ubuf->nr_pages = exp_info.size >> PAGE_SHIFT;
|
||||
lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
|
||||
new_pinned = atomic64_add_return(ubuf->nr_pages, &ubuf->mm->pinned_vm);
|
||||
if (new_pinned > lock_limit && !capable(CAP_IPC_LOCK)) {
|
||||
XDNA_DBG(xdna, "New pin %ld, limit %ld, cap %d",
|
||||
new_pinned, lock_limit, capable(CAP_IPC_LOCK));
|
||||
ret = -ENOMEM;
|
||||
goto sub_pin_cnt;
|
||||
}
|
||||
|
||||
ubuf->pages = kvmalloc_array(ubuf->nr_pages, sizeof(*ubuf->pages), GFP_KERNEL);
|
||||
if (!ubuf->pages) {
|
||||
ret = -ENOMEM;
|
||||
goto sub_pin_cnt;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_entries; i++) {
|
||||
npages = va_ent[i].len >> PAGE_SHIFT;
|
||||
|
||||
ret = pin_user_pages_fast(va_ent[i].vaddr, npages,
|
||||
FOLL_WRITE | FOLL_LONGTERM,
|
||||
&ubuf->pages[start]);
|
||||
if (ret < 0 || ret != npages) {
|
||||
ret = -ENOMEM;
|
||||
XDNA_ERR(xdna, "Failed to pin pages ret %d", ret);
|
||||
goto destroy_pages;
|
||||
}
|
||||
|
||||
start += ret;
|
||||
}
|
||||
|
||||
exp_info.ops = &amdxdna_ubuf_dmabuf_ops;
|
||||
exp_info.priv = ubuf;
|
||||
exp_info.flags = O_RDWR | O_CLOEXEC;
|
||||
|
||||
dbuf = dma_buf_export(&exp_info);
|
||||
if (IS_ERR(dbuf)) {
|
||||
ret = PTR_ERR(dbuf);
|
||||
goto destroy_pages;
|
||||
}
|
||||
kvfree(va_ent);
|
||||
|
||||
return dbuf;
|
||||
|
||||
destroy_pages:
|
||||
if (start)
|
||||
unpin_user_pages(ubuf->pages, start);
|
||||
kvfree(ubuf->pages);
|
||||
sub_pin_cnt:
|
||||
atomic64_sub(ubuf->nr_pages, &ubuf->mm->pinned_vm);
|
||||
free_ent:
|
||||
kvfree(va_ent);
|
||||
free_ubuf:
|
||||
mmdrop(ubuf->mm);
|
||||
kfree(ubuf);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
19
drivers/accel/amdxdna/amdxdna_ubuf.h
Normal file
19
drivers/accel/amdxdna/amdxdna_ubuf.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2025, Advanced Micro Devices, Inc.
|
||||
*/
|
||||
#ifndef _AMDXDNA_UBUF_H_
|
||||
#define _AMDXDNA_UBUF_H_
|
||||
|
||||
#include <drm/drm_device.h>
|
||||
#include <linux/dma-buf.h>
|
||||
|
||||
enum amdxdna_ubuf_flag {
|
||||
AMDXDNA_UBUF_FLAG_MAP_DMA = 1,
|
||||
};
|
||||
|
||||
struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev,
|
||||
enum amdxdna_ubuf_flag flags,
|
||||
u32 num_entries, void __user *va_entries);
|
||||
|
||||
#endif /* _AMDXDNA_UBUF_H_ */
|
||||
@@ -1829,9 +1829,6 @@ static void hl_release_dmabuf(struct dma_buf *dmabuf)
|
||||
struct hl_dmabuf_priv *hl_dmabuf = dmabuf->priv;
|
||||
struct hl_ctx *ctx;
|
||||
|
||||
if (!hl_dmabuf)
|
||||
return;
|
||||
|
||||
ctx = hl_dmabuf->ctx;
|
||||
|
||||
if (hl_dmabuf->memhash_hnode)
|
||||
@@ -1859,7 +1856,12 @@ static int export_dmabuf(struct hl_ctx *ctx,
|
||||
{
|
||||
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
|
||||
struct hl_device *hdev = ctx->hdev;
|
||||
int rc, fd;
|
||||
CLASS(get_unused_fd, fd)(flags);
|
||||
|
||||
if (fd < 0) {
|
||||
dev_err(hdev->dev, "failed to get a file descriptor for a dma-buf, %d\n", fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
exp_info.ops = &habanalabs_dmabuf_ops;
|
||||
exp_info.size = total_size;
|
||||
@@ -1872,13 +1874,6 @@ static int export_dmabuf(struct hl_ctx *ctx,
|
||||
return PTR_ERR(hl_dmabuf->dmabuf);
|
||||
}
|
||||
|
||||
fd = dma_buf_fd(hl_dmabuf->dmabuf, flags);
|
||||
if (fd < 0) {
|
||||
dev_err(hdev->dev, "failed to get a file descriptor for a dma-buf, %d\n", fd);
|
||||
rc = fd;
|
||||
goto err_dma_buf_put;
|
||||
}
|
||||
|
||||
hl_dmabuf->ctx = ctx;
|
||||
hl_ctx_get(hl_dmabuf->ctx);
|
||||
atomic_inc(&ctx->hdev->dmabuf_export_cnt);
|
||||
@@ -1890,13 +1885,9 @@ static int export_dmabuf(struct hl_ctx *ctx,
|
||||
get_file(ctx->hpriv->file_priv->filp);
|
||||
|
||||
*dmabuf_fd = fd;
|
||||
fd_install(take_fd(fd), hl_dmabuf->dmabuf->file);
|
||||
|
||||
return 0;
|
||||
|
||||
err_dma_buf_put:
|
||||
hl_dmabuf->dmabuf->priv = NULL;
|
||||
dma_buf_put(hl_dmabuf->dmabuf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int validate_export_params_common(struct hl_device *hdev, u64 addr, u64 size, u64 offset)
|
||||
|
||||
@@ -141,7 +141,6 @@ ivpu_ipc_rx_msg_add(struct ivpu_device *vdev, struct ivpu_ipc_consumer *cons,
|
||||
struct ivpu_ipc_rx_msg *rx_msg;
|
||||
|
||||
lockdep_assert_held(&ipc->cons_lock);
|
||||
lockdep_assert_irqs_disabled();
|
||||
|
||||
rx_msg = kzalloc(sizeof(*rx_msg), GFP_ATOMIC);
|
||||
if (!rx_msg) {
|
||||
|
||||
24
drivers/accel/rocket/Kconfig
Normal file
24
drivers/accel/rocket/Kconfig
Normal file
@@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
config DRM_ACCEL_ROCKET
|
||||
tristate "Rocket (support for Rockchip NPUs)"
|
||||
depends on DRM
|
||||
depends on (ARCH_ROCKCHIP && ARM64) || COMPILE_TEST
|
||||
depends on ROCKCHIP_IOMMU || COMPILE_TEST
|
||||
depends on MMU
|
||||
select DRM_SCHED
|
||||
select DRM_GEM_SHMEM_HELPER
|
||||
help
|
||||
Choose this option if you have a Rockchip SoC that contains a
|
||||
compatible Neural Processing Unit (NPU), such as the RK3588. Called by
|
||||
Rockchip either RKNN or RKNPU, it accelerates inference of neural
|
||||
networks.
|
||||
|
||||
The interface exposed to userspace is described in
|
||||
include/uapi/drm/rocket_accel.h and is used by the Rocket userspace
|
||||
driver in Mesa3D.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rocket.
|
||||
10
drivers/accel/rocket/Makefile
Normal file
10
drivers/accel/rocket/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
obj-$(CONFIG_DRM_ACCEL_ROCKET) := rocket.o
|
||||
|
||||
rocket-y := \
|
||||
rocket_core.o \
|
||||
rocket_device.o \
|
||||
rocket_drv.o \
|
||||
rocket_gem.o \
|
||||
rocket_job.o
|
||||
110
drivers/accel/rocket/rocket_core.c
Normal file
110
drivers/accel/rocket/rocket_core.c
Normal file
@@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include "rocket_core.h"
|
||||
#include "rocket_job.h"
|
||||
|
||||
int rocket_core_init(struct rocket_core *core)
|
||||
{
|
||||
struct device *dev = core->dev;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
u32 version;
|
||||
int err = 0;
|
||||
|
||||
core->resets[0].id = "srst_a";
|
||||
core->resets[1].id = "srst_h";
|
||||
err = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(core->resets),
|
||||
core->resets);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "failed to get resets for core %d\n", core->index);
|
||||
|
||||
err = devm_clk_bulk_get(dev, ARRAY_SIZE(core->clks), core->clks);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
|
||||
|
||||
core->pc_iomem = devm_platform_ioremap_resource_byname(pdev, "pc");
|
||||
if (IS_ERR(core->pc_iomem)) {
|
||||
dev_err(dev, "couldn't find PC registers %ld\n", PTR_ERR(core->pc_iomem));
|
||||
return PTR_ERR(core->pc_iomem);
|
||||
}
|
||||
|
||||
core->cna_iomem = devm_platform_ioremap_resource_byname(pdev, "cna");
|
||||
if (IS_ERR(core->cna_iomem)) {
|
||||
dev_err(dev, "couldn't find CNA registers %ld\n", PTR_ERR(core->cna_iomem));
|
||||
return PTR_ERR(core->cna_iomem);
|
||||
}
|
||||
|
||||
core->core_iomem = devm_platform_ioremap_resource_byname(pdev, "core");
|
||||
if (IS_ERR(core->core_iomem)) {
|
||||
dev_err(dev, "couldn't find CORE registers %ld\n", PTR_ERR(core->core_iomem));
|
||||
return PTR_ERR(core->core_iomem);
|
||||
}
|
||||
|
||||
dma_set_max_seg_size(dev, UINT_MAX);
|
||||
|
||||
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40));
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
core->iommu_group = iommu_group_get(dev);
|
||||
|
||||
err = rocket_job_init(core);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
|
||||
/*
|
||||
* As this NPU will be most often used as part of a media pipeline that
|
||||
* ends presenting in a display, choose 50 ms (~3 frames at 60Hz) as an
|
||||
* autosuspend delay as that will keep the device powered up while the
|
||||
* pipeline is running.
|
||||
*/
|
||||
pm_runtime_set_autosuspend_delay(dev, 50);
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
err = pm_runtime_get_sync(dev);
|
||||
if (err) {
|
||||
rocket_job_fini(core);
|
||||
return err;
|
||||
}
|
||||
|
||||
version = rocket_pc_readl(core, VERSION);
|
||||
version += rocket_pc_readl(core, VERSION_NUM) & 0xffff;
|
||||
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
dev_info(dev, "Rockchip NPU core %d version: %d\n", core->index, version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rocket_core_fini(struct rocket_core *core)
|
||||
{
|
||||
pm_runtime_dont_use_autosuspend(core->dev);
|
||||
pm_runtime_disable(core->dev);
|
||||
iommu_group_put(core->iommu_group);
|
||||
core->iommu_group = NULL;
|
||||
rocket_job_fini(core);
|
||||
}
|
||||
|
||||
void rocket_core_reset(struct rocket_core *core)
|
||||
{
|
||||
reset_control_bulk_assert(ARRAY_SIZE(core->resets), core->resets);
|
||||
|
||||
udelay(10);
|
||||
|
||||
reset_control_bulk_deassert(ARRAY_SIZE(core->resets), core->resets);
|
||||
}
|
||||
64
drivers/accel/rocket/rocket_core.h
Normal file
64
drivers/accel/rocket/rocket_core.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#ifndef __ROCKET_CORE_H__
|
||||
#define __ROCKET_CORE_H__
|
||||
|
||||
#include <drm/gpu_scheduler.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mutex_types.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include "rocket_registers.h"
|
||||
|
||||
#define rocket_pc_readl(core, reg) \
|
||||
readl((core)->pc_iomem + (REG_PC_##reg))
|
||||
#define rocket_pc_writel(core, reg, value) \
|
||||
writel(value, (core)->pc_iomem + (REG_PC_##reg))
|
||||
|
||||
#define rocket_cna_readl(core, reg) \
|
||||
readl((core)->cna_iomem + (REG_CNA_##reg) - REG_CNA_S_STATUS)
|
||||
#define rocket_cna_writel(core, reg, value) \
|
||||
writel(value, (core)->cna_iomem + (REG_CNA_##reg) - REG_CNA_S_STATUS)
|
||||
|
||||
#define rocket_core_readl(core, reg) \
|
||||
readl((core)->core_iomem + (REG_CORE_##reg) - REG_CORE_S_STATUS)
|
||||
#define rocket_core_writel(core, reg, value) \
|
||||
writel(value, (core)->core_iomem + (REG_CORE_##reg) - REG_CORE_S_STATUS)
|
||||
|
||||
struct rocket_core {
|
||||
struct device *dev;
|
||||
struct rocket_device *rdev;
|
||||
unsigned int index;
|
||||
|
||||
int irq;
|
||||
void __iomem *pc_iomem;
|
||||
void __iomem *cna_iomem;
|
||||
void __iomem *core_iomem;
|
||||
struct clk_bulk_data clks[4];
|
||||
struct reset_control_bulk_data resets[2];
|
||||
|
||||
struct iommu_group *iommu_group;
|
||||
|
||||
struct mutex job_lock;
|
||||
struct rocket_job *in_flight_job;
|
||||
|
||||
spinlock_t fence_lock;
|
||||
|
||||
struct {
|
||||
struct workqueue_struct *wq;
|
||||
struct work_struct work;
|
||||
atomic_t pending;
|
||||
} reset;
|
||||
|
||||
struct drm_gpu_scheduler sched;
|
||||
u64 fence_context;
|
||||
u64 emit_seqno;
|
||||
};
|
||||
|
||||
int rocket_core_init(struct rocket_core *core);
|
||||
void rocket_core_fini(struct rocket_core *core);
|
||||
void rocket_core_reset(struct rocket_core *core);
|
||||
|
||||
#endif
|
||||
60
drivers/accel/rocket/rocket_device.c
Normal file
60
drivers/accel/rocket/rocket_device.c
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#include <drm/drm_drv.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "rocket_device.h"
|
||||
|
||||
struct rocket_device *rocket_device_init(struct platform_device *pdev,
|
||||
const struct drm_driver *rocket_drm_driver)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *core_node;
|
||||
struct rocket_device *rdev;
|
||||
struct drm_device *ddev;
|
||||
unsigned int num_cores = 0;
|
||||
int err;
|
||||
|
||||
rdev = devm_drm_dev_alloc(dev, rocket_drm_driver, struct rocket_device, ddev);
|
||||
if (IS_ERR(rdev))
|
||||
return rdev;
|
||||
|
||||
ddev = &rdev->ddev;
|
||||
dev_set_drvdata(dev, rdev);
|
||||
|
||||
for_each_compatible_node(core_node, NULL, "rockchip,rk3588-rknn-core")
|
||||
if (of_device_is_available(core_node))
|
||||
num_cores++;
|
||||
|
||||
rdev->cores = devm_kcalloc(dev, num_cores, sizeof(*rdev->cores), GFP_KERNEL);
|
||||
if (!rdev->cores)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
dma_set_max_seg_size(dev, UINT_MAX);
|
||||
|
||||
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40));
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
err = devm_mutex_init(dev, &rdev->sched_lock);
|
||||
if (err)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
err = drm_dev_register(ddev, 0);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
return rdev;
|
||||
}
|
||||
|
||||
void rocket_device_fini(struct rocket_device *rdev)
|
||||
{
|
||||
WARN_ON(rdev->num_cores > 0);
|
||||
|
||||
drm_dev_unregister(&rdev->ddev);
|
||||
}
|
||||
30
drivers/accel/rocket/rocket_device.h
Normal file
30
drivers/accel/rocket/rocket_device.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#ifndef __ROCKET_DEVICE_H__
|
||||
#define __ROCKET_DEVICE_H__
|
||||
|
||||
#include <drm/drm_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "rocket_core.h"
|
||||
|
||||
struct rocket_device {
|
||||
struct drm_device ddev;
|
||||
|
||||
struct mutex sched_lock;
|
||||
|
||||
struct rocket_core *cores;
|
||||
unsigned int num_cores;
|
||||
};
|
||||
|
||||
struct rocket_device *rocket_device_init(struct platform_device *pdev,
|
||||
const struct drm_driver *rocket_drm_driver);
|
||||
void rocket_device_fini(struct rocket_device *rdev);
|
||||
#define to_rocket_device(drm_dev) \
|
||||
((struct rocket_device *)(container_of((drm_dev), struct rocket_device, ddev)))
|
||||
|
||||
#endif /* __ROCKET_DEVICE_H__ */
|
||||
290
drivers/accel/rocket/rocket_drv.c
Normal file
290
drivers/accel/rocket/rocket_drv.c
Normal file
@@ -0,0 +1,290 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#include <drm/drm_accel.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_gem.h>
|
||||
#include <drm/drm_ioctl.h>
|
||||
#include <drm/rocket_accel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "rocket_drv.h"
|
||||
#include "rocket_gem.h"
|
||||
#include "rocket_job.h"
|
||||
|
||||
/*
|
||||
* Facade device, used to expose a single DRM device to userspace, that
|
||||
* schedules jobs to any RKNN cores in the system.
|
||||
*/
|
||||
static struct platform_device *drm_dev;
|
||||
static struct rocket_device *rdev;
|
||||
|
||||
static void
|
||||
rocket_iommu_domain_destroy(struct kref *kref)
|
||||
{
|
||||
struct rocket_iommu_domain *domain = container_of(kref, struct rocket_iommu_domain, kref);
|
||||
|
||||
iommu_domain_free(domain->domain);
|
||||
domain->domain = NULL;
|
||||
kfree(domain);
|
||||
}
|
||||
|
||||
static struct rocket_iommu_domain*
|
||||
rocket_iommu_domain_create(struct device *dev)
|
||||
{
|
||||
struct rocket_iommu_domain *domain = kmalloc(sizeof(*domain), GFP_KERNEL);
|
||||
void *err;
|
||||
|
||||
if (!domain)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
domain->domain = iommu_paging_domain_alloc(dev);
|
||||
if (IS_ERR(domain->domain)) {
|
||||
err = ERR_CAST(domain->domain);
|
||||
kfree(domain);
|
||||
return err;
|
||||
}
|
||||
kref_init(&domain->kref);
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
struct rocket_iommu_domain *
|
||||
rocket_iommu_domain_get(struct rocket_file_priv *rocket_priv)
|
||||
{
|
||||
kref_get(&rocket_priv->domain->kref);
|
||||
return rocket_priv->domain;
|
||||
}
|
||||
|
||||
void
|
||||
rocket_iommu_domain_put(struct rocket_iommu_domain *domain)
|
||||
{
|
||||
kref_put(&domain->kref, rocket_iommu_domain_destroy);
|
||||
}
|
||||
|
||||
static int
|
||||
rocket_open(struct drm_device *dev, struct drm_file *file)
|
||||
{
|
||||
struct rocket_device *rdev = to_rocket_device(dev);
|
||||
struct rocket_file_priv *rocket_priv;
|
||||
u64 start, end;
|
||||
int ret;
|
||||
|
||||
if (!try_module_get(THIS_MODULE))
|
||||
return -EINVAL;
|
||||
|
||||
rocket_priv = kzalloc(sizeof(*rocket_priv), GFP_KERNEL);
|
||||
if (!rocket_priv) {
|
||||
ret = -ENOMEM;
|
||||
goto err_put_mod;
|
||||
}
|
||||
|
||||
rocket_priv->rdev = rdev;
|
||||
rocket_priv->domain = rocket_iommu_domain_create(rdev->cores[0].dev);
|
||||
if (IS_ERR(rocket_priv->domain)) {
|
||||
ret = PTR_ERR(rocket_priv->domain);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
file->driver_priv = rocket_priv;
|
||||
|
||||
start = rocket_priv->domain->domain->geometry.aperture_start;
|
||||
end = rocket_priv->domain->domain->geometry.aperture_end;
|
||||
drm_mm_init(&rocket_priv->mm, start, end - start + 1);
|
||||
mutex_init(&rocket_priv->mm_lock);
|
||||
|
||||
ret = rocket_job_open(rocket_priv);
|
||||
if (ret)
|
||||
goto err_mm_takedown;
|
||||
|
||||
return 0;
|
||||
|
||||
err_mm_takedown:
|
||||
mutex_destroy(&rocket_priv->mm_lock);
|
||||
drm_mm_takedown(&rocket_priv->mm);
|
||||
rocket_iommu_domain_put(rocket_priv->domain);
|
||||
err_free:
|
||||
kfree(rocket_priv);
|
||||
err_put_mod:
|
||||
module_put(THIS_MODULE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
rocket_postclose(struct drm_device *dev, struct drm_file *file)
|
||||
{
|
||||
struct rocket_file_priv *rocket_priv = file->driver_priv;
|
||||
|
||||
rocket_job_close(rocket_priv);
|
||||
mutex_destroy(&rocket_priv->mm_lock);
|
||||
drm_mm_takedown(&rocket_priv->mm);
|
||||
rocket_iommu_domain_put(rocket_priv->domain);
|
||||
kfree(rocket_priv);
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
static const struct drm_ioctl_desc rocket_drm_driver_ioctls[] = {
|
||||
#define ROCKET_IOCTL(n, func) \
|
||||
DRM_IOCTL_DEF_DRV(ROCKET_##n, rocket_ioctl_##func, 0)
|
||||
|
||||
ROCKET_IOCTL(CREATE_BO, create_bo),
|
||||
ROCKET_IOCTL(SUBMIT, submit),
|
||||
ROCKET_IOCTL(PREP_BO, prep_bo),
|
||||
ROCKET_IOCTL(FINI_BO, fini_bo),
|
||||
};
|
||||
|
||||
DEFINE_DRM_ACCEL_FOPS(rocket_accel_driver_fops);
|
||||
|
||||
/*
|
||||
* Rocket driver version:
|
||||
* - 1.0 - initial interface
|
||||
*/
|
||||
static const struct drm_driver rocket_drm_driver = {
|
||||
.driver_features = DRIVER_COMPUTE_ACCEL | DRIVER_GEM,
|
||||
.open = rocket_open,
|
||||
.postclose = rocket_postclose,
|
||||
.gem_create_object = rocket_gem_create_object,
|
||||
.ioctls = rocket_drm_driver_ioctls,
|
||||
.num_ioctls = ARRAY_SIZE(rocket_drm_driver_ioctls),
|
||||
.fops = &rocket_accel_driver_fops,
|
||||
.name = "rocket",
|
||||
.desc = "rocket DRM",
|
||||
};
|
||||
|
||||
static int rocket_probe(struct platform_device *pdev)
|
||||
{
|
||||
if (rdev == NULL) {
|
||||
/* First core probing, initialize DRM device. */
|
||||
rdev = rocket_device_init(drm_dev, &rocket_drm_driver);
|
||||
if (IS_ERR(rdev)) {
|
||||
dev_err(&pdev->dev, "failed to initialize rocket device\n");
|
||||
return PTR_ERR(rdev);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int core = rdev->num_cores;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, rdev);
|
||||
|
||||
rdev->cores[core].rdev = rdev;
|
||||
rdev->cores[core].dev = &pdev->dev;
|
||||
rdev->cores[core].index = core;
|
||||
|
||||
rdev->num_cores++;
|
||||
|
||||
return rocket_core_init(&rdev->cores[core]);
|
||||
}
|
||||
|
||||
static void rocket_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
for (unsigned int core = 0; core < rdev->num_cores; core++) {
|
||||
if (rdev->cores[core].dev == dev) {
|
||||
rocket_core_fini(&rdev->cores[core]);
|
||||
rdev->num_cores--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rdev->num_cores == 0) {
|
||||
/* Last core removed, deinitialize DRM device. */
|
||||
rocket_device_fini(rdev);
|
||||
rdev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct of_device_id dt_match[] = {
|
||||
{ .compatible = "rockchip,rk3588-rknn-core" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dt_match);
|
||||
|
||||
static int find_core_for_dev(struct device *dev)
|
||||
{
|
||||
struct rocket_device *rdev = dev_get_drvdata(dev);
|
||||
|
||||
for (unsigned int core = 0; core < rdev->num_cores; core++) {
|
||||
if (dev == rdev->cores[core].dev)
|
||||
return core;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int rocket_device_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct rocket_device *rdev = dev_get_drvdata(dev);
|
||||
int core = find_core_for_dev(dev);
|
||||
int err = 0;
|
||||
|
||||
if (core < 0)
|
||||
return -ENODEV;
|
||||
|
||||
err = clk_bulk_prepare_enable(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to enable (%d) clocks for core %d\n", err, core);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rocket_device_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct rocket_device *rdev = dev_get_drvdata(dev);
|
||||
int core = find_core_for_dev(dev);
|
||||
|
||||
if (core < 0)
|
||||
return -ENODEV;
|
||||
|
||||
if (!rocket_job_is_idle(&rdev->cores[core]))
|
||||
return -EBUSY;
|
||||
|
||||
clk_bulk_disable_unprepare(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_GPL_DEV_PM_OPS(rocket_pm_ops) = {
|
||||
RUNTIME_PM_OPS(rocket_device_runtime_suspend, rocket_device_runtime_resume, NULL)
|
||||
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
|
||||
};
|
||||
|
||||
static struct platform_driver rocket_driver = {
|
||||
.probe = rocket_probe,
|
||||
.remove = rocket_remove,
|
||||
.driver = {
|
||||
.name = "rocket",
|
||||
.pm = pm_ptr(&rocket_pm_ops),
|
||||
.of_match_table = dt_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init rocket_register(void)
|
||||
{
|
||||
drm_dev = platform_device_register_simple("rknn", -1, NULL, 0);
|
||||
if (IS_ERR(drm_dev))
|
||||
return PTR_ERR(drm_dev);
|
||||
|
||||
return platform_driver_register(&rocket_driver);
|
||||
}
|
||||
|
||||
static void __exit rocket_unregister(void)
|
||||
{
|
||||
platform_driver_unregister(&rocket_driver);
|
||||
|
||||
platform_device_unregister(drm_dev);
|
||||
}
|
||||
|
||||
module_init(rocket_register);
|
||||
module_exit(rocket_unregister);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("DRM driver for the Rockchip NPU IP");
|
||||
MODULE_AUTHOR("Tomeu Vizoso");
|
||||
32
drivers/accel/rocket/rocket_drv.h
Normal file
32
drivers/accel/rocket/rocket_drv.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#ifndef __ROCKET_DRV_H__
|
||||
#define __ROCKET_DRV_H__
|
||||
|
||||
#include <drm/drm_mm.h>
|
||||
#include <drm/gpu_scheduler.h>
|
||||
|
||||
#include "rocket_device.h"
|
||||
|
||||
extern const struct dev_pm_ops rocket_pm_ops;
|
||||
|
||||
struct rocket_iommu_domain {
|
||||
struct iommu_domain *domain;
|
||||
struct kref kref;
|
||||
};
|
||||
|
||||
struct rocket_file_priv {
|
||||
struct rocket_device *rdev;
|
||||
|
||||
struct rocket_iommu_domain *domain;
|
||||
struct drm_mm mm;
|
||||
struct mutex mm_lock;
|
||||
|
||||
struct drm_sched_entity sched_entity;
|
||||
};
|
||||
|
||||
struct rocket_iommu_domain *rocket_iommu_domain_get(struct rocket_file_priv *rocket_priv);
|
||||
void rocket_iommu_domain_put(struct rocket_iommu_domain *domain);
|
||||
|
||||
#endif
|
||||
181
drivers/accel/rocket/rocket_gem.c
Normal file
181
drivers/accel/rocket/rocket_gem.c
Normal file
@@ -0,0 +1,181 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#include <drm/drm_device.h>
|
||||
#include <drm/drm_utils.h>
|
||||
#include <drm/rocket_accel.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/iommu.h>
|
||||
|
||||
#include "rocket_drv.h"
|
||||
#include "rocket_gem.h"
|
||||
|
||||
static void rocket_gem_bo_free(struct drm_gem_object *obj)
|
||||
{
|
||||
struct rocket_gem_object *bo = to_rocket_bo(obj);
|
||||
struct rocket_file_priv *rocket_priv = bo->driver_priv;
|
||||
size_t unmapped;
|
||||
|
||||
drm_WARN_ON(obj->dev, refcount_read(&bo->base.pages_use_count) > 1);
|
||||
|
||||
unmapped = iommu_unmap(bo->domain->domain, bo->mm.start, bo->size);
|
||||
drm_WARN_ON(obj->dev, unmapped != bo->size);
|
||||
|
||||
mutex_lock(&rocket_priv->mm_lock);
|
||||
drm_mm_remove_node(&bo->mm);
|
||||
mutex_unlock(&rocket_priv->mm_lock);
|
||||
|
||||
rocket_iommu_domain_put(bo->domain);
|
||||
bo->domain = NULL;
|
||||
|
||||
drm_gem_shmem_free(&bo->base);
|
||||
}
|
||||
|
||||
static const struct drm_gem_object_funcs rocket_gem_funcs = {
|
||||
.free = rocket_gem_bo_free,
|
||||
.print_info = drm_gem_shmem_object_print_info,
|
||||
.pin = drm_gem_shmem_object_pin,
|
||||
.unpin = drm_gem_shmem_object_unpin,
|
||||
.get_sg_table = drm_gem_shmem_object_get_sg_table,
|
||||
.vmap = drm_gem_shmem_object_vmap,
|
||||
.vunmap = drm_gem_shmem_object_vunmap,
|
||||
.mmap = drm_gem_shmem_object_mmap,
|
||||
.vm_ops = &drm_gem_shmem_vm_ops,
|
||||
};
|
||||
|
||||
struct drm_gem_object *rocket_gem_create_object(struct drm_device *dev, size_t size)
|
||||
{
|
||||
struct rocket_gem_object *obj;
|
||||
|
||||
obj = kzalloc(sizeof(*obj), GFP_KERNEL);
|
||||
if (!obj)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
obj->base.base.funcs = &rocket_gem_funcs;
|
||||
|
||||
return &obj->base.base;
|
||||
}
|
||||
|
||||
int rocket_ioctl_create_bo(struct drm_device *dev, void *data, struct drm_file *file)
|
||||
{
|
||||
struct rocket_file_priv *rocket_priv = file->driver_priv;
|
||||
struct drm_rocket_create_bo *args = data;
|
||||
struct drm_gem_shmem_object *shmem_obj;
|
||||
struct rocket_gem_object *rkt_obj;
|
||||
struct drm_gem_object *gem_obj;
|
||||
struct sg_table *sgt;
|
||||
int ret;
|
||||
|
||||
shmem_obj = drm_gem_shmem_create(dev, args->size);
|
||||
if (IS_ERR(shmem_obj))
|
||||
return PTR_ERR(shmem_obj);
|
||||
|
||||
gem_obj = &shmem_obj->base;
|
||||
rkt_obj = to_rocket_bo(gem_obj);
|
||||
|
||||
rkt_obj->driver_priv = rocket_priv;
|
||||
rkt_obj->domain = rocket_iommu_domain_get(rocket_priv);
|
||||
rkt_obj->size = args->size;
|
||||
rkt_obj->offset = 0;
|
||||
|
||||
ret = drm_gem_handle_create(file, gem_obj, &args->handle);
|
||||
drm_gem_object_put(gem_obj);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
sgt = drm_gem_shmem_get_pages_sgt(shmem_obj);
|
||||
if (IS_ERR(sgt)) {
|
||||
ret = PTR_ERR(sgt);
|
||||
goto err;
|
||||
}
|
||||
|
||||
mutex_lock(&rocket_priv->mm_lock);
|
||||
ret = drm_mm_insert_node_generic(&rocket_priv->mm, &rkt_obj->mm,
|
||||
rkt_obj->size, PAGE_SIZE,
|
||||
0, 0);
|
||||
mutex_unlock(&rocket_priv->mm_lock);
|
||||
|
||||
ret = iommu_map_sgtable(rocket_priv->domain->domain,
|
||||
rkt_obj->mm.start,
|
||||
shmem_obj->sgt,
|
||||
IOMMU_READ | IOMMU_WRITE);
|
||||
if (ret < 0 || ret < args->size) {
|
||||
drm_err(dev, "failed to map buffer: size=%d request_size=%u\n",
|
||||
ret, args->size);
|
||||
ret = -ENOMEM;
|
||||
goto err_remove_node;
|
||||
}
|
||||
|
||||
/* iommu_map_sgtable might have aligned the size */
|
||||
rkt_obj->size = ret;
|
||||
args->offset = drm_vma_node_offset_addr(&gem_obj->vma_node);
|
||||
args->dma_address = rkt_obj->mm.start;
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_node:
|
||||
mutex_lock(&rocket_priv->mm_lock);
|
||||
drm_mm_remove_node(&rkt_obj->mm);
|
||||
mutex_unlock(&rocket_priv->mm_lock);
|
||||
|
||||
err:
|
||||
drm_gem_shmem_object_free(gem_obj);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rocket_ioctl_prep_bo(struct drm_device *dev, void *data, struct drm_file *file)
|
||||
{
|
||||
struct drm_rocket_prep_bo *args = data;
|
||||
unsigned long timeout = drm_timeout_abs_to_jiffies(args->timeout_ns);
|
||||
struct drm_gem_object *gem_obj;
|
||||
struct drm_gem_shmem_object *shmem_obj;
|
||||
long ret = 0;
|
||||
|
||||
if (args->reserved != 0) {
|
||||
drm_dbg(dev, "Reserved field in drm_rocket_prep_bo struct should be 0.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gem_obj = drm_gem_object_lookup(file, args->handle);
|
||||
if (!gem_obj)
|
||||
return -ENOENT;
|
||||
|
||||
ret = dma_resv_wait_timeout(gem_obj->resv, DMA_RESV_USAGE_WRITE, true, timeout);
|
||||
if (!ret)
|
||||
ret = timeout ? -ETIMEDOUT : -EBUSY;
|
||||
|
||||
shmem_obj = &to_rocket_bo(gem_obj)->base;
|
||||
|
||||
dma_sync_sgtable_for_cpu(dev->dev, shmem_obj->sgt, DMA_BIDIRECTIONAL);
|
||||
|
||||
drm_gem_object_put(gem_obj);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rocket_ioctl_fini_bo(struct drm_device *dev, void *data, struct drm_file *file)
|
||||
{
|
||||
struct drm_rocket_fini_bo *args = data;
|
||||
struct drm_gem_shmem_object *shmem_obj;
|
||||
struct rocket_gem_object *rkt_obj;
|
||||
struct drm_gem_object *gem_obj;
|
||||
|
||||
if (args->reserved != 0) {
|
||||
drm_dbg(dev, "Reserved field in drm_rocket_fini_bo struct should be 0.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gem_obj = drm_gem_object_lookup(file, args->handle);
|
||||
if (!gem_obj)
|
||||
return -ENOENT;
|
||||
|
||||
rkt_obj = to_rocket_bo(gem_obj);
|
||||
shmem_obj = &rkt_obj->base;
|
||||
|
||||
dma_sync_sgtable_for_device(dev->dev, shmem_obj->sgt, DMA_BIDIRECTIONAL);
|
||||
|
||||
drm_gem_object_put(gem_obj);
|
||||
|
||||
return 0;
|
||||
}
|
||||
34
drivers/accel/rocket/rocket_gem.h
Normal file
34
drivers/accel/rocket/rocket_gem.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#ifndef __ROCKET_GEM_H__
|
||||
#define __ROCKET_GEM_H__
|
||||
|
||||
#include <drm/drm_gem_shmem_helper.h>
|
||||
|
||||
struct rocket_gem_object {
|
||||
struct drm_gem_shmem_object base;
|
||||
|
||||
struct rocket_file_priv *driver_priv;
|
||||
|
||||
struct rocket_iommu_domain *domain;
|
||||
struct drm_mm_node mm;
|
||||
size_t size;
|
||||
u32 offset;
|
||||
};
|
||||
|
||||
struct drm_gem_object *rocket_gem_create_object(struct drm_device *dev, size_t size);
|
||||
|
||||
int rocket_ioctl_create_bo(struct drm_device *dev, void *data, struct drm_file *file);
|
||||
|
||||
int rocket_ioctl_prep_bo(struct drm_device *dev, void *data, struct drm_file *file);
|
||||
|
||||
int rocket_ioctl_fini_bo(struct drm_device *dev, void *data, struct drm_file *file);
|
||||
|
||||
static inline
|
||||
struct rocket_gem_object *to_rocket_bo(struct drm_gem_object *obj)
|
||||
{
|
||||
return container_of(to_drm_gem_shmem_obj(obj), struct rocket_gem_object, base);
|
||||
}
|
||||
|
||||
#endif
|
||||
636
drivers/accel/rocket/rocket_job.c
Normal file
636
drivers/accel/rocket/rocket_job.c
Normal file
@@ -0,0 +1,636 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
|
||||
/* Copyright 2019 Collabora ltd. */
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_file.h>
|
||||
#include <drm/drm_gem.h>
|
||||
#include <drm/rocket_accel.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "rocket_core.h"
|
||||
#include "rocket_device.h"
|
||||
#include "rocket_drv.h"
|
||||
#include "rocket_job.h"
|
||||
#include "rocket_registers.h"
|
||||
|
||||
#define JOB_TIMEOUT_MS 500
|
||||
|
||||
static struct rocket_job *
|
||||
to_rocket_job(struct drm_sched_job *sched_job)
|
||||
{
|
||||
return container_of(sched_job, struct rocket_job, base);
|
||||
}
|
||||
|
||||
static const char *rocket_fence_get_driver_name(struct dma_fence *fence)
|
||||
{
|
||||
return "rocket";
|
||||
}
|
||||
|
||||
static const char *rocket_fence_get_timeline_name(struct dma_fence *fence)
|
||||
{
|
||||
return "rockchip-npu";
|
||||
}
|
||||
|
||||
static const struct dma_fence_ops rocket_fence_ops = {
|
||||
.get_driver_name = rocket_fence_get_driver_name,
|
||||
.get_timeline_name = rocket_fence_get_timeline_name,
|
||||
};
|
||||
|
||||
static struct dma_fence *rocket_fence_create(struct rocket_core *core)
|
||||
{
|
||||
struct dma_fence *fence;
|
||||
|
||||
fence = kzalloc(sizeof(*fence), GFP_KERNEL);
|
||||
if (!fence)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
dma_fence_init(fence, &rocket_fence_ops, &core->fence_lock,
|
||||
core->fence_context, ++core->emit_seqno);
|
||||
|
||||
return fence;
|
||||
}
|
||||
|
||||
static int
|
||||
rocket_copy_tasks(struct drm_device *dev,
|
||||
struct drm_file *file_priv,
|
||||
struct drm_rocket_job *job,
|
||||
struct rocket_job *rjob)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (job->task_struct_size < sizeof(struct drm_rocket_task))
|
||||
return -EINVAL;
|
||||
|
||||
rjob->task_count = job->task_count;
|
||||
|
||||
if (!rjob->task_count)
|
||||
return 0;
|
||||
|
||||
rjob->tasks = kvmalloc_array(job->task_count, sizeof(*rjob->tasks), GFP_KERNEL);
|
||||
if (!rjob->tasks) {
|
||||
drm_dbg(dev, "Failed to allocate task array\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rjob->task_count; i++) {
|
||||
struct drm_rocket_task task = {0};
|
||||
|
||||
if (copy_from_user(&task,
|
||||
u64_to_user_ptr(job->tasks) + i * job->task_struct_size,
|
||||
sizeof(task))) {
|
||||
drm_dbg(dev, "Failed to copy incoming tasks\n");
|
||||
ret = -EFAULT;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (task.regcmd_count == 0) {
|
||||
drm_dbg(dev, "regcmd_count field in drm_rocket_task should be > 0.\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rjob->tasks[i].regcmd = task.regcmd;
|
||||
rjob->tasks[i].regcmd_count = task.regcmd_count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
kvfree(rjob->tasks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rocket_job_hw_submit(struct rocket_core *core, struct rocket_job *job)
|
||||
{
|
||||
struct rocket_task *task;
|
||||
unsigned int extra_bit;
|
||||
|
||||
/* Don't queue the job if a reset is in progress */
|
||||
if (atomic_read(&core->reset.pending))
|
||||
return;
|
||||
|
||||
/* GO ! */
|
||||
|
||||
task = &job->tasks[job->next_task_idx];
|
||||
job->next_task_idx++;
|
||||
|
||||
rocket_pc_writel(core, BASE_ADDRESS, 0x1);
|
||||
|
||||
/* From rknpu, in the TRM this bit is marked as reserved */
|
||||
extra_bit = 0x10000000 * core->index;
|
||||
rocket_cna_writel(core, S_POINTER, CNA_S_POINTER_POINTER_PP_EN(1) |
|
||||
CNA_S_POINTER_EXECUTER_PP_EN(1) |
|
||||
CNA_S_POINTER_POINTER_PP_MODE(1) |
|
||||
extra_bit);
|
||||
|
||||
rocket_core_writel(core, S_POINTER, CORE_S_POINTER_POINTER_PP_EN(1) |
|
||||
CORE_S_POINTER_EXECUTER_PP_EN(1) |
|
||||
CORE_S_POINTER_POINTER_PP_MODE(1) |
|
||||
extra_bit);
|
||||
|
||||
rocket_pc_writel(core, BASE_ADDRESS, task->regcmd);
|
||||
rocket_pc_writel(core, REGISTER_AMOUNTS,
|
||||
PC_REGISTER_AMOUNTS_PC_DATA_AMOUNT((task->regcmd_count + 1) / 2 - 1));
|
||||
|
||||
rocket_pc_writel(core, INTERRUPT_MASK, PC_INTERRUPT_MASK_DPU_0 | PC_INTERRUPT_MASK_DPU_1);
|
||||
rocket_pc_writel(core, INTERRUPT_CLEAR, PC_INTERRUPT_CLEAR_DPU_0 | PC_INTERRUPT_CLEAR_DPU_1);
|
||||
|
||||
rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
|
||||
PC_TASK_CON_TASK_COUNT_CLEAR(1) |
|
||||
PC_TASK_CON_TASK_NUMBER(1) |
|
||||
PC_TASK_CON_TASK_PP_EN(1));
|
||||
|
||||
rocket_pc_writel(core, TASK_DMA_BASE_ADDR, PC_TASK_DMA_BASE_ADDR_DMA_BASE_ADDR(0x0));
|
||||
|
||||
rocket_pc_writel(core, OPERATION_ENABLE, PC_OPERATION_ENABLE_OP_EN(1));
|
||||
|
||||
dev_dbg(core->dev, "Submitted regcmd at 0x%llx to core %d", task->regcmd, core->index);
|
||||
}
|
||||
|
||||
static int rocket_acquire_object_fences(struct drm_gem_object **bos,
|
||||
int bo_count,
|
||||
struct drm_sched_job *job,
|
||||
bool is_write)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < bo_count; i++) {
|
||||
ret = dma_resv_reserve_fences(bos[i]->resv, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = drm_sched_job_add_implicit_dependencies(job, bos[i],
|
||||
is_write);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rocket_attach_object_fences(struct drm_gem_object **bos,
|
||||
int bo_count,
|
||||
struct dma_fence *fence)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < bo_count; i++)
|
||||
dma_resv_add_fence(bos[i]->resv, fence, DMA_RESV_USAGE_WRITE);
|
||||
}
|
||||
|
||||
static int rocket_job_push(struct rocket_job *job)
|
||||
{
|
||||
struct rocket_device *rdev = job->rdev;
|
||||
struct drm_gem_object **bos;
|
||||
struct ww_acquire_ctx acquire_ctx;
|
||||
int ret = 0;
|
||||
|
||||
bos = kvmalloc_array(job->in_bo_count + job->out_bo_count, sizeof(void *),
|
||||
GFP_KERNEL);
|
||||
memcpy(bos, job->in_bos, job->in_bo_count * sizeof(void *));
|
||||
memcpy(&bos[job->in_bo_count], job->out_bos, job->out_bo_count * sizeof(void *));
|
||||
|
||||
ret = drm_gem_lock_reservations(bos, job->in_bo_count + job->out_bo_count, &acquire_ctx);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
scoped_guard(mutex, &rdev->sched_lock) {
|
||||
drm_sched_job_arm(&job->base);
|
||||
|
||||
job->inference_done_fence = dma_fence_get(&job->base.s_fence->finished);
|
||||
|
||||
ret = rocket_acquire_object_fences(job->in_bos, job->in_bo_count, &job->base, false);
|
||||
if (ret)
|
||||
goto err_unlock;
|
||||
|
||||
ret = rocket_acquire_object_fences(job->out_bos, job->out_bo_count, &job->base, true);
|
||||
if (ret)
|
||||
goto err_unlock;
|
||||
|
||||
kref_get(&job->refcount); /* put by scheduler job completion */
|
||||
|
||||
drm_sched_entity_push_job(&job->base);
|
||||
}
|
||||
|
||||
rocket_attach_object_fences(job->out_bos, job->out_bo_count, job->inference_done_fence);
|
||||
|
||||
err_unlock:
|
||||
drm_gem_unlock_reservations(bos, job->in_bo_count + job->out_bo_count, &acquire_ctx);
|
||||
err:
|
||||
kfree(bos);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rocket_job_cleanup(struct kref *ref)
|
||||
{
|
||||
struct rocket_job *job = container_of(ref, struct rocket_job,
|
||||
refcount);
|
||||
unsigned int i;
|
||||
|
||||
rocket_iommu_domain_put(job->domain);
|
||||
|
||||
dma_fence_put(job->done_fence);
|
||||
dma_fence_put(job->inference_done_fence);
|
||||
|
||||
if (job->in_bos) {
|
||||
for (i = 0; i < job->in_bo_count; i++)
|
||||
drm_gem_object_put(job->in_bos[i]);
|
||||
|
||||
kvfree(job->in_bos);
|
||||
}
|
||||
|
||||
if (job->out_bos) {
|
||||
for (i = 0; i < job->out_bo_count; i++)
|
||||
drm_gem_object_put(job->out_bos[i]);
|
||||
|
||||
kvfree(job->out_bos);
|
||||
}
|
||||
|
||||
kvfree(job->tasks);
|
||||
|
||||
kfree(job);
|
||||
}
|
||||
|
||||
static void rocket_job_put(struct rocket_job *job)
|
||||
{
|
||||
kref_put(&job->refcount, rocket_job_cleanup);
|
||||
}
|
||||
|
||||
static void rocket_job_free(struct drm_sched_job *sched_job)
|
||||
{
|
||||
struct rocket_job *job = to_rocket_job(sched_job);
|
||||
|
||||
drm_sched_job_cleanup(sched_job);
|
||||
|
||||
rocket_job_put(job);
|
||||
}
|
||||
|
||||
static struct rocket_core *sched_to_core(struct rocket_device *rdev,
|
||||
struct drm_gpu_scheduler *sched)
|
||||
{
|
||||
unsigned int core;
|
||||
|
||||
for (core = 0; core < rdev->num_cores; core++) {
|
||||
if (&rdev->cores[core].sched == sched)
|
||||
return &rdev->cores[core];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct dma_fence *rocket_job_run(struct drm_sched_job *sched_job)
|
||||
{
|
||||
struct rocket_job *job = to_rocket_job(sched_job);
|
||||
struct rocket_device *rdev = job->rdev;
|
||||
struct rocket_core *core = sched_to_core(rdev, sched_job->sched);
|
||||
struct dma_fence *fence = NULL;
|
||||
int ret;
|
||||
|
||||
if (unlikely(job->base.s_fence->finished.error))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Nothing to execute: can happen if the job has finished while
|
||||
* we were resetting the NPU.
|
||||
*/
|
||||
if (job->next_task_idx == job->task_count)
|
||||
return NULL;
|
||||
|
||||
fence = rocket_fence_create(core);
|
||||
if (IS_ERR(fence))
|
||||
return fence;
|
||||
|
||||
if (job->done_fence)
|
||||
dma_fence_put(job->done_fence);
|
||||
job->done_fence = dma_fence_get(fence);
|
||||
|
||||
ret = pm_runtime_get_sync(core->dev);
|
||||
if (ret < 0)
|
||||
return fence;
|
||||
|
||||
ret = iommu_attach_group(job->domain->domain, core->iommu_group);
|
||||
if (ret < 0)
|
||||
return fence;
|
||||
|
||||
scoped_guard(mutex, &core->job_lock) {
|
||||
core->in_flight_job = job;
|
||||
rocket_job_hw_submit(core, job);
|
||||
}
|
||||
|
||||
return fence;
|
||||
}
|
||||
|
||||
static void rocket_job_handle_irq(struct rocket_core *core)
|
||||
{
|
||||
pm_runtime_mark_last_busy(core->dev);
|
||||
|
||||
rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
|
||||
rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
|
||||
|
||||
scoped_guard(mutex, &core->job_lock)
|
||||
if (core->in_flight_job) {
|
||||
if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
|
||||
rocket_job_hw_submit(core, core->in_flight_job);
|
||||
return;
|
||||
}
|
||||
|
||||
iommu_detach_group(NULL, iommu_group_get(core->dev));
|
||||
dma_fence_signal(core->in_flight_job->done_fence);
|
||||
pm_runtime_put_autosuspend(core->dev);
|
||||
core->in_flight_job = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
|
||||
{
|
||||
if (!atomic_read(&core->reset.pending))
|
||||
return;
|
||||
|
||||
drm_sched_stop(&core->sched, bad);
|
||||
|
||||
/*
|
||||
* Remaining interrupts have been handled, but we might still have
|
||||
* stuck jobs. Let's make sure the PM counters stay balanced by
|
||||
* manually calling pm_runtime_put_noidle().
|
||||
*/
|
||||
scoped_guard(mutex, &core->job_lock) {
|
||||
if (core->in_flight_job)
|
||||
pm_runtime_put_noidle(core->dev);
|
||||
|
||||
iommu_detach_group(NULL, core->iommu_group);
|
||||
|
||||
core->in_flight_job = NULL;
|
||||
}
|
||||
|
||||
/* Proceed with reset now. */
|
||||
rocket_core_reset(core);
|
||||
|
||||
/* NPU has been reset, we can clear the reset pending bit. */
|
||||
atomic_set(&core->reset.pending, 0);
|
||||
|
||||
/* Restart the scheduler */
|
||||
drm_sched_start(&core->sched, 0);
|
||||
}
|
||||
|
||||
static enum drm_gpu_sched_stat rocket_job_timedout(struct drm_sched_job *sched_job)
|
||||
{
|
||||
struct rocket_job *job = to_rocket_job(sched_job);
|
||||
struct rocket_device *rdev = job->rdev;
|
||||
struct rocket_core *core = sched_to_core(rdev, sched_job->sched);
|
||||
|
||||
dev_err(core->dev, "NPU job timed out");
|
||||
|
||||
atomic_set(&core->reset.pending, 1);
|
||||
rocket_reset(core, sched_job);
|
||||
|
||||
return DRM_GPU_SCHED_STAT_RESET;
|
||||
}
|
||||
|
||||
static void rocket_reset_work(struct work_struct *work)
|
||||
{
|
||||
struct rocket_core *core;
|
||||
|
||||
core = container_of(work, struct rocket_core, reset.work);
|
||||
rocket_reset(core, NULL);
|
||||
}
|
||||
|
||||
static const struct drm_sched_backend_ops rocket_sched_ops = {
|
||||
.run_job = rocket_job_run,
|
||||
.timedout_job = rocket_job_timedout,
|
||||
.free_job = rocket_job_free
|
||||
};
|
||||
|
||||
static irqreturn_t rocket_job_irq_handler_thread(int irq, void *data)
|
||||
{
|
||||
struct rocket_core *core = data;
|
||||
|
||||
rocket_job_handle_irq(core);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t rocket_job_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct rocket_core *core = data;
|
||||
u32 raw_status = rocket_pc_readl(core, INTERRUPT_RAW_STATUS);
|
||||
|
||||
WARN_ON(raw_status & PC_INTERRUPT_RAW_STATUS_DMA_READ_ERROR);
|
||||
WARN_ON(raw_status & PC_INTERRUPT_RAW_STATUS_DMA_READ_ERROR);
|
||||
|
||||
if (!(raw_status & PC_INTERRUPT_RAW_STATUS_DPU_0 ||
|
||||
raw_status & PC_INTERRUPT_RAW_STATUS_DPU_1))
|
||||
return IRQ_NONE;
|
||||
|
||||
rocket_pc_writel(core, INTERRUPT_MASK, 0x0);
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
int rocket_job_init(struct rocket_core *core)
|
||||
{
|
||||
struct drm_sched_init_args args = {
|
||||
.ops = &rocket_sched_ops,
|
||||
.num_rqs = DRM_SCHED_PRIORITY_COUNT,
|
||||
.credit_limit = 1,
|
||||
.timeout = msecs_to_jiffies(JOB_TIMEOUT_MS),
|
||||
.name = dev_name(core->dev),
|
||||
.dev = core->dev,
|
||||
};
|
||||
int ret;
|
||||
|
||||
INIT_WORK(&core->reset.work, rocket_reset_work);
|
||||
spin_lock_init(&core->fence_lock);
|
||||
mutex_init(&core->job_lock);
|
||||
|
||||
core->irq = platform_get_irq(to_platform_device(core->dev), 0);
|
||||
if (core->irq < 0)
|
||||
return core->irq;
|
||||
|
||||
ret = devm_request_threaded_irq(core->dev, core->irq,
|
||||
rocket_job_irq_handler,
|
||||
rocket_job_irq_handler_thread,
|
||||
IRQF_SHARED, dev_name(core->dev),
|
||||
core);
|
||||
if (ret) {
|
||||
dev_err(core->dev, "failed to request job irq");
|
||||
return ret;
|
||||
}
|
||||
|
||||
core->reset.wq = alloc_ordered_workqueue("rocket-reset-%d", 0, core->index);
|
||||
if (!core->reset.wq)
|
||||
return -ENOMEM;
|
||||
|
||||
core->fence_context = dma_fence_context_alloc(1);
|
||||
|
||||
args.timeout_wq = core->reset.wq;
|
||||
ret = drm_sched_init(&core->sched, &args);
|
||||
if (ret) {
|
||||
dev_err(core->dev, "Failed to create scheduler: %d.", ret);
|
||||
goto err_sched;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_sched:
|
||||
drm_sched_fini(&core->sched);
|
||||
|
||||
destroy_workqueue(core->reset.wq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void rocket_job_fini(struct rocket_core *core)
|
||||
{
|
||||
drm_sched_fini(&core->sched);
|
||||
|
||||
cancel_work_sync(&core->reset.work);
|
||||
destroy_workqueue(core->reset.wq);
|
||||
}
|
||||
|
||||
int rocket_job_open(struct rocket_file_priv *rocket_priv)
|
||||
{
|
||||
struct rocket_device *rdev = rocket_priv->rdev;
|
||||
struct drm_gpu_scheduler **scheds = kmalloc_array(rdev->num_cores, sizeof(scheds),
|
||||
GFP_KERNEL);
|
||||
unsigned int core;
|
||||
int ret;
|
||||
|
||||
for (core = 0; core < rdev->num_cores; core++)
|
||||
scheds[core] = &rdev->cores[core].sched;
|
||||
|
||||
ret = drm_sched_entity_init(&rocket_priv->sched_entity,
|
||||
DRM_SCHED_PRIORITY_NORMAL,
|
||||
scheds,
|
||||
rdev->num_cores, NULL);
|
||||
if (WARN_ON(ret))
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rocket_job_close(struct rocket_file_priv *rocket_priv)
|
||||
{
|
||||
struct drm_sched_entity *entity = &rocket_priv->sched_entity;
|
||||
|
||||
kfree(entity->sched_list);
|
||||
drm_sched_entity_destroy(entity);
|
||||
}
|
||||
|
||||
int rocket_job_is_idle(struct rocket_core *core)
|
||||
{
|
||||
/* If there are any jobs in this HW queue, we're not idle */
|
||||
if (atomic_read(&core->sched.credit_count))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int rocket_ioctl_submit_job(struct drm_device *dev, struct drm_file *file,
|
||||
struct drm_rocket_job *job)
|
||||
{
|
||||
struct rocket_device *rdev = to_rocket_device(dev);
|
||||
struct rocket_file_priv *file_priv = file->driver_priv;
|
||||
struct rocket_job *rjob = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (job->task_count == 0)
|
||||
return -EINVAL;
|
||||
|
||||
rjob = kzalloc(sizeof(*rjob), GFP_KERNEL);
|
||||
if (!rjob)
|
||||
return -ENOMEM;
|
||||
|
||||
kref_init(&rjob->refcount);
|
||||
|
||||
rjob->rdev = rdev;
|
||||
|
||||
ret = drm_sched_job_init(&rjob->base,
|
||||
&file_priv->sched_entity,
|
||||
1, NULL, file->client_id);
|
||||
if (ret)
|
||||
goto out_put_job;
|
||||
|
||||
ret = rocket_copy_tasks(dev, file, job, rjob);
|
||||
if (ret)
|
||||
goto out_cleanup_job;
|
||||
|
||||
ret = drm_gem_objects_lookup(file, u64_to_user_ptr(job->in_bo_handles),
|
||||
job->in_bo_handle_count, &rjob->in_bos);
|
||||
if (ret)
|
||||
goto out_cleanup_job;
|
||||
|
||||
rjob->in_bo_count = job->in_bo_handle_count;
|
||||
|
||||
ret = drm_gem_objects_lookup(file, u64_to_user_ptr(job->out_bo_handles),
|
||||
job->out_bo_handle_count, &rjob->out_bos);
|
||||
if (ret)
|
||||
goto out_cleanup_job;
|
||||
|
||||
rjob->out_bo_count = job->out_bo_handle_count;
|
||||
|
||||
rjob->domain = rocket_iommu_domain_get(file_priv);
|
||||
|
||||
ret = rocket_job_push(rjob);
|
||||
if (ret)
|
||||
goto out_cleanup_job;
|
||||
|
||||
out_cleanup_job:
|
||||
if (ret)
|
||||
drm_sched_job_cleanup(&rjob->base);
|
||||
out_put_job:
|
||||
rocket_job_put(rjob);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rocket_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *file)
|
||||
{
|
||||
struct drm_rocket_submit *args = data;
|
||||
struct drm_rocket_job *jobs;
|
||||
int ret = 0;
|
||||
unsigned int i = 0;
|
||||
|
||||
if (args->job_count == 0)
|
||||
return 0;
|
||||
|
||||
if (args->job_struct_size < sizeof(struct drm_rocket_job)) {
|
||||
drm_dbg(dev, "job_struct_size field in drm_rocket_submit struct is too small.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (args->reserved != 0) {
|
||||
drm_dbg(dev, "Reserved field in drm_rocket_submit struct should be 0.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
jobs = kvmalloc_array(args->job_count, sizeof(*jobs), GFP_KERNEL);
|
||||
if (!jobs) {
|
||||
drm_dbg(dev, "Failed to allocate incoming job array\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < args->job_count; i++) {
|
||||
if (copy_from_user(&jobs[i],
|
||||
u64_to_user_ptr(args->jobs) + i * args->job_struct_size,
|
||||
sizeof(*jobs))) {
|
||||
ret = -EFAULT;
|
||||
drm_dbg(dev, "Failed to copy incoming job array\n");
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (i = 0; i < args->job_count; i++)
|
||||
rocket_ioctl_submit_job(dev, file, &jobs[i]);
|
||||
|
||||
exit:
|
||||
kfree(jobs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
52
drivers/accel/rocket/rocket_job.h
Normal file
52
drivers/accel/rocket/rocket_job.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
|
||||
#ifndef __ROCKET_JOB_H__
|
||||
#define __ROCKET_JOB_H__
|
||||
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/gpu_scheduler.h>
|
||||
|
||||
#include "rocket_core.h"
|
||||
#include "rocket_drv.h"
|
||||
|
||||
struct rocket_task {
|
||||
u64 regcmd;
|
||||
u32 regcmd_count;
|
||||
};
|
||||
|
||||
struct rocket_job {
|
||||
struct drm_sched_job base;
|
||||
|
||||
struct rocket_device *rdev;
|
||||
|
||||
struct drm_gem_object **in_bos;
|
||||
struct drm_gem_object **out_bos;
|
||||
|
||||
u32 in_bo_count;
|
||||
u32 out_bo_count;
|
||||
|
||||
struct rocket_task *tasks;
|
||||
u32 task_count;
|
||||
u32 next_task_idx;
|
||||
|
||||
/* 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 rocket_iommu_domain *domain;
|
||||
|
||||
struct kref refcount;
|
||||
};
|
||||
|
||||
int rocket_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *file);
|
||||
|
||||
int rocket_job_init(struct rocket_core *core);
|
||||
void rocket_job_fini(struct rocket_core *core);
|
||||
int rocket_job_open(struct rocket_file_priv *rocket_priv);
|
||||
void rocket_job_close(struct rocket_file_priv *rocket_priv);
|
||||
int rocket_job_is_idle(struct rocket_core *core);
|
||||
|
||||
#endif
|
||||
4404
drivers/accel/rocket/rocket_registers.h
Normal file
4404
drivers/accel/rocket/rocket_registers.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2033,7 +2033,7 @@ void __init acpi_ec_ecdt_probe(void)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!strstarts(ecdt_ptr->id, "\\")) {
|
||||
if (!strlen(ecdt_ptr->id)) {
|
||||
/*
|
||||
* The ECDT table on some MSI notebooks contains invalid data, together
|
||||
* with an empty ID string ("").
|
||||
@@ -2042,9 +2042,13 @@ void __init acpi_ec_ecdt_probe(void)
|
||||
* a "fully qualified reference to the (...) embedded controller device",
|
||||
* so this string always has to start with a backslash.
|
||||
*
|
||||
* By verifying this we can avoid such faulty ECDT tables in a safe way.
|
||||
* However some ThinkBook machines have a ECDT table with a valid EC
|
||||
* description but an invalid ID string ("_SB.PC00.LPCB.EC0").
|
||||
*
|
||||
* Because of this we only check if the ID string is empty in order to
|
||||
* avoid the obvious cases.
|
||||
*/
|
||||
pr_err(FW_BUG "Ignoring ECDT due to invalid ID string \"%s\"\n", ecdt_ptr->id);
|
||||
pr_err(FW_BUG "Ignoring ECDT due to empty ID string\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ void acpi_processor_ppc_init(struct cpufreq_policy *policy)
|
||||
struct acpi_processor *pr = per_cpu(processors, cpu);
|
||||
int ret;
|
||||
|
||||
if (!pr || !pr->performance)
|
||||
if (!pr)
|
||||
continue;
|
||||
|
||||
/*
|
||||
@@ -197,6 +197,9 @@ void acpi_processor_ppc_init(struct cpufreq_policy *policy)
|
||||
pr_err("Failed to add freq constraint for CPU%d (%d)\n",
|
||||
cpu, ret);
|
||||
|
||||
if (!pr->performance)
|
||||
continue;
|
||||
|
||||
ret = acpi_processor_get_platform_limit(pr);
|
||||
if (ret)
|
||||
pr_err("Failed to update freq constraint for CPU%d (%d)\n",
|
||||
|
||||
@@ -2075,7 +2075,7 @@ out:
|
||||
* Check if a link is established. This is a relaxed version of
|
||||
* ata_phys_link_online() which accounts for the fact that this is potentially
|
||||
* called after changing the link power management policy, which may not be
|
||||
* reflected immediately in the SSTAUS register (e.g., we may still be seeing
|
||||
* reflected immediately in the SStatus register (e.g., we may still be seeing
|
||||
* the PHY in partial, slumber or devsleep Partial power management state.
|
||||
* So check that:
|
||||
* - A device is still present, that is, DET is 1h (Device presence detected
|
||||
@@ -2089,8 +2089,13 @@ static bool ata_eh_link_established(struct ata_link *link)
|
||||
u32 sstatus;
|
||||
u8 det, ipm;
|
||||
|
||||
/*
|
||||
* For old IDE/PATA adapters that do not have a valid scr_read method,
|
||||
* or if reading the SStatus register fails, assume that the device is
|
||||
* present. Device probe will determine if that is really the case.
|
||||
*/
|
||||
if (sata_scr_read(link, SCR_STATUS, &sstatus))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
det = sstatus & 0x0f;
|
||||
ipm = (sstatus >> 8) & 0x0f;
|
||||
|
||||
@@ -3904,21 +3904,16 @@ static int ata_mselect_control_ata_feature(struct ata_queued_cmd *qc,
|
||||
/* Check cdl_ctrl */
|
||||
switch (buf[0] & 0x03) {
|
||||
case 0:
|
||||
/* Disable CDL if it is enabled */
|
||||
if (!(dev->flags & ATA_DFLAG_CDL_ENABLED))
|
||||
return 0;
|
||||
/* Disable CDL */
|
||||
ata_dev_dbg(dev, "Disabling CDL\n");
|
||||
cdl_action = 0;
|
||||
dev->flags &= ~ATA_DFLAG_CDL_ENABLED;
|
||||
break;
|
||||
case 0x02:
|
||||
/*
|
||||
* Enable CDL if not already enabled. Since this is mutually
|
||||
* exclusive with NCQ priority, allow this only if NCQ priority
|
||||
* is disabled.
|
||||
* Enable CDL. Since CDL is mutually exclusive with NCQ
|
||||
* priority, allow this only if NCQ priority is disabled.
|
||||
*/
|
||||
if (dev->flags & ATA_DFLAG_CDL_ENABLED)
|
||||
return 0;
|
||||
if (dev->flags & ATA_DFLAG_NCQ_PRIO_ENABLED) {
|
||||
ata_dev_err(dev,
|
||||
"NCQ priority must be disabled to enable CDL\n");
|
||||
|
||||
@@ -380,6 +380,9 @@ enum {
|
||||
/* this is/was a write request */
|
||||
__EE_WRITE,
|
||||
|
||||
/* hand back using mempool_free(e, drbd_buffer_page_pool) */
|
||||
__EE_RELEASE_TO_MEMPOOL,
|
||||
|
||||
/* this is/was a write same request */
|
||||
__EE_WRITE_SAME,
|
||||
|
||||
@@ -402,6 +405,7 @@ enum {
|
||||
#define EE_IN_INTERVAL_TREE (1<<__EE_IN_INTERVAL_TREE)
|
||||
#define EE_SUBMITTED (1<<__EE_SUBMITTED)
|
||||
#define EE_WRITE (1<<__EE_WRITE)
|
||||
#define EE_RELEASE_TO_MEMPOOL (1<<__EE_RELEASE_TO_MEMPOOL)
|
||||
#define EE_WRITE_SAME (1<<__EE_WRITE_SAME)
|
||||
#define EE_APPLICATION (1<<__EE_APPLICATION)
|
||||
#define EE_RS_THIN_REQ (1<<__EE_RS_THIN_REQ)
|
||||
@@ -858,7 +862,6 @@ struct drbd_device {
|
||||
struct list_head sync_ee; /* IO in progress (P_RS_DATA_REPLY gets written to disk) */
|
||||
struct list_head done_ee; /* need to send P_WRITE_ACK */
|
||||
struct list_head read_ee; /* [RS]P_DATA_REQUEST being read */
|
||||
struct list_head net_ee; /* zero-copy network send in progress */
|
||||
|
||||
struct list_head resync_reads;
|
||||
atomic_t pp_in_use; /* allocated from page pool */
|
||||
@@ -1329,24 +1332,6 @@ extern struct kmem_cache *drbd_al_ext_cache; /* activity log extents */
|
||||
extern mempool_t drbd_request_mempool;
|
||||
extern mempool_t drbd_ee_mempool;
|
||||
|
||||
/* drbd's page pool, used to buffer data received from the peer,
|
||||
* or data requested by the peer.
|
||||
*
|
||||
* This does not have an emergency reserve.
|
||||
*
|
||||
* When allocating from this pool, it first takes pages from the pool.
|
||||
* Only if the pool is depleted will try to allocate from the system.
|
||||
*
|
||||
* The assumption is that pages taken from this pool will be processed,
|
||||
* and given back, "quickly", and then can be recycled, so we can avoid
|
||||
* frequent calls to alloc_page(), and still will be able to make progress even
|
||||
* under memory pressure.
|
||||
*/
|
||||
extern struct page *drbd_pp_pool;
|
||||
extern spinlock_t drbd_pp_lock;
|
||||
extern int drbd_pp_vacant;
|
||||
extern wait_queue_head_t drbd_pp_wait;
|
||||
|
||||
/* We also need a standard (emergency-reserve backed) page pool
|
||||
* for meta data IO (activity log, bitmap).
|
||||
* We can keep it global, as long as it is used as "N pages at a time".
|
||||
@@ -1354,6 +1339,7 @@ extern wait_queue_head_t drbd_pp_wait;
|
||||
*/
|
||||
#define DRBD_MIN_POOL_PAGES 128
|
||||
extern mempool_t drbd_md_io_page_pool;
|
||||
extern mempool_t drbd_buffer_page_pool;
|
||||
|
||||
/* We also need to make sure we get a bio
|
||||
* when we need it for housekeeping purposes */
|
||||
@@ -1488,10 +1474,7 @@ extern struct drbd_peer_request *drbd_alloc_peer_req(struct drbd_peer_device *,
|
||||
sector_t, unsigned int,
|
||||
unsigned int,
|
||||
gfp_t) __must_hold(local);
|
||||
extern void __drbd_free_peer_req(struct drbd_device *, struct drbd_peer_request *,
|
||||
int);
|
||||
#define drbd_free_peer_req(m,e) __drbd_free_peer_req(m, e, 0)
|
||||
#define drbd_free_net_peer_req(m,e) __drbd_free_peer_req(m, e, 1)
|
||||
extern void drbd_free_peer_req(struct drbd_device *device, struct drbd_peer_request *req);
|
||||
extern struct page *drbd_alloc_pages(struct drbd_peer_device *, unsigned int, bool);
|
||||
extern void _drbd_clear_done_ee(struct drbd_device *device, struct list_head *to_be_freed);
|
||||
extern int drbd_connected(struct drbd_peer_device *);
|
||||
@@ -1610,16 +1593,6 @@ static inline struct page *page_chain_next(struct page *page)
|
||||
for (; page && ({ n = page_chain_next(page); 1; }); page = n)
|
||||
|
||||
|
||||
static inline int drbd_peer_req_has_active_page(struct drbd_peer_request *peer_req)
|
||||
{
|
||||
struct page *page = peer_req->pages;
|
||||
page_chain_for_each(page) {
|
||||
if (page_count(page) > 1)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline union drbd_state drbd_read_state(struct drbd_device *device)
|
||||
{
|
||||
struct drbd_resource *resource = device->resource;
|
||||
|
||||
@@ -114,20 +114,10 @@ struct kmem_cache *drbd_al_ext_cache; /* activity log extents */
|
||||
mempool_t drbd_request_mempool;
|
||||
mempool_t drbd_ee_mempool;
|
||||
mempool_t drbd_md_io_page_pool;
|
||||
mempool_t drbd_buffer_page_pool;
|
||||
struct bio_set drbd_md_io_bio_set;
|
||||
struct bio_set drbd_io_bio_set;
|
||||
|
||||
/* I do not use a standard mempool, because:
|
||||
1) I want to hand out the pre-allocated objects first.
|
||||
2) I want to be able to interrupt sleeping allocation with a signal.
|
||||
Note: This is a single linked list, the next pointer is the private
|
||||
member of struct page.
|
||||
*/
|
||||
struct page *drbd_pp_pool;
|
||||
DEFINE_SPINLOCK(drbd_pp_lock);
|
||||
int drbd_pp_vacant;
|
||||
wait_queue_head_t drbd_pp_wait;
|
||||
|
||||
DEFINE_RATELIMIT_STATE(drbd_ratelimit_state, 5 * HZ, 5);
|
||||
|
||||
static const struct block_device_operations drbd_ops = {
|
||||
@@ -1611,6 +1601,7 @@ static int _drbd_send_zc_bio(struct drbd_peer_device *peer_device, struct bio *b
|
||||
static int _drbd_send_zc_ee(struct drbd_peer_device *peer_device,
|
||||
struct drbd_peer_request *peer_req)
|
||||
{
|
||||
bool use_sendpage = !(peer_req->flags & EE_RELEASE_TO_MEMPOOL);
|
||||
struct page *page = peer_req->pages;
|
||||
unsigned len = peer_req->i.size;
|
||||
int err;
|
||||
@@ -1619,8 +1610,13 @@ static int _drbd_send_zc_ee(struct drbd_peer_device *peer_device,
|
||||
page_chain_for_each(page) {
|
||||
unsigned l = min_t(unsigned, len, PAGE_SIZE);
|
||||
|
||||
err = _drbd_send_page(peer_device, page, 0, l,
|
||||
page_chain_next(page) ? MSG_MORE : 0);
|
||||
if (likely(use_sendpage))
|
||||
err = _drbd_send_page(peer_device, page, 0, l,
|
||||
page_chain_next(page) ? MSG_MORE : 0);
|
||||
else
|
||||
err = _drbd_no_send_page(peer_device, page, 0, l,
|
||||
page_chain_next(page) ? MSG_MORE : 0);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
len -= l;
|
||||
@@ -1962,7 +1958,6 @@ void drbd_init_set_defaults(struct drbd_device *device)
|
||||
INIT_LIST_HEAD(&device->sync_ee);
|
||||
INIT_LIST_HEAD(&device->done_ee);
|
||||
INIT_LIST_HEAD(&device->read_ee);
|
||||
INIT_LIST_HEAD(&device->net_ee);
|
||||
INIT_LIST_HEAD(&device->resync_reads);
|
||||
INIT_LIST_HEAD(&device->resync_work.list);
|
||||
INIT_LIST_HEAD(&device->unplug_work.list);
|
||||
@@ -2043,7 +2038,6 @@ void drbd_device_cleanup(struct drbd_device *device)
|
||||
D_ASSERT(device, list_empty(&device->sync_ee));
|
||||
D_ASSERT(device, list_empty(&device->done_ee));
|
||||
D_ASSERT(device, list_empty(&device->read_ee));
|
||||
D_ASSERT(device, list_empty(&device->net_ee));
|
||||
D_ASSERT(device, list_empty(&device->resync_reads));
|
||||
D_ASSERT(device, list_empty(&first_peer_device(device)->connection->sender_work.q));
|
||||
D_ASSERT(device, list_empty(&device->resync_work.list));
|
||||
@@ -2055,19 +2049,11 @@ void drbd_device_cleanup(struct drbd_device *device)
|
||||
|
||||
static void drbd_destroy_mempools(void)
|
||||
{
|
||||
struct page *page;
|
||||
|
||||
while (drbd_pp_pool) {
|
||||
page = drbd_pp_pool;
|
||||
drbd_pp_pool = (struct page *)page_private(page);
|
||||
__free_page(page);
|
||||
drbd_pp_vacant--;
|
||||
}
|
||||
|
||||
/* D_ASSERT(device, atomic_read(&drbd_pp_vacant)==0); */
|
||||
|
||||
bioset_exit(&drbd_io_bio_set);
|
||||
bioset_exit(&drbd_md_io_bio_set);
|
||||
mempool_exit(&drbd_buffer_page_pool);
|
||||
mempool_exit(&drbd_md_io_page_pool);
|
||||
mempool_exit(&drbd_ee_mempool);
|
||||
mempool_exit(&drbd_request_mempool);
|
||||
@@ -2086,9 +2072,8 @@ static void drbd_destroy_mempools(void)
|
||||
|
||||
static int drbd_create_mempools(void)
|
||||
{
|
||||
struct page *page;
|
||||
const int number = (DRBD_MAX_BIO_SIZE/PAGE_SIZE) * drbd_minor_count;
|
||||
int i, ret;
|
||||
int ret;
|
||||
|
||||
/* caches */
|
||||
drbd_request_cache = kmem_cache_create(
|
||||
@@ -2125,6 +2110,10 @@ static int drbd_create_mempools(void)
|
||||
if (ret)
|
||||
goto Enomem;
|
||||
|
||||
ret = mempool_init_page_pool(&drbd_buffer_page_pool, number, 0);
|
||||
if (ret)
|
||||
goto Enomem;
|
||||
|
||||
ret = mempool_init_slab_pool(&drbd_request_mempool, number,
|
||||
drbd_request_cache);
|
||||
if (ret)
|
||||
@@ -2134,15 +2123,6 @@ static int drbd_create_mempools(void)
|
||||
if (ret)
|
||||
goto Enomem;
|
||||
|
||||
for (i = 0; i < number; i++) {
|
||||
page = alloc_page(GFP_HIGHUSER);
|
||||
if (!page)
|
||||
goto Enomem;
|
||||
set_page_private(page, (unsigned long)drbd_pp_pool);
|
||||
drbd_pp_pool = page;
|
||||
}
|
||||
drbd_pp_vacant = number;
|
||||
|
||||
return 0;
|
||||
|
||||
Enomem:
|
||||
@@ -2169,10 +2149,6 @@ static void drbd_release_all_peer_reqs(struct drbd_device *device)
|
||||
rr = drbd_free_peer_reqs(device, &device->done_ee);
|
||||
if (rr)
|
||||
drbd_err(device, "%d EEs in done list found!\n", rr);
|
||||
|
||||
rr = drbd_free_peer_reqs(device, &device->net_ee);
|
||||
if (rr)
|
||||
drbd_err(device, "%d EEs in net list found!\n", rr);
|
||||
}
|
||||
|
||||
/* caution. no locking. */
|
||||
@@ -2863,11 +2839,6 @@ static int __init drbd_init(void)
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* allocate all necessary structs
|
||||
*/
|
||||
init_waitqueue_head(&drbd_pp_wait);
|
||||
|
||||
drbd_proc = NULL; /* play safe for drbd_cleanup */
|
||||
idr_init(&drbd_devices);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <linux/string.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/part_stat.h>
|
||||
#include <linux/mempool.h>
|
||||
#include "drbd_int.h"
|
||||
#include "drbd_protocol.h"
|
||||
#include "drbd_req.h"
|
||||
@@ -63,182 +64,31 @@ static int e_end_block(struct drbd_work *, int);
|
||||
|
||||
#define GFP_TRY (__GFP_HIGHMEM | __GFP_NOWARN)
|
||||
|
||||
/*
|
||||
* some helper functions to deal with single linked page lists,
|
||||
* page->private being our "next" pointer.
|
||||
*/
|
||||
|
||||
/* If at least n pages are linked at head, get n pages off.
|
||||
* Otherwise, don't modify head, and return NULL.
|
||||
* Locking is the responsibility of the caller.
|
||||
*/
|
||||
static struct page *page_chain_del(struct page **head, int n)
|
||||
{
|
||||
struct page *page;
|
||||
struct page *tmp;
|
||||
|
||||
BUG_ON(!n);
|
||||
BUG_ON(!head);
|
||||
|
||||
page = *head;
|
||||
|
||||
if (!page)
|
||||
return NULL;
|
||||
|
||||
while (page) {
|
||||
tmp = page_chain_next(page);
|
||||
if (--n == 0)
|
||||
break; /* found sufficient pages */
|
||||
if (tmp == NULL)
|
||||
/* insufficient pages, don't use any of them. */
|
||||
return NULL;
|
||||
page = tmp;
|
||||
}
|
||||
|
||||
/* add end of list marker for the returned list */
|
||||
set_page_private(page, 0);
|
||||
/* actual return value, and adjustment of head */
|
||||
page = *head;
|
||||
*head = tmp;
|
||||
return page;
|
||||
}
|
||||
|
||||
/* may be used outside of locks to find the tail of a (usually short)
|
||||
* "private" page chain, before adding it back to a global chain head
|
||||
* with page_chain_add() under a spinlock. */
|
||||
static struct page *page_chain_tail(struct page *page, int *len)
|
||||
{
|
||||
struct page *tmp;
|
||||
int i = 1;
|
||||
while ((tmp = page_chain_next(page))) {
|
||||
++i;
|
||||
page = tmp;
|
||||
}
|
||||
if (len)
|
||||
*len = i;
|
||||
return page;
|
||||
}
|
||||
|
||||
static int page_chain_free(struct page *page)
|
||||
{
|
||||
struct page *tmp;
|
||||
int i = 0;
|
||||
page_chain_for_each_safe(page, tmp) {
|
||||
put_page(page);
|
||||
++i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static void page_chain_add(struct page **head,
|
||||
struct page *chain_first, struct page *chain_last)
|
||||
{
|
||||
#if 1
|
||||
struct page *tmp;
|
||||
tmp = page_chain_tail(chain_first, NULL);
|
||||
BUG_ON(tmp != chain_last);
|
||||
#endif
|
||||
|
||||
/* add chain to head */
|
||||
set_page_private(chain_last, (unsigned long)*head);
|
||||
*head = chain_first;
|
||||
}
|
||||
|
||||
static struct page *__drbd_alloc_pages(struct drbd_device *device,
|
||||
unsigned int number)
|
||||
static struct page *__drbd_alloc_pages(unsigned int number)
|
||||
{
|
||||
struct page *page = NULL;
|
||||
struct page *tmp = NULL;
|
||||
unsigned int i = 0;
|
||||
|
||||
/* Yes, testing drbd_pp_vacant outside the lock is racy.
|
||||
* So what. It saves a spin_lock. */
|
||||
if (drbd_pp_vacant >= number) {
|
||||
spin_lock(&drbd_pp_lock);
|
||||
page = page_chain_del(&drbd_pp_pool, number);
|
||||
if (page)
|
||||
drbd_pp_vacant -= number;
|
||||
spin_unlock(&drbd_pp_lock);
|
||||
if (page)
|
||||
return page;
|
||||
}
|
||||
|
||||
/* GFP_TRY, because we must not cause arbitrary write-out: in a DRBD
|
||||
* "criss-cross" setup, that might cause write-out on some other DRBD,
|
||||
* which in turn might block on the other node at this very place. */
|
||||
for (i = 0; i < number; i++) {
|
||||
tmp = alloc_page(GFP_TRY);
|
||||
tmp = mempool_alloc(&drbd_buffer_page_pool, GFP_TRY);
|
||||
if (!tmp)
|
||||
break;
|
||||
goto fail;
|
||||
set_page_private(tmp, (unsigned long)page);
|
||||
page = tmp;
|
||||
}
|
||||
|
||||
if (i == number)
|
||||
return page;
|
||||
|
||||
/* Not enough pages immediately available this time.
|
||||
* No need to jump around here, drbd_alloc_pages will retry this
|
||||
* function "soon". */
|
||||
if (page) {
|
||||
tmp = page_chain_tail(page, NULL);
|
||||
spin_lock(&drbd_pp_lock);
|
||||
page_chain_add(&drbd_pp_pool, page, tmp);
|
||||
drbd_pp_vacant += i;
|
||||
spin_unlock(&drbd_pp_lock);
|
||||
return page;
|
||||
fail:
|
||||
page_chain_for_each_safe(page, tmp) {
|
||||
set_page_private(page, 0);
|
||||
mempool_free(page, &drbd_buffer_page_pool);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void reclaim_finished_net_peer_reqs(struct drbd_device *device,
|
||||
struct list_head *to_be_freed)
|
||||
{
|
||||
struct drbd_peer_request *peer_req, *tmp;
|
||||
|
||||
/* The EEs are always appended to the end of the list. Since
|
||||
they are sent in order over the wire, they have to finish
|
||||
in order. As soon as we see the first not finished we can
|
||||
stop to examine the list... */
|
||||
|
||||
list_for_each_entry_safe(peer_req, tmp, &device->net_ee, w.list) {
|
||||
if (drbd_peer_req_has_active_page(peer_req))
|
||||
break;
|
||||
list_move(&peer_req->w.list, to_be_freed);
|
||||
}
|
||||
}
|
||||
|
||||
static void drbd_reclaim_net_peer_reqs(struct drbd_device *device)
|
||||
{
|
||||
LIST_HEAD(reclaimed);
|
||||
struct drbd_peer_request *peer_req, *t;
|
||||
|
||||
spin_lock_irq(&device->resource->req_lock);
|
||||
reclaim_finished_net_peer_reqs(device, &reclaimed);
|
||||
spin_unlock_irq(&device->resource->req_lock);
|
||||
list_for_each_entry_safe(peer_req, t, &reclaimed, w.list)
|
||||
drbd_free_net_peer_req(device, peer_req);
|
||||
}
|
||||
|
||||
static void conn_reclaim_net_peer_reqs(struct drbd_connection *connection)
|
||||
{
|
||||
struct drbd_peer_device *peer_device;
|
||||
int vnr;
|
||||
|
||||
rcu_read_lock();
|
||||
idr_for_each_entry(&connection->peer_devices, peer_device, vnr) {
|
||||
struct drbd_device *device = peer_device->device;
|
||||
if (!atomic_read(&device->pp_in_use_by_net))
|
||||
continue;
|
||||
|
||||
kref_get(&device->kref);
|
||||
rcu_read_unlock();
|
||||
drbd_reclaim_net_peer_reqs(device);
|
||||
kref_put(&device->kref, drbd_destroy_device);
|
||||
rcu_read_lock();
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* drbd_alloc_pages() - Returns @number pages, retries forever (or until signalled)
|
||||
* @peer_device: DRBD device.
|
||||
@@ -263,9 +113,8 @@ struct page *drbd_alloc_pages(struct drbd_peer_device *peer_device, unsigned int
|
||||
bool retry)
|
||||
{
|
||||
struct drbd_device *device = peer_device->device;
|
||||
struct page *page = NULL;
|
||||
struct page *page;
|
||||
struct net_conf *nc;
|
||||
DEFINE_WAIT(wait);
|
||||
unsigned int mxb;
|
||||
|
||||
rcu_read_lock();
|
||||
@@ -273,37 +122,9 @@ struct page *drbd_alloc_pages(struct drbd_peer_device *peer_device, unsigned int
|
||||
mxb = nc ? nc->max_buffers : 1000000;
|
||||
rcu_read_unlock();
|
||||
|
||||
if (atomic_read(&device->pp_in_use) < mxb)
|
||||
page = __drbd_alloc_pages(device, number);
|
||||
|
||||
/* Try to keep the fast path fast, but occasionally we need
|
||||
* to reclaim the pages we lended to the network stack. */
|
||||
if (page && atomic_read(&device->pp_in_use_by_net) > 512)
|
||||
drbd_reclaim_net_peer_reqs(device);
|
||||
|
||||
while (page == NULL) {
|
||||
prepare_to_wait(&drbd_pp_wait, &wait, TASK_INTERRUPTIBLE);
|
||||
|
||||
drbd_reclaim_net_peer_reqs(device);
|
||||
|
||||
if (atomic_read(&device->pp_in_use) < mxb) {
|
||||
page = __drbd_alloc_pages(device, number);
|
||||
if (page)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!retry)
|
||||
break;
|
||||
|
||||
if (signal_pending(current)) {
|
||||
drbd_warn(device, "drbd_alloc_pages interrupted!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (schedule_timeout(HZ/10) == 0)
|
||||
mxb = UINT_MAX;
|
||||
}
|
||||
finish_wait(&drbd_pp_wait, &wait);
|
||||
if (atomic_read(&device->pp_in_use) >= mxb)
|
||||
schedule_timeout_interruptible(HZ / 10);
|
||||
page = __drbd_alloc_pages(number);
|
||||
|
||||
if (page)
|
||||
atomic_add(number, &device->pp_in_use);
|
||||
@@ -314,29 +135,25 @@ struct page *drbd_alloc_pages(struct drbd_peer_device *peer_device, unsigned int
|
||||
* Is also used from inside an other spin_lock_irq(&resource->req_lock);
|
||||
* Either links the page chain back to the global pool,
|
||||
* or returns all pages to the system. */
|
||||
static void drbd_free_pages(struct drbd_device *device, struct page *page, int is_net)
|
||||
static void drbd_free_pages(struct drbd_device *device, struct page *page)
|
||||
{
|
||||
atomic_t *a = is_net ? &device->pp_in_use_by_net : &device->pp_in_use;
|
||||
int i;
|
||||
struct page *tmp;
|
||||
int i = 0;
|
||||
|
||||
if (page == NULL)
|
||||
return;
|
||||
|
||||
if (drbd_pp_vacant > (DRBD_MAX_BIO_SIZE/PAGE_SIZE) * drbd_minor_count)
|
||||
i = page_chain_free(page);
|
||||
else {
|
||||
struct page *tmp;
|
||||
tmp = page_chain_tail(page, &i);
|
||||
spin_lock(&drbd_pp_lock);
|
||||
page_chain_add(&drbd_pp_pool, page, tmp);
|
||||
drbd_pp_vacant += i;
|
||||
spin_unlock(&drbd_pp_lock);
|
||||
page_chain_for_each_safe(page, tmp) {
|
||||
set_page_private(page, 0);
|
||||
if (page_count(page) == 1)
|
||||
mempool_free(page, &drbd_buffer_page_pool);
|
||||
else
|
||||
put_page(page);
|
||||
i++;
|
||||
}
|
||||
i = atomic_sub_return(i, a);
|
||||
i = atomic_sub_return(i, &device->pp_in_use);
|
||||
if (i < 0)
|
||||
drbd_warn(device, "ASSERTION FAILED: %s: %d < 0\n",
|
||||
is_net ? "pp_in_use_by_net" : "pp_in_use", i);
|
||||
wake_up(&drbd_pp_wait);
|
||||
drbd_warn(device, "ASSERTION FAILED: pp_in_use: %d < 0\n", i);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -380,6 +197,8 @@ drbd_alloc_peer_req(struct drbd_peer_device *peer_device, u64 id, sector_t secto
|
||||
gfpflags_allow_blocking(gfp_mask));
|
||||
if (!page)
|
||||
goto fail;
|
||||
if (!mempool_is_saturated(&drbd_buffer_page_pool))
|
||||
peer_req->flags |= EE_RELEASE_TO_MEMPOOL;
|
||||
}
|
||||
|
||||
memset(peer_req, 0, sizeof(*peer_req));
|
||||
@@ -403,13 +222,12 @@ drbd_alloc_peer_req(struct drbd_peer_device *peer_device, u64 id, sector_t secto
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void __drbd_free_peer_req(struct drbd_device *device, struct drbd_peer_request *peer_req,
|
||||
int is_net)
|
||||
void drbd_free_peer_req(struct drbd_device *device, struct drbd_peer_request *peer_req)
|
||||
{
|
||||
might_sleep();
|
||||
if (peer_req->flags & EE_HAS_DIGEST)
|
||||
kfree(peer_req->digest);
|
||||
drbd_free_pages(device, peer_req->pages, is_net);
|
||||
drbd_free_pages(device, peer_req->pages);
|
||||
D_ASSERT(device, atomic_read(&peer_req->pending_bios) == 0);
|
||||
D_ASSERT(device, drbd_interval_empty(&peer_req->i));
|
||||
if (!expect(device, !(peer_req->flags & EE_CALL_AL_COMPLETE_IO))) {
|
||||
@@ -424,14 +242,13 @@ int drbd_free_peer_reqs(struct drbd_device *device, struct list_head *list)
|
||||
LIST_HEAD(work_list);
|
||||
struct drbd_peer_request *peer_req, *t;
|
||||
int count = 0;
|
||||
int is_net = list == &device->net_ee;
|
||||
|
||||
spin_lock_irq(&device->resource->req_lock);
|
||||
list_splice_init(list, &work_list);
|
||||
spin_unlock_irq(&device->resource->req_lock);
|
||||
|
||||
list_for_each_entry_safe(peer_req, t, &work_list, w.list) {
|
||||
__drbd_free_peer_req(device, peer_req, is_net);
|
||||
drbd_free_peer_req(device, peer_req);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
@@ -443,18 +260,13 @@ int drbd_free_peer_reqs(struct drbd_device *device, struct list_head *list)
|
||||
static int drbd_finish_peer_reqs(struct drbd_device *device)
|
||||
{
|
||||
LIST_HEAD(work_list);
|
||||
LIST_HEAD(reclaimed);
|
||||
struct drbd_peer_request *peer_req, *t;
|
||||
int err = 0;
|
||||
|
||||
spin_lock_irq(&device->resource->req_lock);
|
||||
reclaim_finished_net_peer_reqs(device, &reclaimed);
|
||||
list_splice_init(&device->done_ee, &work_list);
|
||||
spin_unlock_irq(&device->resource->req_lock);
|
||||
|
||||
list_for_each_entry_safe(peer_req, t, &reclaimed, w.list)
|
||||
drbd_free_net_peer_req(device, peer_req);
|
||||
|
||||
/* possible callbacks here:
|
||||
* e_end_block, and e_end_resync_block, e_send_superseded.
|
||||
* all ignore the last argument.
|
||||
@@ -1975,7 +1787,7 @@ static int drbd_drain_block(struct drbd_peer_device *peer_device, int data_size)
|
||||
data_size -= len;
|
||||
}
|
||||
kunmap(page);
|
||||
drbd_free_pages(peer_device->device, page, 0);
|
||||
drbd_free_pages(peer_device->device, page);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -5224,16 +5036,6 @@ static int drbd_disconnected(struct drbd_peer_device *peer_device)
|
||||
put_ldev(device);
|
||||
}
|
||||
|
||||
/* tcp_close and release of sendpage pages can be deferred. I don't
|
||||
* want to use SO_LINGER, because apparently it can be deferred for
|
||||
* more than 20 seconds (longest time I checked).
|
||||
*
|
||||
* Actually we don't care for exactly when the network stack does its
|
||||
* put_page(), but release our reference on these pages right here.
|
||||
*/
|
||||
i = drbd_free_peer_reqs(device, &device->net_ee);
|
||||
if (i)
|
||||
drbd_info(device, "net_ee not empty, killed %u entries\n", i);
|
||||
i = atomic_read(&device->pp_in_use_by_net);
|
||||
if (i)
|
||||
drbd_info(device, "pp_in_use_by_net = %d, expected 0\n", i);
|
||||
@@ -5980,8 +5782,6 @@ int drbd_ack_receiver(struct drbd_thread *thi)
|
||||
while (get_t_state(thi) == RUNNING) {
|
||||
drbd_thread_current_set_cpu(thi);
|
||||
|
||||
conn_reclaim_net_peer_reqs(connection);
|
||||
|
||||
if (test_and_clear_bit(SEND_PING, &connection->flags)) {
|
||||
if (drbd_send_ping(connection)) {
|
||||
drbd_err(connection, "drbd_send_ping has failed\n");
|
||||
|
||||
@@ -1030,22 +1030,6 @@ out:
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* helper */
|
||||
static void move_to_net_ee_or_free(struct drbd_device *device, struct drbd_peer_request *peer_req)
|
||||
{
|
||||
if (drbd_peer_req_has_active_page(peer_req)) {
|
||||
/* This might happen if sendpage() has not finished */
|
||||
int i = PFN_UP(peer_req->i.size);
|
||||
atomic_add(i, &device->pp_in_use_by_net);
|
||||
atomic_sub(i, &device->pp_in_use);
|
||||
spin_lock_irq(&device->resource->req_lock);
|
||||
list_add_tail(&peer_req->w.list, &device->net_ee);
|
||||
spin_unlock_irq(&device->resource->req_lock);
|
||||
wake_up(&drbd_pp_wait);
|
||||
} else
|
||||
drbd_free_peer_req(device, peer_req);
|
||||
}
|
||||
|
||||
/**
|
||||
* w_e_end_data_req() - Worker callback, to send a P_DATA_REPLY packet in response to a P_DATA_REQUEST
|
||||
* @w: work object.
|
||||
@@ -1059,9 +1043,8 @@ int w_e_end_data_req(struct drbd_work *w, int cancel)
|
||||
int err;
|
||||
|
||||
if (unlikely(cancel)) {
|
||||
drbd_free_peer_req(device, peer_req);
|
||||
dec_unacked(device);
|
||||
return 0;
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (likely((peer_req->flags & EE_WAS_ERROR) == 0)) {
|
||||
@@ -1074,12 +1057,12 @@ int w_e_end_data_req(struct drbd_work *w, int cancel)
|
||||
err = drbd_send_ack(peer_device, P_NEG_DREPLY, peer_req);
|
||||
}
|
||||
|
||||
dec_unacked(device);
|
||||
|
||||
move_to_net_ee_or_free(device, peer_req);
|
||||
|
||||
if (unlikely(err))
|
||||
drbd_err(device, "drbd_send_block() failed\n");
|
||||
out:
|
||||
dec_unacked(device);
|
||||
drbd_free_peer_req(device, peer_req);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1120,9 +1103,8 @@ int w_e_end_rsdata_req(struct drbd_work *w, int cancel)
|
||||
int err;
|
||||
|
||||
if (unlikely(cancel)) {
|
||||
drbd_free_peer_req(device, peer_req);
|
||||
dec_unacked(device);
|
||||
return 0;
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (get_ldev_if_state(device, D_FAILED)) {
|
||||
@@ -1155,13 +1137,12 @@ int w_e_end_rsdata_req(struct drbd_work *w, int cancel)
|
||||
/* update resync data with failure */
|
||||
drbd_rs_failed_io(peer_device, peer_req->i.sector, peer_req->i.size);
|
||||
}
|
||||
|
||||
dec_unacked(device);
|
||||
|
||||
move_to_net_ee_or_free(device, peer_req);
|
||||
|
||||
if (unlikely(err))
|
||||
drbd_err(device, "drbd_send_block() failed\n");
|
||||
out:
|
||||
dec_unacked(device);
|
||||
drbd_free_peer_req(device, peer_req);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1176,9 +1157,8 @@ int w_e_end_csum_rs_req(struct drbd_work *w, int cancel)
|
||||
int err, eq = 0;
|
||||
|
||||
if (unlikely(cancel)) {
|
||||
drbd_free_peer_req(device, peer_req);
|
||||
dec_unacked(device);
|
||||
return 0;
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (get_ldev(device)) {
|
||||
@@ -1220,12 +1200,12 @@ int w_e_end_csum_rs_req(struct drbd_work *w, int cancel)
|
||||
if (drbd_ratelimit())
|
||||
drbd_err(device, "Sending NegDReply. I guess it gets messy.\n");
|
||||
}
|
||||
|
||||
dec_unacked(device);
|
||||
move_to_net_ee_or_free(device, peer_req);
|
||||
|
||||
if (unlikely(err))
|
||||
drbd_err(device, "drbd_send_block/ack() failed\n");
|
||||
out:
|
||||
dec_unacked(device);
|
||||
drbd_free_peer_req(device, peer_req);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ struct ublk_device {
|
||||
|
||||
struct completion completion;
|
||||
unsigned int nr_queues_ready;
|
||||
unsigned int nr_privileged_daemon;
|
||||
bool unprivileged_daemons;
|
||||
struct mutex cancel_mutex;
|
||||
bool canceling;
|
||||
pid_t ublksrv_tgid;
|
||||
@@ -1389,7 +1389,7 @@ static blk_status_t ublk_prep_req(struct ublk_queue *ubq, struct request *rq,
|
||||
{
|
||||
blk_status_t res;
|
||||
|
||||
if (unlikely(ubq->fail_io))
|
||||
if (unlikely(READ_ONCE(ubq->fail_io)))
|
||||
return BLK_STS_TARGET;
|
||||
|
||||
/* With recovery feature enabled, force_abort is set in
|
||||
@@ -1401,7 +1401,8 @@ static blk_status_t ublk_prep_req(struct ublk_queue *ubq, struct request *rq,
|
||||
* Note: force_abort is guaranteed to be seen because it is set
|
||||
* before request queue is unqiuesced.
|
||||
*/
|
||||
if (ublk_nosrv_should_queue_io(ubq) && unlikely(ubq->force_abort))
|
||||
if (ublk_nosrv_should_queue_io(ubq) &&
|
||||
unlikely(READ_ONCE(ubq->force_abort)))
|
||||
return BLK_STS_IOERR;
|
||||
|
||||
if (check_cancel && unlikely(ubq->canceling))
|
||||
@@ -1550,7 +1551,7 @@ static void ublk_reset_ch_dev(struct ublk_device *ub)
|
||||
/* set to NULL, otherwise new tasks cannot mmap io_cmd_buf */
|
||||
ub->mm = NULL;
|
||||
ub->nr_queues_ready = 0;
|
||||
ub->nr_privileged_daemon = 0;
|
||||
ub->unprivileged_daemons = false;
|
||||
ub->ublksrv_tgid = -1;
|
||||
}
|
||||
|
||||
@@ -1644,7 +1645,6 @@ static int ublk_ch_release(struct inode *inode, struct file *filp)
|
||||
* Transition the device to the nosrv state. What exactly this
|
||||
* means depends on the recovery flags
|
||||
*/
|
||||
blk_mq_quiesce_queue(disk->queue);
|
||||
if (ublk_nosrv_should_stop_dev(ub)) {
|
||||
/*
|
||||
* Allow any pending/future I/O to pass through quickly
|
||||
@@ -1652,8 +1652,7 @@ static int ublk_ch_release(struct inode *inode, struct file *filp)
|
||||
* waits for all pending I/O to complete
|
||||
*/
|
||||
for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
|
||||
ublk_get_queue(ub, i)->force_abort = true;
|
||||
blk_mq_unquiesce_queue(disk->queue);
|
||||
WRITE_ONCE(ublk_get_queue(ub, i)->force_abort, true);
|
||||
|
||||
ublk_stop_dev_unlocked(ub);
|
||||
} else {
|
||||
@@ -1663,9 +1662,8 @@ static int ublk_ch_release(struct inode *inode, struct file *filp)
|
||||
} else {
|
||||
ub->dev_info.state = UBLK_S_DEV_FAIL_IO;
|
||||
for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
|
||||
ublk_get_queue(ub, i)->fail_io = true;
|
||||
WRITE_ONCE(ublk_get_queue(ub, i)->fail_io, true);
|
||||
}
|
||||
blk_mq_unquiesce_queue(disk->queue);
|
||||
}
|
||||
unlock:
|
||||
mutex_unlock(&ub->mutex);
|
||||
@@ -1980,12 +1978,10 @@ static void ublk_mark_io_ready(struct ublk_device *ub, struct ublk_queue *ubq)
|
||||
__must_hold(&ub->mutex)
|
||||
{
|
||||
ubq->nr_io_ready++;
|
||||
if (ublk_queue_ready(ubq)) {
|
||||
if (ublk_queue_ready(ubq))
|
||||
ub->nr_queues_ready++;
|
||||
|
||||
if (capable(CAP_SYS_ADMIN))
|
||||
ub->nr_privileged_daemon++;
|
||||
}
|
||||
if (!ub->unprivileged_daemons && !capable(CAP_SYS_ADMIN))
|
||||
ub->unprivileged_daemons = true;
|
||||
|
||||
if (ub->nr_queues_ready == ub->dev_info.nr_hw_queues) {
|
||||
/* now we are ready for handling ublk io request */
|
||||
@@ -2880,8 +2876,8 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub,
|
||||
|
||||
ublk_apply_params(ub);
|
||||
|
||||
/* don't probe partitions if any one ubq daemon is un-trusted */
|
||||
if (ub->nr_privileged_daemon != ub->nr_queues_ready)
|
||||
/* don't probe partitions if any daemon task is un-trusted */
|
||||
if (ub->unprivileged_daemons)
|
||||
set_bit(GD_SUPPRESS_PART_SCAN, &disk->state);
|
||||
|
||||
ublk_get_device(ub);
|
||||
|
||||
@@ -2793,6 +2793,7 @@ static const struct x86_cpu_id intel_pstate_cpu_oob_ids[] __initconst = {
|
||||
X86_MATCH(INTEL_GRANITERAPIDS_X, core_funcs),
|
||||
X86_MATCH(INTEL_ATOM_CRESTMONT, core_funcs),
|
||||
X86_MATCH(INTEL_ATOM_CRESTMONT_X, core_funcs),
|
||||
X86_MATCH(INTEL_ATOM_DARKMONT_X, core_funcs),
|
||||
{}
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -97,6 +97,14 @@ static inline int which_bucket(u64 duration_ns)
|
||||
|
||||
static DEFINE_PER_CPU(struct menu_device, menu_devices);
|
||||
|
||||
static void menu_update_intervals(struct menu_device *data, unsigned int interval_us)
|
||||
{
|
||||
/* Update the repeating-pattern data. */
|
||||
data->intervals[data->interval_ptr++] = interval_us;
|
||||
if (data->interval_ptr >= INTERVALS)
|
||||
data->interval_ptr = 0;
|
||||
}
|
||||
|
||||
static void menu_update(struct cpuidle_driver *drv, struct cpuidle_device *dev);
|
||||
|
||||
/*
|
||||
@@ -222,6 +230,14 @@ static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev,
|
||||
if (data->needs_update) {
|
||||
menu_update(drv, dev);
|
||||
data->needs_update = 0;
|
||||
} else if (!dev->last_residency_ns) {
|
||||
/*
|
||||
* This happens when the driver rejects the previously selected
|
||||
* idle state and returns an error, so update the recent
|
||||
* intervals table to prevent invalid information from being
|
||||
* used going forward.
|
||||
*/
|
||||
menu_update_intervals(data, UINT_MAX);
|
||||
}
|
||||
|
||||
/* Find the shortest expected idle interval. */
|
||||
@@ -482,10 +498,7 @@ static void menu_update(struct cpuidle_driver *drv, struct cpuidle_device *dev)
|
||||
|
||||
data->correction_factor[data->bucket] = new_factor;
|
||||
|
||||
/* update the repeating-pattern data */
|
||||
data->intervals[data->interval_ptr++] = ktime_to_us(measured_ns);
|
||||
if (data->interval_ptr >= INTERVALS)
|
||||
data->interval_ptr = 0;
|
||||
menu_update_intervals(data, ktime_to_us(measured_ns));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -550,6 +550,23 @@ const struct fw_address_region fw_unit_space_region =
|
||||
{ .start = 0xfffff0000900ULL, .end = 0x1000000000000ULL, };
|
||||
#endif /* 0 */
|
||||
|
||||
static void complete_address_handler(struct kref *kref)
|
||||
{
|
||||
struct fw_address_handler *handler = container_of(kref, struct fw_address_handler, kref);
|
||||
|
||||
complete(&handler->done);
|
||||
}
|
||||
|
||||
static void get_address_handler(struct fw_address_handler *handler)
|
||||
{
|
||||
kref_get(&handler->kref);
|
||||
}
|
||||
|
||||
static int put_address_handler(struct fw_address_handler *handler)
|
||||
{
|
||||
return kref_put(&handler->kref, complete_address_handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* fw_core_add_address_handler() - register for incoming requests
|
||||
* @handler: callback
|
||||
@@ -596,6 +613,8 @@ int fw_core_add_address_handler(struct fw_address_handler *handler,
|
||||
if (other != NULL) {
|
||||
handler->offset += other->length;
|
||||
} else {
|
||||
init_completion(&handler->done);
|
||||
kref_init(&handler->kref);
|
||||
list_add_tail_rcu(&handler->link, &address_handler_list);
|
||||
ret = 0;
|
||||
break;
|
||||
@@ -621,6 +640,9 @@ void fw_core_remove_address_handler(struct fw_address_handler *handler)
|
||||
list_del_rcu(&handler->link);
|
||||
|
||||
synchronize_rcu();
|
||||
|
||||
if (!put_address_handler(handler))
|
||||
wait_for_completion(&handler->done);
|
||||
}
|
||||
EXPORT_SYMBOL(fw_core_remove_address_handler);
|
||||
|
||||
@@ -914,22 +936,31 @@ static void handle_exclusive_region_request(struct fw_card *card,
|
||||
handler = lookup_enclosing_address_handler(&address_handler_list, offset,
|
||||
request->length);
|
||||
if (handler)
|
||||
handler->address_callback(card, request, tcode, destination, source,
|
||||
p->generation, offset, request->data,
|
||||
request->length, handler->callback_data);
|
||||
get_address_handler(handler);
|
||||
}
|
||||
|
||||
if (!handler)
|
||||
if (!handler) {
|
||||
fw_send_response(card, request, RCODE_ADDRESS_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Outside the RCU read-side critical section. Without spinlock. With reference count.
|
||||
handler->address_callback(card, request, tcode, destination, source, p->generation, offset,
|
||||
request->data, request->length, handler->callback_data);
|
||||
put_address_handler(handler);
|
||||
}
|
||||
|
||||
// To use kmalloc allocator efficiently, this should be power of two.
|
||||
#define BUFFER_ON_KERNEL_STACK_SIZE 4
|
||||
|
||||
static void handle_fcp_region_request(struct fw_card *card,
|
||||
struct fw_packet *p,
|
||||
struct fw_request *request,
|
||||
unsigned long long offset)
|
||||
{
|
||||
struct fw_address_handler *handler;
|
||||
int tcode, destination, source;
|
||||
struct fw_address_handler *buffer_on_kernel_stack[BUFFER_ON_KERNEL_STACK_SIZE];
|
||||
struct fw_address_handler *handler, **handlers;
|
||||
int tcode, destination, source, i, count, buffer_size;
|
||||
|
||||
if ((offset != (CSR_REGISTER_BASE | CSR_FCP_COMMAND) &&
|
||||
offset != (CSR_REGISTER_BASE | CSR_FCP_RESPONSE)) ||
|
||||
@@ -950,15 +981,55 @@ static void handle_fcp_region_request(struct fw_card *card,
|
||||
return;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
handlers = buffer_on_kernel_stack;
|
||||
buffer_size = ARRAY_SIZE(buffer_on_kernel_stack);
|
||||
scoped_guard(rcu) {
|
||||
list_for_each_entry_rcu(handler, &address_handler_list, link) {
|
||||
if (is_enclosing_handler(handler, offset, request->length))
|
||||
handler->address_callback(card, request, tcode, destination, source,
|
||||
p->generation, offset, request->data,
|
||||
request->length, handler->callback_data);
|
||||
if (is_enclosing_handler(handler, offset, request->length)) {
|
||||
if (count >= buffer_size) {
|
||||
int next_size = buffer_size * 2;
|
||||
struct fw_address_handler **buffer_on_kernel_heap;
|
||||
|
||||
if (handlers == buffer_on_kernel_stack)
|
||||
buffer_on_kernel_heap = NULL;
|
||||
else
|
||||
buffer_on_kernel_heap = handlers;
|
||||
|
||||
buffer_on_kernel_heap =
|
||||
krealloc_array(buffer_on_kernel_heap, next_size,
|
||||
sizeof(*buffer_on_kernel_heap), GFP_ATOMIC);
|
||||
// FCP is used for purposes unrelated to significant system
|
||||
// resources (e.g. storage or networking), so allocation
|
||||
// failures are not considered so critical.
|
||||
if (!buffer_on_kernel_heap)
|
||||
break;
|
||||
|
||||
if (handlers == buffer_on_kernel_stack) {
|
||||
memcpy(buffer_on_kernel_heap, buffer_on_kernel_stack,
|
||||
sizeof(buffer_on_kernel_stack));
|
||||
}
|
||||
|
||||
handlers = buffer_on_kernel_heap;
|
||||
buffer_size = next_size;
|
||||
}
|
||||
get_address_handler(handler);
|
||||
handlers[count++] = handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < count; ++i) {
|
||||
handler = handlers[i];
|
||||
handler->address_callback(card, request, tcode, destination, source,
|
||||
p->generation, offset, request->data,
|
||||
request->length, handler->callback_data);
|
||||
put_address_handler(handler);
|
||||
}
|
||||
|
||||
if (handlers != buffer_on_kernel_stack)
|
||||
kfree(handlers);
|
||||
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
}
|
||||
|
||||
|
||||
@@ -190,9 +190,7 @@ static int mlxbf3_gpio_probe(struct platform_device *pdev)
|
||||
struct mlxbf3_gpio_context *gs;
|
||||
struct gpio_irq_chip *girq;
|
||||
struct gpio_chip *gc;
|
||||
char *colon_ptr;
|
||||
int ret, irq;
|
||||
long num;
|
||||
|
||||
gs = devm_kzalloc(dev, sizeof(*gs), GFP_KERNEL);
|
||||
if (!gs)
|
||||
@@ -229,39 +227,25 @@ static int mlxbf3_gpio_probe(struct platform_device *pdev)
|
||||
gc->owner = THIS_MODULE;
|
||||
gc->add_pin_ranges = mlxbf3_gpio_add_pin_ranges;
|
||||
|
||||
colon_ptr = strchr(dev_name(dev), ':');
|
||||
if (!colon_ptr) {
|
||||
dev_err(dev, "invalid device name format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
irq = platform_get_irq_optional(pdev, 0);
|
||||
if (irq >= 0) {
|
||||
girq = &gs->gc.irq;
|
||||
gpio_irq_chip_set_chip(girq, &gpio_mlxbf3_irqchip);
|
||||
girq->default_type = IRQ_TYPE_NONE;
|
||||
/* This will let us handle the parent IRQ in the driver */
|
||||
girq->num_parents = 0;
|
||||
girq->parents = NULL;
|
||||
girq->parent_handler = NULL;
|
||||
girq->handler = handle_bad_irq;
|
||||
|
||||
ret = kstrtol(++colon_ptr, 16, &num);
|
||||
if (ret) {
|
||||
dev_err(dev, "invalid device instance\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!num) {
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq >= 0) {
|
||||
girq = &gs->gc.irq;
|
||||
gpio_irq_chip_set_chip(girq, &gpio_mlxbf3_irqchip);
|
||||
girq->default_type = IRQ_TYPE_NONE;
|
||||
/* This will let us handle the parent IRQ in the driver */
|
||||
girq->num_parents = 0;
|
||||
girq->parents = NULL;
|
||||
girq->parent_handler = NULL;
|
||||
girq->handler = handle_bad_irq;
|
||||
|
||||
/*
|
||||
* Directly request the irq here instead of passing
|
||||
* a flow-handler because the irq is shared.
|
||||
*/
|
||||
ret = devm_request_irq(dev, irq, mlxbf3_gpio_irq_handler,
|
||||
IRQF_SHARED, dev_name(dev), gs);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to request IRQ");
|
||||
}
|
||||
/*
|
||||
* Directly request the irq here instead of passing
|
||||
* a flow-handler because the irq is shared.
|
||||
*/
|
||||
ret = devm_request_irq(dev, irq, mlxbf3_gpio_irq_handler,
|
||||
IRQF_SHARED, dev_name(dev), gs);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to request IRQ");
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, gs);
|
||||
|
||||
@@ -1139,6 +1139,9 @@ static int amdgpu_cs_vm_handling(struct amdgpu_cs_parser *p)
|
||||
}
|
||||
}
|
||||
|
||||
if (!amdgpu_vm_ready(vm))
|
||||
return -EINVAL;
|
||||
|
||||
r = amdgpu_vm_clear_freed(adev, vm, NULL);
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
@@ -88,8 +88,8 @@ int amdgpu_map_static_csa(struct amdgpu_device *adev, struct amdgpu_vm *vm,
|
||||
}
|
||||
|
||||
r = amdgpu_vm_bo_map(adev, *bo_va, csa_addr, 0, size,
|
||||
AMDGPU_PTE_READABLE | AMDGPU_PTE_WRITEABLE |
|
||||
AMDGPU_PTE_EXECUTABLE);
|
||||
AMDGPU_VM_PAGE_READABLE | AMDGPU_VM_PAGE_WRITEABLE |
|
||||
AMDGPU_VM_PAGE_EXECUTABLE);
|
||||
|
||||
if (r) {
|
||||
DRM_ERROR("failed to do bo_map on static CSA, err=%d\n", r);
|
||||
|
||||
@@ -1039,15 +1039,28 @@ int psp_update_fw_reservation(struct psp_context *psp)
|
||||
{
|
||||
int ret;
|
||||
uint64_t reserv_addr, reserv_addr_ext;
|
||||
uint32_t reserv_size, reserv_size_ext;
|
||||
uint32_t reserv_size, reserv_size_ext, mp0_ip_ver;
|
||||
struct amdgpu_device *adev = psp->adev;
|
||||
|
||||
mp0_ip_ver = amdgpu_ip_version(adev, MP0_HWIP, 0);
|
||||
|
||||
if (amdgpu_sriov_vf(psp->adev))
|
||||
return 0;
|
||||
|
||||
if ((amdgpu_ip_version(adev, MP0_HWIP, 0) != IP_VERSION(14, 0, 2)) &&
|
||||
(amdgpu_ip_version(adev, MP0_HWIP, 0) != IP_VERSION(14, 0, 3)))
|
||||
switch (mp0_ip_ver) {
|
||||
case IP_VERSION(14, 0, 2):
|
||||
if (adev->psp.sos.fw_version < 0x3b0e0d)
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case IP_VERSION(14, 0, 3):
|
||||
if (adev->psp.sos.fw_version < 0x3a0e14)
|
||||
return 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = psp_get_fw_reservation_info(psp, GFX_CMD_ID_FB_FW_RESERV_ADDR, &reserv_addr, &reserv_size);
|
||||
if (ret)
|
||||
|
||||
@@ -654,11 +654,10 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct amdgpu_vm *vm,
|
||||
* Check if all VM PDs/PTs are ready for updates
|
||||
*
|
||||
* Returns:
|
||||
* True if VM is not evicting.
|
||||
* True if VM is not evicting and all VM entities are not stopped
|
||||
*/
|
||||
bool amdgpu_vm_ready(struct amdgpu_vm *vm)
|
||||
{
|
||||
bool empty;
|
||||
bool ret;
|
||||
|
||||
amdgpu_vm_eviction_lock(vm);
|
||||
@@ -666,10 +665,18 @@ bool amdgpu_vm_ready(struct amdgpu_vm *vm)
|
||||
amdgpu_vm_eviction_unlock(vm);
|
||||
|
||||
spin_lock(&vm->status_lock);
|
||||
empty = list_empty(&vm->evicted);
|
||||
ret &= list_empty(&vm->evicted);
|
||||
spin_unlock(&vm->status_lock);
|
||||
|
||||
return ret && empty;
|
||||
spin_lock(&vm->immediate.lock);
|
||||
ret &= !vm->immediate.stopped;
|
||||
spin_unlock(&vm->immediate.lock);
|
||||
|
||||
spin_lock(&vm->delayed.lock);
|
||||
ret &= !vm->delayed.stopped;
|
||||
spin_unlock(&vm->delayed.lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -648,9 +648,8 @@ static void amdgpu_vram_mgr_del(struct ttm_resource_manager *man,
|
||||
list_for_each_entry(block, &vres->blocks, link)
|
||||
vis_usage += amdgpu_vram_mgr_vis_size(adev, block);
|
||||
|
||||
amdgpu_vram_mgr_do_reserve(man);
|
||||
|
||||
drm_buddy_free_list(mm, &vres->blocks, vres->flags);
|
||||
amdgpu_vram_mgr_do_reserve(man);
|
||||
mutex_unlock(&mgr->lock);
|
||||
|
||||
atomic64_sub(vis_usage, &mgr->vis_usage);
|
||||
|
||||
@@ -122,6 +122,7 @@ config DRM_ITE_IT6505
|
||||
select EXTCON
|
||||
select CRYPTO
|
||||
select CRYPTO_HASH
|
||||
select REGMAP_I2C
|
||||
help
|
||||
ITE IT6505 DisplayPort bridge chip driver.
|
||||
|
||||
@@ -316,6 +317,19 @@ config DRM_SIMPLE_BRIDGE
|
||||
Support for non-programmable DRM bridges, such as ADI ADV7123, TI
|
||||
THS8134 and THS8135 or passive resistor ladder DACs.
|
||||
|
||||
config DRM_SOLOMON_SSD2825
|
||||
tristate "SSD2825 RGB/DSI bridge"
|
||||
depends on SPI_MASTER && OF
|
||||
select DRM_MIPI_DSI
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_PANEL
|
||||
help
|
||||
Say Y here if you want support for the Solomon SSD2825 RGB/DSI
|
||||
SPI bridge driver.
|
||||
|
||||
Say M here if you want to support this hardware as a module.
|
||||
The module will be named "ssd2825".
|
||||
|
||||
config DRM_THINE_THC63LVD1024
|
||||
tristate "Thine THC63LVD1024 LVDS decoder bridge"
|
||||
depends on OF
|
||||
@@ -438,6 +452,18 @@ config DRM_TI_TPD12S015
|
||||
Texas Instruments TPD12S015 HDMI level shifter and ESD protection
|
||||
driver.
|
||||
|
||||
config DRM_WAVESHARE_BRIDGE
|
||||
tristate "Waveshare DSI bridge"
|
||||
depends on OF
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
select DRM_PANEL_BRIDGE
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_MIPI_DSI
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Driver for waveshare DSI to DPI bridge board.
|
||||
Please say Y if you have such hardware
|
||||
|
||||
source "drivers/gpu/drm/bridge/analogix/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/bridge/adv7511/Kconfig"
|
||||
|
||||
@@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
|
||||
obj-$(CONFIG_DRM_SII902X) += sii902x.o
|
||||
obj-$(CONFIG_DRM_SII9234) += sii9234.o
|
||||
obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
|
||||
obj-$(CONFIG_DRM_SOLOMON_SSD2825) += ssd2825.o
|
||||
obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
|
||||
obj-$(CONFIG_DRM_TOSHIBA_TC358762) += tc358762.o
|
||||
obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
|
||||
@@ -40,6 +41,7 @@ obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
|
||||
obj-$(CONFIG_DRM_TI_TDP158) += ti-tdp158.o
|
||||
obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
|
||||
obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
|
||||
obj-$(CONFIG_DRM_WAVESHARE_BRIDGE) += waveshare-dsi.o
|
||||
obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi.o
|
||||
obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o
|
||||
|
||||
|
||||
@@ -2604,6 +2604,7 @@ static int anx7625_link_bridge(struct drm_dp_aux *aux)
|
||||
platform->bridge.type = platform->pdata.panel_bridge ?
|
||||
DRM_MODE_CONNECTOR_eDP :
|
||||
DRM_MODE_CONNECTOR_DisplayPort;
|
||||
platform->bridge.support_hdcp = true;
|
||||
|
||||
drm_bridge_add(&platform->bridge);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ static void drm_aux_bridge_release(struct device *dev)
|
||||
{
|
||||
struct auxiliary_device *adev = to_auxiliary_dev(dev);
|
||||
|
||||
of_node_put(dev->of_node);
|
||||
ida_free(&drm_aux_bridge_ida, adev->id);
|
||||
|
||||
kfree(adev);
|
||||
@@ -65,6 +66,7 @@ int drm_aux_bridge_register(struct device *parent)
|
||||
|
||||
ret = auxiliary_device_init(adev);
|
||||
if (ret) {
|
||||
of_node_put(adev->dev.of_node);
|
||||
ida_free(&drm_aux_bridge_ida, adev->id);
|
||||
kfree(adev);
|
||||
return ret;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <video/mipi_display.h>
|
||||
#include <video/videomode.h>
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/interrupt.h>
|
||||
@@ -417,7 +418,8 @@
|
||||
#define DSI_OUTPUT_PORT 0
|
||||
#define DSI_INPUT_PORT(inputid) (1 + (inputid))
|
||||
|
||||
#define DSI_HBP_FRAME_OVERHEAD 12
|
||||
#define DSI_HBP_FRAME_PULSE_OVERHEAD 12
|
||||
#define DSI_HBP_FRAME_EVENT_OVERHEAD 16
|
||||
#define DSI_HSA_FRAME_OVERHEAD 14
|
||||
#define DSI_HFP_FRAME_OVERHEAD 6
|
||||
#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4
|
||||
@@ -452,15 +454,6 @@ bridge_to_cdns_dsi_input(struct drm_bridge *bridge)
|
||||
return container_of(bridge, struct cdns_dsi_input, bridge);
|
||||
}
|
||||
|
||||
static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode,
|
||||
bool mode_valid_check)
|
||||
{
|
||||
if (mode_valid_check)
|
||||
return mode->hsync_start - mode->hdisplay;
|
||||
|
||||
return mode->crtc_hsync_start - mode->crtc_hdisplay;
|
||||
}
|
||||
|
||||
static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing,
|
||||
unsigned int dpi_bpp,
|
||||
unsigned int dsi_pkt_overhead)
|
||||
@@ -476,145 +469,77 @@ static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing,
|
||||
}
|
||||
|
||||
static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi,
|
||||
const struct drm_display_mode *mode,
|
||||
struct cdns_dsi_cfg *dsi_cfg,
|
||||
bool mode_valid_check)
|
||||
const struct videomode *vm,
|
||||
struct cdns_dsi_cfg *dsi_cfg)
|
||||
{
|
||||
struct cdns_dsi_output *output = &dsi->output;
|
||||
unsigned int tmp;
|
||||
bool sync_pulse = false;
|
||||
u32 dpi_hsa, dpi_hbp, dpi_hfp, dpi_hact;
|
||||
bool sync_pulse;
|
||||
int bpp;
|
||||
|
||||
dpi_hsa = vm->hsync_len;
|
||||
dpi_hbp = vm->hback_porch;
|
||||
dpi_hfp = vm->hfront_porch;
|
||||
dpi_hact = vm->hactive;
|
||||
|
||||
memset(dsi_cfg, 0, sizeof(*dsi_cfg));
|
||||
|
||||
if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
|
||||
sync_pulse = true;
|
||||
sync_pulse = output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
|
||||
|
||||
bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format);
|
||||
|
||||
if (mode_valid_check)
|
||||
tmp = mode->htotal -
|
||||
(sync_pulse ? mode->hsync_end : mode->hsync_start);
|
||||
else
|
||||
tmp = mode->crtc_htotal -
|
||||
(sync_pulse ?
|
||||
mode->crtc_hsync_end : mode->crtc_hsync_start);
|
||||
|
||||
dsi_cfg->hbp = dpi_to_dsi_timing(tmp, bpp, DSI_HBP_FRAME_OVERHEAD);
|
||||
|
||||
if (sync_pulse) {
|
||||
if (mode_valid_check)
|
||||
tmp = mode->hsync_end - mode->hsync_start;
|
||||
else
|
||||
tmp = mode->crtc_hsync_end - mode->crtc_hsync_start;
|
||||
dsi_cfg->hbp = dpi_to_dsi_timing(dpi_hbp, bpp,
|
||||
DSI_HBP_FRAME_PULSE_OVERHEAD);
|
||||
|
||||
dsi_cfg->hsa = dpi_to_dsi_timing(tmp, bpp,
|
||||
dsi_cfg->hsa = dpi_to_dsi_timing(dpi_hsa, bpp,
|
||||
DSI_HSA_FRAME_OVERHEAD);
|
||||
} else {
|
||||
dsi_cfg->hbp = dpi_to_dsi_timing(dpi_hbp + dpi_hsa, bpp,
|
||||
DSI_HBP_FRAME_EVENT_OVERHEAD);
|
||||
|
||||
dsi_cfg->hsa = 0;
|
||||
}
|
||||
|
||||
dsi_cfg->hact = dpi_to_dsi_timing(mode_valid_check ?
|
||||
mode->hdisplay : mode->crtc_hdisplay,
|
||||
bpp, 0);
|
||||
dsi_cfg->hfp = dpi_to_dsi_timing(mode_to_dpi_hfp(mode, mode_valid_check),
|
||||
bpp, DSI_HFP_FRAME_OVERHEAD);
|
||||
dsi_cfg->hact = dpi_to_dsi_timing(dpi_hact, bpp, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
dsi_cfg->hfp = dpi_to_dsi_timing(dpi_hfp, bpp, DSI_HFP_FRAME_OVERHEAD);
|
||||
|
||||
static int cdns_dsi_adjust_phy_config(struct cdns_dsi *dsi,
|
||||
struct cdns_dsi_cfg *dsi_cfg,
|
||||
struct phy_configure_opts_mipi_dphy *phy_cfg,
|
||||
const struct drm_display_mode *mode,
|
||||
bool mode_valid_check)
|
||||
{
|
||||
struct cdns_dsi_output *output = &dsi->output;
|
||||
unsigned long long dlane_bps;
|
||||
unsigned long adj_dsi_htotal;
|
||||
unsigned long dsi_htotal;
|
||||
unsigned long dpi_htotal;
|
||||
unsigned long dpi_hz;
|
||||
unsigned int dsi_hfp_ext;
|
||||
unsigned int lanes = output->dev->lanes;
|
||||
dsi_cfg->htotal = dsi_cfg->hact + dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
|
||||
|
||||
dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
|
||||
if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
|
||||
dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
|
||||
|
||||
dsi_htotal += dsi_cfg->hact;
|
||||
dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
|
||||
|
||||
/*
|
||||
* Make sure DSI htotal is aligned on a lane boundary when calculating
|
||||
* the expected data rate. This is done by extending HFP in case of
|
||||
* misalignment.
|
||||
*/
|
||||
adj_dsi_htotal = dsi_htotal;
|
||||
if (dsi_htotal % lanes)
|
||||
adj_dsi_htotal += lanes - (dsi_htotal % lanes);
|
||||
|
||||
dpi_hz = (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000;
|
||||
dlane_bps = (unsigned long long)dpi_hz * adj_dsi_htotal;
|
||||
|
||||
/* data rate in bytes/sec is not an integer, refuse the mode. */
|
||||
dpi_htotal = mode_valid_check ? mode->htotal : mode->crtc_htotal;
|
||||
if (do_div(dlane_bps, lanes * dpi_htotal))
|
||||
return -EINVAL;
|
||||
|
||||
/* data rate was in bytes/sec, convert to bits/sec. */
|
||||
phy_cfg->hs_clk_rate = dlane_bps * 8;
|
||||
|
||||
dsi_hfp_ext = adj_dsi_htotal - dsi_htotal;
|
||||
dsi_cfg->hfp += dsi_hfp_ext;
|
||||
dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext;
|
||||
if (sync_pulse) {
|
||||
dsi_cfg->htotal += dsi_cfg->hbp + DSI_HBP_FRAME_PULSE_OVERHEAD;
|
||||
dsi_cfg->htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
|
||||
} else {
|
||||
dsi_cfg->htotal += dsi_cfg->hbp + DSI_HBP_FRAME_EVENT_OVERHEAD;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
|
||||
const struct drm_display_mode *mode,
|
||||
struct cdns_dsi_cfg *dsi_cfg,
|
||||
bool mode_valid_check)
|
||||
const struct videomode *vm,
|
||||
struct cdns_dsi_cfg *dsi_cfg)
|
||||
{
|
||||
struct cdns_dsi_output *output = &dsi->output;
|
||||
struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
|
||||
unsigned long dsi_hss_hsa_hse_hbp;
|
||||
unsigned int nlanes = output->dev->lanes;
|
||||
int mode_clock = (mode_valid_check ? mode->clock : mode->crtc_clock);
|
||||
int ret;
|
||||
|
||||
ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check);
|
||||
ret = cdns_dsi_mode2cfg(dsi, vm, dsi_cfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = phy_mipi_dphy_get_default_config(mode_clock * 1000,
|
||||
ret = phy_mipi_dphy_get_default_config(vm->pixelclock,
|
||||
mipi_dsi_pixel_format_to_bpp(output->dev->format),
|
||||
nlanes, phy_cfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = cdns_dsi_adjust_phy_config(dsi, dsi_cfg, phy_cfg, mode, mode_valid_check);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = phy_validate(dsi->dphy, PHY_MODE_MIPI_DPHY, 0, &output->phy_opts);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dsi_hss_hsa_hse_hbp = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
|
||||
if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
|
||||
dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
|
||||
|
||||
/*
|
||||
* Make sure DPI(HFP) > DSI(HSS+HSA+HSE+HBP) to guarantee that the FIFO
|
||||
* is empty before we start a receiving a new line on the DPI
|
||||
* interface.
|
||||
*/
|
||||
if ((u64)phy_cfg->hs_clk_rate *
|
||||
mode_to_dpi_hfp(mode, mode_valid_check) * nlanes <
|
||||
(u64)dsi_hss_hsa_hse_hbp *
|
||||
(mode_valid_check ? mode->clock : mode->crtc_clock) * 1000)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -644,8 +569,7 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge,
|
||||
struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
|
||||
struct cdns_dsi *dsi = input_to_dsi(input);
|
||||
struct cdns_dsi_output *output = &dsi->output;
|
||||
struct cdns_dsi_cfg dsi_cfg;
|
||||
int bpp, ret;
|
||||
int bpp;
|
||||
|
||||
/*
|
||||
* VFP_DSI should be less than VFP_DPI and VFP_DSI should be at
|
||||
@@ -663,10 +587,6 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge,
|
||||
if ((mode->hdisplay * bpp) % 32)
|
||||
return MODE_H_ILLEGAL;
|
||||
|
||||
ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, true);
|
||||
if (ret)
|
||||
return MODE_BAD;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
@@ -882,7 +802,13 @@ static void cdns_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
|
||||
|
||||
tx_byte_period = DIV_ROUND_DOWN_ULL((u64)NSEC_PER_SEC * 8,
|
||||
phy_cfg->hs_clk_rate);
|
||||
reg_wakeup = (phy_cfg->hs_prepare + phy_cfg->hs_zero) / tx_byte_period;
|
||||
|
||||
/*
|
||||
* Estimated time [in clock cycles] to perform LP->HS on D-PHY.
|
||||
* It is not clear how to calculate this, so for now,
|
||||
* set it to 1/10 of the total number of clocks in a line.
|
||||
*/
|
||||
reg_wakeup = dsi_cfg.htotal / nlanes / 10;
|
||||
writel(REG_WAKEUP_TIME(reg_wakeup) | REG_LINE_DURATION(tmp),
|
||||
dsi->regs + VID_DPHY_TIME);
|
||||
|
||||
@@ -989,6 +915,28 @@ static u32 *cdns_dsi_bridge_get_input_bus_fmts(struct drm_bridge *bridge,
|
||||
return input_fmts;
|
||||
}
|
||||
|
||||
static long cdns_dsi_round_pclk(struct cdns_dsi *dsi, unsigned long pclk)
|
||||
{
|
||||
struct cdns_dsi_output *output = &dsi->output;
|
||||
unsigned int nlanes = output->dev->lanes;
|
||||
union phy_configure_opts phy_opts = { 0 };
|
||||
u32 bitspp;
|
||||
int ret;
|
||||
|
||||
bitspp = mipi_dsi_pixel_format_to_bpp(output->dev->format);
|
||||
|
||||
ret = phy_mipi_dphy_get_default_config(pclk, bitspp, nlanes,
|
||||
&phy_opts.mipi_dphy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = phy_validate(dsi->dphy, PHY_MODE_MIPI_DPHY, 0, &phy_opts);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return div_u64((u64)phy_opts.mipi_dphy.hs_clk_rate * nlanes, bitspp);
|
||||
}
|
||||
|
||||
static int cdns_dsi_bridge_atomic_check(struct drm_bridge *bridge,
|
||||
struct drm_bridge_state *bridge_state,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
@@ -997,10 +945,32 @@ static int cdns_dsi_bridge_atomic_check(struct drm_bridge *bridge,
|
||||
struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
|
||||
struct cdns_dsi *dsi = input_to_dsi(input);
|
||||
struct cdns_dsi_bridge_state *dsi_state = to_cdns_dsi_bridge_state(bridge_state);
|
||||
const struct drm_display_mode *mode = &crtc_state->mode;
|
||||
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
|
||||
struct cdns_dsi_cfg *dsi_cfg = &dsi_state->dsi_cfg;
|
||||
struct videomode vm;
|
||||
long pclk;
|
||||
|
||||
return cdns_dsi_check_conf(dsi, mode, dsi_cfg, false);
|
||||
/* cdns-dsi requires negative syncs */
|
||||
adjusted_mode->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
|
||||
adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
|
||||
|
||||
/*
|
||||
* The DPHY PLL has quite a coarsely grained clock rate options. See
|
||||
* what hsclk rate we can achieve based on the pixel clock, convert it
|
||||
* back to pixel clock, set that to the adjusted_mode->clock. This is
|
||||
* all in hopes that the CRTC will be able to provide us the requested
|
||||
* clock, as otherwise the DPI and DSI clocks will be out of sync.
|
||||
*/
|
||||
|
||||
pclk = cdns_dsi_round_pclk(dsi, adjusted_mode->clock * 1000);
|
||||
if (pclk < 0)
|
||||
return (int)pclk;
|
||||
|
||||
adjusted_mode->clock = pclk / 1000;
|
||||
|
||||
drm_display_mode_to_videomode(adjusted_mode, &vm);
|
||||
|
||||
return cdns_dsi_check_conf(dsi, &vm, dsi_cfg);
|
||||
}
|
||||
|
||||
static struct drm_bridge_state *
|
||||
@@ -1082,10 +1052,6 @@ static int cdns_dsi_attach(struct mipi_dsi_host *host,
|
||||
if (output->dev)
|
||||
return -EBUSY;
|
||||
|
||||
/* We do not support burst mode yet. */
|
||||
if (dev->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
|
||||
return -ENOTSUPP;
|
||||
|
||||
/*
|
||||
* The host <-> device link might be described using an OF-graph
|
||||
* representation, in this case we extract the device of_node from
|
||||
@@ -1442,4 +1408,3 @@ MODULE_AUTHOR("Boris Brezillon <boris.brezillon@bootlin.com>");
|
||||
MODULE_DESCRIPTION("Cadence DSI driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:cdns-dsi");
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ static u32 *display_connector_get_output_bus_fmts(struct drm_bridge *bridge,
|
||||
struct drm_connector_state *conn_state,
|
||||
unsigned int *num_output_fmts)
|
||||
{
|
||||
struct drm_bridge *prev_bridge = drm_bridge_get_prev_bridge(bridge);
|
||||
struct drm_bridge *prev_bridge __free(drm_bridge_put) = drm_bridge_get_prev_bridge(bridge);
|
||||
struct drm_bridge_state *prev_bridge_state;
|
||||
|
||||
if (!prev_bridge || !prev_bridge->funcs->atomic_get_output_bus_fmts) {
|
||||
@@ -151,7 +151,7 @@ static u32 *display_connector_get_input_bus_fmts(struct drm_bridge *bridge,
|
||||
u32 output_fmt,
|
||||
unsigned int *num_input_fmts)
|
||||
{
|
||||
struct drm_bridge *prev_bridge = drm_bridge_get_prev_bridge(bridge);
|
||||
struct drm_bridge *prev_bridge __free(drm_bridge_put) = drm_bridge_get_prev_bridge(bridge);
|
||||
struct drm_bridge_state *prev_bridge_state;
|
||||
|
||||
if (!prev_bridge || !prev_bridge->funcs->atomic_get_input_bus_fmts) {
|
||||
@@ -373,7 +373,8 @@ static int display_connector_probe(struct platform_device *pdev)
|
||||
if (conn->bridge.ddc)
|
||||
conn->bridge.ops |= DRM_BRIDGE_OP_EDID
|
||||
| DRM_BRIDGE_OP_DETECT;
|
||||
if (conn->hpd_gpio)
|
||||
/* Detecting the monitor requires reading DPCD */
|
||||
if (conn->hpd_gpio && type != DRM_MODE_CONNECTOR_DisplayPort)
|
||||
conn->bridge.ops |= DRM_BRIDGE_OP_DETECT;
|
||||
if (conn->hpd_irq >= 0)
|
||||
conn->bridge.ops |= DRM_BRIDGE_OP_HPD;
|
||||
|
||||
775
drivers/gpu/drm/bridge/ssd2825.c
Normal file
775
drivers/gpu/drm/bridge/ssd2825.c
Normal file
@@ -0,0 +1,775 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
#define SSD2825_DEVICE_ID_REG 0xb0
|
||||
#define SSD2825_RGB_INTERFACE_CTRL_REG_1 0xb1
|
||||
#define SSD2825_RGB_INTERFACE_CTRL_REG_2 0xb2
|
||||
#define SSD2825_RGB_INTERFACE_CTRL_REG_3 0xb3
|
||||
#define SSD2825_RGB_INTERFACE_CTRL_REG_4 0xb4
|
||||
#define SSD2825_RGB_INTERFACE_CTRL_REG_5 0xb5
|
||||
#define SSD2825_RGB_INTERFACE_CTRL_REG_6 0xb6
|
||||
#define SSD2825_NON_BURST_EV BIT(2)
|
||||
#define SSD2825_BURST BIT(3)
|
||||
#define SSD2825_PCKL_HIGH BIT(13)
|
||||
#define SSD2825_HSYNC_HIGH BIT(14)
|
||||
#define SSD2825_VSYNC_HIGH BIT(15)
|
||||
#define SSD2825_CONFIGURATION_REG 0xb7
|
||||
#define SSD2825_CONF_REG_HS BIT(0)
|
||||
#define SSD2825_CONF_REG_CKE BIT(1)
|
||||
#define SSD2825_CONF_REG_SLP BIT(2)
|
||||
#define SSD2825_CONF_REG_VEN BIT(3)
|
||||
#define SSD2825_CONF_REG_HCLK BIT(4)
|
||||
#define SSD2825_CONF_REG_CSS BIT(5)
|
||||
#define SSD2825_CONF_REG_DCS BIT(6)
|
||||
#define SSD2825_CONF_REG_REN BIT(7)
|
||||
#define SSD2825_CONF_REG_ECD BIT(8)
|
||||
#define SSD2825_CONF_REG_EOT BIT(9)
|
||||
#define SSD2825_CONF_REG_LPE BIT(10)
|
||||
#define SSD2825_VC_CTRL_REG 0xb8
|
||||
#define SSD2825_PLL_CTRL_REG 0xb9
|
||||
#define SSD2825_PLL_CONFIGURATION_REG 0xba
|
||||
#define SSD2825_CLOCK_CTRL_REG 0xbb
|
||||
#define SSD2825_PACKET_SIZE_CTRL_REG_1 0xbc
|
||||
#define SSD2825_PACKET_SIZE_CTRL_REG_2 0xbd
|
||||
#define SSD2825_PACKET_SIZE_CTRL_REG_3 0xbe
|
||||
#define SSD2825_PACKET_DROP_REG 0xbf
|
||||
#define SSD2825_OPERATION_CTRL_REG 0xc0
|
||||
#define SSD2825_MAX_RETURN_SIZE_REG 0xc1
|
||||
#define SSD2825_RETURN_DATA_COUNT_REG 0xc2
|
||||
#define SSD2825_ACK_RESPONSE_REG 0xc3
|
||||
#define SSD2825_LINE_CTRL_REG 0xc4
|
||||
#define SSD2825_INTERRUPT_CTRL_REG 0xc5
|
||||
#define SSD2825_INTERRUPT_STATUS_REG 0xc6
|
||||
#define SSD2825_ERROR_STATUS_REG 0xc7
|
||||
#define SSD2825_DATA_FORMAT_REG 0xc8
|
||||
#define SSD2825_DELAY_ADJ_REG_1 0xc9
|
||||
#define SSD2825_DELAY_ADJ_REG_2 0xca
|
||||
#define SSD2825_DELAY_ADJ_REG_3 0xcb
|
||||
#define SSD2825_DELAY_ADJ_REG_4 0xcc
|
||||
#define SSD2825_DELAY_ADJ_REG_5 0xcd
|
||||
#define SSD2825_DELAY_ADJ_REG_6 0xce
|
||||
#define SSD2825_HS_TX_TIMER_REG_1 0xcf
|
||||
#define SSD2825_HS_TX_TIMER_REG_2 0xd0
|
||||
#define SSD2825_LP_RX_TIMER_REG_1 0xd1
|
||||
#define SSD2825_LP_RX_TIMER_REG_2 0xd2
|
||||
#define SSD2825_TE_STATUS_REG 0xd3
|
||||
#define SSD2825_SPI_READ_REG 0xd4
|
||||
#define SSD2825_SPI_READ_REG_RESET 0xfa
|
||||
#define SSD2825_PLL_LOCK_REG 0xd5
|
||||
#define SSD2825_TEST_REG 0xd6
|
||||
#define SSD2825_TE_COUNT_REG 0xd7
|
||||
#define SSD2825_ANALOG_CTRL_REG_1 0xd8
|
||||
#define SSD2825_ANALOG_CTRL_REG_2 0xd9
|
||||
#define SSD2825_ANALOG_CTRL_REG_3 0xda
|
||||
#define SSD2825_ANALOG_CTRL_REG_4 0xdb
|
||||
#define SSD2825_INTERRUPT_OUT_CTRL_REG 0xdc
|
||||
#define SSD2825_RGB_INTERFACE_CTRL_REG_7 0xdd
|
||||
#define SSD2825_LANE_CONFIGURATION_REG 0xde
|
||||
#define SSD2825_DELAY_ADJ_REG_7 0xdf
|
||||
#define SSD2825_INPUT_PIN_CTRL_REG_1 0xe0
|
||||
#define SSD2825_INPUT_PIN_CTRL_REG_2 0xe1
|
||||
#define SSD2825_BIDIR_PIN_CTRL_REG_1 0xe2
|
||||
#define SSD2825_BIDIR_PIN_CTRL_REG_2 0xe3
|
||||
#define SSD2825_BIDIR_PIN_CTRL_REG_3 0xe4
|
||||
#define SSD2825_BIDIR_PIN_CTRL_REG_4 0xe5
|
||||
#define SSD2825_BIDIR_PIN_CTRL_REG_5 0xe6
|
||||
#define SSD2825_BIDIR_PIN_CTRL_REG_6 0xe7
|
||||
#define SSD2825_BIDIR_PIN_CTRL_REG_7 0xe8
|
||||
#define SSD2825_CABC_BRIGHTNESS_CTRL_REG_1 0xe9
|
||||
#define SSD2825_CABC_BRIGHTNESS_CTRL_REG_2 0xea
|
||||
#define SSD2825_CABC_BRIGHTNESS_STATUS_REG 0xeb
|
||||
#define SSD2825_READ_REG 0xff
|
||||
|
||||
#define SSD2825_COM_BYTE 0x00
|
||||
#define SSD2825_DAT_BYTE 0x01
|
||||
|
||||
#define SSD2828_LP_CLOCK_DIVIDER(n) (((n) - 1) & 0x3f)
|
||||
#define SSD2825_LP_MIN_CLK 5000 /* KHz */
|
||||
#define SSD2825_REF_MIN_CLK 2000 /* KHz */
|
||||
|
||||
static const struct regulator_bulk_data ssd2825_supplies[] = {
|
||||
{ .supply = "dvdd" },
|
||||
{ .supply = "avdd" },
|
||||
{ .supply = "vddio" },
|
||||
};
|
||||
|
||||
struct ssd2825_dsi_output {
|
||||
struct mipi_dsi_device *dev;
|
||||
struct drm_panel *panel;
|
||||
struct drm_bridge *bridge;
|
||||
};
|
||||
|
||||
struct ssd2825_priv {
|
||||
struct spi_device *spi;
|
||||
struct device *dev;
|
||||
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct regulator_bulk_data *supplies;
|
||||
|
||||
struct clk *tx_clk;
|
||||
|
||||
struct mipi_dsi_host dsi_host;
|
||||
struct drm_bridge bridge;
|
||||
struct ssd2825_dsi_output output;
|
||||
|
||||
struct mutex mlock; /* for host transfer operations */
|
||||
|
||||
u32 pd_lines; /* number of Parallel Port Input Data Lines */
|
||||
u32 dsi_lanes; /* number of DSI Lanes */
|
||||
|
||||
/* Parameters for PLL programming */
|
||||
u32 pll_freq_kbps; /* PLL in kbps */
|
||||
u32 nibble_freq_khz; /* PLL div by 4 */
|
||||
|
||||
u32 hzd; /* HS Zero Delay in ns*/
|
||||
u32 hpd; /* HS Prepare Delay is ns */
|
||||
};
|
||||
|
||||
static inline struct ssd2825_priv *dsi_host_to_ssd2825(struct mipi_dsi_host *host)
|
||||
{
|
||||
return container_of(host, struct ssd2825_priv, dsi_host);
|
||||
}
|
||||
|
||||
static inline struct ssd2825_priv *bridge_to_ssd2825(struct drm_bridge *bridge)
|
||||
{
|
||||
return container_of(bridge, struct ssd2825_priv, bridge);
|
||||
}
|
||||
|
||||
static int ssd2825_write_raw(struct ssd2825_priv *priv, u8 high_byte, u8 low_byte)
|
||||
{
|
||||
struct spi_device *spi = priv->spi;
|
||||
u8 tx_buf[2];
|
||||
|
||||
/*
|
||||
* Low byte is the value, high byte defines type of
|
||||
* write cycle, 0 for command and 1 for data.
|
||||
*/
|
||||
tx_buf[0] = low_byte;
|
||||
tx_buf[1] = high_byte;
|
||||
|
||||
return spi_write(spi, tx_buf, 2);
|
||||
}
|
||||
|
||||
static int ssd2825_write_reg(struct ssd2825_priv *priv, u8 reg, u16 command)
|
||||
{
|
||||
u8 datal = (command & 0x00FF);
|
||||
u8 datah = (command & 0xFF00) >> 8;
|
||||
int ret;
|
||||
|
||||
/* Command write cycle */
|
||||
ret = ssd2825_write_raw(priv, SSD2825_COM_BYTE, reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Data write cycle bits 7-0 */
|
||||
ret = ssd2825_write_raw(priv, SSD2825_DAT_BYTE, datal);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Data write cycle bits 15-8 */
|
||||
ret = ssd2825_write_raw(priv, SSD2825_DAT_BYTE, datah);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssd2825_write_dsi(struct ssd2825_priv *priv, const u8 *command, int len)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
ret = ssd2825_write_reg(priv, SSD2825_PACKET_SIZE_CTRL_REG_1, len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ssd2825_write_raw(priv, SSD2825_COM_BYTE, SSD2825_PACKET_DROP_REG);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
ret = ssd2825_write_raw(priv, SSD2825_DAT_BYTE, command[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssd2825_read_raw(struct ssd2825_priv *priv, u8 cmd, u16 *data)
|
||||
{
|
||||
struct spi_device *spi = priv->spi;
|
||||
struct spi_message msg;
|
||||
struct spi_transfer xfer[2];
|
||||
u8 tx_buf[2];
|
||||
u8 rx_buf[2];
|
||||
int ret;
|
||||
|
||||
memset(&xfer, 0, sizeof(xfer));
|
||||
|
||||
tx_buf[1] = (cmd & 0xFF00) >> 8;
|
||||
tx_buf[0] = (cmd & 0x00FF);
|
||||
|
||||
xfer[0].tx_buf = tx_buf;
|
||||
xfer[0].bits_per_word = 9;
|
||||
xfer[0].len = 2;
|
||||
|
||||
xfer[1].rx_buf = rx_buf;
|
||||
xfer[1].bits_per_word = 16;
|
||||
xfer[1].len = 2;
|
||||
|
||||
spi_message_init(&msg);
|
||||
spi_message_add_tail(&xfer[0], &msg);
|
||||
spi_message_add_tail(&xfer[1], &msg);
|
||||
|
||||
ret = spi_sync(spi, &msg);
|
||||
if (ret) {
|
||||
dev_err(&spi->dev, "ssd2825 read raw failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*data = rx_buf[1] | (rx_buf[0] << 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssd2825_read_reg(struct ssd2825_priv *priv, u8 reg, u16 *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Reset the read register */
|
||||
ret = ssd2825_write_reg(priv, SSD2825_SPI_READ_REG, SSD2825_SPI_READ_REG_RESET);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Push the address to read */
|
||||
ret = ssd2825_write_raw(priv, SSD2825_COM_BYTE, reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Perform a reading cycle */
|
||||
ret = ssd2825_read_raw(priv, SSD2825_SPI_READ_REG_RESET, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssd2825_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dev)
|
||||
{
|
||||
struct ssd2825_priv *priv = dsi_host_to_ssd2825(host);
|
||||
struct drm_bridge *bridge;
|
||||
struct drm_panel *panel;
|
||||
struct device_node *ep;
|
||||
int ret;
|
||||
|
||||
if (dev->lanes > 4) {
|
||||
dev_err(priv->dev, "unsupported number of data lanes(%u)\n", dev->lanes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* ssd2825 supports both Video and Pulse mode, but the driver only
|
||||
* implements Video (event) mode currently
|
||||
*/
|
||||
if (!(dev->mode_flags & MIPI_DSI_MODE_VIDEO)) {
|
||||
dev_err(priv->dev, "Only MIPI_DSI_MODE_VIDEO is supported\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, &panel, &bridge);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (panel) {
|
||||
bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI);
|
||||
if (IS_ERR(bridge))
|
||||
return PTR_ERR(bridge);
|
||||
}
|
||||
|
||||
priv->output.dev = dev;
|
||||
priv->output.bridge = bridge;
|
||||
priv->output.panel = panel;
|
||||
|
||||
priv->dsi_lanes = dev->lanes;
|
||||
|
||||
/* get input ep (port0/endpoint0) */
|
||||
ret = -EINVAL;
|
||||
ep = of_graph_get_endpoint_by_regs(host->dev->of_node, 0, 0);
|
||||
if (ep) {
|
||||
ret = of_property_read_u32(ep, "bus-width", &priv->pd_lines);
|
||||
of_node_put(ep);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
priv->pd_lines = mipi_dsi_pixel_format_to_bpp(dev->format);
|
||||
|
||||
drm_bridge_add(&priv->bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssd2825_dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *dev)
|
||||
{
|
||||
struct ssd2825_priv *priv = dsi_host_to_ssd2825(host);
|
||||
|
||||
drm_bridge_remove(&priv->bridge);
|
||||
if (priv->output.panel)
|
||||
drm_panel_bridge_remove(priv->output.bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t ssd2825_dsi_host_transfer(struct mipi_dsi_host *host,
|
||||
const struct mipi_dsi_msg *msg)
|
||||
{
|
||||
struct ssd2825_priv *priv = dsi_host_to_ssd2825(host);
|
||||
u16 config;
|
||||
int ret;
|
||||
|
||||
if (msg->rx_len) {
|
||||
dev_warn(priv->dev, "MIPI rx is not supported\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
guard(mutex)(&priv->mlock);
|
||||
|
||||
ret = ssd2825_read_reg(priv, SSD2825_CONFIGURATION_REG, &config);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (msg->type) {
|
||||
case MIPI_DSI_DCS_SHORT_WRITE:
|
||||
case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
|
||||
case MIPI_DSI_DCS_LONG_WRITE:
|
||||
config |= SSD2825_CONF_REG_DCS;
|
||||
break;
|
||||
case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
|
||||
case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
|
||||
case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
|
||||
case MIPI_DSI_GENERIC_LONG_WRITE:
|
||||
config &= ~SSD2825_CONF_REG_DCS;
|
||||
break;
|
||||
case MIPI_DSI_DCS_READ:
|
||||
case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
|
||||
case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
|
||||
case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG, config);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0x0000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ssd2825_write_dsi(priv, msg->tx_buf, msg->tx_len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mipi_dsi_host_ops ssd2825_dsi_host_ops = {
|
||||
.attach = ssd2825_dsi_host_attach,
|
||||
.detach = ssd2825_dsi_host_detach,
|
||||
.transfer = ssd2825_dsi_host_transfer,
|
||||
};
|
||||
|
||||
static void ssd2825_hw_reset(struct ssd2825_priv *priv)
|
||||
{
|
||||
gpiod_set_value_cansleep(priv->reset_gpio, 1);
|
||||
usleep_range(5000, 6000);
|
||||
gpiod_set_value_cansleep(priv->reset_gpio, 0);
|
||||
usleep_range(5000, 6000);
|
||||
}
|
||||
|
||||
/*
|
||||
* PLL configuration register settings.
|
||||
*
|
||||
* See the "PLL Configuration Register Description" in the SSD2825 datasheet.
|
||||
*/
|
||||
static u16 construct_pll_config(struct ssd2825_priv *priv,
|
||||
u32 desired_pll_freq_kbps, u32 reference_freq_khz)
|
||||
{
|
||||
u32 div_factor = 1, mul_factor, fr = 0;
|
||||
|
||||
while (reference_freq_khz / (div_factor + 1) >= SSD2825_REF_MIN_CLK)
|
||||
div_factor++;
|
||||
if (div_factor > 31)
|
||||
div_factor = 31;
|
||||
|
||||
mul_factor = DIV_ROUND_UP(desired_pll_freq_kbps * div_factor,
|
||||
reference_freq_khz);
|
||||
|
||||
priv->pll_freq_kbps = reference_freq_khz * mul_factor / div_factor;
|
||||
priv->nibble_freq_khz = priv->pll_freq_kbps / 4;
|
||||
|
||||
if (priv->pll_freq_kbps >= 501000)
|
||||
fr = 3;
|
||||
else if (priv->pll_freq_kbps >= 251000)
|
||||
fr = 2;
|
||||
else if (priv->pll_freq_kbps >= 126000)
|
||||
fr = 1;
|
||||
|
||||
return (fr << 14) | (div_factor << 8) | mul_factor;
|
||||
}
|
||||
|
||||
static int ssd2825_setup_pll(struct ssd2825_priv *priv,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
u16 pll_config, lp_div;
|
||||
u32 nibble_delay, pclk_mult, tx_freq_khz;
|
||||
u8 hzd, hpd;
|
||||
|
||||
tx_freq_khz = clk_get_rate(priv->tx_clk) / KILO;
|
||||
if (!tx_freq_khz)
|
||||
tx_freq_khz = SSD2825_REF_MIN_CLK;
|
||||
|
||||
pclk_mult = priv->pd_lines / priv->dsi_lanes + 1;
|
||||
pll_config = construct_pll_config(priv, pclk_mult * mode->clock,
|
||||
tx_freq_khz);
|
||||
|
||||
lp_div = priv->pll_freq_kbps / (SSD2825_LP_MIN_CLK * 8);
|
||||
|
||||
/* nibble_delay in nanoseconds */
|
||||
nibble_delay = MICRO / priv->nibble_freq_khz;
|
||||
|
||||
hzd = priv->hzd / nibble_delay;
|
||||
hpd = (priv->hpd - 4 * nibble_delay) / nibble_delay;
|
||||
|
||||
/* Disable PLL */
|
||||
ssd2825_write_reg(priv, SSD2825_PLL_CTRL_REG, 0x0000);
|
||||
ssd2825_write_reg(priv, SSD2825_LINE_CTRL_REG, 0x0001);
|
||||
|
||||
/* Set delays */
|
||||
ssd2825_write_reg(priv, SSD2825_DELAY_ADJ_REG_1, (hzd << 8) | hpd);
|
||||
|
||||
/* Set PLL coefficients */
|
||||
ssd2825_write_reg(priv, SSD2825_PLL_CONFIGURATION_REG, pll_config);
|
||||
|
||||
/* Clock Control Register */
|
||||
ssd2825_write_reg(priv, SSD2825_CLOCK_CTRL_REG,
|
||||
SSD2828_LP_CLOCK_DIVIDER(lp_div));
|
||||
|
||||
/* Enable PLL */
|
||||
ssd2825_write_reg(priv, SSD2825_PLL_CTRL_REG, 0x0001);
|
||||
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ssd2825_bridge_atomic_pre_enable(struct drm_bridge *bridge,
|
||||
struct drm_atomic_state *state)
|
||||
{
|
||||
struct ssd2825_priv *priv = bridge_to_ssd2825(bridge);
|
||||
struct mipi_dsi_device *dsi_dev = priv->output.dev;
|
||||
const struct drm_crtc_state *crtc_state;
|
||||
const struct drm_display_mode *mode;
|
||||
struct drm_connector *connector;
|
||||
struct drm_crtc *crtc;
|
||||
u32 input_bus_flags = bridge->timings->input_bus_flags;
|
||||
u16 flags = 0, config;
|
||||
u8 pixel_format;
|
||||
int ret;
|
||||
|
||||
/* Power Sequence */
|
||||
ret = clk_prepare_enable(priv->tx_clk);
|
||||
if (ret)
|
||||
dev_err(priv->dev, "error enabling tx_clk (%d)\n", ret);
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(ssd2825_supplies), priv->supplies);
|
||||
if (ret)
|
||||
dev_err(priv->dev, "error enabling regulators (%d)\n", ret);
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
ssd2825_hw_reset(priv);
|
||||
|
||||
/* Perform SW reset */
|
||||
ssd2825_write_reg(priv, SSD2825_OPERATION_CTRL_REG, 0x0100);
|
||||
|
||||
/* Set pixel format */
|
||||
switch (dsi_dev->format) {
|
||||
case MIPI_DSI_FMT_RGB565:
|
||||
pixel_format = 0x00;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB666_PACKED:
|
||||
pixel_format = 0x01;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB666:
|
||||
pixel_format = 0x02;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB888:
|
||||
default:
|
||||
pixel_format = 0x03;
|
||||
break;
|
||||
}
|
||||
|
||||
connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
|
||||
crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
|
||||
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
||||
mode = &crtc_state->adjusted_mode;
|
||||
|
||||
/* Set panel timings */
|
||||
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_1,
|
||||
((mode->vtotal - mode->vsync_end) << 8) |
|
||||
(mode->htotal - mode->hsync_end));
|
||||
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_2,
|
||||
((mode->vtotal - mode->vsync_start) << 8) |
|
||||
(mode->htotal - mode->hsync_start));
|
||||
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_3,
|
||||
((mode->vsync_start - mode->vdisplay) << 8) |
|
||||
(mode->hsync_start - mode->hdisplay));
|
||||
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_4, mode->hdisplay);
|
||||
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_5, mode->vdisplay);
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
||||
flags |= SSD2825_HSYNC_HIGH;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
||||
flags |= SSD2825_VSYNC_HIGH;
|
||||
|
||||
if (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO)
|
||||
flags |= SSD2825_NON_BURST_EV;
|
||||
|
||||
if (input_bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE)
|
||||
flags |= SSD2825_PCKL_HIGH;
|
||||
|
||||
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_6, flags | pixel_format);
|
||||
ssd2825_write_reg(priv, SSD2825_LANE_CONFIGURATION_REG, dsi_dev->lanes - 1);
|
||||
ssd2825_write_reg(priv, SSD2825_TEST_REG, 0x0004);
|
||||
|
||||
/* Call PLL configuration */
|
||||
ssd2825_setup_pll(priv, mode);
|
||||
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
config = SSD2825_CONF_REG_HS | SSD2825_CONF_REG_CKE | SSD2825_CONF_REG_DCS |
|
||||
SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT;
|
||||
|
||||
if (dsi_dev->mode_flags & MIPI_DSI_MODE_LPM)
|
||||
config &= ~SSD2825_CONF_REG_HS;
|
||||
|
||||
if (dsi_dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
|
||||
config &= ~SSD2825_CONF_REG_EOT;
|
||||
|
||||
/* Initial DSI configuration register set */
|
||||
ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG, config);
|
||||
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0);
|
||||
|
||||
if (priv->output.panel)
|
||||
drm_panel_enable(priv->output.panel);
|
||||
}
|
||||
|
||||
static void ssd2825_bridge_atomic_enable(struct drm_bridge *bridge,
|
||||
struct drm_atomic_state *state)
|
||||
{
|
||||
struct ssd2825_priv *priv = bridge_to_ssd2825(bridge);
|
||||
struct mipi_dsi_device *dsi_dev = priv->output.dev;
|
||||
u16 config;
|
||||
|
||||
config = SSD2825_CONF_REG_HS | SSD2825_CONF_REG_DCS |
|
||||
SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT;
|
||||
|
||||
if (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO)
|
||||
config |= SSD2825_CONF_REG_VEN;
|
||||
|
||||
if (dsi_dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
|
||||
config &= ~SSD2825_CONF_REG_EOT;
|
||||
|
||||
/* Complete configuration after DSI commands were sent */
|
||||
ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG, config);
|
||||
ssd2825_write_reg(priv, SSD2825_PLL_CTRL_REG, 0x0001);
|
||||
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0x0000);
|
||||
}
|
||||
|
||||
static void ssd2825_bridge_atomic_disable(struct drm_bridge *bridge,
|
||||
struct drm_atomic_state *state)
|
||||
{
|
||||
struct ssd2825_priv *priv = bridge_to_ssd2825(bridge);
|
||||
int ret;
|
||||
|
||||
msleep(100);
|
||||
|
||||
/* Exit DSI configuration register set */
|
||||
ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG,
|
||||
SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT);
|
||||
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0);
|
||||
|
||||
/* HW disable */
|
||||
gpiod_set_value_cansleep(priv->reset_gpio, 1);
|
||||
usleep_range(5000, 6000);
|
||||
|
||||
ret = regulator_bulk_disable(ARRAY_SIZE(ssd2825_supplies),
|
||||
priv->supplies);
|
||||
if (ret < 0)
|
||||
dev_err(priv->dev, "error disabling regulators (%d)\n", ret);
|
||||
|
||||
clk_disable_unprepare(priv->tx_clk);
|
||||
}
|
||||
|
||||
static int ssd2825_bridge_attach(struct drm_bridge *bridge, struct drm_encoder *encoder,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct ssd2825_priv *priv = bridge_to_ssd2825(bridge);
|
||||
|
||||
return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge,
|
||||
flags);
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
ssd2825_bridge_mode_valid(struct drm_bridge *bridge,
|
||||
const struct drm_display_info *info,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
if (mode->hdisplay > 1366)
|
||||
return MODE_H_ILLEGAL;
|
||||
|
||||
if (mode->vdisplay > 1366)
|
||||
return MODE_V_ILLEGAL;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static bool ssd2825_mode_fixup(struct drm_bridge *bridge,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/* Default to positive sync */
|
||||
|
||||
if (!(adjusted_mode->flags &
|
||||
(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC)))
|
||||
adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC;
|
||||
|
||||
if (!(adjusted_mode->flags &
|
||||
(DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC)))
|
||||
adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs ssd2825_bridge_funcs = {
|
||||
.attach = ssd2825_bridge_attach,
|
||||
.mode_valid = ssd2825_bridge_mode_valid,
|
||||
.mode_fixup = ssd2825_mode_fixup,
|
||||
|
||||
.atomic_pre_enable = ssd2825_bridge_atomic_pre_enable,
|
||||
.atomic_enable = ssd2825_bridge_atomic_enable,
|
||||
.atomic_disable = ssd2825_bridge_atomic_disable,
|
||||
|
||||
.atomic_reset = drm_atomic_helper_bridge_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
||||
};
|
||||
|
||||
static const struct drm_bridge_timings default_ssd2825_timings = {
|
||||
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
|
||||
| DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
|
||||
| DRM_BUS_FLAG_DE_HIGH,
|
||||
};
|
||||
|
||||
static int ssd2825_probe(struct spi_device *spi)
|
||||
{
|
||||
struct ssd2825_priv *priv;
|
||||
struct device *dev = &spi->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
int ret;
|
||||
|
||||
/* Driver supports only 8 bit 3 Wire mode */
|
||||
spi->bits_per_word = 9;
|
||||
|
||||
ret = spi_setup(spi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv = devm_drm_bridge_alloc(dev, struct ssd2825_priv, bridge, &ssd2825_bridge_funcs);
|
||||
if (IS_ERR(priv))
|
||||
return PTR_ERR(priv);
|
||||
|
||||
spi_set_drvdata(spi, priv);
|
||||
|
||||
priv->spi = spi;
|
||||
priv->dev = dev;
|
||||
|
||||
mutex_init(&priv->mlock);
|
||||
|
||||
priv->tx_clk = devm_clk_get_optional(dev, NULL);
|
||||
if (IS_ERR(priv->tx_clk))
|
||||
return dev_err_probe(dev, PTR_ERR(priv->tx_clk),
|
||||
"can't retrieve bridge tx_clk\n");
|
||||
|
||||
priv->reset_gpio = devm_gpiod_get_optional(dev, "reset",
|
||||
GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(priv->reset_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(priv->reset_gpio),
|
||||
"failed to get reset GPIO\n");
|
||||
|
||||
ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(ssd2825_supplies),
|
||||
ssd2825_supplies, &priv->supplies);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to get regulators\n");
|
||||
|
||||
priv->hzd = 133; /* ns */
|
||||
device_property_read_u32(dev, "solomon,hs-zero-delay-ns", &priv->hzd);
|
||||
|
||||
priv->hpd = 40; /* ns */
|
||||
device_property_read_u32(dev, "solomon,hs-prep-delay-ns", &priv->hpd);
|
||||
|
||||
priv->dsi_host.dev = dev;
|
||||
priv->dsi_host.ops = &ssd2825_dsi_host_ops;
|
||||
|
||||
priv->bridge.timings = &default_ssd2825_timings;
|
||||
priv->bridge.of_node = np;
|
||||
|
||||
return mipi_dsi_host_register(&priv->dsi_host);
|
||||
}
|
||||
|
||||
static void ssd2825_remove(struct spi_device *spi)
|
||||
{
|
||||
struct ssd2825_priv *priv = spi_get_drvdata(spi);
|
||||
|
||||
mipi_dsi_host_unregister(&priv->dsi_host);
|
||||
}
|
||||
|
||||
static const struct of_device_id ssd2825_of_match[] = {
|
||||
{ .compatible = "solomon,ssd2825" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ssd2825_of_match);
|
||||
|
||||
static struct spi_driver ssd2825_driver = {
|
||||
.driver = {
|
||||
.name = "ssd2825",
|
||||
.of_match_table = ssd2825_of_match,
|
||||
},
|
||||
.probe = ssd2825_probe,
|
||||
.remove = ssd2825_remove,
|
||||
};
|
||||
module_spi_driver(ssd2825_driver);
|
||||
|
||||
MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
|
||||
MODULE_DESCRIPTION("Solomon SSD2825 RGB to MIPI-DSI bridge driver SPI");
|
||||
MODULE_LICENSE("GPL");
|
||||
203
drivers/gpu/drm/bridge/waveshare-dsi.c
Normal file
203
drivers/gpu/drm/bridge/waveshare-dsi.c
Normal file
@@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2025 NXP
|
||||
* Based on panel-raspberrypi-touchscreen by Broadcom
|
||||
*/
|
||||
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
struct ws_bridge {
|
||||
struct drm_bridge bridge;
|
||||
struct drm_bridge *next_bridge;
|
||||
struct backlight_device *backlight;
|
||||
struct device *dev;
|
||||
struct regmap *reg_map;
|
||||
};
|
||||
|
||||
static const struct regmap_config ws_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 0xff,
|
||||
};
|
||||
|
||||
static struct ws_bridge *bridge_to_ws_bridge(struct drm_bridge *bridge)
|
||||
{
|
||||
return container_of(bridge, struct ws_bridge, bridge);
|
||||
}
|
||||
|
||||
static int ws_bridge_attach_dsi(struct ws_bridge *ws)
|
||||
{
|
||||
const struct mipi_dsi_device_info info = {
|
||||
.type = "ws-bridge",
|
||||
.channel = 0,
|
||||
.node = NULL,
|
||||
};
|
||||
struct device_node *dsi_host_node;
|
||||
struct device *dev = ws->dev;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct mipi_dsi_host *host;
|
||||
int ret;
|
||||
|
||||
dsi_host_node = of_graph_get_remote_node(dev->of_node, 0, 0);
|
||||
if (!dsi_host_node) {
|
||||
dev_err(dev, "Failed to get remote port\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
host = of_find_mipi_dsi_host_by_node(dsi_host_node);
|
||||
of_node_put(dsi_host_node);
|
||||
if (!host)
|
||||
return dev_err_probe(dev, -EPROBE_DEFER, "Failed to find dsi_host\n");
|
||||
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi))
|
||||
return dev_err_probe(dev, PTR_ERR(dsi), "Failed to create dsi device\n");
|
||||
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
|
||||
MIPI_DSI_CLOCK_NON_CONTINUOUS;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->lanes = 2;
|
||||
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to attach dsi to host\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ws_bridge_bridge_attach(struct drm_bridge *bridge,
|
||||
struct drm_encoder *encoder,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct ws_bridge *ws = bridge_to_ws_bridge(bridge);
|
||||
int ret;
|
||||
|
||||
ret = ws_bridge_attach_dsi(ws);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return drm_bridge_attach(encoder, ws->next_bridge,
|
||||
&ws->bridge, flags);
|
||||
}
|
||||
|
||||
static void ws_bridge_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ws_bridge *ws = bridge_to_ws_bridge(bridge);
|
||||
|
||||
regmap_write(ws->reg_map, 0xad, 0x01);
|
||||
backlight_enable(ws->backlight);
|
||||
}
|
||||
|
||||
static void ws_bridge_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ws_bridge *ws = bridge_to_ws_bridge(bridge);
|
||||
|
||||
backlight_disable(ws->backlight);
|
||||
regmap_write(ws->reg_map, 0xad, 0x00);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs ws_bridge_bridge_funcs = {
|
||||
.enable = ws_bridge_bridge_enable,
|
||||
.disable = ws_bridge_bridge_disable,
|
||||
.attach = ws_bridge_bridge_attach,
|
||||
};
|
||||
|
||||
static int ws_bridge_bl_update_status(struct backlight_device *bl)
|
||||
{
|
||||
struct ws_bridge *ws = bl_get_data(bl);
|
||||
|
||||
regmap_write(ws->reg_map, 0xab, 0xff - backlight_get_brightness(bl));
|
||||
regmap_write(ws->reg_map, 0xaa, 0x01);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct backlight_ops ws_bridge_bl_ops = {
|
||||
.update_status = ws_bridge_bl_update_status,
|
||||
};
|
||||
|
||||
static struct backlight_device *ws_bridge_create_backlight(struct ws_bridge *ws)
|
||||
{
|
||||
const struct backlight_properties props = {
|
||||
.type = BACKLIGHT_RAW,
|
||||
.brightness = 255,
|
||||
.max_brightness = 255,
|
||||
};
|
||||
struct device *dev = ws->dev;
|
||||
|
||||
return devm_backlight_device_register(dev, dev_name(dev), dev, ws,
|
||||
&ws_bridge_bl_ops, &props);
|
||||
}
|
||||
|
||||
static int ws_bridge_probe(struct i2c_client *i2c)
|
||||
{
|
||||
struct device *dev = &i2c->dev;
|
||||
struct drm_panel *panel;
|
||||
struct ws_bridge *ws;
|
||||
int ret;
|
||||
|
||||
ws = devm_drm_bridge_alloc(dev, struct ws_bridge, bridge, &ws_bridge_bridge_funcs);
|
||||
if (!ws)
|
||||
return -ENOMEM;
|
||||
|
||||
ws->dev = dev;
|
||||
|
||||
ws->reg_map = devm_regmap_init_i2c(i2c, &ws_regmap_config);
|
||||
if (IS_ERR(ws->reg_map))
|
||||
return dev_err_probe(dev, PTR_ERR(ws->reg_map), "Failed to allocate regmap\n");
|
||||
|
||||
ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, &panel, NULL);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to find remote panel\n");
|
||||
|
||||
ws->next_bridge = devm_drm_panel_bridge_add(dev, panel);
|
||||
if (IS_ERR(ws->next_bridge))
|
||||
return PTR_ERR(ws->next_bridge);
|
||||
|
||||
ws->backlight = ws_bridge_create_backlight(ws);
|
||||
if (IS_ERR(ws->backlight)) {
|
||||
ret = PTR_ERR(ws->backlight);
|
||||
dev_err(dev, "Failed to create backlight: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
regmap_write(ws->reg_map, 0xc0, 0x01);
|
||||
regmap_write(ws->reg_map, 0xc2, 0x01);
|
||||
regmap_write(ws->reg_map, 0xac, 0x01);
|
||||
|
||||
ws->bridge.type = DRM_MODE_CONNECTOR_DPI;
|
||||
ws->bridge.of_node = dev->of_node;
|
||||
devm_drm_bridge_add(dev, &ws->bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ws_bridge_of_ids[] = {
|
||||
{.compatible = "waveshare,dsi2dpi",},
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, ws_bridge_of_ids);
|
||||
|
||||
static struct i2c_driver ws_bridge_driver = {
|
||||
.driver = {
|
||||
.name = "ws_dsi2dpi",
|
||||
.of_match_table = ws_bridge_of_ids,
|
||||
},
|
||||
.probe = ws_bridge_probe,
|
||||
};
|
||||
module_i2c_driver(ws_bridge_driver);
|
||||
|
||||
MODULE_AUTHOR("Joseph Guo <qijian.guo@nxp.com>");
|
||||
MODULE_DESCRIPTION("Waveshare DSI2DPI bridge driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <drm/drm_modeset_helper_vtables.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/display/drm_hdcp_helper.h>
|
||||
#include <drm/display/drm_hdmi_audio_helper.h>
|
||||
#include <drm/display/drm_hdmi_cec_helper.h>
|
||||
#include <drm/display/drm_hdmi_helper.h>
|
||||
@@ -641,6 +642,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
|
||||
struct drm_bridge *bridge, *panel_bridge = NULL;
|
||||
unsigned int supported_formats = BIT(HDMI_COLORSPACE_RGB);
|
||||
unsigned int max_bpc = 8;
|
||||
bool support_hdcp = false;
|
||||
int connector_type;
|
||||
int ret;
|
||||
|
||||
@@ -763,6 +765,9 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
|
||||
|
||||
if (drm_bridge_is_panel(bridge))
|
||||
panel_bridge = bridge;
|
||||
|
||||
if (bridge->support_hdcp)
|
||||
support_hdcp = true;
|
||||
}
|
||||
|
||||
if (connector_type == DRM_MODE_CONNECTOR_Unknown)
|
||||
@@ -816,6 +821,8 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
|
||||
|
||||
if (bridge_connector->bridge_hdmi_cec &&
|
||||
bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
|
||||
bridge = bridge_connector->bridge_hdmi_cec;
|
||||
|
||||
ret = drmm_connector_hdmi_cec_notifier_register(connector,
|
||||
NULL,
|
||||
bridge->hdmi_cec_dev);
|
||||
@@ -825,6 +832,8 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
|
||||
|
||||
if (bridge_connector->bridge_hdmi_cec &&
|
||||
bridge_connector->bridge_hdmi_cec->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
|
||||
bridge = bridge_connector->bridge_hdmi_cec;
|
||||
|
||||
ret = drmm_connector_hdmi_cec_register(connector,
|
||||
&drm_bridge_connector_hdmi_cec_funcs,
|
||||
bridge->hdmi_cec_adapter_name,
|
||||
@@ -845,6 +854,10 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
|
||||
if (panel_bridge)
|
||||
drm_panel_bridge_set_orientation(connector, panel_bridge);
|
||||
|
||||
if (support_hdcp && IS_REACHABLE(CONFIG_DRM_DISPLAY_HELPER) &&
|
||||
IS_ENABLED(CONFIG_DRM_DISPLAY_HDCP_HELPER))
|
||||
drm_connector_attach_content_protection_property(connector, true);
|
||||
|
||||
return connector;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_bridge_connector_init);
|
||||
|
||||
@@ -456,6 +456,7 @@ mode_fixup(struct drm_atomic_state *state)
|
||||
ret = drm_atomic_bridge_chain_check(bridge,
|
||||
new_crtc_state,
|
||||
new_conn_state);
|
||||
drm_bridge_put(bridge);
|
||||
if (ret) {
|
||||
drm_dbg_atomic(encoder->dev, "Bridge atomic check failed\n");
|
||||
return ret;
|
||||
@@ -527,6 +528,7 @@ static enum drm_mode_status mode_valid_path(struct drm_connector *connector,
|
||||
bridge = drm_bridge_chain_get_first_bridge(encoder);
|
||||
ret = drm_bridge_chain_mode_valid(bridge, &connector->display_info,
|
||||
mode);
|
||||
drm_bridge_put(bridge);
|
||||
if (ret != MODE_OK) {
|
||||
drm_dbg_atomic(encoder->dev, "[BRIDGE] mode_valid() failed\n");
|
||||
return ret;
|
||||
@@ -1212,6 +1214,7 @@ encoder_bridge_disable(struct drm_device *dev, struct drm_atomic_state *state)
|
||||
*/
|
||||
bridge = drm_bridge_chain_get_first_bridge(encoder);
|
||||
drm_atomic_bridge_chain_disable(bridge, state);
|
||||
drm_bridge_put(bridge);
|
||||
|
||||
/* Right function depends upon target state. */
|
||||
if (funcs) {
|
||||
@@ -1329,6 +1332,7 @@ encoder_bridge_post_disable(struct drm_device *dev, struct drm_atomic_state *sta
|
||||
*/
|
||||
bridge = drm_bridge_chain_get_first_bridge(encoder);
|
||||
drm_atomic_bridge_chain_post_disable(bridge, state);
|
||||
drm_bridge_put(bridge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1501,6 +1505,7 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *state)
|
||||
|
||||
bridge = drm_bridge_chain_get_first_bridge(encoder);
|
||||
drm_bridge_chain_mode_set(bridge, mode, adjusted_mode);
|
||||
drm_bridge_put(bridge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1580,6 +1585,7 @@ encoder_bridge_pre_enable(struct drm_device *dev, struct drm_atomic_state *state
|
||||
*/
|
||||
bridge = drm_bridge_chain_get_first_bridge(encoder);
|
||||
drm_atomic_bridge_chain_pre_enable(bridge, state);
|
||||
drm_bridge_put(bridge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1655,6 +1661,7 @@ encoder_bridge_enable(struct drm_device *dev, struct drm_atomic_state *state)
|
||||
}
|
||||
|
||||
drm_atomic_bridge_chain_enable(bridge, state);
|
||||
drm_bridge_put(bridge);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -941,11 +941,11 @@ static int select_bus_fmt_recursive(struct drm_bridge *first_bridge,
|
||||
{
|
||||
unsigned int i, num_in_bus_fmts = 0;
|
||||
struct drm_bridge_state *cur_state;
|
||||
struct drm_bridge *prev_bridge;
|
||||
struct drm_bridge *prev_bridge __free(drm_bridge_put) =
|
||||
drm_bridge_get_prev_bridge(cur_bridge);
|
||||
u32 *in_bus_fmts;
|
||||
int ret;
|
||||
|
||||
prev_bridge = drm_bridge_get_prev_bridge(cur_bridge);
|
||||
cur_state = drm_atomic_get_new_bridge_state(crtc_state->state,
|
||||
cur_bridge);
|
||||
|
||||
@@ -1227,6 +1227,7 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_check);
|
||||
/**
|
||||
* drm_bridge_detect - check if anything is attached to the bridge output
|
||||
* @bridge: bridge control structure
|
||||
* @connector: attached connector
|
||||
*
|
||||
* If the bridge supports output detection, as reported by the
|
||||
* DRM_BRIDGE_OP_DETECT bridge ops flag, call &drm_bridge_funcs.detect for the
|
||||
|
||||
@@ -332,7 +332,12 @@ drm_gem_object_release_handle(int id, void *ptr, void *data)
|
||||
if (obj->funcs->close)
|
||||
obj->funcs->close(obj, file_priv);
|
||||
|
||||
mutex_lock(&file_priv->prime.lock);
|
||||
|
||||
drm_prime_remove_buf_handle(&file_priv->prime, id);
|
||||
|
||||
mutex_unlock(&file_priv->prime.lock);
|
||||
|
||||
drm_vma_node_revoke(&obj->vma_node, file_priv);
|
||||
|
||||
drm_gem_object_handle_put_unlocked(obj);
|
||||
@@ -870,14 +875,6 @@ long drm_gem_dma_resv_wait(struct drm_file *filep, u32 handle,
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gem_dma_resv_wait);
|
||||
|
||||
/**
|
||||
* drm_gem_close_ioctl - implementation of the GEM_CLOSE ioctl
|
||||
* @dev: drm_device
|
||||
* @data: ioctl data
|
||||
* @file_priv: drm file-private structure
|
||||
*
|
||||
* Releases the handle to an mm object.
|
||||
*/
|
||||
int
|
||||
drm_gem_close_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
@@ -893,17 +890,6 @@ drm_gem_close_ioctl(struct drm_device *dev, void *data,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_flink_ioctl - implementation of the GEM_FLINK ioctl
|
||||
* @dev: drm_device
|
||||
* @data: ioctl data
|
||||
* @file_priv: drm file-private structure
|
||||
*
|
||||
* Create a global name for an object, returning the name.
|
||||
*
|
||||
* Note that the name does not hold a reference; when the object
|
||||
* is freed, the name goes away.
|
||||
*/
|
||||
int
|
||||
drm_gem_flink_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
@@ -943,17 +929,6 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_open_ioctl - implementation of the GEM_OPEN ioctl
|
||||
* @dev: drm_device
|
||||
* @data: ioctl data
|
||||
* @file_priv: drm file-private structure
|
||||
*
|
||||
* Open an object using the global name, returning a handle and the size.
|
||||
*
|
||||
* This handle (of course) holds a reference to the object, so the object
|
||||
* will not go away until the handle is deleted.
|
||||
*/
|
||||
int
|
||||
drm_gem_open_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
@@ -988,6 +963,57 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
{
|
||||
struct drm_gem_change_handle *args = data;
|
||||
struct drm_gem_object *obj;
|
||||
int ret;
|
||||
|
||||
if (!drm_core_check_feature(dev, DRIVER_GEM))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
obj = drm_gem_object_lookup(file_priv, args->handle);
|
||||
if (!obj)
|
||||
return -ENOENT;
|
||||
|
||||
if (args->handle == args->new_handle)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&file_priv->prime.lock);
|
||||
|
||||
spin_lock(&file_priv->table_lock);
|
||||
ret = idr_alloc(&file_priv->object_idr, obj,
|
||||
args->new_handle, args->new_handle + 1, GFP_NOWAIT);
|
||||
spin_unlock(&file_priv->table_lock);
|
||||
|
||||
if (ret < 0)
|
||||
goto out_unlock;
|
||||
|
||||
if (obj->dma_buf) {
|
||||
ret = drm_prime_add_buf_handle(&file_priv->prime, obj->dma_buf, args->new_handle);
|
||||
if (ret < 0) {
|
||||
spin_lock(&file_priv->table_lock);
|
||||
idr_remove(&file_priv->object_idr, args->new_handle);
|
||||
spin_unlock(&file_priv->table_lock);
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
drm_prime_remove_buf_handle(&file_priv->prime, args->handle);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
spin_lock(&file_priv->table_lock);
|
||||
idr_remove(&file_priv->object_idr, args->handle);
|
||||
spin_unlock(&file_priv->table_lock);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&file_priv->prime.lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_open - initializes GEM file-private structures at devnode open time
|
||||
* @dev: drm_device which is being opened by userspace
|
||||
|
||||
@@ -270,6 +270,29 @@ npages_in_range(unsigned long start, unsigned long end)
|
||||
return (end - start) >> PAGE_SHIFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gpusvm_notifier_find() - Find GPU SVM notifier from GPU SVM
|
||||
* @gpusvm: Pointer to the GPU SVM structure.
|
||||
* @start: Start address of the notifier
|
||||
* @end: End address of the notifier
|
||||
*
|
||||
* Return: A pointer to the drm_gpusvm_notifier if found or NULL
|
||||
*/
|
||||
struct drm_gpusvm_notifier *
|
||||
drm_gpusvm_notifier_find(struct drm_gpusvm *gpusvm, unsigned long start,
|
||||
unsigned long end)
|
||||
{
|
||||
struct interval_tree_node *itree;
|
||||
|
||||
itree = interval_tree_iter_first(&gpusvm->root, start, end - 1);
|
||||
|
||||
if (itree)
|
||||
return container_of(itree, struct drm_gpusvm_notifier, itree);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gpusvm_notifier_find);
|
||||
|
||||
/**
|
||||
* drm_gpusvm_range_find() - Find GPU SVM range from GPU SVM notifier
|
||||
* @notifier: Pointer to the GPU SVM notifier structure.
|
||||
@@ -293,86 +316,6 @@ drm_gpusvm_range_find(struct drm_gpusvm_notifier *notifier, unsigned long start,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gpusvm_range_find);
|
||||
|
||||
/**
|
||||
* drm_gpusvm_for_each_range_safe() - Safely iterate over GPU SVM ranges in a notifier
|
||||
* @range__: Iterator variable for the ranges
|
||||
* @next__: Iterator variable for the ranges temporay storage
|
||||
* @notifier__: Pointer to the GPU SVM notifier
|
||||
* @start__: Start address of the range
|
||||
* @end__: End address of the range
|
||||
*
|
||||
* This macro is used to iterate over GPU SVM ranges in a notifier while
|
||||
* removing ranges from it.
|
||||
*/
|
||||
#define drm_gpusvm_for_each_range_safe(range__, next__, notifier__, start__, end__) \
|
||||
for ((range__) = drm_gpusvm_range_find((notifier__), (start__), (end__)), \
|
||||
(next__) = __drm_gpusvm_range_next(range__); \
|
||||
(range__) && (drm_gpusvm_range_start(range__) < (end__)); \
|
||||
(range__) = (next__), (next__) = __drm_gpusvm_range_next(range__))
|
||||
|
||||
/**
|
||||
* __drm_gpusvm_notifier_next() - get the next drm_gpusvm_notifier in the list
|
||||
* @notifier: a pointer to the current drm_gpusvm_notifier
|
||||
*
|
||||
* Return: A pointer to the next drm_gpusvm_notifier if available, or NULL if
|
||||
* the current notifier is the last one or if the input notifier is
|
||||
* NULL.
|
||||
*/
|
||||
static struct drm_gpusvm_notifier *
|
||||
__drm_gpusvm_notifier_next(struct drm_gpusvm_notifier *notifier)
|
||||
{
|
||||
if (notifier && !list_is_last(¬ifier->entry,
|
||||
¬ifier->gpusvm->notifier_list))
|
||||
return list_next_entry(notifier, entry);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct drm_gpusvm_notifier *
|
||||
notifier_iter_first(struct rb_root_cached *root, unsigned long start,
|
||||
unsigned long last)
|
||||
{
|
||||
struct interval_tree_node *itree;
|
||||
|
||||
itree = interval_tree_iter_first(root, start, last);
|
||||
|
||||
if (itree)
|
||||
return container_of(itree, struct drm_gpusvm_notifier, itree);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gpusvm_for_each_notifier() - Iterate over GPU SVM notifiers in a gpusvm
|
||||
* @notifier__: Iterator variable for the notifiers
|
||||
* @notifier__: Pointer to the GPU SVM notifier
|
||||
* @start__: Start address of the notifier
|
||||
* @end__: End address of the notifier
|
||||
*
|
||||
* This macro is used to iterate over GPU SVM notifiers in a gpusvm.
|
||||
*/
|
||||
#define drm_gpusvm_for_each_notifier(notifier__, gpusvm__, start__, end__) \
|
||||
for ((notifier__) = notifier_iter_first(&(gpusvm__)->root, (start__), (end__) - 1); \
|
||||
(notifier__) && (drm_gpusvm_notifier_start(notifier__) < (end__)); \
|
||||
(notifier__) = __drm_gpusvm_notifier_next(notifier__))
|
||||
|
||||
/**
|
||||
* drm_gpusvm_for_each_notifier_safe() - Safely iterate over GPU SVM notifiers in a gpusvm
|
||||
* @notifier__: Iterator variable for the notifiers
|
||||
* @next__: Iterator variable for the notifiers temporay storage
|
||||
* @notifier__: Pointer to the GPU SVM notifier
|
||||
* @start__: Start address of the notifier
|
||||
* @end__: End address of the notifier
|
||||
*
|
||||
* This macro is used to iterate over GPU SVM notifiers in a gpusvm while
|
||||
* removing notifiers from it.
|
||||
*/
|
||||
#define drm_gpusvm_for_each_notifier_safe(notifier__, next__, gpusvm__, start__, end__) \
|
||||
for ((notifier__) = notifier_iter_first(&(gpusvm__)->root, (start__), (end__) - 1), \
|
||||
(next__) = __drm_gpusvm_notifier_next(notifier__); \
|
||||
(notifier__) && (drm_gpusvm_notifier_start(notifier__) < (end__)); \
|
||||
(notifier__) = (next__), (next__) = __drm_gpusvm_notifier_next(notifier__))
|
||||
|
||||
/**
|
||||
* drm_gpusvm_notifier_invalidate() - Invalidate a GPU SVM notifier.
|
||||
* @mni: Pointer to the mmu_interval_notifier structure.
|
||||
@@ -472,22 +415,6 @@ int drm_gpusvm_init(struct drm_gpusvm *gpusvm,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gpusvm_init);
|
||||
|
||||
/**
|
||||
* drm_gpusvm_notifier_find() - Find GPU SVM notifier
|
||||
* @gpusvm: Pointer to the GPU SVM structure
|
||||
* @fault_addr: Fault address
|
||||
*
|
||||
* This function finds the GPU SVM notifier associated with the fault address.
|
||||
*
|
||||
* Return: Pointer to the GPU SVM notifier on success, NULL otherwise.
|
||||
*/
|
||||
static struct drm_gpusvm_notifier *
|
||||
drm_gpusvm_notifier_find(struct drm_gpusvm *gpusvm,
|
||||
unsigned long fault_addr)
|
||||
{
|
||||
return notifier_iter_first(&gpusvm->root, fault_addr, fault_addr + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* to_drm_gpusvm_notifier() - retrieve the container struct for a given rbtree node
|
||||
* @node: a pointer to the rbtree node embedded within a drm_gpusvm_notifier struct
|
||||
@@ -943,7 +870,7 @@ drm_gpusvm_range_find_or_insert(struct drm_gpusvm *gpusvm,
|
||||
if (!mmget_not_zero(mm))
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
notifier = drm_gpusvm_notifier_find(gpusvm, fault_addr);
|
||||
notifier = drm_gpusvm_notifier_find(gpusvm, fault_addr, fault_addr + 1);
|
||||
if (!notifier) {
|
||||
notifier = drm_gpusvm_notifier_alloc(gpusvm, fault_addr);
|
||||
if (IS_ERR(notifier)) {
|
||||
@@ -1107,7 +1034,8 @@ void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm,
|
||||
drm_gpusvm_driver_lock_held(gpusvm);
|
||||
|
||||
notifier = drm_gpusvm_notifier_find(gpusvm,
|
||||
drm_gpusvm_range_start(range));
|
||||
drm_gpusvm_range_start(range),
|
||||
drm_gpusvm_range_start(range) + 1);
|
||||
if (WARN_ON_ONCE(!notifier))
|
||||
return;
|
||||
|
||||
|
||||
@@ -420,6 +420,71 @@
|
||||
* new: |-----------|-----| (b.bo_offset=m,a.bo_offset=n+2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* DOC: Madvise Logic - Splitting and Traversal
|
||||
*
|
||||
* This logic handles GPU VA range updates by generating remap and map operations
|
||||
* without performing unmaps or merging existing mappings.
|
||||
*
|
||||
* 1) The requested range lies entirely within a single drm_gpuva. The logic splits
|
||||
* the existing mapping at the start and end boundaries and inserts a new map.
|
||||
*
|
||||
* ::
|
||||
* a start end b
|
||||
* pre: |-----------------------|
|
||||
* drm_gpuva1
|
||||
*
|
||||
* a start end b
|
||||
* new: |-----|=========|-------|
|
||||
* remap map remap
|
||||
*
|
||||
* one REMAP and one MAP : Same behaviour as SPLIT and MERGE
|
||||
*
|
||||
* 2) The requested range spans multiple drm_gpuva regions. The logic traverses
|
||||
* across boundaries, remapping the start and end segments, and inserting two
|
||||
* map operations to cover the full range.
|
||||
*
|
||||
* :: a start b c end d
|
||||
* pre: |------------------|--------------|------------------|
|
||||
* drm_gpuva1 drm_gpuva2 drm_gpuva3
|
||||
*
|
||||
* a start b c end d
|
||||
* new: |-------|==========|--------------|========|---------|
|
||||
* remap1 map1 drm_gpuva2 map2 remap2
|
||||
*
|
||||
* two REMAPS and two MAPS
|
||||
*
|
||||
* 3) Either start or end lies within a drm_gpuva. A single remap and map operation
|
||||
* are generated to update the affected portion.
|
||||
*
|
||||
*
|
||||
* :: a/start b c end d
|
||||
* pre: |------------------|--------------|------------------|
|
||||
* drm_gpuva1 drm_gpuva2 drm_gpuva3
|
||||
*
|
||||
* a/start b c end d
|
||||
* new: |------------------|--------------|========|---------|
|
||||
* drm_gpuva1 drm_gpuva2 map1 remap1
|
||||
*
|
||||
* :: a start b c/end d
|
||||
* pre: |------------------|--------------|------------------|
|
||||
* drm_gpuva1 drm_gpuva2 drm_gpuva3
|
||||
*
|
||||
* a start b c/end d
|
||||
* new: |-------|==========|--------------|------------------|
|
||||
* remap1 map1 drm_gpuva2 drm_gpuva3
|
||||
*
|
||||
* one REMAP and one MAP
|
||||
*
|
||||
* 4) Both start and end align with existing drm_gpuva boundaries. No operations
|
||||
* are needed as the range is already covered.
|
||||
*
|
||||
* 5) No existing drm_gpuvas. No operations.
|
||||
*
|
||||
* Unlike drm_gpuvm_sm_map_ops_create, this logic avoids unmaps and merging,
|
||||
* focusing solely on remap and map operations for efficient traversal and update.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DOC: Locking
|
||||
*
|
||||
@@ -486,13 +551,18 @@
|
||||
* u64 addr, u64 range,
|
||||
* struct drm_gem_object *obj, u64 offset)
|
||||
* {
|
||||
* struct drm_gpuvm_map_req map_req = {
|
||||
* .map.va.addr = addr,
|
||||
* .map.va.range = range,
|
||||
* .map.gem.obj = obj,
|
||||
* .map.gem.offset = offset,
|
||||
* };
|
||||
* struct drm_gpuva_ops *ops;
|
||||
* struct drm_gpuva_op *op
|
||||
* struct drm_gpuvm_bo *vm_bo;
|
||||
*
|
||||
* driver_lock_va_space();
|
||||
* ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
|
||||
* obj, offset);
|
||||
* ops = drm_gpuvm_sm_map_ops_create(gpuvm, &map_req);
|
||||
* if (IS_ERR(ops))
|
||||
* return PTR_ERR(ops);
|
||||
*
|
||||
@@ -2054,16 +2124,18 @@ EXPORT_SYMBOL_GPL(drm_gpuva_unmap);
|
||||
|
||||
static int
|
||||
op_map_cb(const struct drm_gpuvm_ops *fn, void *priv,
|
||||
u64 addr, u64 range,
|
||||
struct drm_gem_object *obj, u64 offset)
|
||||
const struct drm_gpuvm_map_req *req)
|
||||
{
|
||||
struct drm_gpuva_op op = {};
|
||||
|
||||
if (!req)
|
||||
return 0;
|
||||
|
||||
op.op = DRM_GPUVA_OP_MAP;
|
||||
op.map.va.addr = addr;
|
||||
op.map.va.range = range;
|
||||
op.map.gem.obj = obj;
|
||||
op.map.gem.offset = offset;
|
||||
op.map.va.addr = req->map.va.addr;
|
||||
op.map.va.range = req->map.va.range;
|
||||
op.map.gem.obj = req->map.gem.obj;
|
||||
op.map.gem.offset = req->map.gem.offset;
|
||||
|
||||
return fn->sm_step_map(&op, priv);
|
||||
}
|
||||
@@ -2088,10 +2160,13 @@ op_remap_cb(const struct drm_gpuvm_ops *fn, void *priv,
|
||||
|
||||
static int
|
||||
op_unmap_cb(const struct drm_gpuvm_ops *fn, void *priv,
|
||||
struct drm_gpuva *va, bool merge)
|
||||
struct drm_gpuva *va, bool merge, bool madvise)
|
||||
{
|
||||
struct drm_gpuva_op op = {};
|
||||
|
||||
if (madvise)
|
||||
return 0;
|
||||
|
||||
op.op = DRM_GPUVA_OP_UNMAP;
|
||||
op.unmap.va = va;
|
||||
op.unmap.keep = merge;
|
||||
@@ -2102,10 +2177,15 @@ op_unmap_cb(const struct drm_gpuvm_ops *fn, void *priv,
|
||||
static int
|
||||
__drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm,
|
||||
const struct drm_gpuvm_ops *ops, void *priv,
|
||||
u64 req_addr, u64 req_range,
|
||||
struct drm_gem_object *req_obj, u64 req_offset)
|
||||
const struct drm_gpuvm_map_req *req,
|
||||
bool madvise)
|
||||
{
|
||||
struct drm_gem_object *req_obj = req->map.gem.obj;
|
||||
const struct drm_gpuvm_map_req *op_map = madvise ? NULL : req;
|
||||
struct drm_gpuva *va, *next;
|
||||
u64 req_offset = req->map.gem.offset;
|
||||
u64 req_range = req->map.va.range;
|
||||
u64 req_addr = req->map.va.addr;
|
||||
u64 req_end = req_addr + req_range;
|
||||
int ret;
|
||||
|
||||
@@ -2120,19 +2200,22 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm,
|
||||
u64 end = addr + range;
|
||||
bool merge = !!va->gem.obj;
|
||||
|
||||
if (madvise && obj)
|
||||
continue;
|
||||
|
||||
if (addr == req_addr) {
|
||||
merge &= obj == req_obj &&
|
||||
offset == req_offset;
|
||||
|
||||
if (end == req_end) {
|
||||
ret = op_unmap_cb(ops, priv, va, merge);
|
||||
ret = op_unmap_cb(ops, priv, va, merge, madvise);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
}
|
||||
|
||||
if (end < req_end) {
|
||||
ret = op_unmap_cb(ops, priv, va, merge);
|
||||
ret = op_unmap_cb(ops, priv, va, merge, madvise);
|
||||
if (ret)
|
||||
return ret;
|
||||
continue;
|
||||
@@ -2153,6 +2236,9 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm,
|
||||
ret = op_remap_cb(ops, priv, NULL, &n, &u);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (madvise)
|
||||
op_map = req;
|
||||
break;
|
||||
}
|
||||
} else if (addr < req_addr) {
|
||||
@@ -2173,6 +2259,9 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm,
|
||||
ret = op_remap_cb(ops, priv, &p, NULL, &u);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (madvise)
|
||||
op_map = req;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2180,6 +2269,18 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm,
|
||||
ret = op_remap_cb(ops, priv, &p, NULL, &u);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (madvise) {
|
||||
struct drm_gpuvm_map_req map_req = {
|
||||
.map.va.addr = req_addr,
|
||||
.map.va.range = end - req_addr,
|
||||
};
|
||||
|
||||
ret = op_map_cb(ops, priv, &map_req);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2195,6 +2296,9 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm,
|
||||
ret = op_remap_cb(ops, priv, &p, &n, &u);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (madvise)
|
||||
op_map = req;
|
||||
break;
|
||||
}
|
||||
} else if (addr > req_addr) {
|
||||
@@ -2203,16 +2307,18 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm,
|
||||
(addr - req_addr);
|
||||
|
||||
if (end == req_end) {
|
||||
ret = op_unmap_cb(ops, priv, va, merge);
|
||||
ret = op_unmap_cb(ops, priv, va, merge, madvise);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (end < req_end) {
|
||||
ret = op_unmap_cb(ops, priv, va, merge);
|
||||
ret = op_unmap_cb(ops, priv, va, merge, madvise);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2231,14 +2337,20 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm,
|
||||
ret = op_remap_cb(ops, priv, NULL, &n, &u);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (madvise) {
|
||||
struct drm_gpuvm_map_req map_req = {
|
||||
.map.va.addr = addr,
|
||||
.map.va.range = req_end - addr,
|
||||
};
|
||||
|
||||
return op_map_cb(ops, priv, &map_req);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return op_map_cb(ops, priv,
|
||||
req_addr, req_range,
|
||||
req_obj, req_offset);
|
||||
return op_map_cb(ops, priv, op_map);
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -2290,7 +2402,7 @@ __drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm,
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
ret = op_unmap_cb(ops, priv, va, false);
|
||||
ret = op_unmap_cb(ops, priv, va, false, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@@ -2303,10 +2415,7 @@ __drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm,
|
||||
* drm_gpuvm_sm_map() - calls the &drm_gpuva_op split/merge steps
|
||||
* @gpuvm: the &drm_gpuvm representing the GPU VA space
|
||||
* @priv: pointer to a driver private data structure
|
||||
* @req_addr: the start address of the new mapping
|
||||
* @req_range: the range of the new mapping
|
||||
* @req_obj: the &drm_gem_object to map
|
||||
* @req_offset: the offset within the &drm_gem_object
|
||||
* @req: ptr to struct drm_gpuvm_map_req
|
||||
*
|
||||
* This function iterates the given range of the GPU VA space. It utilizes the
|
||||
* &drm_gpuvm_ops to call back into the driver providing the split and merge
|
||||
@@ -2333,8 +2442,7 @@ __drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm,
|
||||
*/
|
||||
int
|
||||
drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, void *priv,
|
||||
u64 req_addr, u64 req_range,
|
||||
struct drm_gem_object *req_obj, u64 req_offset)
|
||||
const struct drm_gpuvm_map_req *req)
|
||||
{
|
||||
const struct drm_gpuvm_ops *ops = gpuvm->ops;
|
||||
|
||||
@@ -2343,9 +2451,7 @@ drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, void *priv,
|
||||
ops->sm_step_unmap)))
|
||||
return -EINVAL;
|
||||
|
||||
return __drm_gpuvm_sm_map(gpuvm, ops, priv,
|
||||
req_addr, req_range,
|
||||
req_obj, req_offset);
|
||||
return __drm_gpuvm_sm_map(gpuvm, ops, priv, req, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gpuvm_sm_map);
|
||||
|
||||
@@ -2421,10 +2527,7 @@ static const struct drm_gpuvm_ops lock_ops = {
|
||||
* @gpuvm: the &drm_gpuvm representing the GPU VA space
|
||||
* @exec: the &drm_exec locking context
|
||||
* @num_fences: for newly mapped objects, the # of fences to reserve
|
||||
* @req_addr: the start address of the range to unmap
|
||||
* @req_range: the range of the mappings to unmap
|
||||
* @req_obj: the &drm_gem_object to map
|
||||
* @req_offset: the offset within the &drm_gem_object
|
||||
* @req: ptr to drm_gpuvm_map_req struct
|
||||
*
|
||||
* This function locks (drm_exec_lock_obj()) objects that will be unmapped/
|
||||
* remapped, and locks+prepares (drm_exec_prepare_object()) objects that
|
||||
@@ -2445,9 +2548,7 @@ static const struct drm_gpuvm_ops lock_ops = {
|
||||
* ret = drm_gpuvm_sm_unmap_exec_lock(gpuvm, &exec, op->addr, op->range);
|
||||
* break;
|
||||
* case DRIVER_OP_MAP:
|
||||
* ret = drm_gpuvm_sm_map_exec_lock(gpuvm, &exec, num_fences,
|
||||
* op->addr, op->range,
|
||||
* obj, op->obj_offset);
|
||||
* ret = drm_gpuvm_sm_map_exec_lock(gpuvm, &exec, num_fences, &req);
|
||||
* break;
|
||||
* }
|
||||
*
|
||||
@@ -2478,18 +2579,17 @@ static const struct drm_gpuvm_ops lock_ops = {
|
||||
int
|
||||
drm_gpuvm_sm_map_exec_lock(struct drm_gpuvm *gpuvm,
|
||||
struct drm_exec *exec, unsigned int num_fences,
|
||||
u64 req_addr, u64 req_range,
|
||||
struct drm_gem_object *req_obj, u64 req_offset)
|
||||
struct drm_gpuvm_map_req *req)
|
||||
{
|
||||
struct drm_gem_object *req_obj = req->map.gem.obj;
|
||||
|
||||
if (req_obj) {
|
||||
int ret = drm_exec_prepare_obj(exec, req_obj, num_fences);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return __drm_gpuvm_sm_map(gpuvm, &lock_ops, exec,
|
||||
req_addr, req_range,
|
||||
req_obj, req_offset);
|
||||
return __drm_gpuvm_sm_map(gpuvm, &lock_ops, exec, req, false);
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gpuvm_sm_map_exec_lock);
|
||||
@@ -2608,13 +2708,42 @@ static const struct drm_gpuvm_ops gpuvm_list_ops = {
|
||||
.sm_step_unmap = drm_gpuva_sm_step,
|
||||
};
|
||||
|
||||
static struct drm_gpuva_ops *
|
||||
__drm_gpuvm_sm_map_ops_create(struct drm_gpuvm *gpuvm,
|
||||
const struct drm_gpuvm_map_req *req,
|
||||
bool madvise)
|
||||
{
|
||||
struct drm_gpuva_ops *ops;
|
||||
struct {
|
||||
struct drm_gpuvm *vm;
|
||||
struct drm_gpuva_ops *ops;
|
||||
} args;
|
||||
int ret;
|
||||
|
||||
ops = kzalloc(sizeof(*ops), GFP_KERNEL);
|
||||
if (unlikely(!ops))
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
INIT_LIST_HEAD(&ops->list);
|
||||
|
||||
args.vm = gpuvm;
|
||||
args.ops = ops;
|
||||
|
||||
ret = __drm_gpuvm_sm_map(gpuvm, &gpuvm_list_ops, &args, req, madvise);
|
||||
if (ret)
|
||||
goto err_free_ops;
|
||||
|
||||
return ops;
|
||||
|
||||
err_free_ops:
|
||||
drm_gpuva_ops_free(gpuvm, ops);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gpuvm_sm_map_ops_create() - creates the &drm_gpuva_ops to split and merge
|
||||
* @gpuvm: the &drm_gpuvm representing the GPU VA space
|
||||
* @req_addr: the start address of the new mapping
|
||||
* @req_range: the range of the new mapping
|
||||
* @req_obj: the &drm_gem_object to map
|
||||
* @req_offset: the offset within the &drm_gem_object
|
||||
* @req: map request arguments
|
||||
*
|
||||
* This function creates a list of operations to perform splitting and merging
|
||||
* of existent mapping(s) with the newly requested one.
|
||||
@@ -2642,39 +2771,49 @@ static const struct drm_gpuvm_ops gpuvm_list_ops = {
|
||||
*/
|
||||
struct drm_gpuva_ops *
|
||||
drm_gpuvm_sm_map_ops_create(struct drm_gpuvm *gpuvm,
|
||||
u64 req_addr, u64 req_range,
|
||||
struct drm_gem_object *req_obj, u64 req_offset)
|
||||
const struct drm_gpuvm_map_req *req)
|
||||
{
|
||||
struct drm_gpuva_ops *ops;
|
||||
struct {
|
||||
struct drm_gpuvm *vm;
|
||||
struct drm_gpuva_ops *ops;
|
||||
} args;
|
||||
int ret;
|
||||
|
||||
ops = kzalloc(sizeof(*ops), GFP_KERNEL);
|
||||
if (unlikely(!ops))
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
INIT_LIST_HEAD(&ops->list);
|
||||
|
||||
args.vm = gpuvm;
|
||||
args.ops = ops;
|
||||
|
||||
ret = __drm_gpuvm_sm_map(gpuvm, &gpuvm_list_ops, &args,
|
||||
req_addr, req_range,
|
||||
req_obj, req_offset);
|
||||
if (ret)
|
||||
goto err_free_ops;
|
||||
|
||||
return ops;
|
||||
|
||||
err_free_ops:
|
||||
drm_gpuva_ops_free(gpuvm, ops);
|
||||
return ERR_PTR(ret);
|
||||
return __drm_gpuvm_sm_map_ops_create(gpuvm, req, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gpuvm_sm_map_ops_create);
|
||||
|
||||
/**
|
||||
* drm_gpuvm_madvise_ops_create() - creates the &drm_gpuva_ops to split
|
||||
* @gpuvm: the &drm_gpuvm representing the GPU VA space
|
||||
* @req: map request arguments
|
||||
*
|
||||
* This function creates a list of operations to perform splitting
|
||||
* of existent mapping(s) at start or end, based on the request map.
|
||||
*
|
||||
* The list can be iterated with &drm_gpuva_for_each_op and must be processed
|
||||
* in the given order. It can contain map and remap operations, but it
|
||||
* also can be empty if no operation is required, e.g. if the requested mapping
|
||||
* already exists is the exact same way.
|
||||
*
|
||||
* There will be no unmap operations, a maximum of two remap operations and two
|
||||
* map operations. The two map operations correspond to: one from start to the
|
||||
* end of drm_gpuvaX, and another from the start of drm_gpuvaY to end.
|
||||
*
|
||||
* Note that before calling this function again with another mapping request it
|
||||
* is necessary to update the &drm_gpuvm's view of the GPU VA space. The
|
||||
* previously obtained operations must be either processed or abandoned. To
|
||||
* update the &drm_gpuvm's view of the GPU VA space drm_gpuva_insert(),
|
||||
* drm_gpuva_destroy_locked() and/or drm_gpuva_destroy_unlocked() should be
|
||||
* used.
|
||||
*
|
||||
* After the caller finished processing the returned &drm_gpuva_ops, they must
|
||||
* be freed with &drm_gpuva_ops_free.
|
||||
*
|
||||
* Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR on failure
|
||||
*/
|
||||
struct drm_gpuva_ops *
|
||||
drm_gpuvm_madvise_ops_create(struct drm_gpuvm *gpuvm,
|
||||
const struct drm_gpuvm_map_req *req)
|
||||
{
|
||||
return __drm_gpuvm_sm_map_ops_create(gpuvm, req, true);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gpuvm_madvise_ops_create);
|
||||
|
||||
/**
|
||||
* drm_gpuvm_sm_unmap_ops_create() - creates the &drm_gpuva_ops to split on
|
||||
* unmap
|
||||
|
||||
@@ -85,6 +85,8 @@ int drm_prime_fd_to_handle_ioctl(struct drm_device *dev, void *data,
|
||||
|
||||
void drm_prime_init_file_private(struct drm_prime_file_private *prime_fpriv);
|
||||
void drm_prime_destroy_file_private(struct drm_prime_file_private *prime_fpriv);
|
||||
int drm_prime_add_buf_handle(struct drm_prime_file_private *prime_fpriv,
|
||||
struct dma_buf *dma_buf, uint32_t handle);
|
||||
void drm_prime_remove_buf_handle(struct drm_prime_file_private *prime_fpriv,
|
||||
uint32_t handle);
|
||||
|
||||
@@ -170,6 +172,8 @@ int drm_gem_close_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
int drm_gem_flink_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
int drm_gem_open_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
void drm_gem_open(struct drm_device *dev, struct drm_file *file_private);
|
||||
|
||||
@@ -653,6 +653,7 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
|
||||
DRM_IOCTL_DEF(DRM_IOCTL_GEM_CLOSE, drm_gem_close_ioctl, DRM_RENDER_ALLOW),
|
||||
DRM_IOCTL_DEF(DRM_IOCTL_GEM_FLINK, drm_gem_flink_ioctl, DRM_AUTH),
|
||||
DRM_IOCTL_DEF(DRM_IOCTL_GEM_OPEN, drm_gem_open_ioctl, DRM_AUTH),
|
||||
DRM_IOCTL_DEF(DRM_IOCTL_GEM_CHANGE_HANDLE, drm_gem_change_handle_ioctl, DRM_RENDER_ALLOW),
|
||||
|
||||
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, 0),
|
||||
|
||||
|
||||
@@ -773,41 +773,13 @@ ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
|
||||
EXPORT_SYMBOL(mipi_dsi_generic_write);
|
||||
|
||||
/**
|
||||
* mipi_dsi_generic_write_chatty() - mipi_dsi_generic_write() w/ an error log
|
||||
* @dsi: DSI peripheral device
|
||||
* @payload: buffer containing the payload
|
||||
* @size: size of payload buffer
|
||||
*
|
||||
* Like mipi_dsi_generic_write() but includes a dev_err()
|
||||
* call for you and returns 0 upon success, not the number of bytes sent.
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
|
||||
const void *payload, size_t size)
|
||||
{
|
||||
struct device *dev = &dsi->dev;
|
||||
ssize_t ret;
|
||||
|
||||
ret = mipi_dsi_generic_write(dsi, payload, size);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "sending generic data %*ph failed: %zd\n",
|
||||
(int)size, payload, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_generic_write_chatty);
|
||||
|
||||
/**
|
||||
* mipi_dsi_generic_write_multi() - mipi_dsi_generic_write_chatty() w/ accum_err
|
||||
* mipi_dsi_generic_write_multi() - mipi_dsi_generic_write() w/ accum_err
|
||||
* @ctx: Context for multiple DSI transactions
|
||||
* @payload: buffer containing the payload
|
||||
* @size: size of payload buffer
|
||||
*
|
||||
* Like mipi_dsi_generic_write_chatty() but deals with errors in a way that
|
||||
* makes it convenient to make several calls in a row.
|
||||
* A wrapper around mipi_dsi_generic_write() that deals with errors in a way
|
||||
* that makes it convenient to make several calls in a row.
|
||||
*/
|
||||
void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
|
||||
const void *payload, size_t size)
|
||||
@@ -828,6 +800,30 @@ void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_generic_write_multi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dual_generic_write_multi() - mipi_dsi_generic_write_multi() for
|
||||
* two dsi channels, one after the other
|
||||
* @ctx: Context for multiple DSI transactions
|
||||
* @dsi1: First dsi channel to write buffer to
|
||||
* @dsi2: Second dsi channel to write buffer to
|
||||
* @payload: Buffer containing the payload
|
||||
* @size: Size of payload buffer
|
||||
*
|
||||
* A wrapper around mipi_dsi_generic_write_multi() that allows the user to
|
||||
* conveniently write to two dsi channels, one after the other.
|
||||
*/
|
||||
void mipi_dsi_dual_generic_write_multi(struct mipi_dsi_multi_context *ctx,
|
||||
struct mipi_dsi_device *dsi1,
|
||||
struct mipi_dsi_device *dsi2,
|
||||
const void *payload, size_t size)
|
||||
{
|
||||
ctx->dsi = dsi1;
|
||||
mipi_dsi_generic_write_multi(ctx, payload, size);
|
||||
ctx->dsi = dsi2;
|
||||
mipi_dsi_generic_write_multi(ctx, payload, size);
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dual_generic_write_multi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_generic_read() - receive data using a generic read packet
|
||||
* @dsi: DSI peripheral device
|
||||
@@ -1007,6 +1003,30 @@ void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_multi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dual_dcs_write_buffer_multi - mipi_dsi_dcs_write_buffer_multi() for
|
||||
* two dsi channels, one after the other
|
||||
* @ctx: Context for multiple DSI transactions
|
||||
* @dsi1: First dsi channel to write buffer to
|
||||
* @dsi2: Second dsi channel to write buffer to
|
||||
* @data: Buffer containing data to be transmitted
|
||||
* @len: Size of transmission buffer
|
||||
*
|
||||
* A wrapper around mipi_dsi_dcs_write_buffer_multi() that allows the user to
|
||||
* conveniently write to two dsi channels, one after the other.
|
||||
*/
|
||||
void mipi_dsi_dual_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
|
||||
struct mipi_dsi_device *dsi1,
|
||||
struct mipi_dsi_device *dsi2,
|
||||
const void *data, size_t len)
|
||||
{
|
||||
ctx->dsi = dsi1;
|
||||
mipi_dsi_dcs_write_buffer_multi(ctx, data, len);
|
||||
ctx->dsi = dsi2;
|
||||
mipi_dsi_dcs_write_buffer_multi(ctx, data, len);
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dual_dcs_write_buffer_multi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_write() - send DCS write command
|
||||
* @dsi: DSI peripheral device
|
||||
@@ -1076,6 +1096,43 @@ ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data,
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_read);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_read_multi() - mipi_dsi_dcs_read() w/ accum_err
|
||||
* @ctx: Context for multiple DSI transactions
|
||||
* @cmd: DCS command
|
||||
* @data: buffer in which to receive data
|
||||
* @len: size of receive buffer
|
||||
*
|
||||
* Like mipi_dsi_dcs_read() but deals with errors in a way that makes it
|
||||
* convenient to make several calls in a row.
|
||||
*/
|
||||
void mipi_dsi_dcs_read_multi(struct mipi_dsi_multi_context *ctx, u8 cmd,
|
||||
void *data, size_t len)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = ctx->dsi;
|
||||
struct device *dev = &dsi->dev;
|
||||
struct mipi_dsi_msg msg = {
|
||||
.channel = dsi->channel,
|
||||
.type = MIPI_DSI_DCS_READ,
|
||||
.tx_buf = &cmd,
|
||||
.tx_len = 1,
|
||||
.rx_buf = data,
|
||||
.rx_len = len
|
||||
};
|
||||
ssize_t ret;
|
||||
|
||||
if (ctx->accum_err)
|
||||
return;
|
||||
|
||||
ret = mipi_dsi_device_transfer(dsi, &msg);
|
||||
if (ret < 0) {
|
||||
ctx->accum_err = ret;
|
||||
dev_err(dev, "dcs read with command %#x failed: %d\n", cmd,
|
||||
ctx->accum_err);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_read_multi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_nop() - send DCS nop packet
|
||||
* @dsi: DSI peripheral device
|
||||
|
||||
@@ -93,7 +93,7 @@ struct drm_prime_member {
|
||||
struct rb_node handle_rb;
|
||||
};
|
||||
|
||||
static int drm_prime_add_buf_handle(struct drm_prime_file_private *prime_fpriv,
|
||||
int drm_prime_add_buf_handle(struct drm_prime_file_private *prime_fpriv,
|
||||
struct dma_buf *dma_buf, uint32_t handle)
|
||||
{
|
||||
struct drm_prime_member *member;
|
||||
@@ -190,8 +190,6 @@ void drm_prime_remove_buf_handle(struct drm_prime_file_private *prime_fpriv,
|
||||
{
|
||||
struct rb_node *rb;
|
||||
|
||||
mutex_lock(&prime_fpriv->lock);
|
||||
|
||||
rb = prime_fpriv->handles.rb_node;
|
||||
while (rb) {
|
||||
struct drm_prime_member *member;
|
||||
@@ -210,8 +208,6 @@ void drm_prime_remove_buf_handle(struct drm_prime_file_private *prime_fpriv,
|
||||
rb = rb->rb_left;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&prime_fpriv->lock);
|
||||
}
|
||||
|
||||
void drm_prime_init_file_private(struct drm_prime_file_private *prime_fpriv)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user