Skip to main content

drmkit_planes/
multirect.rs

1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Multirect ("smart DMA") plane pairing.
5//!
6//! Port of `src/planes/multirect.hpp` (drm-cxx `8bf20e6`, `4b366b5`).
7//!
8//! Some display controllers drive two rectangles from one hardware source
9//! pipe and publish the second rectangle as a separate, "virtual" DRM plane.
10//! Such a plane is only valid while its parent -- the pipe's first rectangle
11//! -- is in use on the same commit; staged alone, the kernel refuses it. The
12//! downstream SDE display driver marks these planes in the read-only
13//! `capabilities` blob with a `primary_smart_plane_id=<parent>` line. The
14//! SA8155P has seven. Drivers without that key are unaffected by everything
15//! here.
16
17const KEY: &[u8] = b"primary_smart_plane_id=";
18
19/// The parent plane id from a `capabilities` blob's text, or `None` when the
20/// blob does not mark the plane as a multirect rectangle, or the value is
21/// garbage.
22///
23/// Bytes rather than `&str`: the blob is driver-private and nothing promises
24/// it is UTF-8, while the key and the number are ASCII either way.
25#[must_use]
26pub fn parse_multirect_parent(caps_text: &[u8]) -> Option<u32> {
27    let mut from = 0;
28    while let Some(offset) = caps_text[from..]
29        .windows(KEY.len())
30        .position(|window| window == KEY)
31    {
32        let pos = from + offset;
33        // Only a match at the start of a line counts: the blob is key=value
34        // lines.
35        if pos == 0 || caps_text[pos - 1] == b'\n' {
36            let value = &caps_text[pos + KEY.len()..];
37            let digits = value.iter().take_while(|b| b.is_ascii_digit()).count();
38            return std::str::from_utf8(&value[..digits])
39                .ok()
40                .and_then(|text| text.parse::<u32>().ok())
41                .filter(|id| *id != 0);
42        }
43        from = pos + KEY.len();
44    }
45    None
46}
47
48/// Whether a plane whose multirect parent is `parent` may be placed, given
49/// the planes already in use: a plain plane always, a virtual one only once
50/// its parent is in use. `in_use(id)` reports whether a plane id is taken.
51#[must_use]
52pub fn multirect_pairing_ok(parent: Option<u32>, in_use: impl Fn(u32) -> bool) -> bool {
53    parent.is_none_or(in_use)
54}
55
56/// How many planes a CRTC can stack, from its `capabilities` blob, or `None`
57/// when the blob does not say.
58///
59/// The downstream SDE driver publishes `max_blendstages=<n>` and, where a
60/// stage is held back for secure UI, `sec_ui_blendstage=<s>`. A plane's zpos
61/// `z` goes to stage `z + 1` -- stage 0 is the mixer's base -- so ordinary
62/// planes have the stages below whichever of the two comes first. The
63/// SA8155P publishes 11 and 10, so it stacks nine planes, zpos 0 to 8, while
64/// every plane advertises a zpos range of `[0, 10]`; a zpos of 9 or 10 is
65/// refused even on a lone plane (P-46).
66///
67/// As the reference's `parse_blend_limit` reads it: a `sec_ui_blendstage` of
68/// 0 holds nothing back, and `use_baselayer_for_stage=1` makes the base a
69/// stage a plane can use.
70#[must_use]
71pub fn parse_blend_stage_limit(caps_text: &[u8]) -> Option<usize> {
72    let max = key_value(caps_text, b"max_blendstages=").filter(|max| *max != 0)?;
73    let top = key_value(caps_text, b"sec_ui_blendstage=")
74        .filter(|stage| *stage > 0 && *stage < max)
75        .unwrap_or(max);
76    let base_usable = key_value(caps_text, b"use_baselayer_for_stage=").unwrap_or(0) != 0;
77    let limit = if base_usable { top } else { top - 1 };
78    usize::try_from(limit).ok().filter(|limit| *limit > 0)
79}
80
81/// The unsigned 32-bit number after `key` at the start of a line of
82/// `caps_text`. One that does not fit is no number, as for the reference.
83fn key_value(caps_text: &[u8], key: &[u8]) -> Option<u32> {
84    let mut from = 0;
85    while let Some(offset) = caps_text[from..]
86        .windows(key.len())
87        .position(|window| window == key)
88    {
89        let pos = from + offset;
90        if pos == 0 || caps_text[pos - 1] == b'\n' {
91            let value = &caps_text[pos + key.len()..];
92            let digits = value.iter().take_while(|b| b.is_ascii_digit()).count();
93            return std::str::from_utf8(&value[..digits]).ok()?.parse().ok();
94        }
95        from = pos + key.len();
96    }
97    None
98}