Skip to contentSkip to conversation

drm/msm/dcp: Add Qualcomm DCP display support

PATCH In review Jayant Shekhar 6 participants 68 messages Oct 26 – Nov 11 Page 1 of 2

Subsystems (8)
lore
Share & tools
lore
#0 lore raw

v2 cover drm/msm/dcp: Add Qualcomm DCP display support

On Qualcomm platforms using the Display Co-Processor (DCP), the display hardware is owned and programmed by firmware. Linux does not access the display controller, DSI controller, DSI PHY, display clocks, or other display pipeline registers.

The display pipeline must nevertheless be exposed through the DRM/KMS interfaces so userspace can perform modesetting, framebuffer updates, panel control, and page flips.

This series adds a DCP KMS backend to MSM DRM. It represents the firmware-owned display pipeline and translates DRM operations into HFI commands.

DCP communicates with Linux over VirtIO and uses the HFI protocol for command and response handling, capability discovery, and display events.

This series adds:

- Device-tree bindings for the Qualcomm DCP VirtIO transport wrapper, firmware-visible IOVA ranges, separate transport and framebuffer IOMMU contexts, IPCC power notification, interrupts, and the external DSI display graph.

- HFI protocol definitions and packet helpers for capability discovery, modesetting, plane and CRTC configuration, DSI, and panel control.

- A Qualcomm DCP transport wrapper which initializes the IPCC channel, sends the required power notification, and registers the DCP VirtIO device directly. The wrapper also implements the MMIO transport and keeps VirtIO queue activity ordered with DCP power transitions.

- VirtIO command and response queues. HFI command, response, and capability buffers are allocated through the DMA API in the address space visible to DCP firmware.

- Synchronous command tracking, asynchronous response dispatch, timeout handling, and queue teardown synchronization for safe device removal.

- Capability discovery for displays, CRTCs, mixers, VIG and DMA pipes, DSI interfaces, and panel resources.

- DRM planes and CRTCs using the standard atomic-state model. DRM state is translated into HFI properties for layer configuration, mixer limits, bandwidth and clock properties, frame sequencing, and triggering.

- An MSM KMS backend, DCP encoder, and DSI bridge. The backend reuses the existing MSM DRM device, GEM and framebuffer handling, atomic commit infrastructure, encoder, bridge, and connector support.

- Asynchronous VSYNC, frame-scan completion, and error events propagated to DRM for vblank and page-flip completion.

- IPCC power notifications to wake DCP firmware and coordinate HFI queue activity across firmware power transitions.

DCP is integrated as an MSM KMS backend rather than a separate DRM device. This preserves the userspace-visible MSM DRM device and allows Linux to retain normal DRM/KMS semantics while display hardware remains owned by firmware.

The initial implementation supports the DSI output described below and uses resources reported by firmware. Split-pipe, multirect, cursor-pipe, partial-update, DisplayPort, and writeback support are deferred.

Tested on the Qualcomm Hawi MTP with an NT37801 DSC command-mode panel:

- Display console at 1440x3200.
- Panel initialization and DCS transfers complete successfully.

Device Tree Topology
====================

qcom,dcp-virtio

  ├── transport IOMMU context
  ├── firmware-visible iommu-ranges
  ├── VirtIO and power-notify interrupts
  ├── dcp-power-tx mailbox
  └── dcp-subsystem
      ├── compatible = "virtio,device34"
      ├── framebuffer IOMMU context
      ├── port@0
      │   └── endpoint ─────────────── panel endpoint
      └── dsi@0
          └── panel@0

The DCP INTF endpoint connects directly to the panel endpoint. Firmware internal paths, including the INTF-to-DSI connection, are not described in the device tree.

Runtime Architecture
====================

Linux DRM/KMS

  └── DCP MSM KMS backend
      ├── planes, CRTCs, encoder, connector, and DSI bridge
      └── DRM atomic state and commit handling
                  │

▼
DCP VirtIO function driver
│
▼
VirtIO core
│
▼
qcom,dcp-virtio transport wrapper
├── IPCC initialization and power notification ├── MMIO register access
├── VirtIO queue setup and interrupt handling ├── VirtIO device registration
└── suspend/resume power sequencing
│
▼
DCP firmware
├── HFI command and response handling ├── display pipeline and VIG/DMA pipes ├── DSI PHY and controller
└── display clocks and resource handling

Probe and Initialization Order
=============================

qcom,dcp-virtio platform probe
│
▼
Initialize IPCC and send the DCP power notification
│
▼
Access VirtIO MMIO registers and register the virtio_device
│
▼
VirtIO core matches the DCP function driver
│
▼
Initialize HFI and create command/response virtqueues
│
▼
Send DEVICE_INIT and discover firmware capabilities
│
▼
Register the MSM DRM/KMS backend

The transport must complete IPCC initialization and power notification before it accesses the VirtIO MMIO registers. The DCP function driver is matched only after the transport registers the virtio_device.

Dependencies
============

- The VPU IOMMU series provides the IOVA handling required by the DCP VirtIO transport. The Hawi DT series provides the platform nodes and IOMMU descriptions consumed by this series.

https://lore.kernel.org/all/20260926-vpu_iommu_iova_handling-v5-4-0322ca5dc10c@oss.qualcomm.com/

- The QPSFI VirtIO device ID used by the DCP transport is proposed in the following virtio-comment discussion:

https://lore.kernel.org/virtio-comment/[email protected]/T/#u

This series adds the corresponding device ID to include/uapi/linux/virtio_ids.h and uses it to bind the DCP VirtIO function driver.

---
Changes in v2:
- Compared with v1, the series is split into smaller patches. (Dmitry)
- Reworked the VirtIO transport integration so the Qualcomm qcom,dcp-virtio wrapper performs the required IPCC power notification before accessing the VirtIO MMIO registers and registering the virtio_device. The transport registers the device directly.
- Reworked the DT topology so the DCP INTF output connects directly to the panel endpoint. The firmware-internal INTF-to-DSI path is no described in DT. (Dmitry)
- Updated the DCP VirtIO binding to document the Qualcomm VirtIO transport and VirtIO MMIO register layout, Qualcomm power-notify interrupt, IOMMU contexts, mailbox, and firmware-visible IOVA range. (Dmitry)
- Removed the interrupt-names property and used the defined interrupt ordering for the VirtIO and power-notify interrupts. (Dmitry)
- Added panel bridge and component integration so panel bridges are resolved through the DCP DSI host before DRM registration. (Dmitry)
- Tracked CRTCs using firmware display IDs instead of DRM CRTC iteration order. (Dmitry)
- Moved DSI DCS-buffer mapping into KMS hardware initialization, after the IOMMU VM became available, with rollback unmapping on failure and before VM teardown.
- Reworked IPCC resource handling to use managed IRQ and mailbox cleanup. (Dmitry)
- Moved IRQ-wake handling to the suspend and resume paths. (Dmitry)
- Removed the ipcc_valid state and made IPCC initialization and teardown follow the channel lifetime. (Dmitry)
- Embedded the HFI capability cache and removed unused capability fields, feature flags, and callback state. (Dmitry)
- Removed VirtIO config-change capability re-query handling because capabilities are consumed during initialization.
- Removed the temporary self-component shim and registered DCP DSI hosts as components. (Dmitry)
- Added optional GPU component matching for the display-only DCP DRM device. (Dmitry)
- Made HFI packet-ID allocation atomic across wraparound using cmpxchg(). (Dmitry)
- Updated bandwidth aggregation to ignore invisible planes and planes without framebuffers. (Dmitry)
- Moved DSI topology values into atomic bridge state while keeping static panel configuration separate from per-commit state. (Dmitry)
- Removed the unused DP catalog and resource path. (Dmitry)
- Removed unused HFI hotplug and error callback plumbing. (Dmitry)
- Simplified HFI buffer release paths for callers already holding the command lock. (Dmitry)
- Exported the generic MSM GPU-component matching helper for the DCP backend.
- Replaced deprecated DRM logging macros with DRM device logging helpers. (Dmitry)
- Removed unused includes and fixed indentation, naming, logging, and error-handling issues. (Dmitry)
- Removed status in DT bindings and commit subject. (Krzysztof)
- Documented the DSI host DT ABI: DSI hosts use the generic dsi@N node naming convention, and their reg properties identify the corresponding DCP output index. (Krzysztof)
- Fixed the Assisted-by trailer order. (Dmitry, Krzysztof)

- Link to v1: https://patch.msgid.link/20261015-jshekhar-drm-msm-dcp-v1-v1-0-48aad0fe4f72@oss.qualcomm.com

To: Rob Clark <[email protected]>
To: Dmitry Baryshkov <[email protected]>
To: Abhinav Kumar <[email protected]>
To: Jessica Zhang <[email protected]>
To: Sean Paul <[email protected]>
To: Marijn Suijten <[email protected]>
To: David Airlie <[email protected]>
To: Simona Vetter <[email protected]>
To: Maarten Lankhorst <[email protected]>
To: Maxime Ripard <[email protected]>
To: Thomas Zimmermann <[email protected]>
To: Rob Herring <[email protected]>
To: Krzysztof Kozlowski <[email protected]>
To: Conor Dooley <[email protected]>
To: "Michael S. Tsirkin" <[email protected]>
To: Jason Wang <[email protected]>
To: Eugenio Pérez <[email protected]>
To: Xuan Zhuo <[email protected]>
To: Jayant Shekhar <[email protected]>
To: Sumit Semwal <[email protected]>
To: Christian König <[email protected]>
To: Philipp Stanner <[email protected]>
To: Bjorn Andersson <[email protected]>
To: Konrad Dybcio <[email protected]>
To: Abel Vesa <[email protected]>
To: "Joerg Roedel (AMD)" <[email protected]>
To: Will Deacon <[email protected]>
To: Robin Murphy <[email protected]>
To: Flemin Jose <[email protected]>
To: Mahadevan P <[email protected]>
To: Michael S. Tsirkin <[email protected]>
To: Jason Wang <[email protected]>
To: Eugenio Pérez <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]

---
Jayant Shekhar (23):
dt-bindings: display: msm: Add Qualcomm DCP drm/msm/dcp: Add HFI packet helpers
drm/msm/dcp: Add HFI device definitions drm/msm/dcp: Add HFI display definitions drm/msm/dcp: Add HFI panel definitions drm/msm/dcp: Add HFI VirtIO transport drm/msm/dcp: Handle asynchronous HFI responses drm/msm/dcp: Add IPCC power notifications drm/msm/dcp: Add generic HFI display commands drm/msm/dcp: Add HFI DSI and panel commands drm/msm/dcp: Add DCP CRTCs and shared plane state drm/msm/dcp: Add DCP planes
drm/msm/dcp: Route HFI display events drm/msm/dcp: Add DCP encoder
drm/msm/dcp: Add DCP DSI host and panel support drm/msm/dcp: Integrate the DSI bridge with KMS drm/msm/dcp: Register DCP display pipeline
virtio: Add Qualcomm Platform Services Firmware Interface ID drm/msm/dcp: Add Qualcomm VirtIO transport wrapper drm/msm/dcp: Add DCP VirtIO driver
drm/msm: Support KMS backends without a display IRQ drm/msm: Register the DCP backend
arm64: dts: qcom: hawi: Add DCP support

 .../bindings/display/msm/qcom,dcp-dsi.yaml         |   79 ++
 .../devicetree/bindings/display/msm/qcom,dcp.yaml  |  123 +++
 .../bindings/virtio/qcom,dcp-virtio.yaml           |  132 +++
 arch/arm64/boot/dts/qcom/hawi-mtp.dts              |  129 +++
 arch/arm64/boot/dts/qcom/hawi.dtsi                 |   40 +
 drivers/gpu/drm/msm/Kconfig                        |   25 +
 drivers/gpu/drm/msm/Makefile                       |   22 +
 drivers/gpu/drm/msm/dcp/dcp_crtc.c                 |  739 +++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_crtc.h                 |  104 ++
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c              |  686 ++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h              |   59 +
 drivers/gpu/drm/msm/dcp/dcp_drv.c                  |  191 ++++
 drivers/gpu/drm/msm/dcp/dcp_drv.h                  |   46 +
 drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c           | 1110 +++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h           |   76 ++
 drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h            |   94 ++
 drivers/gpu/drm/msm/dcp/dcp_encoder.c              |   29 +
 drivers/gpu/drm/msm/dcp/dcp_encoder.h              |   16 +
 drivers/gpu/drm/msm/dcp/dcp_hfi.h                  |  275 +++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_core.c             | 1155 ++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c             |  191 ++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c              |  252 +++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c        |  592 ++++++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c          |   64 ++
 drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c              |  366 +++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c            |   63 ++
 drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h             |   48 +
 drivers/gpu/drm/msm/dcp/dcp_ipcc.c                 |  173 +++
 drivers/gpu/drm/msm/dcp/dcp_ipcc.h                 |   42 +
 drivers/gpu/drm/msm/dcp/dcp_plane.c                | 1056 ++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_plane.h                |   36 +
 drivers/gpu/drm/msm/dcp/dcp_plane_state.h          |   37 +
 drivers/gpu/drm/msm/dcp/dcp_virtio.c               |  258 +++++
 drivers/gpu/drm/msm/dcp/dcp_virtio.h               |   25 +
 drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c     |  643 +++++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_commands_device.h  |   78 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display.h |  105 ++
 .../drm/msm/dcp/hfi/hfi_commands_display_events.h  |   90 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_commands_panel.h   |   27 +
 drivers/gpu/drm/msm/dcp/hfi/hfi_defs_common.h      |  182 +++
 drivers/gpu/drm/msm/dcp/hfi/hfi_defs_device.h      |   61 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display.h     |  464 ++++++++
 .../gpu/drm/msm/dcp/hfi/hfi_defs_display_color.h   |  247 +++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h       |  287 +++++
 .../gpu/drm/msm/dcp/hfi/hfi_pack_unpack_common.h   |   79 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_packer.c           |  106 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_packer.h           |   65 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_packet.h           |   76 ++
 .../gpu/drm/msm/dcp/hfi/hfi_properties_device.h    |  102 ++
 .../gpu/drm/msm/dcp/hfi/hfi_properties_display.h   |  480 ++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_properties_panel.h |  198 ++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c         |  157 +++
 drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.h         |   77 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c            |   62 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h            |   56 +
 drivers/gpu/drm/msm/msm_drv.c                      |    8 +-
 drivers/gpu/drm/msm/msm_drv.h                      |   10 +
 drivers/gpu/drm/msm/msm_kms.c                      |    2 +-
 include/uapi/linux/virtio_ids.h                    |    1 +
 59 files changed, 11992 insertions(+), 4 deletions(-)

---
base-commit: 9a482184d910775cf648863148944945b74e2815
change-id: 20261022-jshekhar-dcp-hlos-c7c22b0ff7e4
prerequisite-patch-id: eecfd66a55b8f2b7b2fc7d76d815facca0489d00

Best regards,

Signature · 1 line
Jayant Shekhar <[email protected]>
#1 lore raw

PATCH v2 1/23 dt-bindings: display: msm: Add Qualcomm DCP

DCP platforms place the display pipeline under firmware control, so Linux cannot discover display resources through direct register access. The DRM driver therefore needs device-tree descriptions of the Qualcomm transport wrapper, display topology, IOMMU contexts, and panel connections.

Add bindings for the Qualcomm DCP transport wrapper and VirtIO display subsystem. Document the IPCC power-notify resources, transport and framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and DSI panel connections. The wrapper creates a standard VirtIO-MMIO child after the IPCC power-notify sequence has completed.

Assisted-by LLM
---
 .../bindings/display/msm/qcom,dcp-dsi.yaml         |  79 ++++++++++++
 .../devicetree/bindings/display/msm/qcom,dcp.yaml  | 123 +++++++++++++++++++
 .../bindings/virtio/qcom,dcp-virtio.yaml           | 132 +++++++++++++++++++++
 3 files changed, 334 insertions(+)
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +79 -0
new file mode 100644
index 000000000000..9a0acaf00bb3
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/msm/qcom,dcp-dsi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm DCP DSI host
+
+maintainers:
+  - Jayant Shekhar <[email protected]>
+
+description: |
+  A MIPI DSI host exposed by the Qualcomm DCP VirtIO device (see
+  qcom,dcp.yaml). It follows the generic DSI controller binding, with panel
+  devices described as child nodes. The DCP INTF graph endpoint connects
+  directly to the panel; the DSI host node is not a graph endpoint. The host
+  node uses the dsi@N naming convention, and its reg property identifies the
+  DSI host index.
+
+allOf:
+  - $ref: /schemas/display/dsi-controller.yaml#
+
+properties:
+  reg:
+    maxItems: 1
+    description: DSI host index matching the DCP output port.
+
+required:
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    dcp-subsystem {
+        compatible = "virtio,device34";
+        iommus = <&apps_smmu 0x400 0x2>;
+
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port@0 {
+                reg = <0>;
+                dcp_intf0_out: endpoint {
+                    remote-endpoint = <&panel_dcp_in>;
+                };
+            };
+        };
+
+        dsi@0 {
+            reg = <0>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            panel@0 {
+                compatible = "novatek,nt37801";
+                reg = <0>;
+
+                vddio-supply = <&vreg_l12b_1p8>;
+                vci-supply = <&vreg_l13b_3p0>;
+                vdd-supply = <&display_panel_vdd>;
+                reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+                port {
+                    panel_dcp_in: endpoint {
+                        remote-endpoint = <&dcp_intf0_out>;
+                    };
+                };
+            };
+        };
+    };
Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml new file +123 -0

123 lines not on this page · raw message

Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +132 -0

132 lines not on this page · raw message

Signature · 1 line
2.43.0
#2 lore raw

Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] dt-bindings: qcom,dcp-virtio: invalid uint32 $ref in iommu-ranges items - [Medium] dt-bindings…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] dt-bindings: qcom,dcp-virtio: invalid uint32 $ref in iommu-ranges items
- [Medium] dt-bindings: qcom,dcp-virtio: mismatched reg and iommu-ranges cell counts --

commit 148fd24e1536920168a0cc19486ca6a7b8039591
Author: Jayant Shekhar <[email protected]>

dt-bindings: display: msm: Add Qualcomm DCP

This commit adds Device Tree binding schemas for the Qualcomm Display Co-Processor (DCP) VirtIO transport wrapper, the DCP VirtIO display subsystem, and its child MIPI DSI host controller.

Quoted 6 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +0 -0
new file mode 100644
index 0000000000000..6020e848ce259
@@ -0,0 +1,132 @@

[ ... ]

Quoted 4 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +4 -0
+  iommu-ranges:
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    description: |
+      The firmware-visible IOVA range, specified as a four-cell tuple

Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml line 47

Quoted 10 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +10 -0
+      containing a 64-bit IOVA base followed by a 64-bit range size.
+    items:
+      - description: IOVA base, high cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA base, low cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA range size, high cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA range size, low cell
+        $ref: /schemas/types.yaml#/definitions/uint32

[Severity: Medium]
Does make dt_binding_check pass on properties.iommu-ranges in Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml with these per-item $ref entries?

When iommu-ranges uses /schemas/types.yaml#/definitions/uint32-array at the property level, dtschema validates each entry under items against meta-schemas/cell.yaml#/$defs/single-cell, which sets additionalProperties:
false and does not permit $ref, causing dt-doc-validate to fail.

In addition, /schemas/types.yaml#/definitions/uint32 is a property-level 2D matrix schema rather than a scalar cell schema, so applying it to individual cells of a uint32-array creates an array-versus-integer type conflict when dt-validate checks the example's iommu-ranges property. Should the four $ref:
/schemas/types.yaml#/definitions/uint32 lines under items be dropped?

Quoted 29 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +29 -0
+
+  dma-coherent: true
+
+  mboxes:
+    maxItems: 1
+
+  mbox-names:
+    const: dcp-power-tx
+
+  dcp-subsystem:
+    $ref: ../display/msm/qcom,dcp.yaml#
+
+required:
+  - compatible
+  - reg
+  - interrupts-extended
+  - iommus
+  - iommu-ranges
+  - dma-coherent
+  - mboxes
+  - mbox-names
+  - dcp-subsystem
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>

Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml line 86

Quoted 10 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +10 -0
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/mailbox/qcom-ipcc.h>
+
+    virtio@9be8000 {
+        compatible = "qcom,dcp-virtio";
+        reg = <0x09be8000 0x1000>;
+        interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
+                              <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
+        iommus = <&apps_smmu 0x15a3 0x0>;
+        iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;

[Severity: Medium]
In the examples block of Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml, reg is specified with 1 address cell and 1 size cell under the default example bus (#address-cells = <1>; #size-cells = <1>;), whereas iommu-ranges is written as a 4-cell tuple (2 address cells and 2 size cells).

When the kernel parses iommu-ranges via of_iommu_get_resv_regions() -> of_iommu_derive_resv_regions() -> of_translate_dma_region() in drivers/of/address.c:

    struct device_node *parent __free(device_node) = __of_get_dma_parent(dev);
    ...
    na = of_bus_n_addr_cells(parent);
    ns = of_bus_n_size_cells(parent);

    address = of_translate_dma_address(dev, prop);
    if (address == OF_BAD_ADDR)
        return NULL;

    size = of_read_number(prop + na, ns);

both reg and iommu-ranges use the parent bus #address-cells and #size-cells. With a 1-cell parent bus, wouldn't of_iommu_derive_resv_regions() misparse <0x0 0x84000000 0x0 0x04000000> as two 32-bit ranges ([0x0, 0x84000000) and [0x0, 0x04000000)) instead of one 64-bit range ([0x84000000, 0x88000000))? Should the example wrap virtio@9be8000 in a parent bus node with #address-cells = <2> and #size-cells = <2> and use a 4-cell reg property?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#3 lore raw

Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP

In reply to Jayant Shekhar
Quoted 36 lines · Jayant Shekhar

DCP platforms place the display pipeline under firmware control, so Linux cannot discover display resources through direct register access. The DRM driver therefore needs device-tree descriptions of the Qualcomm transport wrapper, display topology, IOMMU contexts, and panel connections.

Add bindings for the Qualcomm DCP transport wrapper and VirtIO display subsystem. Document the IPCC power-notify resources, transport and framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and DSI panel connections. The wrapper creates a standard VirtIO-MMIO child after the IPCC power-notify sequence has completed.

Assisted-by LLM
---
 .../bindings/display/msm/qcom,dcp-dsi.yaml         |  79 ++++++++++++
 .../devicetree/bindings/display/msm/qcom,dcp.yaml  | 123 +++++++++++++++++++
 .../bindings/virtio/qcom,dcp-virtio.yaml           | 132 +++++++++++++++++++++
 3 files changed, 334 insertions(+)
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +11 -0
new file mode 100644
index 000000000000..9a0acaf00bb3
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/msm/qcom,dcp-dsi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm DCP DSI host
+
+maintainers:
+  - Jayant Shekhar <[email protected]>
+

Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml line 21

Quoted 10 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +10 -0
+description: |
+  A MIPI DSI host exposed by the Qualcomm DCP VirtIO device (see
+  qcom,dcp.yaml). It follows the generic DSI controller binding, with panel
+  devices described as child nodes. The DCP INTF graph endpoint connects
+  directly to the panel; the DSI host node is not a graph endpoint. The host
+  node uses the dsi@N naming convention, and its reg property identifies the
+  DSI host index.
+
+allOf:
+  - $ref: /schemas/display/dsi-controller.yaml#

If there are no separate compatibles, can this be folded into the main DCP schema?

Quoted 30 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +30 -0
+
+properties:
+  reg:
+    maxItems: 1
+    description: DSI host index matching the DCP output port.
+
+required:
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    dcp-subsystem {
+        compatible = "virtio,device34";
+        iommus = <&apps_smmu 0x400 0x2>;
+
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port@0 {
+                reg = <0>;

Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml line 61

Quoted 10 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +10 -0
+                dcp_intf0_out: endpoint {
+                    remote-endpoint = <&panel_dcp_in>;
+                };
+            };
+        };
+
+        dsi@0 {
+            reg = <0>;
+            #address-cells = <1>;
+            #size-cells = <0>;

Yes, fold it there. It's not a separate device on its own.

Quoted 82 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +18 -0
+
+            panel@0 {
+                compatible = "novatek,nt37801";
+                reg = <0>;
+
+                vddio-supply = <&vreg_l12b_1p8>;
+                vci-supply = <&vreg_l13b_3p0>;
+                vdd-supply = <&display_panel_vdd>;
+                reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+                port {
+                    panel_dcp_in: endpoint {
+                        remote-endpoint = <&dcp_intf0_out>;
+                    };
+                };
+            };
+        };
+    };
Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml new file +58 -0
new file mode 100644
index 000000000000..96f6c2a17e9e
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/msm/qcom,dcp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm Display Co-Processor (DCP)
+
+maintainers:
+  - Jayant Shekhar <[email protected]>
+
+description: |
+  The Display Co-Processor (DCP) is a firmware-owned display subsystem.
+  Linux communicates with DCP over HFI through VirtIO and does not access
+  display hardware registers directly.
+
+  This node describes the externally connected display graph between DCP
+  INTF outputs and attached DSI panels. It is assigned to the VirtIO device
+  by the Qualcomm transport driver and is not independently enumerated.
+  Firmware capabilities and internal resources, including pipes, mixers,
+  CRTCs, and clocks, are discovered at runtime.
+
+allOf:
+  - $ref: /schemas/virtio/virtio-device.yaml#
+
+properties:
+  compatible:
+    const: virtio,device34
+
+  iommus:
+    maxItems: 1
+    description:
+      SMMU context used by DCP firmware to access GEM framebuffers. This
+      is separate from the HFI transport context specified by the
+      iommus property on the parent DCP transport node.
+      The child context is used for framebuffer DMA performed by DCP
+      firmware, while the parent context is used for VirtIO transport and HFI
+      queue buffers. These contexts represent distinct firmware-visible DMA
+      address spaces and must not be used interchangeably.
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    description: |
+      DPU INTF output-only ports. The port number matches the firmware INTF
+      index. The corresponding DSI host is the child node whose reg value
+      matches the port number; the graph endpoint itself connects directly to
+      the attached panel. Unused INTF ports are omitted.
+    unevaluatedProperties: false
+    patternProperties:
+      "^port@[0-9a-f]+$":
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false

Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml line 68

Quoted 10 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml new file +10 -0
+        properties:
+          endpoint:
+            $ref: /schemas/graph.yaml#/$defs/endpoint-base
+            unevaluatedProperties: false
+            required:
+              - remote-endpoint
+
+patternProperties:
+  "^dsi@[0-9a-f]+$":
+    $ref: qcom,dcp-dsi.yaml#

When adding DP support, would you add more devices at the same level? If so, we risk getting a warning about multiple devices using the same address.

Quoted 107 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml new file +54 -0
+required:
+  - compatible
+  - iommus
+  - "#address-cells"
+  - "#size-cells"
+  - ports
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    dcp-subsystem {
+        compatible = "virtio,device34";
+        iommus = <&apps_smmu 0x400 0x2>;
+
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port@0 {
+                reg = <0>;
+                dcp_intf0_out: endpoint {
+                    remote-endpoint = <&panel_dcp_in>;
+                };
+            };
+        };
+
+        dsi@0 {
+            reg = <0>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            panel@0 {
+                compatible = "novatek,nt37801";
+                reg = <0>;
+
+                vddio-supply = <&vreg_l12b_1p8>;
+                vci-supply = <&vreg_l13b_3p0>;
+                vdd-supply = <&display_panel_vdd>;
+                reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+                port {
+                    panel_dcp_in: endpoint {
+                        remote-endpoint = <&dcp_intf0_out>;
+                    };
+                };
+            };
+        };
+    };
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +47 -0
new file mode 100644
index 000000000000..6020e848ce25
@@ -0,0 +1,132 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/virtio/qcom,dcp-virtio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm DCP VirtIO transport
+
+maintainers:
+  - Jayant Shekhar <[email protected]>
+
+description: |
+  Qualcomm DCP VirtIO transport. The IPCC power-notify channel and
+  associated interrupts are required before the transport can access its
+  MMIO registers. This is a Qualcomm-specific transport and must not use the
+  generic virtio-mmio driver. The DCP VirtIO function is described as a child
+  node and is assigned to the registered VirtIO device by this transport.
+
+properties:
+  compatible:
+    const: qcom,dcp-virtio
+
+  reg:
+    maxItems: 1
+
+  interrupts-extended:
+    minItems: 2
+    maxItems: 2
+    description: The VirtIO-MMIO and DCP power-notify interrupts, in that order.
+
+  iommus:
+    maxItems: 1
+
+  iommu-ranges:
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    description: |
+      The firmware-visible IOVA range, specified as a four-cell tuple
+      containing a 64-bit IOVA base followed by a 64-bit range size.
+    items:
+      - description: IOVA base, high cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA base, low cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA range size, high cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA range size, low cell
+        $ref: /schemas/types.yaml#/definitions/uint32

Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml line 57

Quoted 10 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +10 -0
+
+  dma-coherent: true
+
+  mboxes:
+    maxItems: 1
+
+  mbox-names:
+    const: dcp-power-tx
+
+  dcp-subsystem:

'dcp' is not a generic name. If you want to list it here, it should be something like 'display-subsystem' or 'display-controller'.

Quoted 19 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +19 -0
+    $ref: ../display/msm/qcom,dcp.yaml#
+
+required:
+  - compatible
+  - reg
+  - interrupts-extended
+  - iommus
+  - iommu-ranges
+  - dma-coherent
+  - mboxes
+  - mbox-names
+  - dcp-subsystem
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>

Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml line 86

Quoted 10 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +10 -0
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/mailbox/qcom-ipcc.h>
+
+    virtio@9be8000 {
+        compatible = "qcom,dcp-virtio";
+        reg = <0x09be8000 0x1000>;
+        interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
+                              <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
+        iommus = <&apps_smmu 0x15a3 0x0>;
+        iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;

This doesn't match the address / size cells.

Quoted 50 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +46 -0
+        dma-coherent;
+        mboxes = <&ipcc 13 5>;
+        mbox-names = "dcp-power-tx";
+
+        dcp-subsystem {
+            compatible = "virtio,device34";
+            iommus = <&apps_smmu 0x400 0x2>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    dcp_intf0_out: endpoint {
+                        remote-endpoint = <&panel_dcp_in>;
+                    };
+                };
+            };
+
+            dsi@0 {
+                reg = <0>;
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                panel@0 {
+                    compatible = "novatek,nt37801";
+                    reg = <0>;
+
+                    vddio-supply = <&vreg_l12b_1p8>;
+                    vci-supply = <&vreg_l13b_3p0>;
+                    vdd-supply = <&display_panel_vdd>;
+                    reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+                    port {
+                        panel_dcp_in: endpoint {
+                            remote-endpoint = <&dcp_intf0_out>;
+                        };
+                    };
+                };
+            };
+        };
+    };
Signature · 2 lines
-- 
2.43.0
Signature · 2 lines
With best wishes
Dmitry
#4 lore raw

Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP

Quoted 41 lines · Dmitry Baryshkov
Quoted 40 lines · Jayant Shekhar

DCP platforms place the display pipeline under firmware control, so Linux cannot discover display resources through direct register access. The DRM driver therefore needs device-tree descriptions of the Qualcomm transport wrapper, display topology, IOMMU contexts, and panel connections.

Add bindings for the Qualcomm DCP transport wrapper and VirtIO display subsystem. Document the IPCC power-notify resources, transport and framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and DSI panel connections. The wrapper creates a standard VirtIO-MMIO child after the IPCC power-notify sequence has completed.

Assisted-by LLM
---
  .../bindings/display/msm/qcom,dcp-dsi.yaml         |  79 ++++++++++++
  .../devicetree/bindings/display/msm/qcom,dcp.yaml  | 123 +++++++++++++++++++
  .../bindings/virtio/qcom,dcp-virtio.yaml           | 132 +++++++++++++++++++++
  3 files changed, 334 insertions(+)
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +15 -0
new file mode 100644
index 000000000000..9a0acaf00bb3
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/msm/qcom,dcp-dsi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm DCP DSI host
+
+maintainers:
+  - Jayant Shekhar <[email protected]>
+
+description: |
+  A MIPI DSI host exposed by the Qualcomm DCP VirtIO device (see
+  qcom,dcp.yaml). It follows the generic DSI controller binding, with panel
+  devices described as child nodes. The DCP INTF graph endpoint connects
Quoted 9 lines

Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml line 21

Quoted 6 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +6 -0
+  directly to the panel; the DSI host node is not a graph endpoint. The host
+  node uses the dsi@N naming convention, and its reg property identifies the
+  DSI host index.
+
+allOf:
+  - $ref: /schemas/display/dsi-controller.yaml#

If there are no separate compatibles, can this be folded into the main DCP schema?

This will be addressed in v3.

Quoted 129 lines
Quoted 30 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +30 -0
+
+properties:
+  reg:
+    maxItems: 1
+    description: DSI host index matching the DCP output port.
+
+required:
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    dcp-subsystem {
+        compatible = "virtio,device34";
+        iommus = <&apps_smmu 0x400 0x2>;
+
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port@0 {
+                reg = <0>;

Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml line 61

Quoted 10 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +10 -0
+                dcp_intf0_out: endpoint {
+                    remote-endpoint = <&panel_dcp_in>;
+                };
+            };
+        };
+
+        dsi@0 {
+            reg = <0>;
+            #address-cells = <1>;
+            #size-cells = <0>;

Yes, fold it there. It's not a separate device on its own.

Quoted 87 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml new file +18 -0
+
+            panel@0 {
+                compatible = "novatek,nt37801";
+                reg = <0>;
+
+                vddio-supply = <&vreg_l12b_1p8>;
+                vci-supply = <&vreg_l13b_3p0>;
+                vdd-supply = <&display_panel_vdd>;
+                reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+                port {
+                    panel_dcp_in: endpoint {
+                        remote-endpoint = <&dcp_intf0_out>;
+                    };
+                };
+            };
+        };
+    };
Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml new file +63 -0
new file mode 100644
index 000000000000..96f6c2a17e9e
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/msm/qcom,dcp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm Display Co-Processor (DCP)
+
+maintainers:
+  - Jayant Shekhar <[email protected]>
+
+description: |
+  The Display Co-Processor (DCP) is a firmware-owned display subsystem.
+  Linux communicates with DCP over HFI through VirtIO and does not access
+  display hardware registers directly.
+
+  This node describes the externally connected display graph between DCP
+  INTF outputs and attached DSI panels. It is assigned to the VirtIO device
+  by the Qualcomm transport driver and is not independently enumerated.
+  Firmware capabilities and internal resources, including pipes, mixers,
+  CRTCs, and clocks, are discovered at runtime.
+
+allOf:
+  - $ref: /schemas/virtio/virtio-device.yaml#
+
+properties:
+  compatible:
+    const: virtio,device34
+
+  iommus:
+    maxItems: 1
+    description:
+      SMMU context used by DCP firmware to access GEM framebuffers. This
+      is separate from the HFI transport context specified by the
+      iommus property on the parent DCP transport node.
+      The child context is used for framebuffer DMA performed by DCP
+      firmware, while the parent context is used for VirtIO transport and HFI
+      queue buffers. These contexts represent distinct firmware-visible DMA
+      address spaces and must not be used interchangeably.
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    description: |
+      DPU INTF output-only ports. The port number matches the firmware INTF
+      index. The corresponding DSI host is the child node whose reg value
+      matches the port number; the graph endpoint itself connects directly to
+      the attached panel. Unused INTF ports are omitted.
+    unevaluatedProperties: false
+    patternProperties:
+      "^port@[0-9a-f]+$":
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        properties:
+          endpoint:
+            $ref: /schemas/graph.yaml#/$defs/endpoint-base
+            unevaluatedProperties: false
+            required:
Quoted 9 lines

Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml line 68

Quoted 5 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml new file +5 -0
+              - remote-endpoint
+
+patternProperties:
+  "^dsi@[0-9a-f]+$":
+    $ref: qcom,dcp-dsi.yaml#

When adding DP support, would you add more devices at the same level? If so, we risk getting a warning about multiple devices using the same address.

Is it good to model each firmware INTF as a single interface@N node carrying the INTF reg, with the protocol-specific DSI or DP description nested beneath it? The driver can then obtain the INTF index from the common parent, avoiding duplicate addresses across protocol types.

Quoted 111 lines
Quoted 111 lines
Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml new file +54 -0
+required:
+  - compatible
+  - iommus
+  - "#address-cells"
+  - "#size-cells"
+  - ports
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    dcp-subsystem {
+        compatible = "virtio,device34";
+        iommus = <&apps_smmu 0x400 0x2>;
+
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port@0 {
+                reg = <0>;
+                dcp_intf0_out: endpoint {
+                    remote-endpoint = <&panel_dcp_in>;
+                };
+            };
+        };
+
+        dsi@0 {
+            reg = <0>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            panel@0 {
+                compatible = "novatek,nt37801";
+                reg = <0>;
+
+                vddio-supply = <&vreg_l12b_1p8>;
+                vci-supply = <&vreg_l13b_3p0>;
+                vdd-supply = <&display_panel_vdd>;
+                reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+                port {
+                    panel_dcp_in: endpoint {
+                        remote-endpoint = <&dcp_intf0_out>;
+                    };
+                };
+            };
+        };
+    };
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +51 -0
new file mode 100644
index 000000000000..6020e848ce25
@@ -0,0 +1,132 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/virtio/qcom,dcp-virtio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm DCP VirtIO transport
+
+maintainers:
+  - Jayant Shekhar <[email protected]>
+
+description: |
+  Qualcomm DCP VirtIO transport. The IPCC power-notify channel and
+  associated interrupts are required before the transport can access its
+  MMIO registers. This is a Qualcomm-specific transport and must not use the
+  generic virtio-mmio driver. The DCP VirtIO function is described as a child
+  node and is assigned to the registered VirtIO device by this transport.
+
+properties:
+  compatible:
+    const: qcom,dcp-virtio
+
+  reg:
+    maxItems: 1
+
+  interrupts-extended:
+    minItems: 2
+    maxItems: 2
+    description: The VirtIO-MMIO and DCP power-notify interrupts, in that order.
+
+  iommus:
+    maxItems: 1
+
+  iommu-ranges:
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    description: |
+      The firmware-visible IOVA range, specified as a four-cell tuple
+      containing a 64-bit IOVA base followed by a 64-bit range size.
+    items:
+      - description: IOVA base, high cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA base, low cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA range size, high cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+      - description: IOVA range size, low cell
+        $ref: /schemas/types.yaml#/definitions/uint32
+
+  dma-coherent: true
+
+  mboxes:
Quoted 9 lines

Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml line 57

Quoted 6 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +6 -0
+    maxItems: 1
+
+  mbox-names:
+    const: dcp-power-tx
+
+  dcp-subsystem:

'dcp' is not a generic name. If you want to list it here, it should be something like 'display-subsystem' or 'display-controller'.

To be addressed in v3.

Thanks

Jayant

Quoted 81 lines
Quoted 19 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +19 -0
+    $ref: ../display/msm/qcom,dcp.yaml#
+
+required:
+  - compatible
+  - reg
+  - interrupts-extended
+  - iommus
+  - iommu-ranges
+  - dma-coherent
+  - mboxes
+  - mbox-names
+  - dcp-subsystem
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>

Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml line 86

Quoted 10 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +10 -0
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/mailbox/qcom-ipcc.h>
+
+    virtio@9be8000 {
+        compatible = "qcom,dcp-virtio";
+        reg = <0x09be8000 0x1000>;
+        interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
+                              <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
+        iommus = <&apps_smmu 0x15a3 0x0>;
+        iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;

This doesn't match the address / size cells.

Quoted 50 lines
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml new file +46 -0
+        dma-coherent;
+        mboxes = <&ipcc 13 5>;
+        mbox-names = "dcp-power-tx";
+
+        dcp-subsystem {
+            compatible = "virtio,device34";
+            iommus = <&apps_smmu 0x400 0x2>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    dcp_intf0_out: endpoint {
+                        remote-endpoint = <&panel_dcp_in>;
+                    };
+                };
+            };
+
+            dsi@0 {
+                reg = <0>;
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                panel@0 {
+                    compatible = "novatek,nt37801";
+                    reg = <0>;
+
+                    vddio-supply = <&vreg_l12b_1p8>;
+                    vci-supply = <&vreg_l13b_3p0>;
+                    vdd-supply = <&display_panel_vdd>;
+                    reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+                    port {
+                        panel_dcp_in: endpoint {
+                            remote-endpoint = <&dcp_intf0_out>;
+                        };
+                    };
+                };
+            };
+        };
+    };
Signature · 2 lines
-- 
2.43.0
#5 lore raw

Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP

In reply to Jayant Shekhar
Quoted 11 lines · Jayant Shekhar

DCP platforms place the display pipeline under firmware control, so Linux cannot discover display resources through direct register access. The DRM driver therefore needs device-tree descriptions of the Qualcomm transport wrapper, display topology, IOMMU contexts, and panel connections.

Add bindings for the Qualcomm DCP transport wrapper and VirtIO display subsystem. Document the IPCC power-notify resources, transport and framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and DSI panel connections. The wrapper creates a standard VirtIO-MMIO child after the IPCC power-notify sequence has completed.

Quoted 8 lines
Assisted-by LLM
---
 .../bindings/display/msm/qcom,dcp-dsi.yaml         |  79 ++++++++++++
 .../devicetree/bindings/display/msm/qcom,dcp.yaml  | 123 +++++++++++++++++++
 .../bindings/virtio/qcom,dcp-virtio.yaml           | 132 +++++++++++++++++++++
 3 files changed, 334 insertions(+)

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml: iommu-ranges: multiple incompatible types: uint32-array /usr/local/lib/python3.13/dist-packages/dtschema/schemas/iommu/iommu.yaml: iommu-ranges: multiple incompatible types: uint32-matrix Could not decode node virtio@9be8000 property iommu-ranges with value b'\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00'

Traceback (most recent call last):
File "/usr/local/bin/dt-validate", line 8, in <module>

    sys.exit(main())
             ~~~~^^

File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb_validate.py", line 443, in main
dtb_diagnostics = sg.check_dtb(filename)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb_validate.py", line 331, in check_dtb
dt = self.validator.decode_dtb(f.read(), decode_errors)
File "/usr/local/lib/python3.13/dist-packages/dtschema/validator.py", line 631, in decode_dtb
return [dtschema.dtb.fdt_unflatten(self, dtb)]
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 591, in fdt_unflatten
dt = fdt_scan_node(validator, fdt, '/', offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 320, in fdt_scan_node
node = fdt_scan_node(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 320, in fdt_scan_node
node = fdt_scan_node(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 312, in fdt_scan_node
node_dict = node_props(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 258, in node_props
props_dict[p.name] = prop_value(validator, nodename, p)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 185, in prop_value
if len(p) / type_format[fmt].size >= min_dim:
~~~~~~~~~~~^^^^^
KeyError: 'uint32-array'

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/[email protected]

The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema.

#6 lore raw

Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP

Quoted 67 lines · Rob Herring (Arm)
Quoted 11 lines · Jayant Shekhar

DCP platforms place the display pipeline under firmware control, so Linux cannot discover display resources through direct register access. The DRM driver therefore needs device-tree descriptions of the Qualcomm transport wrapper, display topology, IOMMU contexts, and panel connections.

Add bindings for the Qualcomm DCP transport wrapper and VirtIO display subsystem. Document the IPCC power-notify resources, transport and framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and DSI panel connections. The wrapper creates a standard VirtIO-MMIO child after the IPCC power-notify sequence has completed.

Quoted 8 lines
Assisted-by LLM
---
  .../bindings/display/msm/qcom,dcp-dsi.yaml         |  79 ++++++++++++
  .../devicetree/bindings/display/msm/qcom,dcp.yaml  | 123 +++++++++++++++++++
  .../bindings/virtio/qcom,dcp-virtio.yaml           | 132 +++++++++++++++++++++
  3 files changed, 334 insertions(+)

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml: iommu-ranges: multiple incompatible types: uint32-array /usr/local/lib/python3.13/dist-packages/dtschema/schemas/iommu/iommu.yaml: iommu-ranges: multiple incompatible types: uint32-matrix Could not decode node virtio@9be8000 property iommu-ranges with value b'\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00'

Traceback (most recent call last):
File "/usr/local/bin/dt-validate", line 8, in <module>

     sys.exit(main())
              ~~~~^^

File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb_validate.py", line 443, in main
dtb_diagnostics = sg.check_dtb(filename)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb_validate.py", line 331, in check_dtb
dt = self.validator.decode_dtb(f.read(), decode_errors)
File "/usr/local/lib/python3.13/dist-packages/dtschema/validator.py", line 631, in decode_dtb
return [dtschema.dtb.fdt_unflatten(self, dtb)]
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 591, in fdt_unflatten
dt = fdt_scan_node(validator, fdt, '/', offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 320, in fdt_scan_node
node = fdt_scan_node(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 320, in fdt_scan_node
node = fdt_scan_node(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 312, in fdt_scan_node
node_dict = node_props(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 258, in node_props
props_dict[p.name] = prop_value(validator, nodename, p)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 185, in prop_value
if len(p) / type_format[fmt].size >= min_dim:
~~~~~~~~~~~^^^^^
KeyError: 'uint32-array'

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/[email protected]

The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date:

Quoted 5 lines

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema.

There is a dependent patch which adds iommu-ranges, I had tried with that locally and it passes.

#7 lore raw

Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP

In reply to Jayant Shekhar
Quoted 1 line · Jayant Shekhar

DCP platforms place the display pipeline under firmware control, so Linux

What is a DCP platform?

Quoted 9 lines

cannot discover display resources through direct register access. The DRM driver therefore needs device-tree descriptions of the Qualcomm transport wrapper, display topology, IOMMU contexts, and panel connections.

Add bindings for the Qualcomm DCP transport wrapper and VirtIO display subsystem. Document the IPCC power-notify resources, transport and framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and DSI panel connections. The wrapper creates a standard VirtIO-MMIO child after the IPCC power-notify sequence has completed.

It's still DT for real hardware, so I wonder why no SoC specific compatibles were used.

Best regards,
Krzysztof

#8 lore raw

Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP

Quoted 4 lines · Krzysztof Kozlowski
Quoted 1 line · Jayant Shekhar

DCP platforms place the display pipeline under firmware control, so Linux

What is a DCP platform?

Will address this in v3.

Quoted 11 lines
Quoted 9 lines

cannot discover display resources through direct register access. The DRM driver therefore needs device-tree descriptions of the Qualcomm transport wrapper, display topology, IOMMU contexts, and panel connections.

Add bindings for the Qualcomm DCP transport wrapper and VirtIO display subsystem. Document the IPCC power-notify resources, transport and framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and DSI panel connections. The wrapper creates a standard VirtIO-MMIO child after the IPCC power-notify sequence has completed.

It's still DT for real hardware, so I wonder why no SoC specific compatibles were used.

Thanks. The same DCP transport wrapper works on Hawi and Maili, so the current driver uses the generic qcom,dcp-virtio compatible.

If a future SoC requires different wrapper handling or has a different layout, we can add a SoC-

specific compatible and match data then. The child’s virtio,device34 compatible identifies the VirtIO function.

Quoted 2 lines

Best regards,
Krzysztof

#9 lore raw

PATCH v2 2/23 drm/msm/dcp: Add HFI packet helpers

In reply to Jayant Shekhar

Define the HFI packet formats and serialization helpers required to communicate with DCP firmware. Without common packing and unpacking rules, the transport and display command layers cannot exchange commands reliably.

Implement packet creation, property packing and unpacking, and shared HFI parsing utilities for the DCP transport and command implementations.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Kconfig                        |  25 +++
 drivers/gpu/drm/msm/Makefile                       |   6 +
 drivers/gpu/drm/msm/dcp/hfi/hfi_defs_common.h      | 182 +++++++++++++++++++++
 .../gpu/drm/msm/dcp/hfi/hfi_pack_unpack_common.h   |  79 +++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_packer.c           | 106 ++++++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_packer.h           |  65 ++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_packet.h           |  76 +++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c         | 157 ++++++++++++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.h         |  77 +++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c            |  62 +++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h            |  56 +++++++
 11 files changed, 891 insertions(+)
drivers/gpu/drm/msm/Kconfig +25 -0
index ed1c4775d1e9..a9f049271c25 100644
@@ -114,6 +114,31 @@ config DRM_MSM_DPU
 	  the MSM DRM driver. It is the display controller found in devices
 	  using e.g. SDM845 and newer platforms.
 
+config DRM_MSM_DCP
+	bool "Enable DCP firmware-delegated display support in MSM DRM driver"
+	depends on DRM_MSM
+	depends on VIRTIO
+	select DRM_MSM_KMS
+	select DRM_BRIDGE_CONNECTOR
+	select DRM_MIPI_DSI
+	select DRM_DISPLAY_DSC_HELPER
+	default y
+	help
+	  Compile in support for the DCP (Display Coprocessor) firmware-delegated
+	  display architecture in the MSM DRM driver.
+
+	  DCP is used on Hawi (qcom,hawi) SoC where all display hardware is
+	  owned by a separate Display Coprocessor firmware. The host AP
+	  communicates with DCP via two interfaces:
+	    - IPCC SMP2P mailbox: one-shot wake signal before each HFI command
+	    - HFI over VirtIO MMIO: all display command/response exchange
+
+	  DPU and bridge nodes in the Device Tree are topology-only (no
+	  compatible string, no reg). The driver detects them by node name
+	  prefix using of_node_name_prefix().
+
+	  If unsure, say N.
+
 config DRM_MSM_DP
 	bool "Enable DisplayPort support in MSM DRM driver"
 	depends on DRM_MSM
drivers/gpu/drm/msm/Makefile +6 -0
index 1e246616f83f..ad7b9dbf736e 100644
@@ -4,6 +4,7 @@ ccflags-y += -I $(obj)/generated
 ccflags-y += -I $(src)/disp/dpu1
 ccflags-$(CONFIG_DRM_MSM_DSI) += -I $(src)/dsi
 ccflags-$(CONFIG_DRM_MSM_DP) += -I $(src)/dp
+ccflags-$(CONFIG_DRM_MSM_DCP) += -I $(src)/dcp
 
 adreno-y := \
 	adreno/adreno_device.o \
@@ -70,6 +71,11 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 	disp/mdp5/mdp5_plane.o \
 	disp/mdp5/mdp5_smp.o \
 
+msm-display-$(CONFIG_DRM_MSM_DCP) += \
+	dcp/hfi/hfi_packer.o \
+	dcp/hfi/hfi_unpacker.o \
+	dcp/hfi/hfi_utils.o
+
 msm-display-$(CONFIG_DRM_MSM_DPU) += \
 	disp/dpu1/dpu_core_perf.o \
 	disp/dpu1/dpu_crtc.o \
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_common.h new file +182 -0

182 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_pack_unpack_common.h new file +79 -0

79 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_packer.c new file +106 -0

106 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_packer.h new file +65 -0

65 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_packet.h new file +76 -0

76 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c new file +157 -0

157 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.h new file +77 -0

77 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c new file +62 -0

62 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h new file +56 -0

56 lines not on this page · raw message

Signature · 1 line
2.43.0
#10 lore raw

Re: [PATCH v2 02/23] drm/msm/dcp: Add HFI packet helpers

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] drm/msm/dcp: DRM_MSM_DCP bool dependency on tristate VIRTIO allows DRM_MSM=y with VIRTIO=m --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] drm/msm/dcp: DRM_MSM_DCP bool dependency on tristate VIRTIO allows DRM_MSM=y with VIRTIO=m --

commit 29edd03d5dbd7916e10401025f35f2e578051ed0
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add HFI packet helpers

This commit adds the HFI packet definitions, packing and unpacking helpers, and key-value parsing utilities for communicating with the Display Coprocessor (DCP) firmware, along with the DRM_MSM_DCP Kconfig symbol and Makefile rules.

Quoted 7 lines
drivers/gpu/drm/msm/Kconfig +0 -0
index ed1c4775d1e92..a9f049271c251 100644
@@ -114,6 +114,31 @@ config DRM_MSM_DPU
 	  the MSM DRM driver. It is the display controller found in devices
 	  using e.g. SDM845 and newer platforms.

drivers/gpu/drm/msm/Kconfig config DRM_MSM_DPU · line 125

Quoted 10 lines
drivers/gpu/drm/msm/Kconfig +9 -0
 
+config DRM_MSM_DCP
+	bool "Enable DCP firmware-delegated display support in MSM DRM driver"
+	depends on DRM_MSM
+	depends on VIRTIO
+	select DRM_MSM_KMS
+	select DRM_BRIDGE_CONNECTOR
+	select DRM_MIPI_DSI
+	select DRM_DISPLAY_DSC_HELPER
+	default y

[Severity: Low]
Can this Kconfig dependency allow CONFIG_DRM_MSM_DCP=y when CONFIG_DRM_MSM=y and CONFIG_VIRTIO=m?

Both DRM_MSM and VIRTIO are tristate symbols, while DRM_MSM_DCP is a bool symbol whose objects in drivers/gpu/drm/msm/Makefile are linked directly into msm.o via msm-display-$(CONFIG_DRM_MSM_DCP) and msm-$(CONFIG_DRM_MSM_KMS) += $(msm-display-y):

drivers/gpu/drm/msm/Makefile:

    msm-display-$(CONFIG_DRM_MSM_DCP) += \
            dcp/hfi/hfi_packer.o \
            dcp/hfi/hfi_unpacker.o \
            dcp/hfi/hfi_utils.o

In Kconfig, a bool option with depends on DRM_MSM and depends on VIRTIO evaluates its upper bound as y even when CONFIG_DRM_MSM=y and CONFIG_VIRTIO=m. When the DCP VirtIO transport and driver objects are added to msm-display-$(CONFIG_DRM_MSM_DCP) later in this series ("drm/msm/dcp: Add HFI VirtIO transport", "drm/msm/dcp: Add Qualcomm VirtIO transport wrapper", and "drm/msm/dcp: Add DCP VirtIO driver"), built-in msm.o calls VirtIO core and vring symbols such as register_virtio_driver(), register_virtio_device(), virtio_find_vqs(), and vring_create_virtqueue() that live in loadable modules when CONFIG_VIRTIO=m, resulting in a vmlinux link failure.

Should DRM_MSM_DCP use depends on VIRTIO=y || VIRTIO=DRM_MSM (or DRM_MSM use depends on VIRTIO || !DRM_MSM_DCP) to prevent a built-in msm driver from linking against modular virtio?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#11 lore raw

PATCH v2 3/23 drm/msm/dcp: Add HFI device definitions

In reply to Jayant Shekhar

Define the HFI device commands and properties needed to discover DCP capabilities and register display resources. The KMS backend cannot size or allocate the firmware-owned pipeline without these protocol definitions.

Describe device commands, resource data, and capability property identifiers used by the DCP device and KMS layers.

Assisted-by LLM
---
 drivers/gpu/drm/msm/dcp/hfi/hfi_commands_device.h  |  78 ++++++++++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_defs_device.h      |  61 ++++++++++++
 .../gpu/drm/msm/dcp/hfi/hfi_properties_device.h    | 102 +++++++++++++++++++++
 3 files changed, 241 insertions(+)
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_device.h new file +78 -0
new file mode 100644
index 000000000000..abf15e6ceaec
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_COMMANDS_DEVICE_H__
+#define __H_HFI_COMMANDS_DEVICE_H__
+
+/* Device-level command range. */
+#define HFI_COMMAND_DEVICE_BEGIN                                     0x01000000
+
+/* Initialize the device and return firmware capabilities. */
+#define HFI_COMMAND_DEVICE_INIT                                      0x01000001
+
+/* Device, display, and pipeline capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS                          0x01000002
+
+/* VIG pipeline capability response. */
+#define HFI_COMMAND_DEVICE_INIT_VIG_CAPS                             0x01000003
+
+/* DMA pipeline capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DMA_CAPS                             0x01000004
+
+/* Common VIG/DMA capability response. */
+#define HFI_COMMAND_DEVICE_INIT_COMMON_LAYER_CAPS                    0x01000005
+
+/* Display capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS                         0x01000006
+
+/* Writeback capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DISPLAY_WB_CAPS                      0x01000007
+
+/* Register clocks and bandwidth resources with firmware. */
+#define HFI_COMMAND_DEVICE_RESOURCE_REGISTER                         0x01000008
+
+/* Firmware requests a resource vote from the host. */
+#define HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE                    0x01000009
+
+/* VIG rect-1 capability response. */
+#define HFI_COMMAND_DEVICE_INIT_VIG_R1_CAPS                          0x0100000a
+
+/* DMA rect-1 capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DMA_R1_CAPS                          0x0100000b
+
+/* Provide the last-command buffer to firmware. */
+#define HFI_COMMAND_DEVICE_LUT_DMA_LAST_CMD                          0x0100000c
+
+/* Deinitialize the device. */
+#define HFI_COMMAND_DEVICE_DEINIT                                    0x0100000d
+
+/* LSR writeback CSC layer capability response. */
+#define HFI_COMMAND_DEVICE_INIT_LSR_WB_CSC_LAYER_CAPS                0x0100000e
+
+/* LSR writeback reprojection layer capability response. */
+#define HFI_COMMAND_DEVICE_INIT_LSR_WB_REPROJ_LAYER_CAPS             0x0100000f
+
+/* LSR writeback CSC output capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DISPLAY_LSR_WB_CSC_CAPS              0x01000010
+
+/* LSR writeback reprojection output capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DISPLAY_LSR_WB_REPROJ_CAPS           0x01000011
+
+/* Configure the hardware-fence HFI queue. */
+#define HFI_COMMAND_DEVICE_HWFENCE_HFI_CONFIG                        0x01000012
+
+/* Update hotplug state and provide EDID/mode buffers. */
+#define HFI_COMMAND_DEVICE_HOT_PLUG_DETECT                           0x01000013
+
+/* Configure resources shared across virtual machines. */
+#define HFI_COMMAND_DEVICE_RESOURCE_CTRL                             0x01000014
+
+/* Notify firmware of a subsystem restart. */
+#define HFI_COMMAND_DEVICE_SUBSYSTEM_RESTART_NOTIFY                  0x01000015
+
+/* End of device-level command range. */
+#define HFI_COMMAND_DEVICE_END                                       0x01ffffff
+
+#endif /* __H_HFI_COMMANDS_DEVICE_H__ */
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_device.h new file +61 -0

61 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_properties_device.h new file +102 -0

102 lines not on this page · raw message

Signature · 1 line
2.43.0
#12 lore raw

Re: [PATCH v2 03/23] drm/msm/dcp: Add HFI device definitions

Sashiko has reviewed this patch and found no issues.
Quoted 10 lines

Define the HFI device commands and properties needed to discover DCP capabilities and register display resources. The KMS backend cannot size or allocate the firmware-owned pipeline without these protocol definitions.

Describe device commands, resource data, and capability property identifiers used by the DCP device and KMS layers.

Assisted-by LLM

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
#13 lore raw

PATCH v2 4/23 drm/msm/dcp: Add HFI display definitions

In reply to Jayant Shekhar

Define the HFI display protocol used to configure the firmware-owned display pipeline. DCP does not expose host-visible display registers, so DRM state must be represented by display commands and properties.

Describe display commands, events, data structures, color-management data, and property identifiers consumed by the DCP KMS and command layers.

Assisted-by LLM
---
 drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display.h | 105 +++++
 .../drm/msm/dcp/hfi/hfi_commands_display_events.h  |  90 ++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display.h     | 464 ++++++++++++++++++++
 .../gpu/drm/msm/dcp/hfi/hfi_defs_display_color.h   | 247 +++++++++++
 .../gpu/drm/msm/dcp/hfi/hfi_properties_display.h   | 480 +++++++++++++++++++++
 5 files changed, 1386 insertions(+)
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display.h new file +105 -0
new file mode 100644
index 000000000000..5f24b45e67b1
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_COMMANDS_DISPLAY_H__
+#define __H_HFI_COMMANDS_DISPLAY_H__
+
+/* Display-command range. */
+#define HFI_COMMAND_DISPLAY_BEGIN                                     0x02000000
+
+/* Set display properties. */
+#define HFI_COMMAND_DISPLAY_SET_PROPERTY                              0x02000001
+
+/* Read display or layer properties. */
+#define HFI_COMMAND_DISPLAY_GET_PROPERTY                              0x02000002
+
+/* Commit previously submitted display state. */
+#define HFI_COMMAND_DISPLAY_FRAME_TRIGGER                             0x02000003
+
+/* Register a display event. */
+#define HFI_COMMAND_DISPLAY_EVENT_REGISTER                            0x02000004
+
+/* Unregister a display event. */
+#define HFI_COMMAND_DISPLAY_EVENT_DEREGISTER                          0x02000005
+
+/* Enable the display session. */
+#define HFI_COMMAND_DISPLAY_ENABLE                                    0x02000006
+
+/* Send post-enable panel commands. */
+#define HFI_COMMAND_DISPLAY_POST_ENABLE                               0x02000007
+
+/* Disable the display stream. */
+#define HFI_COMMAND_DISPLAY_DISABLE                                   0x02000008
+
+/* Release post-disable display resources. */
+#define HFI_COMMAND_DISPLAY_POST_DISABLE                              0x02000009
+
+/* Set a previously validated display mode. */
+#define HFI_COMMAND_DISPLAY_SET_MODE                                  0x0200000a
+
+/* Validate a display mode in firmware. */
+#define HFI_COMMAND_DISPLAY_MODE_VALIDATE                             0x0200000b
+
+/* Request host-side display power control. */
+#define HFI_COMMAND_DISPLAY_POWER_CONTROL                             0x0200000c
+
+/* Register display power-control handling. */
+#define HFI_COMMAND_DISPLAY_POWER_REGISTER                            0x0200000d
+
+/* Transfer one DCS command. */
+#define HFI_COMMAND_DISPLAY_TRANSFER_DCS_CMD                          0x0200000e
+
+/* Transfer a DCS command set. */
+#define HFI_COMMAND_DISPLAY_TRANSFER_DCS_CMD_SET                      0x0200001d
+
+/* Configure the display idle timer. */
+#define HFI_COMMAND_DISPLAY_IDLE_TIMER_CONTROL                        0x0200000f
+
+/* Configure DisplayPort transfer units. */
+#define HFI_COMMAND_DISPLAY_SET_DP_TU                                 0x02000010
+
+/* Configure HDR display properties. */
+#define HFI_COMMAND_DISPLAY_CONFIG_HDR                                0x02000011
+
+/* Configure display audio. */
+#define HFI_COMMAND_DISPLAY_AUDIO_CONFIG                              0x02000012
+
+/* Control display audio. */
+#define HFI_COMMAND_DISPLAY_AUDIO_CONTROL                             0x02000013
+
+/* Set the display color space. */
+#define HFI_COMMAND_DISPLAY_SET_COLORSPACE                            0x02000014
+
+/* Provide the HDCP 1.x AKSV. */
+#define HFI_COMMAND_DISPLAY_HDCP1X_AKSV                               0x02000015
+
+/* Provide an HDCP 2.x response. */
+#define HFI_COMMAND_DISPLAY_HDCP2X_RESPONSE                           0x02000016
+
+/* Report sink HDCP capabilities. */
+#define HFI_COMMAND_DISPLAY_HDCP_FEATURE_SUPPORTED                    0x02000017
+
+/* Configure DSI DCS command remapping. */
+#define HFI_COMMAND_DISPLAY_DSI_CUSTOM_DCS_CMDS_SET_REMAP             0x02000018
+
+/* Configure DSI DCS command replacement. */
+#define HFI_COMMAND_DISPLAY_DSI_CUSTOM_DCS_CMDS_SET_REPLACE           0x02000019
+
+/* Configure display batch mode. */
+#define HFI_COMMAND_DISPLAY_BATCH_MODE                                0x0200001a
+
+/* Configure the display HFI subsystem. */
+#define HFI_COMMAND_DISPLAY_HFI_SUBSYSTEM_CONFIG                      0x0200001b
+
+/* Execute a DCS low-power command type. */
+#define HFI_COMMAND_DISPLAY_EXEC_DCS_CMD_TYPE                         0x0200001c
+
+/* End of display-command range. */
+#define HFI_COMMAND_DISPLAY_END                                       0x02ffffff
+
+/* Alias for the display-command range end. */
+#define HFI_COMMAND_DISPLAY_CLOSE                                     HFI_COMMAND_DISPLAY_END
+
+#endif /* __H_HFI_COMMANDS_DISPLAY_H__ */
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display_events.h new file +90 -0

90 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display.h new file +464 -0

464 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display_color.h new file +247 -0

247 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_properties_display.h new file +480 -0

480 lines not on this page · raw message

Signature · 1 line
2.43.0
#14 lore raw

Re: [PATCH v2 04/23] drm/msm/dcp: Add HFI display definitions

Sashiko has reviewed this patch and found no issues.
Quoted 10 lines

Define the HFI display protocol used to configure the firmware-owned display pipeline. DCP does not expose host-visible display registers, so DRM state must be represented by display commands and properties.

Describe display commands, events, data structures, color-management data, and property identifiers consumed by the DCP KMS and command layers.

Assisted-by LLM

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
#15 lore raw

PATCH v2 5/23 drm/msm/dcp: Add HFI panel definitions

In reply to Jayant Shekhar

Define the HFI panel protocol required to initialize DSI panels and issue DCS transfers through DCP firmware. Without these definitions, the DSI path cannot describe panel modes or panel commands to firmware.

Describe panel commands, timing and panel data structures, and property identifiers used by panel initialization and DCS transfer helpers.

Assisted-by LLM
---
 drivers/gpu/drm/msm/dcp/hfi/hfi_commands_panel.h   |  27 ++
 drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h       | 287 +++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/hfi/hfi_properties_panel.h | 198 ++++++++++++++
 3 files changed, 512 insertions(+)
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_panel.h new file +27 -0
new file mode 100644
index 000000000000..ceccc8da8a29
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_COMMANDS_PANEL_H__
+#define __H_HFI_COMMANDS_PANEL_H__
+
+/* Panel-command range. */
+#define HFI_COMMAND_PANEL_BEGIN                                         0x03000000
+
+/* Start panel initialization; send with timing and generic packets in one HFI header. */
+#define HFI_COMMAND_PANEL_INIT_PANEL_CAPS                            0x03000001
+
+/* Provide one panel timing-mode description. */
+#define HFI_COMMAND_PANEL_INIT_TIMING_MODE_CAPS                      0x03000002
+
+/* Provide non-timing panel properties. */
+#define HFI_COMMAND_PANEL_INIT_GENERIC_CAPS                          0x03000003
+
+/* Deinitialize panel attributes. */
+#define HFI_COMMAND_PANEL_DEINIT                                     0x03000004
+
+/* End of panel-command range. */
+#define HFI_COMMAND_PANEL_END                                        0x03ffffff
+
+#endif /* __H_HFI_COMMANDS_PANEL_H__ */
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h new file +287 -0

287 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/hfi/hfi_properties_panel.h new file +198 -0

198 lines not on this page · raw message

Signature · 1 line
2.43.0
#16 lore raw

Re: [PATCH v2 05/23] drm/msm/dcp: Add HFI panel definitions

Sashiko has reviewed this patch and found no issues.
Quoted 9 lines

Define the HFI panel protocol required to initialize DSI panels and issue DCS transfers through DCP firmware. Without these definitions, the DSI path cannot describe panel modes or panel commands to firmware.

Describe panel commands, timing and panel data structures, and property identifiers used by panel initialization and DCS transfer helpers.

Assisted-by LLM

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
#17 lore raw

PATCH v2 6/23 drm/msm/dcp: Add HFI VirtIO transport

In reply to Jayant Shekhar

Implement the HFI transport between Linux and DCP firmware over VirtIO. The display backend needs a transport that owns DMA buffers, submits HFI packets, receives responses, and completes commands without exposing firmware queues to higher layers.

Allocate and manage command and response buffers, handle VirtIO queue callbacks, provide synchronous and asynchronous submission paths, and synchronize transport teardown with in-flight activity.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile           |   1 +
 drivers/gpu/drm/msm/dcp/dcp_drv.h      |  28 ++
 drivers/gpu/drm/msm/dcp/dcp_hfi.h      | 169 +++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 836 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h |  48 ++
 5 files changed, 1082 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index ad7b9dbf736e..56c48fe2e2c5 100644
@@ -72,6 +72,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 	disp/mdp5/mdp5_smp.o \
 
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
+	dcp/dcp_hfi_core.o \
 	dcp/hfi/hfi_packer.o \
 	dcp/hfi/hfi_unpacker.o \
 	dcp/hfi/hfi_utils.o
drivers/gpu/drm/msm/dcp/dcp_drv.h new file +28 -0
new file mode 100644
index 000000000000..44d9b2cd5171
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __DCP_DRV_H__
+#define __DCP_DRV_H__
+
+#include <linux/init.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+
+#include "dcp_hfi.h"
+
+struct dcp_dpu_kms;
+
+struct dcp_drv {
+	struct virtio_device	*vdev;
+	struct platform_device	*dcp_subsystem_dev;
+	struct device_dma_parameters dcp_subsystem_dma_parms;
+	struct device_node	*dcp_subsystem_node;
+	struct dcp_hfi		*hfi;
+	struct dcp_dpu_kms	*kms;
+	struct dcp_caps		 caps;
+	struct notifier_block	 panic_notifier;
+};
+
+#endif /* __DCP_DRV_H__ */
drivers/gpu/drm/msm/dcp/dcp_hfi.h new file +169 -0

169 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +836 -0

836 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h new file +48 -0

48 lines not on this page · raw message

Signature · 1 line
2.43.0
#18 lore raw

Re: [PATCH v2 06/23] drm/msm/dcp: Add HFI VirtIO transport

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Critical] drm/msm/dcp: stack use-after-free on timeout in dcp_hfi_send_sync_on_vq() - [High]…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] drm/msm/dcp: stack use-after-free on timeout in dcp_hfi_send_sync_on_vq()
- [High] drm/msm/dcp: unsynchronized virtqueue callbacks and work re-arming on teardown
- [Medium] drm/msm/dcp: double-fetch from shared DMA buffer in dcp_hfi_resp_callback() --

commit 250b478306210e9e3849c522be712bc686a63c4b
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add HFI VirtIO transport

This commit implements the HFI transport over VirtIO between Linux and the DCP firmware, managing DMA command and response buffers, VirtIO queue callbacks, synchronous and asynchronous command submission, and teardown synchronization.

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +0 -0
new file mode 100644
index 0000000000000..9f7ac798e13c4

[ ... ]

Quoted 38 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +38 -0
+void dcp_hfi_resp_callback(struct virtqueue *vq)
+{
+	struct dcp_drv *drv = vq->vdev->priv;
+	struct dcp_hfi *hfi = drv ? drv->hfi : NULL;
+	struct dcp_hfi_buf *buf;
+	unsigned int len;
+	unsigned long drain_flags;
+
+	if (!hfi || atomic_read(&hfi->shutdown_in_progress))
+		return;
+
+	spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
+	virtqueue_disable_cb(vq);
+	for (;;) {
+		while ((buf = virtqueue_get_buf(vq, &len)) != NULL) {
+			struct hfi_cmd_buff_hdl hdl;
+			struct hfi_header_info hdr_info;
+			struct hfi_packet_info pkt_info;
+			u32 pkt_num;
+			int rc;
+
+			spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
+
+			if (unlikely(len < sizeof(struct hfi_header) ||
+				     len > DCP_HFI_BUF_SIZE)) {
+				dev_warn_ratelimited(hfi->dev,
+						     "HFI resp: short buffer len=%u (min=%zu) — skipping\n",
+						     len, sizeof(struct hfi_header));
+				goto repost;
+			}
+
+			hdl.cmd_buffer = buf->data;
+			hdl.size = (u32)len;
+			rc = hfi_unpacker_get_header_info(&hdl, &hdr_info);
+			if (rc) {
+				dev_warn_ratelimited(hfi->dev,
+						     "HFI resp: invalid header (rc=%d) len=%u\n",
+					     rc, len);

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c line 277

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +10 -0
+				print_hex_dump(KERN_WARNING, "HFI resp raw: ",
+					       DUMP_PREFIX_OFFSET, 16, 4,
+					       buf->data, min_t(u32, len, 64), false);
+				goto repost;
+			}
+
+			for (pkt_num = 1; pkt_num <= hdr_info.num_packets; pkt_num++) {
+				memset(&pkt_info, 0, sizeof(pkt_info));
+				rc = hfi_unpacker_get_packet_info(&hdl, pkt_num,
+								  &pkt_info);

[Severity: Medium]
Does unpacking directly from the coherent DMA buffer in dcp_hfi_resp_callback() expose the parser to a double-fetch if the firmware or VirtIO backend modifies the shared buffer concurrently?

In dcp_hfi_init(), the buffers in hfi->resp_pool are allocated with dma_alloc_coherent() and premapped into hfi->resp_vq, so buf->data remains in shared DMA memory writable by the device.

When hfi_unpacker_get_packet_info() runs on buf->data, it first validates the buffer through hfi_sanitize_cmd_buff(), and then re-reads packet->payload_info in hfi_packet_at() and GET_PACKET_SIZE(packet->payload_info):

drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c:hfi_unpacker_get_packet_info() {
	hdr = cmd_buf_hdl->cmd_buffer;
	if (GET_HEADER_SIZE(hdr->cmd_buff_info) > cmd_buf_hdl->size ||
	    packet_num > hdr->num_packets || hfi_sanitize_cmd_buff(hdr))
		return -EINVAL;

	packet = hfi_packet_at(hdr, packet_num);
	packet_size = GET_PACKET_SIZE(packet->payload_info);
	...
	packet_info->payload_size = packet_size - sizeof(*packet);
	packet_info->payload_ptr = packet_info->payload_size ?
		(void *)(packet + 1) : NULL;
}

If hdr->cmd_buff_info or packet->payload_info changes in shared memory after hfi_sanitize_cmd_buff() returns, hfi_packet_at() can walk past the buffer or packet_size can become smaller than sizeof(*packet) (underflowing packet_info->payload_size) before dcp_hfi_complete_pending() calls memcpy() on pkt_info->payload_ptr.

Should dcp_hfi_resp_callback() copy the received len bytes out of buf->data into a non-shared kernel buffer before validating and unpacking the packets?

[ ... ]

Quoted 15 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +15 -0
+static int dcp_hfi_send_sync_on_vq(struct dcp_hfi *hfi,
+				   struct virtqueue *vq __maybe_unused,
+				   u32 cmd,
+				   enum hfi_cmd_buff_type cmd_type,
+				   u32 object_id,
+				   enum hfi_packet_payload_type payload_type,
+				   void *payload,
+				   u32 payload_size,
+				   u32 tx_flags,
+				   void *resp_data,
+				   size_t resp_len,
+				   unsigned int timeout_ms)
+{
+	struct dcp_hfi_buf *buf;
+	struct dcp_hfi_completion comp;

[ ... ]

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +8 -0
+	ret = dcp_hfi_send_cmd_async(hfi, buf,
+				     hfi_get_header_size(&hdl), &comp);
+	if (ret)
+		return ret;
+
+	if (!wait_for_completion_timeout(&comp.done, msecs_to_jiffies(timeout_ms))) {
+		dev_err(hfi->dev,
+			"HFI cmd=0x%x buf_type=%s object_id=%u timed out after %ums (pkt_id=%u)\n",

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c line 486

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +10 -0
+			cmd, dcp_hfi_cmd_buff_type_name(cmd_type), object_id,
+			timeout_ms, packet_id);
+		spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+		dcp_hfi_pending_find(hfi, packet_id);
+		spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+		return -ETIMEDOUT;
+	}
+
+	return comp.status;
+}

[Severity: Critical]
Can a timeout racing with response completion cause a stack use-after-free of comp and the caller's resp_data buffer in dcp_hfi_send_sync_on_vq()?

In dcp_hfi_resp_callback(), hfi->resp_drain_lock is dropped before unpacking the response and calling dcp_hfi_complete_pending():

dcp_hfi_resp_callback()
spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
...
dcp_hfi_complete_pending()

    comp = dcp_hfi_pending_find(hfi, pkt_info->packet_id);
      spin_lock_irqsave(&hfi->pending_lock, flags);
      comp = xa_erase(&hfi->pending_xa, packet_id);
      spin_unlock_irqrestore(&hfi->pending_lock, flags);
    if (!status && comp->resp_data && comp->resp_len &&
        pkt_info->payload_size) {
        n = min_t(size_t, comp->resp_len, pkt_info->payload_size);
        memcpy(comp->resp_data, pkt_info->payload_ptr, n);
    }
    comp->status = status;
    complete(&comp->done);

If wait_for_completion_timeout() expires right after dcp_hfi_pending_find() in dcp_hfi_complete_pending() erases comp from hfi->pending_xa, acquiring hfi->resp_drain_lock on the timeout path does not wait for dcp_hfi_complete_pending() because neither resp_drain_lock nor pending_lock is held during the memcpy(), status assignment, and complete() calls.

The timeout path's dcp_hfi_pending_find(hfi, packet_id) then returns NULL, and dcp_hfi_send_sync_on_vq() immediately returns -ETIMEDOUT while dcp_hfi_complete_pending() on the other CPU is still writing to the stack- allocated comp and comp->resp_data.

Should the completion update be serialized under a lock held across the lookup and memcpy()/complete(), or should the timeout path wait on wait_for_completion(&comp.done) when dcp_hfi_pending_find() returns NULL?

[ ... ]

Quoted 28 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +28 -0
+void dcp_hfi_stop(struct dcp_hfi *hfi)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&hfi->cmd_lock, flags);
+	atomic_set(&hfi->shutdown_in_progress, 1);
+	spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+
+	cancel_delayed_work_sync(&hfi->resp_repost_work);
+	dcp_hfi_cancel_pending(hfi, -ENODEV);
+}
+
+void dcp_hfi_quiesce_queues(struct dcp_hfi *hfi)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+	if (hfi->cmd_vq)
+		virtqueue_disable_cb(hfi->cmd_vq);
+	if (hfi->resp_vq)
+		virtqueue_disable_cb(hfi->resp_vq);
+	spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+
+	if (hfi->cmd_vq)
+		while (virtqueue_detach_unused_buf(hfi->cmd_vq))
+			;
+	if (hfi->resp_vq)
+		while (virtqueue_detach_unused_buf(hfi->resp_vq))

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c line 836

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c new file +10 -0
+			;
+}
+
+void dcp_hfi_deinit(struct dcp_hfi *hfi)
+{
+	xa_destroy(&hfi->pending_xa);
+
+	dcp_hfi_free_pool(hfi, hfi->cmd_pool, DCP_HFI_CMD_POOL_SIZE);
+	dcp_hfi_free_pool(hfi, hfi->resp_pool, DCP_HFI_RESP_POOL_SIZE);
+}

[Severity: High]
How are in-flight dcp_hfi_resp_callback() and dcp_hfi_cmd_callback() calls synchronized with dcp_hfi_stop(), dcp_hfi_quiesce_queues(), and dcp_hfi_deinit() during teardown?

In dcp_hfi_resp_callback(), hfi->shutdown_in_progress is only checked once at entry before acquiring hfi->resp_drain_lock, and hfi->resp_drain_lock is released while unpacking each buffer before being re-acquired at repost:

dcp_hfi_resp_callback() {
	if (!hfi || atomic_read(&hfi->shutdown_in_progress))
		return;

	spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
	...
			spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
			...
repost:
			spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
			dcp_hfi_buf_init_sg(buf, DCP_HFI_BUF_SIZE);
			if (virtqueue_add_inbuf_premapped(vq, &buf->sg, 1, buf,
							  NULL, GFP_ATOMIC)) {
				list_add_tail(&buf->node, &hfi->resp_repost_list);
				schedule_delayed_work(&hfi->resp_repost_work,
						      msecs_to_jiffies(20));
			}
		}

		if (virtqueue_kick_prepare(vq))
			virtqueue_notify(vq);
		if (virtqueue_enable_cb(vq))
			break;
}

If dcp_hfi_stop() runs while dcp_hfi_resp_callback() has dropped hfi->resp_drain_lock, dcp_hfi_stop() sets shutdown_in_progress = 1 and calls cancel_delayed_work_sync(&hfi->resp_repost_work), after which dcp_hfi_resp_callback() can reach repost:, re-add the buffer to vq, re-arm hfi->resp_repost_work, and call virtqueue_enable_cb(vq).

Similarly, in dcp_hfi_cmd_callback(), shutdown_in_progress is checked before acquiring hfi->cmd_lock, and in dcp_hfi_quiesce_queues(), virtqueue_disable_cb(hfi->cmd_vq) is called under hfi->resp_drain_lock instead of hfi->cmd_lock while virtqueue_detach_unused_buf() is called on both queues without holding cmd_lock or resp_drain_lock and without waiting for an in-flight dcp_hfi_resp_callback() to finish using buf->data before dcp_hfi_deinit() frees the pools.

Should the callbacks re-check shutdown_in_progress under their respective locks, and should teardown synchronize with in-flight callbacks before canceling the delayed work, detaching virtqueue buffers, and freeing the DMA pools?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
#19 lore raw

PATCH v2 7/23 drm/msm/dcp: Handle asynchronous HFI responses

In reply to Jayant Shekhar

Handle HFI messages that are not synchronous command completions. Capability discovery, resource voting, and display power control produce asynchronous responses that must not be mistaken for ordinary command replies.

Dispatch display events, cache capability payloads, deliver power-control notifications, and defer resource-vote processing while preserving the existing completion path for synchronous commands.

Assisted-by LLM
---
 drivers/gpu/drm/msm/dcp/dcp_hfi.h      |  49 ++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 298 +++++++++++++++++++++++++++++++++
 2 files changed, 347 insertions(+)
drivers/gpu/drm/msm/dcp/dcp_hfi.h +49 -0
index 23e1cd39b89d..055e9c584892 100644
@@ -19,11 +19,24 @@
 #include <linux/workqueue.h>
 #include <linux/xarray.h>
 
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
 #include "hfi/hfi_packet.h"
 #include "hfi/hfi_pack_unpack_common.h"
 #include "hfi/hfi_packer.h"
 #include "hfi/hfi_unpacker.h"
+#include "hfi/hfi_commands_device.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_panel.h"
+#include "hfi/hfi_commands_display_events.h"
 #include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_defs_device.h"
+#include "hfi/hfi_defs_panel.h"
+#include "hfi/hfi_properties_device.h"
+#include "hfi/hfi_properties_display.h"
+#include "hfi/hfi_properties_panel.h"
 
 struct dcp_drv;
 
@@ -35,6 +48,19 @@ struct dcp_drv;
 #define DCP_MAX_DISPLAYS	4
 #define DCP_HFI_CAPS_BUF_SIZE	4096
 
+#define DCP_HFI_RESOURCE_VOTE_BUF_SIZE	256
+
+/* Wire-format resource vote entry. */
+struct dcp_resource_vote_pair {
+	u32 key;
+	u64 value;
+};
+
+struct dcp_resource_vote_wire {
+	__le32 key;
+	__le64 value;
+} __packed;
+
 struct dcp_hfi_caps_cache {
 	struct completion device_done;
 	struct completion display_done;
@@ -135,6 +161,24 @@ struct dcp_hfi {
 	atomic_t packet_id;
 	/* Blocks new work once transport teardown begins. */
 	atomic_t shutdown_in_progress;
+	struct work_struct resource_vote_work;
+	/* Protects resource_vote_buf, resource_vote_len, and callback state. */
+	spinlock_t resource_vote_lock;
+	u8 resource_vote_buf[DCP_HFI_RESOURCE_VOTE_BUF_SIZE];
+	u32 resource_vote_len;
+	u32 resource_vote_generation;
+	void (*resource_vote_cb)(struct dcp_drv *drv,
+				 const struct dcp_resource_vote_pair *pairs,
+				 u32 count);
+	/* Protects capability payload buffers during event delivery. */
+	spinlock_t caps_lock;
+	/* Serializes capability transactions. */
+	struct mutex caps_cache_lock;
+	struct dcp_hfi_caps_cache caps_cache;
+
+	void *power_control_priv;
+	void (*power_control_cb)(void *priv, u32 display_id,
+				 u32 power_mask, bool enable, u32 packet_id);
 };
 
 int dcp_hfi_init(struct dcp_drv *drv, struct virtio_device *vdev,
@@ -167,3 +211,8 @@ int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
 				      enum hfi_packet_payload_type payload_type,
 				      void *payload, u32 payload_size, u32 tx_flags);
 #endif /* __DCP_HFI_H__ */
+void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
+				  void (*cb)(void *priv, u32 display_id,
+					     u32 power_mask, bool enable,
+					     u32 packet_id));
+void dcp_hfi_clear_power_control_cb(struct dcp_hfi *hfi, void *priv);
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +298 -0

369 lines not on this page · raw message

Signature · 1 line
2.43.0
#20 lore raw

Re: [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [High] drm/msm/dcp: uninitialized completions in dcp_hfi_init() and dcp_hfi_deinit() - [High]…

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] drm/msm/dcp: uninitialized completions in dcp_hfi_init() and dcp_hfi_deinit()
- [High] drm/msm/dcp: race re-scheduling resource_vote_work after dcp_hfi_stop()
- [High] drm/msm/dcp: race between dcp_hfi_clear_power_control_cb() and power_cb()
- [Medium] drm/msm/dcp: malformed RESOURCE_VOTE clobbers vote in dcp_hfi_dispatch_event()
- [Low] drm/msm/dcp: declarations placed after #endif include guard in dcp_hfi.h --

commit 181941c645b83584ab1315f1b30bec1f0ef47673
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Handle asynchronous HFI responses

This commit adds handling for asynchronous HFI messages in the DCP driver, dispatching display events, caching capability payloads, invoking power control callbacks, and deferring resource vote updates to a workqueue.

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_hfi.h +0 -0
index 23e1cd39b89d4..055e9c584892b 100644

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_hfi.h int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd, · line 218

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_hfi.h +5 -0
@@ -167,3 +211,8 @@ int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
 				      enum hfi_packet_payload_type payload_type,
 				      void *payload, u32 payload_size, u32 tx_flags);
 #endif /* __DCP_HFI_H__ */
+void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
+				  void (*cb)(void *priv, u32 display_id,
+					     u32 power_mask, bool enable,
+					     u32 packet_id));
+void dcp_hfi_clear_power_control_cb(struct dcp_hfi *hfi, void *priv);

[Severity: Low]
Should the declarations for dcp_hfi_set_power_control_cb() and dcp_hfi_clear_power_control_cb() be placed before #endif /* __DCP_HFI_H__ */ so they remain inside the header include guard?

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +0 -0
index 9f7ac798e13c4..774a5afac0f0a 100644

[ ... ]

Quoted 36 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +32 -0
@@ -227,6 +233,218 @@ void dcp_hfi_cmd_callback(struct virtqueue *vq)
 	spin_unlock_irqrestore(&hfi->cmd_lock, flags);
 }
 
+static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi,
+				       struct completion *done,
+				       u8 *dst, u32 *dst_len,
+				       const struct hfi_packet_info *pkt_info,
+				       const char *name)
+{
+	u32 n;
+
+	if (!pkt_info->payload_ptr || !pkt_info->payload_size) {
+		dev_dbg(hfi->dev, "%s: empty payload\n", name);
+		return;
+	}
+
+	if (pkt_info->payload_size > DCP_HFI_CAPS_BUF_SIZE) {
+		dev_warn(hfi->dev, "%s: payload too large: %u\n",
+			 name, pkt_info->payload_size);
+		return;
+	}
+
+	n = pkt_info->payload_size;
+	memcpy(dst, pkt_info->payload_ptr, n);
+	*dst_len = n;
+	if (done)
+		complete(done);
+	dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
+}
+
+/* Route asynchronous events and capability packets before flag handling. */
+static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
+				   const struct hfi_packet_info *pkt_info)
+{
+	switch (pkt_info->cmd) {

[ ... ]

Quoted 17 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +17 -0
+	case HFI_COMMAND_DISPLAY_POWER_CONTROL: {
+		u32 display_id = pkt_info->id & 0xFFFF;
+		u32 power_mask = 0;
+		bool enable = true;
+		unsigned long flags;
+		void *power_priv;
+		void (*power_cb)(void *priv, u32 display_id,
+				 u32 power_mask, bool enable, u32 packet_id);
+
+		if (pkt_info->payload_ptr &&
+		    pkt_info->payload_size >= 2 * sizeof(u32)) {
+			const u32 *p = pkt_info->payload_ptr;
+
+			power_mask = p[0];
+			enable     = (p[1] == HFI_TRUE);
+		}
+

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c void dcp_hfi_cmd_callback(struct virtqueue *vq) · line 318

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +10 -0
+		spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+		power_cb = hfi->power_control_cb;
+		power_priv = hfi->power_control_priv;
+		spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+
+		if (power_cb)
+			power_cb(power_priv, display_id, power_mask, enable,
+				 pkt_info->packet_id);
+		return true;
+	}

[Severity: High]
Can power_cb() race with dcp_hfi_clear_power_control_cb() during teardown?

In dcp_hfi_dispatch_event(), HFI_COMMAND_DISPLAY_POWER_CONTROL copies hfi->power_control_cb and hfi->power_control_priv under hfi->resp_drain_lock and then drops the lock before calling power_cb(power_priv, ...).

Meanwhile, dcp_hfi_clear_power_control_cb() only clears those pointers under hfi->resp_drain_lock and returns immediately:

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_clear_power_control_cb() {
	spin_lock_irqsave(&hfi->resp_drain_lock, flags);
	if (hfi->power_control_priv == priv) {
		hfi->power_control_priv = NULL;
		hfi->power_control_cb = NULL;
	}
	spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
}

If a caller unregisters its callback and cancels its work or frees priv while dcp_hfi_dispatch_event() has already dropped hfi->resp_drain_lock, couldn't power_cb() still run after dcp_hfi_clear_power_control_cb() returns, re-scheduling work or accessing power_priv after it is torn down?

Quoted 24 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +24 -0
+
+	case HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS: {
+		unsigned long flags;
+
+		spin_lock_irqsave(&hfi->caps_lock, flags);
+		dcp_hfi_store_caps_payload(hfi, &hfi->caps_cache.device_done,
+					   hfi->caps_cache.device_buf,
+					   &hfi->caps_cache.device_len,
+					   pkt_info, "DEVICE_INIT_DEVICE_CAPS");
+		spin_unlock_irqrestore(&hfi->caps_lock, flags);
+		return true;
+	}
+
+	case HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS: {
+		unsigned long flags;
+
+		spin_lock_irqsave(&hfi->caps_lock, flags);
+		dcp_hfi_store_caps_payload(hfi, &hfi->caps_cache.display_done,
+					   hfi->caps_cache.display_buf,
+					   &hfi->caps_cache.display_len,
+					   pkt_info, "DEVICE_INIT_DISPLAY_CAPS");
+		spin_unlock_irqrestore(&hfi->caps_lock, flags);
+		return true;
+	}

[ ... ]

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +11 -0
+	case HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE: {
+		unsigned long vote_flags;
+		u32 payload_size = pkt_info->payload_size;
+		bool valid = pkt_info->payload_ptr && payload_size &&
+			payload_size <= DCP_HFI_RESOURCE_VOTE_BUF_SIZE &&
+			payload_size % sizeof(struct dcp_resource_vote_wire) == 0;
+
+		if (!valid) {
+			dev_warn_ratelimited(hfi->dev,
+					     "dropping malformed RESOURCE_VOTE payload size=%u max=%u\n",
+					     payload_size, DCP_HFI_RESOURCE_VOTE_BUF_SIZE);

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c void dcp_hfi_cmd_callback(struct virtqueue *vq) · line 436

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +10 -0
+			payload_size = 0;
+		}
+
+		spin_lock_irqsave(&hfi->resource_vote_lock, vote_flags);
+		if (payload_size)
+			memcpy(hfi->resource_vote_buf, pkt_info->payload_ptr,
+			       payload_size);
+		hfi->resource_vote_len = payload_size;
+		hfi->resource_vote_generation++;
+		spin_unlock_irqrestore(&hfi->resource_vote_lock, vote_flags);

[Severity: Medium]
When a malformed HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE packet arrives, why does this set payload_size = 0 and continue instead of returning early?

By setting hfi->resource_vote_len = 0 and incrementing hfi->resource_vote_generation under hfi->resource_vote_lock, a malformed packet will clobber any valid resource vote already queued in hfi->resource_vote_buf that dcp_hfi_resource_vote_work() has not yet processed, causing dcp_hfi_resource_vote_work() to see len == 0 and drop the earlier valid vote.

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c int dcp_hfi_init(struct dcp_drv *drv, · line 993

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +3 -0
@@ -726,7 +987,10 @@ int dcp_hfi_init(struct dcp_drv *drv,
 
 	spin_lock_init(&hfi->cmd_lock);
 	spin_lock_init(&hfi->pending_lock);
+	spin_lock_init(&hfi->resource_vote_lock);
 	spin_lock_init(&hfi->resp_drain_lock);
+	spin_lock_init(&hfi->caps_lock);
+	mutex_init(&hfi->caps_cache_lock);

[Severity: High]
Where are hfi->caps_cache.device_done and hfi->caps_cache.display_done initialized?

In dcp_hfi_init(), hfi is allocated with devm_kzalloc() and response buffers are posted immediately via dcp_hfi_prepost_resp_bufs(), but init_completion() is never called on hfi->caps_cache.device_done or hfi->caps_cache.display_done. In addition, dcp_hfi_deinit() zeroes the entire struct with memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache)), which wipes the embedded completion waitqueues and spinlocks.

If HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS or HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS arrives in dcp_hfi_resp_callback()->dcp_hfi_dispatch_event()->dcp_hfi_store_caps_payload() while those completions are zeroed, won't complete(done) dereference a NULL wait.task_list pointer in swake_up_locked()?

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c void dcp_hfi_stop(struct dcp_hfi *hfi) · line 1103

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +1 -0
@@ -804,6 +1097,7 @@ void dcp_hfi_stop(struct dcp_hfi *hfi)
 	atomic_set(&hfi->shutdown_in_progress, 1);
 	spin_unlock_irqrestore(&hfi->cmd_lock, flags);
 
+	cancel_work_sync(&hfi->resource_vote_work);
 	cancel_delayed_work_sync(&hfi->resp_repost_work);
 	dcp_hfi_cancel_pending(hfi, -ENODEV);
 }

[Severity: High]
Can dcp_hfi_resp_callback() re-schedule hfi->resource_vote_work after dcp_hfi_stop() has already called cancel_work_sync()?

In dcp_hfi_resp_callback(), hfi->shutdown_in_progress is only checked at the start of the function, and hfi->resp_drain_lock is dropped while unpacking packets and calling dcp_hfi_dispatch_event():

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_resp_callback() {
	if (!hfi || atomic_read(&hfi->shutdown_in_progress))
		return;

	spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
	virtqueue_disable_cb(vq);
	for (;;) {
		while ((buf = virtqueue_get_buf(vq, &len)) != NULL) {
			...
			spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
			...
			} else if (dcp_hfi_dispatch_event(hfi, &pkt_info)) {
}

If dcp_hfi_stop() runs on another CPU while dcp_hfi_resp_callback() is processing a buffer, dcp_hfi_stop() can set hfi->shutdown_in_progress = 1 and finish cancel_work_sync(&hfi->resource_vote_work) before dcp_hfi_dispatch_event() handles HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE and calls schedule_work(&hfi->resource_vote_work).

Since neither HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE nor dcp_hfi_resource_vote_work() checks hfi->shutdown_in_progress, and neither dcp_hfi_quiesce_queues() nor dcp_hfi_deinit() cancels resource_vote_work after virtqueue callbacks are synchronized, could dcp_hfi_resource_vote_work() execute after teardown completes?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
#21 lore raw

PATCH v2 8/23 drm/msm/dcp: Add IPCC power notifications

In reply to Jayant Shekhar

Wake and acknowledge DCP firmware through the platform IPCC channel. DCP may power-collapse while VirtIO-MMIO remains accessible, so a VirtIO queue kick alone cannot reliably wake firmware during system power transitions.

Implement serialized IPCC notifications, bounded acknowledgment waits, IRQ wake handling, and teardown-safe callback lifetime management while leaving HFI data transport on VirtIO.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile       |   1 +
 drivers/gpu/drm/msm/dcp/dcp_drv.h  |   1 +
 drivers/gpu/drm/msm/dcp/dcp_ipcc.c | 173 +++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_ipcc.h |  42 +++++++++
 4 files changed, 217 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index 56c48fe2e2c5..9c698c4f4949 100644
@@ -73,6 +73,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_core.o \
+	dcp/dcp_ipcc.o \
 	dcp/hfi/hfi_packer.o \
 	dcp/hfi/hfi_unpacker.o \
 	dcp/hfi/hfi_utils.o
drivers/gpu/drm/msm/dcp/dcp_drv.h +1 -0
index 44d9b2cd5171..16f87035cb9a 100644
@@ -11,6 +11,7 @@
 #include <linux/platform_device.h>
 
 #include "dcp_hfi.h"
+#include "dcp_ipcc.h"
 
 struct dcp_dpu_kms;
 
drivers/gpu/drm/msm/dcp/dcp_ipcc.c new file +173 -0

173 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_ipcc.h new file +42 -0

42 lines not on this page · raw message

Signature · 1 line
2.43.0
#22 lore raw

Re: [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] drm/msm/dcp: unsynchronized resp_vq kick in dcp_ipcc_handle_rx() - [Medium] drm/msm/dcp…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] drm/msm/dcp: unsynchronized resp_vq kick in dcp_ipcc_handle_rx()
- [Medium] drm/msm/dcp: dcp_ipcc_send_power_notify() misuses mbox_client_txdone()
- [Low] drm/msm/dcp: missing MAILBOX dependency in Kconfig causes link failure --

commit 847d1e1e504619e829df644d8cb1af09e3cef49c
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add IPCC power notifications

This commit adds an inter-processor communication controller mailbox client to wake and acknowledge display coprocessor firmware during power transitions. It implements serialized notifications, bounded acknowledgment waits, wake interrupt handling, and callback lifetime management.

drivers/gpu/drm/msm/Makefile msm-display-$(CONFIG_DRM_MSM_MDP5) += \ · line 76

Quoted 9 lines
drivers/gpu/drm/msm/Makefile +1 -0
index 56c48fe2e2c5f..9c698c4f49495 100644
@@ -73,6 +73,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_core.o \
+	dcp/dcp_ipcc.o \

[Severity: Low]
Will this cause a link failure when the mailbox framework is disabled?

In drivers/gpu/drm/msm/Makefile, dcp/dcp_ipcc.o is built unconditionally whenever the display coprocessor option is enabled, and dcp_ipcc_init(), dcp_ipcc_free_channel(), and dcp_ipcc_send_power_notify() call mbox_request_channel(), mbox_free_channel(), mbox_send_message(), and mbox_client_txdone().

However, include/linux/mailbox_client.h does not provide inline stubs when mailbox support is disabled, and drivers/gpu/drm/msm/Kconfig only lists:

config DRM_MSM_DCP
bool "Enable DCP firmware-delegated display support in MSM DRM driver" depends on DRM_MSM
depends on VIRTIO

Should the Kconfig entry also depend on or select the mailbox subsystem?

[ ... ]

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_ipcc.c new file +0 -0
new file mode 100644
index 0000000000000..e161939dddc1e
@@ -0,0 +1,173 @@

[ ... ]

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_ipcc.c new file +6 -0
+static void dcp_ipcc_handle_rx(struct dcp_ipcc *ipcc)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipcc->state_lock, flags);
+	if (ipcc->shutting_down) {

drivers/gpu/drm/msm/dcp/dcp_ipcc.c line 39

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_ipcc.c new file +10 -0
+		spin_unlock_irqrestore(&ipcc->state_lock, flags);
+		return;
+	}
+
+	complete(&ipcc->power_notify_done);
+	if (ipcc->resp_vq && virtqueue_kick_prepare(ipcc->resp_vq))
+		virtqueue_notify(ipcc->resp_vq);
+
+	spin_unlock_irqrestore(&ipcc->state_lock, flags);
+}

[Severity: High]
Can calling virtqueue_kick_prepare(ipcc->resp_vq) here under only ipcc->state_lock race with concurrent response virtqueue operations?

In drivers/gpu/drm/msm/dcp/dcp_hfi_core.c, operations on the same response virtqueue in dcp_hfi_resp_callback(), dcp_hfi_resp_repost_work(), and dcp_hfi_quiesce_queues() are serialized under hfi->resp_drain_lock:

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_resp_callback() {
    ...
repost:
			spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
			dcp_hfi_buf_init_sg(buf, DCP_HFI_BUF_SIZE);
			if (virtqueue_add_inbuf_premapped(vq, &buf->sg, 1, buf,
							  NULL, GFP_ATOMIC)) {
				list_add_tail(&buf->node, &hfi->resp_repost_list);
				schedule_delayed_work(&hfi->resp_repost_work,
						      msecs_to_jiffies(20));
			}
		}

		if (virtqueue_kick_prepare(vq))
			virtqueue_notify(vq);
}

Because virtqueue_kick_prepare() requires external serialization with other operations on the same virtqueue and updates vq->num_added and vq->in_use, running dcp_ipcc_handle_rx() from dcp_ipcc_rx_callback() or dcp_ipcc_power_notify_irq() without holding hfi->resp_drain_lock can corrupt vq->num_added kick tracking or trigger a virtio ring in_use check failure.

In addition, complete(&ipcc->power_notify_done) runs before virtqueue_notify(ipcc->resp_vq), waking waiters in dcp_ipcc_send_power_notify() before the response virtqueue notification has been sent.

[ ... ]

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_ipcc.c new file +6 -0
+int dcp_ipcc_send_power_notify(struct dcp_ipcc *ipcc)
+{
+	unsigned long timeout;
+	int ret;
+
+	mutex_lock(&ipcc->power_notify_lock);

drivers/gpu/drm/msm/dcp/dcp_ipcc.c line 158

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_ipcc.c new file +10 -0
+
+	reinit_completion(&ipcc->power_notify_done);
+
+	ret = mbox_send_message(ipcc->power_chan, NULL);
+	mbox_client_txdone(ipcc->power_chan, ret);
+	if (ret < 0) {
+		dev_err(ipcc->client.dev,
+			"IPCC TX: power notify failed: %d\n", ret);
+		goto out_unlock;
+	}

[Severity: Medium]
Should mbox_client_txdone() only be called with 0 after verifying that mbox_send_message() succeeded?

When mbox_send_message() fails (ret < 0), no message was queued for this request, yet calling mbox_client_txdone(ipcc->power_chan, ret) before the error check still invokes tx_tick(chan, ret) on the mailbox channel (and would dereference a null pointer if ipcc->power_chan were null).

Furthermore, when mbox_send_message() succeeds in non-blocking mode (ipcc->client.tx_block = false), it returns the non-negative ring buffer slot index from add_to_rbuf(), whereas mbox_client_txdone() expects 0 on success or a negative errno on failure.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
#23 lore raw

PATCH v2 9/23 drm/msm/dcp: Add generic HFI display commands

In reply to Jayant Shekhar

Translate DRM display state into HFI commands for the firmware-owned DCP pipeline. Since Linux cannot program the display hardware directly, KMS operations need command helpers for resources, CRTCs, planes, bandwidth, and frame sequencing.

Implement generic display command helpers and encode capability, resource, layer, mixer, bandwidth, frame-trigger, and scan-sequence state into HFI properties.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile              |   4 +
 drivers/gpu/drm/msm/dcp/dcp_hfi.h         |  32 +++
 drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c    | 191 ++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c |  64 ++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c     | 366 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c   |  63 +++++
 6 files changed, 720 insertions(+)
drivers/gpu/drm/msm/Makefile +4 -0
index 9c698c4f4949..bdc5e3b8efa7 100644
@@ -73,6 +73,10 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_core.o \
+	dcp/dcp_hfi_crtc.o \
+	dcp/dcp_hfi_encoder.o \
+	dcp/dcp_hfi_kms.o \
+	dcp/dcp_hfi_plane.o \
 	dcp/dcp_ipcc.o \
 	dcp/hfi/hfi_packer.o \
 	dcp/hfi/hfi_unpacker.o \
drivers/gpu/drm/msm/dcp/dcp_hfi.h +32 -0
index 055e9c584892..6d3eadb5e811 100644
@@ -210,6 +210,38 @@ int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
 				      enum hfi_cmd_buff_type cmd_type, u32 object_id,
 				      enum hfi_packet_payload_type payload_type,
 				      void *payload, u32 payload_size, u32 tx_flags);
+int dcp_hfi_display_event_register(struct dcp_hfi *hfi, u32 session_id,
+				   u32 event_id);
+int dcp_hfi_display_event_deregister(struct dcp_hfi *hfi, u32 session_id,
+				     u32 event_id);
+int dcp_hfi_display_event_register_async(struct dcp_hfi *hfi, u32 session_id,
+					 u32 event_id);
+int dcp_hfi_display_event_deregister_async(struct dcp_hfi *hfi,
+					   u32 session_id, u32 event_id);
+int dcp_hfi_crtc_set_resource(struct dcp_hfi *hfi, u32 session_id,
+			      u32 lm_idx, u32 width, u32 height,
+			      u32 roi_x, u32 roi_y);
+int dcp_hfi_crtc_set_dest_roi(struct dcp_hfi *hfi, u32 session_id,
+			      u32 x, u32 y, u32 width, u32 height);
+int dcp_hfi_crtc_set_scan_sequence_id(struct dcp_hfi *hfi, u32 session_id,
+				      u32 seq_id);
+int dcp_hfi_crtc_set_power_mode(struct dcp_hfi *hfi, u32 session_id,
+				enum hfi_display_power_mode mode);
+int dcp_hfi_crtc_set_autorefresh(struct dcp_hfi *hfi, u32 session_id,
+				 bool enable, u32 frame_count);
+int dcp_hfi_crtc_set_bw(struct dcp_hfi *hfi, u32 session_id,
+			u64 dram_ib, u64 dram_ab, u64 llcc_ib,
+			u64 llcc_ab, u64 core_clk);
+int dcp_hfi_submit_prop_pkt(struct dcp_hfi *hfi, const void *pkt,
+			    size_t pkt_size);
+int dcp_hfi_plane_disable(struct dcp_hfi *hfi, u32 session_id, u32 pipe_idx);
+int dcp_hfi_display_commit(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_send_device_init(struct dcp_hfi *hfi);
+int dcp_hfi_get_capabilities(struct dcp_hfi *hfi, struct dcp_caps *caps);
+int dcp_hfi_send_resource_register(struct dcp_hfi *hfi);
+int dcp_hfi_set_display_property(struct dcp_hfi *hfi, u32 session_id,
+				 u32 prop_id, u32 value);
+
 #endif /* __DCP_HFI_H__ */
 void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
 				  void (*cb)(void *priv, u32 display_id,
drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c new file +191 -0

191 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c new file +64 -0

64 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c new file +366 -0

366 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c new file +63 -0

63 lines not on this page · raw message

Signature · 1 line
2.43.0
#24 lore raw

Re: [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] drm/msm/dcp: completion corruption and unlocked caps_cache in dcp_hfi_kms.c - [High] drm/msm/dcp…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/msm/dcp: completion corruption and unlocked caps_cache in dcp_hfi_kms.c
- [High] drm/msm/dcp: pipe count clobbering in dcp_hfi_parse_init_caps_kv() --

commit 90bc8a62bf35e8d6f13a5451d4e21b3d608a5097
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add generic HFI display commands

This commit adds generic HFI display command helpers and capability parsing for the firmware-owned DCP display pipeline, translating DRM state for resources, CRTCs, planes, bandwidth, and frame sequencing into HFI commands and properties.

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c new file +0 -0
new file mode 100644
index 0000000000000..ac8000a8ee900
@@ -0,0 +1,366 @@

[ ... ]

Quoted 62 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c new file +62 -0
+static void dcp_hfi_caps_cache_init(struct dcp_hfi_caps_cache *cache)
+{
+	init_completion(&cache->device_done);
+	init_completion(&cache->display_done);
+}
+
+static void dcp_hfi_caps_cache_start(struct dcp_hfi *hfi)
+{
+	memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache));
+	dcp_hfi_caps_cache_init(&hfi->caps_cache);
+}
+
+static void dcp_hfi_caps_cache_stop(struct dcp_hfi *hfi)
+{
+	memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache));
+}
+
+int dcp_hfi_send_device_init(struct dcp_hfi *hfi)
+{
+	int ret;
+
+	mutex_lock(&hfi->caps_cache_lock);
+	dcp_hfi_caps_cache_start(hfi);
+
+	/* Payload and flags follow the HFI device-command definition. */
+	dev_dbg(hfi->dev,
+		"HFI DEVICE_INIT cmd=0x%x flags=0x%x timeout=%ums\n",
+		HFI_COMMAND_DEVICE_INIT,
+		HFI_TX_FLAGS_RESPONSE_REQUIRED | HFI_TX_FLAGS_NON_DISCARDABLE,
+		DCP_HFI_TIMEOUT_INIT_MS);
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DEVICE_INIT,
+				HFI_CMD_BUFF_DEVICE, 0,
+				HFI_PAYLOAD_NONE, NULL, 0,
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_INIT_MS);
+	if (ret) {
+		dev_err(hfi->dev, "HFI DEVICE_INIT failed: %d\n", ret);
+		dcp_hfi_caps_cache_stop(hfi);
+	} else {
+		dev_dbg(hfi->dev, "HFI DEVICE_INIT succeeded\n");
+	}
+
+	mutex_unlock(&hfi->caps_cache_lock);
+	return ret;
+}
+
+static void dcp_hfi_parse_init_caps_kv(struct dcp_caps *caps,
+				       u32 prop_key,
+				       const u32 *payload,
+				       u32 max_words)
+{
+	u32 prop_id = HFI_PROP_ID(prop_key);
+	u32 count, i;
+
+	if (!payload || !max_words)
+		return;
+
+	switch (prop_id) {

[ ... ]

Quoted 30 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c new file +30 -0
+	case HFI_PROPERTY_DEVICE_INIT_VIG_INDICES:
+		if (max_words < 1)
+			break;
+		count = payload[0];
+		if (count > max_words - 1)
+			count = max_words - 1;
+		caps->num_vig_pipes = 0;
+		caps->virt_vig_count = 0;
+		for (i = 0; i < count; i++) {
+			u32 idx = payload[1 + i];
+
+			if (DCP_REC_ID(idx) == 0) {
+				if (caps->num_vig_pipes < DCP_MAX_SSPP_COUNT)
+					caps->vig_pipe_ids[caps->num_vig_pipes++] = idx;
+			} else {
+				if (caps->virt_vig_count < DCP_MAX_SSPP_COUNT)
+					caps->vig_r1_indices[caps->virt_vig_count++] = idx;
+			}
+		}
+		break;
+	case HFI_PROPERTY_DEVICE_INIT_DMA_INDICES:
+		if (max_words < 1)
+			break;
+		count = payload[0];
+		if (count > max_words - 1)
+			count = max_words - 1;
+		caps->num_dma_pipes = 0;
+		caps->virt_dma_count = 0;
+		for (i = 0; i < count; i++) {
+			u32 idx = payload[1 + i];

drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c line 131

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c new file +10 -0
+
+			if (DCP_REC_ID(idx) == 0) {
+				if (caps->num_dma_pipes < DCP_MAX_SSPP_COUNT)
+					caps->dma_pipe_ids[caps->num_dma_pipes++] = idx;
+			} else {
+				if (caps->virt_dma_count < DCP_MAX_SSPP_COUNT)
+					caps->dma_r1_indices[caps->virt_dma_count++] = idx;
+			}
+		}
+		break;

[Severity: High]
Won't resetting both the primary and rect-1 pipe counters here clobber the previously parsed pipe counts when dcp_hfi_get_capabilities() parses multiple capability buffers into the same struct dcp_caps?

In drivers/gpu/drm/msm/dcp/hfi/hfi_properties_device.h, both rect-0 pipes (DCP_REC_ID(idx) == 0) and rect-1 multirect pipes (DCP_REC_ID(idx) != 0) use HFI_PROPERTY_DEVICE_INIT_VIG_INDICES and HFI_PROPERTY_DEVICE_INIT_DMA_INDICES.

dcp_hfi_get_capabilities() calls dcp_hfi_parse_kv_buf() sequentially on cache->device_buf, cache->display_buf, cache->vig_buf, cache->dma_buf, cache->vig_r1_buf, and cache->dma_r1_buf.

When cache->vig_r1_buf and cache->dma_r1_buf are parsed after the primary pipe buffers, their HFI_PROPERTY_DEVICE_INIT_VIG_INDICES and HFI_PROPERTY_DEVICE_INIT_DMA_INDICES properties contain only rect-1 indices, which unconditionally resets caps->num_vig_pipes and caps->num_dma_pipes back to 0. As a result, caps->max_planes evaluates to 0 and dcp_hfi_get_capabilities() fails with -EINVAL ("invalid DCP caps: no scanout pipes"). Conversely, if device_buf contains both rect-0 and rect-1 indices and vig_buf/dma_buf contain only rect-0 indices, parsing vig_buf/dma_buf resets caps->virt_vig_count and caps->virt_dma_count to 0.

[ ... ]

Quoted 55 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c new file +55 -0
+int dcp_hfi_get_capabilities(struct dcp_hfi *hfi, struct dcp_caps *caps)
+{
+	struct dcp_hfi_caps_cache *cache;
+	int ret = 0, opt_ret;
+
+	memset(caps, 0, sizeof(*caps));
+
+	mutex_lock(&hfi->caps_cache_lock);
+	cache = &hfi->caps_cache;
+
+	if (!wait_for_completion_timeout(&cache->device_done,
+					 msecs_to_jiffies(DCP_HFI_TIMEOUT_CAPS_MS))) {
+		dev_err(hfi->dev,
+			"DEVICE_INIT_DEVICE_CAPS timeout after %ums\n",
+			DCP_HFI_TIMEOUT_CAPS_MS);
+		ret = -ETIMEDOUT;
+		goto out_free_cache;
+	}
+
+	if (!cache->device_len) {
+		dev_err(hfi->dev, "DEVICE_INIT_DEVICE_CAPS: empty payload\n");
+		ret = -ENODATA;
+		goto out_free_cache;
+	}
+
+	ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->device_buf,
+				   cache->device_len);
+	if (ret)
+		goto out_free_cache;
+
+	if (!wait_for_completion_timeout(&cache->display_done,
+					 msecs_to_jiffies(DCP_HFI_TIMEOUT_CAPS_MS))) {
+		dev_warn(hfi->dev,
+			 "DEVICE_INIT_DISPLAY_CAPS not received\n");
+	} else if (cache->display_len > 0) {
+		opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->display_buf,
+					       cache->display_len);
+		if (opt_ret) {
+			dev_warn(hfi->dev,
+				 "DISPLAY_CAPS parse failed: %d (non-fatal)\n",
+				 opt_ret);
+		}
+	}
+	if (cache->vig_len > 0) {
+		opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->vig_buf,
+					       cache->vig_len);
+		if (opt_ret)
+			dev_warn(hfi->dev,
+				 "VIG_CAPS parse failed: %d (non-fatal)\n",
+				 opt_ret);
+		else
+			dcp_hfi_log_pipe_ids(hfi->dev, "VIG_CAPS",
+					     caps->vig_pipe_ids,
+					     caps->num_vig_pipes);
+	}

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c line 326

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c new file +5 -0
+out_free_cache:
+	dcp_hfi_caps_cache_stop(hfi);
+	mutex_unlock(&hfi->caps_cache_lock);
+	return ret;
+}

[Severity: High]
Can zeroing hfi->caps_cache in dcp_hfi_caps_cache_stop() corrupt device_done and display_done while capability events are still in flight, and does caps_cache access here race with dcp_hfi_dispatch_event()?

struct dcp_hfi_caps_cache embeds the device_done and display_done struct completion objects. Calling memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache)) in dcp_hfi_caps_cache_stop() (and in dcp_hfi_caps_cache_start() before dcp_hfi_caps_cache_init(), as well as prior to the first dcp_hfi_send_device_init() call after dcp_hfi_init() posts response buffers) zeroes wait.lock and sets wait.task_list.next and wait.task_list.prev to NULL.

In dcp_hfi_get_capabilities(), a timeout on display_done is treated as a non-fatal warning and execution proceeds to out_free_cache, where dcp_hfi_caps_cache_stop() zeroes hfi->caps_cache. The same happens if device_done times out, if dcp_hfi_parse_kv_buf() fails on device_buf, or if dcp_hfi_send_sync() times out in dcp_hfi_send_device_init().

If HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS or HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS then arrives in dcp_hfi_resp_callback()->dcp_hfi_dispatch_event()->dcp_hfi_store_caps_payload(), complete() calls swake_up_locked() on the zeroed completion. Because list_empty(&q->task_list) checks READ_ONCE(head->next) == head, a NULL next pointer evaluates as non-empty and causes a NULL pointer dereference in interrupt context (along with operating on a zeroed raw_spinlock_t).

In addition, dcp_hfi_dispatch_event() in dcp_hfi_core.c protects writes to hfi->caps_cache with spin_lock_irqsave(&hfi->caps_lock, flags), whereas dcp_hfi_caps_cache_start(), dcp_hfi_caps_cache_stop(), and dcp_hfi_get_capabilities() only hold the mutex hfi->caps_cache_lock and never acquire hfi->caps_lock. Because VIG_CAPS, DMA_CAPS, VIG_R1_CAPS, and DMA_R1_CAPS have no completion wait in dcp_hfi_get_capabilities(), reading cache->vig_buf, cache->dma_buf, cache->vig_r1_buf, and cache->dma_r1_buf (and zeroing hfi->caps_cache at out_free_cache) can also race concurrently with dcp_hfi_store_caps_payload() on another CPU.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
#25 lore raw

PATCH v2 10/23 drm/msm/dcp: Add HFI DSI and panel commands

In reply to Jayant Shekhar

Translate DSI host and panel operations into HFI commands. Firmware owns the DSI hardware, so mode validation, display power, panel setup, and DCS transfers must cross the HFI interface instead of accessing controller registers from Linux.

Implement DSI mode validation, display session and power management, panel initialization, and DCS transfer helpers while keeping them separate from generic display commands.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile                |   2 +
 drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h     |  81 ++++
 drivers/gpu/drm/msm/dcp/dcp_hfi.h           |  25 ++
 drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c       | 252 ++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c | 592 ++++++++++++++++++++++++++++
 5 files changed, 952 insertions(+)
drivers/gpu/drm/msm/Makefile +2 -0
index bdc5e3b8efa7..77c268aabca6 100644
@@ -74,6 +74,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_core.o \
 	dcp/dcp_hfi_crtc.o \
+	dcp/dcp_hfi_dsi.o \
+	dcp/dcp_hfi_dsi_panel.o \
 	dcp/dcp_hfi_encoder.o \
 	dcp/dcp_hfi_kms.o \
 	dcp/dcp_hfi_plane.o \
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +81 -0

81 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_hfi.h +25 -0
index 6d3eadb5e811..88d73de69383 100644
@@ -242,6 +242,31 @@ int dcp_hfi_send_resource_register(struct dcp_hfi *hfi);
 int dcp_hfi_set_display_property(struct dcp_hfi *hfi, u32 session_id,
 				 u32 prop_id, u32 value);
 
+struct dcp_dsi_hfi_panel_config;
+int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
+		       const struct drm_display_mode *mode,
+		       const struct mipi_dsi_device *dsi,
+		       const struct dcp_dsi_hfi_panel_config *cfg);
+int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx,
+			   u32 display_id, u32 *session_id);
+int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
+				  const struct drm_display_mode *mode,
+				  u64 clk_rate_hz);
+int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
+			     const struct drm_display_mode *mode,
+			     u64 clk_rate_hz);
+int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
+				      u32 packet_id);
+int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id);
+ssize_t dcp_hfi_dcs_transfer(struct dcp_hfi *hfi, u32 session_id,
+			     const struct mipi_dsi_msg *msg,
+			     void *tx_buf, dma_addr_t tx_iova,
+			     void *rx_buf, dma_addr_t rx_iova,
+			     u32 post_wait_ms, u32 ctrl_idx);
 #endif /* __DCP_HFI_H__ */
 void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
 				  void (*cb)(void *priv, u32 display_id,
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c new file +252 -0

252 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +592 -0

592 lines not on this page · raw message

Signature · 1 line
2.43.0
#26 lore raw

Re: [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Critical] drm/msm/dcp: stack use-after-free on timeout in dcp_hfi_panel_wait() - [High] drm/msm/dcp…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] drm/msm/dcp: stack use-after-free on timeout in dcp_hfi_panel_wait()
- [High] drm/msm/dcp: dcp_hfi_dcs_transfer() returns tx_len instead of rx_len on read
- [Medium] drm/msm/dcp: unsigned bpp in dcp_hfi_panel_init() makes error check dead --

commit 7a46a036c27a241a8f787ac3f2ae566d027d7a46
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add HFI DSI and panel commands

This commit translates DSI host and panel operations into HFI commands, implementing DSI mode validation, display session and power management, panel initialization, and DCS transfer helpers.

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +0 -0
new file mode 100644
index 0000000000000..b27fa8fa31609

[ ... ]

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +5 -0
+static bool dcp_hfi_panel_wait(struct dcp_hfi *hfi,
+			       struct dcp_hfi_completion *comp)
+{
+	unsigned long flags;
+

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 81

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +10 -0
+	if (wait_for_completion_timeout(&comp->done,
+					msecs_to_jiffies(DCP_HFI_TIMEOUT_DISPLAY_MS)))
+		return false;
+
+	/* Serialize removal with response dispatch before the stack object dies. */
+	spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+	dcp_hfi_pending_find(hfi, comp->packet_id);
+	spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+	return true;
+}

[Severity: Critical]
Can dcp_hfi_panel_wait() in drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c cause a stack use-after-free when a timeout races with response completion?

In dcp_hfi_panel_init(), comp1, comp2, and comp3 are allocated on the stack and waited on via dcp_hfi_panel_wait(), which returns -ETIMEDOUT and unwinds the stack frame as soon as dcp_hfi_panel_wait() returns true.

However, in drivers/gpu/drm/msm/dcp/dcp_hfi_core.c, dcp_hfi_resp_callback() drops hfi->resp_drain_lock before unpacking packets and calling dcp_hfi_complete_pending():

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_resp_callback() {
    ...
    spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
    ...
    dcp_hfi_complete_pending(hfi, &pkt_info, status);
}

And dcp_hfi_complete_pending() erases comp from hfi->pending_xa under hfi->pending_lock, drops the lock, and then writes comp->status and calls complete(&comp->done) without holding either resp_drain_lock or pending_lock:

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_complete_pending() {
    comp = dcp_hfi_pending_find(hfi, pkt_info->packet_id);
    if (!comp) {
        ...
        return;
    }
    ...
    comp->status = status;
    complete(&comp->done);
}

If dcp_hfi_complete_pending() erases comp from pending_xa right as wait_for_completion_timeout() expires, dcp_hfi_panel_wait() will acquire the unheld resp_drain_lock, ignore the NULL return from dcp_hfi_pending_find(), and allow dcp_hfi_panel_init() to return while dcp_hfi_complete_pending() is still writing to comp->status and calling complete(&comp->done) on the unwound stack.

If dcp_hfi_pending_find() returns NULL on timeout, should dcp_hfi_panel_wait() call wait_for_completion(&comp->done) to wait for the in-flight completion to finish before returning?

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +9 -0
+
+int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
+		       const struct drm_display_mode *mode,
+		       const struct mipi_dsi_device *dsi,
+		       const struct dcp_dsi_hfi_panel_config *cfg)
+{
+	u32 num_lanes;
+	u32 bpp;
+	bool is_cmd_mode;

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 124

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +7 -0
+	num_lanes = dsi->lanes;
+	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+	if (bpp < 0)
+		return -EINVAL;
+	is_cmd_mode = !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
+	traffic_mode = dcp_dsi_traffic_mode(dsi);
+	link_bpp_x16 = bpp << 4;

[Severity: Medium]
In dcp_hfi_panel_init() in drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c, bpp is declared as u32, whereas mipi_dsi_pixel_format_to_bpp() returns a signed int that can be -EINVAL on an unrecognized pixel format.

Because bpp is unsigned, is the check (bpp < 0) always false? If mipi_dsi_pixel_format_to_bpp() returns -EINVAL, bpp becomes 0xffffffeaU, bypassing the error return, defaulting to HFI_PANEL_BPP_24 in dcp_bpp_to_hfi(), and corrupting link_bpp_x16 and clk_hz. Should bpp be declared as int instead of u32?

[ ... ]

Quoted 23 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +23 -0
+ssize_t dcp_hfi_dcs_transfer(struct dcp_hfi *hfi, u32 session_id,
+			     const struct mipi_dsi_msg *msg,
+			     void *tx_buf, dma_addr_t tx_iova,
+			     void *rx_buf, dma_addr_t rx_iova,
+			     u32 post_wait_ms, u32 ctrl_idx)
+{
+	struct hfi_dsi_cmd_desc cmd_desc = {};
+	static atomic_t seq_no = ATOMIC_INIT(0);
+	int ret;
+
+	if (!msg || !msg->tx_buf || !msg->tx_len)
+		return -EINVAL;
+
+	if (msg->rx_len && (!msg->rx_buf || !rx_buf))
+		return -EINVAL;
+
+	if (msg->tx_len > DCP_DSI_DCS_TX_BUF_SIZE) {
+		dev_err(hfi->dev, "DCS TX too large: %zu > %u\n",
+			msg->tx_len, DCP_DSI_DCS_TX_BUF_SIZE);
+		return -EINVAL;
+	}
+
+	memcpy(tx_buf, msg->tx_buf, msg->tx_len);

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 592

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +5 -0
+	if (msg->rx_len > 0)
+		memcpy(msg->rx_buf, rx_buf, msg->rx_len);
+
+	return msg->tx_len;
+}

[Severity: High]
Should dcp_hfi_dcs_transfer() in drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c return msg->rx_len instead of msg->tx_len when msg->rx_len > 0?

Under the struct mipi_dsi_host_ops.transfer contract in include/drm/drm_mipi_dsi.h, the return value must be the number of bytes transmitted for write packets or the number of bytes received for read packets. Returning msg->tx_len on read transfers causes multi-byte reads through mipi_dsi_dcs_read() or mipi_dsi_generic_read() (where msg->tx_len != msg->rx_len) to appear as short reads to callers checking the received byte count.

Also, at the entry of dcp_hfi_dcs_transfer(), the check (!msg || !msg->tx_buf || !msg->tx_len) rejects 0-parameter generic transfers where msg->tx_len == 0, while hfi and tx_buf are dereferenced without a NULL check. Should this validate hfi and tx_buf and allow 0-length tx when appropriate?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
#27 lore raw

Re: [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands

In reply to Jayant Shekhar
Quoted 9 lines · Jayant Shekhar

Translate DSI host and panel operations into HFI commands. Firmware owns the DSI hardware, so mode validation, display power, panel setup, and DCS transfers must cross the HFI interface instead of accessing controller registers from Linux.

Implement DSI mode validation, display session and power management, panel initialization, and DCS transfer helpers while keeping them separate from generic display commands.

Quoted 8 lines
Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile                |   2 +
 drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h     |  81 ++++
 drivers/gpu/drm/msm/dcp/dcp_hfi.h           |  25 ++
 drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c       | 252 ++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c | 592 ++++++++++++++++++++++++++++

Please stop calling it a panel. There can be any bridge after the DSI device. From the Linux point of view, drm_panel is a subclass of drm_bridge.

Quoted 21 lines
 5 files changed, 952 insertions(+)
drivers/gpu/drm/msm/Makefile +2 -0
index bdc5e3b8efa7..77c268aabca6 100644
@@ -74,6 +74,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_core.o \
 	dcp/dcp_hfi_crtc.o \
+	dcp/dcp_hfi_dsi.o \
+	dcp/dcp_hfi_dsi_panel.o \
 	dcp/dcp_hfi_encoder.o \
 	dcp/dcp_hfi_kms.o \
 	dcp/dcp_hfi_plane.o \
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +0 -0
new file mode 100644
index 000000000000..a8898e469b44

drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h line 5

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +5 -0
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI panel configuration shared by the HFI panel implementation and

What if there is a DSI2DPI or DSI2LVDS bridge and then DPI or LVDS panel?

Quoted 51 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +51 -0
+ * the DRM DSI bridge.
+ */
+
+#ifndef __DCP_DSI_PANEL_H__
+#define __DCP_DSI_PANEL_H__
+
+#include <linux/dma-mapping.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+#define DCP_DSI_MAX_PHY_TIMINGS	32
+
+/**
+ * struct dcp_dsi_hfi_panel_config - Complete HFI PANEL_INIT configuration.
+ *
+ * This contains the bridge-owned panel configuration plus the
+ * mode-dependent values assembled before the HFI PANEL_INIT command.
+ */
+struct dcp_dsi_hfi_panel_config {
+	u32 display_type;
+	u32 num_ctrls;
+	u32 ctrl_indices[4];
+	u32 num_phys;
+	u32 phy_indices[4];
+	u32 vsync_source;
+	u32 te_dcs_command;
+	u32 dma_trigger;
+	u32 stream_trigger;
+	u32 wr_mem_start;
+	u32 wr_mem_continue;
+	u32 dma_sched_line;
+	u32 dma_sched_window;
+	u32 phy_type;
+	bool cphy_mode;
+	bool lp11_init;
+	bool ulps_supported;
+	bool bllp_eof_power_mode;
+	bool bllp_power_mode;
+	u32 lane_map;
+	u32 color_order;
+	u32 esd_mode;
+	u32 jitter_numer;
+	u32 jitter_denom;
+	dma_addr_t dcs_tx_dva;
+	dma_addr_t dcs_tx_iova;
+	u32 dcs_tx_buf_size;
+	u32 phy_timings[DCP_DSI_MAX_PHY_TIMINGS];
+	u32 num_phy_timings;
+	bool dsc_enabled;
+	u32 dsc_version_major;
+	u32 dsc_version_minor;

drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h line 66

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +10 -0
+	u32 dsc_scr_version;
+	u32 dsc_slice_height;
+	u32 dsc_slice_width;
+	u32 dsc_slices_per_pkt;
+	u32 dsc_bits_per_component;
+	u32 dsc_pps_delay_ms;
+	u32 dsc_bits_per_pixel;
+	u32 dsc_chroma_format;
+	u32 dsc_color_space;
+	u32 dsc_block_prediction_enable;

Why do you need to copy those? Can't you use struct drm_dsc directly? Having it as a non-NULL pointer would have saved you from having the dsc_enabled flag.

Quoted 285 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +15 -0
+	/* Optional DSC rate-control override panel configuration. */
+	bool dsc_rc_override;
+	u8 dsc_rc_min_qp[15];
+	u8 dsc_rc_max_qp[15];
+	u8 dsc_rc_bpg_offsets[15];
+	u32 topology_mixer_count;
+	u32 topology_enc_count;
+	u32 topology_intf_count;
+};
+
+/* DCS buffers use IOVAs directly in the firmware command descriptor. */
+#define DCP_DSI_DCS_TX_BUF_SIZE	SZ_4K
+#define DCP_DSI_DCS_RX_BUF_SIZE	SZ_4K
+
+#endif /* __DCP_DSI_PANEL_H__ */
drivers/gpu/drm/msm/dcp/dcp_hfi.h +25 -0
index 6d3eadb5e811..88d73de69383 100644
@@ -242,6 +242,31 @@ int dcp_hfi_send_resource_register(struct dcp_hfi *hfi);
 int dcp_hfi_set_display_property(struct dcp_hfi *hfi, u32 session_id,
 				 u32 prop_id, u32 value);
 
+struct dcp_dsi_hfi_panel_config;
+int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
+		       const struct drm_display_mode *mode,
+		       const struct mipi_dsi_device *dsi,
+		       const struct dcp_dsi_hfi_panel_config *cfg);
+int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx,
+			   u32 display_id, u32 *session_id);
+int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
+				  const struct drm_display_mode *mode,
+				  u64 clk_rate_hz);
+int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
+			     const struct drm_display_mode *mode,
+			     u64 clk_rate_hz);
+int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
+				      u32 packet_id);
+int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id);
+ssize_t dcp_hfi_dcs_transfer(struct dcp_hfi *hfi, u32 session_id,
+			     const struct mipi_dsi_msg *msg,
+			     void *tx_buf, dma_addr_t tx_iova,
+			     void *rx_buf, dma_addr_t rx_iova,
+			     u32 post_wait_ms, u32 ctrl_idx);
 #endif /* __DCP_HFI_H__ */
 void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
 				  void (*cb)(void *priv, u32 display_id,
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c new file +228 -0
new file mode 100644
index 000000000000..fbeec03cf90d
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for the DSI display-session lifecycle.
+ * Panel initialization and DCS transfers are implemented in dcp_hfi_dsi_panel.c.
+ */
+
+#include <linux/device.h>
+#include <drm/drm_modes.h>
+
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_packet.h"
+#include "hfi/hfi_packer.h"
+#include "hfi/hfi_properties_display.h"
+
+static void dcp_hfi_fill_mode_info(struct hfi_display_mode_info *info,
+				   const struct drm_display_mode *mode)
+{
+	memset(info, 0, sizeof(*info));
+	info->size            = sizeof(*info);
+	info->h_active        = mode->hdisplay;
+	info->h_back_porch    = mode->htotal - mode->hsync_end;
+	info->h_sync_width    = mode->hsync_end - mode->hsync_start;
+	info->h_front_porch   = mode->hsync_start - mode->hdisplay;
+	info->h_skew          = mode->hskew;
+	info->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
+	info->v_active        = mode->vdisplay;
+	info->v_back_porch    = mode->vtotal - mode->vsync_end;
+	info->v_sync_width    = mode->vsync_end - mode->vsync_start;
+	info->v_front_porch   = mode->vsync_start - mode->vdisplay;
+	info->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
+	info->refresh_rate    = drm_mode_vrefresh(mode);
+	info->clk_rate_hz_lo = lower_32_bits((u64)mode->clock * 1000);
+	info->clk_rate_hz_hi = upper_32_bits((u64)mode->clock * 1000);
+}
+
+static void dcp_hfi_dump_mode_info(struct dcp_hfi *hfi, const char *cmd,
+				   u32 session_id,
+				   const struct hfi_display_mode_info *info)
+{
+	dev_dbg(hfi->dev,
+		"HFI %s s=%u h=%u/%u/%u/%u v=%u/%u/%u/%u p=%u/%u r=%u clk=%llu sz=%u\n",
+		cmd, session_id,
+		info->h_active, info->h_back_porch, info->h_sync_width,
+		info->h_front_porch,
+		info->v_active, info->v_back_porch, info->v_sync_width,
+		info->v_front_porch,
+		info->h_sync_polarity, info->v_sync_polarity,
+		info->refresh_rate,
+		(u64)info->clk_rate_hz_lo | ((u64)info->clk_rate_hz_hi << 32),
+		info->size);
+}
+
+int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
+				  const struct drm_display_mode *mode, u64 clk_rate_hz)
+{
+	struct hfi_display_mode_info info;
+	int ret;
+
+	dcp_hfi_fill_mode_info(&info, mode);
+	info.clk_rate_hz_lo = lower_32_bits(clk_rate_hz);
+	info.clk_rate_hz_hi = upper_32_bits(clk_rate_hz);
+	dcp_hfi_dump_mode_info(hfi, "HFI MODE_VALIDATE", session_id, &info);
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DISPLAY_MODE_VALIDATE,
+				HFI_CMD_BUFF_DISPLAY, session_id,
+				HFI_PAYLOAD_U32_ARRAY,
+				&info, sizeof(info),
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_MODE_MS);
+
+	return ret;
+}
+
+int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
+			     const struct drm_display_mode *mode, u64 clk_rate_hz)
+{
+	struct hfi_display_mode_info info;
+	int ret;
+
+	dcp_hfi_fill_mode_info(&info, mode);
+	info.clk_rate_hz_lo = lower_32_bits(clk_rate_hz);
+	info.clk_rate_hz_hi = upper_32_bits(clk_rate_hz);
+	dcp_hfi_dump_mode_info(hfi, "HFI SET_MODE", session_id, &info);
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DISPLAY_SET_MODE,
+				HFI_CMD_BUFF_DISPLAY, session_id,
+				HFI_PAYLOAD_U32_ARRAY,
+				&info, sizeof(info),
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_MODE_MS);
+
+	return ret;
+}
+
+int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id)
+{
+	return dcp_hfi_send_sync(hfi,
+				 HFI_COMMAND_DISPLAY_POWER_REGISTER,
+				 HFI_CMD_BUFF_DISPLAY, session_id,
+				 HFI_PAYLOAD_NONE, NULL, 0,
+				 HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				 HFI_TX_FLAGS_NON_DISCARDABLE,
+				 NULL, 0,
+				 DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
+				      u32 packet_id)
+{
+	struct dcp_hfi_buf *buf;
+	struct hfi_cmd_buff_hdl hdl;
+	struct hfi_header_info hdr_info;
+	struct hfi_packet_info pkt_info;
+	int ret;
+
+	buf = dcp_hfi_buf_alloc(hfi);
+	if (!buf)
+		return -ENOMEM;
+
+	hdl.cmd_buffer = buf->data;
+	hdl.size       = DCP_HFI_BUF_SIZE;
+
+	hdr_info.num_packets   = 0;
+	hdr_info.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
+	hdr_info.object_id     = session_id;
+	hdr_info.header_id     = dcp_hfi_next_packet_id(hfi);
+
+	ret = hfi_create_header(&hdl, &hdr_info);
+	if (ret)
+		goto err_free_buf;
+
+	pkt_info.cmd          = HFI_COMMAND_DISPLAY_POWER_CONTROL;
+	pkt_info.id           = session_id & 0xFFFF;
+	pkt_info.flags        = HFI_RX_FLAGS_SUCCESS;
+	pkt_info.packet_id    = packet_id;
+	pkt_info.payload_type = HFI_PAYLOAD_NONE;
+	pkt_info.payload_size = 0;
+	pkt_info.payload_ptr  = NULL;
+
+	ret = hfi_create_packet_header(&hdl, &pkt_info);
+	if (ret)
+		goto err_free_buf;
+
+	return dcp_hfi_send_cmd_async(hfi, buf, hfi_get_header_size(&hdl), NULL);
+
+err_free_buf:
+	dcp_hfi_buf_free(hfi, buf);
+	return ret;
+}
+
+int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx, u32 display_id,
+			   u32 *session_id)
+{
+	int ret;
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DISPLAY_ENABLE,
+				HFI_CMD_BUFF_DISPLAY, display_id,
+				HFI_PAYLOAD_NONE, NULL, 0,
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_DISPLAY_MS);
+	if (ret) {
+		dev_err(hfi->dev,
+			"HFI DISPLAY_ENABLE intf=%u display_id=%u failed: %d\n",
+			intf_idx, display_id, ret);
+		return ret;
+	}
+
+	*session_id = display_id;
+
+	return 0;
+}
+
+int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id)
+{
+	return dcp_hfi_send_sync(hfi,
+				 HFI_COMMAND_DISPLAY_POST_ENABLE,
+				 HFI_CMD_BUFF_DISPLAY, session_id,
+				 HFI_PAYLOAD_NONE, NULL, 0,
+				 HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				 HFI_TX_FLAGS_NON_DISCARDABLE,
+				 NULL, 0,
+				 DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id)
+{
+	return dcp_hfi_send_sync(hfi,
+				 HFI_COMMAND_DISPLAY_DISABLE,
+				 HFI_CMD_BUFF_DISPLAY, session_id,
+				 HFI_PAYLOAD_NONE, NULL, 0,
+				 HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				 HFI_TX_FLAGS_NON_DISCARDABLE,
+				 NULL, 0,
+				 DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id)
+{
+	return dcp_hfi_send_sync(hfi,
+				 HFI_COMMAND_DISPLAY_POST_DISABLE,
+				 HFI_CMD_BUFF_DISPLAY, session_id,
+				 HFI_PAYLOAD_NONE, NULL, 0,
+				 HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				 HFI_TX_FLAGS_NON_DISCARDABLE,
+				 NULL, 0,
+				 DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id)
+{
+	int ret;
+
+	ret = dcp_hfi_display_disable(hfi, session_id);

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c line 238

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c new file +10 -0
+	if (ret) {
+		dev_warn(hfi->dev, "HFI DISPLAY_DISABLE failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = dcp_hfi_display_post_disable(hfi, session_id);
+	if (ret) {
+		dev_warn(hfi->dev, "HFI DISPLAY_POST_DISABLE failed: %d\n", ret);
+		return ret;
+	}

Why are there two types of calls, pre and post? Should they go to the corresponding separate DRM callbacks?

Quoted 66 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c new file +14 -0
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DISPLAY_CLOSE,
+				HFI_CMD_BUFF_DISPLAY, session_id,
+				HFI_PAYLOAD_NONE, NULL, 0,
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_DISPLAY_MS);
+	if (ret)
+		dev_warn(hfi->dev, "HFI DISPLAY_CLOSE failed: %d\n", ret);
+
+	return ret;
+}
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +46 -0
new file mode 100644
index 000000000000..b27fa8fa3160
@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for panel initialization and DCS transfers.
+ * Firmware owns the DSI controller and PHY.
+ */
+
+#include <linux/device.h>
+#include <linux/math64.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_mipi_dsi.h>
+
+#include "dcp_dsi_panel.h"
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_panel.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_panel.h"
+#include "hfi/hfi_packet.h"
+#include "hfi/hfi_packer.h"
+#include "hfi/hfi_properties_panel.h"
+
+#define DCP_DSI_CTRL_CMD_READ              BIT(0)
+#define DCP_DSI_CTRL_CMD_FETCH_MEMORY      BIT(5)
+#define DCP_DSI_CTRL_CMD_LAST_COMMAND      BIT(6)
+
+static u32 dcp_bpp_to_hfi(u32 bpp)
+{
+	switch (bpp) {
+	case 16: return HFI_PANEL_BPP_16;
+	case 18: return HFI_PANEL_BPP_18;
+	case 30: return HFI_PANEL_BPP_30;
+	case 24:
+	default: return HFI_PANEL_BPP_24;
+	}
+}
+
+static u32 dcp_dsi_traffic_mode(const struct mipi_dsi_device *dsi)
+{
+	if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO))
+		return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE;
+	if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+		return HFI_PANEL_TRAFFIC_BURST_MODE;
+	if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 56

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +10 -0
+		return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_PULSE_MODE;
+	return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE;
+}
+
+static u32 dcp_lanes_to_hfi(u32 num_lanes)
+{
+	u32 mask = 0;
+
+	if (num_lanes >= 1)
+		mask |= HFI_PANEL_LANE_0;

There is always at least one lane. Set the mask to HFI_PANEL_LANE_0.

Quoted 41 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +41 -0
+	if (num_lanes >= 2)
+		mask |= HFI_PANEL_LANE_1;
+	if (num_lanes >= 3)
+		mask |= HFI_PANEL_LANE_2;
+	if (num_lanes >= 4)
+		mask |= HFI_PANEL_LANE_3;
+
+	return mask ?: HFI_PANEL_LANE_0;
+}
+
+static bool dcp_hfi_panel_wait(struct dcp_hfi *hfi,
+			       struct dcp_hfi_completion *comp)
+{
+	unsigned long flags;
+
+	if (wait_for_completion_timeout(&comp->done,
+					msecs_to_jiffies(DCP_HFI_TIMEOUT_DISPLAY_MS)))
+		return false;
+
+	/* Serialize removal with response dispatch before the stack object dies. */
+	spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+	dcp_hfi_pending_find(hfi, comp->packet_id);
+	spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+	return true;
+}
+
+int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
+		       const struct drm_display_mode *mode,
+		       const struct mipi_dsi_device *dsi,
+		       const struct dcp_dsi_hfi_panel_config *cfg)
+{
+	u32 num_lanes;
+	u32 bpp;
+	bool is_cmd_mode;
+	u32 display_type;
+	u32 vsync_source;
+	u32 traffic_mode;
+	u32 phy_type;
+
+	struct dcp_hfi_buf *buf1, *buf2;
+	struct hfi_cmd_buff_hdl hdl1, hdl2;

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 107

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +10 -0
+	struct hfi_header_info hdr_info1, hdr_info2;
+	struct hfi_packet_info pkt_info;
+	struct dcp_hfi_completion comp1, comp2, comp3;
+	u32 payload[128];
+	u32 *p;
+	u32 packet_id, packet_id2, packet_id3;
+	u64 clk_hz;
+	int ret;
+	bool timeout1, timeout2, timeout3;
+	u32 link_bpp_x16;

Reverse christmas tree, apply universally to all your code.

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 110

Quoted 3 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +3 -0
+
+	if (!hfi || !mode || !dsi || !dsi->lanes || !cfg)
+		return -EINVAL;

And which of those can actually be passed as NULL?

Quoted 27 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +27 -0
+	if (cfg->num_ctrls > ARRAY_SIZE(cfg->ctrl_indices) ||
+	    cfg->num_phys > ARRAY_SIZE(cfg->phy_indices))
+		return -EINVAL;
+
+	display_type = cfg->display_type;
+	vsync_source = cfg->vsync_source;
+	phy_type = cfg->phy_type;
+	num_lanes = dsi->lanes;
+	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+	if (bpp < 0)
+		return -EINVAL;
+	is_cmd_mode = !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
+	traffic_mode = dcp_dsi_traffic_mode(dsi);
+	link_bpp_x16 = bpp << 4;
+
+	buf1 = dcp_hfi_buf_alloc(hfi);
+	if (!buf1) {
+		dev_err(hfi->dev, "HFI PANEL_INIT: TX buffer exhausted\n");
+		return -ENOMEM;
+	}
+
+	hdl1.cmd_buffer = buf1->data;
+	hdl1.size       = DCP_HFI_BUF_SIZE;
+
+	hdr_info1.num_packets   = 0;
+	hdr_info1.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
+	hdr_info1.object_id     = intf_idx;

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 147

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +10 -0
+	hdr_info1.header_id     = dcp_hfi_next_packet_id(hfi);
+
+	ret = hfi_create_header(&hdl1, &hdr_info1);
+	if (ret) {
+		dev_err(hfi->dev, "PANEL_INIT: hfi_create_header (A) failed: %d\n",
+			ret);
+		goto err_free_buf1;
+	}
+
+	{

Jayant, you really like to ignore comments. Please stop doing that. And also please stop having unconditional nested blocks. If you need one, extract it to a function.

Quoted 23 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +23 -0
+		u32 num_props = 0;
+
+		p = payload + 1;
+		num_props++;
+		p += HFI_PACK(p, HFI_PROPERTY_PANEL_TIMING_MODE_COUNT, 1);
+
+		/* DCS TX buffer is described by both DVA and IOVA properties. */
+		if (cfg->dcs_tx_iova && cfg->dcs_tx_dva &&
+		    !(cfg->dcs_tx_iova & 0x3ff) && !(cfg->dcs_tx_dva & 0x3ff)) {
+			num_props++;
+			p += HFI_PACK(p, HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_DVA,
+				       lower_32_bits(cfg->dcs_tx_dva),
+				       upper_32_bits(cfg->dcs_tx_dva),
+				       cfg->dcs_tx_buf_size, 0, 0);
+			num_props++;
+			p += HFI_PACK(p, HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_IOVA,
+				       lower_32_bits(cfg->dcs_tx_iova),
+				       upper_32_bits(cfg->dcs_tx_iova),
+				       cfg->dcs_tx_buf_size, 0, 0);
+		}
+		payload[0] = num_props;
+	}
+
Signature · 2 lines
With best wishes
Dmitry
#28 lore raw

Re: [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands

Quoted 107 lines · Dmitry Baryshkov
Quoted 9 lines · Jayant Shekhar

Translate DSI host and panel operations into HFI commands. Firmware owns the DSI hardware, so mode validation, display power, panel setup, and DCS transfers must cross the HFI interface instead of accessing controller registers from Linux.

Implement DSI mode validation, display session and power management, panel initialization, and DCS transfer helpers while keeping them separate from generic display commands.

Quoted 8 lines
Assisted-by LLM
---
  drivers/gpu/drm/msm/Makefile                |   2 +
  drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h     |  81 ++++
  drivers/gpu/drm/msm/dcp/dcp_hfi.h           |  25 ++
  drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c       | 252 ++++++++++++
  drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c | 592 ++++++++++++++++++++++++++++

Please stop calling it a panel. There can be any bridge after the DSI device. From the Linux point of view, drm_panel is a subclass of drm_bridge.

Quoted 21 lines
  5 files changed, 952 insertions(+)
drivers/gpu/drm/msm/Makefile +2 -0
index bdc5e3b8efa7..77c268aabca6 100644
@@ -74,6 +74,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
  msm-display-$(CONFIG_DRM_MSM_DCP) += \
  	dcp/dcp_hfi_core.o \
  	dcp/dcp_hfi_crtc.o \
+	dcp/dcp_hfi_dsi.o \
+	dcp/dcp_hfi_dsi_panel.o \
  	dcp/dcp_hfi_encoder.o \
  	dcp/dcp_hfi_kms.o \
  	dcp/dcp_hfi_plane.o \
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +0 -0
new file mode 100644
index 000000000000..a8898e469b44

drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h line 5

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +5 -0
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI panel configuration shared by the HFI panel implementation and

What if there is a DSI2DPI or DSI2LVDS bridge and then DPI or LVDS panel?

Quoted 56 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +56 -0
+ * the DRM DSI bridge.
+ */
+
+#ifndef __DCP_DSI_PANEL_H__
+#define __DCP_DSI_PANEL_H__
+
+#include <linux/dma-mapping.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+#define DCP_DSI_MAX_PHY_TIMINGS	32
+
+/**
+ * struct dcp_dsi_hfi_panel_config - Complete HFI PANEL_INIT configuration.
+ *
+ * This contains the bridge-owned panel configuration plus the
+ * mode-dependent values assembled before the HFI PANEL_INIT command.
+ */
+struct dcp_dsi_hfi_panel_config {
+	u32 display_type;
+	u32 num_ctrls;
+	u32 ctrl_indices[4];
+	u32 num_phys;
+	u32 phy_indices[4];
+	u32 vsync_source;
+	u32 te_dcs_command;
+	u32 dma_trigger;
+	u32 stream_trigger;
+	u32 wr_mem_start;
+	u32 wr_mem_continue;
+	u32 dma_sched_line;
+	u32 dma_sched_window;
+	u32 phy_type;
+	bool cphy_mode;
+	bool lp11_init;
+	bool ulps_supported;
+	bool bllp_eof_power_mode;
+	bool bllp_power_mode;
+	u32 lane_map;
+	u32 color_order;
+	u32 esd_mode;
+	u32 jitter_numer;
+	u32 jitter_denom;
+	dma_addr_t dcs_tx_dva;
+	dma_addr_t dcs_tx_iova;
+	u32 dcs_tx_buf_size;
+	u32 phy_timings[DCP_DSI_MAX_PHY_TIMINGS];
+	u32 num_phy_timings;
+	bool dsc_enabled;
+	u32 dsc_version_major;
+	u32 dsc_version_minor;
+	u32 dsc_scr_version;
+	u32 dsc_slice_height;
+	u32 dsc_slice_width;
+	u32 dsc_slices_per_pkt;
+	u32 dsc_bits_per_component;
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h line 66

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +5 -0
+	u32 dsc_pps_delay_ms;
+	u32 dsc_bits_per_pixel;
+	u32 dsc_chroma_format;
+	u32 dsc_color_space;
+	u32 dsc_block_prediction_enable;

Why do you need to copy those? Can't you use struct drm_dsc directly? Having it as a non-NULL pointer would have saved you from having the dsc_enabled flag.

Will remove in v3.

Quoted 289 lines
Quoted 289 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h new file +15 -0
+	/* Optional DSC rate-control override panel configuration. */
+	bool dsc_rc_override;
+	u8 dsc_rc_min_qp[15];
+	u8 dsc_rc_max_qp[15];
+	u8 dsc_rc_bpg_offsets[15];
+	u32 topology_mixer_count;
+	u32 topology_enc_count;
+	u32 topology_intf_count;
+};
+
+/* DCS buffers use IOVAs directly in the firmware command descriptor. */
+#define DCP_DSI_DCS_TX_BUF_SIZE	SZ_4K
+#define DCP_DSI_DCS_RX_BUF_SIZE	SZ_4K
+
+#endif /* __DCP_DSI_PANEL_H__ */
drivers/gpu/drm/msm/dcp/dcp_hfi.h +25 -0
index 6d3eadb5e811..88d73de69383 100644
@@ -242,6 +242,31 @@ int dcp_hfi_send_resource_register(struct dcp_hfi *hfi);
  int dcp_hfi_set_display_property(struct dcp_hfi *hfi, u32 session_id,
  				 u32 prop_id, u32 value);
 
+struct dcp_dsi_hfi_panel_config;
+int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
+		       const struct drm_display_mode *mode,
+		       const struct mipi_dsi_device *dsi,
+		       const struct dcp_dsi_hfi_panel_config *cfg);
+int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx,
+			   u32 display_id, u32 *session_id);
+int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
+				  const struct drm_display_mode *mode,
+				  u64 clk_rate_hz);
+int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
+			     const struct drm_display_mode *mode,
+			     u64 clk_rate_hz);
+int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
+				      u32 packet_id);
+int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id);
+ssize_t dcp_hfi_dcs_transfer(struct dcp_hfi *hfi, u32 session_id,
+			     const struct mipi_dsi_msg *msg,
+			     void *tx_buf, dma_addr_t tx_iova,
+			     void *rx_buf, dma_addr_t rx_iova,
+			     u32 post_wait_ms, u32 ctrl_idx);
  #endif /* __DCP_HFI_H__ */
  void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
  				  void (*cb)(void *priv, u32 display_id,
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c new file +232 -0
new file mode 100644
index 000000000000..fbeec03cf90d
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for the DSI display-session lifecycle.
+ * Panel initialization and DCS transfers are implemented in dcp_hfi_dsi_panel.c.
+ */
+
+#include <linux/device.h>
+#include <drm/drm_modes.h>
+
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_packet.h"
+#include "hfi/hfi_packer.h"
+#include "hfi/hfi_properties_display.h"
+
+static void dcp_hfi_fill_mode_info(struct hfi_display_mode_info *info,
+				   const struct drm_display_mode *mode)
+{
+	memset(info, 0, sizeof(*info));
+	info->size            = sizeof(*info);
+	info->h_active        = mode->hdisplay;
+	info->h_back_porch    = mode->htotal - mode->hsync_end;
+	info->h_sync_width    = mode->hsync_end - mode->hsync_start;
+	info->h_front_porch   = mode->hsync_start - mode->hdisplay;
+	info->h_skew          = mode->hskew;
+	info->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
+	info->v_active        = mode->vdisplay;
+	info->v_back_porch    = mode->vtotal - mode->vsync_end;
+	info->v_sync_width    = mode->vsync_end - mode->vsync_start;
+	info->v_front_porch   = mode->vsync_start - mode->vdisplay;
+	info->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
+	info->refresh_rate    = drm_mode_vrefresh(mode);
+	info->clk_rate_hz_lo = lower_32_bits((u64)mode->clock * 1000);
+	info->clk_rate_hz_hi = upper_32_bits((u64)mode->clock * 1000);
+}
+
+static void dcp_hfi_dump_mode_info(struct dcp_hfi *hfi, const char *cmd,
+				   u32 session_id,
+				   const struct hfi_display_mode_info *info)
+{
+	dev_dbg(hfi->dev,
+		"HFI %s s=%u h=%u/%u/%u/%u v=%u/%u/%u/%u p=%u/%u r=%u clk=%llu sz=%u\n",
+		cmd, session_id,
+		info->h_active, info->h_back_porch, info->h_sync_width,
+		info->h_front_porch,
+		info->v_active, info->v_back_porch, info->v_sync_width,
+		info->v_front_porch,
+		info->h_sync_polarity, info->v_sync_polarity,
+		info->refresh_rate,
+		(u64)info->clk_rate_hz_lo | ((u64)info->clk_rate_hz_hi << 32),
+		info->size);
+}
+
+int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
+				  const struct drm_display_mode *mode, u64 clk_rate_hz)
+{
+	struct hfi_display_mode_info info;
+	int ret;
+
+	dcp_hfi_fill_mode_info(&info, mode);
+	info.clk_rate_hz_lo = lower_32_bits(clk_rate_hz);
+	info.clk_rate_hz_hi = upper_32_bits(clk_rate_hz);
+	dcp_hfi_dump_mode_info(hfi, "HFI MODE_VALIDATE", session_id, &info);
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DISPLAY_MODE_VALIDATE,
+				HFI_CMD_BUFF_DISPLAY, session_id,
+				HFI_PAYLOAD_U32_ARRAY,
+				&info, sizeof(info),
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_MODE_MS);
+
+	return ret;
+}
+
+int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
+			     const struct drm_display_mode *mode, u64 clk_rate_hz)
+{
+	struct hfi_display_mode_info info;
+	int ret;
+
+	dcp_hfi_fill_mode_info(&info, mode);
+	info.clk_rate_hz_lo = lower_32_bits(clk_rate_hz);
+	info.clk_rate_hz_hi = upper_32_bits(clk_rate_hz);
+	dcp_hfi_dump_mode_info(hfi, "HFI SET_MODE", session_id, &info);
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DISPLAY_SET_MODE,
+				HFI_CMD_BUFF_DISPLAY, session_id,
+				HFI_PAYLOAD_U32_ARRAY,
+				&info, sizeof(info),
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_MODE_MS);
+
+	return ret;
+}
+
+int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id)
+{
+	return dcp_hfi_send_sync(hfi,
+				 HFI_COMMAND_DISPLAY_POWER_REGISTER,
+				 HFI_CMD_BUFF_DISPLAY, session_id,
+				 HFI_PAYLOAD_NONE, NULL, 0,
+				 HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				 HFI_TX_FLAGS_NON_DISCARDABLE,
+				 NULL, 0,
+				 DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
+				      u32 packet_id)
+{
+	struct dcp_hfi_buf *buf;
+	struct hfi_cmd_buff_hdl hdl;
+	struct hfi_header_info hdr_info;
+	struct hfi_packet_info pkt_info;
+	int ret;
+
+	buf = dcp_hfi_buf_alloc(hfi);
+	if (!buf)
+		return -ENOMEM;
+
+	hdl.cmd_buffer = buf->data;
+	hdl.size       = DCP_HFI_BUF_SIZE;
+
+	hdr_info.num_packets   = 0;
+	hdr_info.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
+	hdr_info.object_id     = session_id;
+	hdr_info.header_id     = dcp_hfi_next_packet_id(hfi);
+
+	ret = hfi_create_header(&hdl, &hdr_info);
+	if (ret)
+		goto err_free_buf;
+
+	pkt_info.cmd          = HFI_COMMAND_DISPLAY_POWER_CONTROL;
+	pkt_info.id           = session_id & 0xFFFF;
+	pkt_info.flags        = HFI_RX_FLAGS_SUCCESS;
+	pkt_info.packet_id    = packet_id;
+	pkt_info.payload_type = HFI_PAYLOAD_NONE;
+	pkt_info.payload_size = 0;
+	pkt_info.payload_ptr  = NULL;
+
+	ret = hfi_create_packet_header(&hdl, &pkt_info);
+	if (ret)
+		goto err_free_buf;
+
+	return dcp_hfi_send_cmd_async(hfi, buf, hfi_get_header_size(&hdl), NULL);
+
+err_free_buf:
+	dcp_hfi_buf_free(hfi, buf);
+	return ret;
+}
+
+int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx, u32 display_id,
+			   u32 *session_id)
+{
+	int ret;
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DISPLAY_ENABLE,
+				HFI_CMD_BUFF_DISPLAY, display_id,
+				HFI_PAYLOAD_NONE, NULL, 0,
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_DISPLAY_MS);
+	if (ret) {
+		dev_err(hfi->dev,
+			"HFI DISPLAY_ENABLE intf=%u display_id=%u failed: %d\n",
+			intf_idx, display_id, ret);
+		return ret;
+	}
+
+	*session_id = display_id;
+
+	return 0;
+}
+
+int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id)
+{
+	return dcp_hfi_send_sync(hfi,
+				 HFI_COMMAND_DISPLAY_POST_ENABLE,
+				 HFI_CMD_BUFF_DISPLAY, session_id,
+				 HFI_PAYLOAD_NONE, NULL, 0,
+				 HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				 HFI_TX_FLAGS_NON_DISCARDABLE,
+				 NULL, 0,
+				 DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id)
+{
+	return dcp_hfi_send_sync(hfi,
+				 HFI_COMMAND_DISPLAY_DISABLE,
+				 HFI_CMD_BUFF_DISPLAY, session_id,
+				 HFI_PAYLOAD_NONE, NULL, 0,
+				 HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				 HFI_TX_FLAGS_NON_DISCARDABLE,
+				 NULL, 0,
+				 DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id)
+{
+	return dcp_hfi_send_sync(hfi,
+				 HFI_COMMAND_DISPLAY_POST_DISABLE,
+				 HFI_CMD_BUFF_DISPLAY, session_id,
+				 HFI_PAYLOAD_NONE, NULL, 0,
+				 HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				 HFI_TX_FLAGS_NON_DISCARDABLE,
+				 NULL, 0,
+				 DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id)
+{
+	int ret;
+
+	ret = dcp_hfi_display_disable(hfi, session_id);
+	if (ret) {
+		dev_warn(hfi->dev, "HFI DISPLAY_DISABLE failed: %d\n", ret);
+		return ret;
+	}
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c line 238

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c new file +6 -0
+
+	ret = dcp_hfi_display_post_disable(hfi, session_id);
+	if (ret) {
+		dev_warn(hfi->dev, "HFI DISPLAY_POST_DISABLE failed: %d\n", ret);
+		return ret;
+	}

Why are there two types of calls, pre and post? Should they go to the corresponding separate DRM callbacks?

This display close function would be removed, its redundant.

Quoted 168 lines
Quoted 66 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c new file +14 -0
+
+	ret = dcp_hfi_send_sync(hfi,
+				HFI_COMMAND_DISPLAY_CLOSE,
+				HFI_CMD_BUFF_DISPLAY, session_id,
+				HFI_PAYLOAD_NONE, NULL, 0,
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				DCP_HFI_TIMEOUT_DISPLAY_MS);
+	if (ret)
+		dev_warn(hfi->dev, "HFI DISPLAY_CLOSE failed: %d\n", ret);
+
+	return ret;
+}
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +46 -0
new file mode 100644
index 000000000000..b27fa8fa3160
@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for panel initialization and DCS transfers.
+ * Firmware owns the DSI controller and PHY.
+ */
+
+#include <linux/device.h>
+#include <linux/math64.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_mipi_dsi.h>
+
+#include "dcp_dsi_panel.h"
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_panel.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_panel.h"
+#include "hfi/hfi_packet.h"
+#include "hfi/hfi_packer.h"
+#include "hfi/hfi_properties_panel.h"
+
+#define DCP_DSI_CTRL_CMD_READ              BIT(0)
+#define DCP_DSI_CTRL_CMD_FETCH_MEMORY      BIT(5)
+#define DCP_DSI_CTRL_CMD_LAST_COMMAND      BIT(6)
+
+static u32 dcp_bpp_to_hfi(u32 bpp)
+{
+	switch (bpp) {
+	case 16: return HFI_PANEL_BPP_16;
+	case 18: return HFI_PANEL_BPP_18;
+	case 30: return HFI_PANEL_BPP_30;
+	case 24:
+	default: return HFI_PANEL_BPP_24;
+	}
+}
+
+static u32 dcp_dsi_traffic_mode(const struct mipi_dsi_device *dsi)
+{
+	if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO))
+		return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE;
+	if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+		return HFI_PANEL_TRAFFIC_BURST_MODE;
+	if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 56

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +10 -0
+		return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_PULSE_MODE;
+	return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE;
+}
+
+static u32 dcp_lanes_to_hfi(u32 num_lanes)
+{
+	u32 mask = 0;
+
+	if (num_lanes >= 1)
+		mask |= HFI_PANEL_LANE_0;

There is always at least one lane. Set the mask to HFI_PANEL_LANE_0.

Quoted 41 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +41 -0
+	if (num_lanes >= 2)
+		mask |= HFI_PANEL_LANE_1;
+	if (num_lanes >= 3)
+		mask |= HFI_PANEL_LANE_2;
+	if (num_lanes >= 4)
+		mask |= HFI_PANEL_LANE_3;
+
+	return mask ?: HFI_PANEL_LANE_0;
+}
+
+static bool dcp_hfi_panel_wait(struct dcp_hfi *hfi,
+			       struct dcp_hfi_completion *comp)
+{
+	unsigned long flags;
+
+	if (wait_for_completion_timeout(&comp->done,
+					msecs_to_jiffies(DCP_HFI_TIMEOUT_DISPLAY_MS)))
+		return false;
+
+	/* Serialize removal with response dispatch before the stack object dies. */
+	spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+	dcp_hfi_pending_find(hfi, comp->packet_id);
+	spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+	return true;
+}
+
+int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
+		       const struct drm_display_mode *mode,
+		       const struct mipi_dsi_device *dsi,
+		       const struct dcp_dsi_hfi_panel_config *cfg)
+{
+	u32 num_lanes;
+	u32 bpp;
+	bool is_cmd_mode;
+	u32 display_type;
+	u32 vsync_source;
+	u32 traffic_mode;
+	u32 phy_type;
+
+	struct dcp_hfi_buf *buf1, *buf2;
+	struct hfi_cmd_buff_hdl hdl1, hdl2;

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 107

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +10 -0
+	struct hfi_header_info hdr_info1, hdr_info2;
+	struct hfi_packet_info pkt_info;
+	struct dcp_hfi_completion comp1, comp2, comp3;
+	u32 payload[128];
+	u32 *p;
+	u32 packet_id, packet_id2, packet_id3;
+	u64 clk_hz;
+	int ret;
+	bool timeout1, timeout2, timeout3;
+	u32 link_bpp_x16;

Reverse christmas tree, apply universally to all your code.

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 110

Quoted 3 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +3 -0
+
+	if (!hfi || !mode || !dsi || !dsi->lanes || !cfg)
+		return -EINVAL;

And which of those can actually be passed as NULL?

Quoted 32 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +32 -0
+	if (cfg->num_ctrls > ARRAY_SIZE(cfg->ctrl_indices) ||
+	    cfg->num_phys > ARRAY_SIZE(cfg->phy_indices))
+		return -EINVAL;
+
+	display_type = cfg->display_type;
+	vsync_source = cfg->vsync_source;
+	phy_type = cfg->phy_type;
+	num_lanes = dsi->lanes;
+	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+	if (bpp < 0)
+		return -EINVAL;
+	is_cmd_mode = !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
+	traffic_mode = dcp_dsi_traffic_mode(dsi);
+	link_bpp_x16 = bpp << 4;
+
+	buf1 = dcp_hfi_buf_alloc(hfi);
+	if (!buf1) {
+		dev_err(hfi->dev, "HFI PANEL_INIT: TX buffer exhausted\n");
+		return -ENOMEM;
+	}
+
+	hdl1.cmd_buffer = buf1->data;
+	hdl1.size       = DCP_HFI_BUF_SIZE;
+
+	hdr_info1.num_packets   = 0;
+	hdr_info1.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
+	hdr_info1.object_id     = intf_idx;
+	hdr_info1.header_id     = dcp_hfi_next_packet_id(hfi);
+
+	ret = hfi_create_header(&hdl1, &hdr_info1);
+	if (ret) {
+		dev_err(hfi->dev, "PANEL_INIT: hfi_create_header (A) failed: %d\n",
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c line 147

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +5 -0
+			ret);
+		goto err_free_buf1;
+	}
+
+	{

Jayant, you really like to ignore comments. Please stop doing that. And also please stop having unconditional nested blocks. If you need one, extract it to a function.

Sorry about that, probably missed at one place, had addressed all the other places.

All other comments including this to be addressed in v3.

Quoted 23 lines
Quoted 23 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c new file +23 -0
+		u32 num_props = 0;
+
+		p = payload + 1;
+		num_props++;
+		p += HFI_PACK(p, HFI_PROPERTY_PANEL_TIMING_MODE_COUNT, 1);
+
+		/* DCS TX buffer is described by both DVA and IOVA properties. */
+		if (cfg->dcs_tx_iova && cfg->dcs_tx_dva &&
+		    !(cfg->dcs_tx_iova & 0x3ff) && !(cfg->dcs_tx_dva & 0x3ff)) {
+			num_props++;
+			p += HFI_PACK(p, HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_DVA,
+				       lower_32_bits(cfg->dcs_tx_dva),
+				       upper_32_bits(cfg->dcs_tx_dva),
+				       cfg->dcs_tx_buf_size, 0, 0);
+			num_props++;
+			p += HFI_PACK(p, HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_IOVA,
+				       lower_32_bits(cfg->dcs_tx_iova),
+				       upper_32_bits(cfg->dcs_tx_iova),
+				       cfg->dcs_tx_buf_size, 0, 0);
+		}
+		payload[0] = num_props;
+	}
+
#29 lore raw

PATCH v2 11/23 drm/msm/dcp: Add DCP CRTCs and shared plane state

In reply to Jayant Shekhar

Create the DRM CRTC objects and shared state needed to expose the firmware-owned DCP pipeline through MSM DRM. Without CRTC state, DRM cannot track firmware sessions, aggregate bandwidth, or validate the display configuration.

Implement CRTC objects, display lookup and session state, CRTC bandwidth aggregation, and the shared plane state used by validation and plane programming.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile              |   2 +
 drivers/gpu/drm/msm/dcp/dcp_crtc.c        | 725 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_crtc.h        | 104 +++++
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c     |  21 +
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h     |  54 +++
 drivers/gpu/drm/msm/dcp/dcp_plane_state.h |  37 ++
 6 files changed, 943 insertions(+)
drivers/gpu/drm/msm/Makefile +2 -0
index 77c268aabca6..80341ce07d53 100644
@@ -72,6 +72,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 	disp/mdp5/mdp5_smp.o \
 
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
+	dcp/dcp_crtc.o \
+	dcp/dcp_dpu_kms.o \
 	dcp/dcp_hfi_core.o \
 	dcp/dcp_hfi_crtc.o \
 	dcp/dcp_hfi_dsi.o \
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +725 -0

725 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +104 -0

104 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c new file +21 -0
new file mode 100644
index 000000000000..44b1c2200aee
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP KMS driver.
+ */
+
+#include <drm/drm_crtc.h>
+
+#include "dcp_dpu_kms.h"
+
+struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
+				      u32 display_id)
+{
+	unsigned int i;
+
+	for (i = 0; i < dcp_kms->num_crtcs; i++)
+		if (dcp_kms->crtcs[i].display_id == display_id)
+			return dcp_kms->crtcs[i].crtc;
+
+	return NULL;
+}
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h new file +54 -0

54 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_plane_state.h new file +37 -0
new file mode 100644
index 000000000000..aafa93cb69b9
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP plane state definitions.
+ */
+
+#ifndef __DCP_PLANE_STATE_H__
+#define __DCP_PLANE_STATE_H__
+
+#include <drm/drm_plane.h>
+
+#define DCP_PLANE_PROP_PKT_MAX_SIZE	512
+
+struct dcp_plane_state {
+	struct drm_plane_state base;
+
+	u64 fetch_bw;
+	u64 clk;
+
+	u8     prop_pkt[DCP_PLANE_PROP_PKT_MAX_SIZE];
+	size_t prop_pkt_size;
+	u32 prop_pkt_id;
+
+	bool needs_dirtyfb;
+
+	u32 src_img_w;
+	u32 src_img_h;
+
+	u32 hfi_pipe_id;
+	u32 hfi_format;
+};
+
+#define to_dcp_plane_state(x) \
+	container_of(x, struct dcp_plane_state, base)
+
+#endif /* __DCP_PLANE_STATE_H__ */
Signature · 1 line
2.43.0
#30 lore raw

Re: [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state

Quoted 11 lines · Jayant Shekhar

Create the DRM CRTC objects and shared state needed to expose the firmware-owned DCP pipeline through MSM DRM. Without CRTC state, DRM cannot track firmware sessions, aggregate bandwidth, or validate the display configuration.

Implement CRTC objects, display lookup and session state, CRTC bandwidth aggregation, and the shared plane state used by validation and plane programming.

Assisted-by LLM
Quoted 7 lines
---
 drivers/gpu/drm/msm/Makefile              |   2 +
 drivers/gpu/drm/msm/dcp/dcp_crtc.c        | 725 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_crtc.h        | 104 +++++
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c     |  21 +
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h     |  54 +++
 drivers/gpu/drm/msm/dcp/dcp_plane_state.h |  37 ++

If you need plane state for the CRTCs, then put planes support before adding CRTCs.

Quoted 117 lines
 6 files changed, 943 insertions(+)
drivers/gpu/drm/msm/Makefile +2 -0
index 77c268aabca6..80341ce07d53 100644
@@ -72,6 +72,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 	disp/mdp5/mdp5_smp.o \
 
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
+	dcp/dcp_crtc.o \
+	dcp/dcp_dpu_kms.o \
 	dcp/dcp_hfi_core.o \
 	dcp/dcp_hfi_crtc.o \
 	dcp/dcp_hfi_dsi.o \
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +96 -0
new file mode 100644
index 000000000000..ec4f74c2920f
@@ -0,0 +1,725 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP CRTC implementation.
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_rect.h>
+#include <drm/drm_vblank.h>
+
+#include <drm/drm_managed.h>
+
+#include "msm_drv.h"
+
+#include "dcp_crtc.h"
+#include "dcp_drv.h"
+#include "dcp_hfi.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_plane_state.h"
+
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_properties_display.h"
+
+#define DCP_FRAME_DONE_TIMEOUT_MS	500
+
+static struct dcp_dpu_kms *dcp_crtc_get_kms(struct drm_crtc *crtc)
+{
+	struct msm_drm_private *priv = crtc->dev->dev_private;
+
+	return to_dcp_dpu_kms(priv->kms);
+}
+
+static struct drm_crtc_state *
+dcp_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
+{
+	struct dcp_crtc_state *cstate;
+
+	if (WARN_ON(!crtc->state))
+		return NULL;
+
+	cstate = kzalloc_obj(*cstate, GFP_KERNEL);
+	if (!cstate)
+		return NULL;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &cstate->base);
+
+	cstate->topology_mixer_count =
+		to_dcp_crtc_state(crtc->state)->topology_mixer_count;
+	cstate->topology_bits_per_component =
+		to_dcp_crtc_state(crtc->state)->topology_bits_per_component;
+	memcpy(cstate->mixer_roi, to_dcp_crtc_state(crtc->state)->mixer_roi,
+	       sizeof(cstate->mixer_roi));
+	cstate->update_roi = to_dcp_crtc_state(crtc->state)->update_roi;
+	return &cstate->base;
+}
+
+static void dcp_crtc_atomic_destroy_state(struct drm_crtc *crtc,
+					  struct drm_crtc_state *state)
+{
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(state);
+
+	__drm_atomic_helper_crtc_destroy_state(state);
+	kfree(cstate);
+}
+
+static struct drm_crtc_state *
+dcp_crtc_atomic_create_state(struct drm_crtc *crtc)
+{
+	struct dcp_crtc_state *cstate;
+
+	cstate = kzalloc_obj(*cstate, GFP_KERNEL);
+	if (!cstate)
+		return ERR_PTR(-ENOMEM);
+
+	__drm_atomic_helper_crtc_state_init(&cstate->base, crtc);
+	cstate->topology_mixer_count = 1;
+	cstate->topology_bits_per_component = 8;
+	return &cstate->base;
+}
+
+static void dcp_crtc_aggregate_bw(struct drm_crtc_state *crtc_state,
+				  bool enable)
+{
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc_state);
+	struct drm_plane *plane;
+	const struct drm_plane_state *pstate;
+
+	cstate->dram_ib  = 0;

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 106

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+	cstate->dram_ab  = 0;
+	cstate->llcc_ib  = 0;
+	cstate->llcc_ab  = 0;
+	cstate->core_clk = 0;
+
+	if (!enable)
+		return;
+
+	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+		if (!pstate->visible || !pstate->fb)

Can it be visible if there is no fb?

Quoted 28 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +28 -0
+			continue;
+
+		const struct dcp_plane_state *dcp_pstate = to_dcp_plane_state(pstate);
+
+		cstate->dram_ib  = max(cstate->dram_ib, dcp_pstate->fetch_bw);
+		cstate->dram_ab += dcp_pstate->fetch_bw;
+		cstate->llcc_ib  = max(cstate->llcc_ib, dcp_pstate->fetch_bw);
+		cstate->llcc_ab += dcp_pstate->fetch_bw;
+		cstate->core_clk = max(cstate->core_clk, dcp_pstate->clk);
+	}
+}
+
+static enum drm_mode_status
+dcp_crtc_mode_valid(struct drm_crtc *crtc,
+		    const struct drm_display_mode *mode)
+{
+	struct dcp_dpu_kms *kms = dcp_crtc_get_kms(crtc);
+	u32 max_width, max_height;
+
+	dcp_mode_limits(mode, kms->caps->max_width, kms->caps->max_height,
+			&max_width, &max_height);
+
+	return drm_mode_validate_size(mode, max_width, max_height);
+}
+
+static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
+				  struct drm_atomic_commit *state)
+{

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 144

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	u32 mixer_count;
+	u32 session_id;
+	u32 i;
+	int ret;
+
+	if (!crtc->state->active)
+		return;

Can't you just use DRM_PLANE_COMMIT_ACTIVE_ONLY?

Quoted 55 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +55 -0
+	if (!dcp_kms->commit_pm_active) {
+		cstate->hfi_error = -EIO;
+		return;
+	}
+
+	if (!dcp_crtc_get_session(crtc, &session_id)) {
+		cstate->hfi_error = -ENODEV;
+		return;
+	}
+
+	if (cstate->core_clk > 0 || cstate->dram_ib > 0) {
+		ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id,
+					  cstate->dram_ib,  cstate->dram_ab,
+					  cstate->llcc_ib,  cstate->llcc_ab,
+					  cstate->core_clk);
+		if (ret) {
+			cstate->hfi_error = ret;
+			return;
+		}
+	}
+
+	mixer_count = cstate->topology_mixer_count;
+	if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
+		cstate->hfi_error = -EINVAL;
+		return;
+	}
+	for (i = 0; i < mixer_count; i++) {
+		struct dcp_mixer_roi *roi = &cstate->mixer_roi[i];
+
+		if (!roi->w || !roi->h) {
+			drm_err(crtc->dev, "CRTC%u: invalid zero-sized LM%u ROI\n",
+				dcp_crtc->display_id, i);
+			cstate->hfi_error = -EINVAL;
+			return;
+		}
+
+		ret = dcp_hfi_crtc_set_resource(dcp_kms->hfi, session_id,
+						i, roi->w, roi->h, roi->x, roi->y);
+		if (ret) {
+			cstate->hfi_error = ret;
+			return;
+		}
+	}
+
+	ret = dcp_hfi_crtc_set_dest_roi(dcp_kms->hfi, session_id,
+					cstate->update_roi.x, cstate->update_roi.y,
+					cstate->update_roi.w, cstate->update_roi.h);
+	if (ret) {
+		cstate->hfi_error = ret;
+		return;
+	}
+}
+
+static void dcp_crtc_atomic_flush(struct drm_crtc *crtc,
+				  struct drm_atomic_commit *state)

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 209

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	unsigned long flags;
+	u32 session_id;
+	int ret;
+
+	if (!crtc->state->enable)
+		return;

So, atomic_being happens only for active CRTCs, but atomic_flush also happens for enabled, but not active CRTCs. Why?

Quoted 33 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +33 -0
+	drm_dbg_driver(crtc->dev, "DCP_FLUSH CRTC%u mask=0x%x err=%d\n",
+		       dcp_crtc->display_id, crtc->state->plane_mask,
+		       cstate->hfi_error);
+
+	if (!dcp_crtc_get_session(crtc, &session_id)) {
+		drm_dbg_driver(crtc->dev,
+			       "CRTC%u: atomic flush without HFI session\n",
+			       dcp_crtc->display_id);
+		goto flush_done;
+	}
+
+	if (cstate->hfi_error)
+		goto flush_done;
+
+	ret = dcp_hfi_crtc_set_scan_sequence_id(dcp_kms->hfi, session_id,
+						++dcp_crtc->commit_seq_id);
+	if (ret)
+		goto flush_error;
+
+	ret = dcp_hfi_crtc_set_autorefresh(dcp_kms->hfi,
+					   session_id, true, 1);
+	if (ret)
+		goto flush_error;
+
+	goto flush_done;
+
+flush_error:
+	cstate->hfi_error = ret;
+
+flush_done:
+	spin_lock_irqsave(&crtc->dev->event_lock, flags);
+	dcp_crtc->event = crtc->state->event;
+	crtc->state->event = NULL;

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 252

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+}
+
+void dcp_crtc_set_session(struct drm_crtc *crtc, u32 session_id)
+{
+	struct dcp_crtc *dcp_crtc;
+	unsigned long flags;
+
+	if (!crtc)
+		return;

Why?

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +5 -0
+
+	dcp_crtc = to_dcp_crtc(crtc);
+	spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+	dcp_crtc->session_id = session_id;
+	dcp_crtc->session_valid = true;

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 267

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+	spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
+}
+
+void dcp_crtc_clear_session(struct drm_crtc *crtc)
+{
+	struct dcp_crtc *dcp_crtc;
+	unsigned long flags;
+
+	if (!crtc)
+		return;

Why?

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +6 -0
+
+	dcp_crtc = to_dcp_crtc(crtc);
+	spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+	dcp_crtc->session_valid = false;
+	dcp_crtc->session_id = 0;
+	spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 283

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+}
+
+bool dcp_crtc_get_session(struct drm_crtc *crtc, u32 *session_id)
+{
+	struct dcp_crtc *dcp_crtc;
+	unsigned long flags;
+	bool valid;
+
+	if (!crtc)
+		return false;

Why?

Quoted 95 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +95 -0
+
+	dcp_crtc = to_dcp_crtc(crtc);
+	spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+	valid = dcp_crtc->session_valid;
+	if (valid && session_id)
+		*session_id = dcp_crtc->session_id;
+	spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
+
+	return valid;
+}
+
+void dcp_crtc_set_topology_state(struct drm_crtc_state *state,
+				 u32 mixer_count,
+				 u32 bits_per_component)
+{
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(state);
+
+	cstate->topology_mixer_count = mixer_count;
+	cstate->topology_bits_per_component = bits_per_component;
+}
+
+void dcp_crtc_commit_kickoff(struct drm_crtc *crtc)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	u32 session_id;
+	int ret;
+
+	if (cstate->hfi_error) {
+		drm_err(crtc->dev,
+			"DCP_TRIGGER CRTC%u suppressed after HFI error %d\n",
+			dcp_crtc->display_id, cstate->hfi_error);
+		return;
+	}
+
+	if (!dcp_crtc_get_session(crtc, &session_id)) {
+		drm_warn(crtc->dev,
+			 "CRTC%u: no active session, skipping FRAME_TRIGGER\n",
+			 dcp_crtc->display_id);
+		return;
+	}
+
+	/* MSM serializes commits, so only one frame completion may be pending. */
+	if (atomic_cmpxchg(&dcp_crtc->pending_kickoff_cnt, 0, 1)) {
+		drm_err(crtc->dev,
+			"CRTC%u: frame completion already pending\n",
+			dcp_crtc->display_id);
+		cstate->hfi_error = -EBUSY;
+		return;
+	}
+	dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;
+
+	ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
+	if (ret) {
+		drm_err(crtc->dev,
+			"HFI commit failed for CRTC%u (session=%u): %d\n",
+			dcp_crtc->display_id, session_id, ret);
+		atomic_dec(&dcp_crtc->pending_kickoff_cnt);
+		wake_up(&dcp_crtc->frame_done_wq);
+	}
+}
+
+void dcp_crtc_wait_for_commit_done(struct drm_crtc *crtc)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	unsigned long timeout = msecs_to_jiffies(DCP_FRAME_DONE_TIMEOUT_MS);
+
+	if (!atomic_read(&dcp_crtc->pending_kickoff_cnt))
+		return;
+
+	if (!wait_event_timeout(dcp_crtc->frame_done_wq,
+				atomic_read(&dcp_crtc->pending_kickoff_cnt) == 0,
+				timeout)) {
+		drm_err(crtc->dev,
+			"frame done timeout for CRTC%u\n",
+			dcp_crtc->display_id);
+		atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
+	}
+}
+
+void dcp_crtc_complete_commit(struct drm_crtc *crtc)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	unsigned long flags;
+
+	if (!crtc->state->active)
+		dcp_crtc_aggregate_bw(crtc->state, false);
+
+	spin_lock_irqsave(&crtc->dev->event_lock, flags);
+	if (dcp_crtc->event) {
+		drm_crtc_send_vblank_event(crtc, dcp_crtc->event);
+		dcp_crtc->event = NULL;
+	}
+	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 388

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+}
+
+int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	u32 session_id;
+	int ret;
+
+	if (!dcp_kms->hfi || !dcp_crtc_get_session(crtc, &session_id))

Can HFI be NULL here?

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 400

Quoted 12 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +12 -0
+		return -ENODEV;
+
+	if (enable)
+		ret = dcp_hfi_display_event_register_async(dcp_kms->hfi,
+							   session_id, HFI_EVENT_VSYNC);
+	else
+		ret = dcp_hfi_display_event_deregister_async(dcp_kms->hfi,
+							     session_id, HFI_EVENT_VSYNC);
+	if (ret)
+		return ret;
+
+	WRITE_ONCE(dcp_crtc->vblank_enabled, enable);

Why do you need vblank_enabled in addition to the
drm_vblank_crtc::enabled?

Quoted 24 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +24 -0
+	return 0;
+}
+
+void dcp_crtc_vblank_callback(struct drm_crtc *crtc)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+	if (!READ_ONCE(dcp_crtc->vblank_enabled))
+		return;
+
+	if (ktime_compare(dcp_crtc->vblank_cb_time, ktime_set(0, 0)) == 0)
+		dcp_crtc->vblank_cb_time = ktime_get();
+	else
+		dcp_crtc->vblank_cb_count++;
+	drm_crtc_handle_vblank(crtc);
+}
+
+void dcp_crtc_frame_done_callback(struct drm_crtc *crtc)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+	atomic_add_unless(&dcp_crtc->pending_kickoff_cnt, -1, 0);
+	wake_up(&dcp_crtc->frame_done_wq);
+}

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 434

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+
+static struct drm_crtc *dcp_crtc_find_by_session_id(struct dcp_drv *drv,
+						    u32 session_id)
+{
+	struct dcp_dpu_kms *dcp_kms;
+	struct drm_crtc *crtc;
+	u32 active_session_id;
+
+	if (!drv || !drv->kms)
+		return NULL;

Why?

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 438

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +4 -0
+
+	dcp_kms = drv->kms;
+
+	crtc = dcp_dpu_kms_get_crtc(dcp_kms, session_id);

Hmm, why are you passing session_id here even though the function accepts display_id?

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +5 -0
+	if (!crtc || !dcp_crtc_get_session(crtc, &active_session_id) ||
+	    active_session_id != session_id)
+		return NULL;
+
+	return crtc;

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 453

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+}
+
+void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id)
+{
+	struct drm_device *dev = drv && drv->kms ? drv->kms->dev : NULL;
+	struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
+
+	if (crtc)
+		dcp_crtc_vblank_callback(crtc);
+	else if (dev)

Can dev be NULL here?

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 464

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +11 -0
+		drm_err_ratelimited(dev,
+				    "unknown CRTC for HFI VSYNC session %u\n",
+				    session_id);
+}
+
+void dcp_crtc_hfi_frame_done_cb(struct dcp_drv *drv, u32 session_id, u32 sequence_id)
+{
+	struct drm_device *dev = drv && drv->kms ? drv->kms->dev : NULL;
+	struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
+
+	if (crtc) {

Why? Also, if the !crtc can actually happen, handle it as something non-standard:

if (!crtc) {
	drm_err_ratelimited(...);
	return;
}

struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
...

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 475

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +11 -0
+		struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+		/* A single pending commit makes equality valid across sequence wrap. */
+		if (sequence_id != dcp_crtc->pending_seq_id) {
+			drm_warn(crtc->dev,
+				 "CRTC%u: ignoring stale FRAME_SCAN_COMPLETE seq=%u expected=%u\n",
+				 dcp_crtc->display_id, sequence_id,
+				 dcp_crtc->pending_seq_id);
+			return;
+		}
+		dcp_crtc_frame_done_callback(crtc);

Can't you inline it here?

Quoted 51 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +51 -0
+	} else if (dev)
+		drm_err_ratelimited(dev,
+				    "unknown CRTC for HFI frame-done session %u seq=%u\n",
+				    session_id, sequence_id);
+}
+
+static int dcp_crtc_setup_mixer_rois(struct drm_crtc *crtc,
+				     struct drm_crtc_state *crtc_state,
+				     struct dcp_crtc_state *cstate)
+{
+	const struct drm_plane_state *pstate;
+	struct drm_plane *plane;
+	const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+	u32 mixer_count = cstate->topology_mixer_count;
+	u32 mixer_width = mode->hdisplay / mixer_count;
+	u32 i;
+
+	if (!mixer_width)
+		return -EINVAL;
+
+	cstate->update_roi.x = 0;
+	cstate->update_roi.y = 0;
+	cstate->update_roi.w = mode->hdisplay;
+	cstate->update_roi.h = mode->vdisplay;
+	for (i = 0; i < mixer_count; i++) {
+		cstate->mixer_roi[i].x = (mode->hdisplay * i) / mixer_count;
+		cstate->mixer_roi[i].y = 0;
+		cstate->mixer_roi[i].w = (mode->hdisplay * (i + 1)) / mixer_count -
+			cstate->mixer_roi[i].x;
+		cstate->mixer_roi[i].h = mode->vdisplay;
+		drm_dbg_driver(crtc->dev,
+			       "DCP_CHECK CRTC%u LM%u roi=(%u,%u %ux%u)\n",
+				to_dcp_crtc(crtc)->display_id, i,
+				cstate->mixer_roi[i].x, cstate->mixer_roi[i].y,
+				cstate->mixer_roi[i].w, cstate->mixer_roi[i].h);
+	}
+
+	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+		bool fullscreen;
+		bool assigned = false;
+		u32 mixer_idx;
+
+		if (!pstate || !pstate->visible || !pstate->fb)
+			continue;
+
+		for (mixer_idx = 0; mixer_idx < mixer_count; mixer_idx++) {
+			struct dcp_mixer_roi *mixer = &cstate->mixer_roi[mixer_idx];
+
+			if (pstate->crtc_x >= mixer->x &&
+			    pstate->crtc_x + pstate->crtc_w <= mixer->x + mixer->w) {
+				assigned = true;

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 536

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+				break;
+			}
+		}
+
+		/* Any plane may cover the complete split display. Plane type is
+		 * not a resource-selection mechanism in the universal-plane API.
+		 */
+		fullscreen = pstate->crtc_x == 0 && pstate->crtc_y == 0 &&
+			pstate->crtc_w == mode->hdisplay &&
+			pstate->crtc_h == mode->vdisplay;

Why do we need to special-handle the fullscreen planes?

Quoted 30 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +30 -0
+
+		if (!assigned && !fullscreen && mixer_count > 1) {
+			/*
+			 * Split-pipe/source-split and multirect allocation are
+			 * deferred until the atomic pipe resource manager is added.
+			 * Reject planes needing multiple mixer rectangles for now.
+			 */
+			return -EOPNOTSUPP;
+		}
+	}
+
+	return 0;
+}
+
+static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
+				 struct drm_atomic_commit *state)
+{
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	struct drm_crtc_state *crtc_state =
+		drm_atomic_get_new_crtc_state(state, crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc_state);
+	struct drm_plane *plane;
+	const struct drm_plane_state *pstate;
+	u32 max_width, max_height;
+	u32 mixer_count;
+	int num_planes = 0;
+	int ret;
+
+	drm_dbg_atomic(crtc->dev, "CRTC%u: atomic_check enable=%d active=%d mode=%s\n",
+		       crtc->base.id, crtc_state->enable, crtc_state->active,

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 576

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+		       crtc_state->mode.name[0] ? crtc_state->mode.name : "none");
+
+	if (!crtc_state->enable || !crtc_state->active)
+		return 0;
+
+	mixer_count = cstate->topology_mixer_count;
+	if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
+		drm_err(crtc->dev,
+			"CRTC%u: invalid mixer count %u (limit 1..%u)\n",
+				crtc->base.id, mixer_count, DCP_MAX_MIXERS);

No, don't let userspace spam the kernel log.

Quoted 17 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +17 -0
+		return -EINVAL;
+	}
+
+	ret = dcp_crtc_setup_mixer_rois(crtc, crtc_state, cstate);
+	if (ret)
+		return ret;
+
+	dcp_mode_limits(&crtc_state->adjusted_mode,
+			dcp_kms->caps->max_width, dcp_kms->caps->max_height,
+			&max_width, &max_height);
+
+	if (crtc_state->adjusted_mode.hdisplay > max_width ||
+	    crtc_state->adjusted_mode.vdisplay > max_height) {
+		drm_err(crtc->dev, "CRTC%u: mode %dx%d exceeds DCP caps %dx%d\n",
+			crtc->base.id,
+			crtc_state->adjusted_mode.hdisplay,
+			crtc_state->adjusted_mode.vdisplay,

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 603

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+			dcp_kms->caps->max_width,
+			dcp_kms->caps->max_height);
+		return -E2BIG;
+	}
+
+	/* Limit visible planes to the firmware blend-stage count. */
+	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+		if (!pstate || !pstate->visible)
+			continue;
+		num_planes++;

I think I commented here. Can't you use normalized zpos in plane's atomic_check instead?

Quoted 149 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +122 -0
+	}
+
+	if (num_planes > DCP_MAX_BLEND_STAGES) {
+		drm_err(crtc->dev, "CRTC%u: %d planes exceeds max blend stages %d\n",
+			crtc->base.id, num_planes,
+			DCP_MAX_BLEND_STAGES);
+		return -EINVAL;
+	}
+
+	dcp_crtc_aggregate_bw(crtc_state, true);
+
+	return 0;
+}
+
+static u32 dcp_crtc_get_vblank_counter(struct drm_crtc *crtc)
+{
+	return drm_crtc_vblank_count(crtc);
+}
+
+static void dcp_crtc_atomic_enable(struct drm_crtc *crtc,
+				   struct drm_atomic_commit *state)
+{
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	u32 session_id;
+	int ret;
+
+	if (!dcp_crtc_get_session(crtc, &session_id))
+		return;
+
+	ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
+					  HFI_MODE_DPMS_ON);
+	if (ret) {
+		drm_err(crtc->dev, "CRTC%u: DPMS ON failed: %d\n",
+			to_dcp_crtc(crtc)->display_id, ret);
+		cstate->hfi_error = ret;
+	}
+
+	drm_crtc_vblank_on(crtc);
+}
+
+static void dcp_crtc_atomic_disable(struct drm_crtc *crtc,
+				    struct drm_atomic_commit *state)
+{
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	u32 session_id;
+	int ret;
+
+	if (dcp_crtc_get_session(crtc, &session_id)) {
+		ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id, 0, 0, 0, 0, 0);
+		if (ret) {
+			drm_err(crtc->dev,
+				"CRTC%u: clearing bandwidth vote failed: %d\n",
+				to_dcp_crtc(crtc)->display_id, ret);
+			cstate->hfi_error = ret;
+		}
+
+		ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
+						  HFI_MODE_DPMS_OFF);
+		if (ret) {
+			drm_err(crtc->dev, "CRTC%u: DPMS OFF failed: %d\n",
+				to_dcp_crtc(crtc)->display_id, ret);
+			cstate->hfi_error = ret;
+		}
+	}
+
+	drm_crtc_vblank_off(crtc);
+}
+
+static const struct drm_crtc_funcs dcp_crtc_funcs = {
+	.set_config             = drm_atomic_helper_set_config,
+	.page_flip              = drm_atomic_helper_page_flip,
+	.atomic_create_state    = dcp_crtc_atomic_create_state,
+	.atomic_duplicate_state = dcp_crtc_atomic_duplicate_state,
+	.atomic_destroy_state   = dcp_crtc_atomic_destroy_state,
+	.enable_vblank          = msm_crtc_enable_vblank,
+	.disable_vblank         = msm_crtc_disable_vblank,
+	.get_vblank_counter     = dcp_crtc_get_vblank_counter,
+};
+
+static const struct drm_crtc_helper_funcs dcp_crtc_helper_funcs = {
+	.atomic_check         = dcp_crtc_atomic_check,
+	.atomic_enable        = dcp_crtc_atomic_enable,
+	.atomic_disable       = dcp_crtc_atomic_disable,
+	.atomic_begin         = dcp_crtc_atomic_begin,
+	.atomic_flush         = dcp_crtc_atomic_flush,
+	.mode_valid           = dcp_crtc_mode_valid,
+};
+
+struct drm_crtc *dcp_crtc_init(struct dcp_dpu_kms *kms,
+			       struct drm_plane *primary_plane,
+			       u32 display_id)
+{
+	struct drm_device *dev = kms->dev;
+	struct dcp_crtc *dcp_crtc;
+	int ret;
+
+	dcp_crtc = drmm_kzalloc(dev, sizeof(*dcp_crtc), GFP_KERNEL);
+	if (!dcp_crtc)
+		return ERR_PTR(-ENOMEM);
+
+	dcp_crtc->display_id = display_id;
+
+	spin_lock_init(&dcp_crtc->spin_lock);
+	init_waitqueue_head(&dcp_crtc->frame_done_wq);
+	atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
+
+	ret = drmm_crtc_init_with_planes(dev, &dcp_crtc->base,
+					 primary_plane, NULL,
+					 &dcp_crtc_funcs,
+					 NULL);
+	if (ret) {
+		drm_err(dev, "CRTC%u init failed: %d\n", display_id, ret);
+		return ERR_PTR(ret);
+	}
+
+	drm_crtc_helper_add(&dcp_crtc->base, &dcp_crtc_helper_funcs);
+
+	drm_dbg_driver(dev, "CRTC%u initialized\n", display_id);
+	return &dcp_crtc->base;
+}
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +21 -0
new file mode 100644
index 000000000000..ae7046e8d97a
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP CRTC implementation.
+ */
+
+#ifndef __DCP_CRTC_H__
+#define __DCP_CRTC_H__
+
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_crtc.h>
+
+struct dcp_dpu_kms;
+struct drm_plane;
+
+#define DCP_MAX_MIXERS 6
+#define DCP_MAX_BLEND_STAGES 12
+

drivers/gpu/drm/msm/dcp/dcp_crtc.h line 31

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +10 -0
+static inline void dcp_mode_limits(const struct drm_display_mode *mode,
+				   u32 cap_width, u32 cap_height,
+				   u32 *max_width, u32 *max_height)
+{
+	*max_width = cap_width;
+	*max_height = cap_height;
+
+	if (mode->hdisplay < mode->vdisplay && cap_width > cap_height) {
+		*max_width = cap_height;
+		*max_height = cap_width;

Why? I guess, the caps interface is just incorrect. We have two
limitations: the maximum width and the maximum clock / bandwdith. maximum height doesn't seem to make any sense here.

drivers/gpu/drm/msm/dcp/dcp_crtc.h line 40

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +9 -0
+	}
+}
+
+struct dcp_mixer_roi {
+	u32 x;
+	u32 y;
+	u32 w;
+	u32 h;
+};

Is this an HFI or an internal value? If it's the latter, please use drm_rect.

drivers/gpu/drm/msm/dcp/dcp_crtc.h line 46

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +6 -0
+
+struct dcp_crtc {
+	struct drm_crtc base;
+	u32 display_id;
+	u32 session_id;
+	bool session_valid;

Why do we need a separate flag here? When is the session being set and when is it removed?

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +11 -0
+	struct drm_pending_vblank_event *event;
+	wait_queue_head_t frame_done_wq;
+	atomic_t          pending_kickoff_cnt;
+	/* Protects CRTC-local vblank and commit state. */
+	spinlock_t        spin_lock;
+	u32     vblank_cb_count;
+	bool    vblank_enabled;
+	ktime_t vblank_cb_time;
+	bool    enabled;
+	u32 commit_seq_id;
+	u32 pending_seq_id;

drivers/gpu/drm/msm/dcp/dcp_crtc.h line 68

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +10 -0
+};
+
+struct dcp_crtc_state {
+	struct drm_crtc_state base;
+
+	u32 topology_mixer_count;
+	u32 topology_bits_per_component;
+	struct dcp_mixer_roi mixer_roi[DCP_MAX_MIXERS];
+
+	struct dcp_mixer_roi update_roi;

It's set exactly to the display mode. Can we drop it?

drivers/gpu/drm/msm/dcp/dcp_crtc.h line 69

Quoted 1 line
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +1 -0
+	int hfi_error;

Why is it a part of the state? What does it mean if we got an HFI error? What is a recovery model? Is it fine to restart the session? Or is the whole device wedged?

Quoted 12 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +12 -0
+	u64 bw_ctl;
+
+	u64 dram_ib;
+	u64 dram_ab;
+	u64 llcc_ib;
+	u64 llcc_ab;
+	u64 core_clk;
+};
+
+#define to_dcp_crtc(x)       container_of(x, struct dcp_crtc, base)
+#define to_dcp_crtc_state(x) container_of(x, struct dcp_crtc_state, base)
+

drivers/gpu/drm/msm/dcp/dcp_crtc.h line 91

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +10 -0
+struct drm_crtc *dcp_crtc_init(struct dcp_dpu_kms *kms,
+			       struct drm_plane *primary_plane,
+			       u32 display_id);
+
+void dcp_crtc_commit_kickoff(struct drm_crtc *crtc);
+void dcp_crtc_wait_for_commit_done(struct drm_crtc *crtc);
+void dcp_crtc_complete_commit(struct drm_crtc *crtc);
+int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable);
+void dcp_crtc_vblank_callback(struct drm_crtc *crtc);
+void dcp_crtc_frame_done_callback(struct drm_crtc *crtc);

Are those used outside of dcp_crtc.c? If not, drop all the prototypes and definitions which are local to the module. Reorder the functions as required to remove the need for the forward declarations, where possible.

Quoted 146 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.h new file +12 -0
+struct dcp_drv;
+void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id);
+void dcp_crtc_hfi_frame_done_cb(struct dcp_drv *drv, u32 session_id, u32 sequence_id);
+
+void dcp_crtc_set_session(struct drm_crtc *crtc, u32 session_id);
+void dcp_crtc_clear_session(struct drm_crtc *crtc);
+bool dcp_crtc_get_session(struct drm_crtc *crtc, u32 *session_id);
+void dcp_crtc_set_topology_state(struct drm_crtc_state *state,
+				 u32 mixer_count,
+				 u32 bits_per_component);
+
+#endif /* __DCP_CRTC_H__ */
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c new file +21 -0
new file mode 100644
index 000000000000..44b1c2200aee
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP KMS driver.
+ */
+
+#include <drm/drm_crtc.h>
+
+#include "dcp_dpu_kms.h"
+
+struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
+				      u32 display_id)
+{
+	unsigned int i;
+
+	for (i = 0; i < dcp_kms->num_crtcs; i++)
+		if (dcp_kms->crtcs[i].display_id == display_id)
+			return dcp_kms->crtcs[i].crtc;
+
+	return NULL;
+}
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h new file +54 -0
new file mode 100644
index 000000000000..f8010fc5d32f
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP KMS state.
+ */
+
+#ifndef __DCP_DPU_KMS_H__
+#define __DCP_DPU_KMS_H__
+
+#include <linux/spinlock.h>
+
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+struct dcp_hfi;
+struct dcp_caps;
+struct dcp_dsi_bridge;
+struct dcp_drv;
+struct drm_crtc;
+
+#define DCP_DPU_AUTOSUSPEND_DELAY_MS	5000
+#define DCP_DPU_MAX_CRTCS	4
+#define DCP_DPU_MAX_PLANES	12
+
+struct dcp_dpu_kms {
+	struct msm_kms		 base;
+	struct drm_device	*dev;
+	struct device		*pm_dev;
+	struct dcp_drv		*drv;
+	struct dcp_hfi		*hfi;
+	struct dcp_caps		*caps;
+	struct device_node	*dpus_np;
+	struct device		*iommu_dev;
+	bool			 rpm_enabled;
+	bool			 commit_pm_active;
+	struct dcp_dsi_bridge	*dsi_bridge[2];
+	struct {
+		struct drm_crtc *crtc;
+		u32 display_id;
+	} crtcs[DCP_DPU_MAX_CRTCS];
+	unsigned int num_crtcs;
+};
+
+#define to_dcp_dpu_kms(x) container_of(x, struct dcp_dpu_kms, base)
+struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
+				      u32 display_id);
+int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
+				     struct drm_crtc *crtc);
+
+int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms);
+extern const struct msm_kms_funcs dcp_dpu_kms_funcs;
+int dcp_kms_init(struct drm_device *ddev);
+
+#endif /* __DCP_DPU_KMS_H__ */
drivers/gpu/drm/msm/dcp/dcp_plane_state.h new file +37 -0
new file mode 100644
index 000000000000..aafa93cb69b9
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP plane state definitions.
+ */
+
+#ifndef __DCP_PLANE_STATE_H__
+#define __DCP_PLANE_STATE_H__
+
+#include <drm/drm_plane.h>
+
+#define DCP_PLANE_PROP_PKT_MAX_SIZE	512
+
+struct dcp_plane_state {
+	struct drm_plane_state base;
+
+	u64 fetch_bw;
+	u64 clk;
+
+	u8     prop_pkt[DCP_PLANE_PROP_PKT_MAX_SIZE];
+	size_t prop_pkt_size;
+	u32 prop_pkt_id;
+
+	bool needs_dirtyfb;
+
+	u32 src_img_w;
+	u32 src_img_h;
+
+	u32 hfi_pipe_id;
+	u32 hfi_format;
+};
+
+#define to_dcp_plane_state(x) \
+	container_of(x, struct dcp_plane_state, base)
+
+#endif /* __DCP_PLANE_STATE_H__ */
Signature · 2 lines
-- 
2.43.0
Signature · 2 lines
With best wishes
Dmitry
#31 lore raw

Re: [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state

In reply to Jayant Shekhar
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider: - [High] drm/msm/dcp: unconsumed crtc event on disable in dcp_crtc_atomic_flush() - [High] drm/msm/dcp…

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] drm/msm/dcp: unconsumed crtc event on disable in dcp_crtc_atomic_flush()
- [High] drm/msm/dcp: software vblank count returned in dcp_crtc_get_vblank_counter()
- [Medium] drm/msm/dcp: signed crtc_x promotion in dcp_crtc_setup_mixer_rois()
- [Medium] drm/msm/dcp: pending_kickoff_cnt underflow in dcp_crtc_commit_kickoff()
- [Medium] drm/msm/dcp: skipped drm_crtc_vblank_on() in dcp_crtc_atomic_enable()
- [Medium] drm/msm/dcp: stale bandwidth vote when planes hide in dcp_crtc_atomic_begin()
- [Medium] drm/msm/dcp: vblank_enabled race and stale state in dcp_crtc_vblank() --

commit 5e8f3961384fe1603b59d4d7ed8e2050307e4116
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add DCP CRTCs and shared plane state

This commit adds DRM CRTC objects, session tracking, bandwidth aggregation, and shared plane state definitions for the firmware-owned DCP display pipeline in MSM DRM.

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +0 -0
new file mode 100644
index 0000000000000..ec4f74c2920f8

[ ... ]

Quoted 23 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +23 -0
+static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
+				  struct drm_atomic_commit *state)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	u32 mixer_count;
+	u32 session_id;
+	u32 i;
+	int ret;
+
+	if (!crtc->state->active)
+		return;
+	if (!dcp_kms->commit_pm_active) {
+		cstate->hfi_error = -EIO;
+		return;
+	}
+
+	if (!dcp_crtc_get_session(crtc, &session_id)) {
+		cstate->hfi_error = -ENODEV;
+		return;
+	}
+

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 164

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+	if (cstate->core_clk > 0 || cstate->dram_ib > 0) {
+		ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id,
+					  cstate->dram_ib,  cstate->dram_ab,
+					  cstate->llcc_ib,  cstate->llcc_ab,
+					  cstate->core_clk);
+		if (ret) {
+			cstate->hfi_error = ret;
+			return;
+		}
+	}

[Severity: Medium]
What happens in dcp_crtc_atomic_begin() when an active CRTC that previously had visible planes is updated so that all planes are disabled or clipped off-screen while the CRTC remains active?

In dcp_crtc_atomic_check(), dcp_crtc_aggregate_bw() resets dram_ib, dram_ab, llcc_ib, llcc_ab, and core_clk to 0. Because dcp_crtc_atomic_begin() guards the call to dcp_hfi_crtc_set_bw() with (cstate->core_clk > 0 || cstate->dram_ib > 0), the zeroed bandwidth values are skipped and the previous non-zero DRAM/LLCC bandwidth and core clock votes remain pinned in firmware.

Should dcp_hfi_crtc_set_bw() also be called when the aggregated bandwidth drops to zero on an active CRTC?

[ ... ]

Quoted 12 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +12 -0
+static void dcp_crtc_atomic_flush(struct drm_crtc *crtc,
+				  struct drm_atomic_commit *state)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	unsigned long flags;
+	u32 session_id;
+	int ret;
+
+	if (!crtc->state->enable)
+		return;

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 244

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +6 -0
+flush_done:
+	spin_lock_irqsave(&crtc->dev->event_lock, flags);
+	dcp_crtc->event = crtc->state->event;
+	crtc->state->event = NULL;
+	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+}

[Severity: High]
Does disabling an active CRTC leave crtc->state->event unconsumed?

In drm_atomic_helper_setup_commit(), whenever old_crtc_state->active is true (including when disabling an active CRTC with new_crtc_state->enable == false and new_crtc_state->active == false), DRM core populates new_crtc_state->event to signal commit->flip_done and takes a commit reference via drm_crtc_commit_get().

During msm_atomic_commit_tail():

drm_atomic_helper_commit_modeset_disables()

    -> dcp_crtc_atomic_disable()
  drm_atomic_helper_commit_planes()
    -> dcp_crtc_atomic_flush()
  dcp_dpu_kms_complete_commit()
    -> dcp_crtc_complete_commit()

drm_atomic_helper_commit_hw_done()

Unlike dpu_crtc_disable(), mdp5_crtc_atomic_disable(), and mdp4_crtc_atomic_disable(), dcp_crtc_atomic_disable() does not send and clear crtc->state->event. Then dcp_crtc_atomic_flush() returns early when !crtc->state->enable before transferring crtc->state->event to dcp_crtc->event, so dcp_crtc_complete_commit() sees dcp_crtc->event == NULL.

Wouldn't this trigger WARN_ON(new_crtc_state->event) in drm_atomic_helper_commit_hw_done(), leave commit->flip_done unsignaled (stalling subsequent commits in drm_atomic_helper_wait_for_dependencies()), and leak the drm_pending_vblank_event and drm_crtc_commit reference? Also, if an earlier event was still in dcp_crtc->event during dcp_crtc_atomic_flush(), could unconditionally assigning dcp_crtc->event = crtc->state->event overwrite it?

[ ... ]

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +8 -0
+void dcp_crtc_commit_kickoff(struct drm_crtc *crtc)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	u32 session_id;
+	int ret;
+

[ ... ]

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +9 -0
+	/* MSM serializes commits, so only one frame completion may be pending. */
+	if (atomic_cmpxchg(&dcp_crtc->pending_kickoff_cnt, 0, 1)) {
+		drm_err(crtc->dev,
+			"CRTC%u: frame completion already pending\n",
+			dcp_crtc->display_id);
+		cstate->hfi_error = -EBUSY;
+		return;
+	}
+	dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 345

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+
+	ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
+	if (ret) {
+		drm_err(crtc->dev,
+			"HFI commit failed for CRTC%u (session=%u): %d\n",
+			dcp_crtc->display_id, session_id, ret);
+		atomic_dec(&dcp_crtc->pending_kickoff_cnt);
+		wake_up(&dcp_crtc->frame_done_wq);
+	}
+}

[Severity: Medium]
Can dcp_crtc->pending_kickoff_cnt underflow to -1 if dcp_crtc_hfi_frame_done_cb() races with dcp_crtc_commit_kickoff()?

In dcp_crtc_commit_kickoff(), pending_kickoff_cnt is set to 1 via atomic_cmpxchg() before dcp_crtc->pending_seq_id is updated, and then dcp_hfi_display_commit() waits synchronously for the HFI response.

If a late HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE from a prior timed-out commit arrives before pending_seq_id is written, or if the frame scan complete event for the current commit arrives and runs dcp_crtc_frame_done_callback() (decrementing pending_kickoff_cnt from 1 to 0 via atomic_add_unless(&dcp_crtc->pending_kickoff_cnt, -1, 0)) before dcp_hfi_display_commit() returns an error, the error path executes atomic_dec(&dcp_crtc->pending_kickoff_cnt) and drives pending_kickoff_cnt to -1.

Once pending_kickoff_cnt is -1, dcp_crtc_wait_for_commit_done() will wait for pending_kickoff_cnt == 0 until the 500ms timeout expires, and any further dcp_crtc_frame_done_callback() calls will continue decrementing the negative counter because atomic_add_unless(..., -1, 0) only stops at 0.

Should dcp_crtc->pending_seq_id be written before setting pending_kickoff_cnt, and should the error path use atomic_set(..., 0) or atomic_add_unless(..., -1, 0) instead of atomic_dec()?

[ ... ]

Quoted 19 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +19 -0
+int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	u32 session_id;
+	int ret;
+
+	if (!dcp_kms->hfi || !dcp_crtc_get_session(crtc, &session_id))
+		return -ENODEV;
+
+	if (enable)
+		ret = dcp_hfi_display_event_register_async(dcp_kms->hfi,
+							   session_id, HFI_EVENT_VSYNC);
+	else
+		ret = dcp_hfi_display_event_deregister_async(dcp_kms->hfi,
+							     session_id, HFI_EVENT_VSYNC);
+	if (ret)
+		return ret;
+

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 409

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+	WRITE_ONCE(dcp_crtc->vblank_enabled, enable);
+	return 0;
+}
+
+void dcp_crtc_vblank_callback(struct drm_crtc *crtc)
+{
+	struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+	if (!READ_ONCE(dcp_crtc->vblank_enabled))
+		return;

[Severity: Medium]
Could updating dcp_crtc->vblank_enabled at the end of dcp_crtc_vblank() drop initial VSYNC events or leave vblank_enabled stuck at true after disable?

When enable is true, dcp_hfi_display_event_register_async() queues and kicks the HFI command before WRITE_ONCE(dcp_crtc->vblank_enabled, true) runs. If a VSYNC event arrives on the response virtqueue before WRITE_ONCE() executes, dcp_crtc_vblank_callback() sees vblank_enabled == false and drops it.

Conversely, when enable is false, msm_crtc_disable_vblank() runs vblank_ctrl_worker asynchronously. If the CRTC session has already been cleared via dcp_crtc_clear_session() by the time dcp_crtc_vblank(crtc, false) runs (or if dcp_hfi_display_event_deregister_async() fails), dcp_crtc_vblank() returns early without clearing dcp_crtc->vblank_enabled.

[ ... ]

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +4 -0
+static int dcp_crtc_setup_mixer_rois(struct drm_crtc *crtc,
+				     struct drm_crtc_state *crtc_state,
+				     struct dcp_crtc_state *cstate)
+{

[ ... ]

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +7 -0
+	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+		bool fullscreen;
+		bool assigned = false;
+		u32 mixer_idx;
+
+		if (!pstate || !pstate->visible || !pstate->fb)
+			continue;

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 529

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+
+		for (mixer_idx = 0; mixer_idx < mixer_count; mixer_idx++) {
+			struct dcp_mixer_roi *mixer = &cstate->mixer_roi[mixer_idx];
+
+			if (pstate->crtc_x >= mixer->x &&
+			    pstate->crtc_x + pstate->crtc_w <= mixer->x + mixer->w) {
+				assigned = true;
+				break;
+			}
+		}

[Severity: Medium]
In dcp_crtc_setup_mixer_rois(), pstate->crtc_x is a signed int32_t while mixer->x, mixer->w, and pstate->crtc_w are unsigned u32. What happens when a visible plane has a negative crtc_x?

Because C integer promotion converts pstate->crtc_x to u32 in both comparisons:

pstate->crtc_x >= mixer->x
pstate->crtc_x + pstate->crtc_w <= mixer->x + mixer->w

a negative pstate->crtc_x becomes a large unsigned value (>= 0x80000000) that is always >= mixer->x, while (u32)pstate->crtc_x + pstate->crtc_w wraps around to the plane's positive right edge. On a multi-mixer topology (mixer_count > 1), a plane starting at crtc_x < 0 and extending across the mixer boundary into mixer 1 will satisfy both checks for mixer 1 and set assigned = true instead of returning -EOPNOTSUPP.

In addition, should this check use the clipped destination rectangle (pstate->dst.x1 and pstate->dst.x2) rather than unclipped pstate->crtc_x and pstate->crtc_w so that planes partially clipped at the display edges are validated against the active mixer ROIs?

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 621

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +4 -0
+static u32 dcp_crtc_get_vblank_counter(struct drm_crtc *crtc)
+{
+	return drm_crtc_vblank_count(crtc);
+}

[Severity: High]
Is it valid for dcp_crtc_get_vblank_counter() to return drm_crtc_vblank_count(crtc) as the drm_crtc_funcs.get_vblank_counter callback?

drm_crtc_funcs.get_vblank_counter is called by __get_vblank_counter() in drivers/gpu/drm/drm_vblank.c to read a raw hardware frame counter, whereas drm_crtc_vblank_count() returns DRM core's own cooked software counter (vblank->count).

During drm_crtc_vblank_on() -> drm_reset_vblank_timestamp() (and during drm_crtc_handle_vblank()), store_vblank() sets vblank->last to cur_vblank (N) and increments vblank->count to N + 1. On the next non-IRQ call to drm_update_vblank_count(dev, pipe, false) (such as drm_vblank_enable() in drm_crtc_vblank_get() or drm_vblank_disable_and_save() in drm_crtc_vblank_off()), __get_vblank_counter() returns N + 1 while vblank->last is N:

  if (max_vblank_count == 0)
      drm_WARN_ON_ONCE(dev, cur_vblank != vblank->last);

When max_vblank_count is 0, this immediately triggers drm_WARN_ON_ONCE(); when max_vblank_count is non-zero, it spuriously increments the software vblank counter on every vblank enable and disable.

If DCP does not expose a hardware frame counter register, shouldn't get_vblank_counter be omitted (left NULL in dcp_crtc_funcs)?

Quoted 12 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +12 -0
+
+static void dcp_crtc_atomic_enable(struct drm_crtc *crtc,
+				   struct drm_atomic_commit *state)
+{
+	struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+	struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+	u32 session_id;
+	int ret;
+
+	if (!dcp_crtc_get_session(crtc, &session_id))
+		return;
+

drivers/gpu/drm/msm/dcp/dcp_crtc.c line 643

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c new file +10 -0
+	ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
+					  HFI_MODE_DPMS_ON);
+	if (ret) {
+		drm_err(crtc->dev, "CRTC%u: DPMS ON failed: %d\n",
+			to_dcp_crtc(crtc)->display_id, ret);
+		cstate->hfi_error = ret;
+	}
+
+	drm_crtc_vblank_on(crtc);
+}

[Severity: Medium]
If dcp_crtc_get_session() returns false in dcp_crtc_atomic_enable(), does returning early before drm_crtc_vblank_on(crtc) unbalance the vblank refcount?

When a CRTC is initialized or disabled via drm_crtc_vblank_off(), vblank->inmodeset is 1 and vblank->refcount is 1. Later in msm_atomic_commit_tail(), vblank_get() checks crtc->state->active (which is true) and calls drm_crtc_vblank_get(crtc); because drm_crtc_vblank_on() was skipped, vblank->inmodeset is still 1, so drm_crtc_vblank_get() fails with -EINVAL without incrementing refcount, and then vblank_put() unconditionally calls drm_crtc_vblank_put(crtc), decrementing vblank->refcount from 1 to 0. In addition, dcp_crtc_atomic_disable() unconditionally calls drm_crtc_vblank_off(crtc) regardless of whether a session is present.

Should drm_crtc_vblank_on(crtc) always be called in dcp_crtc_atomic_enable() even if dcp_crtc_get_session() fails?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
#32 lore raw

PATCH v2 12/23 drm/msm/dcp: Add DCP planes

In reply to Jayant Shekhar

Expose DCP overlay planes through the DRM plane API. Userspace needs normal atomic plane state while the firmware-owned pipeline requires validated formats, assigned pipes, mapped buffers, bandwidth votes, and HFI property packets.

Implement plane validation, format and scaling checks, firmware pipe assignment, framebuffer mapping, bandwidth calculation, atomic state handling, and HFI updates and disables.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile        |    1 +
 drivers/gpu/drm/msm/dcp/dcp_plane.c | 1056 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_plane.h |   36 ++
 3 files changed, 1093 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index 80341ce07d53..b425fefda890 100644
@@ -74,6 +74,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
 msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_crtc.o \
 	dcp/dcp_dpu_kms.o \
+	dcp/dcp_plane.o \
 	dcp/dcp_hfi_core.o \
 	dcp/dcp_hfi_crtc.o \
 	dcp/dcp_hfi_dsi.o \
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +1056 -0

1,056 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_plane.h new file +36 -0
new file mode 100644
index 000000000000..377a156c046a
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP plane implementation and API.
+ */
+
+#ifndef __DCP_PLANE_H__
+#define __DCP_PLANE_H__
+
+#include "dcp_plane_state.h"
+
+struct dcp_dpu_kms;
+struct drm_display_mode;
+
+struct dcp_plane_perf {
+	u64 fetch_bw;
+	u64 clk;
+};
+
+struct dcp_plane {
+	struct drm_plane base;
+	u32 hfi_pipe_id;
+};
+
+#define to_dcp_plane(x) container_of(x, struct dcp_plane, base)
+
+void dcp_plane_calc_perf(const struct drm_plane_state *state,
+			 const struct drm_display_mode *mode,
+			 struct dcp_plane_perf *perf);
+struct drm_plane *dcp_plane_init(struct drm_device *dev,
+				 u32 hfi_pipe_id,
+				 enum drm_plane_type type,
+				 unsigned long possible_crtcs);
+
+#endif /* __DCP_PLANE_H__ */
Signature · 1 line
2.43.0
#33 lore raw

Re: [PATCH v2 12/23] drm/msm/dcp: Add DCP planes

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: - [Critical] drm/msm/dcp: missing HFI pipe detach in dcp_plane_atomic_update() - [Critical] drm/msm/dcp…

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [Critical] drm/msm/dcp: missing HFI pipe detach in dcp_plane_atomic_update()
- [Critical] drm/msm/dcp: unlocked plane->state access in dcp_plane_pipe_busy()
- [High] drm/msm/dcp: stale plane visibility used in dcp_plane_assign_pipe()
- [High] drm/msm/dcp: 90/270 rotation ignored in dcp_plane_atomic_check()
- [High] drm/msm/dcp: dcp_plane_atomic_create_state() returns NULL on OOM
- [High] drm/msm/dcp: broken IS_ERR() and NULL checks in dcp_plane_atomic_check()
- [High] drm/msm/dcp: divide-by-zero in dcp_plane_calc_bw() on zero vblank
- [Medium] drm/msm/dcp: chroma planes omitted in dcp_plane_calc_perf() bpp
- [Medium] drm/msm/dcp: non-alpha formats not forced to opaque blending in dcp_plane
- [Medium] drm/msm/dcp: missing minimum source size check for RGB formats in dcp_plane_atomic_check() --

commit 22e98b5154e2eb947949b5c49894d72a16ccd158
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add DCP planes

This commit adds DRM plane support for the DCP display driver, including format and scaling validation, firmware pipe assignment, bandwidth and clock calculation, and HFI property packet construction and submission.

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +0 -0
new file mode 100644
index 0000000000000..af8aa2162b03d

[ ... ]

Quoted 46 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +46 -0
+static bool dcp_plane_pipe_busy(struct drm_atomic_commit *state,
+				struct drm_plane *self, u32 pipe_id)
+{
+	struct drm_plane *plane;
+	struct drm_plane_state *plane_state;
+	struct drm_plane *iter_plane;
+	struct drm_plane_state *new_plane_state;
+	int i;
+
+	drm_for_each_plane(plane, state->dev) {
+		if (plane == self)
+			continue;
+
+		plane_state = plane->state;
+		for_each_new_plane_in_state(state, iter_plane, new_plane_state, i) {
+			if (iter_plane == plane) {
+				plane_state = new_plane_state;
+				break;
+			}
+		}
+
+		if (plane_state && plane_state->crtc && plane_state->visible &&
+		    to_dcp_plane_state(plane_state)->hfi_pipe_id == pipe_id)
+			return true;
+	}
+
+	return false;
+}
+
+static u32 dcp_plane_assign_pipe(struct dcp_dpu_kms *dcp_kms,
+				 struct drm_atomic_commit *state,
+				 struct drm_plane *plane,
+				 struct dcp_plane_state *pstate,
+				 bool yuv, bool scaling)
+{
+	const struct dcp_caps *caps = dcp_kms->caps;
+	u32 i, pipe_id;
+
+	/* YUV scanout requires a VIG pipe with the CSC path. */
+	if (dcp_plane_hfi_pipe_valid(dcp_kms, pstate->hfi_pipe_id) &&
+	    (!yuv || dcp_plane_pipe_is_vig(caps, pstate->hfi_pipe_id)) &&
+	    (!scaling || dcp_plane_pipe_is_vig(caps, pstate->hfi_pipe_id)) &&
+	    !dcp_plane_pipe_busy(state, plane, pstate->hfi_pipe_id))
+		return pstate->hfi_pipe_id;
+
+	/*

drivers/gpu/drm/msm/dcp/dcp_plane.c line 156

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+	 * R1/virtual pipes are intentionally not allocated as independent
+	 * planes yet. They require transactional physical-pipe/R1 ownership
+	 * and multirect programming, which will be added with the resource
+	 * manager.
+	 */
+	for (i = 0; i < caps->num_vig_pipes; i++) {
+		pipe_id = caps->vig_pipe_ids[i];
+		if (!dcp_plane_pipe_busy(state, plane, pipe_id))
+			return pipe_id;
+	}

[Severity: Critical]
Is it safe for dcp_plane_pipe_busy() to read plane->state for planes that are not part of the current drm_atomic_commit without holding plane->mutex?

When dcp_plane_atomic_check() calls dcp_plane_assign_pipe() -> dcp_plane_pipe_busy(), it iterates over all planes on the device via drm_for_each_plane() and falls back to plane_state = plane->state for any plane not present in state.

Because DCP supports multiple CRTCs and planes are shared across possible_crtcs, a concurrent atomic commit on another CRTC holding that other plane's mutex can swap plane->state in drm_atomic_helper_swap_state() and free the old state via drm_atomic_commit_put() -> dcp_plane_atomic_destroy_state() -> kfree() while dcp_plane_pipe_busy() is dereferencing plane_state->crtc, plane_state->visible, and to_dcp_plane_state(plane_state)->hfi_pipe_id.

In addition, without serializing pipe assignment through a shared drm_private_obj state or lock, can two concurrent atomic checks on different CRTCs both see the same pipe_id as free in dcp_plane_pipe_busy() and assign the same hardware HFI pipe to two active planes?

[ ... ]

Quoted 15 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +15 -0
+static u64 dcp_plane_calc_bw(u32 bpp,
+			     const struct drm_display_mode *mode,
+			     const struct drm_rect *src,
+			     const struct drm_rect *dst)
+{
+	int src_w = drm_rect_width(src) >> 16;
+	int src_h = drm_rect_height(src) >> 16;
+	int dst_h = drm_rect_height(dst);
+	int fps   = drm_mode_vrefresh(mode);
+	int vbp   = mode->vtotal - mode->vsync_end;
+	int vpw   = mode->vsync_end - mode->vsync_start;
+	int vfp   = mode->vsync_start - mode->vdisplay;
+	u32 hw_latency = 25;
+	u64 scale_factor = (src_h > dst_h) ? mult_frac(src_h, 1, dst_h) : 1;
+	u64 plane_bw, prefill_bw;

drivers/gpu/drm/msm/dcp/dcp_plane.c line 319

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+
+	plane_bw = (u64)src_w * mode->vtotal * fps * bpp * scale_factor;
+	prefill_bw = plane_bw * hw_latency;
+
+	if ((vbp + vpw) > hw_latency)
+		do_div(prefill_bw, (vbp + vpw));
+	else if ((vbp + vpw + vfp) < hw_latency)
+		do_div(prefill_bw, (vbp + vpw + vfp));
+	else
+		do_div(prefill_bw, hw_latency);

[Severity: High]
Can dcp_plane_calc_bw() trigger a divide-by-zero when (vbp + vpw + vfp) is zero?

Here, vbp + vpw + vfp equals mode->vtotal - mode->vdisplay. Neither drm_mode_validate_basic() nor dcp_crtc_mode_valid() rejects a mode with vdisplay == vsync_start == vsync_end == vtotal.

If userspace submits an atomic commit or DRM_MODE_ATOMIC_TEST_ONLY check with such a mode and a visible plane, (vbp + vpw) > hw_latency is false and (vbp + vpw + vfp) < hw_latency is true (0 < 25), causing do_div(prefill_bw, (vbp + vpw + vfp)) to divide by zero.

[ ... ]

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+void dcp_plane_calc_perf(const struct drm_plane_state *state,
+			 const struct drm_display_mode *mode,
+			 struct dcp_plane_perf *perf)
+{
+	struct drm_rect src, dst;
+	u32 bpp;
+
+	memset(perf, 0, sizeof(*perf));
+
+	if (!state || !state->visible || !state->fb || !mode || !mode->clock ||

drivers/gpu/drm/msm/dcp/dcp_plane.c line 361

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+	    !drm_mode_vrefresh(mode))
+		return;
+
+	src = drm_plane_state_src(state);
+	dst = drm_plane_state_dest(state);
+	bpp = drm_format_info_bpp(state->fb->format, 0);
+
+	perf->fetch_bw = dcp_plane_calc_bw(bpp, mode, &src, &dst);
+	perf->clk = dcp_plane_calc_clk(mode, &src, &dst);
+}

[Severity: Medium]
Does querying drm_format_info_bpp(state->fb->format, 0) in dcp_plane_calc_perf() undercount the bandwidth for multi-planar YUV formats?

For formats such as DRM_FORMAT_NV12, DRM_FORMAT_NV21, DRM_FORMAT_YUV420, DRM_FORMAT_YVU420, and DRM_FORMAT_P010, plane 0 only accounts for the luma plane (8 bpp for 8-bit 4:2:0 or 16 bpp for P010), omitting the chroma planes (which add another 4 bpp or 8 bpp) from perf->fetch_bw.

[ ... ]

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +5 -0
+static int dcp_plane_build_one_hfi_props_packet(struct dcp_plane_state *pstate,
+						const struct drm_plane_state *state,
+						u32 session_id, u32 packet_id,
+						u8 *packet)
+{

[ ... ]

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +6 -0
+	hfi_rotation = dcp_drm_to_hfi_rotation(state->rotation);
+	hfi_blend_op = dcp_drm_to_hfi_blend_op(state->pixel_blend_mode);
+
+	src_x = state->src_x >> 16;
+	src_y = state->src_y >> 16;
+	src_w = state->src_w >> 16;

drivers/gpu/drm/msm/dcp/dcp_plane.c line 464

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+	src_h = state->src_h >> 16;
+	dst_x = state->crtc_x;
+	dst_y = state->crtc_y;
+	dst_w = state->crtc_w;
+	dst_h = state->crtc_h;
+
+	hfi_alpha = dcp_hfi_scale_alpha(state, state->alpha);
+	hfi_bg_alpha = dcp_hfi_scale_alpha(state,
+					   dcp_compute_bg_alpha(state->pixel_blend_mode,
+								state->alpha));

[Severity: Medium]
When !fb->format->has_alpha (for example with DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB565, or YUV formats), should dcp_plane_build_one_hfi_props_packet() fall back to DRM_MODE_BLEND_PIXEL_NONE instead of using state->pixel_blend_mode directly?

dcp_plane_init() attaches the pixel blend mode property with DRM_MODE_BLEND_PREMULTI as the default. When a non-alpha framebuffer is used, passing DRM_MODE_BLEND_PREMULTI or DRM_MODE_BLEND_COVERAGE to dcp_drm_to_hfi_blend_op() and dcp_compute_bg_alpha() programs HFI_BLEND_OP_PREMULTIPLIED or HFI_BLEND_OP_COVERAGE and sets bg_alpha to fg_alpha (or 0 when opaque) rather than HFI_BLEND_OP_OPAQUE with DRM_BLEND_ALPHA_OPAQUE - fg_alpha.

[ ... ]

Quoted 3 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +3 -0
+static int dcp_plane_atomic_check(struct drm_plane *plane,
+				  struct drm_atomic_commit *state)
+{

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_plane.c line 659

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+	if (!new_state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);
+	if (IS_ERR(crtc_state)) {
+		ret = PTR_ERR(crtc_state);
+		drm_err(plane->dev, "plane%u: failed to get CRTC state: %d\n",
+			plane->base.id, ret);
+		return ret;
+	}

[Severity: High]
drm_atomic_get_new_crtc_state() returns NULL when the CRTC is not in state, rather than an ERR_PTR(). Should this check for !crtc_state instead of IS_ERR(crtc_state) so a NULL crtc_state is not passed to drm_atomic_helper_check_plane_state()?

Similarly, in dcp_plane_assign_pipe(), dcp_plane_hfi_pipe_valid() checks if (!dcp_kms->caps), but when it returns false, dcp_plane_assign_pipe() immediately dereferences caps->num_vig_pipes and caps->num_dma_pipes without checking if caps is NULL.

Quoted 13 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +13 -0
+	if (!new_state->fb)
+		return 0;
+
+	scaling = new_state->src_w != ((u32)new_state->crtc_w << 16) ||
+			  new_state->src_h != ((u32)new_state->crtc_h << 16);
+
+	pstate->hfi_pipe_id = dcp_plane_assign_pipe(dcp_kms, state, plane,
+						    pstate, new_state->fb->format->is_yuv,
+						    scaling);
+	if (pstate->hfi_pipe_id == U32_MAX) {
+		drm_err(plane->dev, "plane%u: no compatible HFI pipe available\n",
+			plane->base.id);
+		return -ENOSPC;

drivers/gpu/drm/msm/dcp/dcp_plane.c line 683

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+	}
+
+	if (dcp_plane_pipe_is_vig(dcp_kms->caps, pstate->hfi_pipe_id)) {
+		min_scale = FRAC_16_16(1, DCP_MAX_UPSCALE_RATIO);
+		max_scale = DCP_MAX_DOWNSCALE_RATIO << 16;
+	}
+
+	ret = drm_atomic_helper_check_plane_state(new_state, crtc_state,
+						  min_scale, max_scale,
+						  true, true);

[Severity: High]
Because dcp_plane_assign_pipe() -> dcp_plane_pipe_busy() runs before drm_atomic_helper_check_plane_state() computes new_state->visible, won't dcp_plane_pipe_busy() inspect stale visibility flags?

Specifically:
1. When drm_atomic_helper_check_planes() checks planes in ascending index order, any higher-indexed plane in the same atomic commit has not run dcp_plane_atomic_check() yet, so its new_plane_state->visible still holds the old state's visibility. If that higher-indexed plane was previously disabled and is being enabled in this commit, dcp_plane_pipe_busy() sees new_plane_state->visible == false and allows an earlier plane to take its hfi_pipe_id.
2. If the current plane is clipped off-screen so drm_atomic_helper_check_plane_state() sets new_state->visible = false, it has already claimed a pipe in dcp_plane_assign_pipe().
3. When a previously visible plane is disabled (!new_state->crtc or !new_state->fb), dcp_plane_atomic_check() returns 0 early before calling drm_atomic_helper_check_plane_state(), leaving new_state->visible set to true on the disabled state.

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +8 -0
+	if (ret) {
+		drm_err(plane->dev, "plane%u: plane state check failed: %d\n",
+			plane->base.id, ret);
+		return ret;
+	}
+
+	if (!new_state->visible)
+		return 0;

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_plane.c line 756

Quoted 12 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +12 -0
+	dcp_mode_limits(&crtc_state->adjusted_mode,
+			dcp_kms->caps->max_width, dcp_kms->caps->max_height,
+			&max_width, &max_height);
+
+	if (new_state->fb->width > max_width ||
+	    new_state->fb->height > max_height) {
+		drm_err(plane->dev,
+			"plane%u: framebuffer %ux%u exceeds caps %ux%u\n",
+			plane->base.id, new_state->fb->width, new_state->fb->height,
+			dcp_kms->caps->max_width, dcp_kms->caps->max_height);
+		return -E2BIG;
+	}

[Severity: High]
How does dcp_plane_atomic_check() handle 90 and 270 degree rotations?

dcp_plane_init() advertises DRM_MODE_ROTATE_90 and DRM_MODE_ROTATE_270, and drm_atomic_helper_check_plane_state() rotates the source rectangle via drm_rect_rotate() before checking hscale and vscale. However:
1. The scaling check earlier in dcp_plane_atomic_check():

     scaling = new_state->src_w != ((u32)new_state->crtc_w << 16) ||
               new_state->src_h != ((u32)new_state->crtc_h << 16);

compares unrotated source dimensions against crtc_w and crtc_h. For a non-square plane rotated 90 or 270 degrees, an unscaled plane (where src_w == crtc_h << 16 and src_h == crtc_w << 16) is treated as scaled and forced onto a VIG pipe, whereas a scaled plane with src_w == crtc_w << 16 and src_h == crtc_h << 16 is treated as unscaled, assigned a DMA pipe with DRM_PLANE_NO_SCALING, and rejected by drm_atomic_helper_check_plane_state() with -ERANGE.
2. The dcp_mode_limits() check compares unrotated new_state->fb->width and new_state->fb->height against mode-oriented max_width and max_height, rejecting a landscape framebuffer rotated 90/270 degrees onto a portrait mode with -E2BIG.
3. dcp_plane_calc_perf() passes unrotated drm_plane_state_src(state) to dcp_plane_calc_bw() and dcp_plane_calc_clk(), using unrotated src_w and src_h for bandwidth and clock scaling calculations.

Quoted 17 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +17 -0
+	for (i = 0; i < new_state->fb->format->num_planes; i++) {
+		if (new_state->fb->pitches[i] > DCP_SSPP_MAX_PITCH_SIZE) {
+			drm_err(plane->dev,
+				"plane%u: pitch[%d]=%u exceeds limit %u\n",
+				plane->base.id, i, new_state->fb->pitches[i],
+				DCP_SSPP_MAX_PITCH_SIZE);
+			return -E2BIG;
+		}
+	}
+
+	src = drm_plane_state_src(new_state);
+
+	pstate->src_img_w = new_state->fb->width;
+	pstate->src_img_h = new_state->fb->height;
+
+	if (new_state->fb->format->is_yuv) {
+		u32 src_x = src.x1 >> 16;

drivers/gpu/drm/msm/dcp/dcp_plane.c line 784

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+		u32 src_y = src.y1 >> 16;
+		u32 src_w = drm_rect_width(&src) >> 16;
+		u32 src_h = drm_rect_height(&src) >> 16;
+
+		if (src_w < 2 || src_h < 2) {
+			drm_err(plane->dev,
+				"plane%u: YUV source too small: %ux%u\n",
+				plane->base.id, src_w, src_h);
+			return -EINVAL;
+		}

[Severity: Medium]
Should non-YUV formats also enforce a minimum integer source size of src_w >= 1 and src_h >= 1?

When an RGB plane is assigned to a VIG pipe, min_scale is FRAC_16_16(1, DCP_MAX_UPSCALE_RATIO), which allows sub-pixel 16.16 source dimensions (such as src_w or src_h in [3276, 65535] with crtc_w = 1 or crtc_h = 1) to pass drm_atomic_helper_check_plane_state() with new_state->visible = true.

Because minimum source dimensions are only checked inside the is_yuv block, state->src_w >> 16 or state->src_h >> 16 then truncates to 0 in dcp_plane_calc_bw() (resulting in a 0 bandwidth vote for a visible plane) and in dcp_plane_build_one_hfi_props_packet() (sending a 0-width or 0-height HFI_PROPERTY_LAYER_SRC_ROI to the firmware).

[ ... ]

Quoted 31 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +31 -0
+static void dcp_plane_atomic_update(struct drm_plane *plane,
+				    struct drm_atomic_commit *state)
+{
+	struct drm_plane_state *new_state =
+		drm_atomic_get_new_plane_state(state, plane);
+	struct dcp_plane_state *pstate = to_dcp_plane_state(new_state);
+	int ret;
+
+	drm_dbg_driver(plane->dev,
+		       "plane%u: atomic_update visible=%d fb=%u pipe_id=%u\n",
+		       plane->base.id, new_state->visible,
+		       new_state->fb ? new_state->fb->base.id : 0,
+		       pstate->hfi_pipe_id);
+
+	pstate->prop_pkt_size = 0;
+
+	if (!new_state->visible || !new_state->fb)
+		return;
+
+	ret = dcp_plane_submit_update(plane, new_state);
+	if (ret) {
+		drm_err(plane->dev,
+			"plane%u: HFI update failed: %d\n", plane->base.id, ret);
+		dcp_plane_set_hfi_error(new_state, ret);
+		return;
+	}
+}
+
+static void dcp_plane_atomic_disable(struct drm_plane *plane,
+				     struct drm_atomic_commit *state)
+{

drivers/gpu/drm/msm/dcp/dcp_plane.c line 902

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +10 -0
+	struct drm_plane_state *old_state =
+		drm_atomic_get_old_plane_state(state, plane);
+	struct dcp_plane_state *pstate = to_dcp_plane_state(old_state);
+	int ret;
+
+	drm_dbg_driver(plane->dev,
+		       "plane%u: atomic_disable pipe_id=%u\n",
+		       plane->base.id, pstate->hfi_pipe_id);
+
+	ret = dcp_plane_submit_disable(plane, old_state);

[Severity: Critical]
Can HFI pipes remain attached to unmapped framebuffers or be erroneously detached from another plane during atomic commits?

There appear to be three related issues in the update/disable paths:
1. When an active visible plane remains bound to a CRTC and framebuffer (new_state->crtc != NULL and new_state->fb != NULL) but is moved off-screen so new_state->visible becomes false, drm_atomic_plane_disabling() is false and drm_atomic_helper_commit_planes() calls dcp_plane_atomic_update() rather than dcp_plane_atomic_disable(). dcp_plane_atomic_update() returns early at if (!new_state->visible || !new_state->fb) without detaching old_state->hfi_pipe_id, leaving the DCP firmware scanning out from the old framebuffer IOVA after dcp_plane_cleanup_fb() -> msm_framebuffer_cleanup() unmaps it.
2. When an already-visible plane is reassigned to a different pipe by dcp_plane_assign_pipe() (for example, switching from an unscaled RGB buffer on a DMA pipe to a YUV or scaled buffer requiring a VIG pipe, so old_pstate->hfi_pipe_id != pstate->hfi_pipe_id), dcp_plane_atomic_update() attaches the new pipe but never sends HFI_PROPERTY_DISPLAY_DETACH_LAYER for old_pstate->hfi_pipe_id.
3. Because dcp_plane_pipe_busy() treats an invisible or disabling plane's hfi_pipe_id as free, a lower-indexed plane can be assigned and attach that pipe in dcp_plane_atomic_update() before the higher-indexed plane runs dcp_plane_atomic_disable() (which executes in ascending plane index order and does not check old_state->visible), causing dcp_plane_atomic_disable() to detach the pipe that the lower-indexed plane just attached.

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_plane.c line 954

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_plane.c new file +9 -0
+static struct drm_plane_state *
+dcp_plane_atomic_create_state(struct drm_plane *plane)
+{
+	struct dcp_plane_state *pstate;
+	u32 hfi_pipe_id = to_dcp_plane(plane)->hfi_pipe_id;
+
+	pstate = kzalloc_obj(*pstate, GFP_KERNEL);
+	if (!pstate)
+		return NULL;

[Severity: High]
Should dcp_plane_atomic_create_state() return ERR_PTR(-ENOMEM) instead of NULL on allocation failure?

Its caller drm_mode_config_plane_create_state() checks for errors using IS_ERR(plane_state), so returning NULL bypasses error handling and leaves plane->state as NULL.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
#34 lore raw

PATCH v2 13/23 drm/msm/dcp: Route HFI display events

In reply to Jayant Shekhar

Route asynchronous display events from DCP firmware into the DRM CRTC objects. Without this mapping, VSYNC and frame-scan completion cannot drive DRM vblank and page-flip completion.

Match firmware display events to the corresponding CRTC session and deliver VSYNC and frame-scan completion notifications through the MSM DRM callbacks.

Assisted-by LLM
---
 drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +21 -0
index 774a5afac0f0..e54ccfafdae0 100644
@@ -19,6 +19,7 @@
 #include "dcp_hfi.h"
 #include "dcp_hfi_priv.h"
 #include "dcp_drv.h"
+#include "dcp_crtc.h"
 #include "hfi/hfi_packet.h"
 #include "hfi/hfi_packer.h"
 #include "hfi/hfi_unpacker.h"
@@ -260,18 +261,35 @@ static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi,
 	dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
 }
 
+static void dcp_hfi_dispatch_frame_done(struct dcp_hfi *hfi,
+					const struct hfi_packet_info *pkt_info)
+{
+	const struct hfi_display_frame_event_data *frame = NULL;
+	u32 sequence_id = 0;
+
+	if (pkt_info->payload_ptr &&
+	    pkt_info->payload_size >= sizeof(*frame)) {
+		frame = pkt_info->payload_ptr;
+		sequence_id = frame->bufferflip_index;
+	}
+	dcp_crtc_hfi_frame_done_cb(hfi->drv, pkt_info->id & 0xFFFF,
+				   sequence_id);
+}
+
 /* Route asynchronous events and capability packets before flag handling. */
 static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
 				   const struct hfi_packet_info *pkt_info)
 {
 	switch (pkt_info->cmd) {
 	case HFI_COMMAND_DISPLAY_EVENT_VSYNC:
+		dcp_crtc_hfi_vblank_cb(hfi->drv, pkt_info->id & 0xFFFF);
 		return true;
 
 	case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_START:
 		return true;
 
 	case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE:
+		dcp_hfi_dispatch_frame_done(hfi, pkt_info);
 		return true;
 
 	case HFI_COMMAND_DISPLAY_EVENT_HPD_STATUS:
@@ -975,6 +993,9 @@ int dcp_hfi_init(struct dcp_drv *drv,
 	struct dcp_hfi *hfi;
 	int i, ret;
 
+	if (!dma_dev)
+		return -ENODEV;
+
 	hfi = devm_kzalloc(&vdev->dev, sizeof(*hfi), GFP_KERNEL);
 	if (!hfi)
 		return -ENOMEM;
Signature · 1 line
2.43.0
#35 lore raw

Re: [PATCH v2 13/23] drm/msm/dcp: Route HFI display events

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] drm/msm/dcp: unsynchronized frame done state in dcp_crtc_hfi_frame_done_cb() - [Medium]…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/msm/dcp: unsynchronized frame done state in dcp_crtc_hfi_frame_done_cb()
- [Medium] drm/msm/dcp: missing payload validation in dcp_hfi_dispatch_frame_done() --

commit 7606ba3e12102c6ab2bc26f57c18f121dc3e91f7
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Route HFI display events

This commit routes asynchronous HFI VSYNC and frame-scan completion display events from DCP firmware to the corresponding DRM CRTC callbacks, and adds a NULL check for dma_dev in dcp_hfi_init().

Quoted 20 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +5 -0
index 774a5afac0f0a..e54ccfafdae0f 100644
@@ -19,6 +19,7 @@
 #include "dcp_hfi.h"
 #include "dcp_hfi_priv.h"
 #include "dcp_drv.h"
+#include "dcp_crtc.h"
 #include "hfi/hfi_packet.h"
 #include "hfi/hfi_packer.h"
 #include "hfi/hfi_unpacker.h"
@@ -260,18 +261,35 @@ static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi,
 	dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
 }
 
+static void dcp_hfi_dispatch_frame_done(struct dcp_hfi *hfi,
+					const struct hfi_packet_info *pkt_info)
+{
+	const struct hfi_display_frame_event_data *frame = NULL;

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi, · line 277

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +10 -0
+	u32 sequence_id = 0;
+
+	if (pkt_info->payload_ptr &&
+	    pkt_info->payload_size >= sizeof(*frame)) {
+		frame = pkt_info->payload_ptr;
+		sequence_id = frame->bufferflip_index;
+	}
+	dcp_crtc_hfi_frame_done_cb(hfi->drv, pkt_info->id & 0xFFFF,
+				   sequence_id);
+}

[Severity: Medium]
Should dcp_hfi_dispatch_frame_done() drop the packet and return early when pkt_info->payload_ptr is NULL or pkt_info->payload_size is smaller than sizeof(*frame)?

As written, a truncated or empty HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE packet falls through and invokes dcp_crtc_hfi_frame_done_cb() with a fabricated sequence_id of 0:

drivers/gpu/drm/msm/dcp/dcp_crtc.c:dcp_crtc_hfi_frame_done_cb() {
	...
	if (crtc) {
		struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);

		/* A single pending commit makes equality valid across sequence wrap. */
		if (sequence_id != dcp_crtc->pending_seq_id) {
			drm_warn(crtc->dev,
				 "CRTC%u: ignoring stale FRAME_SCAN_COMPLETE seq=%u expected=%u\n",
				 dcp_crtc->display_id, sequence_id,
				 dcp_crtc->pending_seq_id);
			return;
		}
		dcp_crtc_frame_done_callback(crtc);
	}
	...
}

If dcp_crtc->pending_seq_id happens to be 0 (such as before the first commit sequence is latched or when the 32-bit commit_seq_id counter wraps around to 0), this will falsely match pending_seq_id and prematurely complete the frame. When pending_seq_id is non-zero, it logs an unratelimited warning for a bogus sequence 0 event.

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +1 -0
+
 /* Route asynchronous events and capability packets before flag handling. */
 static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
 				   const struct hfi_packet_info *pkt_info)
 {
 	switch (pkt_info->cmd) {

drivers/gpu/drm/msm/dcp/dcp_hfi_core.c static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi, · line 293

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c +2 -0
 	case HFI_COMMAND_DISPLAY_EVENT_VSYNC:
+		dcp_crtc_hfi_vblank_cb(hfi->drv, pkt_info->id & 0xFFFF);
 		return true;
 
 	case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_START:
 		return true;
 
 	case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE:
+		dcp_hfi_dispatch_frame_done(hfi, pkt_info);
 		return true;

[Severity: High]
With HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE now routed from the asynchronous VirtIO response callback (dcp_hfi_resp_callback() -> dcp_hfi_dispatch_event() -> dcp_hfi_dispatch_frame_done()) into dcp_crtc_hfi_frame_done_cb(), can concurrent access to dcp_crtc->pending_seq_id and dcp_crtc->pending_kickoff_cnt race with the commit path?

In drivers/gpu/drm/msm/dcp/dcp_crtc.c, dcp_crtc_commit_kickoff() updates pending_kickoff_cnt and pending_seq_id without lock synchronization against dcp_crtc_hfi_frame_done_cb():

	if (atomic_cmpxchg(&dcp_crtc->pending_kickoff_cnt, 0, 1)) {
		drm_err(crtc->dev,
			"CRTC%u: frame completion already pending\n",
			dcp_crtc->display_id);
		cstate->hfi_error = -EBUSY;
		return;
	}
	dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;

	ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
	if (ret) {
		drm_err(crtc->dev,
			"HFI commit failed for CRTC%u (session=%u): %d\n",
			dcp_crtc->display_id, session_id, ret);
		atomic_dec(&dcp_crtc->pending_kickoff_cnt);
		wake_up(&dcp_crtc->frame_done_wq);
	}

This appears to allow several races when asynchronous frame-done events arrive:

1. pending_kickoff_cnt is set to 1 before dcp_crtc->pending_seq_id is updated to dcp_crtc->commit_seq_id. If a FRAME_SCAN_COMPLETE event carrying the previous sequence ID arrives in that window (for example after a prior commit timeout in dcp_crtc_wait_for_commit_done() reset pending_kickoff_cnt to 0 while leaving pending_seq_id at the old value, or from periodic scanout), dcp_crtc_hfi_frame_done_cb() sees sequence_id == dcp_crtc->pending_seq_id and calls dcp_crtc_frame_done_callback(), decrementing pending_kickoff_cnt back to 0 before dcp_hfi_display_commit() even runs.

2. If dcp_crtc_frame_done_callback() decrements pending_kickoff_cnt from 1 to 0 (either via the window above or because firmware sent FRAME_SCAN_COMPLETE while dcp_hfi_display_commit() timed out waiting for the command response) and dcp_hfi_display_commit() returns an error, the error path unconditionally runs atomic_dec(&dcp_crtc->pending_kickoff_cnt), underflowing pending_kickoff_cnt to -1. Because dcp_crtc_frame_done_callback() uses atomic_add_unless(&dcp_crtc->pending_kickoff_cnt, -1, 0), subsequent frame-done events will decrement it further (-2, -3, ...) and cause future commits to stall until DCP_FRAME_DONE_TIMEOUT_MS expires.

3. In dcp_crtc_atomic_flush(), ++dcp_crtc->commit_seq_id is sent to firmware via dcp_hfi_crtc_set_scan_sequence_id() and autorefresh is enabled via dcp_hfi_crtc_set_autorefresh() before dcp_crtc_commit_kickoff() sets pending_seq_id and pending_kickoff_cnt. If firmware emits a FRAME_SCAN_COMPLETE event for the new sequence ID before dcp_crtc_commit_kickoff() updates pending_seq_id, dcp_crtc_hfi_frame_done_cb() will drop it as a stale sequence mismatch.

Should pending_seq_id and pending_kickoff_cnt updates and checks be synchronized under a spinlock across the flush, kickoff, and HFI callback paths?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
#36 lore raw

PATCH v2 14/23 drm/msm/dcp: Add DCP encoder

In reply to Jayant Shekhar

Create the DRM encoder required to connect a DCP CRTC to its firmware-backed output bridge. The encoder must fit the existing MSM DRM object model while remaining independent of bridge discovery and KMS registration.

Implement the DCP encoder wrapper, allocation, and DRM object initialization.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile          |  1 +
 drivers/gpu/drm/msm/dcp/dcp_encoder.c | 29 +++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_encoder.h | 16 ++++++++++++++++
 3 files changed, 46 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index b425fefda890..4024f6b1e3cf 100644
@@ -83,6 +83,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_kms.o \
 	dcp/dcp_hfi_plane.o \
 	dcp/dcp_ipcc.o \
+	dcp/dcp_encoder.o \
 	dcp/hfi/hfi_packer.o \
 	dcp/hfi/hfi_unpacker.o \
 	dcp/hfi/hfi_utils.o
drivers/gpu/drm/msm/dcp/dcp_encoder.c new file +29 -0
new file mode 100644
index 000000000000..e7a64c960bdc
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP encoder wrapper for the CRTC-to-bridge connection.
+ * Hardware programming and mode sequencing are handled by the bridge and HFI.
+ */
+
+#include <drm/drm_encoder.h>
+#include <drm/drm_managed.h>
+
+#include "dcp_encoder.h"
+
+struct drm_encoder *dcp_encoder_init(struct drm_device *dev, int enc_type)
+{
+	struct drm_encoder *encoder;
+	int ret;
+
+	encoder = drmm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL);
+	if (!encoder)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drmm_encoder_init(dev, encoder, NULL, enc_type, "encoder%d",
+				dev->mode_config.num_encoder);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return encoder;
+}
drivers/gpu/drm/msm/dcp/dcp_encoder.h new file +16 -0
new file mode 100644
index 000000000000..de40c9bca4bb
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP encoder interface between the CRTC and bridge chain.
+ * Hardware programming is delegated to DCP firmware.
+ */
+
+#ifndef __DCP_ENCODER_H__
+#define __DCP_ENCODER_H__
+
+#include <drm/drm_encoder.h>
+
+struct drm_encoder *dcp_encoder_init(struct drm_device *dev, int enc_type);
+
+#endif /* __DCP_ENCODER_H__ */
Signature · 1 line
2.43.0
#37 lore raw

Re: [PATCH v2 14/23] drm/msm/dcp: Add DCP encoder

Sashiko has reviewed this patch and found no issues.
Quoted 10 lines

Create the DRM encoder required to connect a DCP CRTC to its firmware-backed output bridge. The encoder must fit the existing MSM DRM object model while remaining independent of bridge discovery and KMS registration.

Implement the DCP encoder wrapper, allocation, and DRM object initialization.

Assisted-by LLM

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
#38 lore raw

PATCH v2 15/23 drm/msm/dcp: Add DCP DSI host and panel support

In reply to Jayant Shekhar

Expose the DCP DSI host and panel as DRM-facing objects. The DCP display path needs a host, panel configuration, bridge state, DSC setup, DCS transfer buffers, and panel lifetime management before it can drive a physical display.

Implement the MIPI DSI host, panel-device support, bridge state, DSC configuration, DCS transfers, and DSI buffer management.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile             |    1 +
 drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1110 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h |   76 ++
 drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h  |   13 +
 4 files changed, 1200 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index 4024f6b1e3cf..9f0a9bfeb2a7 100644
@@ -75,6 +75,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_crtc.o \
 	dcp/dcp_dpu_kms.o \
 	dcp/dcp_plane.o \
+	dcp/dcp_dsi_bridge.o \
 	dcp/dcp_hfi_core.o \
 	dcp/dcp_hfi_crtc.o \
 	dcp/dcp_hfi_dsi.o \
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +1110 -0

1,110 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h new file +76 -0

76 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h +13 -0
index a8898e469b44..cc4e9f4c6fc4 100644
@@ -74,6 +74,19 @@ struct dcp_dsi_hfi_panel_config {
 	u32 topology_intf_count;
 };
 
+/**
+ * struct dcp_dsi_panel_state - Mode-dependent DSI panel state.
+ *
+ * This state is owned by a DRM atomic bridge state. It is combined with
+ * the static panel configuration when constructing the HFI PANEL_INIT
+ * payload.
+ */
+struct dcp_dsi_panel_state {
+	u32 topology_mixer_count;
+	u32 topology_enc_count;
+	u32 topology_intf_count;
+};
+
 /* DCS buffers use IOVAs directly in the firmware command descriptor. */
 #define DCP_DSI_DCS_TX_BUF_SIZE	SZ_4K
 #define DCP_DSI_DCS_RX_BUF_SIZE	SZ_4K
Signature · 1 line
2.43.0
#39 lore raw

Re: [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support

Quoted 285 lines · Jayant Shekhar

Expose the DCP DSI host and panel as DRM-facing objects. The DCP display path needs a host, panel configuration, bridge state, DSC setup, DCS transfer buffers, and panel lifetime management before it can drive a physical display.

Implement the MIPI DSI host, panel-device support, bridge state, DSC configuration, DCS transfers, and DSI buffer management.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile             |    1 +
 drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1110 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h |   76 ++
 drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h  |   13 +
 4 files changed, 1200 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index 4024f6b1e3cf..9f0a9bfeb2a7 100644
@@ -75,6 +75,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_crtc.o \
 	dcp/dcp_dpu_kms.o \
 	dcp/dcp_plane.o \
+	dcp/dcp_dsi_bridge.o \
 	dcp/dcp_hfi_core.o \
 	dcp/dcp_hfi_crtc.o \
 	dcp/dcp_hfi_dsi.o \
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +250 -0
new file mode 100644
index 000000000000..b3e782b8f047
@@ -0,0 +1,1110 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI bridge driver.
+ *
+ * Implements a DRM bridge for DSI displays in the DCP firmware-delegated
+ * architecture. DCP firmware owns the DSI PHY and controller; this driver
+ * uses HFI commands to configure DSI parameters rather than direct register
+ * writes.
+ */
+
+#include <linux/component.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/jiffies.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+
+#include <video/mipi_display.h>
+
+#include "dcp_crtc.h"
+#include "dcp_dsi_bridge.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "hfi/hfi_defs_panel.h"
+
+struct dcp_dsi_bridge_state {
+	struct drm_bridge_state base;
+	struct dcp_dsi_panel_state panel_state;
+	int prepare_error;
+};
+
+#define to_dcp_dsi_bridge_state(s) \
+	container_of(s, struct dcp_dsi_bridge_state, base)
+
+static struct dcp_dsi_bridge_state *
+dcp_dsi_bridge_get_state(struct dcp_dsi_bridge *bridge)
+{
+	return to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->bridge.base.state));
+}
+
+static struct drm_bridge_state *
+dcp_dsi_bridge_atomic_create_state(struct drm_bridge *bridge)
+{
+	struct dcp_dsi_bridge_state *state;
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+
+	state = kzalloc_obj(*state);
+	if (!state)
+		return ERR_PTR(-ENOMEM);
+
+	__drm_atomic_helper_bridge_state_init(&state->base, bridge);
+	state->panel_state.topology_mixer_count =
+		dsi_bridge->hfi_panel_config.topology_mixer_count;
+	state->panel_state.topology_enc_count =
+		dsi_bridge->hfi_panel_config.topology_enc_count;
+	state->panel_state.topology_intf_count =
+		dsi_bridge->hfi_panel_config.topology_intf_count;
+	return &state->base;
+}
+
+static struct drm_bridge_state *
+dcp_dsi_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
+{
+	struct dcp_dsi_bridge_state *state, *old_state;
+
+	if (WARN_ON(!bridge->base.state))
+		return NULL;
+
+	state = kzalloc_obj(*state);
+	if (!state)
+		return NULL;
+
+	old_state = to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->base.state));
+	__drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
+	state->panel_state = old_state->panel_state;
+	return &state->base;
+}
+
+static void dcp_dsi_bridge_atomic_destroy_state(struct drm_bridge *bridge,
+						struct drm_bridge_state *bridge_state)
+{
+	kfree(to_dcp_dsi_bridge_state(bridge_state));
+}
+
+static void
+dcp_dsi_bridge_build_hfi_config(struct dcp_dsi_bridge *dsi_bridge,
+				const struct dcp_dsi_bridge_state *state,
+				struct dcp_dsi_hfi_panel_config *cfg)
+{
+	*cfg = dsi_bridge->hfi_panel_config;
+	cfg->topology_mixer_count = state->panel_state.topology_mixer_count;
+	cfg->topology_enc_count = state->panel_state.topology_enc_count;
+	cfg->topology_intf_count = state->panel_state.topology_intf_count;
+}
+
+static void dcp_dsi_host_dev_release(struct device *dev)
+{
+	of_node_put(dev->of_node);
+}
+
+static bool dcp_dsi_bridge_cmd_mode(const struct dcp_dsi_bridge *dsi_bridge)
+{
+	return dsi_bridge->dsi_device &&
+		!(dsi_bridge->dsi_device->mode_flags & MIPI_DSI_MODE_VIDEO);
+}
+
+static u64 dcp_dsi_link_clock(const struct dcp_dsi_bridge *dsi_bridge,
+			      const struct dcp_dsi_hfi_panel_config *cfg,
+			      const struct drm_display_mode *mode)
+{
+	const struct mipi_dsi_device *dsi = dsi_bridge->dsi_device;
+	int bpp;
+	u32 bpp_x16;
+
+	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+	if (bpp < 0)
+		return 0;
+
+	bpp_x16 = cfg->dsc_enabled && cfg->dsc_bits_per_pixel ?
+		cfg->dsc_bits_per_pixel : bpp << 4;
+
+	if (dsi->hs_rate)
+		return dsi->hs_rate;
+
+	return div_u64((u64)mode->clock * 1000 * bpp_x16, dsi->lanes * 16);
+}
+
+static void dcp_dsi_power_control_work(struct work_struct *work)
+{
+	struct dcp_dsi_bridge *dsi_bridge =
+		container_of(work, struct dcp_dsi_bridge, power_control_work);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+	dcp_hfi_display_power_control_ack(kms->hfi,
+					  dsi_bridge->session_id,
+					  dsi_bridge->power_packet_id);
+
+	complete(&dsi_bridge->power_done);
+}
+
+static void dcp_dsi_power_control_cb(void *priv, u32 display_id,
+				     u32 power_mask, bool enable,
+				     u32 packet_id)
+{
+	struct dcp_dpu_kms *kms = priv;
+	struct dcp_dsi_bridge *dsi_bridge = NULL;
+	int idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(kms->dsi_bridge); idx++) {
+		struct dcp_dsi_bridge *b = kms->dsi_bridge[idx];
+
+		if (b && b->session_id == display_id) {
+			dsi_bridge = b;
+			break;
+		}
+	}
+
+	if (!dsi_bridge) {
+		dev_warn(kms->pm_dev,
+			 "DSI: power_control for unknown display_id=%u\n", display_id);
+		return;
+	}
+
+	dsi_bridge->power_packet_id = packet_id;
+	schedule_work(&dsi_bridge->power_control_work);
+}
+
+static const struct component_ops dcp_dsi_component_ops;
+
+static int dcp_dsi_host_attach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *dsi)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+	struct dcp_dsi_hfi_panel_config *cfg = &dsi_bridge->hfi_panel_config;
+	int ret;
+
+	if (!dsi->lanes || dsi->lanes > 4)
+		return -EINVAL;
+
+	cfg->dsc_enabled = false;
+	cfg->dsc_version_major = 0;
+	cfg->dsc_version_minor = 0;
+	cfg->dsc_scr_version = 0;
+	cfg->dsc_slice_height = 0;
+	cfg->dsc_slice_width = 0;
+	cfg->dsc_slices_per_pkt = 0;
+	cfg->dsc_bits_per_component = 0;
+	cfg->dsc_pps_delay_ms = 0;
+	cfg->dsc_bits_per_pixel = 0;
+	cfg->dsc_chroma_format = 0;
+	cfg->dsc_color_space = 0;
+	cfg->dsc_block_prediction_enable = 0;
+	cfg->dsc_rc_override = false;
+	memset(cfg->dsc_rc_min_qp, 0, sizeof(cfg->dsc_rc_min_qp));
+	memset(cfg->dsc_rc_max_qp, 0, sizeof(cfg->dsc_rc_max_qp));
+	memset(cfg->dsc_rc_bpg_offsets, 0, sizeof(cfg->dsc_rc_bpg_offsets));
+
+	switch (dsi->format) {
+	case MIPI_DSI_FMT_RGB888:
+	case MIPI_DSI_FMT_RGB666:
+	case MIPI_DSI_FMT_RGB666_PACKED:
+	case MIPI_DSI_FMT_RGB565:
+	case MIPI_DSI_FMT_RGB101010:
+		break;
+	default:
+		dev_err(&dsi->dev, "unsupported pixel format %u\n", dsi->format);
+		return -EINVAL;
+	}
+
+	dsi_bridge->dsi_device = dsi;
+
+	if (dsi->dsc) {
+		struct drm_dsc_config *dsc = dsi->dsc;
+		u32 chroma_format = 0;
+		u32 color_space = 0;
+
+		if (dsc->native_422)
+			chroma_format = 1;
+		else if (dsc->native_420)
+			chroma_format = 2;
+		if (dsc->native_422 || dsc->native_420)
+			color_space = 1;
+
+		cfg->dsc_enabled                 = true;
+		cfg->dsc_version_major           = dsc->dsc_version_major;
+		cfg->dsc_version_minor           = dsc->dsc_version_minor;
+		cfg->dsc_scr_version              = 0;
+		cfg->dsc_slice_height             = dsc->slice_height;
+		cfg->dsc_slice_width              = dsc->slice_width;
+		cfg->dsc_slices_per_pkt           = 1;
+		cfg->dsc_bits_per_component       = dsc->bits_per_component;
+		cfg->dsc_pps_delay_ms             = 0;
+		cfg->dsc_bits_per_pixel           = dsc->bits_per_pixel;

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 260

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+		cfg->dsc_chroma_format            = chroma_format;
+		cfg->dsc_color_space              = color_space;
+		cfg->dsc_block_prediction_enable  = dsc->block_pred_enable;
+
+		dev_dbg(&dsi->dev,
+			"DSI%u: DSC enabled: version=%u.%u slice=%ux%u bpc=%u bpp=%u.%u\n",
+			dsi_bridge->intf_idx, dsc->dsc_version_major,
+			dsc->dsc_version_minor, dsc->slice_width,
+			dsc->slice_height, dsc->bits_per_component,
+			dsc->bits_per_pixel >> 4, (dsc->bits_per_pixel & 0xf) * 100 / 16);

I'd rather see this happen at the atomic_check state. We are trying to shift towards that model.

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 267

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +7 -0
+	}
+
+	ret = component_add(&dsi_bridge->dsi_host_dev,
+			    &dcp_dsi_component_ops);
+	if (ret)
+		return ret;
+	dsi_bridge->component_added = true;

What for? Can the bridge really be attached twice?

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 280

Quoted 13 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +13 -0
+
+	return 0;
+}
+
+static int dcp_dsi_host_detach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *dsi)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+
+	if (dsi_bridge->component_added) {
+		component_del(&dsi_bridge->dsi_host_dev,
+			      &dcp_dsi_component_ops);
+		dsi_bridge->component_added = false;

Can it be detached twice? How? Why?

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 284

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +4 -0
+	}
+	dsi_bridge->panel_bridge = NULL;
+	dsi_bridge->dsi_device = NULL;
+	dsi_bridge->hfi_panel_config.dsc_enabled = false;

Why do you need to NULL all of that?

Quoted 25 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +25 -0
+	return 0;
+}
+
+static ssize_t dcp_dsi_host_transfer(struct mipi_dsi_host *host,
+				     const struct mipi_dsi_msg *msg)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	u32 session_id;
+	bool temporary_session = false;
+	ssize_t ret;
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (!dsi_bridge->dcs_tx_buf)
+		return -ENODEV;
+
+	/* Serialize DCS transfers */
+	guard(mutex)(&dsi_bridge->dcs_lock);
+
+	if (dsi_bridge->session_open) {
+		session_id = dsi_bridge->session_id;
+	} else {
+		/*
+		 * mipi_dsi_host_ops::transfer() may be called while the host is
+		 * disabled. Open a transaction-only DCP session in that case;

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 319

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+		 * atomic_pre_enable() will create the full display session later.
+		 */
+		ret = dcp_hfi_display_enable(kms->hfi, dsi_bridge->intf_idx,
+					     dsi_bridge->fw_display_id,
+					     &session_id);
+		if (ret)
+			return ret;
+
+		temporary_session = true;
+	}

Nice

Quoted 202 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +202 -0
+
+	ret = dcp_hfi_dcs_transfer(kms->hfi, session_id, msg,
+				   dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova,
+				   dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova, 0,
+				   dsi_bridge->hfi_panel_config.ctrl_indices[0]);
+	if (temporary_session) {
+		int close_ret = dcp_hfi_display_close(kms->hfi, session_id);
+
+		if (ret >= 0 && close_ret)
+			ret = close_ret;
+	}
+	if (ret < 0)
+		dev_warn(kms->pm_dev,
+			 "DSI%u: live DCS command transfer failed: %zd\n",
+			 dsi_bridge->intf_idx, ret);
+
+	return ret;
+}
+
+static int dcp_dsi_component_bind(struct device *dev,
+				  struct device *master, void *data)
+{
+	struct dcp_dsi_bridge *dsi_bridge =
+		container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
+
+	dsi_bridge->panel_bridge =
+		devm_drm_of_get_bridge(dev, dsi_bridge->dcp_np,
+				       dsi_bridge->dcp_port, 0);
+	if (IS_ERR(dsi_bridge->panel_bridge)) {
+		int ret = PTR_ERR(dsi_bridge->panel_bridge);
+
+		dsi_bridge->panel_bridge = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dcp_dsi_component_unbind(struct device *dev,
+				     struct device *master, void *data)
+{
+	struct dcp_dsi_bridge *dsi_bridge =
+		container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
+
+	dsi_bridge->panel_bridge = NULL;
+}
+
+static const struct component_ops dcp_dsi_component_ops = {
+	.bind = dcp_dsi_component_bind,
+	.unbind = dcp_dsi_component_unbind,
+};
+
+static const struct mipi_dsi_host_ops dcp_dsi_host_ops = {
+	.attach   = dcp_dsi_host_attach,
+	.detach   = dcp_dsi_host_detach,
+	.transfer = dcp_dsi_host_transfer,
+};
+
+static int dcp_dsi_bridge_attach(struct drm_bridge *bridge,
+				 struct drm_encoder *encoder,
+				 enum drm_bridge_attach_flags flags)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct drm_bridge *panel_bridge;
+	int ret;
+
+	panel_bridge = dsi_bridge->panel_bridge;
+	if (!panel_bridge)
+		return -EPROBE_DEFER;
+
+	ret = drm_bridge_attach(encoder, panel_bridge, bridge, flags);
+	if (ret) {
+		drm_err(bridge->dev,
+			"DSI%u: panel bridge attach failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		return ret;
+	}
+
+	drm_dbg_driver(dsi_bridge->kms->dev,
+		       "DSI%u: panel bridge attached\n",
+		       dsi_bridge->intf_idx);
+
+	dcp_hfi_set_power_control_cb(dsi_bridge->kms->hfi,
+				     dsi_bridge->kms, dcp_dsi_power_control_cb);
+
+	return 0;
+}
+
+static u32 dcp_dsi_bridge_get_mixer_count(struct dcp_dsi_bridge *dsi_bridge,
+					  const struct dcp_dsi_hfi_panel_config *cfg,
+					  const struct drm_display_mode *mode)
+{
+	u32 mixer_count = cfg->topology_mixer_count;
+
+	if (!mixer_count)
+		mixer_count = 1;
+
+	if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+		const struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
+
+		if (dsc->slice_count)
+			mixer_count = dsc->slice_count;
+		else if (dsc->slice_width && mode->hdisplay)
+			mixer_count = DIV_ROUND_UP(mode->hdisplay,
+						   dsc->slice_width);
+	}
+
+	return mixer_count;
+}
+
+static void dcp_dsi_bridge_publish_session(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	struct drm_crtc *crtc;
+
+	crtc = dcp_dpu_kms_get_crtc(kms, dsi_bridge->fw_display_id);
+	if (crtc)
+		dcp_crtc_set_session(crtc, dsi_bridge->session_id);
+	else
+		drm_err(kms->dev, "DSI%u: no CRTC for session %u\n",
+			dsi_bridge->intf_idx, dsi_bridge->session_id);
+}
+
+static void dcp_dsi_bridge_clear_session(struct dcp_dsi_bridge *dsi_bridge)
+{
+	dcp_crtc_clear_session(dcp_dpu_kms_get_crtc(dsi_bridge->kms,
+						    dsi_bridge->fw_display_id));
+}
+
+static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
+				    struct dcp_dsi_hfi_panel_config *cfg,
+				    const struct drm_display_mode *mode)
+{
+	struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
+	enum drm_dsc_params_type params_type;
+	unsigned int i;
+	int ret;
+
+	dsc->pic_width = mode->hdisplay;
+	dsc->pic_height = mode->vdisplay;
+	dsc->convert_rgb = !dsc->native_422 && !dsc->native_420;
+	dsc->slice_chunk_size = DIV_ROUND_UP(dsc->slice_width *
+					dsc->bits_per_pixel, 8 * 16);
+	dsc->simple_422 = 0;
+	dsc->vbr_enable = 0;
+
+	if (dsc->dsc_version_major != 1)
+		return -EOPNOTSUPP;
+
+	switch (dsc->dsc_version_minor) {
+	case 1:
+		if (dsc->native_422 || dsc->native_420)
+			return -EOPNOTSUPP;
+		params_type = DRM_DSC_1_1_PRE_SCR;
+		break;
+	case 2:
+		if (dsc->native_422)
+			params_type = DRM_DSC_1_2_422;
+		else if (dsc->native_420)
+			params_type = DRM_DSC_1_2_420;
+		else
+			params_type = DRM_DSC_1_2_444;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	drm_dsc_set_const_params(dsc);
+	drm_dsc_set_rc_buf_thresh(dsc);
+	ret = drm_dsc_setup_rc_params(dsc, params_type);
+	if (ret)
+		return ret;
+
+	dsc->initial_scale_value = drm_dsc_initial_scale_value(dsc);
+	dsc->line_buf_depth = dsc->bits_per_component + 1;
+	ret = drm_dsc_compute_rc_parameters(dsc);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(cfg->dsc_rc_min_qp); i++) {
+		cfg->dsc_rc_min_qp[i] = dsc->rc_range_params[i].range_min_qp;
+		cfg->dsc_rc_max_qp[i] = dsc->rc_range_params[i].range_max_qp;
+		cfg->dsc_rc_bpg_offsets[i] = dsc->rc_range_params[i].range_bpg_offset;
+	}
+
+	return 0;
+}
+
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+				   const struct drm_display_mode *mode)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	struct dcp_dsi_bridge_state *state = dcp_dsi_bridge_get_state(dsi_bridge);
+	struct dcp_dsi_hfi_panel_config cfg;
+	int ret;
+	u32 opened_session_id;
+
+	if (!mode)
+		return -ENODEV;
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (dsi_bridge->session_open)

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 531

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+		return 0;
+
+	dsi_bridge->session_id = dsi_bridge->fw_display_id;
+
+	if (!dsi_bridge->dsi_device)
+		return -ENODEV;
+
+	dcp_dsi_bridge_build_hfi_config(dsi_bridge, state, &cfg);
+
+	if (dsi_bridge->dsi_device->dsc) {

When is this going to be called? There are panels which require different DSC setup for different modes.

Quoted 16 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +16 -0
+		ret = dcp_dsi_bridge_setup_dsc(dsi_bridge, &cfg, mode);
+		if (ret) {
+			drm_err(kms->dev, "DSI%u: DSC setup failed: %d\n",
+				dsi_bridge->intf_idx, ret);
+			goto err_close;
+		}
+	}
+
+	ret = dcp_hfi_panel_init(kms->hfi, dsi_bridge->intf_idx,
+				 mode,
+				 dsi_bridge->dsi_device,
+				 &cfg);
+	if (ret) {
+		drm_err(kms->dev, "DSI%u: HFI PANEL_INIT failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 557

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+	}
+
+	if (mode->clock) {
+		u64 link_clock;
+
+		link_clock = dcp_dsi_link_clock(dsi_bridge, &cfg,
+						mode);
+		ret = dcp_hfi_display_mode_validate(kms->hfi,
+						    dsi_bridge->session_id,
+						    mode, link_clock);

mode validation should have happened at the atomic_check().

Quoted 13 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +13 -0
+		if (ret) {
+			drm_err(kms->dev, "DSI%u: HFI MODE_VALIDATE failed: %d\n",
+				dsi_bridge->intf_idx, ret);
+			goto err_close;
+		}
+
+		ret = dcp_hfi_display_set_mode(kms->hfi,
+					       dsi_bridge->session_id,
+					       mode,
+					       dcp_dsi_link_clock(dsi_bridge, &cfg,
+								  mode));
+		if (ret) {
+			drm_err(kms->dev, "DSI%u: HFI SET_MODE failed: %d\n",

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 580

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+				dsi_bridge->intf_idx, ret);
+			goto err_close;
+		}
+	} else {
+		drm_warn(kms->dev, "DSI%u: no valid mode, skipping SET_MODE\n",
+			 dsi_bridge->intf_idx);
+	}
+
+	dcp_hfi_set_power_control_cb(kms->hfi, kms,
+				     dcp_dsi_power_control_cb);

And what happens when you add DP?

Quoted 103 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +103 -0
+
+	reinit_completion(&dsi_bridge->power_done);
+
+	ret = dcp_hfi_display_power_register(kms->hfi, dsi_bridge->session_id);
+	if (ret) {
+		drm_err(kms->dev, "DSI%u: HFI POWER_REGISTER failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;
+	}
+
+	ret = dcp_hfi_display_enable(kms->hfi,
+				     dsi_bridge->intf_idx,
+				     dsi_bridge->fw_display_id,
+				     &opened_session_id);
+	if (ret) {
+		drm_err(kms->dev, "DSI%u: HFI DISPLAY_ENABLE failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;
+	}
+	dsi_bridge->session_id   = opened_session_id;
+	dsi_bridge->session_open = true;
+	dcp_dsi_bridge_publish_session(dsi_bridge);
+
+	drm_dbg_driver(kms->dev, "DSI%u: session prepared (session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_id);
+	return 0;
+
+err_close:
+	dcp_hfi_clear_power_control_cb(kms->hfi, kms);
+	dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+	dsi_bridge->session_id   = 0;
+	dsi_bridge->session_open = false;
+	dcp_dsi_bridge_clear_session(dsi_bridge);
+
+	return ret;
+}
+
+static void dcp_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+					     struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	int ret;
+
+	ret = dsi_bridge->session_open ? 0 : -ENODEV;
+	dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
+	if (ret)
+		drm_err(bridge->dev, "DSI%u: session prepare failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
+					 struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	bool scan_start_registered = false;
+	bool scan_complete_registered = false;
+	bool autorefresh_enabled = false;
+	int ret;
+
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: bridge atomic_enable (session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_id);
+
+	if (dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error)
+		return;
+	if (!dsi_bridge->session_open)
+		return;
+
+	ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);
+	if (ret) {
+		drm_err(bridge->dev,
+			"DSI%u: HFI DISPLAY_POST_ENABLE failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;
+	}
+
+	dcp_dsi_bridge_publish_session(dsi_bridge);
+
+	ret = dcp_hfi_display_event_register(kms->hfi,
+					     dsi_bridge->session_id, HFI_EVENT_FRAME_SCAN_START);
+	if (ret)
+		goto err_close;
+	scan_start_registered = true;
+
+	ret = dcp_hfi_display_event_register(kms->hfi,
+					     dsi_bridge->session_id, HFI_EVENT_FRAME_SCAN_COMPLETE);
+	if (ret)
+		goto err_close;
+	scan_complete_registered = true;
+
+	if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
+		ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
+						   dsi_bridge->session_id, true, 1);
+		if (ret) {
+			drm_err(bridge->dev,
+				"DSI%u: initial autorefresh setup failed: %d\n",
+				dsi_bridge->intf_idx, ret);
+			goto err_close;
+		}
+		autorefresh_enabled = true;
+	}
+

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 693

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+	drm_dbg_driver(bridge->dev,
+		       "DSI%u: enable complete (session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_id);
+	return;
+
+err_close:
+	if (autorefresh_enabled)
+		dcp_hfi_crtc_set_autorefresh(kms->hfi, dsi_bridge->session_id,
+					     false, 0);
+	if (scan_complete_registered)

Turn each of the booleans into a separate label.

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 704

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +11 -0
+		dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+						 HFI_EVENT_FRAME_SCAN_COMPLETE);
+	if (scan_start_registered)
+		dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+						 HFI_EVENT_FRAME_SCAN_START);
+	drm_err(bridge->dev,
+		"DSI%u: bridge enable failed; session closed\n",
+		dsi_bridge->intf_idx);
+	dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+	{
+		guard(mutex)(&dsi_bridge->session_lock);

scoped_guard()

Quoted 119 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +119 -0
+		dsi_bridge->session_id   = 0;
+		dsi_bridge->session_open = false;
+	}
+	dcp_dsi_bridge_clear_session(dsi_bridge);
+}
+
+static void dcp_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
+					  struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	int ret;
+
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: atomic_disable (session_open=%d session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_open,
+		       dsi_bridge->session_id);
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (!dsi_bridge->session_open)
+		return;
+
+	if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
+		ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
+						   dsi_bridge->session_id, false, 0);
+		if (ret)
+			drm_warn(bridge->dev,
+				 "DSI%u: autorefresh disable failed: %d\n",
+				 dsi_bridge->intf_idx, ret);
+	}
+
+	dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+					 HFI_EVENT_FRAME_SCAN_COMPLETE);
+	dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+					 HFI_EVENT_FRAME_SCAN_START);
+
+	ret = dcp_hfi_display_disable(kms->hfi, dsi_bridge->session_id);
+	if (ret)
+		drm_warn(bridge->dev,
+			 "DSI%u: HFI DISPLAY_DISABLE failed: %d\n",
+			 dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
+					       struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	int ret;
+
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: atomic_post_disable\n",
+		       dsi_bridge->intf_idx);
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (!dsi_bridge->session_open)
+		return;
+
+	ret = dcp_hfi_display_post_disable(kms->hfi, dsi_bridge->session_id);
+	if (ret)
+		drm_warn(bridge->dev,
+			 "DSI%u: HFI DISPLAY_POST_DISABLE failed: %d\n",
+			 dsi_bridge->intf_idx, ret);
+
+	ret = dcp_hfi_send_sync(kms->hfi,
+				HFI_COMMAND_DISPLAY_CLOSE,
+				HFI_CMD_BUFF_DISPLAY, dsi_bridge->session_id,
+				HFI_PAYLOAD_NONE, NULL, 0,
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				2000 /* ms */);
+	if (ret)
+		drm_warn(bridge->dev,
+			 "DSI%u: HFI DISPLAY_CLOSE failed: %d\n",
+			 dsi_bridge->intf_idx, ret);
+
+	dsi_bridge->session_id   = 0;
+	dsi_bridge->session_open = false;
+
+	dcp_dsi_bridge_clear_session(dsi_bridge);
+}
+
+static int dcp_dsi_bridge_atomic_check(struct drm_bridge *bridge,
+				       struct drm_bridge_state *bridge_state,
+				       struct drm_crtc_state *crtc_state,
+				       struct drm_connector_state *conn_state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dsi_bridge_state *state =
+		to_dcp_dsi_bridge_state(bridge_state);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+	u32 max_width, max_height;
+	u32 mixer_count, bpc;
+
+	state->prepare_error = 0;
+
+	if (!mode->clock || !drm_mode_vrefresh(mode))
+		return -EINVAL;
+
+	if (dsi_bridge->dsi_device &&
+	    (!dsi_bridge->dsi_device->lanes ||
+	     !dcp_dsi_link_clock(dsi_bridge, &dsi_bridge->hfi_panel_config, mode)))
+		return -EINVAL;
+
+	if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+		state->panel_state.topology_mixer_count =
+			dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+						       &dsi_bridge->hfi_panel_config, mode);
+		state->panel_state.topology_enc_count =
+			state->panel_state.topology_mixer_count;
+		state->panel_state.topology_intf_count = 1;
+	}
+
+	mixer_count = dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+						     &dsi_bridge->hfi_panel_config, mode);
+	bpc = dsi_bridge->hfi_panel_config.dsc_bits_per_component ?
+		dsi_bridge->hfi_panel_config.dsc_bits_per_component : 8;

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 833

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+	dcp_crtc_set_topology_state(crtc_state, mixer_count, bpc);
+
+	dcp_mode_limits(mode, kms->caps->max_width, kms->caps->max_height,
+			&max_width, &max_height);
+
+	if (mode->hdisplay > max_width || mode->vdisplay > max_height) {
+		drm_err(dsi_bridge->kms->dev,
+			"DSI%u: mode %dx%d exceeds DCP caps %dx%d\n",
+				 dsi_bridge->intf_idx, mode->hdisplay, mode->vdisplay,
+				 kms->caps->max_width, kms->caps->max_height);

Don't let userspace spam the dmesg.

Quoted 368 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +277 -0
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static const struct drm_bridge_funcs dcp_dsi_bridge_funcs = {
+	.attach                  = dcp_dsi_bridge_attach,
+	.atomic_pre_enable       = dcp_dsi_bridge_atomic_pre_enable,
+	.atomic_enable           = dcp_dsi_bridge_atomic_enable,
+	.atomic_disable          = dcp_dsi_bridge_atomic_disable,
+	.atomic_post_disable     = dcp_dsi_bridge_atomic_post_disable,
+	.atomic_check            = dcp_dsi_bridge_atomic_check,
+	.atomic_create_state     = dcp_dsi_bridge_atomic_create_state,
+	.atomic_duplicate_state  = dcp_dsi_bridge_atomic_duplicate_state,
+	.atomic_destroy_state    = dcp_dsi_bridge_atomic_destroy_state,
+};
+
+#define DCP_DSI_DUAL_MAP_ALIGN	SZ_1K
+
+static int dcp_dsi_dual_map_buf(struct dcp_dpu_kms *kms,
+				struct device *dma_dev, void *cpu_addr,
+				dma_addr_t iova, size_t size, int prot)
+{
+	struct sg_table sgt = { 0 };
+	struct iommu_domain *domain;
+	ssize_t mapped;
+	int ret;
+
+	if (!kms->iommu_dev || !cpu_addr || !iova ||
+	    !IS_ALIGNED(iova, DCP_DSI_DUAL_MAP_ALIGN))
+		return -EINVAL;
+
+	domain = iommu_get_domain_for_dev(kms->iommu_dev);
+	if (!domain)
+		return -ENODEV;
+
+	ret = dma_get_sgtable(dma_dev, &sgt, cpu_addr, iova, size);
+	if (ret)
+		return ret;
+
+	mapped = iommu_map_sgtable(domain, iova, &sgt, prot);
+	if (mapped != PAGE_ALIGN(size)) {
+		if (mapped > 0)
+			iommu_unmap(domain, iova, mapped);
+		ret = mapped < 0 ? mapped : -EIO;
+		drm_err(kms->dev,
+			"failed to dual-map iova=%pad into iommu_dev domain: %zd\n",
+			&iova, mapped);
+		sg_free_table(&sgt);
+		return ret;
+	}
+
+	sg_free_table(&sgt);
+	return 0;
+}
+
+static void dcp_dsi_dual_unmap_buf(struct dcp_dpu_kms *kms, dma_addr_t iova,
+				   size_t size)
+{
+	struct iommu_domain *domain;
+
+	if (!kms->iommu_dev || !iova)
+		return;
+
+	domain = iommu_get_domain_for_dev(kms->iommu_dev);
+	if (!domain)
+		return;
+
+	iommu_unmap(domain, iova, PAGE_ALIGN(size));
+}
+
+static void dcp_dsi_init_hfi_panel_defaults(struct dcp_dsi_hfi_panel_config *cfg,
+					    u32 intf_idx)
+{
+	memset(cfg, 0, sizeof(*cfg));
+
+	cfg->display_type = HFI_PANEL_DISPLAY_TYPE_BUILT_IN_0;
+	cfg->num_ctrls = 1;
+	cfg->ctrl_indices[0] = intf_idx;
+	cfg->num_phys = 1;
+	cfg->phy_indices[0] = intf_idx;
+
+	cfg->dma_trigger = HFI_PANEL_TRIGGER_SW;
+	cfg->stream_trigger = HFI_PANEL_TRIGGER_NONE;
+	cfg->te_dcs_command = 1;
+	cfg->wr_mem_start = 0x2C;
+	cfg->wr_mem_continue = 0x3C;
+
+	cfg->phy_type = HFI_PANEL_PHY_OLED;
+	cfg->bllp_eof_power_mode = true;
+	cfg->bllp_power_mode = true;
+	cfg->lane_map = 1;
+	cfg->color_order = 1;
+
+	cfg->jitter_numer = 4;
+	cfg->jitter_denom = 1;
+	cfg->dcs_tx_buf_size = DCP_DSI_DCS_TX_BUF_SIZE;
+
+	cfg->topology_mixer_count = 1;
+	cfg->topology_intf_count = 1;
+}
+
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+				       struct device_node *np,
+				       struct device_node *dcp_np,
+				       u32 dcp_port, u32 intf_idx,
+				       u32 fw_display_id)
+{
+	struct dcp_dsi_bridge *dsi_bridge;
+	struct dcp_dsi_hfi_panel_config *cfg;
+	struct device *dev = kms->pm_dev;
+	struct device *dma_dev = kms->hfi->dma_dev;
+	int ret;
+
+	dsi_bridge = devm_drm_bridge_alloc(dev, struct dcp_dsi_bridge,
+					   bridge, &dcp_dsi_bridge_funcs);
+	if (IS_ERR(dsi_bridge))
+		return ERR_CAST(dsi_bridge);
+
+	dsi_bridge->dcp_np        = of_node_get(dcp_np);
+	dsi_bridge->dcp_port      = dcp_port;
+	dsi_bridge->kms           = kms;
+	dsi_bridge->intf_idx      = intf_idx;
+	dsi_bridge->fw_display_id = fw_display_id;
+
+	INIT_WORK(&dsi_bridge->power_control_work, dcp_dsi_power_control_work);
+	init_completion(&dsi_bridge->power_done);
+	mutex_init(&dsi_bridge->session_lock);
+	mutex_init(&dsi_bridge->dcs_lock);
+
+	cfg = &dsi_bridge->hfi_panel_config;
+	dcp_dsi_init_hfi_panel_defaults(cfg, intf_idx);
+
+	/*
+	 * Allocate the buffers for the firmware DMA domain here. The second
+	 * mapping into the display domain is deferred until msm_kms_init_vm()
+	 * has installed the final display IOMMU domain.
+	 */
+	dsi_bridge->dcs_tx_buf = dma_alloc_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+						    &dsi_bridge->dcs_tx_iova, GFP_KERNEL);
+	if (!dsi_bridge->dcs_tx_buf) {
+		ret = -ENOMEM;
+		goto err_unwind_dma;
+	}
+
+	dsi_bridge->hfi_panel_config.dcs_tx_dva = dsi_bridge->dcs_tx_iova;
+	dsi_bridge->hfi_panel_config.dcs_tx_iova = dsi_bridge->dcs_tx_iova;
+
+	dsi_bridge->dcs_rx_buf = dma_alloc_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+						    &dsi_bridge->dcs_rx_iova, GFP_KERNEL);
+	if (!dsi_bridge->dcs_rx_buf) {
+		ret = -ENOMEM;
+		goto err_unwind_dma;
+	}
+
+	device_initialize(&dsi_bridge->dsi_host_dev);
+	dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
+	dsi_bridge->dsi_host_dev.parent  = dev;
+	dsi_bridge->dsi_host_dev.of_node = of_node_get(np);
+	dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np);
+	dsi_bridge->dsi_host.ops = &dcp_dsi_host_ops;
+	dsi_bridge->dsi_host.dev = &dsi_bridge->dsi_host_dev;
+
+	ret = device_add(&dsi_bridge->dsi_host_dev);
+	if (ret) {
+		dev_err(dev,
+			"DSI%u: failed to add dsi_host_dev: %d\n", intf_idx, ret);
+		put_device(&dsi_bridge->dsi_host_dev);
+		goto err_unwind_dma;
+	}
+
+	ret = mipi_dsi_host_register(&dsi_bridge->dsi_host);
+	if (ret) {
+		dev_err(dev,
+			"DSI%u: failed to register MIPI DSI host: %d\n",
+			intf_idx, ret);
+		device_del(&dsi_bridge->dsi_host_dev);
+		put_device(&dsi_bridge->dsi_host_dev);
+		goto err_unwind_dma;
+	}
+
+	dsi_bridge->bridge.of_node = of_node_get(np);
+	dsi_bridge->bridge.type    = DRM_MODE_CONNECTOR_DSI;
+
+	drm_bridge_add(&dsi_bridge->bridge);
+
+	return &dsi_bridge->bridge;
+
+err_unwind_dma:
+	if (dsi_bridge->dcs_rx_buf) {
+		dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+				  dsi_bridge->dcs_rx_buf,
+				  dsi_bridge->dcs_rx_iova);
+	}
+	if (dsi_bridge->dcs_tx_buf) {
+		dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+				  dsi_bridge->dcs_tx_buf,
+				  dsi_bridge->dcs_tx_iova);
+	}
+	cancel_work_sync(&dsi_bridge->power_control_work);
+	mutex_destroy(&dsi_bridge->session_lock);
+	mutex_destroy(&dsi_bridge->dcs_lock);
+	return ERR_PTR(ret);
+}
+
+int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	struct device *dma_dev = kms->hfi->dma_dev;
+	int ret;
+
+	if (dsi_bridge->dcs_mapped)
+		return 0;
+
+	ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_tx_buf,
+				   dsi_bridge->dcs_tx_iova,
+				   DCP_DSI_DCS_TX_BUF_SIZE, IOMMU_READ);
+	if (ret)
+		return ret;
+
+	ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_rx_buf,
+				   dsi_bridge->dcs_rx_iova,
+				   DCP_DSI_DCS_RX_BUF_SIZE, IOMMU_WRITE);
+	if (ret) {
+		dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+				       DCP_DSI_DCS_TX_BUF_SIZE);
+		return ret;
+	}
+
+	dsi_bridge->dcs_mapped = true;
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: mapped DCS buffers tx=%pad rx=%pad in display domain\n",
+		       dsi_bridge->intf_idx, &dsi_bridge->dcs_tx_iova,
+		       &dsi_bridge->dcs_rx_iova);
+
+	return 0;
+}
+
+void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+	if (!dsi_bridge->dcs_mapped)
+		return;
+
+	dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+			       DCP_DSI_DCS_TX_BUF_SIZE);
+	dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_rx_iova,
+			       DCP_DSI_DCS_RX_BUF_SIZE);
+	dsi_bridge->dcs_mapped = false;
+}
+
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct device *dma_dev = dsi_bridge->kms->hfi->dma_dev;
+
+	dcp_hfi_clear_power_control_cb(dsi_bridge->kms->hfi, dsi_bridge->kms);
+	cancel_work_sync(&dsi_bridge->power_control_work);
+
+	drm_bridge_remove(&dsi_bridge->bridge);
+	of_node_put(dsi_bridge->bridge.of_node);
+	of_node_put(dsi_bridge->dcp_np);
+
+	mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
+	device_del(&dsi_bridge->dsi_host_dev);
+	put_device(&dsi_bridge->dsi_host_dev);
+
+	dcp_dsi_bridge_unmap_buffers(dsi_bridge);
+	dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+			  dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova);
+
+	dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+			  dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova);
+
+	mutex_destroy(&dsi_bridge->session_lock);
+	mutex_destroy(&dsi_bridge->dcs_lock);
+}
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h new file +76 -0
new file mode 100644
index 000000000000..f2c7abf23ac9
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI bridge state and panel configuration.
+ * DCP firmware owns the DSI PHY and controller; the bridge submits HFI commands.
+ */
+
+#ifndef __DCP_DSI_BRIDGE_H__
+#define __DCP_DSI_BRIDGE_H__
+
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/ktime.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
+#include "dcp_dsi_panel.h"
+
+struct dcp_dpu_kms;
+struct device_node;
+
+struct dcp_dsi_bridge {
+	struct drm_bridge    bridge;
+	struct device        dsi_host_dev;
+	struct mipi_dsi_host  dsi_host;
+	struct mipi_dsi_device *dsi_device;
+	struct drm_bridge *panel_bridge;
+	bool component_added;
+
+	struct dcp_dpu_kms  *kms;
+	struct device_node  *dcp_np;
+	u32                  dcp_port;
+	u32                  intf_idx;
+	u32                  fw_display_id;
+	u32                  session_id;
+	bool                 session_open;
+	/* Protects session_id and session_open. */
+	struct mutex         session_lock;
+
+	struct dcp_dsi_hfi_panel_config hfi_panel_config;
+	/* Serializes DCS transfers. */
+	struct mutex dcs_lock;
+
+	void       *dcs_tx_buf;
+	dma_addr_t  dcs_tx_iova;
+	void       *dcs_rx_buf;
+	dma_addr_t  dcs_rx_iova;
+	bool         dcs_mapped;
+	struct work_struct power_control_work;
+	struct completion  power_done;
+	u32                power_packet_id;
+};
+
+#define to_dcp_dsi_host(h) \
+	container_of(h, struct dcp_dsi_bridge, dsi_host)
+
+#define to_dcp_dsi_bridge(b) \
+	container_of(b, struct dcp_dsi_bridge, bridge)
+
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+				       struct device_node *np,
+				       struct device_node *dcp_np,
+				       u32 dcp_port, u32 intf_idx,
+				       u32 fw_display_id);
+int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge);
+void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge);
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+				   const struct drm_display_mode *mode);
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge);
+
+#endif /* __DCP_DSI_BRIDGE_H__ */
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h +1 -0
index a8898e469b44..cc4e9f4c6fc4 100644
@@ -74,6 +74,19 @@ struct dcp_dsi_hfi_panel_config {
 	u32 topology_intf_count;
 };
 
+/**

drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h struct dcp_dsi_hfi_panel_config { · line 87

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h +10 -0
+ * struct dcp_dsi_panel_state - Mode-dependent DSI panel state.
+ *
+ * This state is owned by a DRM atomic bridge state. It is combined with
+ * the static panel configuration when constructing the HFI PANEL_INIT
+ * payload.
+ */
+struct dcp_dsi_panel_state {
+	u32 topology_mixer_count;
+	u32 topology_enc_count;
+	u32 topology_intf_count;

Why is it public? Who uses it?

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h +2 -0
+};
+
 /* DCS buffers use IOVAs directly in the firmware command descriptor. */
 #define DCP_DSI_DCS_TX_BUF_SIZE	SZ_4K
 #define DCP_DSI_DCS_RX_BUF_SIZE	SZ_4K
Signature · 2 lines
-- 
2.43.0
Signature · 2 lines
With best wishes
Dmitry
#40 lore raw

Re: [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support

Quoted 290 lines · Dmitry Baryshkov
Quoted 289 lines · Jayant Shekhar

Expose the DCP DSI host and panel as DRM-facing objects. The DCP display path needs a host, panel configuration, bridge state, DSC setup, DCS transfer buffers, and panel lifetime management before it can drive a physical display.

Implement the MIPI DSI host, panel-device support, bridge state, DSC configuration, DCS transfers, and DSI buffer management.

Assisted-by LLM
---
  drivers/gpu/drm/msm/Makefile             |    1 +
  drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1110 ++++++++++++++++++++++++++++++
  drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h |   76 ++
  drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h  |   13 +
  4 files changed, 1200 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index 4024f6b1e3cf..9f0a9bfeb2a7 100644
@@ -75,6 +75,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
  	dcp/dcp_crtc.o \
  	dcp/dcp_dpu_kms.o \
  	dcp/dcp_plane.o \
+	dcp/dcp_dsi_bridge.o \
  	dcp/dcp_hfi_core.o \
  	dcp/dcp_hfi_crtc.o \
  	dcp/dcp_hfi_dsi.o \
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +254 -0
new file mode 100644
index 000000000000..b3e782b8f047
@@ -0,0 +1,1110 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI bridge driver.
+ *
+ * Implements a DRM bridge for DSI displays in the DCP firmware-delegated
+ * architecture. DCP firmware owns the DSI PHY and controller; this driver
+ * uses HFI commands to configure DSI parameters rather than direct register
+ * writes.
+ */
+
+#include <linux/component.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/jiffies.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+
+#include <video/mipi_display.h>
+
+#include "dcp_crtc.h"
+#include "dcp_dsi_bridge.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "hfi/hfi_defs_panel.h"
+
+struct dcp_dsi_bridge_state {
+	struct drm_bridge_state base;
+	struct dcp_dsi_panel_state panel_state;
+	int prepare_error;
+};
+
+#define to_dcp_dsi_bridge_state(s) \
+	container_of(s, struct dcp_dsi_bridge_state, base)
+
+static struct dcp_dsi_bridge_state *
+dcp_dsi_bridge_get_state(struct dcp_dsi_bridge *bridge)
+{
+	return to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->bridge.base.state));
+}
+
+static struct drm_bridge_state *
+dcp_dsi_bridge_atomic_create_state(struct drm_bridge *bridge)
+{
+	struct dcp_dsi_bridge_state *state;
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+
+	state = kzalloc_obj(*state);
+	if (!state)
+		return ERR_PTR(-ENOMEM);
+
+	__drm_atomic_helper_bridge_state_init(&state->base, bridge);
+	state->panel_state.topology_mixer_count =
+		dsi_bridge->hfi_panel_config.topology_mixer_count;
+	state->panel_state.topology_enc_count =
+		dsi_bridge->hfi_panel_config.topology_enc_count;
+	state->panel_state.topology_intf_count =
+		dsi_bridge->hfi_panel_config.topology_intf_count;
+	return &state->base;
+}
+
+static struct drm_bridge_state *
+dcp_dsi_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
+{
+	struct dcp_dsi_bridge_state *state, *old_state;
+
+	if (WARN_ON(!bridge->base.state))
+		return NULL;
+
+	state = kzalloc_obj(*state);
+	if (!state)
+		return NULL;
+
+	old_state = to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->base.state));
+	__drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
+	state->panel_state = old_state->panel_state;
+	return &state->base;
+}
+
+static void dcp_dsi_bridge_atomic_destroy_state(struct drm_bridge *bridge,
+						struct drm_bridge_state *bridge_state)
+{
+	kfree(to_dcp_dsi_bridge_state(bridge_state));
+}
+
+static void
+dcp_dsi_bridge_build_hfi_config(struct dcp_dsi_bridge *dsi_bridge,
+				const struct dcp_dsi_bridge_state *state,
+				struct dcp_dsi_hfi_panel_config *cfg)
+{
+	*cfg = dsi_bridge->hfi_panel_config;
+	cfg->topology_mixer_count = state->panel_state.topology_mixer_count;
+	cfg->topology_enc_count = state->panel_state.topology_enc_count;
+	cfg->topology_intf_count = state->panel_state.topology_intf_count;
+}
+
+static void dcp_dsi_host_dev_release(struct device *dev)
+{
+	of_node_put(dev->of_node);
+}
+
+static bool dcp_dsi_bridge_cmd_mode(const struct dcp_dsi_bridge *dsi_bridge)
+{
+	return dsi_bridge->dsi_device &&
+		!(dsi_bridge->dsi_device->mode_flags & MIPI_DSI_MODE_VIDEO);
+}
+
+static u64 dcp_dsi_link_clock(const struct dcp_dsi_bridge *dsi_bridge,
+			      const struct dcp_dsi_hfi_panel_config *cfg,
+			      const struct drm_display_mode *mode)
+{
+	const struct mipi_dsi_device *dsi = dsi_bridge->dsi_device;
+	int bpp;
+	u32 bpp_x16;
+
+	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+	if (bpp < 0)
+		return 0;
+
+	bpp_x16 = cfg->dsc_enabled && cfg->dsc_bits_per_pixel ?
+		cfg->dsc_bits_per_pixel : bpp << 4;
+
+	if (dsi->hs_rate)
+		return dsi->hs_rate;
+
+	return div_u64((u64)mode->clock * 1000 * bpp_x16, dsi->lanes * 16);
+}
+
+static void dcp_dsi_power_control_work(struct work_struct *work)
+{
+	struct dcp_dsi_bridge *dsi_bridge =
+		container_of(work, struct dcp_dsi_bridge, power_control_work);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+	dcp_hfi_display_power_control_ack(kms->hfi,
+					  dsi_bridge->session_id,
+					  dsi_bridge->power_packet_id);
+
+	complete(&dsi_bridge->power_done);
+}
+
+static void dcp_dsi_power_control_cb(void *priv, u32 display_id,
+				     u32 power_mask, bool enable,
+				     u32 packet_id)
+{
+	struct dcp_dpu_kms *kms = priv;
+	struct dcp_dsi_bridge *dsi_bridge = NULL;
+	int idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(kms->dsi_bridge); idx++) {
+		struct dcp_dsi_bridge *b = kms->dsi_bridge[idx];
+
+		if (b && b->session_id == display_id) {
+			dsi_bridge = b;
+			break;
+		}
+	}
+
+	if (!dsi_bridge) {
+		dev_warn(kms->pm_dev,
+			 "DSI: power_control for unknown display_id=%u\n", display_id);
+		return;
+	}
+
+	dsi_bridge->power_packet_id = packet_id;
+	schedule_work(&dsi_bridge->power_control_work);
+}
+
+static const struct component_ops dcp_dsi_component_ops;
+
+static int dcp_dsi_host_attach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *dsi)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+	struct dcp_dsi_hfi_panel_config *cfg = &dsi_bridge->hfi_panel_config;
+	int ret;
+
+	if (!dsi->lanes || dsi->lanes > 4)
+		return -EINVAL;
+
+	cfg->dsc_enabled = false;
+	cfg->dsc_version_major = 0;
+	cfg->dsc_version_minor = 0;
+	cfg->dsc_scr_version = 0;
+	cfg->dsc_slice_height = 0;
+	cfg->dsc_slice_width = 0;
+	cfg->dsc_slices_per_pkt = 0;
+	cfg->dsc_bits_per_component = 0;
+	cfg->dsc_pps_delay_ms = 0;
+	cfg->dsc_bits_per_pixel = 0;
+	cfg->dsc_chroma_format = 0;
+	cfg->dsc_color_space = 0;
+	cfg->dsc_block_prediction_enable = 0;
+	cfg->dsc_rc_override = false;
+	memset(cfg->dsc_rc_min_qp, 0, sizeof(cfg->dsc_rc_min_qp));
+	memset(cfg->dsc_rc_max_qp, 0, sizeof(cfg->dsc_rc_max_qp));
+	memset(cfg->dsc_rc_bpg_offsets, 0, sizeof(cfg->dsc_rc_bpg_offsets));
+
+	switch (dsi->format) {
+	case MIPI_DSI_FMT_RGB888:
+	case MIPI_DSI_FMT_RGB666:
+	case MIPI_DSI_FMT_RGB666_PACKED:
+	case MIPI_DSI_FMT_RGB565:
+	case MIPI_DSI_FMT_RGB101010:
+		break;
+	default:
+		dev_err(&dsi->dev, "unsupported pixel format %u\n", dsi->format);
+		return -EINVAL;
+	}
+
+	dsi_bridge->dsi_device = dsi;
+
+	if (dsi->dsc) {
+		struct drm_dsc_config *dsc = dsi->dsc;
+		u32 chroma_format = 0;
+		u32 color_space = 0;
+
+		if (dsc->native_422)
+			chroma_format = 1;
+		else if (dsc->native_420)
+			chroma_format = 2;
+		if (dsc->native_422 || dsc->native_420)
+			color_space = 1;
+
+		cfg->dsc_enabled                 = true;
+		cfg->dsc_version_major           = dsc->dsc_version_major;
+		cfg->dsc_version_minor           = dsc->dsc_version_minor;
+		cfg->dsc_scr_version              = 0;
+		cfg->dsc_slice_height             = dsc->slice_height;
+		cfg->dsc_slice_width              = dsc->slice_width;
+		cfg->dsc_slices_per_pkt           = 1;
+		cfg->dsc_bits_per_component       = dsc->bits_per_component;
+		cfg->dsc_pps_delay_ms             = 0;
+		cfg->dsc_bits_per_pixel           = dsc->bits_per_pixel;
+		cfg->dsc_chroma_format            = chroma_format;
+		cfg->dsc_color_space              = color_space;
+		cfg->dsc_block_prediction_enable  = dsc->block_pred_enable;
+
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 260

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +6 -0
+		dev_dbg(&dsi->dev,
+			"DSI%u: DSC enabled: version=%u.%u slice=%ux%u bpc=%u bpp=%u.%u\n",
+			dsi_bridge->intf_idx, dsc->dsc_version_major,
+			dsc->dsc_version_minor, dsc->slice_width,
+			dsc->slice_height, dsc->bits_per_component,
+			dsc->bits_per_pixel >> 4, (dsc->bits_per_pixel & 0xf) * 100 / 16);

I'd rather see this happen at the atomic_check state. We are trying to shift towards that model.

Sure, will update in v3

Quoted 274 lines

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 267

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +7 -0
+	}
+
+	ret = component_add(&dsi_bridge->dsi_host_dev,
+			    &dcp_dsi_component_ops);
+	if (ret)
+		return ret;
+	dsi_bridge->component_added = true;

What for? Can the bridge really be attached twice?

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 280

Quoted 13 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +13 -0
+
+	return 0;
+}
+
+static int dcp_dsi_host_detach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *dsi)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+
+	if (dsi_bridge->component_added) {
+		component_del(&dsi_bridge->dsi_host_dev,
+			      &dcp_dsi_component_ops);
+		dsi_bridge->component_added = false;

Can it be detached twice? How? Why?

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 284

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +4 -0
+	}
+	dsi_bridge->panel_bridge = NULL;
+	dsi_bridge->dsi_device = NULL;
+	dsi_bridge->hfi_panel_config.dsc_enabled = false;

Why do you need to NULL all of that?

Quoted 25 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +25 -0
+	return 0;
+}
+
+static ssize_t dcp_dsi_host_transfer(struct mipi_dsi_host *host,
+				     const struct mipi_dsi_msg *msg)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	u32 session_id;
+	bool temporary_session = false;
+	ssize_t ret;
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (!dsi_bridge->dcs_tx_buf)
+		return -ENODEV;
+
+	/* Serialize DCS transfers */
+	guard(mutex)(&dsi_bridge->dcs_lock);
+
+	if (dsi_bridge->session_open) {
+		session_id = dsi_bridge->session_id;
+	} else {
+		/*
+		 * mipi_dsi_host_ops::transfer() may be called while the host is
+		 * disabled. Open a transaction-only DCP session in that case;

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 319

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+		 * atomic_pre_enable() will create the full display session later.
+		 */
+		ret = dcp_hfi_display_enable(kms->hfi, dsi_bridge->intf_idx,
+					     dsi_bridge->fw_display_id,
+					     &session_id);
+		if (ret)
+			return ret;
+
+		temporary_session = true;
+	}

Nice

Quoted 207 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +207 -0
+
+	ret = dcp_hfi_dcs_transfer(kms->hfi, session_id, msg,
+				   dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova,
+				   dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova, 0,
+				   dsi_bridge->hfi_panel_config.ctrl_indices[0]);
+	if (temporary_session) {
+		int close_ret = dcp_hfi_display_close(kms->hfi, session_id);
+
+		if (ret >= 0 && close_ret)
+			ret = close_ret;
+	}
+	if (ret < 0)
+		dev_warn(kms->pm_dev,
+			 "DSI%u: live DCS command transfer failed: %zd\n",
+			 dsi_bridge->intf_idx, ret);
+
+	return ret;
+}
+
+static int dcp_dsi_component_bind(struct device *dev,
+				  struct device *master, void *data)
+{
+	struct dcp_dsi_bridge *dsi_bridge =
+		container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
+
+	dsi_bridge->panel_bridge =
+		devm_drm_of_get_bridge(dev, dsi_bridge->dcp_np,
+				       dsi_bridge->dcp_port, 0);
+	if (IS_ERR(dsi_bridge->panel_bridge)) {
+		int ret = PTR_ERR(dsi_bridge->panel_bridge);
+
+		dsi_bridge->panel_bridge = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dcp_dsi_component_unbind(struct device *dev,
+				     struct device *master, void *data)
+{
+	struct dcp_dsi_bridge *dsi_bridge =
+		container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
+
+	dsi_bridge->panel_bridge = NULL;
+}
+
+static const struct component_ops dcp_dsi_component_ops = {
+	.bind = dcp_dsi_component_bind,
+	.unbind = dcp_dsi_component_unbind,
+};
+
+static const struct mipi_dsi_host_ops dcp_dsi_host_ops = {
+	.attach   = dcp_dsi_host_attach,
+	.detach   = dcp_dsi_host_detach,
+	.transfer = dcp_dsi_host_transfer,
+};
+
+static int dcp_dsi_bridge_attach(struct drm_bridge *bridge,
+				 struct drm_encoder *encoder,
+				 enum drm_bridge_attach_flags flags)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct drm_bridge *panel_bridge;
+	int ret;
+
+	panel_bridge = dsi_bridge->panel_bridge;
+	if (!panel_bridge)
+		return -EPROBE_DEFER;
+
+	ret = drm_bridge_attach(encoder, panel_bridge, bridge, flags);
+	if (ret) {
+		drm_err(bridge->dev,
+			"DSI%u: panel bridge attach failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		return ret;
+	}
+
+	drm_dbg_driver(dsi_bridge->kms->dev,
+		       "DSI%u: panel bridge attached\n",
+		       dsi_bridge->intf_idx);
+
+	dcp_hfi_set_power_control_cb(dsi_bridge->kms->hfi,
+				     dsi_bridge->kms, dcp_dsi_power_control_cb);
+
+	return 0;
+}
+
+static u32 dcp_dsi_bridge_get_mixer_count(struct dcp_dsi_bridge *dsi_bridge,
+					  const struct dcp_dsi_hfi_panel_config *cfg,
+					  const struct drm_display_mode *mode)
+{
+	u32 mixer_count = cfg->topology_mixer_count;
+
+	if (!mixer_count)
+		mixer_count = 1;
+
+	if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+		const struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
+
+		if (dsc->slice_count)
+			mixer_count = dsc->slice_count;
+		else if (dsc->slice_width && mode->hdisplay)
+			mixer_count = DIV_ROUND_UP(mode->hdisplay,
+						   dsc->slice_width);
+	}
+
+	return mixer_count;
+}
+
+static void dcp_dsi_bridge_publish_session(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	struct drm_crtc *crtc;
+
+	crtc = dcp_dpu_kms_get_crtc(kms, dsi_bridge->fw_display_id);
+	if (crtc)
+		dcp_crtc_set_session(crtc, dsi_bridge->session_id);
+	else
+		drm_err(kms->dev, "DSI%u: no CRTC for session %u\n",
+			dsi_bridge->intf_idx, dsi_bridge->session_id);
+}
+
+static void dcp_dsi_bridge_clear_session(struct dcp_dsi_bridge *dsi_bridge)
+{
+	dcp_crtc_clear_session(dcp_dpu_kms_get_crtc(dsi_bridge->kms,
+						    dsi_bridge->fw_display_id));
+}
+
+static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
+				    struct dcp_dsi_hfi_panel_config *cfg,
+				    const struct drm_display_mode *mode)
+{
+	struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
+	enum drm_dsc_params_type params_type;
+	unsigned int i;
+	int ret;
+
+	dsc->pic_width = mode->hdisplay;
+	dsc->pic_height = mode->vdisplay;
+	dsc->convert_rgb = !dsc->native_422 && !dsc->native_420;
+	dsc->slice_chunk_size = DIV_ROUND_UP(dsc->slice_width *
+					dsc->bits_per_pixel, 8 * 16);
+	dsc->simple_422 = 0;
+	dsc->vbr_enable = 0;
+
+	if (dsc->dsc_version_major != 1)
+		return -EOPNOTSUPP;
+
+	switch (dsc->dsc_version_minor) {
+	case 1:
+		if (dsc->native_422 || dsc->native_420)
+			return -EOPNOTSUPP;
+		params_type = DRM_DSC_1_1_PRE_SCR;
+		break;
+	case 2:
+		if (dsc->native_422)
+			params_type = DRM_DSC_1_2_422;
+		else if (dsc->native_420)
+			params_type = DRM_DSC_1_2_420;
+		else
+			params_type = DRM_DSC_1_2_444;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	drm_dsc_set_const_params(dsc);
+	drm_dsc_set_rc_buf_thresh(dsc);
+	ret = drm_dsc_setup_rc_params(dsc, params_type);
+	if (ret)
+		return ret;
+
+	dsc->initial_scale_value = drm_dsc_initial_scale_value(dsc);
+	dsc->line_buf_depth = dsc->bits_per_component + 1;
+	ret = drm_dsc_compute_rc_parameters(dsc);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(cfg->dsc_rc_min_qp); i++) {
+		cfg->dsc_rc_min_qp[i] = dsc->rc_range_params[i].range_min_qp;
+		cfg->dsc_rc_max_qp[i] = dsc->rc_range_params[i].range_max_qp;
+		cfg->dsc_rc_bpg_offsets[i] = dsc->rc_range_params[i].range_bpg_offset;
+	}
+
+	return 0;
+}
+
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+				   const struct drm_display_mode *mode)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	struct dcp_dsi_bridge_state *state = dcp_dsi_bridge_get_state(dsi_bridge);
+	struct dcp_dsi_hfi_panel_config cfg;
+	int ret;
+	u32 opened_session_id;
+
+	if (!mode)
+		return -ENODEV;
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (dsi_bridge->session_open)
+		return 0;
+
+	dsi_bridge->session_id = dsi_bridge->fw_display_id;
+
+	if (!dsi_bridge->dsi_device)
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 531

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +5 -0
+		return -ENODEV;
+
+	dcp_dsi_bridge_build_hfi_config(dsi_bridge, state, &cfg);
+
+	if (dsi_bridge->dsi_device->dsc) {

When is this going to be called? There are panels which require different DSC setup for different modes.

Sure, will address this in v3.

Quoted 44 lines
Quoted 16 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +16 -0
+		ret = dcp_dsi_bridge_setup_dsc(dsi_bridge, &cfg, mode);
+		if (ret) {
+			drm_err(kms->dev, "DSI%u: DSC setup failed: %d\n",
+				dsi_bridge->intf_idx, ret);
+			goto err_close;
+		}
+	}
+
+	ret = dcp_hfi_panel_init(kms->hfi, dsi_bridge->intf_idx,
+				 mode,
+				 dsi_bridge->dsi_device,
+				 &cfg);
+	if (ret) {
+		drm_err(kms->dev, "DSI%u: HFI PANEL_INIT failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 557

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+	}
+
+	if (mode->clock) {
+		u64 link_clock;
+
+		link_clock = dcp_dsi_link_clock(dsi_bridge, &cfg,
+						mode);
+		ret = dcp_hfi_display_mode_validate(kms->hfi,
+						    dsi_bridge->session_id,
+						    mode, link_clock);

mode validation should have happened at the atomic_check().

Quoted 16 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +16 -0
+		if (ret) {
+			drm_err(kms->dev, "DSI%u: HFI MODE_VALIDATE failed: %d\n",
+				dsi_bridge->intf_idx, ret);
+			goto err_close;
+		}
+
+		ret = dcp_hfi_display_set_mode(kms->hfi,
+					       dsi_bridge->session_id,
+					       mode,
+					       dcp_dsi_link_clock(dsi_bridge, &cfg,
+								  mode));
+		if (ret) {
+			drm_err(kms->dev, "DSI%u: HFI SET_MODE failed: %d\n",
+				dsi_bridge->intf_idx, ret);
+			goto err_close;
+		}
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 580

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +7 -0
+	} else {
+		drm_warn(kms->dev, "DSI%u: no valid mode, skipping SET_MODE\n",
+			 dsi_bridge->intf_idx);
+	}
+
+	dcp_hfi_set_power_control_cb(kms->hfi, kms,
+				     dcp_dsi_power_control_cb);

And what happens when you add DP?

Will remove power register and power control, those are no-op now.

Quoted 106 lines
Quoted 106 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +106 -0
+
+	reinit_completion(&dsi_bridge->power_done);
+
+	ret = dcp_hfi_display_power_register(kms->hfi, dsi_bridge->session_id);
+	if (ret) {
+		drm_err(kms->dev, "DSI%u: HFI POWER_REGISTER failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;
+	}
+
+	ret = dcp_hfi_display_enable(kms->hfi,
+				     dsi_bridge->intf_idx,
+				     dsi_bridge->fw_display_id,
+				     &opened_session_id);
+	if (ret) {
+		drm_err(kms->dev, "DSI%u: HFI DISPLAY_ENABLE failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;
+	}
+	dsi_bridge->session_id   = opened_session_id;
+	dsi_bridge->session_open = true;
+	dcp_dsi_bridge_publish_session(dsi_bridge);
+
+	drm_dbg_driver(kms->dev, "DSI%u: session prepared (session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_id);
+	return 0;
+
+err_close:
+	dcp_hfi_clear_power_control_cb(kms->hfi, kms);
+	dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+	dsi_bridge->session_id   = 0;
+	dsi_bridge->session_open = false;
+	dcp_dsi_bridge_clear_session(dsi_bridge);
+
+	return ret;
+}
+
+static void dcp_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+					     struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	int ret;
+
+	ret = dsi_bridge->session_open ? 0 : -ENODEV;
+	dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
+	if (ret)
+		drm_err(bridge->dev, "DSI%u: session prepare failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
+					 struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	bool scan_start_registered = false;
+	bool scan_complete_registered = false;
+	bool autorefresh_enabled = false;
+	int ret;
+
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: bridge atomic_enable (session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_id);
+
+	if (dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error)
+		return;
+	if (!dsi_bridge->session_open)
+		return;
+
+	ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);
+	if (ret) {
+		drm_err(bridge->dev,
+			"DSI%u: HFI DISPLAY_POST_ENABLE failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;
+	}
+
+	dcp_dsi_bridge_publish_session(dsi_bridge);
+
+	ret = dcp_hfi_display_event_register(kms->hfi,
+					     dsi_bridge->session_id, HFI_EVENT_FRAME_SCAN_START);
+	if (ret)
+		goto err_close;
+	scan_start_registered = true;
+
+	ret = dcp_hfi_display_event_register(kms->hfi,
+					     dsi_bridge->session_id, HFI_EVENT_FRAME_SCAN_COMPLETE);
+	if (ret)
+		goto err_close;
+	scan_complete_registered = true;
+
+	if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
+		ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
+						   dsi_bridge->session_id, true, 1);
+		if (ret) {
+			drm_err(bridge->dev,
+				"DSI%u: initial autorefresh setup failed: %d\n",
+				dsi_bridge->intf_idx, ret);
+			goto err_close;
+		}
+		autorefresh_enabled = true;
+	}
+
+	drm_dbg_driver(bridge->dev,
+		       "DSI%u: enable complete (session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_id);
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 693

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +7 -0
+	return;
+
+err_close:
+	if (autorefresh_enabled)
+		dcp_hfi_crtc_set_autorefresh(kms->hfi, dsi_bridge->session_id,
+					     false, 0);
+	if (scan_complete_registered)

Turn each of the booleans into a separate label.

Sure.

Quoted 4 lines
Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +4 -0
+		dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+						 HFI_EVENT_FRAME_SCAN_COMPLETE);
+	if (scan_start_registered)
+		dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 704

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +7 -0
+						 HFI_EVENT_FRAME_SCAN_START);
+	drm_err(bridge->dev,
+		"DSI%u: bridge enable failed; session closed\n",
+		dsi_bridge->intf_idx);
+	dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+	{
+		guard(mutex)(&dsi_bridge->session_lock);

scoped_guard()

Sure.

Quoted 122 lines
Quoted 122 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +122 -0
+		dsi_bridge->session_id   = 0;
+		dsi_bridge->session_open = false;
+	}
+	dcp_dsi_bridge_clear_session(dsi_bridge);
+}
+
+static void dcp_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
+					  struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	int ret;
+
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: atomic_disable (session_open=%d session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_open,
+		       dsi_bridge->session_id);
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (!dsi_bridge->session_open)
+		return;
+
+	if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
+		ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
+						   dsi_bridge->session_id, false, 0);
+		if (ret)
+			drm_warn(bridge->dev,
+				 "DSI%u: autorefresh disable failed: %d\n",
+				 dsi_bridge->intf_idx, ret);
+	}
+
+	dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+					 HFI_EVENT_FRAME_SCAN_COMPLETE);
+	dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+					 HFI_EVENT_FRAME_SCAN_START);
+
+	ret = dcp_hfi_display_disable(kms->hfi, dsi_bridge->session_id);
+	if (ret)
+		drm_warn(bridge->dev,
+			 "DSI%u: HFI DISPLAY_DISABLE failed: %d\n",
+			 dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
+					       struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	int ret;
+
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: atomic_post_disable\n",
+		       dsi_bridge->intf_idx);
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (!dsi_bridge->session_open)
+		return;
+
+	ret = dcp_hfi_display_post_disable(kms->hfi, dsi_bridge->session_id);
+	if (ret)
+		drm_warn(bridge->dev,
+			 "DSI%u: HFI DISPLAY_POST_DISABLE failed: %d\n",
+			 dsi_bridge->intf_idx, ret);
+
+	ret = dcp_hfi_send_sync(kms->hfi,
+				HFI_COMMAND_DISPLAY_CLOSE,
+				HFI_CMD_BUFF_DISPLAY, dsi_bridge->session_id,
+				HFI_PAYLOAD_NONE, NULL, 0,
+				HFI_TX_FLAGS_RESPONSE_REQUIRED |
+				HFI_TX_FLAGS_NON_DISCARDABLE,
+				NULL, 0,
+				2000 /* ms */);
+	if (ret)
+		drm_warn(bridge->dev,
+			 "DSI%u: HFI DISPLAY_CLOSE failed: %d\n",
+			 dsi_bridge->intf_idx, ret);
+
+	dsi_bridge->session_id   = 0;
+	dsi_bridge->session_open = false;
+
+	dcp_dsi_bridge_clear_session(dsi_bridge);
+}
+
+static int dcp_dsi_bridge_atomic_check(struct drm_bridge *bridge,
+				       struct drm_bridge_state *bridge_state,
+				       struct drm_crtc_state *crtc_state,
+				       struct drm_connector_state *conn_state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dsi_bridge_state *state =
+		to_dcp_dsi_bridge_state(bridge_state);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+	u32 max_width, max_height;
+	u32 mixer_count, bpc;
+
+	state->prepare_error = 0;
+
+	if (!mode->clock || !drm_mode_vrefresh(mode))
+		return -EINVAL;
+
+	if (dsi_bridge->dsi_device &&
+	    (!dsi_bridge->dsi_device->lanes ||
+	     !dcp_dsi_link_clock(dsi_bridge, &dsi_bridge->hfi_panel_config, mode)))
+		return -EINVAL;
+
+	if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+		state->panel_state.topology_mixer_count =
+			dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+						       &dsi_bridge->hfi_panel_config, mode);
+		state->panel_state.topology_enc_count =
+			state->panel_state.topology_mixer_count;
+		state->panel_state.topology_intf_count = 1;
+	}
+
+	mixer_count = dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+						     &dsi_bridge->hfi_panel_config, mode);
+	bpc = dsi_bridge->hfi_panel_config.dsc_bits_per_component ?
+		dsi_bridge->hfi_panel_config.dsc_bits_per_component : 8;
+	dcp_crtc_set_topology_state(crtc_state, mixer_count, bpc);
+
+	dcp_mode_limits(mode, kms->caps->max_width, kms->caps->max_height,
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 833

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +7 -0
+			&max_width, &max_height);
+
+	if (mode->hdisplay > max_width || mode->vdisplay > max_height) {
+		drm_err(dsi_bridge->kms->dev,
+			"DSI%u: mode %dx%d exceeds DCP caps %dx%d\n",
+				 dsi_bridge->intf_idx, mode->hdisplay, mode->vdisplay,
+				 kms->caps->max_width, kms->caps->max_height);

Don't let userspace spam the dmesg.

Comments to be addressed in v3.

Quoted 389 lines
Quoted 368 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +277 -0
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static const struct drm_bridge_funcs dcp_dsi_bridge_funcs = {
+	.attach                  = dcp_dsi_bridge_attach,
+	.atomic_pre_enable       = dcp_dsi_bridge_atomic_pre_enable,
+	.atomic_enable           = dcp_dsi_bridge_atomic_enable,
+	.atomic_disable          = dcp_dsi_bridge_atomic_disable,
+	.atomic_post_disable     = dcp_dsi_bridge_atomic_post_disable,
+	.atomic_check            = dcp_dsi_bridge_atomic_check,
+	.atomic_create_state     = dcp_dsi_bridge_atomic_create_state,
+	.atomic_duplicate_state  = dcp_dsi_bridge_atomic_duplicate_state,
+	.atomic_destroy_state    = dcp_dsi_bridge_atomic_destroy_state,
+};
+
+#define DCP_DSI_DUAL_MAP_ALIGN	SZ_1K
+
+static int dcp_dsi_dual_map_buf(struct dcp_dpu_kms *kms,
+				struct device *dma_dev, void *cpu_addr,
+				dma_addr_t iova, size_t size, int prot)
+{
+	struct sg_table sgt = { 0 };
+	struct iommu_domain *domain;
+	ssize_t mapped;
+	int ret;
+
+	if (!kms->iommu_dev || !cpu_addr || !iova ||
+	    !IS_ALIGNED(iova, DCP_DSI_DUAL_MAP_ALIGN))
+		return -EINVAL;
+
+	domain = iommu_get_domain_for_dev(kms->iommu_dev);
+	if (!domain)
+		return -ENODEV;
+
+	ret = dma_get_sgtable(dma_dev, &sgt, cpu_addr, iova, size);
+	if (ret)
+		return ret;
+
+	mapped = iommu_map_sgtable(domain, iova, &sgt, prot);
+	if (mapped != PAGE_ALIGN(size)) {
+		if (mapped > 0)
+			iommu_unmap(domain, iova, mapped);
+		ret = mapped < 0 ? mapped : -EIO;
+		drm_err(kms->dev,
+			"failed to dual-map iova=%pad into iommu_dev domain: %zd\n",
+			&iova, mapped);
+		sg_free_table(&sgt);
+		return ret;
+	}
+
+	sg_free_table(&sgt);
+	return 0;
+}
+
+static void dcp_dsi_dual_unmap_buf(struct dcp_dpu_kms *kms, dma_addr_t iova,
+				   size_t size)
+{
+	struct iommu_domain *domain;
+
+	if (!kms->iommu_dev || !iova)
+		return;
+
+	domain = iommu_get_domain_for_dev(kms->iommu_dev);
+	if (!domain)
+		return;
+
+	iommu_unmap(domain, iova, PAGE_ALIGN(size));
+}
+
+static void dcp_dsi_init_hfi_panel_defaults(struct dcp_dsi_hfi_panel_config *cfg,
+					    u32 intf_idx)
+{
+	memset(cfg, 0, sizeof(*cfg));
+
+	cfg->display_type = HFI_PANEL_DISPLAY_TYPE_BUILT_IN_0;
+	cfg->num_ctrls = 1;
+	cfg->ctrl_indices[0] = intf_idx;
+	cfg->num_phys = 1;
+	cfg->phy_indices[0] = intf_idx;
+
+	cfg->dma_trigger = HFI_PANEL_TRIGGER_SW;
+	cfg->stream_trigger = HFI_PANEL_TRIGGER_NONE;
+	cfg->te_dcs_command = 1;
+	cfg->wr_mem_start = 0x2C;
+	cfg->wr_mem_continue = 0x3C;
+
+	cfg->phy_type = HFI_PANEL_PHY_OLED;
+	cfg->bllp_eof_power_mode = true;
+	cfg->bllp_power_mode = true;
+	cfg->lane_map = 1;
+	cfg->color_order = 1;
+
+	cfg->jitter_numer = 4;
+	cfg->jitter_denom = 1;
+	cfg->dcs_tx_buf_size = DCP_DSI_DCS_TX_BUF_SIZE;
+
+	cfg->topology_mixer_count = 1;
+	cfg->topology_intf_count = 1;
+}
+
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+				       struct device_node *np,
+				       struct device_node *dcp_np,
+				       u32 dcp_port, u32 intf_idx,
+				       u32 fw_display_id)
+{
+	struct dcp_dsi_bridge *dsi_bridge;
+	struct dcp_dsi_hfi_panel_config *cfg;
+	struct device *dev = kms->pm_dev;
+	struct device *dma_dev = kms->hfi->dma_dev;
+	int ret;
+
+	dsi_bridge = devm_drm_bridge_alloc(dev, struct dcp_dsi_bridge,
+					   bridge, &dcp_dsi_bridge_funcs);
+	if (IS_ERR(dsi_bridge))
+		return ERR_CAST(dsi_bridge);
+
+	dsi_bridge->dcp_np        = of_node_get(dcp_np);
+	dsi_bridge->dcp_port      = dcp_port;
+	dsi_bridge->kms           = kms;
+	dsi_bridge->intf_idx      = intf_idx;
+	dsi_bridge->fw_display_id = fw_display_id;
+
+	INIT_WORK(&dsi_bridge->power_control_work, dcp_dsi_power_control_work);
+	init_completion(&dsi_bridge->power_done);
+	mutex_init(&dsi_bridge->session_lock);
+	mutex_init(&dsi_bridge->dcs_lock);
+
+	cfg = &dsi_bridge->hfi_panel_config;
+	dcp_dsi_init_hfi_panel_defaults(cfg, intf_idx);
+
+	/*
+	 * Allocate the buffers for the firmware DMA domain here. The second
+	 * mapping into the display domain is deferred until msm_kms_init_vm()
+	 * has installed the final display IOMMU domain.
+	 */
+	dsi_bridge->dcs_tx_buf = dma_alloc_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+						    &dsi_bridge->dcs_tx_iova, GFP_KERNEL);
+	if (!dsi_bridge->dcs_tx_buf) {
+		ret = -ENOMEM;
+		goto err_unwind_dma;
+	}
+
+	dsi_bridge->hfi_panel_config.dcs_tx_dva = dsi_bridge->dcs_tx_iova;
+	dsi_bridge->hfi_panel_config.dcs_tx_iova = dsi_bridge->dcs_tx_iova;
+
+	dsi_bridge->dcs_rx_buf = dma_alloc_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+						    &dsi_bridge->dcs_rx_iova, GFP_KERNEL);
+	if (!dsi_bridge->dcs_rx_buf) {
+		ret = -ENOMEM;
+		goto err_unwind_dma;
+	}
+
+	device_initialize(&dsi_bridge->dsi_host_dev);
+	dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
+	dsi_bridge->dsi_host_dev.parent  = dev;
+	dsi_bridge->dsi_host_dev.of_node = of_node_get(np);
+	dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np);
+	dsi_bridge->dsi_host.ops = &dcp_dsi_host_ops;
+	dsi_bridge->dsi_host.dev = &dsi_bridge->dsi_host_dev;
+
+	ret = device_add(&dsi_bridge->dsi_host_dev);
+	if (ret) {
+		dev_err(dev,
+			"DSI%u: failed to add dsi_host_dev: %d\n", intf_idx, ret);
+		put_device(&dsi_bridge->dsi_host_dev);
+		goto err_unwind_dma;
+	}
+
+	ret = mipi_dsi_host_register(&dsi_bridge->dsi_host);
+	if (ret) {
+		dev_err(dev,
+			"DSI%u: failed to register MIPI DSI host: %d\n",
+			intf_idx, ret);
+		device_del(&dsi_bridge->dsi_host_dev);
+		put_device(&dsi_bridge->dsi_host_dev);
+		goto err_unwind_dma;
+	}
+
+	dsi_bridge->bridge.of_node = of_node_get(np);
+	dsi_bridge->bridge.type    = DRM_MODE_CONNECTOR_DSI;
+
+	drm_bridge_add(&dsi_bridge->bridge);
+
+	return &dsi_bridge->bridge;
+
+err_unwind_dma:
+	if (dsi_bridge->dcs_rx_buf) {
+		dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+				  dsi_bridge->dcs_rx_buf,
+				  dsi_bridge->dcs_rx_iova);
+	}
+	if (dsi_bridge->dcs_tx_buf) {
+		dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+				  dsi_bridge->dcs_tx_buf,
+				  dsi_bridge->dcs_tx_iova);
+	}
+	cancel_work_sync(&dsi_bridge->power_control_work);
+	mutex_destroy(&dsi_bridge->session_lock);
+	mutex_destroy(&dsi_bridge->dcs_lock);
+	return ERR_PTR(ret);
+}
+
+int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	struct device *dma_dev = kms->hfi->dma_dev;
+	int ret;
+
+	if (dsi_bridge->dcs_mapped)
+		return 0;
+
+	ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_tx_buf,
+				   dsi_bridge->dcs_tx_iova,
+				   DCP_DSI_DCS_TX_BUF_SIZE, IOMMU_READ);
+	if (ret)
+		return ret;
+
+	ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_rx_buf,
+				   dsi_bridge->dcs_rx_iova,
+				   DCP_DSI_DCS_RX_BUF_SIZE, IOMMU_WRITE);
+	if (ret) {
+		dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+				       DCP_DSI_DCS_TX_BUF_SIZE);
+		return ret;
+	}
+
+	dsi_bridge->dcs_mapped = true;
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: mapped DCS buffers tx=%pad rx=%pad in display domain\n",
+		       dsi_bridge->intf_idx, &dsi_bridge->dcs_tx_iova,
+		       &dsi_bridge->dcs_rx_iova);
+
+	return 0;
+}
+
+void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+	if (!dsi_bridge->dcs_mapped)
+		return;
+
+	dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+			       DCP_DSI_DCS_TX_BUF_SIZE);
+	dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_rx_iova,
+			       DCP_DSI_DCS_RX_BUF_SIZE);
+	dsi_bridge->dcs_mapped = false;
+}
+
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct device *dma_dev = dsi_bridge->kms->hfi->dma_dev;
+
+	dcp_hfi_clear_power_control_cb(dsi_bridge->kms->hfi, dsi_bridge->kms);
+	cancel_work_sync(&dsi_bridge->power_control_work);
+
+	drm_bridge_remove(&dsi_bridge->bridge);
+	of_node_put(dsi_bridge->bridge.of_node);
+	of_node_put(dsi_bridge->dcp_np);
+
+	mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
+	device_del(&dsi_bridge->dsi_host_dev);
+	put_device(&dsi_bridge->dsi_host_dev);
+
+	dcp_dsi_bridge_unmap_buffers(dsi_bridge);
+	dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+			  dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova);
+
+	dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+			  dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova);
+
+	mutex_destroy(&dsi_bridge->session_lock);
+	mutex_destroy(&dsi_bridge->dcs_lock);
+}
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h new file +76 -0
new file mode 100644
index 000000000000..f2c7abf23ac9
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI bridge state and panel configuration.
+ * DCP firmware owns the DSI PHY and controller; the bridge submits HFI commands.
+ */
+
+#ifndef __DCP_DSI_BRIDGE_H__
+#define __DCP_DSI_BRIDGE_H__
+
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/ktime.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
+#include "dcp_dsi_panel.h"
+
+struct dcp_dpu_kms;
+struct device_node;
+
+struct dcp_dsi_bridge {
+	struct drm_bridge    bridge;
+	struct device        dsi_host_dev;
+	struct mipi_dsi_host  dsi_host;
+	struct mipi_dsi_device *dsi_device;
+	struct drm_bridge *panel_bridge;
+	bool component_added;
+
+	struct dcp_dpu_kms  *kms;
+	struct device_node  *dcp_np;
+	u32                  dcp_port;
+	u32                  intf_idx;
+	u32                  fw_display_id;
+	u32                  session_id;
+	bool                 session_open;
+	/* Protects session_id and session_open. */
+	struct mutex         session_lock;
+
+	struct dcp_dsi_hfi_panel_config hfi_panel_config;
+	/* Serializes DCS transfers. */
+	struct mutex dcs_lock;
+
+	void       *dcs_tx_buf;
+	dma_addr_t  dcs_tx_iova;
+	void       *dcs_rx_buf;
+	dma_addr_t  dcs_rx_iova;
+	bool         dcs_mapped;
+	struct work_struct power_control_work;
+	struct completion  power_done;
+	u32                power_packet_id;
+};
+
+#define to_dcp_dsi_host(h) \
+	container_of(h, struct dcp_dsi_bridge, dsi_host)
+
+#define to_dcp_dsi_bridge(b) \
+	container_of(b, struct dcp_dsi_bridge, bridge)
+
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+				       struct device_node *np,
+				       struct device_node *dcp_np,
+				       u32 dcp_port, u32 intf_idx,
+				       u32 fw_display_id);
+int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge);
+void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge);
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+				   const struct drm_display_mode *mode);
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge);
+
+#endif /* __DCP_DSI_BRIDGE_H__ */
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h +1 -0
index a8898e469b44..cc4e9f4c6fc4 100644
@@ -74,6 +74,19 @@ struct dcp_dsi_hfi_panel_config {
  	u32 topology_intf_count;
  };
 
+/**

drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h struct dcp_dsi_hfi_panel_config { · line 87

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h +10 -0
+ * struct dcp_dsi_panel_state - Mode-dependent DSI panel state.
+ *
+ * This state is owned by a DRM atomic bridge state. It is combined with
+ * the static panel configuration when constructing the HFI PANEL_INIT
+ * payload.
+ */
+struct dcp_dsi_panel_state {
+	u32 topology_mixer_count;
+	u32 topology_enc_count;
+	u32 topology_intf_count;

Why is it public? Who uses it?

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h +2 -0
+};
+
  /* DCS buffers use IOVAs directly in the firmware command descriptor. */
  #define DCP_DSI_DCS_TX_BUF_SIZE	SZ_4K
  #define DCP_DSI_DCS_RX_BUF_SIZE	SZ_4K
Signature · 2 lines
-- 
2.43.0
#41 lore raw

Re: [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support

In reply to Jayant Shekhar
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: - [High] drm/msm/dcp: off-by-one DSC chroma and color space enums in dcp_dsi_host_attach() - [High]…

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] drm/msm/dcp: off-by-one DSC chroma and color space enums in dcp_dsi_host_attach()
- [High] drm/msm/dcp: deadlock and unsafe teardown in dcp_dsi_bridge_cleanup()
- [Medium] drm/msm/dcp: OF node leak of dcp_np on error in dcp_dsi_bridge_init()
- [Medium] drm/msm/dcp: dcp_dsi_bridge_setup_dsc() never sets dsc_rc_override
- [Medium] drm/msm/dcp: duplicate dsi_host_dev name in dcp_dsi_bridge_init()
- [Medium] drm/msm/dcp: invalid session close and goto mixing in dcp_dsi_bridge_prepare_session()
- [Medium] drm/msm/dcp: missing session_lock in dcp_dsi_bridge_atomic_enable()
- [Medium] drm/msm/dcp: unsynchronized session_id and packet_id in power control
- [Medium] drm/msm/dcp: firmware session leak on dcp_hfi_display_close() early return
- [Medium] drm/msm/dcp: wrong byte count returned for DSI reads in dcp_dsi_host_transfer() --

commit 7535bc53c50473e4189f52c83b74358ab514c8d0
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add DCP DSI host and panel support

This commit implements the MIPI DSI host, panel-device support, bridge state, DSC configuration, DCS transfers, and DSI buffer management for the DCP display path.

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +0 -0
new file mode 100644
index 0000000000000..b3e782b8f0470
@@ -0,0 +1,1110 @@

[ ... ]

Quoted 29 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +29 -0
+static void dcp_dsi_power_control_work(struct work_struct *work)
+{
+	struct dcp_dsi_bridge *dsi_bridge =
+		container_of(work, struct dcp_dsi_bridge, power_control_work);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+	dcp_hfi_display_power_control_ack(kms->hfi,
+					  dsi_bridge->session_id,
+					  dsi_bridge->power_packet_id);
+
+	complete(&dsi_bridge->power_done);
+}
+
+static void dcp_dsi_power_control_cb(void *priv, u32 display_id,
+				     u32 power_mask, bool enable,
+				     u32 packet_id)
+{
+	struct dcp_dpu_kms *kms = priv;
+	struct dcp_dsi_bridge *dsi_bridge = NULL;
+	int idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(kms->dsi_bridge); idx++) {
+		struct dcp_dsi_bridge *b = kms->dsi_bridge[idx];
+
+		if (b && b->session_id == display_id) {
+			dsi_bridge = b;
+			break;
+		}
+	}

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 183

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+
+	if (!dsi_bridge) {
+		dev_warn(kms->pm_dev,
+			 "DSI: power_control for unknown display_id=%u\n", display_id);
+		return;
+	}
+
+	dsi_bridge->power_packet_id = packet_id;
+	schedule_work(&dsi_bridge->power_control_work);
+}

[Severity: Medium]
Are dsi_bridge->session_id and dsi_bridge->power_packet_id synchronized and matched safely between dcp_dsi_power_control_cb() and dcp_dsi_power_control_work()?

When dcp_hfi_dispatch_event() drops resp_drain_lock and invokes dcp_dsi_power_control_cb(), the loop checks b->session_id == display_id without checking b->session_open or holding session_lock. Since closed bridges have session_id == 0, an unopened bridge matches when display_id == 0, whereas a temporary session opened in dcp_dsi_host_transfer() stores its session ID in a local variable and leaves dsi_bridge->session_id == 0, failing to match a non-zero display_id.

Furthermore, dcp_dsi_power_control_work() reads dsi_bridge->session_id and dsi_bridge->power_packet_id without locking while dcp_dsi_bridge_atomic_post_disable(), dcp_dsi_bridge_atomic_enable(), and dcp_dsi_bridge_prepare_session() reset dsi_bridge->session_id = 0 without flushing power_control_work. And in dcp_dsi_bridge_cleanup(), dcp_hfi_clear_power_control_cb() does not wait for an in-flight callback that already dropped resp_drain_lock, allowing schedule_work(&dsi_bridge->power_control_work) to run after cancel_work_sync().

[ ... ]

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +6 -0
+static int dcp_dsi_host_attach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *dsi)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+	struct dcp_dsi_hfi_panel_config *cfg = &dsi_bridge->hfi_panel_config;
+	int ret;

[ ... ]

Quoted 14 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +14 -0
+	if (dsi->dsc) {
+		struct drm_dsc_config *dsc = dsi->dsc;
+		u32 chroma_format = 0;
+		u32 color_space = 0;
+
+		if (dsc->native_422)
+			chroma_format = 1;
+		else if (dsc->native_420)
+			chroma_format = 2;
+		if (dsc->native_422 || dsc->native_420)
+			color_space = 1;
+
+		cfg->dsc_enabled                 = true;
+		cfg->dsc_version_major           = dsc->dsc_version_major;

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 252

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+		cfg->dsc_version_minor           = dsc->dsc_version_minor;
+		cfg->dsc_scr_version              = 0;
+		cfg->dsc_slice_height             = dsc->slice_height;
+		cfg->dsc_slice_width              = dsc->slice_width;
+		cfg->dsc_slices_per_pkt           = 1;
+		cfg->dsc_bits_per_component       = dsc->bits_per_component;
+		cfg->dsc_pps_delay_ms             = 0;
+		cfg->dsc_bits_per_pixel           = dsc->bits_per_pixel;
+		cfg->dsc_chroma_format            = chroma_format;
+		cfg->dsc_color_space              = color_space;

[Severity: High]
Are chroma_format and color_space off by one here?

In drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h, the HFI protocol enums packed into HFI_PROPERTY_PANEL_COMPRESSION_DATA by dcp_hfi_panel_init() are 1-based:

enum hfi_panel_chroma_format_type {
	HFI_PANEL_CHROMA_FORMAT_444     = 1,
	HFI_PANEL_CHROMA_FORMAT_422     = 2,
	HFI_PANEL_CHROMA_FORMAT_420     = 3,
};

enum hfi_panel_color_space_type {
HFI_PANEL_COLOR_SPACE_RGB = 1,
HFI_PANEL_COLOR_SPACE_YUV = 2,
};

Should dcp_dsi_host_attach() use the HFI_PANEL_CHROMA_FORMAT_* and HFI_PANEL_COLOR_SPACE_* constants instead of 0-based values?

[ ... ]

Quoted 27 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +27 -0
+static ssize_t dcp_dsi_host_transfer(struct mipi_dsi_host *host,
+				     const struct mipi_dsi_msg *msg)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	u32 session_id;
+	bool temporary_session = false;
+	ssize_t ret;
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (!dsi_bridge->dcs_tx_buf)
+		return -ENODEV;
+
+	/* Serialize DCS transfers */
+	guard(mutex)(&dsi_bridge->dcs_lock);
+
+	if (dsi_bridge->session_open) {
+		session_id = dsi_bridge->session_id;
+	} else {
+		/*
+		 * mipi_dsi_host_ops::transfer() may be called while the host is
+		 * disabled. Open a transaction-only DCP session in that case;
+		 * atomic_pre_enable() will create the full display session later.
+		 */
+		ret = dcp_hfi_display_enable(kms->hfi, dsi_bridge->intf_idx,
+					     dsi_bridge->fw_display_id,
+					     &session_id);

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 324

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+		if (ret)
+			return ret;
+
+		temporary_session = true;
+	}
+
+	ret = dcp_hfi_dcs_transfer(kms->hfi, session_id, msg,
+				   dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova,
+				   dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova, 0,
+				   dsi_bridge->hfi_panel_config.ctrl_indices[0]);

[Severity: Medium]
Does dcp_dsi_host_transfer() return the wrong byte count for DSI read transfers and reject 0-parameter generic transfers?

In drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c, dcp_hfi_dcs_transfer() ends with:

	if (msg->rx_len > 0)
		memcpy(msg->rx_buf, rx_buf, msg->rx_len);

	return msg->tx_len;

and rejects msg->tx_len == 0 at entry with -EINVAL.

For mipi_dsi_host_ops::transfer(), callers such as mipi_dsi_dcs_read() and mipi_dsi_generic_read() expect the number of received bytes (msg->rx_len) on read transfers, and 0-parameter generic transfers (MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM and MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM) have msg->tx_len == 0.

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 330

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +6 -0
+	if (temporary_session) {
+		int close_ret = dcp_hfi_display_close(kms->hfi, session_id);
+
+		if (ret >= 0 && close_ret)
+			ret = close_ret;
+	}

[Severity: Medium]
Can calling dcp_hfi_display_close() here (and on the err_close path in dcp_dsi_bridge_atomic_enable()) leak the firmware display session if DISPLAY_DISABLE or DISPLAY_POST_DISABLE fails?

In drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c, dcp_hfi_display_close() returns early before sending HFI_COMMAND_DISPLAY_CLOSE if either dcp_hfi_display_disable() or dcp_hfi_display_post_disable() returns an error:

int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id)

{
	int ret;

	ret = dcp_hfi_display_disable(hfi, session_id);
	if (ret) {
		dev_warn(hfi->dev, "HFI DISPLAY_DISABLE failed: %d\n", ret);
		return ret;
	}

	ret = dcp_hfi_display_post_disable(hfi, session_id);
	if (ret) {
		dev_warn(hfi->dev, "HFI DISPLAY_POST_DISABLE failed: %d\n", ret);
		return ret;
	}

When closing a temporary session in dcp_dsi_host_transfer() (which only called dcp_hfi_display_enable() and never called dcp_hfi_display_post_enable()) or rolling back a partially enabled session in dcp_dsi_bridge_atomic_enable(), an error from DISPLAY_DISABLE or DISPLAY_POST_DISABLE skips HFI_COMMAND_DISPLAY_CLOSE while the driver drops session_id and marks session_open = false. In contrast, dcp_dsi_bridge_atomic_disable() and dcp_dsi_bridge_atomic_post_disable() still send HFI_COMMAND_DISPLAY_CLOSE even if the preceding disable commands warn.

[ ... ]

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +4 -0
+static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
+				    struct dcp_dsi_hfi_panel_config *cfg,
+				    const struct drm_display_mode *mode)
+{

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 506

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +8 -0
+	for (i = 0; i < ARRAY_SIZE(cfg->dsc_rc_min_qp); i++) {
+		cfg->dsc_rc_min_qp[i] = dsc->rc_range_params[i].range_min_qp;
+		cfg->dsc_rc_max_qp[i] = dsc->rc_range_params[i].range_max_qp;
+		cfg->dsc_rc_bpg_offsets[i] = dsc->rc_range_params[i].range_bpg_offset;
+	}
+
+	return 0;
+}

[Severity: Medium]
Should dcp_dsi_bridge_setup_dsc() also set cfg->dsc_rc_override = true?

dcp_dsi_host_attach() initializes cfg->dsc_rc_override = false, and dcp_dsi_bridge_prepare_session() passes a stack-local copy of cfg to dcp_dsi_bridge_setup_dsc() and then to dcp_hfi_panel_init(). In drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c, dcp_hfi_panel_init() only packs HFI_PROPERTY_PANEL_COMPRESSION_RC_OVERRIDE when (cfg->dsc_enabled && cfg->dsc_rc_override) is true, so the rate-control parameters populated in cfg->dsc_rc_min_qp, cfg->dsc_rc_max_qp, and cfg->dsc_rc_bpg_offsets are never sent to the firmware.

Quoted 21 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +21 -0
+
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+				   const struct drm_display_mode *mode)
+{
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	struct dcp_dsi_bridge_state *state = dcp_dsi_bridge_get_state(dsi_bridge);
+	struct dcp_dsi_hfi_panel_config cfg;
+	int ret;
+	u32 opened_session_id;
+
+	if (!mode)
+		return -ENODEV;
+
+	guard(mutex)(&dsi_bridge->session_lock);
+	if (dsi_bridge->session_open)
+		return 0;
+
+	dsi_bridge->session_id = dsi_bridge->fw_display_id;
+
+	if (!dsi_bridge->dsi_device)
+		return -ENODEV;

[ ... ]

Quoted 16 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +16 -0
+	ret = dcp_hfi_display_enable(kms->hfi,
+				     dsi_bridge->intf_idx,
+				     dsi_bridge->fw_display_id,
+				     &opened_session_id);
+	if (ret) {
+		drm_err(kms->dev, "DSI%u: HFI DISPLAY_ENABLE failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+		goto err_close;
+	}
+	dsi_bridge->session_id   = opened_session_id;
+	dsi_bridge->session_open = true;
+	dcp_dsi_bridge_publish_session(dsi_bridge);
+
+	drm_dbg_driver(kms->dev, "DSI%u: session prepared (session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_id);
+	return 0;

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 616

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+
+err_close:
+	dcp_hfi_clear_power_control_cb(kms->hfi, kms);
+	dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+	dsi_bridge->session_id   = 0;
+	dsi_bridge->session_open = false;
+	dcp_dsi_bridge_clear_session(dsi_bridge);
+
+	return ret;
+}

[Severity: Medium]
There appear to be several issues with error handling and cleanup in dcp_dsi_bridge_prepare_session():

1. It mixes guard(mutex)(&dsi_bridge->session_lock) with goto err_close, contrary to the cleanup.h guideline not to mix goto error paths and scope-based cleanup helpers in the same function.

2. If !dsi_bridge->dsi_device is true, returning -ENODEV early leaves dsi_bridge->session_id set to dsi_bridge->fw_display_id while session_open is false.

3. Every goto err_close path triggers before dcp_hfi_display_enable() has succeeded (even if dcp_dsi_bridge_setup_dsc() fails in software before any HFI command is sent), yet err_close calls dcp_hfi_display_close() to send DISPLAY_DISABLE, DISPLAY_POST_DISABLE, and DISPLAY_CLOSE for a session that was never enabled.

4. Calling dcp_hfi_clear_power_control_cb(kms->hfi, kms) in err_close clears the single shared power control callback on kms->hfi for all DSI bridges in kms->dsi_bridge[].

Quoted 34 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +34 -0
+
+static void dcp_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+					     struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	int ret;
+
+	ret = dsi_bridge->session_open ? 0 : -ENODEV;
+	dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
+	if (ret)
+		drm_err(bridge->dev, "DSI%u: session prepare failed: %d\n",
+			dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
+					 struct drm_atomic_commit *state)
+{
+	struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+	struct dcp_dpu_kms *kms = dsi_bridge->kms;
+	bool scan_start_registered = false;
+	bool scan_complete_registered = false;
+	bool autorefresh_enabled = false;
+	int ret;
+
+	drm_dbg_driver(kms->dev,
+		       "DSI%u: bridge atomic_enable (session_id=%u)\n",
+		       dsi_bridge->intf_idx, dsi_bridge->session_id);
+
+	if (dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error)
+		return;
+	if (!dsi_bridge->session_open)
+		return;
+
+	ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);

[ ... ]

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +11 -0
+err_close:
+	if (autorefresh_enabled)
+		dcp_hfi_crtc_set_autorefresh(kms->hfi, dsi_bridge->session_id,
+					     false, 0);
+	if (scan_complete_registered)
+		dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+						 HFI_EVENT_FRAME_SCAN_COMPLETE);
+	if (scan_start_registered)
+		dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+						 HFI_EVENT_FRAME_SCAN_START);
+	drm_err(bridge->dev,

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 709

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+		"DSI%u: bridge enable failed; session closed\n",
+		dsi_bridge->intf_idx);
+	dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+	{
+		guard(mutex)(&dsi_bridge->session_lock);
+		dsi_bridge->session_id   = 0;
+		dsi_bridge->session_open = false;
+	}
+	dcp_dsi_bridge_clear_session(dsi_bridge);
+}

[Severity: Medium]
Should dcp_dsi_bridge_atomic_enable() (and dcp_dsi_bridge_atomic_pre_enable()) hold dsi_bridge->session_lock across the session operations rather than only around clearing session_id and session_open at the end of err_close?

In addition to mixing goto err_close with guard(mutex), reading dsi_bridge->session_open and dsi_bridge->session_id and calling dcp_hfi_display_close() without holding session_lock allows a concurrent dcp_dsi_host_transfer() call to observe session_open == true and send DCS commands on a session ID that is actively being closed or has already been closed in firmware.

[ ... ]

Quoted 18 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +18 -0
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+				       struct device_node *np,
+				       struct device_node *dcp_np,
+				       u32 dcp_port, u32 intf_idx,
+				       u32 fw_display_id)
+{
+	struct dcp_dsi_bridge *dsi_bridge;
+	struct dcp_dsi_hfi_panel_config *cfg;
+	struct device *dev = kms->pm_dev;
+	struct device *dma_dev = kms->hfi->dma_dev;
+	int ret;
+
+	dsi_bridge = devm_drm_bridge_alloc(dev, struct dcp_dsi_bridge,
+					   bridge, &dcp_dsi_bridge_funcs);
+	if (IS_ERR(dsi_bridge))
+		return ERR_CAST(dsi_bridge);
+
+	dsi_bridge->dcp_np        = of_node_get(dcp_np);

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 997

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +9 -0
+	device_initialize(&dsi_bridge->dsi_host_dev);
+	dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
+	dsi_bridge->dsi_host_dev.parent  = dev;
+	dsi_bridge->dsi_host_dev.of_node = of_node_get(np);
+	dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np);
+	dsi_bridge->dsi_host.ops = &dcp_dsi_host_ops;
+	dsi_bridge->dsi_host.dev = &dsi_bridge->dsi_host_dev;
+
+	ret = device_add(&dsi_bridge->dsi_host_dev);

[Severity: Medium]
Won't dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np) cause a device name collision in device_add() when two DSI bridges are present?

The %pOFn specifier strips the @unit-address suffix from the node name, so both dsi@0 and dsi@1 receive the name "dsi" under the same parent device (kms->pm_dev), causing the second device_add() to fail with -EEXIST.

[ ... ]

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +6 -0
+err_unwind_dma:
+	if (dsi_bridge->dcs_rx_buf) {
+		dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+				  dsi_bridge->dcs_rx_buf,
+				  dsi_bridge->dcs_rx_iova);
+	}

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 1037

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+	if (dsi_bridge->dcs_tx_buf) {
+		dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+				  dsi_bridge->dcs_tx_buf,
+				  dsi_bridge->dcs_tx_iova);
+	}
+	cancel_work_sync(&dsi_bridge->power_control_work);
+	mutex_destroy(&dsi_bridge->session_lock);
+	mutex_destroy(&dsi_bridge->dcs_lock);
+	return ERR_PTR(ret);
+}

[Severity: Medium]
Does the err_unwind_dma path in dcp_dsi_bridge_init() leak the dsi_bridge->dcp_np device_node reference acquired via of_node_get(dcp_np) if dma_alloc_coherent(), device_add(), or mipi_dsi_host_register() fails?

[ ... ]

Quoted 15 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +15 -0
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
+{
+	struct device *dma_dev = dsi_bridge->kms->hfi->dma_dev;
+
+	dcp_hfi_clear_power_control_cb(dsi_bridge->kms->hfi, dsi_bridge->kms);
+	cancel_work_sync(&dsi_bridge->power_control_work);
+
+	drm_bridge_remove(&dsi_bridge->bridge);
+	of_node_put(dsi_bridge->bridge.of_node);
+	of_node_put(dsi_bridge->dcp_np);
+
+	mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
+	device_del(&dsi_bridge->dsi_host_dev);
+	put_device(&dsi_bridge->dsi_host_dev);
+

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c line 1110

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c new file +10 -0
+	dcp_dsi_bridge_unmap_buffers(dsi_bridge);
+	dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+			  dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova);
+
+	dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+			  dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova);
+
+	mutex_destroy(&dsi_bridge->session_lock);
+	mutex_destroy(&dsi_bridge->dcs_lock);
+}

[Severity: High]
Can calling mipi_dsi_host_unregister() from dcp_dsi_bridge_cleanup() deadlock on component_mutex and trigger use-after-free during teardown?

1. When a DSI device is attached, mipi_dsi_host_unregister() removes the child device and invokes:

mipi_dsi_remove_device_fn()

     -> mipi_dsi_detach()
       -> dcp_dsi_host_detach()
         -> component_del(&dsi_bridge->dsi_host_dev, &dcp_dsi_component_ops)

component_del() acquires the global component_mutex. When dcp_dsi_bridge_cleanup() is called from aggregate device bringup failure or aggregate device takedown (for example via component_master_del() / take_down_aggregate_device() -> msm_drm_unbind() -> msm_drm_uninit() -> dcp_dpu_kms_destroy()), component_mutex is already held by the caller, causing a recursive deadlock.

2. struct dcp_dsi_bridge is allocated with devm_drm_bridge_alloc() on kms->pm_dev and embeds struct device dsi_host_dev, which is refcounted independently. Calling device_del() and put_device() on dsi_host_dev during kms->destroy() happens before component_unbind_all() finishes using component->dev (&dsi_bridge->dsi_host_dev). In addition, dcs_tx_buf and dcs_rx_buf are freed without holding session_lock/dcs_lock or clearing dsi_bridge->dcs_tx_buf to NULL, and session_lock and dcs_lock are destroyed while the bridge object is still alive.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
#42 lore raw

PATCH v2 16/23 drm/msm/dcp: Integrate the DSI bridge with KMS

In reply to Jayant Shekhar

Connect the DCP DSI host and panel bridge to the DCP KMS pipeline. Without bridge integration, DRM cannot create display sessions, attach connectors, or run the bridge enable and disable sequence for atomic commits.

Set up and tear down the bridge and CRTC relationship, pass panel configuration through KMS, and connect bridge-backed display operations to the DCP HFI commands.

Assisted-by LLM
---
 drivers/gpu/drm/msm/dcp/dcp_crtc.c    | 14 ++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 40 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h |  2 ++
 3 files changed, 56 insertions(+)
drivers/gpu/drm/msm/dcp/dcp_crtc.c +14 -0
index ec4f74c2920f..173a07df71c5 100644
@@ -147,6 +147,12 @@ static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
 		return;
 	}
 
+	ret = dcp_dpu_kms_prepare_crtc_session(dcp_kms, crtc);
+	if (ret) {
+		cstate->hfi_error = ret;
+		return;
+	}
+
 	if (!dcp_crtc_get_session(crtc, &session_id)) {
 		cstate->hfi_error = -ENODEV;
 		return;
@@ -559,6 +565,7 @@ static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
 	const struct drm_plane_state *pstate;
 	u32 max_width, max_height;
 	u32 mixer_count;
+	bool needs_dirtyfb;
 	int num_planes = 0;
 	int ret;
 
@@ -569,6 +576,8 @@ static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
 	if (!crtc_state->enable || !crtc_state->active)
 		return 0;
 
+	needs_dirtyfb = dcp_dpu_kms_needs_dirtyfb(dcp_kms, crtc);
+
 	mixer_count = cstate->topology_mixer_count;
 	if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
 		drm_err(crtc->dev,
@@ -598,8 +607,13 @@ static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
 
 	/* Limit visible planes to the firmware blend-stage count. */
 	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+		struct dcp_plane_state *dcp_pstate;
+
 		if (!pstate || !pstate->visible)
 			continue;
+
+		dcp_pstate = to_dcp_plane_state((struct drm_plane_state *)pstate);
+		dcp_pstate->needs_dirtyfb = needs_dirtyfb;
 		num_planes++;
 	}
 
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +40 -0
index 44b1c2200aee..61a4d8565359 100644
@@ -7,6 +7,27 @@
 #include <drm/drm_crtc.h>
 
 #include "dcp_dpu_kms.h"
+#include "dcp_dsi_bridge.h"
+
+int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
+				     struct drm_crtc *crtc)
+{
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+		struct dcp_dsi_bridge *bridge = dcp_kms->dsi_bridge[i];
+
+		if (!bridge)
+			continue;
+		if (dcp_dpu_kms_get_crtc(dcp_kms, bridge->fw_display_id) != crtc)
+			continue;
+
+		return dcp_dsi_bridge_prepare_session(bridge,
+						      &crtc->state->adjusted_mode);
+	}
+
+	return -ENODEV;
+}
 
 struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
 				      u32 display_id)
@@ -19,3 +40,22 @@ struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
 
 	return NULL;
 }
+
+bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms,
+			       struct drm_crtc *crtc)
+{
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+		struct dcp_dsi_bridge *bridge = dcp_kms->dsi_bridge[i];
+
+		if (!bridge ||
+		    dcp_dpu_kms_get_crtc(dcp_kms, bridge->fw_display_id) != crtc)
+			continue;
+
+		return bridge->dsi_device &&
+			!(bridge->dsi_device->mode_flags & MIPI_DSI_MODE_VIDEO);
+	}
+
+	return false;
+}
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h +2 -0
index f8010fc5d32f..333567a78ad7 100644
@@ -44,6 +44,8 @@ struct dcp_dpu_kms {
 #define to_dcp_dpu_kms(x) container_of(x, struct dcp_dpu_kms, base)
 struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
 				      u32 display_id);
+bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms,
+			       struct drm_crtc *crtc);
 int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
 				     struct drm_crtc *crtc);
 
Signature · 1 line
2.43.0
#43 lore raw

Re: [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] drm/msm/dcp: dcp_crtc_atomic_begin() opens session on non-modeset commits --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] drm/msm/dcp: dcp_crtc_atomic_begin() opens session on non-modeset commits --

commit 9b8003ef1934260f1a1ec514fc980b7068b0a204
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Integrate the DSI bridge with KMS

This commit connects the DCP DSI bridge to the DCP KMS pipeline by adding helpers to prepare the CRTC display session during atomic_begin and check whether planes require dirtyfb updates during atomic_check.

drivers/gpu/drm/msm/dcp/dcp_crtc.c static void dcp_crtc_atomic_begin(struct drm_crtc *crtc, · line 154

Quoted 13 lines
drivers/gpu/drm/msm/dcp/dcp_crtc.c +5 -0
index ec4f74c2920f8..173a07df71c5f 100644
@@ -147,6 +147,12 @@ static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
 		return;
 	}
 
+	ret = dcp_dpu_kms_prepare_crtc_session(dcp_kms, crtc);
+	if (ret) {
+		cstate->hfi_error = ret;
+		return;
+	}

[Severity: Medium]
Should dcp_dpu_kms_prepare_crtc_session() only open a new session when drm_atomic_crtc_needs_modeset(crtc->state) is true, or can calling it unconditionally in dcp_crtc_atomic_begin() leave the session partially initialized on non-modeset commits?

In msm_atomic_commit_tail(), drm_atomic_helper_commit_planes() (which calls dcp_crtc_atomic_begin()) runs before drm_atomic_helper_commit_modeset_enables() (which calls dcp_crtc_atomic_enable(), dcp_dsi_bridge_atomic_pre_enable(), and dcp_dsi_bridge_atomic_enable(), and only runs when drm_atomic_crtc_needs_modeset(crtc->state) is true).

If an initial modeset commit fails in dcp_dsi_bridge_prepare_session() or in dcp_dsi_bridge_atomic_enable():

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c:dcp_dsi_bridge_atomic_enable() {
	...
	ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);
	if (ret) {
		drm_err(bridge->dev,
			"DSI%u: HFI DISPLAY_POST_ENABLE failed: %d\n",
			dsi_bridge->intf_idx, ret);
		goto err_close;
	}
	...
err_close:
	dcp_hfi_clear_power_control_cb(kms->hfi, kms);
	dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
	dsi_bridge->session_id   = 0;
	dsi_bridge->session_open = false;
	dcp_dsi_bridge_clear_session(dsi_bridge);
}

the session is closed and dsi_bridge->session_open is reset to false, while crtc->state->active remains true.

On the next non-modeset plane update or pageflip where drm_atomic_crtc_needs_modeset(crtc->state) is false, dcp_crtc_atomic_begin() -> dcp_dpu_kms_prepare_crtc_session() -> dcp_dsi_bridge_prepare_session() sees !dsi_bridge->session_open, opens a new HFI session, sets dsi_bridge->session_open = true, and publishes session_id to the CRTC:

drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c:dcp_dsi_bridge_prepare_session() {
	...
	dsi_bridge->session_id   = opened_session_id;
	dsi_bridge->session_open = true;
	dcp_dsi_bridge_publish_session(dsi_bridge);
	...
}

Because drm_atomic_helper_commit_modeset_enables() skips the CRTC and bridge enable callbacks on a non-modeset commit, dcp_crtc_atomic_enable() and dcp_dsi_bridge_atomic_enable() never run, skipping HFI_MODE_DPMS_ON, drm_crtc_vblank_on(), panel bridge enable, HFI_COMMAND_DISPLAY_POST_ENABLE, and HFI_EVENT_FRAME_SCAN_COMPLETE registration.

When dcp_crtc_commit_kickoff() then submits frame triggers for that session, dcp_crtc_wait_for_commit_done() will wait on frame_done_wq for an event that was never registered and time out after 500ms on every subsequent commit:

drivers/gpu/drm/msm/dcp/dcp_crtc.c:dcp_crtc_wait_for_commit_done() {
	...
	if (!wait_event_timeout(dcp_crtc->frame_done_wq,
				atomic_read(&dcp_crtc->pending_kickoff_cnt) == 0,
				timeout)) {
		drm_err(crtc->dev,
			"frame done timeout for CRTC%u\n",
			dcp_crtc->display_id);
		atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
	}
}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
#44 lore raw

PATCH v2 17/23 drm/msm/dcp: Register DCP display pipeline

In reply to Jayant Shekhar

Register the DCP display pipeline from firmware capabilities and the device-tree display graph. DCP exposes display resources through firmware rather than host-visible display registers, so KMS objects cannot be registered from a static hardware catalog alone.

Integrate power and IOMMU setup, parse capabilities and display graph data, and create the DRM planes, CRTCs, encoders, bridges, and connectors used by the DCP backend.

Assisted-by LLM
---
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 625 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h |   3 +
 2 files changed, 628 insertions(+)
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +625 -0
index 61a4d8565359..8a73a99b2c76 100644
@@ -4,10 +4,29 @@
  * DCP KMS driver.
  */
 
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
 #include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_of.h>
+#include <drm/drm_plane.h>
+
+#include "msm_drv.h"
+#include "msm_gem.h"
+#include "msm_kms.h"
 
 #include "dcp_dpu_kms.h"
 #include "dcp_dsi_bridge.h"
+#include "dcp_crtc.h"
+#include "dcp_drv.h"
+#include "dcp_encoder.h"
+#include "dcp_hfi.h"
+#include "dcp_plane.h"
 
 int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
 				     struct drm_crtc *crtc)
@@ -59,3 +78,609 @@ bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms,
 
 	return false;
 }
+
+void dcp_dpu_kms_cleanup_bridges(struct dcp_dpu_kms *dcp_kms)
+{
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+		if (!dcp_kms->dsi_bridge[i])
+			continue;
+
+		dcp_dsi_bridge_cleanup(dcp_kms->dsi_bridge[i]);
+		dcp_kms->dsi_bridge[i] = NULL;
+	}
+}
+
+static void dcp_dpu_kms_dump_caps(struct dcp_dpu_kms *dcp_kms)
+{
+	const struct dcp_caps *caps = dcp_kms->caps;
+	u32 i;
+
+	drm_dbg_driver(dcp_kms->dev, "DCP caps: hw=%u.%u fw=%u displays=%u\n",
+		       caps->dcp_hw_rev, caps->hw_rev, caps->fw_rev,
+		       caps->num_displays);
+	drm_dbg_driver(dcp_kms->dev, "DCP caps: max_planes=%u max=%ux%u\n",
+		       caps->max_planes, caps->max_width, caps->max_height);
+
+	drm_dbg_driver(dcp_kms->dev, "DCP caps: VIG pipes=%u virt=%u\n",
+		       caps->num_vig_pipes, caps->virt_vig_count);
+	for (i = 0; i < min_t(u32, caps->num_vig_pipes,
+			      DCP_MAX_SSPP_COUNT); i++)
+		drm_dbg_driver(dcp_kms->dev, "DCP caps: VIG[%u] pipe=%u r1=%u\n", i,
+			       caps->vig_pipe_ids[i], caps->vig_r1_indices[i]);
+
+	drm_dbg_driver(dcp_kms->dev, "DCP caps: DMA pipes=%u virt=%u\n",
+		       caps->num_dma_pipes, caps->virt_dma_count);
+	for (i = 0; i < min_t(u32, caps->num_dma_pipes,
+			      DCP_MAX_SSPP_COUNT); i++)
+		drm_dbg_driver(dcp_kms->dev, "DCP caps: DMA[%u] pipe=%u r1=%u\n", i,
+			       caps->dma_pipe_ids[i], caps->dma_r1_indices[i]);
+
+	drm_dbg_driver(dcp_kms->dev, "DCP caps: DSI count=%u\n", caps->dsi_count);
+}
+
+static int dcp_dpu_kms_hw_init(struct msm_kms *kms)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+	struct drm_device *dev = dcp_kms->dev;
+	struct drm_gpuvm *vm;
+	unsigned int i;
+	int ret;
+
+	kms->irq = IRQ_NOTCONNECTED;
+	dev->mode_config.cursor_width  = 512;
+	dev->mode_config.cursor_height = 512;
+
+	ret = pm_runtime_resume_and_get(dcp_kms->pm_dev);
+	if (ret < 0) {
+		drm_err(dev, "failed to enable power: %d\n", ret);
+		return ret;
+	}
+
+	vm = msm_kms_init_vm(dev, NULL);
+	if (IS_ERR(vm)) {
+		drm_err(dev, "SMMU/IOMMU init failed: %pe\n", vm);
+		ret = PTR_ERR(vm);
+		goto err_cleanup;
+	}
+	kms->vm = vm;
+
+	for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+		if (!dcp_kms->dsi_bridge[i])
+			continue;
+
+		ret = dcp_dsi_bridge_map_buffers(dcp_kms->dsi_bridge[i]);
+		if (ret) {
+			drm_err(dev, "failed to map DSI%u DCS buffers: %d\n", i, ret);
+			goto err_cleanup;
+		}
+	}
+
+	if (!dcp_kms->hfi) {
+		drm_err(dev, "HFI unavailable, cannot initialize DCP KMS\n");
+		ret = -ENODEV;
+		goto err_cleanup;
+	}
+	if (!dcp_kms->caps) {
+		drm_err(dev, "DCP caps unavailable, cannot initialize DCP KMS\n");
+		ret = -ENODEV;
+		goto err_cleanup;
+	}
+
+	dcp_dpu_kms_dump_caps(dcp_kms);
+
+	dev->mode_config.min_width  = 0;
+	dev->mode_config.min_height = 0;
+	dev->mode_config.max_width  = max(dcp_kms->caps->max_width,
+					  dcp_kms->caps->max_height);
+	dev->mode_config.max_height = dev->mode_config.max_width;
+	dev->max_vblank_count = 0xffffffff;
+	dev->vblank_disable_immediate = true;
+
+	ret = dcp_dpu_drm_obj_init(dcp_kms);
+	if (ret) {
+		dev_err_probe(dev->dev, ret, "DRM object init failed\n");
+		goto err_cleanup;
+	}
+
+	pm_runtime_put_autosuspend(dcp_kms->pm_dev);
+	drm_dbg_driver(dev, "DCP hw_init complete\n");
+	return 0;
+
+err_cleanup:
+	for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+		if (dcp_kms->dsi_bridge[i])
+			dcp_dsi_bridge_unmap_buffers(dcp_kms->dsi_bridge[i]);
+	}
+
+	if (kms->vm) {
+		struct msm_gem_vm *msm_vm = to_msm_vm(kms->vm);
+
+		if (msm_vm->mmu && msm_vm->mmu->funcs->detach)
+			msm_vm->mmu->funcs->detach(msm_vm->mmu);
+		drm_gpuvm_put(kms->vm);
+		kms->vm = NULL;
+	}
+	pm_runtime_put_autosuspend(dcp_kms->pm_dev);
+	return ret;
+}
+
+static void dcp_dpu_kms_enable_commit(struct msm_kms *kms)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+	int ret;
+
+	ret = pm_runtime_resume_and_get(dcp_kms->pm_dev);
+	if (ret < 0) {
+		dcp_kms->commit_pm_active = false;
+		drm_err_ratelimited(dcp_kms->dev,
+				    "pm_runtime_resume_and_get failed: %d\n",
+				    ret);
+		return;
+	}
+
+	dcp_kms->commit_pm_active = true;
+}
+
+static void dcp_dpu_kms_disable_commit(struct msm_kms *kms)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+
+	if (dcp_kms->commit_pm_active) {
+		pm_runtime_put_autosuspend(dcp_kms->pm_dev);
+		dcp_kms->commit_pm_active = false;
+	}
+}
+
+static int dcp_dpu_kms_check_mode_changed(struct msm_kms *kms,
+					  struct drm_atomic_commit *state)
+{
+	struct drm_crtc_state *new_crtc_state, *old_crtc_state;
+	struct drm_crtc *crtc;
+	int i;
+
+	for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
+				      new_crtc_state, i) {
+		if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
+			continue;
+		if (!new_crtc_state->active)
+			continue;
+		new_crtc_state->planes_changed = true;
+	}
+
+	return 0;
+}
+
+static void dcp_dpu_kms_flush_commit(struct msm_kms *kms, unsigned int crtc_mask)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+	struct drm_crtc *crtc;
+
+	for_each_crtc_mask(dcp_kms->dev, crtc, crtc_mask) {
+		if (!crtc->state || !crtc->state->active)
+			continue;
+		dcp_crtc_commit_kickoff(crtc);
+	}
+}
+
+static void dcp_dpu_kms_wait_flush(struct msm_kms *kms, unsigned int crtc_mask)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+	struct drm_crtc *crtc;
+
+	for_each_crtc_mask(dcp_kms->dev, crtc, crtc_mask)
+		dcp_crtc_wait_for_commit_done(crtc);
+}
+
+static void dcp_dpu_kms_complete_commit(struct msm_kms *kms, unsigned int crtc_mask)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+	struct drm_crtc *crtc;
+
+	for_each_crtc_mask(dcp_kms->dev, crtc, crtc_mask)
+		dcp_crtc_complete_commit(crtc);
+}
+
+static int dcp_dpu_kms_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
+{
+	return dcp_crtc_vblank(crtc, true);
+}
+
+static void dcp_dpu_kms_disable_vblank(struct msm_kms *kms,
+				       struct drm_crtc *crtc)
+{
+	dcp_crtc_vblank(crtc, false);
+}
+
+static void dcp_dpu_kms_destroy(struct msm_kms *kms)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+
+	dcp_dpu_kms_cleanup_bridges(dcp_kms);
+
+	if (kms->vm) {
+		struct msm_gem_vm *msm_vm = to_msm_vm(kms->vm);
+
+		if (msm_vm->mmu && msm_vm->mmu->funcs->detach) {
+			drm_dbg_driver(dcp_kms->dev,
+				       "detaching IOMMU domain from dcp-dpus\n");
+			msm_vm->mmu->funcs->detach(msm_vm->mmu);
+		}
+
+		drm_gpuvm_put(kms->vm);
+		kms->vm = NULL;
+	}
+
+	if (dcp_kms->rpm_enabled) {
+		pm_runtime_disable(dcp_kms->pm_dev);
+		dcp_kms->rpm_enabled = false;
+	}
+
+	msm_kms_destroy(&dcp_kms->base);
+}
+
+static void dcp_dpu_kms_irq_preinstall(struct msm_kms *kms) {}
+static void dcp_dpu_kms_irq_uninstall(struct msm_kms *kms) {}
+
+static irqreturn_t dcp_dpu_kms_irq(struct msm_kms *kms)
+{
+	return IRQ_NONE;
+}
+
+static void dcp_dpu_kms_snapshot(struct msm_disp_state *disp_state,
+				 struct msm_kms *kms)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+
+	drm_dbg_driver(dcp_kms->dev,
+		       "DCP snapshot: max %ux%u\n",
+		       dcp_kms->caps->max_width,
+		       dcp_kms->caps->max_height);
+}
+
+const struct msm_kms_funcs dcp_dpu_kms_funcs = {
+	.hw_init            = dcp_dpu_kms_hw_init,
+	.irq_preinstall     = dcp_dpu_kms_irq_preinstall,
+	.irq_uninstall      = dcp_dpu_kms_irq_uninstall,
+	.irq                = dcp_dpu_kms_irq,
+	.enable_commit      = dcp_dpu_kms_enable_commit,
+	.disable_commit     = dcp_dpu_kms_disable_commit,
+	.check_mode_changed = dcp_dpu_kms_check_mode_changed,
+	.flush_commit       = dcp_dpu_kms_flush_commit,
+	.wait_flush         = dcp_dpu_kms_wait_flush,
+	.complete_commit    = dcp_dpu_kms_complete_commit,
+	.enable_vblank      = dcp_dpu_kms_enable_vblank,
+	.disable_vblank     = dcp_dpu_kms_disable_vblank,
+	.destroy            = dcp_dpu_kms_destroy,
+	.snapshot           = dcp_dpu_kms_snapshot,
+};
+
+static u32 dcp_dpu_kms_resolve_fw_display_id(struct dcp_dpu_kms *dcp_kms,
+					     u32 intf_idx)
+{
+	const struct dcp_caps *caps = dcp_kms->caps;
+	u32 count;
+	const u32 *ids;
+	u32 id;
+
+	count = caps->dsi_count;
+	ids = caps->dsi_indices;
+
+	if (intf_idx >= count) {
+		drm_err(dcp_kms->dev,
+			"DSI%u: no catalog entry (%u)\n", intf_idx, count);
+		return U32_MAX;
+	}
+
+	id = ids[intf_idx];
+
+	if (id > 0xFF) {
+		drm_err(dcp_kms->dev,
+			"DSI%u: catalog id 0x%x exceeds 8-bit range\n",
+			intf_idx, id);
+		return U32_MAX;
+	}
+
+	return id;
+}
+
+int dcp_dpu_kms_init_dsi_bridges(struct dcp_dpu_kms *dcp_kms,
+				 struct component_match **matchptr)
+{
+	struct device_node *dpu_np = dcp_kms->dpus_np;
+	struct device_node *ep_np, *dsi_np, *child_np;
+	struct of_endpoint ep;
+	int ret;
+
+	for_each_of_graph_port(dpu_np, port_np) {
+		u32 dsi_idx, fw_display_id;
+		struct drm_bridge *bridge;
+
+		ep_np = of_get_next_child(port_np, NULL);
+		if (!ep_np)
+			continue;
+
+		ret = of_graph_parse_endpoint(ep_np, &ep);
+		of_node_put(ep_np);
+		if (ret)
+			continue;
+
+		dsi_idx = ep.port;
+		if (dsi_idx >= ARRAY_SIZE(dcp_kms->dsi_bridge))
+			return -E2BIG;
+
+		if (dcp_kms->dsi_bridge[dsi_idx])
+			continue;
+
+		dsi_np = NULL;
+		for_each_available_child_of_node(dpu_np, child_np) {
+			u32 reg;
+
+			if (!of_node_name_prefix(child_np, "dsi") ||
+			    of_property_read_u32(child_np, "reg", &reg) ||
+			    reg != dsi_idx)
+				continue;
+
+			dsi_np = of_node_get(child_np);
+			break;
+		}
+		of_node_put(child_np);
+		if (!dsi_np)
+			return -ENODEV;
+
+		fw_display_id =
+			dcp_dpu_kms_resolve_fw_display_id(dcp_kms, dsi_idx);
+		if (fw_display_id == U32_MAX) {
+			of_node_put(dsi_np);
+			return -EINVAL;
+		}
+
+		bridge = dcp_dsi_bridge_init(dcp_kms, dsi_np, dpu_np,
+					     ep.port, dsi_idx, fw_display_id);
+		if (IS_ERR(bridge)) {
+			ret = PTR_ERR(bridge);
+			of_node_put(dsi_np);
+			return ret;
+		}
+
+		dcp_kms->dsi_bridge[dsi_idx] = to_dcp_dsi_bridge(bridge);
+		drm_of_component_match_add(dcp_kms->pm_dev, matchptr,
+					   component_compare_of, dsi_np);
+		of_node_put(dsi_np);
+	}
+
+	return 0;
+}
+
+static int dcp_dpu_kms_setup_bridges(struct dcp_dpu_kms *dcp_kms,
+				     struct device_node *dpu_np)
+{
+	struct drm_device *dev = dcp_kms->dev;
+	struct device_node *ep_np, *remote_np;
+	struct of_endpoint ep;
+	struct drm_encoder *encoder;
+	struct drm_bridge *bridge;
+	struct drm_connector *connector;
+	int ret = 0;
+	int dsi_idx = 0;
+
+	for_each_of_graph_port(dpu_np, port_np) {
+		ep_np = of_get_next_child(port_np, NULL);
+		if (!ep_np)
+			continue;
+
+		ret = of_graph_parse_endpoint(ep_np, &ep);
+		if (ret) {
+			of_node_put(ep_np);
+			continue;
+		}
+
+		remote_np = of_graph_get_remote_port_parent(ep_np);
+		of_node_put(ep_np);
+		if (!remote_np)
+			continue;
+
+		if (!of_device_is_available(remote_np)) {
+			of_node_put(remote_np);
+			continue;
+		}
+
+		dsi_idx = ep.port;
+		if (dsi_idx >= ARRAY_SIZE(dcp_kms->dsi_bridge)) {
+			of_node_put(remote_np);
+			drm_err(dev, "too many DSI outputs\n");
+			ret = -E2BIG;
+			goto out;
+		}
+
+		drm_dbg_driver(dev, "port@%u: DSI bridge intf_idx=%u\n",
+			       ep.port, dsi_idx);
+		if (!dcp_kms->dsi_bridge[dsi_idx]) {
+			of_node_put(remote_np);
+			drm_err(dev, "port@%u: no DSI host for index %u\n",
+				ep.port, dsi_idx);
+			ret = -ENODEV;
+			goto out;
+		}
+		bridge = &dcp_kms->dsi_bridge[dsi_idx]->bridge;
+
+		of_node_put(remote_np);
+
+		encoder = dcp_encoder_init(dev, DRM_MODE_ENCODER_DSI);
+		if (IS_ERR(encoder)) {
+			ret = PTR_ERR(encoder);
+			drm_err(dev, "encoder init failed: %d\n", ret);
+			goto out;
+		}
+
+		ret = drm_bridge_attach(encoder, bridge, NULL,
+					DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+		if (ret) {
+			if (ret == -EPROBE_DEFER) {
+				drm_dbg_driver(dev,
+					       "port@%u bridge deferred; panel not ready\n",
+					       ep.port);
+				goto out;
+			}
+			drm_err(dev, "bridge attach failed: %d\n", ret);
+			goto out;
+		}
+
+		connector = drm_bridge_connector_init(dev, encoder);
+		if (IS_ERR(connector)) {
+			ret = PTR_ERR(connector);
+			drm_err(dev, "bridge connector init failed: %d\n", ret);
+			goto out;
+		}
+	}
+
+out:
+	if (ret)
+		dcp_dpu_kms_cleanup_bridges(dcp_kms);
+	return ret;
+}
+
+static int dcp_dpu_setup_pipeline(struct dcp_dpu_kms *dcp_kms,
+				  struct device_node *dpu_np)
+{
+	struct drm_device *dev = dcp_kms->dev;
+	struct drm_plane *primary_planes[DCP_DPU_MAX_CRTCS];
+	struct drm_plane *plane;
+	struct drm_crtc *crtc;
+	struct drm_encoder *encoder;
+	int primary_idx = 0;
+	int max_crtc_count;
+	int total_planes;
+	int i, ret;
+
+	ret = dcp_dpu_kms_setup_bridges(dcp_kms, dpu_np);
+	if (ret)
+		return dev_err_probe(dev->dev, ret, "bridge setup failed\n");
+
+	max_crtc_count = 0;
+	drm_for_each_encoder(encoder, dev)
+		max_crtc_count++;
+
+	max_crtc_count = min(max_crtc_count, DCP_DPU_MAX_CRTCS);
+	max_crtc_count = min_t(int, max_crtc_count,
+			       dcp_kms->caps->num_displays);
+	max_crtc_count = min_t(int, max_crtc_count,
+			       ARRAY_SIZE(dcp_kms->dsi_bridge));
+
+	total_planes = min_t(int, dcp_kms->caps->max_planes,
+			     DCP_DPU_MAX_CRTCS * DCP_DPU_MAX_PLANES);
+
+	for (i = 0; i < total_planes; i++) {
+		enum drm_plane_type type;
+		unsigned long possible_crtcs;
+
+		possible_crtcs = ((1UL << max_crtc_count) - 1)
+				 << dev->mode_config.num_crtc;
+
+		if (primary_idx < max_crtc_count)
+			type = DRM_PLANE_TYPE_PRIMARY;
+		else
+			type = DRM_PLANE_TYPE_OVERLAY;
+
+		plane = dcp_plane_init(dev, U32_MAX, type, possible_crtcs);
+		if (IS_ERR(plane)) {
+			ret = PTR_ERR(plane);
+			drm_err(dev, "plane init failed: %d\n", ret);
+			goto err_cleanup_bridges;
+		}
+
+		if (type == DRM_PLANE_TYPE_PRIMARY)
+			primary_planes[primary_idx++] = plane;
+	}
+
+	max_crtc_count = min(max_crtc_count, primary_idx);
+
+	dcp_kms->num_crtcs = 0;
+	for (i = 0; i < max_crtc_count; i++) {
+		u32 display_id;
+
+		if (!dcp_kms->dsi_bridge[i]) {
+			drm_err(dev, "missing DSI bridge for CRTC%u\n", i);
+			ret = -ENODEV;
+			goto err_cleanup_bridges;
+		}
+
+		display_id = dcp_kms->dsi_bridge[i]->fw_display_id;
+		if (dcp_dpu_kms_get_crtc(dcp_kms, display_id)) {
+			drm_err(dev, "duplicate firmware display ID %u\n",
+				display_id);
+			ret = -EINVAL;
+			goto err_cleanup_bridges;
+		}
+
+		crtc = dcp_crtc_init(dcp_kms, primary_planes[i],
+				     display_id);
+		if (IS_ERR(crtc)) {
+			ret = PTR_ERR(crtc);
+			drm_err(dev, "CRTC init failed: %d\n", ret);
+			goto err_cleanup_bridges;
+		}
+		dcp_kms->crtcs[dcp_kms->num_crtcs].crtc = crtc;
+		dcp_kms->crtcs[dcp_kms->num_crtcs++].display_id = display_id;
+	}
+
+	return 0;
+
+err_cleanup_bridges:
+	dcp_dpu_kms_cleanup_bridges(dcp_kms);
+	return ret;
+}
+
+int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms)
+{
+	struct drm_device *dev = dcp_kms->dev;
+	struct device_node *dpus_np = dcp_kms->dpus_np;
+	struct drm_encoder *encoder;
+	int enc_idx = 0;
+	int ret;
+
+	if (!dpus_np) {
+		drm_err(dev, "no DCP subsystem node — cannot create DRM objects\n");
+		return -ENODEV;
+	}
+
+	ret = dcp_dpu_setup_pipeline(dcp_kms, dpus_np);
+	if (ret)
+		return dev_err_probe(dev->dev, ret, "pipeline setup failed\n");
+
+	/* v1 - Encoders map one-to-one to CRTCs; cloning is unsupported. */
+	drm_for_each_encoder(encoder, dev) {
+		encoder->possible_crtcs = BIT(enc_idx);
+		encoder->possible_clones = 0;
+		enc_idx++;
+	}
+
+	drm_dbg_driver(dev,
+		       "DCP KMS: DPU ready — %d CRTCs, %d planes, %d encoders\n",
+		       dev->mode_config.num_crtc,
+		       dev->mode_config.num_total_plane,
+		       dev->mode_config.num_encoder);
+
+	return 0;
+}
+
+int dcp_kms_init(struct drm_device *ddev)
+{
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(priv->kms);
+	struct dcp_drv *drv = dcp_kms->drv;
+
+	dcp_kms->dev = ddev;
+	if (drv) {
+		drv->kms = dcp_kms;
+		dcp_kms->pm_dev = &drv->dcp_subsystem_dev->dev;
+	}
+
+	if (!dcp_kms->hfi)
+		return -ENODEV;
+	if (!dcp_kms->caps)
+		return -ENODEV;
+
+	return 0;
+}
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h +3 -0
index 333567a78ad7..857ff211e0aa 100644
@@ -50,6 +50,9 @@ int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
 				     struct drm_crtc *crtc);
 
 int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms);
+void dcp_dpu_kms_cleanup_bridges(struct dcp_dpu_kms *dcp_kms);
+int dcp_dpu_kms_init_dsi_bridges(struct dcp_dpu_kms *dcp_kms,
+				 struct component_match **matchptr);
 extern const struct msm_kms_funcs dcp_dpu_kms_funcs;
 int dcp_kms_init(struct drm_device *ddev);
 
Signature · 1 line
2.43.0
#45 lore raw

Re: [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider: - [High] drm/msm/dcp: dcp_dpu_kms_cleanup_bridges() called under component_mutex - [High] drm/msm/dcp…

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] drm/msm/dcp: dcp_dpu_kms_cleanup_bridges() called under component_mutex
- [High] drm/msm/dcp: missing remote port check and sequential bridge lookup
- [High] drm/msm/dcp: race and runtime PM leak in dcp_dpu_kms_enable_commit()
- [High] drm/msm/dcp: unbalanced msm_kms and runtime PM teardown in dcp_dpu_kms_destroy()
- [High] drm/msm/dcp: dangling CRTC and drm_device pointers in dcp_dpu_kms_destroy()
- [Medium] drm/msm/dcp: invalid possible_crtcs mask when max_crtc_count is clamped --

commit 5fc21d18f27dcbb7ac8d64f95bc52703e58d9984
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Register DCP display pipeline

This commit registers the DCP display pipeline using firmware capabilities and the device-tree display graph, integrating runtime PM and IOMMU setup and creating the DRM planes, CRTCs, encoders, bridges, and connectors.

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +0 -0
index 61a4d85653594..8a73a99b2c763 100644

[ ... ]

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +3 -0
@@ -59,3 +78,609 @@ bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms,
 
 	return false;
 }
+
+void dcp_dpu_kms_cleanup_bridges(struct dcp_dpu_kms *dcp_kms)
+{

drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms, · line 93

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +10 -0
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+		if (!dcp_kms->dsi_bridge[i])
+			continue;
+
+		dcp_dsi_bridge_cleanup(dcp_kms->dsi_bridge[i]);
+		dcp_kms->dsi_bridge[i] = NULL;
+	}
+}

[Severity: High]
Will calling dcp_dpu_kms_cleanup_bridges() from dcp_dpu_kms_setup_bridges(), dcp_dpu_setup_pipeline(), or dcp_dpu_kms_destroy() deadlock on component_mutex and break deferred probe retries?

Before registering the aggregate component master, dcp_component_register() calls dcp_dpu_kms_init_dsi_bridges() -> dcp_dsi_bridge_init(), which registers dsi_host_dev and mipi_dsi_host and adds each dsi_np to the component match list. When the DSI panel attaches, dcp_dsi_host_attach() calls component_add(&dsi_bridge->dsi_host_dev, &dcp_dsi_component_ops).

Once all components are added, drivers/base/component.c holds the non- recursive component_mutex across both aggregate bind and unbind:

try_to_bring_up_aggregate_device() [holds component_mutex]

    -> msm_drm_bind()
      -> msm_drm_init()
        -> msm_drm_kms_init()
          -> dcp_dpu_kms_hw_init()
            -> dcp_dpu_drm_obj_init()
              -> dcp_dpu_setup_pipeline()
                -> dcp_dpu_kms_setup_bridges()

take_down_aggregate_device() [holds component_mutex]

    -> msm_drm_unbind()
      -> msm_drm_uninit()
        -> msm_drm_kms_uninit()
          -> dcp_dpu_kms_destroy()

When dcp_dpu_kms_cleanup_bridges() runs from any of those paths, it calls:

dcp_dsi_bridge_cleanup()

    -> mipi_dsi_host_unregister()
      -> mipi_dsi_remove_device_fn()
        -> mipi_dsi_detach()
          -> dcp_dsi_host_detach()
            -> component_del(&dsi_bridge->dsi_host_dev, ...)

which tries to acquire component_mutex while it is already held by the calling thread.

In addition, in msm_drm_uninit(), msm_drm_kms_uninit() runs before component_unbind_all() and drm_dev_put(), so dcp_dpu_kms_destroy() tears down the bridges and deletes dsi_host_dev while it is still bound as a component and attached to encoder->bridge_chain. Destroying and clearing dcp_kms->dsi_bridge[] on error or -EPROBE_DEFER in dcp_dpu_kms_setup_bridges() or dcp_dpu_setup_pipeline() also prevents any subsequent deferred probe retry from succeeding because dcp_dpu_kms_init_dsi_bridges() only runs once before component_master_add_with_match().

[ ... ]

Quoted 16 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +16 -0
+static void dcp_dpu_kms_enable_commit(struct msm_kms *kms)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+	int ret;
+
+	ret = pm_runtime_resume_and_get(dcp_kms->pm_dev);
+	if (ret < 0) {
+		dcp_kms->commit_pm_active = false;
+		drm_err_ratelimited(dcp_kms->dev,
+				    "pm_runtime_resume_and_get failed: %d\n",
+				    ret);
+		return;
+	}
+
+	dcp_kms->commit_pm_active = true;
+}

drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms, · line 234

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +10 -0
+
+static void dcp_dpu_kms_disable_commit(struct msm_kms *kms)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+
+	if (dcp_kms->commit_pm_active) {
+		pm_runtime_put_autosuspend(dcp_kms->pm_dev);
+		dcp_kms->commit_pm_active = false;
+	}
+}

[Severity: High]
Can overlapping commits race on dcp_kms->commit_pm_active and leak a runtime PM reference on dcp_kms->pm_dev?

msm_atomic_commit_tail() calls kms->funcs->enable_commit(kms) before lock_crtcs(kms, crtc_mask) and calls kms->funcs->disable_commit(kms) after unlock_crtcs(kms, crtc_mask), while msm_atomic_async_commit() also calls enable_commit() and disable_commit() from per-CRTC worker threads, and commits on separate CRTCs execute concurrently.

If two commits overlap between dcp_dpu_kms_enable_commit() and dcp_dpu_kms_disable_commit():

1. Both commits call pm_runtime_resume_and_get(dcp_kms->pm_dev) and set the single shared boolean dcp_kms->commit_pm_active = true.
2. When the first commit finishes and calls dcp_dpu_kms_disable_commit(), it calls pm_runtime_put_autosuspend(dcp_kms->pm_dev) once and sets dcp_kms->commit_pm_active = false.
3. The second in-flight commit then sees !dcp_kms->commit_pm_active in dcp_crtc_atomic_begin() (failing the commit with cstate->hfi_error = -EIO), and when it reaches dcp_dpu_kms_disable_commit() it sees dcp_kms->commit_pm_active == false and skips pm_runtime_put_autosuspend(dcp_kms->pm_dev), leaking its runtime PM usage count.

[ ... ]

Quoted 16 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +16 -0
+static void dcp_dpu_kms_destroy(struct msm_kms *kms)
+{
+	struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+
+	dcp_dpu_kms_cleanup_bridges(dcp_kms);
+
+	if (kms->vm) {
+		struct msm_gem_vm *msm_vm = to_msm_vm(kms->vm);
+
+		if (msm_vm->mmu && msm_vm->mmu->funcs->detach) {
+			drm_dbg_driver(dcp_kms->dev,
+				       "detaching IOMMU domain from dcp-dpus\n");
+			msm_vm->mmu->funcs->detach(msm_vm->mmu);
+		}
+
+		drm_gpuvm_put(kms->vm);

drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms, · line 321

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +10 -0
+		kms->vm = NULL;
+	}
+
+	if (dcp_kms->rpm_enabled) {
+		pm_runtime_disable(dcp_kms->pm_dev);
+		dcp_kms->rpm_enabled = false;
+	}
+
+	msm_kms_destroy(&dcp_kms->base);
+}

[Severity: High]
Is the teardown in dcp_dpu_kms_destroy() balanced with dcp_kms_init() across deferred probe or component rebind?

In msm_drm_init(), msm_drm_kms_init() calls priv->kms_init(ddev) (dcp_kms_init()) and kms->funcs->hw_init(kms) (dcp_dpu_kms_hw_init()). If msm_drm_kms_init() fails (for instance with -EPROBE_DEFER when a panel is not ready in dcp_dpu_kms_setup_bridges()) or when the aggregate device unbinds, msm_drm_uninit() calls msm_drm_kms_uninit(), which flushes kms->wq and calls dcp_dpu_kms_destroy().

However, msm_kms_init(&dcp_kms->base, &dcp_dpu_kms_funcs) and pm_runtime_enable(&drv->dcp_subsystem_dev->dev) are only called once during virtio probe (in dcp_virtio_probe() and dcp_subsystem_dev_init()), not in dcp_kms_init(). When dcp_dpu_kms_destroy() runs:

- pm_runtime_disable(dcp_kms->pm_dev) disables runtime PM on pm_dev (and on driver removal, dcp_subsystem_dev_deinit() calls pm_runtime_disable() a second time on the same device).
- msm_kms_destroy(&dcp_kms->base) destroys kms->wq and kms->pending_timers[], leaving kms->wq as a dangling pointer that will be flushed and destroyed again on a subsequent bind attempt or uninit.

[Severity: High]
Does dcp_dpu_kms_destroy() (and the error path in dcp_dpu_setup_pipeline()) leave dangling CRTC and drm_device pointers reachable from HFI event callbacks?

dcp_kms_init() sets dcp_kms->dev = ddev and drv->kms = dcp_kms, and dcp_dpu_setup_pipeline() populates dcp_kms->crtcs[] with CRTCs allocated via drmm_kzalloc(ddev, ...) in dcp_crtc_init() and increments dcp_kms->num_crtcs.

When msm_drm_uninit() runs on init failure, component unbind, or during dcp_virtio_remove() -> dcp_component_unregister() before dcp_virtio_hfi_cleanup() stops the VirtIO queues, drm_dev_put(ddev) frees ddev and all drmm-managed dcp_crtc instances while dcp_kms and drv remain allocated. Because neither dcp_dpu_kms_destroy() nor dcp_dpu_setup_pipeline()'s error path clears dcp_kms->num_crtcs, dcp_kms->crtcs[], dcp_kms->dev, or drv->kms, an incoming HFI_COMMAND_DISPLAY_EVENT_VSYNC or HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE message in dcp_hfi_resp_callback() will invoke dcp_crtc_hfi_vblank_cb() or dcp_crtc_hfi_frame_done_cb() and dereference the freed dcp_crtc or drm_device.

[ ... ]

Quoted 67 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +67 -0
+int dcp_dpu_kms_init_dsi_bridges(struct dcp_dpu_kms *dcp_kms,
+				 struct component_match **matchptr)
+{
+	struct device_node *dpu_np = dcp_kms->dpus_np;
+	struct device_node *ep_np, *dsi_np, *child_np;
+	struct of_endpoint ep;
+	int ret;
+
+	for_each_of_graph_port(dpu_np, port_np) {
+		u32 dsi_idx, fw_display_id;
+		struct drm_bridge *bridge;
+
+		ep_np = of_get_next_child(port_np, NULL);
+		if (!ep_np)
+			continue;
+
+		ret = of_graph_parse_endpoint(ep_np, &ep);
+		of_node_put(ep_np);
+		if (ret)
+			continue;
+
+		dsi_idx = ep.port;
+		if (dsi_idx >= ARRAY_SIZE(dcp_kms->dsi_bridge))
+			return -E2BIG;
+
+		if (dcp_kms->dsi_bridge[dsi_idx])
+			continue;
+
+		dsi_np = NULL;
+		for_each_available_child_of_node(dpu_np, child_np) {
+			u32 reg;
+
+			if (!of_node_name_prefix(child_np, "dsi") ||
+			    of_property_read_u32(child_np, "reg", &reg) ||
+			    reg != dsi_idx)
+				continue;
+
+			dsi_np = of_node_get(child_np);
+			break;
+		}
+		of_node_put(child_np);
+		if (!dsi_np)
+			return -ENODEV;
+
+		fw_display_id =
+			dcp_dpu_kms_resolve_fw_display_id(dcp_kms, dsi_idx);
+		if (fw_display_id == U32_MAX) {
+			of_node_put(dsi_np);
+			return -EINVAL;
+		}
+
+		bridge = dcp_dsi_bridge_init(dcp_kms, dsi_np, dpu_np,
+					     ep.port, dsi_idx, fw_display_id);
+		if (IS_ERR(bridge)) {
+			ret = PTR_ERR(bridge);
+			of_node_put(dsi_np);
+			return ret;
+		}
+
+		dcp_kms->dsi_bridge[dsi_idx] = to_dcp_dsi_bridge(bridge);
+		drm_of_component_match_add(dcp_kms->pm_dev, matchptr,
+					   component_compare_of, dsi_np);
+		of_node_put(dsi_np);
+	}
+
+	return 0;
+}

[ ... ]

Quoted 3 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +3 -0
+static int dcp_dpu_setup_pipeline(struct dcp_dpu_kms *dcp_kms,
+				  struct device_node *dpu_np)
+{

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms, · line 609

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +11 -0
+	dcp_kms->num_crtcs = 0;
+	for (i = 0; i < max_crtc_count; i++) {
+		u32 display_id;
+
+		if (!dcp_kms->dsi_bridge[i]) {
+			drm_err(dev, "missing DSI bridge for CRTC%u\n", i);
+			ret = -ENODEV;
+			goto err_cleanup_bridges;
+		}
+
+		display_id = dcp_kms->dsi_bridge[i]->fw_display_id;

[Severity: High]
What happens if a DT graph port has no available remote panel or if port@0 is omitted or disabled while port@1 is active?

First, dcp_dpu_kms_setup_bridges() checks of_graph_get_remote_port_parent() and of_device_is_available(remote_np) and skips ports without an available panel, but dcp_dpu_kms_init_dsi_bridges() does not perform this check before creating dcp_kms->dsi_bridge[dsi_idx] and adding dsi_np to matchptr via drm_of_component_match_add(). Because component_add() is only called from dcp_dsi_host_attach() when a panel driver attaches to the DSI host, any unpopulated or disabled panel endpoint in DT will prevent the component master from ever binding.

Second, both dcp_dpu_kms_init_dsi_bridges() and dcp_dpu_kms_setup_bridges() index dcp_kms->dsi_bridge[] by ep.port, whereas dcp_dpu_setup_pipeline() loops from i = 0 to max_crtc_count - 1 and looks up dcp_kms->dsi_bridge[i] using the sequential CRTC index i. If port@0 is absent or disabled and only port@1 is active, dcp_kms->dsi_bridge[1] holds the active bridge while max_crtc_count is 1, so dcp_dpu_setup_pipeline() checks dcp_kms->dsi_bridge[0] and either fails with -ENODEV or binds CRTC 0 to the wrong bridge's fw_display_id.

Quoted 38 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +38 -0
+		if (dcp_dpu_kms_get_crtc(dcp_kms, display_id)) {
+			drm_err(dev, "duplicate firmware display ID %u\n",
+				display_id);
+			ret = -EINVAL;
+			goto err_cleanup_bridges;
+		}
+
+		crtc = dcp_crtc_init(dcp_kms, primary_planes[i],
+				     display_id);
+		if (IS_ERR(crtc)) {
+			ret = PTR_ERR(crtc);
+			drm_err(dev, "CRTC init failed: %d\n", ret);
+			goto err_cleanup_bridges;
+		}
+		dcp_kms->crtcs[dcp_kms->num_crtcs].crtc = crtc;
+		dcp_kms->crtcs[dcp_kms->num_crtcs++].display_id = display_id;
+	}
+
+	return 0;
+
+err_cleanup_bridges:
+	dcp_dpu_kms_cleanup_bridges(dcp_kms);
+	return ret;
+}
+
+int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms)
+{
+	struct drm_device *dev = dcp_kms->dev;
+	struct device_node *dpus_np = dcp_kms->dpus_np;
+	struct drm_encoder *encoder;
+	int enc_idx = 0;
+	int ret;
+
+	if (!dpus_np) {
+		drm_err(dev, "no DCP subsystem node — cannot create DRM objects\n");
+		return -ENODEV;
+	}
+

drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms, · line 657

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c +10 -0
+	ret = dcp_dpu_setup_pipeline(dcp_kms, dpus_np);
+	if (ret)
+		return dev_err_probe(dev->dev, ret, "pipeline setup failed\n");
+
+	/* v1 - Encoders map one-to-one to CRTCs; cloning is unsupported. */
+	drm_for_each_encoder(encoder, dev) {
+		encoder->possible_crtcs = BIT(enc_idx);
+		encoder->possible_clones = 0;
+		enc_idx++;
+	}

[Severity: Medium]
Can plane and encoder possible_crtcs masks reference non-existent CRTCs when max_crtc_count is clamped in dcp_dpu_setup_pipeline()?

In dcp_dpu_setup_pipeline(), dcp_dpu_kms_setup_bridges() creates an encoder for each active DSI port before CRTC and plane limits are applied:

max_crtc_count = min(max_crtc_count, DCP_DPU_MAX_CRTCS); max_crtc_count = min_t(int, max_crtc_count,

                         dcp_kms->caps->num_displays);
  max_crtc_count = min_t(int, max_crtc_count,
                         ARRAY_SIZE(dcp_kms->dsi_bridge));

Then, during plane creation, possible_crtcs is computed from max_crtc_count before max_crtc_count is clamped again to min(max_crtc_count, primary_idx). Finally, in dcp_dpu_drm_obj_init(), every created encoder is assigned encoder->possible_crtcs = BIT(enc_idx).

If caps->num_displays or total_planes clamps the number of created CRTCs below the number of encoders, planes and encoders will have possible_crtcs bits set for CRTC indices that do not exist, triggering a WARN() in validate_encoder_possible_crtcs() during drm_mode_config_validate().

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
#46 lore raw

PATCH v2 18/23 virtio: Add Qualcomm Platform Services Firmware Interface ID

In reply to Jayant Shekhar

The Qualcomm Platform Services Firmware Interface (QPSFI) is exposed as a VirtIO device with device ID 52. Define VIRTIO_ID_QPSFI in the common VirtIO ID header so transport and function drivers use the shared identifier.

Assisted-by LLM
---
 include/uapi/linux/virtio_ids.h | 1 +
 1 file changed, 1 insertion(+)
include/uapi/linux/virtio_ids.h +1 -0
index f9056af0c622..6c6f283733b1 100644
@@ -70,6 +70,7 @@
 #define VIRTIO_ID_GPIO			41 /* virtio gpio */
 #define VIRTIO_ID_SPI			45 /* virtio spi */
 #define VIRTIO_ID_MEDIA			48 /* virtio media */
+#define VIRTIO_ID_QPSFI			52 /* Qualcomm Platform Services Firmware Interface */
 
 /*
  * Virtio Transitional IDs
Signature · 1 line
2.43.0
#47 lore raw

Re: [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID

Quoted 1 line · Jayant Shekhar

The Qualcomm Platform Services Firmware Interface (QPSFI) is exposed

Can it be anything other than the DCP? If so, how would you identify it on the Linux side?

Quoted 26 lines

as a VirtIO device with device ID 52. Define VIRTIO_ID_QPSFI in the common VirtIO ID header so transport and function drivers use the shared identifier.

Assisted-by LLM
---
 include/uapi/linux/virtio_ids.h | 1 +
 1 file changed, 1 insertion(+)
include/uapi/linux/virtio_ids.h +1 -0
index f9056af0c622..6c6f283733b1 100644
@@ -70,6 +70,7 @@
 #define VIRTIO_ID_GPIO			41 /* virtio gpio */
 #define VIRTIO_ID_SPI			45 /* virtio spi */
 #define VIRTIO_ID_MEDIA			48 /* virtio media */
+#define VIRTIO_ID_QPSFI			52 /* Qualcomm Platform Services Firmware Interface */
 
 /*
  * Virtio Transitional IDs
Signature · 2 lines
-- 
2.43.0
Signature · 2 lines
With best wishes
Dmitry
#48 lore raw

Re: [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID

In reply to Jayant Shekhar
Quoted 16 lines · Jayant Shekhar

The Qualcomm Platform Services Firmware Interface (QPSFI) is exposed as a VirtIO device with device ID 52. Define VIRTIO_ID_QPSFI in the common VirtIO ID header so transport and function drivers use the shared identifier.

Assisted-by LLM
---
 include/uapi/linux/virtio_ids.h | 1 +
 1 file changed, 1 insertion(+)
include/uapi/linux/virtio_ids.h +0 -0
index f9056af0c622..6c6f283733b1 100644

include/uapi/linux/virtio_ids.h line 76

Quoted 7 lines
include/uapi/linux/virtio_ids.h +1 -0
@@ -70,6 +70,7 @@
 #define VIRTIO_ID_GPIO			41 /* virtio gpio */
 #define VIRTIO_ID_SPI			45 /* virtio spi */
 #define VIRTIO_ID_MEDIA			48 /* virtio media */
+#define VIRTIO_ID_QPSFI			52 /* Qualcomm Platform Services Firmware Interface */
 
 /*
  * Virtio Transitional IDs

virtio IDs are allocated through the virtio TC.

Reserving this spcific one as a generic VIRTIO_ID_VENDOR is now under discussion on the virtio-comments mailing list:

https://lore.kernel.org/all/[email protected]/

Please
- switch your driver to use that + qualcomm vendor id + validating the device through the DT
- do not merge this yet, before the virtio TC has reserved the device ID.

Quoted 2 lines
Signature · 1 line
2.43.0
#49 lore raw

Re: [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID

In reply to Jayant Shekhar
Sashiko has reviewed this patch and found no issues.
Quoted 7 lines

The Qualcomm Platform Services Firmware Interface (QPSFI) is exposed as a VirtIO device with device ID 52. Define VIRTIO_ID_QPSFI in the common VirtIO ID header so transport and function drivers use the shared identifier.

Assisted-by LLM

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
#50 lore raw

PATCH v2 19/23 drm/msm/dcp: Add Qualcomm VirtIO transport wrapper

In reply to Jayant Shekhar

Add the Qualcomm-specific VirtIO transport required by DCP firmware.

The transport sends an IPCC power notification before accessing the VirtIO MMIO registers, then registers a VirtIO device directly.

Implement MMIO configuration access, feature negotiation, virtqueue setup, interrupt handling, reset, DMA setup, and suspend/resume sequencing in the transport. Keep power-management ordering in the transport so VirtIO queues are quiesced before DCP power changes.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile                   |   1 +
 drivers/gpu/drm/msm/dcp/dcp_virtio.h           |  23 +
 drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c | 643 +++++++++++++++++++++++++
 3 files changed, 667 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index 9f0a9bfeb2a7..e1a995a8c980 100644
@@ -85,6 +85,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_plane.o \
 	dcp/dcp_ipcc.o \
 	dcp/dcp_encoder.o \
+	dcp/dcp_virtio_transport.o \
 	dcp/hfi/hfi_packer.o \
 	dcp/hfi/hfi_unpacker.o \
 	dcp/hfi/hfi_utils.o
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +23 -0
new file mode 100644
index 000000000000..a9c3b9aff61d
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __DCP_VIRTIO_H__
+#define __DCP_VIRTIO_H__
+
+#include <linux/device.h>
+#include <linux/virtio.h>
+
+struct dcp_drv;
+struct dcp_ipcc;
+
+struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
+
+int dcp_virtio_transport_register(void);
+void dcp_virtio_transport_unregister(void);
+
+int dcp_virtio_register(void);
+void dcp_virtio_unregister(void);
+
+#endif /* __DCP_VIRTIO_H__ */
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +643 -0

643 lines not on this page · raw message

Signature · 1 line
2.43.0
#51 lore raw

Re: [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper

Quoted 10 lines · Jayant Shekhar

Add the Qualcomm-specific VirtIO transport required by DCP firmware.

The transport sends an IPCC power notification before accessing the VirtIO MMIO registers, then registers a VirtIO device directly.

Implement MMIO configuration access, feature negotiation, virtqueue setup, interrupt handling, reset, DMA setup, and suspend/resume sequencing in the transport. Keep power-management ordering in the transport so VirtIO queues are quiesced before DCP power changes.

Quoted 6 lines
Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile                   |   1 +
 drivers/gpu/drm/msm/dcp/dcp_virtio.h           |  23 +
 drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c | 643 +++++++++++++++++++++++++

This is a separate VirtIO host driver. Please move it to drivers/virtio like the rest of the VirtIO hosts. It has nothing to do with the drm subsystem now.

Quoted 25 lines
 3 files changed, 667 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index 9f0a9bfeb2a7..e1a995a8c980 100644
@@ -85,6 +85,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_plane.o \
 	dcp/dcp_ipcc.o \
 	dcp/dcp_encoder.o \
+	dcp/dcp_virtio_transport.o \
 	dcp/hfi/hfi_packer.o \
 	dcp/hfi/hfi_unpacker.o \
 	dcp/hfi/hfi_utils.o
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +5 -0
new file mode 100644
index 000000000000..a9c3b9aff61d
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+

drivers/gpu/drm/msm/dcp/dcp_virtio.h line 15

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +10 -0
+#ifndef __DCP_VIRTIO_H__
+#define __DCP_VIRTIO_H__
+
+#include <linux/device.h>
+#include <linux/virtio.h>
+
+struct dcp_drv;
+struct dcp_ipcc;
+
+struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);

What is the use for it? The DCP driver should not communicate to the IPCC, it's a part of the underlying VirtIO mechanism. If we export DCP to the VM there will be no IPCC anymore.

drivers/gpu/drm/msm/dcp/dcp_virtio.h line 21

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +6 -0
+
+int dcp_virtio_transport_register(void);
+void dcp_virtio_transport_unregister(void);
+
+int dcp_virtio_register(void);
+void dcp_virtio_unregister(void);

Unused, remove these two.

Quoted 53 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +2 -0
+
+#endif /* __DCP_VIRTIO_H__ */
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +45 -0
new file mode 100644
index 000000000000..02c0e2a9fafe
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Qualcomm DCP VirtIO transport.
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <uapi/linux/virtio_mmio.h>
+#include <linux/virtio_ring.h>
+
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+
+#define VIRTIO_MMIO_VRING_ALIGN		PAGE_SIZE
+
+#define to_dcp_virtio_transport(_plat_dev) \
+	container_of(_plat_dev, struct dcp_virtio_transport, vdev)
+
+struct dcp_virtio_transport {
+	struct virtio_device vdev;
+	struct platform_device *pdev;
+
+	void __iomem *base;
+	unsigned long version;
+
+	bool wake_irq_enabled;
+	struct dcp_ipcc ipcc;
+};
+
+static u64 dcp_virtio_get_features(struct virtio_device *vdev)

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 55

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +10 -0
+{
+	struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+	u64 features;
+
+	writel(1, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
+	features = readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);
+	features <<= 32;
+
+	writel(0, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
+	features |= readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);

Can we turn virtio-mmio into the library code instead of copy-pasting everything here?

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +4 -0
+
+	return features;
+}
+

[...]

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 505

Quoted 3 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +3 -0
+
+	magic = readl(transport->base + VIRTIO_MMIO_MAGIC_VALUE);
+	if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) {

Is it really a correct magic here? should it be a different magic as you are not just a virtio-mmio?

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 512

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +7 -0
+		dev_warn(&pdev->dev, "Wrong magic value 0x%08lx!\n", magic);
+		ret = -ENODEV;
+		goto err_node;
+	}
+
+	transport->version = readl(transport->base + VIRTIO_MMIO_VERSION);
+	if (transport->version < 1 || transport->version > 3) {

Isn't transport version fixed for the DCP purposes? Why do we need to determine it again at the runtime?

Quoted 121 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +121 -0
+		dev_err(&pdev->dev, "Version %ld not supported!\n",
+			transport->version);
+		ret = -ENXIO;
+		goto err_node;
+	}
+
+	transport->vdev.id.device =
+		readl(transport->base + VIRTIO_MMIO_DEVICE_ID);
+	if (!transport->vdev.id.device) {
+		ret = -ENODEV;
+		goto err_node;
+	}
+	transport->vdev.id.vendor =
+		readl(transport->base + VIRTIO_MMIO_VENDOR_ID);
+
+	if (transport->version == 1) {
+		writel(PAGE_SIZE,
+		       transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+		ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
+		if (!ret)
+			ret = dma_set_coherent_mask(&pdev->dev,
+						    DMA_BIT_MASK(32 + PAGE_SHIFT));
+	} else {
+		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	}
+	if (ret)
+		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret)
+		dev_warn(&pdev->dev,
+			 "Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.\n");
+
+	ret = register_virtio_device(&transport->vdev);
+	if (ret) {
+		put_device(&transport->vdev.dev);
+		dcp_ipcc_deinit(&transport->ipcc);
+		platform_set_drvdata(pdev, NULL);
+		return ret;
+	}
+
+	return 0;
+
+err_node:
+err_ipcc:
+	dcp_ipcc_deinit(&transport->ipcc);
+err_free:
+	platform_set_drvdata(pdev, NULL);
+	return ret;
+}
+
+static void dcp_virtio_transport_remove(struct platform_device *pdev)
+{
+	struct dcp_virtio_transport *transport = platform_get_drvdata(pdev);
+
+	unregister_virtio_device(&transport->vdev);
+	platform_set_drvdata(pdev, NULL);
+	dcp_ipcc_deinit(&transport->ipcc);
+}
+
+static int dcp_virtio_transport_suspend(struct device *dev)
+{
+	struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+	int ret;
+
+	ret = virtio_device_freeze(&transport->vdev);
+	if (ret)
+		return ret;
+
+	ret = dcp_ipcc_enable_irq_wake(&transport->ipcc);
+	if (ret)
+		goto err_restore;
+
+	ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+	if (ret) {
+		dcp_ipcc_disable_irq_wake(&transport->ipcc);
+		goto err_restore;
+	}
+
+	return 0;
+
+err_restore:
+	virtio_device_restore(&transport->vdev);
+	return ret;
+}
+
+static int dcp_virtio_transport_resume(struct device *dev)
+{
+	struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+	int ret;
+
+	ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+	if (ret)
+		return ret;
+
+	dcp_ipcc_disable_irq_wake(&transport->ipcc);
+	if (transport->version == 1)
+		writel(PAGE_SIZE,
+		       transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+
+	return virtio_device_restore(&transport->vdev);
+}
+
+static const struct dev_pm_ops dcp_virtio_transport_pm_ops = {
+	.suspend = dcp_virtio_transport_suspend,
+	.resume = dcp_virtio_transport_resume,
+};
+
+static const struct of_device_id dcp_virtio_transport_of_match[] = {
+	{ .compatible = "qcom,dcp-virtio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, dcp_virtio_transport_of_match);
+
+static struct platform_driver dcp_virtio_transport_driver = {
+	.probe = dcp_virtio_transport_probe,
+	.remove = dcp_virtio_transport_remove,
+	.driver = {
+		.name = "qcom-dcp-virtio",
+		.of_match_table = dcp_virtio_transport_of_match,
+		.pm = &dcp_virtio_transport_pm_ops,
+	},
+};

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 643

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +10 -0
+
+int dcp_virtio_transport_register(void)
+{
+	return platform_driver_register(&dcp_virtio_transport_driver);
+}
+
+void dcp_virtio_transport_unregister(void)
+{
+	platform_driver_unregister(&dcp_virtio_transport_driver);
+}

Turn it into a separate driver, use module_platform_driver().

Signature · 2 lines
With best wishes
Dmitry
#52 lore raw

Re: [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper

Quoted 17 lines · Dmitry Baryshkov
Quoted 10 lines · Jayant Shekhar

Add the Qualcomm-specific VirtIO transport required by DCP firmware.

The transport sends an IPCC power notification before accessing the VirtIO MMIO registers, then registers a VirtIO device directly.

Implement MMIO configuration access, feature negotiation, virtqueue setup, interrupt handling, reset, DMA setup, and suspend/resume sequencing in the transport. Keep power-management ordering in the transport so VirtIO queues are quiesced before DCP power changes.

Quoted 6 lines
Assisted-by LLM
---
  drivers/gpu/drm/msm/Makefile                   |   1 +
  drivers/gpu/drm/msm/dcp/dcp_virtio.h           |  23 +
  drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c | 643 +++++++++++++++++++++++++
Quoted 4 lines

This is a separate VirtIO host driver. Please move it to drivers/virtio like the rest of the VirtIO hosts. It has nothing to do with the drm subsystem now.

Will be moving this into drivers/virtio in v3.

Quoted 268 lines
Quoted 25 lines
  3 files changed, 667 insertions(+)
drivers/gpu/drm/msm/Makefile +1 -0
index 9f0a9bfeb2a7..e1a995a8c980 100644
@@ -85,6 +85,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
  	dcp/dcp_hfi_plane.o \
  	dcp/dcp_ipcc.o \
  	dcp/dcp_encoder.o \
+	dcp/dcp_virtio_transport.o \
  	dcp/hfi/hfi_packer.o \
  	dcp/hfi/hfi_unpacker.o \
  	dcp/hfi/hfi_utils.o
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +5 -0
new file mode 100644
index 000000000000..a9c3b9aff61d
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+

drivers/gpu/drm/msm/dcp/dcp_virtio.h line 15

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +10 -0
+#ifndef __DCP_VIRTIO_H__
+#define __DCP_VIRTIO_H__
+
+#include <linux/device.h>
+#include <linux/virtio.h>
+
+struct dcp_drv;
+struct dcp_ipcc;
+
+struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);

What is the use for it? The DCP driver should not communicate to the IPCC, it's a part of the underlying VirtIO mechanism. If we export DCP to the VM there will be no IPCC anymore.

drivers/gpu/drm/msm/dcp/dcp_virtio.h line 21

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +6 -0
+
+int dcp_virtio_transport_register(void);
+void dcp_virtio_transport_unregister(void);
+
+int dcp_virtio_register(void);
+void dcp_virtio_unregister(void);

Unused, remove these two.

Quoted 53 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h new file +2 -0
+
+#endif /* __DCP_VIRTIO_H__ */
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +45 -0
new file mode 100644
index 000000000000..02c0e2a9fafe
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Qualcomm DCP VirtIO transport.
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <uapi/linux/virtio_mmio.h>
+#include <linux/virtio_ring.h>
+
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+
+#define VIRTIO_MMIO_VRING_ALIGN		PAGE_SIZE
+
+#define to_dcp_virtio_transport(_plat_dev) \
+	container_of(_plat_dev, struct dcp_virtio_transport, vdev)
+
+struct dcp_virtio_transport {
+	struct virtio_device vdev;
+	struct platform_device *pdev;
+
+	void __iomem *base;
+	unsigned long version;
+
+	bool wake_irq_enabled;
+	struct dcp_ipcc ipcc;
+};
+
+static u64 dcp_virtio_get_features(struct virtio_device *vdev)

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 55

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +10 -0
+{
+	struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+	u64 features;
+
+	writel(1, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
+	features = readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);
+	features <<= 32;
+
+	writel(0, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
+	features |= readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);

Can we turn virtio-mmio into the library code instead of copy-pasting everything here?

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +4 -0
+
+	return features;
+}
+

[...]

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 505

Quoted 3 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +3 -0
+
+	magic = readl(transport->base + VIRTIO_MMIO_MAGIC_VALUE);
+	if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) {

Is it really a correct magic here? should it be a different magic as you are not just a virtio-mmio?

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 512

Quoted 7 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +7 -0
+		dev_warn(&pdev->dev, "Wrong magic value 0x%08lx!\n", magic);
+		ret = -ENODEV;
+		goto err_node;
+	}
+
+	transport->version = readl(transport->base + VIRTIO_MMIO_VERSION);
+	if (transport->version < 1 || transport->version > 3) {

Isn't transport version fixed for the DCP purposes? Why do we need to determine it again at the runtime?

Quoted 121 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +121 -0
+		dev_err(&pdev->dev, "Version %ld not supported!\n",
+			transport->version);
+		ret = -ENXIO;
+		goto err_node;
+	}
+
+	transport->vdev.id.device =
+		readl(transport->base + VIRTIO_MMIO_DEVICE_ID);
+	if (!transport->vdev.id.device) {
+		ret = -ENODEV;
+		goto err_node;
+	}
+	transport->vdev.id.vendor =
+		readl(transport->base + VIRTIO_MMIO_VENDOR_ID);
+
+	if (transport->version == 1) {
+		writel(PAGE_SIZE,
+		       transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+		ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
+		if (!ret)
+			ret = dma_set_coherent_mask(&pdev->dev,
+						    DMA_BIT_MASK(32 + PAGE_SHIFT));
+	} else {
+		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	}
+	if (ret)
+		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret)
+		dev_warn(&pdev->dev,
+			 "Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.\n");
+
+	ret = register_virtio_device(&transport->vdev);
+	if (ret) {
+		put_device(&transport->vdev.dev);
+		dcp_ipcc_deinit(&transport->ipcc);
+		platform_set_drvdata(pdev, NULL);
+		return ret;
+	}
+
+	return 0;
+
+err_node:
+err_ipcc:
+	dcp_ipcc_deinit(&transport->ipcc);
+err_free:
+	platform_set_drvdata(pdev, NULL);
+	return ret;
+}
+
+static void dcp_virtio_transport_remove(struct platform_device *pdev)
+{
+	struct dcp_virtio_transport *transport = platform_get_drvdata(pdev);
+
+	unregister_virtio_device(&transport->vdev);
+	platform_set_drvdata(pdev, NULL);
+	dcp_ipcc_deinit(&transport->ipcc);
+}
+
+static int dcp_virtio_transport_suspend(struct device *dev)
+{
+	struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+	int ret;
+
+	ret = virtio_device_freeze(&transport->vdev);
+	if (ret)
+		return ret;
+
+	ret = dcp_ipcc_enable_irq_wake(&transport->ipcc);
+	if (ret)
+		goto err_restore;
+
+	ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+	if (ret) {
+		dcp_ipcc_disable_irq_wake(&transport->ipcc);
+		goto err_restore;
+	}
+
+	return 0;
+
+err_restore:
+	virtio_device_restore(&transport->vdev);
+	return ret;
+}
+
+static int dcp_virtio_transport_resume(struct device *dev)
+{
+	struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+	int ret;
+
+	ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+	if (ret)
+		return ret;
+
+	dcp_ipcc_disable_irq_wake(&transport->ipcc);
+	if (transport->version == 1)
+		writel(PAGE_SIZE,
+		       transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+
+	return virtio_device_restore(&transport->vdev);
+}
+
+static const struct dev_pm_ops dcp_virtio_transport_pm_ops = {
+	.suspend = dcp_virtio_transport_suspend,
+	.resume = dcp_virtio_transport_resume,
+};
+
+static const struct of_device_id dcp_virtio_transport_of_match[] = {
+	{ .compatible = "qcom,dcp-virtio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, dcp_virtio_transport_of_match);
+
+static struct platform_driver dcp_virtio_transport_driver = {
+	.probe = dcp_virtio_transport_probe,
+	.remove = dcp_virtio_transport_remove,
+	.driver = {
+		.name = "qcom-dcp-virtio",
+		.of_match_table = dcp_virtio_transport_of_match,
+		.pm = &dcp_virtio_transport_pm_ops,
+	},
+};

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 643

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +10 -0
+
+int dcp_virtio_transport_register(void)
+{
+	return platform_driver_register(&dcp_virtio_transport_driver);
+}
+
+void dcp_virtio_transport_unregister(void)
+{
+	platform_driver_unregister(&dcp_virtio_transport_driver);
+}

Turn it into a separate driver, use module_platform_driver().

#53 lore raw

Re: [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper

In reply to Jayant Shekhar
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] drm/msm/dcp: use-after-free of vdev due to devm_kzalloc() and empty release - [High] drm/msm/dcp…

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] drm/msm/dcp: use-after-free of vdev due to devm_kzalloc() and empty release
- [High] drm/msm/dcp: broken virtqueue PM sequencing in suspend and resume
- [Medium] drm/msm/dcp: unbalanced IRQ wake in dcp_virtio_transport_resume() on error
- [Low] drm/msm/dcp: build failure in dcp_virtio_transport_suspend() when !PM_SLEEP --

commit 8e04d90ffae5a36ffd1db1c49707f1bc447c570d
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add Qualcomm VirtIO transport wrapper

This commit adds the Qualcomm-specific VirtIO MMIO transport wrapper for DCP firmware, handling IPCC power notifications, virtqueue setup, interrupt handling, and system suspend/resume sequencing.

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +0 -0
new file mode 100644
index 0000000000000..02c0e2a9fafe9

[ ... ]

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +10 -0
+struct dcp_virtio_transport {
+	struct virtio_device vdev;
+	struct platform_device *pdev;
+
+	void __iomem *base;
+	unsigned long version;
+
+	bool wake_irq_enabled;
+	struct dcp_ipcc ipcc;
+};

[ ... ]

Quoted 24 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +24 -0
+static void dcp_virtio_release_dev(struct device *dev)
+{
+}
+
+struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev)
+{
+	return &to_dcp_virtio_transport(vdev)->ipcc;
+}
+
+static int dcp_virtio_transport_probe(struct platform_device *pdev)
+{
+	struct dcp_virtio_transport *transport;
+	unsigned long magic;
+	int ret;
+
+	transport = devm_kzalloc(&pdev->dev, sizeof(*transport), GFP_KERNEL);
+	if (!transport)
+		return -ENOMEM;
+
+	transport->pdev = pdev;
+	transport->vdev.dev.parent = &pdev->dev;
+	transport->vdev.dev.release = dcp_virtio_release_dev;
+	transport->vdev.config = &dcp_virtio_config_ops;
+	platform_set_drvdata(pdev, transport);

[ ... ]

Quoted 16 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +16 -0
+	ret = register_virtio_device(&transport->vdev);
+	if (ret) {
+		put_device(&transport->vdev.dev);
+		dcp_ipcc_deinit(&transport->ipcc);
+		platform_set_drvdata(pdev, NULL);
+		return ret;
+	}
+
+	return 0;
+
+err_node:
+err_ipcc:
+	dcp_ipcc_deinit(&transport->ipcc);
+err_free:
+	platform_set_drvdata(pdev, NULL);
+	return ret;

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 569

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +10 -0
+}
+
+static void dcp_virtio_transport_remove(struct platform_device *pdev)
+{
+	struct dcp_virtio_transport *transport = platform_get_drvdata(pdev);
+
+	unregister_virtio_device(&transport->vdev);
+	platform_set_drvdata(pdev, NULL);
+	dcp_ipcc_deinit(&transport->ipcc);
+}

[Severity: High]
struct dcp_virtio_transport embeds struct virtio_device vdev, which in turn embeds a reference-counted struct device dev. In dcp_virtio_transport_probe(), transport is allocated with devm_kzalloc(&pdev->dev, ...) and transport->vdev.dev.release is set to the empty callback dcp_virtio_release_dev().

Once register_virtio_device(&transport->vdev) calls device_initialize(&dev->dev), the lifetime of transport->vdev.dev is governed by its reference count rather than the parent platform device's devres lifetime. When dcp_virtio_transport_remove() returns (or when register_virtio_device() fails), devres_release_all(&pdev->dev) immediately frees transport regardless of whether references to transport->vdev.dev are still held.

Additionally, if transport is instead allocated with kzalloc() and freed in dcp_virtio_release_dev() (as in drivers/virtio/virtio_mmio.c), calling dcp_ipcc_deinit(&transport->ipcc) after put_device(&transport->vdev.dev) in dcp_virtio_transport_probe() or after unregister_virtio_device(&transport->vdev) in dcp_virtio_transport_remove() would access transport->ipcc after the final reference drops and frees transport.

Can this cause a use-after-free of transport->vdev.dev and transport->ipcc?

Quoted 25 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +25 -0
+
+static int dcp_virtio_transport_suspend(struct device *dev)
+{
+	struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+	int ret;
+
+	ret = virtio_device_freeze(&transport->vdev);
+	if (ret)
+		return ret;
+
+	ret = dcp_ipcc_enable_irq_wake(&transport->ipcc);
+	if (ret)
+		goto err_restore;
+
+	ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+	if (ret) {
+		dcp_ipcc_disable_irq_wake(&transport->ipcc);
+		goto err_restore;
+	}
+
+	return 0;
+
+err_restore:
+	virtio_device_restore(&transport->vdev);
+	return ret;

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 604

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +10 -0
+}
+
+static int dcp_virtio_transport_resume(struct device *dev)
+{
+	struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+	int ret;
+
+	ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+	if (ret)
+		return ret;

[Severity: Medium]
During system suspend, dcp_virtio_transport_suspend() enables IRQ wake on transport->ipcc.power_notify_irq via dcp_ipcc_enable_irq_wake(&transport->ipcc).

During system resume, dcp_virtio_transport_resume() calls dcp_ipcc_send_power_notify(&transport->ipcc) before calling dcp_ipcc_disable_irq_wake(&transport->ipcc).

If dcp_ipcc_send_power_notify() fails or times out, does returning early here leave wake_depth unbalanced on transport->ipcc.power_notify_irq?

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 612

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +8 -0
+
+	dcp_ipcc_disable_irq_wake(&transport->ipcc);
+	if (transport->version == 1)
+		writel(PAGE_SIZE,
+		       transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+
+	return virtio_device_restore(&transport->vdev);
+}

[Severity: High]
In dcp_virtio_transport_suspend(), virtio_device_freeze(&transport->vdev) runs before dcp_ipcc_send_power_notify(&transport->ipcc), while transport->ipcc.resp_vq remains non-NULL because neither this function nor dcp_virtio_driver clears it during freeze. When the firmware acknowledges the power notification, dcp_ipcc_handle_rx() executes:

drivers/gpu/drm/msm/dcp/dcp_ipcc.c:dcp_ipcc_handle_rx() {
	...
	complete(&ipcc->power_notify_done);
	if (ipcc->resp_vq && virtqueue_kick_prepare(ipcc->resp_vq))
		virtqueue_notify(ipcc->resp_vq);
	...
}

Because VIRTIO_RING_F_EVENT_IDX is not negotiated, virtqueue_kick_prepare() returns true whenever VRING_USED_F_NO_NOTIFY is clear, writing to VIRTIO_MMIO_QUEUE_NOTIFY after the VirtIO device has already been frozen.

Similarly, in dcp_virtio_transport_resume(), dcp_ipcc_send_power_notify() is called before virtio_device_restore(&transport->vdev), so the RX acknowledgment kicks ipcc->resp_vq before restore. Immediately after, virtio_device_restore() calls virtio_reset_device() -> dcp_virtio_reset(), which writes 0 to VIRTIO_MMIO_STATUS and resets the hardware virtqueue state, and nothing in the restore path or dcp_virtio_driver reprograms the virtqueue registers before setting VIRTIO_CONFIG_S_DRIVER_OK.

Will this leave the virtqueues unconfigured in hardware and break the display transport across suspend and resume?

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +11 -0
+
+static const struct dev_pm_ops dcp_virtio_transport_pm_ops = {
+	.suspend = dcp_virtio_transport_suspend,
+	.resume = dcp_virtio_transport_resume,
+};
+
+static const struct of_device_id dcp_virtio_transport_of_match[] = {
+	{ .compatible = "qcom,dcp-virtio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, dcp_virtio_transport_of_match);

drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c line 633

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c new file +10 -0
+
+static struct platform_driver dcp_virtio_transport_driver = {
+	.probe = dcp_virtio_transport_probe,
+	.remove = dcp_virtio_transport_remove,
+	.driver = {
+		.name = "qcom-dcp-virtio",
+		.of_match_table = dcp_virtio_transport_of_match,
+		.pm = &dcp_virtio_transport_pm_ops,
+	},
+};

[Severity: Low]
In include/linux/virtio.h, virtio_device_freeze() and virtio_device_restore() are only declared under #ifdef CONFIG_PM_SLEEP:

#ifdef CONFIG_PM_SLEEP
int virtio_device_freeze(struct virtio_device *dev); int virtio_device_restore(struct virtio_device *dev); #endif

Since CONFIG_DRM_MSM_DCP depends on CONFIG_DRM_MSM (which only depends on CONFIG_PM) and CONFIG_VIRTIO, this file can be built with CONFIG_PM=y and CONFIG_PM_SLEEP=n.

Won't compiling dcp_virtio_transport_suspend() and dcp_virtio_transport_resume() unconditionally without #ifdef CONFIG_PM_SLEEP (and without pm_sleep_ptr() / pm_ptr() on dcp_virtio_transport_pm_ops) cause a build failure when CONFIG_PM_SLEEP is disabled?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
#54 lore raw

PATCH v2 20/23 drm/msm/dcp: Add DCP VirtIO driver

In reply to Jayant Shekhar

Add the DCP function driver that binds the VirtIO device to MSM DRM. Without a function driver, Linux cannot establish HFI queues, discover firmware capabilities, or register the firmware-owned display backend.

Set up the DCP DMA and IOMMU identity, initialize VirtIO and HFI transport, register the MSM DRM component, and synchronize suspend, resume, remove, and shutdown with in-flight HFI activity.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile         |   2 +
 drivers/gpu/drm/msm/dcp/dcp_drv.c    | 191 ++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_drv.h    |  17 +++
 drivers/gpu/drm/msm/dcp/dcp_virtio.c | 258 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_virtio.h |   2 +
 drivers/gpu/drm/msm/msm_drv.c        |   6 +-
 drivers/gpu/drm/msm/msm_drv.h        |   2 +
 7 files changed, 475 insertions(+), 3 deletions(-)
drivers/gpu/drm/msm/Makefile +2 -0
index e1a995a8c980..ffdb159dd9ad 100644
@@ -85,6 +85,8 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_plane.o \
 	dcp/dcp_ipcc.o \
 	dcp/dcp_encoder.o \
+	dcp/dcp_drv.o \
+	dcp/dcp_virtio.o \
 	dcp/dcp_virtio_transport.o \
 	dcp/hfi/hfi_packer.o \
 	dcp/hfi/hfi_unpacker.o \
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +191 -0

191 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_drv.h +17 -0
index 16f87035cb9a..3adc5855a9a2 100644
@@ -14,6 +14,7 @@
 #include "dcp_ipcc.h"
 
 struct dcp_dpu_kms;
+struct component_match;
 
 struct dcp_drv {
 	struct virtio_device	*vdev;
@@ -24,6 +25,22 @@ struct dcp_drv {
 	struct dcp_dpu_kms	*kms;
 	struct dcp_caps		 caps;
 	struct notifier_block	 panic_notifier;
+	struct dcp_ipcc		*ipcc;
 };
 
+int dcp_subsystem_dev_init(struct dcp_drv *drv);
+void dcp_subsystem_dev_deinit(struct dcp_drv *drv);
+
+int dcp_component_register(struct dcp_drv *drv);
+void dcp_component_unregister(struct dcp_drv *drv);
+
+int dcp_pm_prepare(struct device *vdev_dev);
+void dcp_pm_complete(struct device *vdev_dev);
+
+void dcp_panic_notifier_register(struct dcp_drv *drv);
+void dcp_panic_notifier_unregister(struct dcp_drv *drv);
+
+void __init msm_dcp_register(void);
+void __exit msm_dcp_unregister(void);
+
 #endif /* __DCP_DRV_H__ */
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +258 -0

258 lines not on this page · raw message

drivers/gpu/drm/msm/dcp/dcp_virtio.h +2 -0
index a9c3b9aff61d..3ded8293ab18 100644
@@ -8,9 +8,11 @@
 
 #include <linux/device.h>
 #include <linux/virtio.h>
+#include <linux/virtio_ids.h>
 
 struct dcp_drv;
 struct dcp_ipcc;
+#define VIRTIO_VENDOR_ID_DCP_HFI		1
 
 struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
 
drivers/gpu/drm/msm/msm_drv.c +3 -3
index 5e8f3ce26b32..5816c64d7ce7 100644
@@ -1093,8 +1093,8 @@ static const struct of_device_id msm_gpu_match[] = {
 	{ },
 };
 
-static int add_gpu_components(struct device *dev,
-			      struct component_match **matchptr)
+int msm_add_gpu_components(struct device *dev,
+			   struct component_match **matchptr)
 {
 	struct device_node *np;
 
@@ -1153,7 +1153,7 @@ int msm_drv_probe(struct device *master_dev,
 	}
 
 	if (!msm_separate_gpu_kms_components()) {
-		ret = add_gpu_components(master_dev, &match);
+		ret = msm_add_gpu_components(master_dev, &match);
 		if (ret)
 			return ret;
 	}
drivers/gpu/drm/msm/msm_drv.h +2 -0
index 2840e8a5052d..aa5dfd82ac6f 100644
@@ -523,6 +523,8 @@ static inline unsigned long timeout_to_jiffies(const ktime_t *timeout)
 /* Driver helpers */
 
 extern const struct component_master_ops msm_drm_ops;
+int msm_add_gpu_components(struct device *dev,
+			   struct component_match **matchptr);
 
 int msm_kms_pm_prepare(struct device *dev);
 void msm_kms_pm_complete(struct device *dev);
Signature · 1 line
2.43.0
#55 lore raw

Re: [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver

Quoted 76 lines · Jayant Shekhar

Add the DCP function driver that binds the VirtIO device to MSM DRM. Without a function driver, Linux cannot establish HFI queues, discover firmware capabilities, or register the firmware-owned display backend.

Set up the DCP DMA and IOMMU identity, initialize VirtIO and HFI transport, register the MSM DRM component, and synchronize suspend, resume, remove, and shutdown with in-flight HFI activity.

Assisted-by LLM
---
 drivers/gpu/drm/msm/Makefile         |   2 +
 drivers/gpu/drm/msm/dcp/dcp_drv.c    | 191 ++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_drv.h    |  17 +++
 drivers/gpu/drm/msm/dcp/dcp_virtio.c | 258 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_virtio.h |   2 +
 drivers/gpu/drm/msm/msm_drv.c        |   6 +-
 drivers/gpu/drm/msm/msm_drv.h        |   2 +
 7 files changed, 475 insertions(+), 3 deletions(-)
drivers/gpu/drm/msm/Makefile +2 -0
index e1a995a8c980..ffdb159dd9ad 100644
@@ -85,6 +85,8 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
 	dcp/dcp_hfi_plane.o \
 	dcp/dcp_ipcc.o \
 	dcp/dcp_encoder.o \
+	dcp/dcp_drv.o \
+	dcp/dcp_virtio.o \
 	dcp/dcp_virtio_transport.o \
 	dcp/hfi/hfi_packer.o \
 	dcp/hfi/hfi_unpacker.o \
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +36 -0
new file mode 100644
index 000000000000..8c38de883bf6
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP VirtIO driver.
+ */
+
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/panic_notifier.h>
+#include <linux/pm_runtime.h>
+
+#define DCP_DPU_AUTOSUSPEND_DELAY_MS	5000
+
+#include "dcp_drv.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+int dcp_subsystem_dev_init(struct dcp_drv *drv)
+{
+	struct virtio_device *vdev = drv->vdev;
+	struct device_node *dcp_subsystem_node;
+	struct platform_device *pdev;
+	struct device *dev;
+	char busid[32];
+	bool iommu_mapped;
+	int ret;
+
+	dcp_subsystem_node = of_node_get(vdev->dev.of_node);
+	if (!dcp_subsystem_node) {

drivers/gpu/drm/msm/dcp/dcp_drv.c line 46

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +10 -0
+		dev_err(&vdev->dev, "no DCP subsystem DT node\n");
+		return -ENODEV;
+	}
+
+	snprintf(busid, sizeof(busid), "dcp-subsystem.%s", dev_name(vdev->dev.parent));
+
+	/*
+	 * DCP is discovered through VirtIO, but display/HFI buffers must be
+	 * mapped through the dcp-subsystem node's DMA/IOMMU identity. Create a
+	 * platform device for that DT node so DMA API and msm_kms_init_vm() use

Does DMA API require platform_device or would it work with the generic struct device? In the latter case, should the MSM layer be changed to pass struct device instead of struct platform_device?

Quoted 84 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +84 -0
+	 * the display function's IOMMU domain.
+	 */
+	pdev = of_platform_device_create(dcp_subsystem_node, busid, vdev->dev.parent);
+	if (!pdev) {
+		dev_err(&vdev->dev, "dcp_subsystem_dev creation failed\n");
+		of_node_put(dcp_subsystem_node);
+		return -ENODEV;
+	}
+	dev = &pdev->dev;
+
+	dev->dma_parms = &drv->dcp_subsystem_dma_parms;
+	dma_set_max_seg_size(dev, UINT_MAX);
+
+	ret = of_dma_configure_id(dev, dcp_subsystem_node, true, NULL);
+	if (ret) {
+		dev_err(dev, "of_dma_configure_id failed: %d\n", ret);
+		goto err_destroy;
+	}
+
+	iommu_mapped = device_iommu_mapped(dev);
+	if (!iommu_mapped) {
+		dev_err(dev, "no IOMMU group attached\n");
+		ret = -ENODEV;
+		goto err_destroy;
+	}
+
+	dev_dbg(dev, "dcp-subsystem: iommu_mapped=%d iommu_group=%d\n",
+		iommu_mapped,
+		dev->iommu_group ? iommu_group_id(dev->iommu_group) : -1);
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_set_autosuspend_delay(dev, DCP_DPU_AUTOSUSPEND_DELAY_MS);
+	pm_runtime_use_autosuspend(dev);
+
+	drv->dcp_subsystem_dev = pdev;
+	drv->dcp_subsystem_node = dcp_subsystem_node;
+
+	return 0;
+
+err_destroy:
+	of_platform_device_destroy(dev, NULL);
+	of_node_put(dcp_subsystem_node);
+	return ret;
+}
+
+void dcp_subsystem_dev_deinit(struct dcp_drv *drv)
+{
+	if (!drv->dcp_subsystem_dev)
+		return;
+
+	pm_runtime_disable(&drv->dcp_subsystem_dev->dev);
+	of_platform_device_destroy(&drv->dcp_subsystem_dev->dev, NULL);
+	of_node_put(drv->dcp_subsystem_node);
+	drv->dcp_subsystem_dev = NULL;
+}
+
+int dcp_pm_prepare(struct device *vdev_dev)
+{
+	struct dcp_drv *drv = vdev_dev ?
+		dev_to_virtio(vdev_dev)->priv : NULL;
+
+	if (!drv || !drv->dcp_subsystem_dev)
+		return 0;
+
+	return msm_kms_pm_prepare(&drv->dcp_subsystem_dev->dev);
+}
+
+void dcp_pm_complete(struct device *vdev_dev)
+{
+	struct dcp_drv *drv = vdev_dev ?
+		dev_to_virtio(vdev_dev)->priv : NULL;
+
+	if (!drv || !drv->dcp_subsystem_dev)
+		return;
+
+	msm_kms_pm_complete(&drv->dcp_subsystem_dev->dev);
+}
+
+int dcp_component_register(struct dcp_drv *drv)
+{
+	struct device *dev = &drv->dcp_subsystem_dev->dev;
+	struct component_match *match = NULL;
+	int ret;

drivers/gpu/drm/msm/dcp/dcp_drv.c line 140

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +10 -0
+
+	ret = dcp_dpu_kms_init_dsi_bridges(drv->kms, &match);
+	if (ret) {
+		dev_err(dev, "DSI host initialization failed: %d\n", ret);
+		dcp_dpu_kms_cleanup_bridges(drv->kms);
+		return ret;
+	}
+
+	/* A GPU is optional for the DCP display-only DRM device. */
+	msm_add_gpu_components(dev, &match);

Missign error handling.

Quoted 143 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +51 -0
+
+	ret = component_master_add_with_match(dev, &msm_drm_ops, match);
+	if (ret) {
+		dev_err(dev, "component_master_add_with_match failed: %d\n",
+			ret);
+		dcp_dpu_kms_cleanup_bridges(drv->kms);
+		return ret;
+	}
+
+	return 0;
+}
+
+void dcp_component_unregister(struct dcp_drv *drv)
+{
+	struct device *dev = &drv->dcp_subsystem_dev->dev;
+
+	component_master_del(dev, &msm_drm_ops);
+	dcp_dpu_kms_cleanup_bridges(drv->kms);
+}
+
+static int dcp_panic_notifier_cb(struct notifier_block *nb,
+				 unsigned long action, void *data)
+{
+	struct dcp_drv *drv = container_of(nb, struct dcp_drv, panic_notifier);
+
+	if (drv->hfi)
+		atomic_set(&drv->hfi->shutdown_in_progress, 1);
+
+	return NOTIFY_OK;
+}
+
+void dcp_panic_notifier_register(struct dcp_drv *drv)
+{
+	drv->panic_notifier.notifier_call = dcp_panic_notifier_cb;
+	atomic_notifier_chain_register(&panic_notifier_list, &drv->panic_notifier);
+}
+
+void dcp_panic_notifier_unregister(struct dcp_drv *drv)
+{
+	atomic_notifier_chain_unregister(&panic_notifier_list, &drv->panic_notifier);
+}
+
+void __init msm_dcp_register(void)
+{
+	dcp_virtio_register();
+}
+
+void __exit msm_dcp_unregister(void)
+{
+	dcp_virtio_unregister();
+}
drivers/gpu/drm/msm/dcp/dcp_drv.h +17 -0
index 16f87035cb9a..3adc5855a9a2 100644
@@ -14,6 +14,7 @@
 #include "dcp_ipcc.h"
 
 struct dcp_dpu_kms;
+struct component_match;
 
 struct dcp_drv {
 	struct virtio_device	*vdev;
@@ -24,6 +25,22 @@ struct dcp_drv {
 	struct dcp_dpu_kms	*kms;
 	struct dcp_caps		 caps;
 	struct notifier_block	 panic_notifier;
+	struct dcp_ipcc		*ipcc;
 };
 
+int dcp_subsystem_dev_init(struct dcp_drv *drv);
+void dcp_subsystem_dev_deinit(struct dcp_drv *drv);
+
+int dcp_component_register(struct dcp_drv *drv);
+void dcp_component_unregister(struct dcp_drv *drv);
+
+int dcp_pm_prepare(struct device *vdev_dev);
+void dcp_pm_complete(struct device *vdev_dev);
+
+void dcp_panic_notifier_register(struct dcp_drv *drv);
+void dcp_panic_notifier_unregister(struct dcp_drv *drv);
+
+void __init msm_dcp_register(void);
+void __exit msm_dcp_unregister(void);
+
 #endif /* __DCP_DRV_H__ */
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +51 -0
new file mode 100644
index 000000000000..15d5caf02ff5
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP VirtIO device driver.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/pm_runtime.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/slab.h>
+
+#include "dcp_drv.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv);
+
+static const unsigned int dcp_virtio_features[] = {
+	VIRTIO_F_VERSION_1,
+	VIRTIO_F_NOTIFICATION_DATA,
+	VIRTIO_F_RING_RESET,
+	VIRTIO_F_ACCESS_PLATFORM,
+};
+
+/*
+ * The VirtIO core owns transport discovery. This driver owns HFI setup and
+ * creates the DRM component only after firmware capabilities are available.
+ */
+static int dcp_virtio_probe(struct virtio_device *vdev)
+{
+	struct dcp_drv *drv;
+	struct virtqueue *vqs[DCP_NUM_VQS];
+	struct virtqueue_info vqs_info[DCP_NUM_VQS] = {
+		[DCP_VQ_CMD]  = { .name = "cmd",  .callback = dcp_hfi_cmd_callback },
+		[DCP_VQ_RESP] = { .name = "resp", .callback = dcp_hfi_resp_callback },
+	};
+	struct dcp_dpu_kms *dcp_kms;
+	struct msm_drm_private *priv;
+	int ret;
+
+	dev_dbg(&vdev->dev, "DCP VirtIO probe started\n");
+
+	drv = devm_kzalloc(&vdev->dev, sizeof(*drv), GFP_KERNEL);
+	if (!drv)
+		return -ENOMEM;

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 61

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +10 -0
+
+	drv->vdev = vdev;
+	vdev->priv = drv;
+
+	if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {
+		dev_err(&vdev->dev, "VIRTIO_F_VERSION_1 not negotiated\n");
+		return -ENODEV;
+	}
+
+	drv->ipcc = dcp_virtio_get_ipcc(vdev);

Here you are breaking the abstraction boundary. The DCP is a VirtIO device. You should not use IPCC to communicate with it.

Quoted 144 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +144 -0
+	/* Access-platform is required for the firmware-visible IOVA mappings. */
+	if (!virtio_has_feature(vdev, VIRTIO_F_ACCESS_PLATFORM)) {
+		dev_err(&vdev->dev, "VIRTIO_F_ACCESS_PLATFORM not negotiated\n");
+		return -ENODEV;
+	}
+
+	ret = dcp_subsystem_dev_init(drv);
+	if (ret)
+		return ret;
+
+	ret = virtio_find_vqs(vdev, DCP_NUM_VQS, vqs, vqs_info, NULL);
+	if (ret) {
+		dev_err(&vdev->dev, "virtqueue allocation failed: %d\n", ret);
+		goto err_fn_dev;
+	}
+
+	ret = dcp_hfi_init(drv, vdev, vqs[DCP_VQ_CMD], vqs[DCP_VQ_RESP],
+			   vdev->dev.parent);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI init failed: %d\n", ret);
+		goto err_del_vqs;
+	}
+
+	dcp_ipcc_set_resp_vq(drv->ipcc, vqs[DCP_VQ_RESP]);
+
+	/*
+	 * Keep the device unready until HFI state and virtqueues are initialized.
+	 * KMS registration below depends on synchronous firmware initialization.
+	 */
+	virtio_device_ready(vdev);
+	virtqueue_kick(drv->hfi->resp_vq);
+
+	ret = dcp_hfi_send_device_init(drv->hfi);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI DEVICE_INIT failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	ret = dcp_hfi_get_capabilities(drv->hfi, &drv->caps);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI GET_CAPS failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	/* Register the display resources before exposing the DRM component. */
+	ret = dcp_hfi_send_resource_register(drv->hfi);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI RESOURCE_REGISTER failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	dev_dbg(&vdev->dev,
+		"DCP caps: %u displays, max %ux%u, %u planes\n",
+		drv->caps.num_displays, drv->caps.max_width, drv->caps.max_height,
+		drv->caps.max_planes);
+
+	dcp_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dcp_kms), GFP_KERNEL);
+	if (!dcp_kms) {
+		ret = -ENOMEM;
+		goto err_hfi_deinit;
+	}
+
+	dcp_kms->drv       = drv;
+	dcp_kms->pm_dev    = &drv->dcp_subsystem_dev->dev;
+	dcp_kms->hfi       = drv->hfi;
+	dcp_kms->caps      = &drv->caps;
+	dcp_kms->dpus_np   = drv->dcp_subsystem_node;
+	dcp_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
+	drv->kms        = dcp_kms;
+
+	ret = msm_kms_init(&dcp_kms->base, &dcp_dpu_kms_funcs);
+	if (ret) {
+		dev_err(&vdev->dev, "msm_kms_init failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	priv = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		ret = -ENOMEM;
+		goto err_hfi_deinit;
+	}
+
+	priv->kms      = &dcp_kms->base;
+	priv->kms_init = dcp_kms_init;
+	dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv);
+	dcp_kms->rpm_enabled = true;
+
+	/* Component binding now sees initialized HFI, capabilities, and IOMMU state. */
+	ret = dcp_component_register(drv);
+	if (ret) {
+		dev_err_probe(&vdev->dev, ret, "component register failed\n");
+		goto err_hfi_deinit;
+	}
+
+	dcp_panic_notifier_register(drv);
+
+	dev_dbg(&vdev->dev, "DCP VirtIO probe complete\n");
+	return 0;
+
+err_hfi_deinit:
+	dcp_virtio_hfi_cleanup(drv);
+	goto err_fn_dev;
+err_del_vqs:
+	virtio_reset_device(vdev);
+	vdev->config->del_vqs(vdev);
+err_fn_dev:
+	dcp_subsystem_dev_deinit(drv);
+	return ret;
+}
+
+static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv)
+{
+	struct virtio_device *vdev = drv->vdev;
+
+	if (!drv->hfi)
+		return;
+
+	dcp_ipcc_set_resp_vq(drv->ipcc, NULL);
+
+	dcp_hfi_stop(drv->hfi);
+	virtio_reset_device(vdev);
+	virtio_synchronize_cbs(vdev);
+	dcp_hfi_quiesce_queues(drv->hfi);
+	vdev->config->del_vqs(vdev);
+	dcp_hfi_deinit(drv->hfi);
+}
+
+/* Remove DRM users before tearing down HFI, IPCC, or the VirtIO queues. */
+static void dcp_virtio_remove(struct virtio_device *vdev)
+{
+	struct dcp_drv *drv = vdev->priv;
+
+	dev_dbg(&vdev->dev, "DCP VirtIO remove\n");
+
+	dcp_component_unregister(drv);
+	dcp_panic_notifier_unregister(drv);
+
+	dcp_virtio_hfi_cleanup(drv);
+
+	dcp_subsystem_dev_deinit(drv);
+}
+
+/* KMS shutdown must complete while the firmware transport is available. */
+static void dcp_virtio_shutdown(struct virtio_device *vdev)

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 215

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +10 -0
+{
+	struct dcp_drv *drv = vdev->priv;
+
+	pm_runtime_get_sync(&drv->dcp_subsystem_dev->dev);
+	msm_kms_shutdown(drv->dcp_subsystem_dev);
+	pm_runtime_put_noidle(&drv->dcp_subsystem_dev->dev);
+}
+
+static const struct virtio_device_id dcp_virtio_id_table[] = {
+	{ VIRTIO_ID_QPSFI, VIRTIO_VENDOR_ID_DCP_HFI },

What exactly does it mean? Are you using Vendor ID (which should be Qualcomm) to distinguish between possible QPSFI devices?

Quoted 48 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +43 -0
+	{ 0 },
+};
+MODULE_DEVICE_TABLE(virtio, dcp_virtio_id_table);
+
+static const struct dev_pm_ops dcp_virtio_pm_ops = {
+	.prepare = dcp_pm_prepare,
+	.complete = dcp_pm_complete,
+};
+
+static struct virtio_driver dcp_virtio_driver = {
+	.driver = {
+		.name  = "qcom-dcp-hfi",
+		.owner = THIS_MODULE,
+		.pm    = &dcp_virtio_pm_ops,
+	},
+	.id_table           = dcp_virtio_id_table,
+	.feature_table      = dcp_virtio_features,
+	.feature_table_size = ARRAY_SIZE(dcp_virtio_features),
+	.probe              = dcp_virtio_probe,
+	.remove             = dcp_virtio_remove,
+	.shutdown           = dcp_virtio_shutdown,
+};
+
+int dcp_virtio_register(void)
+{
+	int ret;
+
+	ret = dcp_virtio_transport_register();
+	if (ret)
+		return ret;
+
+	ret = register_virtio_driver(&dcp_virtio_driver);
+	if (ret)
+		dcp_virtio_transport_unregister();
+
+	return ret;
+}
+
+void dcp_virtio_unregister(void)
+{
+	unregister_virtio_driver(&dcp_virtio_driver);
+	dcp_virtio_transport_unregister();
+}
drivers/gpu/drm/msm/dcp/dcp_virtio.h +0 -0
index a9c3b9aff61d..3ded8293ab18 100644

drivers/gpu/drm/msm/dcp/dcp_virtio.h line 15

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h +2 -0
@@ -8,9 +8,11 @@
 
 #include <linux/device.h>
 #include <linux/virtio.h>
+#include <linux/virtio_ids.h>
 
 struct dcp_drv;
 struct dcp_ipcc;
+#define VIRTIO_VENDOR_ID_DCP_HFI		1

Squatting the ID?

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h +0 -0
 struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
 
drivers/gpu/drm/msm/msm_drv.c +0 -0
index 5e8f3ce26b32..5816c64d7ce7 100644

drivers/gpu/drm/msm/msm_drv.c static const struct of_device_id msm_gpu_match[] = { · line 1097

Quoted 7 lines
drivers/gpu/drm/msm/msm_drv.c +2 -2
@@ -1093,8 +1093,8 @@ static const struct of_device_id msm_gpu_match[] = {
 	{ },
 };
 
-static int add_gpu_components(struct device *dev,
-			      struct component_match **matchptr)
+int msm_add_gpu_components(struct device *dev,
+			   struct component_match **matchptr)

Separate commit.

Quoted 29 lines
drivers/gpu/drm/msm/msm_drv.c +1 -1
 {
 	struct device_node *np;
 
@@ -1153,7 +1153,7 @@ int msm_drv_probe(struct device *master_dev,
 	}
 
 	if (!msm_separate_gpu_kms_components()) {
-		ret = add_gpu_components(master_dev, &match);
+		ret = msm_add_gpu_components(master_dev, &match);
 		if (ret)
 			return ret;
 	}
drivers/gpu/drm/msm/msm_drv.h +2 -0
index 2840e8a5052d..aa5dfd82ac6f 100644
@@ -523,6 +523,8 @@ static inline unsigned long timeout_to_jiffies(const ktime_t *timeout)
 /* Driver helpers */
 
 extern const struct component_master_ops msm_drm_ops;
+int msm_add_gpu_components(struct device *dev,
+			   struct component_match **matchptr);
 
 int msm_kms_pm_prepare(struct device *dev);
 void msm_kms_pm_complete(struct device *dev);
Signature · 2 lines
-- 
2.43.0
Signature · 2 lines
With best wishes
Dmitry
#56 lore raw

Re: [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver

Quoted 334 lines · Dmitry Baryshkov
Quoted 76 lines · Jayant Shekhar

Add the DCP function driver that binds the VirtIO device to MSM DRM. Without a function driver, Linux cannot establish HFI queues, discover firmware capabilities, or register the firmware-owned display backend.

Set up the DCP DMA and IOMMU identity, initialize VirtIO and HFI transport, register the MSM DRM component, and synchronize suspend, resume, remove, and shutdown with in-flight HFI activity.

Assisted-by LLM
---
  drivers/gpu/drm/msm/Makefile         |   2 +
  drivers/gpu/drm/msm/dcp/dcp_drv.c    | 191 ++++++++++++++++++++++++++
  drivers/gpu/drm/msm/dcp/dcp_drv.h    |  17 +++
  drivers/gpu/drm/msm/dcp/dcp_virtio.c | 258 +++++++++++++++++++++++++++++++++++
  drivers/gpu/drm/msm/dcp/dcp_virtio.h |   2 +
  drivers/gpu/drm/msm/msm_drv.c        |   6 +-
  drivers/gpu/drm/msm/msm_drv.h        |   2 +
  7 files changed, 475 insertions(+), 3 deletions(-)
drivers/gpu/drm/msm/Makefile +2 -0
index e1a995a8c980..ffdb159dd9ad 100644
@@ -85,6 +85,8 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
  	dcp/dcp_hfi_plane.o \
  	dcp/dcp_ipcc.o \
  	dcp/dcp_encoder.o \
+	dcp/dcp_drv.o \
+	dcp/dcp_virtio.o \
  	dcp/dcp_virtio_transport.o \
  	dcp/hfi/hfi_packer.o \
  	dcp/hfi/hfi_unpacker.o \
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +36 -0
new file mode 100644
index 000000000000..8c38de883bf6
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP VirtIO driver.
+ */
+
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/panic_notifier.h>
+#include <linux/pm_runtime.h>
+
+#define DCP_DPU_AUTOSUSPEND_DELAY_MS	5000
+
+#include "dcp_drv.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+int dcp_subsystem_dev_init(struct dcp_drv *drv)
+{
+	struct virtio_device *vdev = drv->vdev;
+	struct device_node *dcp_subsystem_node;
+	struct platform_device *pdev;
+	struct device *dev;
+	char busid[32];
+	bool iommu_mapped;
+	int ret;
+
+	dcp_subsystem_node = of_node_get(vdev->dev.of_node);
+	if (!dcp_subsystem_node) {

drivers/gpu/drm/msm/dcp/dcp_drv.c line 46

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +10 -0
+		dev_err(&vdev->dev, "no DCP subsystem DT node\n");
+		return -ENODEV;
+	}
+
+	snprintf(busid, sizeof(busid), "dcp-subsystem.%s", dev_name(vdev->dev.parent));
+
+	/*
+	 * DCP is discovered through VirtIO, but display/HFI buffers must be
+	 * mapped through the dcp-subsystem node's DMA/IOMMU identity. Create a
+	 * platform device for that DT node so DMA API and msm_kms_init_vm() use

Does DMA API require platform_device or would it work with the generic struct device? In the latter case, should the MSM layer be changed to pass struct device instead of struct platform_device?

Quoted 84 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +84 -0
+	 * the display function's IOMMU domain.
+	 */
+	pdev = of_platform_device_create(dcp_subsystem_node, busid, vdev->dev.parent);
+	if (!pdev) {
+		dev_err(&vdev->dev, "dcp_subsystem_dev creation failed\n");
+		of_node_put(dcp_subsystem_node);
+		return -ENODEV;
+	}
+	dev = &pdev->dev;
+
+	dev->dma_parms = &drv->dcp_subsystem_dma_parms;
+	dma_set_max_seg_size(dev, UINT_MAX);
+
+	ret = of_dma_configure_id(dev, dcp_subsystem_node, true, NULL);
+	if (ret) {
+		dev_err(dev, "of_dma_configure_id failed: %d\n", ret);
+		goto err_destroy;
+	}
+
+	iommu_mapped = device_iommu_mapped(dev);
+	if (!iommu_mapped) {
+		dev_err(dev, "no IOMMU group attached\n");
+		ret = -ENODEV;
+		goto err_destroy;
+	}
+
+	dev_dbg(dev, "dcp-subsystem: iommu_mapped=%d iommu_group=%d\n",
+		iommu_mapped,
+		dev->iommu_group ? iommu_group_id(dev->iommu_group) : -1);
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_set_autosuspend_delay(dev, DCP_DPU_AUTOSUSPEND_DELAY_MS);
+	pm_runtime_use_autosuspend(dev);
+
+	drv->dcp_subsystem_dev = pdev;
+	drv->dcp_subsystem_node = dcp_subsystem_node;
+
+	return 0;
+
+err_destroy:
+	of_platform_device_destroy(dev, NULL);
+	of_node_put(dcp_subsystem_node);
+	return ret;
+}
+
+void dcp_subsystem_dev_deinit(struct dcp_drv *drv)
+{
+	if (!drv->dcp_subsystem_dev)
+		return;
+
+	pm_runtime_disable(&drv->dcp_subsystem_dev->dev);
+	of_platform_device_destroy(&drv->dcp_subsystem_dev->dev, NULL);
+	of_node_put(drv->dcp_subsystem_node);
+	drv->dcp_subsystem_dev = NULL;
+}
+
+int dcp_pm_prepare(struct device *vdev_dev)
+{
+	struct dcp_drv *drv = vdev_dev ?
+		dev_to_virtio(vdev_dev)->priv : NULL;
+
+	if (!drv || !drv->dcp_subsystem_dev)
+		return 0;
+
+	return msm_kms_pm_prepare(&drv->dcp_subsystem_dev->dev);
+}
+
+void dcp_pm_complete(struct device *vdev_dev)
+{
+	struct dcp_drv *drv = vdev_dev ?
+		dev_to_virtio(vdev_dev)->priv : NULL;
+
+	if (!drv || !drv->dcp_subsystem_dev)
+		return;
+
+	msm_kms_pm_complete(&drv->dcp_subsystem_dev->dev);
+}
+
+int dcp_component_register(struct dcp_drv *drv)
+{
+	struct device *dev = &drv->dcp_subsystem_dev->dev;
+	struct component_match *match = NULL;
+	int ret;

drivers/gpu/drm/msm/dcp/dcp_drv.c line 140

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +10 -0
+
+	ret = dcp_dpu_kms_init_dsi_bridges(drv->kms, &match);
+	if (ret) {
+		dev_err(dev, "DSI host initialization failed: %d\n", ret);
+		dcp_dpu_kms_cleanup_bridges(drv->kms);
+		return ret;
+	}
+
+	/* A GPU is optional for the DCP display-only DRM device. */
+	msm_add_gpu_components(dev, &match);

Missign error handling.

Quoted 147 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +51 -0
+
+	ret = component_master_add_with_match(dev, &msm_drm_ops, match);
+	if (ret) {
+		dev_err(dev, "component_master_add_with_match failed: %d\n",
+			ret);
+		dcp_dpu_kms_cleanup_bridges(drv->kms);
+		return ret;
+	}
+
+	return 0;
+}
+
+void dcp_component_unregister(struct dcp_drv *drv)
+{
+	struct device *dev = &drv->dcp_subsystem_dev->dev;
+
+	component_master_del(dev, &msm_drm_ops);
+	dcp_dpu_kms_cleanup_bridges(drv->kms);
+}
+
+static int dcp_panic_notifier_cb(struct notifier_block *nb,
+				 unsigned long action, void *data)
+{
+	struct dcp_drv *drv = container_of(nb, struct dcp_drv, panic_notifier);
+
+	if (drv->hfi)
+		atomic_set(&drv->hfi->shutdown_in_progress, 1);
+
+	return NOTIFY_OK;
+}
+
+void dcp_panic_notifier_register(struct dcp_drv *drv)
+{
+	drv->panic_notifier.notifier_call = dcp_panic_notifier_cb;
+	atomic_notifier_chain_register(&panic_notifier_list, &drv->panic_notifier);
+}
+
+void dcp_panic_notifier_unregister(struct dcp_drv *drv)
+{
+	atomic_notifier_chain_unregister(&panic_notifier_list, &drv->panic_notifier);
+}
+
+void __init msm_dcp_register(void)
+{
+	dcp_virtio_register();
+}
+
+void __exit msm_dcp_unregister(void)
+{
+	dcp_virtio_unregister();
+}
drivers/gpu/drm/msm/dcp/dcp_drv.h +17 -0
index 16f87035cb9a..3adc5855a9a2 100644
@@ -14,6 +14,7 @@
  #include "dcp_ipcc.h"
 
  struct dcp_dpu_kms;
+struct component_match;
 
  struct dcp_drv {
  	struct virtio_device	*vdev;
@@ -24,6 +25,22 @@ struct dcp_drv {
  	struct dcp_dpu_kms	*kms;
  	struct dcp_caps		 caps;
  	struct notifier_block	 panic_notifier;
+	struct dcp_ipcc		*ipcc;
  };
 
+int dcp_subsystem_dev_init(struct dcp_drv *drv);
+void dcp_subsystem_dev_deinit(struct dcp_drv *drv);
+
+int dcp_component_register(struct dcp_drv *drv);
+void dcp_component_unregister(struct dcp_drv *drv);
+
+int dcp_pm_prepare(struct device *vdev_dev);
+void dcp_pm_complete(struct device *vdev_dev);
+
+void dcp_panic_notifier_register(struct dcp_drv *drv);
+void dcp_panic_notifier_unregister(struct dcp_drv *drv);
+
+void __init msm_dcp_register(void);
+void __exit msm_dcp_unregister(void);
+
  #endif /* __DCP_DRV_H__ */
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +55 -0
new file mode 100644
index 000000000000..15d5caf02ff5
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP VirtIO device driver.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/pm_runtime.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/slab.h>
+
+#include "dcp_drv.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv);
+
+static const unsigned int dcp_virtio_features[] = {
+	VIRTIO_F_VERSION_1,
+	VIRTIO_F_NOTIFICATION_DATA,
+	VIRTIO_F_RING_RESET,
+	VIRTIO_F_ACCESS_PLATFORM,
+};
+
+/*
+ * The VirtIO core owns transport discovery. This driver owns HFI setup and
+ * creates the DRM component only after firmware capabilities are available.
+ */
+static int dcp_virtio_probe(struct virtio_device *vdev)
+{
+	struct dcp_drv *drv;
+	struct virtqueue *vqs[DCP_NUM_VQS];
+	struct virtqueue_info vqs_info[DCP_NUM_VQS] = {
+		[DCP_VQ_CMD]  = { .name = "cmd",  .callback = dcp_hfi_cmd_callback },
+		[DCP_VQ_RESP] = { .name = "resp", .callback = dcp_hfi_resp_callback },
+	};
+	struct dcp_dpu_kms *dcp_kms;
+	struct msm_drm_private *priv;
+	int ret;
+
+	dev_dbg(&vdev->dev, "DCP VirtIO probe started\n");
+
+	drv = devm_kzalloc(&vdev->dev, sizeof(*drv), GFP_KERNEL);
+	if (!drv)
+		return -ENOMEM;
+
+	drv->vdev = vdev;
+	vdev->priv = drv;
+
Quoted 9 lines

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 61

Quoted 6 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +6 -0
+	if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {
+		dev_err(&vdev->dev, "VIRTIO_F_VERSION_1 not negotiated\n");
+		return -ENODEV;
+	}
+
+	drv->ipcc = dcp_virtio_get_ipcc(vdev);

Here you are breaking the abstraction boundary. The DCP is a VirtIO device. You should not use IPCC to communicate with it.

Will be addressed in v3.

Quoted 226 lines
Quoted 144 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +144 -0
+	/* Access-platform is required for the firmware-visible IOVA mappings. */
+	if (!virtio_has_feature(vdev, VIRTIO_F_ACCESS_PLATFORM)) {
+		dev_err(&vdev->dev, "VIRTIO_F_ACCESS_PLATFORM not negotiated\n");
+		return -ENODEV;
+	}
+
+	ret = dcp_subsystem_dev_init(drv);
+	if (ret)
+		return ret;
+
+	ret = virtio_find_vqs(vdev, DCP_NUM_VQS, vqs, vqs_info, NULL);
+	if (ret) {
+		dev_err(&vdev->dev, "virtqueue allocation failed: %d\n", ret);
+		goto err_fn_dev;
+	}
+
+	ret = dcp_hfi_init(drv, vdev, vqs[DCP_VQ_CMD], vqs[DCP_VQ_RESP],
+			   vdev->dev.parent);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI init failed: %d\n", ret);
+		goto err_del_vqs;
+	}
+
+	dcp_ipcc_set_resp_vq(drv->ipcc, vqs[DCP_VQ_RESP]);
+
+	/*
+	 * Keep the device unready until HFI state and virtqueues are initialized.
+	 * KMS registration below depends on synchronous firmware initialization.
+	 */
+	virtio_device_ready(vdev);
+	virtqueue_kick(drv->hfi->resp_vq);
+
+	ret = dcp_hfi_send_device_init(drv->hfi);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI DEVICE_INIT failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	ret = dcp_hfi_get_capabilities(drv->hfi, &drv->caps);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI GET_CAPS failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	/* Register the display resources before exposing the DRM component. */
+	ret = dcp_hfi_send_resource_register(drv->hfi);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI RESOURCE_REGISTER failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	dev_dbg(&vdev->dev,
+		"DCP caps: %u displays, max %ux%u, %u planes\n",
+		drv->caps.num_displays, drv->caps.max_width, drv->caps.max_height,
+		drv->caps.max_planes);
+
+	dcp_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dcp_kms), GFP_KERNEL);
+	if (!dcp_kms) {
+		ret = -ENOMEM;
+		goto err_hfi_deinit;
+	}
+
+	dcp_kms->drv       = drv;
+	dcp_kms->pm_dev    = &drv->dcp_subsystem_dev->dev;
+	dcp_kms->hfi       = drv->hfi;
+	dcp_kms->caps      = &drv->caps;
+	dcp_kms->dpus_np   = drv->dcp_subsystem_node;
+	dcp_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
+	drv->kms        = dcp_kms;
+
+	ret = msm_kms_init(&dcp_kms->base, &dcp_dpu_kms_funcs);
+	if (ret) {
+		dev_err(&vdev->dev, "msm_kms_init failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	priv = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		ret = -ENOMEM;
+		goto err_hfi_deinit;
+	}
+
+	priv->kms      = &dcp_kms->base;
+	priv->kms_init = dcp_kms_init;
+	dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv);
+	dcp_kms->rpm_enabled = true;
+
+	/* Component binding now sees initialized HFI, capabilities, and IOMMU state. */
+	ret = dcp_component_register(drv);
+	if (ret) {
+		dev_err_probe(&vdev->dev, ret, "component register failed\n");
+		goto err_hfi_deinit;
+	}
+
+	dcp_panic_notifier_register(drv);
+
+	dev_dbg(&vdev->dev, "DCP VirtIO probe complete\n");
+	return 0;
+
+err_hfi_deinit:
+	dcp_virtio_hfi_cleanup(drv);
+	goto err_fn_dev;
+err_del_vqs:
+	virtio_reset_device(vdev);
+	vdev->config->del_vqs(vdev);
+err_fn_dev:
+	dcp_subsystem_dev_deinit(drv);
+	return ret;
+}
+
+static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv)
+{
+	struct virtio_device *vdev = drv->vdev;
+
+	if (!drv->hfi)
+		return;
+
+	dcp_ipcc_set_resp_vq(drv->ipcc, NULL);
+
+	dcp_hfi_stop(drv->hfi);
+	virtio_reset_device(vdev);
+	virtio_synchronize_cbs(vdev);
+	dcp_hfi_quiesce_queues(drv->hfi);
+	vdev->config->del_vqs(vdev);
+	dcp_hfi_deinit(drv->hfi);
+}
+
+/* Remove DRM users before tearing down HFI, IPCC, or the VirtIO queues. */
+static void dcp_virtio_remove(struct virtio_device *vdev)
+{
+	struct dcp_drv *drv = vdev->priv;
+
+	dev_dbg(&vdev->dev, "DCP VirtIO remove\n");
+
+	dcp_component_unregister(drv);
+	dcp_panic_notifier_unregister(drv);
+
+	dcp_virtio_hfi_cleanup(drv);
+
+	dcp_subsystem_dev_deinit(drv);
+}
+
+/* KMS shutdown must complete while the firmware transport is available. */
+static void dcp_virtio_shutdown(struct virtio_device *vdev)

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 215

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +10 -0
+{
+	struct dcp_drv *drv = vdev->priv;
+
+	pm_runtime_get_sync(&drv->dcp_subsystem_dev->dev);
+	msm_kms_shutdown(drv->dcp_subsystem_dev);
+	pm_runtime_put_noidle(&drv->dcp_subsystem_dev->dev);
+}
+
+static const struct virtio_device_id dcp_virtio_id_table[] = {
+	{ VIRTIO_ID_QPSFI, VIRTIO_VENDOR_ID_DCP_HFI },

What exactly does it mean? Are you using Vendor ID (which should be Qualcomm) to distinguish between possible QPSFI devices?

Quoted 48 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +43 -0
+	{ 0 },
+};
+MODULE_DEVICE_TABLE(virtio, dcp_virtio_id_table);
+
+static const struct dev_pm_ops dcp_virtio_pm_ops = {
+	.prepare = dcp_pm_prepare,
+	.complete = dcp_pm_complete,
+};
+
+static struct virtio_driver dcp_virtio_driver = {
+	.driver = {
+		.name  = "qcom-dcp-hfi",
+		.owner = THIS_MODULE,
+		.pm    = &dcp_virtio_pm_ops,
+	},
+	.id_table           = dcp_virtio_id_table,
+	.feature_table      = dcp_virtio_features,
+	.feature_table_size = ARRAY_SIZE(dcp_virtio_features),
+	.probe              = dcp_virtio_probe,
+	.remove             = dcp_virtio_remove,
+	.shutdown           = dcp_virtio_shutdown,
+};
+
+int dcp_virtio_register(void)
+{
+	int ret;
+
+	ret = dcp_virtio_transport_register();
+	if (ret)
+		return ret;
+
+	ret = register_virtio_driver(&dcp_virtio_driver);
+	if (ret)
+		dcp_virtio_transport_unregister();
+
+	return ret;
+}
+
+void dcp_virtio_unregister(void)
+{
+	unregister_virtio_driver(&dcp_virtio_driver);
+	dcp_virtio_transport_unregister();
+}
drivers/gpu/drm/msm/dcp/dcp_virtio.h +0 -0
index a9c3b9aff61d..3ded8293ab18 100644

drivers/gpu/drm/msm/dcp/dcp_virtio.h line 15

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h +2 -0
@@ -8,9 +8,11 @@
 
  #include <linux/device.h>
  #include <linux/virtio.h>
+#include <linux/virtio_ids.h>
 
  struct dcp_drv;
  struct dcp_ipcc;
+#define VIRTIO_VENDOR_ID_DCP_HFI		1

Squatting the ID?

Quoted 11 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.h +0 -0
  struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
 
drivers/gpu/drm/msm/msm_drv.c +0 -0
index 5e8f3ce26b32..5816c64d7ce7 100644
@@ -1093,8 +1093,8 @@ static const struct of_device_id msm_gpu_match[] = {
  	{ },
  };
 
Quoted 7 lines

drivers/gpu/drm/msm/msm_drv.c static const struct of_device_id msm_gpu_match[] = { · line 1097

Quoted 4 lines
drivers/gpu/drm/msm/msm_drv.c +2 -2
-static int add_gpu_components(struct device *dev,
-			      struct component_match **matchptr)
+int msm_add_gpu_components(struct device *dev,
+			   struct component_match **matchptr)

Separate commit.

Quoted 1 line

Comments to be addressed in v3.

Quoted 29 lines
Quoted 29 lines

{
struct device_node *np;

drivers/gpu/drm/msm/msm_drv.c +1 -1
@@ -1153,7 +1153,7 @@ int msm_drv_probe(struct device *master_dev,
  	}
 
  	if (!msm_separate_gpu_kms_components()) {
-		ret = add_gpu_components(master_dev, &match);
+		ret = msm_add_gpu_components(master_dev, &match);
  		if (ret)
  			return ret;
  	}
drivers/gpu/drm/msm/msm_drv.h +2 -0
index 2840e8a5052d..aa5dfd82ac6f 100644
@@ -523,6 +523,8 @@ static inline unsigned long timeout_to_jiffies(const ktime_t *timeout)
  /* Driver helpers */
 
  extern const struct component_master_ops msm_drm_ops;
+int msm_add_gpu_components(struct device *dev,
+			   struct component_match **matchptr);
 
  int msm_kms_pm_prepare(struct device *dev);
  void msm_kms_pm_complete(struct device *dev);
Signature · 2 lines
-- 
2.43.0
#57 lore raw

Re: [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver

In reply to Jayant Shekhar
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: - [High] drm/msm/dcp: workqueue/kthread leak and UAF from msm_kms_init() in dcp_virtio_probe() - [High]…

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] drm/msm/dcp: workqueue/kthread leak and UAF from msm_kms_init() in dcp_virtio_probe()
- [High] drm/msm/dcp: missing virtio_driver freeze() and restore() callbacks break resume
- [High] drm/msm/dcp: component_mutex self-deadlock on bridge cleanup during unbind
- [High] drm/msm/dcp: missing GPU split and match checks in dcp_component_register()
- [High] drm/msm/dcp: teardown race in dcp_virtio_hfi_cleanup() allows work re-queue
- [High] drm/msm/dcp: unserialized concurrent virtqueue operations on resp_vq
- [Medium] drm/msm/dcp: missing HFI stop and device reset in dcp_virtio_shutdown()
- [Medium] drm/msm/dcp: unbalanced pm_runtime_disable() on dcp_subsystem_dev
- [Medium] drm/msm/dcp: missing 64-bit DMA mask setup in dcp_subsystem_dev_init()
- [Medium] drm/msm/dcp: OF node reference leak in dcp_dsi_bridge_init() error path --

commit c14944800f78cc1aa467fd751736b4f2706040b6
Author: Jayant Shekhar <[email protected]>

drm/msm/dcp: Add DCP VirtIO driver

This commit adds the DCP VirtIO function driver to set up the DCP DMA and IOMMU identity, initialize the VirtIO and HFI transport, register the MSM DRM component master, and manage suspend, resume, remove, and shutdown.

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +0 -0
new file mode 100644
index 0000000000000..8c38de883bf68

[ ... ]

Quoted 31 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +30 -0
@@ -0,0 +1,191 @@
+int dcp_subsystem_dev_init(struct dcp_drv *drv)
+{
+	struct virtio_device *vdev = drv->vdev;
+	struct device_node *dcp_subsystem_node;
+	struct platform_device *pdev;
+	struct device *dev;
+	char busid[32];
+	bool iommu_mapped;
+	int ret;
+
+	dcp_subsystem_node = of_node_get(vdev->dev.of_node);
+	if (!dcp_subsystem_node) {
+		dev_err(&vdev->dev, "no DCP subsystem DT node\n");
+		return -ENODEV;
+	}
+
+	snprintf(busid, sizeof(busid), "dcp-subsystem.%s", dev_name(vdev->dev.parent));
+
+	/*
+	 * DCP is discovered through VirtIO, but display/HFI buffers must be
+	 * mapped through the dcp-subsystem node's DMA/IOMMU identity. Create a
+	 * platform device for that DT node so DMA API and msm_kms_init_vm() use
+	 * the display function's IOMMU domain.
+	 */
+	pdev = of_platform_device_create(dcp_subsystem_node, busid, vdev->dev.parent);
+	if (!pdev) {
+		dev_err(&vdev->dev, "dcp_subsystem_dev creation failed\n");
+		of_node_put(dcp_subsystem_node);
+		return -ENODEV;
+	}

drivers/gpu/drm/msm/dcp/dcp_drv.c line 40

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +10 -0
+	dev = &pdev->dev;
+
+	dev->dma_parms = &drv->dcp_subsystem_dma_parms;
+	dma_set_max_seg_size(dev, UINT_MAX);
+
+	ret = of_dma_configure_id(dev, dcp_subsystem_node, true, NULL);
+	if (ret) {
+		dev_err(dev, "of_dma_configure_id failed: %d\n", ret);
+		goto err_destroy;
+	}

[Severity: Medium]
Should dcp_subsystem_dev_init() also call dma_set_mask_and_coherent(dev, ~0) after of_dma_configure_id()?

of_platform_device_create() initializes pdev->dev.coherent_dma_mask to DMA_BIT_MASK(32) and sets pdev->dev.dma_mask to point to it, and of_dma_configure_id() only bitwise-ANDs those masks without widening them.

Other MSM KMS master devices go through msm_drv_probe() in drivers/gpu/drm/msm/msm_drv.c:

    ret = dma_set_mask_and_coherent(master_dev, ~0);
    if (ret)
        return ret;

Without widening the mask on dcp_subsystem_dev->dev (which becomes ddev->dev and drm_dev_dma_dev(ddev)), DMA operations such as dma_map_sgtable() in sync_for_device() in drivers/gpu/drm/msm/msm_gem.c or PRIME imports are restricted to 32-bit addressing, causing SWIOTLB bounce buffering or mapping failures for 2 MiB THP GEM folios above 4 GiB when passthrough DMA is used, and limiting default-domain IOVA allocations to 32 bits under iommu-dma.

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_drv.c line 102

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +10 -0
+void dcp_subsystem_dev_deinit(struct dcp_drv *drv)
+{
+	if (!drv->dcp_subsystem_dev)
+		return;
+
+	pm_runtime_disable(&drv->dcp_subsystem_dev->dev);
+	of_platform_device_destroy(&drv->dcp_subsystem_dev->dev, NULL);
+	of_node_put(drv->dcp_subsystem_node);
+	drv->dcp_subsystem_dev = NULL;
+}

[Severity: Medium]
Does this result in an unbalanced pm_runtime_disable() on drv->dcp_subsystem_dev->dev?

In dcp_subsystem_dev_init(), pm_runtime_enable(dev) is called once on &drv->dcp_subsystem_dev->dev. Later, dcp_virtio_probe() sets dcp_kms->pm_dev = &drv->dcp_subsystem_dev->dev and dcp_kms->rpm_enabled = true without a matching pm_runtime_enable() call.

When the DRM component master is torn down via dcp_component_unregister() -> component_master_del() -> msm_drm_unbind() -> msm_drm_uninit() -> msm_drm_kms_uninit() -> dcp_dpu_kms_destroy() in drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c:

    if (dcp_kms->rpm_enabled) {
        pm_runtime_disable(dcp_kms->pm_dev);
        dcp_kms->rpm_enabled = false;
    }

dcp_dpu_kms_destroy() disables runtime PM on dcp_kms->pm_dev, and then dcp_subsystem_dev_deinit() calls pm_runtime_disable() a second time on the same device.

In addition, if a subordinate component unbinds (or if msm_drm_init() fails after component_bind_all() during a deferred bind), dcp_dpu_kms_destroy() disables runtime PM on dcp_subsystem_dev->dev and clears dcp_kms->rpm_enabled, causing any subsequent bind attempt to fail in dcp_dpu_kms_hw_init() -> pm_runtime_resume_and_get() with -EACCES.

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_drv.c line 137

Quoted 12 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +12 -0
+int dcp_component_register(struct dcp_drv *drv)
+{
+	struct device *dev = &drv->dcp_subsystem_dev->dev;
+	struct component_match *match = NULL;
+	int ret;
+
+	ret = dcp_dpu_kms_init_dsi_bridges(drv->kms, &match);
+	if (ret) {
+		dev_err(dev, "DSI host initialization failed: %d\n", ret);
+		dcp_dpu_kms_cleanup_bridges(drv->kms);
+		return ret;
+	}

[Severity: Medium]
Can dcp_dpu_kms_init_dsi_bridges() -> dcp_dsi_bridge_init() leak the dcp_np device_node reference on error?

In drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c, dcp_dsi_bridge_init() takes a reference with dsi_bridge->dcp_np = of_node_get(dcp_np). If a subsequent dma_alloc_coherent(), device_add(), or mipi_dsi_host_register() call fails, it jumps to err_unwind_dma:

err_unwind_dma:

    if (dsi_bridge->dcs_rx_buf) {
        dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
                  dsi_bridge->dcs_rx_buf,
                  dsi_bridge->dcs_rx_iova);
    }
    if (dsi_bridge->dcs_tx_buf) {
        dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
                  dsi_bridge->dcs_tx_buf,
                  dsi_bridge->dcs_tx_iova);
    }
    cancel_work_sync(&dsi_bridge->power_control_work);
    mutex_destroy(&dsi_bridge->session_lock);
    mutex_destroy(&dsi_bridge->dcs_lock);
    return ERR_PTR(ret);

Because dcp_dsi_bridge_init() returns ERR_PTR(ret) before the bridge is stored in dcp_kms->dsi_bridge[dsi_idx], dcp_dpu_kms_cleanup_bridges() will not clean it up, and err_unwind_dma never calls of_node_put(dsi_bridge->dcp_np).

Quoted 4 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +4 -0
+
+	/* A GPU is optional for the DCP display-only DRM device. */
+	msm_add_gpu_components(dev, &match);
+

drivers/gpu/drm/msm/dcp/dcp_drv.c line 151

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +10 -0
+	ret = component_master_add_with_match(dev, &msm_drm_ops, match);
+	if (ret) {
+		dev_err(dev, "component_master_add_with_match failed: %d\n",
+			ret);
+		dcp_dpu_kms_cleanup_bridges(drv->kms);
+		return ret;
+	}
+
+	return 0;
+}

[Severity: High]
What happens in dcp_component_register() when msm_separate_gpu_kms_components() is true, or when match is NULL or an error pointer?

First, msm_drv_probe() in drivers/gpu/drm/msm/msm_drv.c guards msm_add_gpu_components() with:

    if (!msm_separate_gpu_kms_components()) {
        ret = msm_add_gpu_components(master_dev, &match);
        if (ret)
            return ret;
    }

When msm_separate_gpu_kms_components() returns true (for example, when separate_gpu_kms=1 is set or in multi-GPU configurations), adreno_probe() in drivers/gpu/drm/msm/adreno/adreno_device.c probes the GPU as a standalone DRM device via msm_gpu_probe() instead of calling component_add(). Calling msm_add_gpu_components() unconditionally here adds a GPU component to match that will never be registered, preventing the DCP DRM master from ever binding.

Second, drm_of_component_match_add() -> __component_match_add() sets *matchptr = ERR_PTR(-ENOMEM) on allocation failure while dcp_dpu_kms_init_dsi_bridges() and msm_add_gpu_components() still return 0, and match remains NULL if neither DSI bridges nor GPU components are matched. Passing a NULL or ERR_PTR(match) to component_master_add_with_match() in drivers/base/component.c immediately dereferences match->num:

    ret = component_match_realloc(match, match->num);

drivers/gpu/drm/msm/dcp/dcp_drv.c line 159

Quoted 8 lines
drivers/gpu/drm/msm/dcp/dcp_drv.c new file +8 -0
+
+void dcp_component_unregister(struct dcp_drv *drv)
+{
+	struct device *dev = &drv->dcp_subsystem_dev->dev;
+
+	component_master_del(dev, &msm_drm_ops);
+	dcp_dpu_kms_cleanup_bridges(drv->kms);
+}

[Severity: High]
Won't component_master_del() self-deadlock on component_mutex when a MIPI DSI device is attached to the DCP DSI host?

During probe, dcp_dpu_kms_init_dsi_bridges() registers the MIPI DSI host, and when a DSI panel attaches, dcp_dsi_host_attach() in drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c calls component_add() and sets dsi_bridge->component_added = true.

On removal (or if msm_drm_init() fails after component_bind_all() and unwinds through msm_drm_uninit()), component_master_del() acquires component_mutex in drivers/base/component.c and runs the following call chain while holding component_mutex:

    component_master_del() [locks component_mutex]
      -> take_down_aggregate_device()
        -> msm_drm_unbind()
          -> msm_drm_uninit()
            -> msm_drm_kms_uninit()
              -> dcp_dpu_kms_destroy()
                -> dcp_dpu_kms_cleanup_bridges()
                  -> dcp_dsi_bridge_cleanup()
                    -> mipi_dsi_host_unregister()
                      -> mipi_dsi_remove_device_fn()
                        -> mipi_dsi_detach()
                          -> dcp_dsi_host_detach()
                            -> component_del() [locks component_mutex]

Because component_del() attempts to acquire component_mutex on the same thread, this deadlocks deterministically. Additionally, msm_drm_kms_uninit() runs before component_unbind_all() in msm_drm_uninit(), so dcp_dsi_bridge_cleanup() calls device_del(&dsi_bridge->dsi_host_dev) and put_device(&dsi_bridge->dsi_host_dev) while the component is still bound.

[ ... ]

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +0 -0
new file mode 100644
index 0000000000000..15d5caf02ff58

[ ... ]

Quoted 3 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +2 -0
@@ -0,0 +1,258 @@
+static int dcp_virtio_probe(struct virtio_device *vdev)
+{

[ ... ]

Quoted 5 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +5 -0
+	ret = dcp_hfi_init(drv, vdev, vqs[DCP_VQ_CMD], vqs[DCP_VQ_RESP],
+			   vdev->dev.parent);
+	if (ret) {
+		dev_err(&vdev->dev, "HFI init failed: %d\n", ret);
+		goto err_del_vqs;

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 92

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +10 -0
+	}
+
+	dcp_ipcc_set_resp_vq(drv->ipcc, vqs[DCP_VQ_RESP]);
+
+	/*
+	 * Keep the device unready until HFI state and virtqueues are initialized.
+	 * KMS registration below depends on synchronous firmware initialization.
+	 */
+	virtio_device_ready(vdev);
+	virtqueue_kick(drv->hfi->resp_vq);

[Severity: High]
Is access to drv->hfi->resp_vq properly serialized across dcp_virtio_probe(), dcp_ipcc_handle_rx(), and dcp_hfi_resp_callback()?

Here in dcp_virtio_probe(), dcp_ipcc_set_resp_vq() publishes resp_vq to the IPCC interrupt/RX handler and virtio_device_ready() enables virtqueue callbacks before virtqueue_kick(drv->hfi->resp_vq) is called without holding any lock.

In drivers/gpu/drm/msm/dcp/dcp_hfi_core.c, dcp_hfi_resp_callback() and dcp_hfi_resp_repost_work() serialize resp_vq operations under hfi->resp_drain_lock, whereas dcp_ipcc_handle_rx() in drivers/gpu/drm/msm/dcp/dcp_ipcc.c calls virtqueue_kick_prepare(ipcc->resp_vq) under ipcc->state_lock without acquiring hfi->resp_drain_lock:

    complete(&ipcc->power_notify_done);
    if (ipcc->resp_vq && virtqueue_kick_prepare(ipcc->resp_vq))
        virtqueue_notify(ipcc->resp_vq);

Can concurrent calls to virtqueue_kick_prepare() and virtqueue_add_inbuf_premapped() / virtqueue_get_buf() / virtqueue_enable_cb() on resp_vq corrupt vq->num_added in virtio_ring.c or trigger START_USE(vq) assertions?

[ ... ]

Quoted 27 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +27 -0
+	dcp_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dcp_kms), GFP_KERNEL);
+	if (!dcp_kms) {
+		ret = -ENOMEM;
+		goto err_hfi_deinit;
+	}
+
+	dcp_kms->drv       = drv;
+	dcp_kms->pm_dev    = &drv->dcp_subsystem_dev->dev;
+	dcp_kms->hfi       = drv->hfi;
+	dcp_kms->caps      = &drv->caps;
+	dcp_kms->dpus_np   = drv->dcp_subsystem_node;
+	dcp_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
+	drv->kms        = dcp_kms;
+
+	ret = msm_kms_init(&dcp_kms->base, &dcp_dpu_kms_funcs);
+	if (ret) {
+		dev_err(&vdev->dev, "msm_kms_init failed: %d\n", ret);
+		goto err_hfi_deinit;
+	}
+
+	priv = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		ret = -ENOMEM;
+		goto err_hfi_deinit;
+	}
+
+	priv->kms      = &dcp_kms->base;

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 154

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +10 -0
+	priv->kms_init = dcp_kms_init;
+	dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv);
+	dcp_kms->rpm_enabled = true;
+
+	/* Component binding now sees initialized HFI, capabilities, and IOMMU state. */
+	ret = dcp_component_register(drv);
+	if (ret) {
+		dev_err_probe(&vdev->dev, ret, "component register failed\n");
+		goto err_hfi_deinit;
+	}

[Severity: High]
Does jumping to err_hfi_deinit after msm_kms_init() (or removing the driver in dcp_virtio_remove() when the component master never bound) leak kms->wq and the pending_timers kthreads, and cause a use-after-free on dcp_kms?

msm_kms_init() in drivers/gpu/drm/msm/msm_kms.h allocates the ordered "msm" workqueue (kms->wq) and initializes kms->pending_timers[] via msm_atomic_init_pending_timer(), which spawns real-time kthread_worker threads holding pointers into dcp_kms->base.

However, msm_kms_destroy(&dcp_kms->base) is only called from dcp_dpu_kms_destroy() when a bound DRM aggregate device is torn down via msm_drm_uninit(). If allocating priv or calling dcp_component_register(drv) fails in dcp_virtio_probe(), or if dcp_virtio_remove() runs while component binding is still pending or deferred, dcp_subsystem_dev_deinit() destroys drv->dcp_subsystem_dev via of_platform_device_destroy() and frees the devm-allocated dcp_kms while kms->wq and the atomic-worker kthreads/hrtimers remain active.

Quoted 23 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +23 -0
+
+	dcp_panic_notifier_register(drv);
+
+	dev_dbg(&vdev->dev, "DCP VirtIO probe complete\n");
+	return 0;
+
+err_hfi_deinit:
+	dcp_virtio_hfi_cleanup(drv);
+	goto err_fn_dev;
+err_del_vqs:
+	virtio_reset_device(vdev);
+	vdev->config->del_vqs(vdev);
+err_fn_dev:
+	dcp_subsystem_dev_deinit(drv);
+	return ret;
+}
+
+static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv)
+{
+	struct virtio_device *vdev = drv->vdev;
+
+	if (!drv->hfi)
+		return;

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 187

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +10 -0
+
+	dcp_ipcc_set_resp_vq(drv->ipcc, NULL);
+
+	dcp_hfi_stop(drv->hfi);
+	virtio_reset_device(vdev);
+	virtio_synchronize_cbs(vdev);
+	dcp_hfi_quiesce_queues(drv->hfi);
+	vdev->config->del_vqs(vdev);
+	dcp_hfi_deinit(drv->hfi);
+}

[Severity: High]
Can an in-flight dcp_hfi_resp_callback() re-queue resource_vote_work or resp_repost_work after dcp_hfi_stop() has cancelled them in dcp_virtio_hfi_cleanup()?

In dcp_virtio_hfi_cleanup(), dcp_hfi_stop() sets hfi->shutdown_in_progress = 1 and calls cancel_work_sync(&hfi->resource_vote_work) and cancel_delayed_work_sync(&hfi->resp_repost_work) before virtio_reset_device() and virtio_synchronize_cbs() have finished draining running virtqueue callbacks.

If dcp_hfi_resp_callback() in drivers/gpu/drm/msm/dcp/dcp_hfi_core.c already passed its initial atomic_read(&hfi->shutdown_in_progress) check on another CPU, it drops hfi->resp_drain_lock while parsing packets and can schedule hfi->resource_vote_work or hfi->resp_repost_work after dcp_hfi_stop() returned. Because neither dcp_hfi_quiesce_queues() nor dcp_hfi_deinit() cancels those work items after virtio_synchronize_cbs(), can those work items remain pending in freed struct dcp_hfi memory?

[ ... ]

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 212

Quoted 9 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +9 -0
+/* KMS shutdown must complete while the firmware transport is available. */
+static void dcp_virtio_shutdown(struct virtio_device *vdev)
+{
+	struct dcp_drv *drv = vdev->priv;
+
+	pm_runtime_get_sync(&drv->dcp_subsystem_dev->dev);
+	msm_kms_shutdown(drv->dcp_subsystem_dev);
+	pm_runtime_put_noidle(&drv->dcp_subsystem_dev->dev);
+}

[Severity: Medium]
Should dcp_virtio_shutdown() also stop HFI activity, clear ipcc->resp_vq, and reset/quiesce the VirtIO device after msm_kms_shutdown()?

In drivers/virtio/virtio.c, virtio_dev_shutdown() skips the default virtio_device_shutdown(dev) path whenever a driver provides its own .shutdown callback:

    /* If the driver has its own shutdown method, use that. */
    if (drv->shutdown) {
        drv->shutdown(dev);
        return;
    }

    virtio_device_shutdown(dev);

Because dcp_virtio_shutdown() only runs msm_kms_shutdown(), it returns without clearing ipcc->resp_vq via dcp_ipcc_set_resp_vq(drv->ipcc, NULL), without calling dcp_hfi_stop(drv->hfi) to cancel resource_vote_work and resp_repost_work, and without calling virtio_device_shutdown(vdev) (or breaking and resetting the virtqueues), leaving the VirtIO DMA rings and pre-posted HFI response buffers active while downstream SMMU shutdown disables translation.

Quoted 15 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +15 -0
+
+static const struct virtio_device_id dcp_virtio_id_table[] = {
+	{ VIRTIO_ID_QPSFI, VIRTIO_VENDOR_ID_DCP_HFI },
+	{ 0 },
+};
+MODULE_DEVICE_TABLE(virtio, dcp_virtio_id_table);
+
+static const struct dev_pm_ops dcp_virtio_pm_ops = {
+	.prepare = dcp_pm_prepare,
+	.complete = dcp_pm_complete,
+};
+
+static struct virtio_driver dcp_virtio_driver = {
+	.driver = {
+		.name  = "qcom-dcp-hfi",

drivers/gpu/drm/msm/dcp/dcp_virtio.c line 237

Quoted 10 lines
drivers/gpu/drm/msm/dcp/dcp_virtio.c new file +10 -0
+		.owner = THIS_MODULE,
+		.pm    = &dcp_virtio_pm_ops,
+	},
+	.id_table           = dcp_virtio_id_table,
+	.feature_table      = dcp_virtio_features,
+	.feature_table_size = ARRAY_SIZE(dcp_virtio_features),
+	.probe              = dcp_virtio_probe,
+	.remove             = dcp_virtio_remove,
+	.shutdown           = dcp_virtio_shutdown,
+};

[Severity: High]
How are virtqueues and HFI state preserved across system suspend and resume without .freeze and .restore callbacks on dcp_virtio_driver?

In drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c, dcp_virtio_transport_suspend() calls virtio_device_freeze(&transport->vdev) and dcp_virtio_transport_resume() calls virtio_device_restore(&transport->vdev).

In drivers/virtio/virtio.c, virtio_device_freeze() invokes drv->freeze(dev) to quiesce driver activity and delete virtqueues before sleep, and virtio_device_restore_priv() unconditionally calls virtio_reset_device(dev) (which calls dcp_virtio_reset() and writes 0 to VIRTIO_MMIO_STATUS, resetting hardware queue state) before invoking drv->restore(dev) to recreate the virtqueues.

With .freeze and .restore left NULL on dcp_virtio_driver, HFI work items and virtqueues are not torn down during suspend, and on resume the VirtIO MMIO device is reset without recreating cmd_vq and resp_vq or reposting HFI response buffers, leaving the virtqueues unusable when dcp_pm_complete() resumes KMS.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=20
#58 lore raw

PATCH v2 21/23 drm/msm: Support KMS backends without a display IRQ

In reply to Jayant Shekhar

Some MSM KMS backends receive display events through firmware or another transport and therefore have no host-visible display interrupt. Rejecting such a backend during common KMS initialization prevents it from using the MSM DRM framework.

Allow KMS backends to use IRQ_NOTCONNECTED. DCP receives VSYNC and frame-scan completion as asynchronous HFI events over VirtIO; its IPCC interrupt is reserved for power-notify acknowledgments. Existing IRQ-backed KMS implementations retain their current request and teardown flow.

Assisted-by LLM
---
 drivers/gpu/drm/msm/msm_kms.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/gpu/drm/msm/msm_kms.c +1 -1
index e5d0ea629448..1870a7008ead 100644
@@ -72,7 +72,7 @@ static int msm_irq_install(struct drm_device *dev, unsigned int irq)
 	int ret;
 
 	if (irq == IRQ_NOTCONNECTED)
-		return -ENOTCONN;
+		return 0;
 
 	msm_irq_preinstall(dev);
 
Signature · 1 line
2.43.0
#59 lore raw

Re: [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ

Sashiko has reviewed this patch and found no issues.
Quoted 5 lines

Some MSM KMS backends receive display events through firmware or another transport and therefore have no host-visible display interrupt. Rejecting such a backend during common KMS initialization prevents it from using the MSM DRM framework.

Quoted 8 lines

Allow KMS backends to use IRQ_NOTCONNECTED. DCP receives VSYNC and frame-scan completion as asynchronous HFI events over VirtIO; its IPCC interrupt is reserved for power-notify acknowledgments. Existing IRQ-backed KMS implementations retain their current request and teardown flow.

Assisted-by LLM

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=21
Reviewed-by on v2 21/23
#60 lore raw

Re: [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ

In reply to Jayant Shekhar
Quoted 1 line · Jayant Shekhar

Some MSM KMS backends receive display events through firmware or

Which ones?

Quoted 9 lines

another transport and therefore have no host-visible display interrupt. Rejecting such a backend during common KMS initialization prevents it from using the MSM DRM framework.

Allow KMS backends to use IRQ_NOTCONNECTED. DCP receives VSYNC and frame-scan completion as asynchronous HFI events over VirtIO; its IPCC interrupt is reserved for power-notify acknowledgments. Existing IRQ-backed KMS implementations retain their current request and teardown flow.

With the commit message fixed:

Quoted 6 lines
Assisted-by LLM
---
 drivers/gpu/drm/msm/msm_kms.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Signature · 2 lines
With best wishes
Dmitry
#61 lore raw

PATCH v2 22/23 drm/msm: Register the DCP backend

In reply to Jayant Shekhar

The DCP platform driver provides an MSM KMS backend. Register it from the MSM DRM driver init and exit paths so the VirtIO driver can use the backend when the DCP function is discovered.

Add DCP registration hooks with no-op stubs when CONFIG_DRM_MSM_DCP is disabled. This keeps backend registration consistent with the other MSM display backends while allowing DCP support to remain optional.

Assisted-by LLM
---
 drivers/gpu/drm/msm/msm_drv.c | 2 ++
 drivers/gpu/drm/msm/msm_drv.h | 8 ++++++++
 2 files changed, 10 insertions(+)
drivers/gpu/drm/msm/msm_drv.c +2 -0
index 5816c64d7ce7..d413c361c442 100644
@@ -1208,6 +1208,7 @@ static int __init msm_drm_register(void)
 	DBG("init");
 	msm_mdp_register();
 	msm_dpu_register();
+	msm_dcp_register();
 	msm_dsi_register();
 	msm_hdmi_register();
 	msm_dp_register();
@@ -1229,6 +1230,7 @@ static void __exit msm_drm_unregister(void)
 	msm_dsi_unregister();
 	msm_mdp_unregister();
 	msm_dpu_unregister();
+	msm_dcp_unregister();
 }
 
 module_init(msm_drm_register);
drivers/gpu/drm/msm/msm_drv.h +8 -0
index aa5dfd82ac6f..befaa9f2cb5c 100644
@@ -417,6 +417,14 @@ static inline void msm_dpu_register(void) {}
 static inline void msm_dpu_unregister(void) {}
 #endif
 
+#ifdef CONFIG_DRM_MSM_DCP
+void msm_dcp_register(void);
+void msm_dcp_unregister(void);
+#else
+static inline void msm_dcp_register(void) {}
+static inline void msm_dcp_unregister(void) {}
+#endif
+
 #ifdef CONFIG_DRM_MSM_MDSS
 void msm_mdss_register(void);
 void msm_mdss_unregister(void);
Signature · 1 line
2.43.0
#62 lore raw

Re: [PATCH v2 22/23] drm/msm: Register the DCP backend

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] drm/msm: misordered msm_dcp_register() and msm_dcp_unregister() calls - [High] drm/msm: missing…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] drm/msm: misordered msm_dcp_register() and msm_dcp_unregister() calls
- [High] drm/msm: missing msm_separate_gpu_kms_components() check and DCP count
- [Low] drm/msm: link failure in msm_dcp_register() when DRM_MSM=y and VIRTIO=m --

commit 8c20c0a0e35ec53b4a670a45026a398c4476bb45
Author: Jayant Shekhar <[email protected]>

drm/msm: Register the DCP backend

This commit registers and unregisters the DCP platform driver from the MSM DRM module init and exit paths, and provides no-op stubs in msm_drv.h when CONFIG_DRM_MSM_DCP is disabled.

Quoted 13 lines
drivers/gpu/drm/msm/msm_drv.c +1 -0
index 5816c64d7ce7d..d413c361c4426 100644
@@ -1208,6 +1208,7 @@ static int __init msm_drm_register(void)
 	DBG("init");
 	msm_mdp_register();
 	msm_dpu_register();
+	msm_dcp_register();
 	msm_dsi_register();
 	msm_hdmi_register();
 	msm_dp_register();

drivers/gpu/drm/msm/msm_drv.c static void __exit msm_drm_unregister(void) · line 1233

Quoted 4 lines
drivers/gpu/drm/msm/msm_drv.c +1 -0
@@ -1229,6 +1230,7 @@ static void __exit msm_drm_unregister(void)
 	msm_dsi_unregister();
 	msm_mdp_unregister();
 	msm_dpu_unregister();
+	msm_dcp_unregister();

[Severity: High]
Are msm_dcp_register() and msm_dcp_unregister() ordered in the right place relative to adreno_register() and adreno_unregister()?

In msm_drm_register() and msm_drm_unregister(), MDP5 (msm_mdp_register()) and DPU (msm_dpu_register()) are registered first and unregistered last because their platform devices are children of the top-level msm_mdss device. They are populated when msm_mdss_register() runs at the end of msm_drm_register() and depopulated via mdss_remove()->of_platform_depopulate() when msm_mdss_unregister() runs at the start of msm_drm_unregister(), which tears down the component master before adreno_unregister() is called.

By contrast, DCP (qcom,dcp-virtio) is a standalone top-level platform device rather than a child of msm_mdss:

drivers/gpu/drm/msm/dcp/dcp_drv.c:dcp_component_register() {
    ...
	/* A GPU is optional for the DCP display-only DRM device. */
	msm_add_gpu_components(dev, &match);

	ret = component_master_add_with_match(dev, &msm_drm_ops, match);
    ...
}
drivers/gpu/drm/msm/dcp/dcp_drv.c:dcp_component_unregister() {
	struct msm_drm_private *drv = dev_get_drvdata(dev);

	component_master_del(dev, &msm_drm_ops);
	dcp_dpu_kms_cleanup_bridges(drv->kms);
}

Calling msm_dcp_register() before adreno_register() registers the DCP component master before the Adreno component driver is registered. On module unload, calling msm_dcp_unregister() at the end of msm_drm_unregister() runs adreno_unregister() while the DCP component master (dcp_subsystem_dev) is still active and bound, rather than first deleting the component master via msm_dcp_unregister()->dcp_virtio_unregister()->dcp_virtio_remove()-> dcp_component_unregister()->component_master_del().

Should msm_dcp_register() be called at the end of msm_drm_register() and msm_dcp_unregister() at the start of msm_drm_unregister(), alongside msm_mdp4_register()/msm_mdss_register() and msm_mdss_unregister()/msm_mdp4_unregister()?

drivers/gpu/drm/msm/msm_drv.h static inline void msm_dpu_register(void) {} · line 422

Quoted 11 lines
drivers/gpu/drm/msm/msm_drv.h +3 -0
index aa5dfd82ac6f7..befaa9f2cb5c5 100644
@@ -417,6 +417,14 @@ static inline void msm_dpu_register(void) {}
 static inline void msm_dpu_unregister(void) {}
 #endif
 
+#ifdef CONFIG_DRM_MSM_DCP
+void msm_dcp_register(void);
+void msm_dcp_unregister(void);

[Severity: Low]
Will enabling CONFIG_DRM_MSM_DCP cause a vmlinux link failure when CONFIG_DRM_MSM=y and CONFIG_VIRTIO=m?

In drivers/gpu/drm/msm/Kconfig, DRM_MSM_DCP is a bool symbol that depends on tristate DRM_MSM and tristate VIRTIO:

config DRM_MSM_DCP
bool "Enable DCP firmware-delegated display support in MSM DRM driver" depends on DRM_MSM
depends on VIRTIO

When CONFIG_DRM_MSM=y and CONFIG_VIRTIO=m, CONFIG_DRM_MSM_DCP can be enabled as y, building msm_drv.o and the DCP VirtIO backend objects into vmlinux while virtio.ko and virtio_ring.ko are built as loadable modules, leaving msm_drm_register()->msm_dcp_register()->dcp_virtio_register() with unresolved references to VirtIO symbols such as register_virtio_driver(), register_virtio_device(), and vring_create_virtqueue().

drivers/gpu/drm/msm/msm_drv.h static inline void msm_dpu_register(void) {} · line 426

Quoted 4 lines
drivers/gpu/drm/msm/msm_drv.h +4 -0
+#else
+static inline void msm_dcp_register(void) {}
+static inline void msm_dcp_unregister(void) {}
+#endif

[Severity: High]
How does DCP registration interact with msm_separate_gpu_kms_components() and msm_separate_gpu_kms_auto()?

When msm_dcp_register()->dcp_virtio_register()->dcp_virtio_probe()-> dcp_component_register() runs, it adds the GPU to the component match list unconditionally:

drivers/gpu/drm/msm/dcp/dcp_drv.c:dcp_component_register() {
    ...
	/* A GPU is optional for the DCP display-only DRM device. */
	msm_add_gpu_components(dev, &match);

	ret = component_master_add_with_match(dev, &msm_drm_ops, match);
    ...
}

Unlike msm_drv_probe() in drivers/gpu/drm/msm/msm_drv.c, this does not check !msm_separate_gpu_kms_components():

drivers/gpu/drm/msm/msm_drv.c:msm_drv_probe() {
    ...
	if (!msm_separate_gpu_kms_components()) {
		ret = msm_add_gpu_components(master_dev, &match);
		if (ret)
			return ret;
	}
    ...
}

When msm_separate_gpu_kms_components() is true and an Adreno GPU is present in DT, adreno_probe() calls msm_gpu_probe() instead of component_add(). Because dcp_component_register() still adds the GPU node to its match list, component_master_add_with_match() will wait indefinitely for a GPU component that is never registered, preventing the DCP DRM device from ever binding.

In addition, unlike CONFIG_DRM_MSM_MDP4 and CONFIG_DRM_MSM_MDSS in drivers/gpu/drm/msm/msm_drv.h, no msm_count_dcp() helper is provided or included in msm_separate_gpu_kms_auto():

drivers/gpu/drm/msm/msm_drv.c:msm_separate_gpu_kms_auto() {
	int gpus = msm_count_gpus();
	int display_subsystems = msm_count_mdss() + msm_count_mdp4();

	if (gpus <= 0 || display_subsystems <= 0)
		return false;

	/* Single card if there is exactly one GPU and one display subsystem */
	return gpus > 1 || display_subsystems > 1;
}

Without counting DCP nodes in display_subsystems, won't msm_separate_gpu_kms_auto() miscalculate the number of display subsystems on platforms with DCP?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=22
#63 lore raw

Re: [PATCH v2 22/23] drm/msm: Register the DCP backend

In reply to Jayant Shekhar
Quoted 7 lines · Jayant Shekhar

The DCP platform driver provides an MSM KMS backend. Register it from the MSM DRM driver init and exit paths so the VirtIO driver can use the backend when the DCP function is discovered.

Add DCP registration hooks with no-op stubs when CONFIG_DRM_MSM_DCP is disabled. This keeps backend registration consistent with the other MSM display backends while allowing DCP support to remain optional.

There is no need to describe patch contents. Please update the prompts you pass to your AI.

Quoted 33 lines
Assisted-by LLM
---
 drivers/gpu/drm/msm/msm_drv.c | 2 ++
 drivers/gpu/drm/msm/msm_drv.h | 8 ++++++++
 2 files changed, 10 insertions(+)
drivers/gpu/drm/msm/msm_drv.c +2 -0
index 5816c64d7ce7..d413c361c442 100644
@@ -1208,6 +1208,7 @@ static int __init msm_drm_register(void)
 	DBG("init");
 	msm_mdp_register();
 	msm_dpu_register();
+	msm_dcp_register();
 	msm_dsi_register();
 	msm_hdmi_register();
 	msm_dp_register();
@@ -1229,6 +1230,7 @@ static void __exit msm_drm_unregister(void)
 	msm_dsi_unregister();
 	msm_mdp_unregister();
 	msm_dpu_unregister();
+	msm_dcp_unregister();
 }
 
 module_init(msm_drm_register);
drivers/gpu/drm/msm/msm_drv.h +0 -0
index aa5dfd82ac6f..befaa9f2cb5c 100644

drivers/gpu/drm/msm/msm_drv.h static inline void msm_dpu_register(void) {} · line 422

Quoted 6 lines
drivers/gpu/drm/msm/msm_drv.h +3 -0
@@ -417,6 +417,14 @@ static inline void msm_dpu_register(void) {}
 static inline void msm_dpu_unregister(void) {}
 #endif
 
+#ifdef CONFIG_DRM_MSM_DCP
+void msm_dcp_register(void);
+void msm_dcp_unregister(void);

This means that the earlier patches generate "function without prototype" warnings. Please fix that. The prototypes must come together with the function definition.

Quoted 12 lines
drivers/gpu/drm/msm/msm_drv.h +5 -0
+#else
+static inline void msm_dcp_register(void) {}
+static inline void msm_dcp_unregister(void) {}
+#endif
+
 #ifdef CONFIG_DRM_MSM_MDSS
 void msm_mdss_register(void);
 void msm_mdss_unregister(void);
Signature · 2 lines
-- 
2.43.0
Signature · 2 lines
With best wishes
Dmitry