diff --git a/.github/workflows/sep-source.yml b/.github/workflows/sep-source.yml new file mode 100644 index 0000000000000..052f282d60061 --- /dev/null +++ b/.github/workflows/sep-source.yml @@ -0,0 +1,69 @@ +name: SEP source checks + +on: + pull_request: + paths: + - 'drivers/soc/apple/**' + - 'include/uapi/linux/apple-sep.h' + - 'tools/aurora-sep/**' + - '.github/workflows/sep-source.yml' + +jobs: + source: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + filter: blob:none + sparse-checkout-cone-mode: false + sparse-checkout: | + drivers/soc/apple/** + include/uapi/linux/apple-sep.h + tools/aurora-sep/** + - name: Check biometric ioctl ABI sizes + run: | + cat > /tmp/sep-abi.c <<'EOF' + #include + _Static_assert(sizeof(struct sep_bio_enrol_start) == 144, "enrol ABI"); + _Static_assert(sizeof(struct sep_bio_enclave_list) == 520, "list ABI"); + _Static_assert(sizeof(struct sep_bio_delete) == 16, "delete ABI"); + int main(void) { return 0; } + EOF + cc -Wall -Werror -Iinclude/uapi /tmp/sep-abi.c -o /tmp/sep-abi + /tmp/sep-abi + - name: Test recovery utility and package sources + run: | + python3 -m unittest tools/aurora-sep/tests/test_sep_bio_identities.py -v + python3 -m unittest tools/aurora-sep/tests/test_orphan_delete.py -v + python3 -m unittest tools/aurora-sep/tests/test_enrol_admission.py -v + python3 -m unittest tools/aurora-sep/tests/test_owner_preparation.py -v + python3 -m unittest discover -s tools/aurora-sep/tests -p test_packaging.py -v + + rust-build: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Install kernel build dependencies + run: | + sudo apt-get update + sudo apt-get install -y clang lld llvm libclang-dev libelf-dev libssl-dev \ + flex bison bc pkg-config make perl + rustup component add rust-src + cargo install bindgen-cli --version 0.71.1 --locked + - name: Compile Apple SEP module for arm64 + env: + LIBCLANG_PATH: /usr/lib/llvm-18/lib + run: | + make LLVM=1 ARCH=arm64 rustavailable + make LLVM=1 ARCH=arm64 defconfig + scripts/kconfig/merge_config.sh -m .config arch/arm64/configs/asahi.config + scripts/config -e KUNIT -e APPLE_SEP_KUNIT_TEST + make LLVM=1 ARCH=arm64 olddefconfig + grep -qx 'CONFIG_APPLE_SEP=m' .config + grep -qx 'CONFIG_APPLE_SEP_KUNIT_TEST=y' .config + make -j2 LLVM=1 ARCH=arm64 modules_prepare + make -j2 LLVM=1 ARCH=arm64 drivers/soc/apple/apple-sep.o diff --git a/Documentation/devicetree/bindings/display/apple,dcp.yaml b/Documentation/devicetree/bindings/display/apple,dcp.yaml index d79ad457f7703..c67a71b7a169b 100644 --- a/Documentation/devicetree/bindings/display/apple,dcp.yaml +++ b/Documentation/devicetree/bindings/display/apple,dcp.yaml @@ -33,7 +33,9 @@ properties: - apple,t8112-dcpext - apple,t6000-dcpext - apple,t6020-dcpext + - apple,t6041-dcpext - const: apple,dcpext + - const: apple,t6041-dcpext - items: - const: apple,t8140-dcpext - const: apple,dcp @@ -213,6 +215,23 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32 description: Display coprocessor index on multi-DCP systems. + apple,typec-hybrid-first: + type: boolean + description: + A hybrid pipeline (a fixed output plus Type-C routes) is planned for a + Type-C port before the other pipelines, while its fixed output has no + sink, instead of being kept back for that output. Set where this + pipeline is the only one that can be the primary of a display wider than + one pipe. + + apple,dcp-endpoint: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + The display endpoint this coprocessor is to its firmware (its display + index), by which two external pipes name each other when a display + wider than one pipe is driven by two of them. 0 is the internal + display. + apple,dptx-phy: $ref: /schemas/types.yaml#/definitions/uint32 description: DisplayPort transmitter PHY selector. @@ -276,6 +295,8 @@ properties: '#size-cells': const: 2 + ranges: true + piodma: type: object properties: @@ -372,10 +393,19 @@ allOf: pattern: '^(dp-xbar|typec[0-9]+)$' - if: not: - properties: - compatible: - contains: - const: apple,t8132-dcp + anyOf: + - properties: + compatible: + contains: + const: apple,t8132-dcp + - properties: + compatible: + contains: + const: apple,t6041-dcpext + status: + const: disabled + required: + - status then: required: - apple,bw-scratch diff --git a/Documentation/devicetree/bindings/dma/apple,sio.yaml b/Documentation/devicetree/bindings/dma/apple,sio.yaml index dbaaa2d3d1a1a..5b6baf0a14024 100644 --- a/Documentation/devicetree/bindings/dma/apple,sio.yaml +++ b/Documentation/devicetree/bindings/dma/apple,sio.yaml @@ -9,7 +9,9 @@ title: Apple SIO Coprocessor description: SIO is a coprocessor on Apple M1 and later chips (and maybe also on earlier chips). Its role is to offload SPI, UART and DisplayPort audio transfers, - being a pretend DMA controller. + being a pretend DMA controller. The SoC-specific compatible selects the + firmware protocol and channel configuration. In particular, apple,t6030-sio + requires protocol-10 boot tables before the coprocessor can be started. maintainers: - Martin PoviĊĦer @@ -19,12 +21,15 @@ allOf: properties: compatible: - items: - - enum: - - apple,t6000-sio - - apple,t6030-sio - - apple,t8103-sio - - const: apple,sio + oneOf: + - items: + - enum: + - apple,t6000-sio + - apple,t6030-sio + - apple,t8103-sio + - const: apple,sio + - description: T6041 firmware requires a generation-specific backend. + const: apple,t6041-sio reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/gpu/apple,agx.yaml b/Documentation/devicetree/bindings/gpu/apple,agx.yaml index c0a32c3badd07..f5b1afd7d7dd3 100644 --- a/Documentation/devicetree/bindings/gpu/apple,agx.yaml +++ b/Documentation/devicetree/bindings/gpu/apple,agx.yaml @@ -72,6 +72,22 @@ properties: - enum: [hw-cal-b, fw-text, l2-pagetables] - enum: [globals, fw-data, fw-text] + apple,clpc: + $ref: /schemas/types.yaml#/definitions/phandle + description: | + Host performance controller receiving GPU submission and idle demand. + GFX supplier power cannot represent GPU work on firmware that keeps + its coprocessor domain powered while the cores are idle. + + apple,idle-off-delay-ms: + minimum: 2 + maximum: 1000 + default: 2 + description: + Continuous idle time before GPU power-off, in milliseconds. The G17 + backend extends the firmware's native delay with its host idle-hold + control; an absent property preserves the native policy. + apple,pmp: $ref: /schemas/types.yaml#/definitions/phandle description: PMP firmware provider owning the GPU power vote and telemetry @@ -83,9 +99,9 @@ properties: minItems: 3 maxItems: 3 description: - macOS version (major, minor, patch) whose boot firmware loaded the GPU - firmware, which identifies the firmware build. Filled in by the boot - loader. + Reported macOS version (major, minor, patch) whose boot firmware loaded + the GPU firmware. Filled in by the boot loader. This is a selection + hint; the T6040 driver requires the exact loaded image identity. apple,cs-opp: $ref: /schemas/types.yaml#/definitions/phandle @@ -99,6 +115,75 @@ properties: Operating-point table of the GPU's AFR clock domain, one voltage cell per state. + apple,t6040-handoff-version: + $ref: /schemas/types.yaml#/definitions/uint32 + const: 2 + description: + Written last by the boot loader after it has filled the T6040 operating + points, memory regions and per-chip calibration. Absent in the source DT. + + apple,core-mask: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Enabled cores in the two ten-core T6040 clusters, low cluster first + + apple,max-power-mw: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Per-chip maximum GPU power in milliwatts, calculated by the loader + + apple,perf-base-pstate: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Logical base state of the frequency-relative performance table. T6040 + supports the first active state, also used when its ADT omits the property. + + apple,perf-map-a: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + All 16 A selector register fields, indexing raw ADT operating points. + Eleven populated entries, including off, followed by five zeros. + + apple,perf-map-b: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + All 16 B selector register fields, indexing raw ADT operating points. + Eleven populated entries, including off, followed by five zeros. + + apple,perf-relative-a: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Per-chip relative power of each populated A selector, in percent, + followed by zeros for the five unused selectors. + + apple,perf-relative-b: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Relative frequency of the populated A ladder above the base state, + in percent, followed by zeros for the five unused selectors. + + apple,core-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + GPU core leakage, one coefficient per cluster. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,sram-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + GPU SRAM leakage, one coefficient per cluster. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,cs-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + CS leakage. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,afr-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + AFR leakage. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + apple,firmware-abi: $ref: /schemas/types.yaml#/definitions/uint32-array minItems: 3 @@ -190,6 +275,38 @@ allOf: const: apple,agx-t6040 then: properties: + apple,core-mask: + minimum: 1 + maximum: 1048575 + apple,max-power-mw: + minimum: 1 + maximum: 200000 + apple,perf-base-pstate: + const: 1 + apple,perf-map-a: + minItems: 16 + maxItems: 16 + apple,perf-map-b: + minItems: 16 + maxItems: 16 + apple,perf-relative-a: + minItems: 16 + maxItems: 16 + apple,perf-relative-b: + minItems: 16 + maxItems: 16 + apple,core-leak-coef: + minItems: 2 + maxItems: 2 + apple,sram-leak-coef: + minItems: 2 + maxItems: 2 + apple,cs-leak-coef: + minItems: 1 + maxItems: 1 + apple,afr-leak-coef: + minItems: 1 + maxItems: 1 reg: items: - description: GFX coprocessor control registers diff --git a/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml b/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml index 53b304bcec7ff..631ba47a5a3ca 100644 --- a/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml +++ b/Documentation/devicetree/bindings/input/apple,mesa-fingerprint.yaml @@ -57,11 +57,17 @@ properties: description: Rail selector words, in power-on order. firmware-name: - maxItems: 1 + minItems: 1 + items: + - description: + Calibration blob for this sensor. + - description: + Signed root-key container, for sensors whose enclave session needs + one. description: - Calibration blob for this sensor. It is per-device factory data written - at manufacture, so it is neither redistributable nor derivable from the - compatible; the board description has to name it. + Per-device factory data written at manufacture, so neither + redistributable nor derivable from the compatible; the board + description has to name it. spi-cpha: true diff --git a/Documentation/devicetree/bindings/media/apple,t8140-ave.yaml b/Documentation/devicetree/bindings/media/apple,t8140-ave.yaml index 110ebbc3d09e2..61e10feb5df71 100644 --- a/Documentation/devicetree/bindings/media/apple,t8140-ave.yaml +++ b/Documentation/devicetree/bindings/media/apple,t8140-ave.yaml @@ -4,19 +4,21 @@ $id: http://devicetree.org/schemas/media/apple,t8140-ave.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Apple T8140 video encoder +title: Apple video encoder maintainers: - Miguel Cruz <7362641+Acelogic@users.noreply.github.com> description: | - The T8140 AVE coprocessor encodes video using firmware preloaded by the + The T6041 and T8140 AVE coprocessors encode video using firmware preloaded by the bootloader. Its two DART instances translate the same stream. The bootloader supplies two reserved firmware regions and their IOVA mappings. properties: compatible: - const: apple,t8140-ave + enum: + - apple,t6041-ave + - apple,t8140-ave reg: items: diff --git a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml index 81fd3e37452a6..8f935a2d2b320 100644 --- a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml +++ b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml @@ -46,6 +46,7 @@ properties: - cypress,cyw4373-fmac - cypress,cyw43012-fmac - infineon,cyw43439-fmac + - pci14e4,4434 # BCM4388 - const: brcm,bcm4329-fmac - enum: - brcm,bcm4329-fmac diff --git a/Documentation/devicetree/bindings/npu/apple,ane.yaml b/Documentation/devicetree/bindings/npu/apple,ane.yaml index 79cf942654f7b..feae5b133ac2f 100644 --- a/Documentation/devicetree/bindings/npu/apple,ane.yaml +++ b/Documentation/devicetree/bindings/npu/apple,ane.yaml @@ -15,7 +15,9 @@ description: drives the engine through its register window. On T602x and T8112 a coprocessor runs the ANE firmware and the host talks to it through an ASC mailbox. T8140 has two interrupts and one system power domain; its - coprocessor uses a different firmware interface. + coprocessor uses a different firmware interface. T6041 likewise needs a + generation-specific firmware backend; it has one interrupt and its CPU + power state supplies the engine through the ANE system domain. properties: compatible: @@ -25,6 +27,7 @@ properties: - apple,t6020-ane - apple,t6021-ane - apple,t6022-ane + - apple,t6041-ane - apple,t8112-ane - apple,t8140-ane @@ -87,19 +90,35 @@ required: - power-domains allOf: + - if: + properties: + compatible: + const: apple,t8140-ane + then: + properties: + interrupts: + minItems: 2 + - if: + properties: + compatible: + const: apple,t6041-ane + then: + properties: + interrupts: + maxItems: 1 - if: properties: compatible: contains: - const: apple,t8140-ane + enum: + - apple,t8140-ane + - apple,t6041-ane then: properties: reg: maxItems: 1 reg-names: maxItems: 1 - interrupts: - minItems: 2 interrupt-names: false iommus: minItems: 3 diff --git a/Documentation/devicetree/bindings/phy/apple,t6040-atcphy.yaml b/Documentation/devicetree/bindings/phy/apple,t6040-atcphy.yaml index 04c8907584946..7f24f79264211 100644 --- a/Documentation/devicetree/bindings/phy/apple,t6040-atcphy.yaml +++ b/Documentation/devicetree/bindings/phy/apple,t6040-atcphy.yaml @@ -106,8 +106,8 @@ properties: port: $ref: /schemas/graph.yaml#/properties/port description: > - Link to the SuperSpeed lanes of the Type-C connector. Absent on a fixed - DisplayPort PHY. + Link to the SuperSpeed lanes of the Type-C connector, or to the DCP + DisplayPort output on a fixed DisplayPort PHY. apple,tunable-axi2af: $ref: "#/$defs/apple,tunable" @@ -174,7 +174,6 @@ allOf: "#reset-cells": false orientation-switch: false mode-switch: false - port: false additionalProperties: false diff --git a/Documentation/devicetree/bindings/power/apple,t8140-pmp-report.yaml b/Documentation/devicetree/bindings/power/apple,t8140-pmp-report.yaml index e8eac9e5411e3..e79ea331865b5 100644 --- a/Documentation/devicetree/bindings/power/apple,t8140-pmp-report.yaml +++ b/Documentation/devicetree/bindings/power/apple,t8140-pmp-report.yaml @@ -93,6 +93,13 @@ patternProperties: holds this entry's device ID, instead of by reg. Only valid on a report whose PMP the host starts. + apple,irq-safe: + type: boolean + description: + The entry is the parent of IRQ-safe PMGR power states and is + switched with interrupts disabled. Setting the state takes a + spinlock and polls the acknowledgment atomically. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml index fdf3d1883a99d..755c5d195cf0f 100644 --- a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml +++ b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml @@ -36,6 +36,15 @@ properties: Model name for presentation to users $ref: /schemas/types.yaml#/definitions/string + speaker-id-gpios: + minItems: 1 + maxItems: 8 + description: + Inputs encoding the fitted speaker assembly variant, least significant + bit first. The machine driver exposes their logical levels through the + read-only Speaker Variant ID control. GPIO polarity flags define each + bit's logical level. + patternProperties: "^dai-link(@[0-9a-f]+)?$": description: | diff --git a/Documentation/devicetree/bindings/sound/apple,t6030-dpaudio.yaml b/Documentation/devicetree/bindings/sound/apple,t6030-dpaudio.yaml index 9b4e9328d9020..bb92d6a30c428 100644 --- a/Documentation/devicetree/bindings/sound/apple,t6030-dpaudio.yaml +++ b/Documentation/devicetree/bindings/sound/apple,t6030-dpaudio.yaml @@ -4,29 +4,36 @@ $id: http://devicetree.org/schemas/sound/apple,t6030-dpaudio.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Apple T6030 DisplayPort audio +title: Apple T6030 and T6041 DisplayPort audio maintainers: - Ryan Murray description: | - The external display processor provides the HDMI port's audio service. - Its DisplayPort audio interface has no application-processor registers. - Samples arrive through SIO, and the graph connects this interface to - port 0 of the display processor that owns the audio service. + Each DisplayPort audio interface has no application-processor registers. + Samples arrive through SIO, and the graph connects the interface to port 0 + of the display processor that owns its audio service. An interface without + an assigned display processor has no graph connection and remains disabled. + + T6041 requires a generation-specific SIO firmware backend and a compatible + display audio service before these interfaces can be enabled. select: properties: compatible: contains: - const: apple,t6030-dpaudio + enum: + - apple,t6030-dpaudio + - apple,t6041-dpaudio required: - compatible properties: compatible: items: - - const: apple,t6030-dpaudio + - enum: + - apple,t6030-dpaudio + - apple,t6041-dpaudio - const: apple,dpaudio dmas: @@ -52,6 +59,26 @@ required: - dmas - dma-names - power-domains - - ports + +allOf: + - if: + properties: + compatible: + contains: + const: apple,t6030-dpaudio + then: + required: + - ports + + - if: + not: + required: + - ports + then: + properties: + status: + const: disabled + required: + - status additionalProperties: false diff --git a/Documentation/devicetree/bindings/spi/apple,qspi.yaml b/Documentation/devicetree/bindings/spi/apple,qspi.yaml index 39924afc9a8b6..0b98af1ad68d2 100644 --- a/Documentation/devicetree/bindings/spi/apple,qspi.yaml +++ b/Documentation/devicetree/bindings/spi/apple,qspi.yaml @@ -22,6 +22,7 @@ properties: enum: - apple,t8122-qspi - apple,t8132-qspi + - apple,t6041-qspi - apple,t8140-qspi reg: diff --git a/Documentation/devicetree/bindings/spi/apple,spi.yaml b/Documentation/devicetree/bindings/spi/apple,spi.yaml index 4b30265686fa1..be47c5f9ff075 100644 --- a/Documentation/devicetree/bindings/spi/apple,spi.yaml +++ b/Documentation/devicetree/bindings/spi/apple,spi.yaml @@ -18,6 +18,7 @@ properties: - items: - enum: - apple,t6020-spi + - apple,t6041-spi - apple,t8140-spi - const: apple,t8103-spi - items: diff --git a/Documentation/driver-api/media/drivers/apple-isp-j616s.rst b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst new file mode 100644 index 0000000000000..9a2bf93d7b059 --- /dev/null +++ b/Documentation/driver-api/media/drivers/apple-isp-j616s.rst @@ -0,0 +1,322 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +J616s ISP modes and capture protocol +==================================== + +Sources and scope +----------------- + +The source reference is Ryan's m1n1 PR 20, branch ``j616s`` at +``9be380c8a5a94609fdbe62d693c7b2121478ad8f``: + +* ``proxyclient/m1n1/fw/isp/isp_sensor.py`` discovers IMX958 and queries + ``CH_CAMERA_CONFIG_GET`` for every configuration reported by ``CH_INFO_GET``. +* ``isp_capture.py:T6040PreviewConfiguration`` contains the successful + processed capture command sequence. ``isp_pixels.py`` decodes its P010 + output as video-range BT.709. +* ``docs/j616s-bringup.md`` records all 13 configuration queries, completed + 1280x720 P010 captures (including an 80-frame ring-wrap trial) and a later + single completed 1920x1080 P010 frame. Intermittent startup/streaming stalls + remain unresolved. Neither the requested AE limits nor the host capture + rate is a measured sensor FPS. + +Both processed output sizes use firmware configuration **5**, whose reported +input dimensions are 1920x2160. The branch does not establish that this input +height is an independently deliverable 2160-row colour image, nor that config +5 is the highest-resolution configuration the sensor offers. It contains no +completed colour result from another configuration. RAW trials produced +stable pixels after stop but no completed RAW report; combined RAW/YUV was +refused at the allocation limit. No sensor full-resolution claim follows. + +Linux PR 231, branch ``j616s/linux-integration-next`` at +``a4e7ee86bc5eed8ff9dab61ef9656adfe000b207``, has the corresponding +``isp-profile.[ch]`` and IPC/buffer preparation. Its T6040 profile is pinned +to **25G76** and remains gated. It is a protocol reference, not an admission +profile for the current firmware. + +This driver change builds on the working T6040 H17 implementation in Linux +PR 384, now in ``integration/aurora``. That PR reports 26.6.x firmware capture +at 1920x1080 NV12 and use of config-5 1080p/720p presets. P010 and additional +firmware-derived output modes still need capture validation on 26.6.2. +Only our source and synthetic protocol tests are included; no Apple binary, +setfile contents, calibration table or firmware image is added. + +Public source audit +------------------- + +The pinned PR and its discussion were checked again for the RAW layout and +per-configuration timing. The reusable code establishes the processed path; +it does not contain a RAW Bayer packing/CFA contract or the 13 reply records: + +* ``isp_capture.py`` accepts only 1280x720 and 1920x1080 processed geometry. + Its generic pool-3 builder describes a single address, not the pixel packing, + CFA order or RAW receiver command sequence. The qualified preview uses pool 9. +* ``isp_pixels.py`` contains only an MSB-aligned P010 decoder, with no Bayer decoder. +* ``isp_sensor.py`` reads the sensor ID/count and queries an index at runtime. + It has neither saved per-index replies nor a frame-rate decoder. +* ``isp_profile.py`` and ``fixtures/isp_t6040_25g76.json`` describe boot segments, + allocation geometry and channels. The fixture has no camera-info or config replies. +* ``isp_vid.py`` and ``experiments/isp.py`` use the older fixed 720p NV12 receiver. + ``isp_cmd.py`` includes legacy sensor-0x248 examples and a temporary 3.75 FPS + AE request (0x3c0), alongside a comment describing the older 30 FPS request. + These are not IMX958 per-mode timing records. The qualified T6040 helper instead + requests 30/15 FPS AE limits; neither set supplies a measured nominal-rate table. +* ``isp_command.py`` implements ownership, command/reply and deadline handling. + ``src/isp.c`` prepares boot-time power and heap handoff, not a RAW capture client. +* ``docs/j616s-bringup.md`` lines 752-789 explicitly distinguish a stable RAW + snapshot after stop from a completed buffer report and say orchestration remains + private. Commit ``743e4e75`` adds that diagnostic RAW evidence as documentation; + it does not add a RAW receiver implementation. Lines 832-841 qualify one + completed 1080p P010 frame, with measured FPS still unqualified. + +These findings do not deny the recorded RAW snapshot. They mean a V4L2 Bayer +FOURCC, frame-completion contract and per-config rates cannot be derived from +these files without additional runtime replies or capture evidence. In particular, +the old 0x103 SIF example must not be treated as an IMX958 Bayer-format declaration. + +AOP endpoints and separate audio stream +----------------------------------------- + +The modern ``fw/aop/client.py`` map in PR 20 is 0x20 SPUApp, 0x21 wakehint, +0x22 audio, 0x23 voice trigger, 0x24 accelerometer, 0x25 gyroscope, 0x26 lid +angle, 0x27 ALS and four endpoints at 0x28-0x2b with unqualified roles. The +separate ENDPOINTS13 map assigns different roles and is not the T6040 map. + +The documented HQ audio sequence uses the advertised aop-audio service at +endpoint 0x22, EPIC version 2, checked attachment/property replies and owned +DART mappings. It first establishes LP readiness using adpx buffer property 300, +attachments lpai/pdm0/adpx, lpai pw1, lai attachment and lai/lpai runn. HQ then +attaches hpai, requests pw1, prepares stream-10 NS RX0 DMA and starts DMA before +requesting hpai pwrd. Matching reports and two full copies precede RX stop and +return through pw1 to idle. These are audio commands, not camera mode selectors. + +The qualified ISP preview helper contains no AOP command. This camera change +therefore does not replay the AOP audio stream or alter its Linux driver. The +ISP channel and command maps below describe the camera path independently. + +Mode and format selection +------------------------- + +On T6040/IMX958, probe queries every advertised configuration, bounded at 64, +using the 0x120-byte H17 ``CH_CAMERA_CONFIG_GET`` exchange. The complete list +is available through dynamic debug at probe and cached in full for +``VIDIOC_LOG_STATUS`` (``v4l2-ctl --log-status``), without sending firmware +commands. Collect this diagnostic dump before starting a stress run. It includes +input/output dimensions and the sensor clock word; that word is not FPS. + +The board's existing config-5 1920x1080 and 1280x720 presets stay first and +NV12 stays the default. Additional distinct output sizes are appended only +when the reply's 16-bit and repeated 32-bit dimensions agree, the dimensions +are nonzero, output is even and within input, and output is at most 16384 +in either dimension. The full reported output becomes the crop and output +size for its own config index. Input dimensions alone never add a mode. +Thus a larger consistent firmware output can be selected by ``S_FMT`` and +listed by ``ENUM_FRAMESIZES``; no invented full-resolution preset is added. +These consistency checks cannot establish image quality, supported FPS or +whether a configuration needs additional sensor-specific commands. + +``V4L2_PIX_FMT_P010`` is offered only for T6040/IMX958. The config-5 +1280x720 and 1920x1080 processed previews are enabled when +the runtime input geometry matches the source profile. An unspecified P010 +size selects 1280x720 first. Larger P010 modes require a consistent firmware +output reply. P010 uses two little-endian 16-bit component planes with ten useful +MSB-aligned bits, and equal 64-byte-aligned Y/UV strides. UV immediately +follows Y, as required for contiguous P010. Both NV12 and P010 use the +standard single-planar V4L2 API with one memory plane containing both +component planes. The firmware still receives two component addresses. + +At 1920x1080, stride is 3840, Y is 4147200 bytes, UV is 2073600 bytes and +``sizeimage`` is 6220800. At 1280x720, stride is 2560 and ``sizeimage`` is +2764800. The proxy's independently page-aligned plane extents were +0x3f8000/0x1fc000 at 1080p and 0x1c4000/0xe4000 at 720p; inserting those +inter-plane gaps into a V4L2 contiguous P010 buffer would misdescribe it. +Linux passes its actual contiguous extents to the rendered pool instead. + +P010 uses the proxy's eight metadata slots (0x8000 bytes each) and two +rendered slots. The queue limits userspace to two rendered buffers, matching +the pool capacity. Completion matches either both full submitted addresses +or both zero-extended low-32-bit plane addresses, plus plane count, pool 9 +and a fresh 64-bit submission tag; an old tag +cannot release a new lease. Buffer completion is published to videobuf2 +only after the firmware report ACK and doorbell have been published. +NV12 pool sizes and the T8140/Neo default formats, presets and command path +retain their existing behavior. + +The inherited ``multiplanar`` module parameter and its inconsistent queue-type +hooks are removed. All sensors use the shipped single-planar V4L2 API; the +non-contiguous NV12M alias is not advertised. No driver +module/kernel parameter, platform flag or per-platform Kconfig option selects +support. NV12 stays first and is the initial format because browsers/WebRTC +and many applications do not support P010 or simply choose the first format. +Applications can select P010 explicitly with ``VIDIOC_S_FMT``. + +Endpoints +--------- + +These are the seven shared-memory ISP channels, not AOP/RTKit endpoints. +The table records the 25G76 source fixture. The current driver negotiates +channel names, types, sources, counts and 64-bit addresses on each boot; +it does not impose the captured ring addresses or counts on 26.6.2. + +================= ==== ====== ===== ======================================= +Channel Type Source Slots Purpose +================= ==== ====== ===== ======================================= +TERMINAL 2 0 768 Firmware log reports / terminal ACKs +IO 0 1 8 Host commands and in-place replies +DEBUG 0 1 8 Discovered; no capture commands sent +BUF_H2T 0 2 64 Host metadata/output descriptor batches +BUF_T2H 1 3 64 Firmware buffer completion/return reports +SHAREDMALLOC 1 3 8 Firmware allocation/free requests +IO_T2H 1 3 8 Discovered; no capture handler installed +================= ==== ====== ===== ======================================= + +A source word selects a doorbell bit, so IO and DEBUG share bit 1; the +three reply channels share bit 3. Each channel descriptor is 0x100 bytes +with a u64 ring address at 0x50. Each slot is 0x40 bytes / eight u64 words. +Publish the payload before arg0, which transfers slot ownership. The +existing IPC parser bounds rings against the allocation, descriptor table, +boot/command storage and other rings. The threaded IRQ services allocations, +terminal messages and buffer reports while a command waits for its ACK. + +Command stream +-------------- + +IO commands have a zero/status u32 followed by the opcode u32, then the +parameters below. Sizes include that eight-byte header. ``ch`` is zero. + +====== ================================== ===== ======================= +Opcode Command Size Parameters +====== ================================== ===== ======================= +0x0003 CONFIG_GET 0x1c Camera channel count +0x3206 SET_DSID_CLR_MULTI_BC_REG_BASE 0x50 8 slots, range 0x2fc +0x0000 START 0x0c Proxy 0x11, Linux 0 +0x010d CH_INFO_GET 0x190 ch +0x0106 CH_CAMERA_CONFIG_GET 0x120 ch, index (all) +0x0024 FLICKER_SENSOR_SET 0x0c 0 +0x013b CH_SBS_ENABLE 0x10 ch, 1 +0x0107 CH_CAMERA_CONFIG_SELECT 0x10 ch, selected config +0x010e CH_BUFFER_RECYCLE_MODE_SET 0x10 ch, 1 (empty only) +0x010f CH_BUFFER_RECYCLE_START 0x0c ch +0x080c CH_CROP_SCL1_SET 0x1c ch, x, y, width, height +0x0b09 CH_OUTPUT_CONFIG_SCL1_SET 0x38 ch, output geometry +0x0208 CH_AE_FRAME_RATE_MAX_SET 0x10 ch, fps * 256 +0x020a CH_AE_FRAME_RATE_MIN_SET 0x10 ch, min_fps * 256 +0x0117 CH_BUFFER_POOL_CONFIG_SET (meta) 0x9c ch, pool 0, count 8 +0x0117 CH_BUFFER_POOL_CONFIG_SET (output) 0x9c ch, pool 9, count 2 +0x0125 CH_LOCAL_RAW_BUFFER_ENABLE 0x10 ch, 1 +0x0b0d CH_PREVIEW_STREAM_SET 0x10 ch, 1 +0x0138 CH_MASTER_SLAVE_SYNC_MODE_SET 0x10 ch, 0 +0x0100 CH_START 0x0c ch +0x0101 CH_STOP 0x0c ch +0x0104 CH_BUFFER_RETURN 0x0c ch +====== ================================== ===== ======================= + +The DSID bases are 0x2201d4000, 0x2221d4000, 0x2241d4000, 0x2261d4000, +then four U64_MAX sentinels. Linux retains its existing boot-time PMP and +logging commands in addition to discovery; it does not replay the proxy's +boot allocations or private power orchestration. + +For config 5, crop is (0, 0, output width, output height). The output command +parameters after ch are width, height, Rec.709=1, **format=0x12**, Y stride, +UV stride, 0, 0, height, 0, width. NV12 continues to use format 0. +Metadata pool sizes/strides are 0x8000, one data block; the output pool +contains actual Y/UV sizes and strides, two data blocks, no compression. + +Before CH_START, BUF_H2T submits metadata and output buffers as a 16-byte +header (type 1, count) and 64-byte descriptors. Each descriptor contains +four u64 addresses, four u32 flags (0x40000000 for each used plane), +u32 plane count, u32 pool and u64 tag. BUF_T2H returns completion reports; +render status 0x10000000 means a completed frame. Submission status is +0x30000000 and return ACK status is 0x80000000. Requeue only a returned lease. +CH_STOP and CH_BUFFER_RETURN retire the stream; resident firmware remains +running for the existing reopen path. + +The proxy used AE max/min 7680/3840 (30/15 fps). Linux retains its V4L2 rate +and exposure-priority controls: the default holds 30/30, and enabling +``exposure_auto_priority`` allows 30/15. These are requested limits, not FPS +measurements. P010 follows the minimal proxy sequence without adding +setfile-load, CNR, MBNR, SIF-format or temporal-filter commands; NV12 keeps +its existing calibrated path. A previously loaded setfile may still be +resident when switching streams. + +26.6.2 comparison +----------------- + +The 25G76 firmware UUID differs from the 26.6.2 image. Matching struct sizes +is source-level ABI evidence, not a new live qualification of P010. + +* Boot block: both use 0x290 bytes, IPC address at 0x08, shared base/size at + 0x10/0x18, extra heap at 0x20/0x28, platform at 0x30, queue offset+1 at + 0x50, 0x40 at 0x68, camera scheme at 0xd0 and the no-optical-card flag + at 0x120. The current H17 path checks descriptor flag bit 1. +* Memory: 25G76 pinned TEXT/DATA sizes 0xca8000/0x1344000, remap 1<<40, + shared base 0x1fec000, and a 256 MiB shared aperture. Linux uses the + bootloader's current TEXT/DATA/main-memory reservations, backs the missing + main-memory tail, and retains T6040's **0xde600000** shared-size descriptor. + No pinned segment size, heap request, IOVA or firmware hash is ported. +* IPC: 0x100-byte descriptors, 0x40-byte slots, 64-bit addresses and the + seven names agree. Counts and addresses remain negotiated, with the + existing overlap checks retained. The driver uses its three-DART IOMMU + domain and bootloader address-filter setup, not proxy DART table writes. +* Commands: DSID's 0x50-byte eight-slot layout, + CH_INFO_GET's 0x190-byte record, scaler-1 crop/output, 0x9c-byte pools, + local RAW, preview, sync and channel start/stop layouts agree with the + current H17 builders. START is an important parameter difference: the + proxy sends 0x11, while the working Linux H17 boot path sends 0. Linux + retains its current boot command; issuing START a second time after + selecting P010 would violate the proxy's guarded startup lifecycle. The new config query explicitly exchanges 0x120, + rather than the unused legacy 0xdc current-config helper. Header/channel/ + index checks and duplicated geometry checks guard decoding. The H17 + appended bytes remain uninterpreted. The old preview validator also checks + CH_INFO_GET byte 0x138; the current Linux helper retains only the common + 0x118-byte prefix of its 0x190-byte reply and does not make that check. +* Metadata: CH_INFO_GET reports 0x4d00 on both paths. The proxy rounds its + metadata backing/pool to 0x8000; P010 does the same even on a 4 KiB host. + NV12 retains the current driver's 0x4d00 pool and 16-buffer capacity. + Neither path configures a T6040 capture-metadata pool or capture watchdog. +* Startup: Linux retains its once-per-boot resident-firmware admission, + current reset-vector check and H17 handshake; the 25G76 SHA/UUID admission + and recovery model from PR 231 are not substituted. Linux's command + timeout/IRQ service remain in use; the proxy's 30-second inactivity / + 40-second wall startup budget is not copied. + +Validation on hardware must record the current config list first, then +exercise both P010 presets, requeue, stop/reopen and a longer run. If a larger +firmware output appears, test its format and command requirements separately. +Do not label input dimensions, a successful query, or a requested rate as a +completed full-resolution stream or measured FPS. + +Open qualification and performance checks +----------------------------------------- + +``tools/testing/selftests/media/apple-isp-stress.c`` exercises long runs, +repeated start/stop and format/size switches through MMAP. It logs completed +frames, delivered FPS, maximum inter-frame gap, sequence gaps and buffer errors. +Use the established nominal rate as its explicit rate argument. A driver-generated +sequence is not a firmware frame counter, so timing and sequence checks alone +cannot prove the sensor dropped no frames internally. + +The P010 path requeues even the last returned lease into an otherwise empty +queue, publishes completion ACKs before releasing output or metadata leases, +acquires message validity before reading payload and bounds each IRQ ring drain +to one ring traversal so terminal/allocation traffic cannot starve completions. +Command replies must match the requested reply length, zero status and original +header. A timeout or malformed ACK latches failure; no late ACK can authorize +another command. The existing forced coprocessor stop runs before uncertain +leases are released. Resident firmware then needs a reboot before another capture. +These corrections address source-visible stalls/races. They are not evidence +that the intermittent stalls observed by the USB proxy have been reproduced +or cured on the current firmware. + +No verified IMX958 RAW Bayer packing/order, RAW completion path or nominal FPS +for every configuration is present in the public source. The recorded RAW trial +reads stable memory after stop but receives no completed RAW report. A Bayer +FOURCC cannot be chosen from that evidence. The RAW format and an exhaustive +per-config FPS table remain open pending the original capture evidence and +runtime command replies; neither is inferred from the P010 output or input height. +The 30/15 values in the preview are AE requests, not per-mode nominal measurements. + +The T6040 device tree's current fixed TEXT/DATA reservations should eventually +come from a bootloader handoff describing the loaded image and mapping lifetimes. +They are firmware-build-specific. This change does not implement that handoff +or replace those reservations with 25G76 addresses. diff --git a/Documentation/driver-api/media/drivers/index.rst b/Documentation/driver-api/media/drivers/index.rst index 7f6f3dcd5c90c..f1ead3c9e389f 100644 --- a/Documentation/driver-api/media/drivers/index.rst +++ b/Documentation/driver-api/media/drivers/index.rst @@ -12,6 +12,7 @@ Video4Linux (V4L) drivers .. toctree:: :maxdepth: 5 + apple-isp-j616s bttv-devel cx2341x-devel cx88-devel diff --git a/Documentation/gpu/apple-dcp-t6041-panel.rst b/Documentation/gpu/apple-dcp-t6041-panel.rst new file mode 100644 index 0000000000000..bdf15e644d68f --- /dev/null +++ b/Documentation/gpu/apple-dcp-t6041-panel.rst @@ -0,0 +1,182 @@ +.. SPDX-License-Identifier: GPL-2.0-only OR MIT + +T6041 internal panel +=================== + +Desktop 26.6 firmware uses its own IOMFB instance. Panel brightness is in +integer nits from zero to the panel ceiling, with the same 250 ms fade used +by the H17P panel. The backlight interface reports the requested target and +``actual_brightness`` follows accepted, completed presents. A registration +default does not change the brightness inherited from the boot loader. +Until a loader hint or a completed user request supplies a known level, +``actual_brightness`` reports the target. Echoing the unchanged registration +value also leaves the panel alone while its level is unknown. A request for a +different target takes control. Once a hint or completed request establishes +the level, all targets are usable. Framebuffer unblank and core suspend +notifications also preserve an unknown inherited level. +The first explicit change fades from a known inherited level, or from the +reporting target when no hint is available. Firmware reports are observations, +not requests. After takeover they cannot replace the target, restart a fade or +cause a brightness write. Actual readback follows accepted, completed presents. +Ambient-light policy can use the normal backlight interface, just as a slider +does. Requests made while blanked or suspended preserve the restore target. + +Fixed refresh modes keep the timing and color IDs supplied by DCP. The driver +does not synthesize lower refresh modes from a 120 Hz mode. Fractional rates +remain in 16.16 Hz until the DRM pixel clock is calculated. A ProMotion mode +needs desktop presentation deadlines even with adaptive sync disabled. + +The DT panel compatible ``apple,panel-j616`` identifies the ProMotion panel and +enables adaptive sync support without a module parameter. The connector capability +requires the desktop firmware profile, the internal main display, and a valid +adaptive-sync range. Other panel compatibles do not acquire this capability. The +existing internal ProMotion convention supplies 24 Hz to the mode rate when +the firmware omits both range fields or reports both as zero placeholders; +a partial, duplicate or invalid range +leaves the timing usable at fixed refresh. + +A rejected adaptive-sync parameter or mode selection gets one retry with +adaptive sync disabled. An adaptive swap that stops completing invokes the +swap watchdog and re-applies the active CRTC at fixed refresh. Either failure +latches adaptive sync off until reboot. A transport that stops replying cannot +be recovered by issuing another command on its busy channel; it still requires +a reboot. Use the previous kernel entry if a panel fails qualification. + +Panel identity +-------------- + +The desktop internal panel has no external EDID service. Its ProductAttributes +supply the monitor identity, independently of optional colorimetry or timing +fields. The driver publishes an EDID using that product data, the panel dimensions +from the device tree, and a representable DCP timing plus catalog limits. +The EDID clock rounds to its required 10 kHz units. DRM still uses the exact +firmware timing list; the EDID adds no synthetic modes or color capabilities. + +Compact powerlog reports may be bare serialized dictionaries or carry a command +descriptor. The receiver preserves the serialization header before handing the +report to systemep. Only the first valid report before Linux control can seed +takeover; later reports confirm output without changing the requested target. + +Desktop mode changes use the D202 and D454 adapters for this firmware's records. +The callback table also includes physical mapping and the empty resize +notification. An unknown callback during a modeset gets a bounded unsupported +reply. Its enclosing command must ACK before the set fails, so a subsequent +mode change can reuse the drained channel. Accepted atomic modeset errors are +reported through the out-fence. A timed-out command retains its channel until +the firmware replies; another set cannot overwrite its stack. + +Repeated or partial DisplayAttributes retain a validated fixed-panel identity. +They do not emit another identity hotplug event or revoke the EDID. + +Identity accepts both chunked DisplayAttributes and the desktop dictionary +setters. A legacy PNP manufacturer code is decoded when the text code is absent. +Missing optional labels are represented by the firmware vendor/product code and +unspecified numeric serial, without inventing a per-device identity. Partial +updates for the same vendor/product retain known optional identity fields. + +Desktop identity diagnostics and fallback +----------------------------------------- + +Identity inspection uses the existing DRM KMS debug category (``drm.debug`` +bit 2), with no driver-specific switch. When that category is enabled, D552, D561, +D114 and chunked property delivery report the outer key, serialized key names, +value type numbers and lengths. D000 identifies chunked delivery. Type numbers +are dictionary 1, array 2, integer 4, string 9, blob 10 and boolean 11. Values +are never printed. Each property walk stops at depth 4 or 64 objects, and the +DCP stops after 128 diagnostic records per callback class (512 total), with bounded +inspection-status lines in addition. Atomic reservations include framing records. D552, D561, D114 and chunked delivery have +separate budgets so periodic statistics cannot hide the panel setters. Service +dictionary setter D413 is inspected and can supply identity or a raw EDID too. +Scalar setters D102/D104/D414/D415/D563/D565/D567 report keys and types at +ingress when their public wire lengths match. They share the +fourth budget with chunked delivery. Null scalar values report size zero; no +scalar value or service handle is printed, and acknowledgements are unchanged. +When KMS debugging is disabled no inspection budget is consumed. Malformed +serialization only stops inspection. + +A real EDID with a usable identity takes priority. It may arrive as raw bytes, +a serialized blob, or an ``EDID``, ``edid`` or ``IODisplayEDID`` blob nested +in a dictionary. Checksum-valid blocks without a usable manufacturer, model +and serial are rejected so they cannot reintroduce null userspace strings. Without +one, ProductAttributes remain usable, but are no longer a prerequisite for +publishing an identity. The fallback reads the original ADT from m1n1's +reserved-memory ``phram`` node with ``label = "adt"``. The reader accepts at +most 16 MiB, 4096 nodes, 4096 properties per node and 32 levels, and validates +all extents before committing any identity data. + +The supported ADT identity properties are as follows. These are property +mappings, not a claim that a particular firmware supplies every property. + +* ``/panel`` or ``/product/panel`` ``vendor-id`` or ``display-vendor-id`` is a + little-endian 32-bit packed PNP manufacturer code. +* ``product-id``, ``display-product-id`` or ``panel-id`` supplies the low 16 + bits of the EDID product code. +* ``display-serial-number`` supplies a little-endian numeric serial. + ``serial-number`` supplies panel serial bytes, hashed with FNV-1a into the + numeric EDID serial. ``raw-panel-serial-number`` on ``/product`` or a panel + node is handled the same way. Machine serials from other nodes are ignored. +* ``width-mm`` and ``height-mm`` supply bounded physical dimensions. Missing + dimensions retain the OF panel node's ``width-mm`` and ``height-mm`` (or + ``adj-height-mm`` when the notch is hidden). The full height is read first, + so a missing optional adjusted height cannot leave the height at zero. + J616s has a 345x223 mm full panel and a 345x216 mm notch-hidden viewport. + The connector retains these exact millimetres after the EDID update. + +A successfully parsed ADT does not necessarily contain a panel node. In +particular, a machine-level ``/product`` ID and ``/arm-io/disp0`` timing data +are not panel identity properties. They do not override OF panel dimensions +or prevent the generic fallback from being published. + +If the ADT or its identity fields are absent, the desktop J616 fallback is +manufacturer APP, product 0616, name Apple Panel and unspecified numeric +serial zero, represented by the non-empty serial descriptor ``unspecified``. +This generic identity does not claim to identify an individual panel. The +preferred representable timing and range limits come from the retained DCP +TimingElements catalog. Exact mode enumeration continues to use that catalog. +For notch-hidden modes, the synthetic identity DTD retains the clock, active +area and total blanking but bounds the vertical front porch and sync pulse to +EDID's six-bit fields. It is an identity summary, never an enumerated or +programmed mode; the original DCP sync positions and mode IDs are unchanged. +This also lets a 118-line porch and 82-line notch-expanded pulse publish an +identity instead of failing the base EDID size check. +The EDID records model year 2024 (not a manufacture date) and a nominal D65 +reference white point required by EDID parsing. No calibrated primaries, gamma, +new mode or machine serial is claimed. The ADT reader, +EDID fallback and real EDID setters are restricted to desktop 26.6; T8140 +continues to use its existing path. + +Completed desktop brightness changes emit a backlight change uevent and notify +both ``brightness`` and ``actual_brightness``. The work runs outside the receive +path's locks and is drained during shutdown. Takeover sends the same event when +it updates the inherited level. Requested brightness remains the user's target +through the fade; notifications do not replace it with an intermediate level. + +DT capability and boot-loader handoff +----------------------------------- + +The J616 panel's existing DT compatible supplies adaptive-sync capability; +there is no T6041 VRR module parameter. Firmware and mode validation, the main +panel restriction, and the fixed-refresh failure latch still apply. EDID and +backlight notifications have no driver-specific tunables. + +The kernel does not consume ``chosen.asahi,t6041-dcp``. Its early T6041 gate +already follows the handoff published by m1n1: ``apple,t6041-handoff = <1>`` on +DCP, display and PIODMA, firmware compatibility 26.6.2, the expected DART stream +and mailbox relationships, initially disabled nodes, and a running DCP. +The DCP driver separately validates register resources and the firmware memory +handoff. Missing or invalid handoff data leaves the boot framebuffer in place. +These checks must remain when the loader's policy changes. + +The current m1n1 producer uses that chosen property to decide whether to attempt +publication. Removing it cannot be completed by a kernel change: a loader with +no published handoff provides nothing for the kernel to adopt. The producer +needs to select the T6041 handoff from its existing chip/board match and the +supported DCP/display/panel DT compatibles, then run the existing ADT, power, +running-session, firmware-segment, reservation and live DART mapping checks +without a chosen opt-in. Only a successful transaction may publish the existing +markers, memory regions and firmware properties. Keeping nodes disabled until +the kernel accepts that contract preserves compatibility with older kernels. +A failed transaction must publish no partial handoff. Loader host tests should +cover supported DT without the chosen property, an unsupported board/DT, and +each failed validation, as well as unchanged other-SoC policy. No new policy +flag or kernel bypass is needed. This kernel series does not replace Stage 2. diff --git a/Documentation/gpu/apple-drm-parameters.rst b/Documentation/gpu/apple-drm-parameters.rst new file mode 100644 index 0000000000000..599cf050f7c81 --- /dev/null +++ b/Documentation/gpu/apple-drm-parameters.rst @@ -0,0 +1,14 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Apple display module parameters +=============================== + +Use ``appledrm.show_notch=1`` to include the notch rows in the panel mode. +This common preference also applies to the M3 internal display firmware; +it replaces the platform-specific ``appledrm.t6030_show_notch`` alias. +When the boot framebuffer should cover those rows too, keep the useful +``chosen.asahi,show-notch=1`` m1n1 display preference alongside it. + +The old ``appledrm.dcpext_probe`` parameter had no consumer and has been +removed. External display support follows the device tree and the driver's +compatible and firmware admission checks. diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst index 8351e25ba8ef1..ced2721b96c75 100644 --- a/Documentation/gpu/drivers.rst +++ b/Documentation/gpu/drivers.rst @@ -7,6 +7,7 @@ GPU Driver Documentation amdgpu/index apple-dcp-backlight + apple-drm-parameters asahi-reclaim asahi-m4-bringup apple-dcp-m4 diff --git a/Documentation/power/apple-s2idle.rst b/Documentation/power/apple-s2idle.rst new file mode 100644 index 0000000000000..aeceefe3fabff --- /dev/null +++ b/Documentation/power/apple-s2idle.rst @@ -0,0 +1,109 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Apple Silicon suspend-to-idle wake qualification +=============================================== + +A missing wake IRQ number does not establish that an Apple Silicon system +has no wake source. There are two interrupt paths to distinguish: + +* An ordinary wake interrupt is enabled with ``enable_irq_wake()``. The IRQ + core keeps it enabled during device IRQ suspension and reports a system + wakeup when it arrives. AIC and AIC2/AIC3 use ``IRQCHIP_SKIP_SET_WAKE``: + retained-power suspend-to-idle needs normal interrupt delivery rather + than a second hardware wake routing configuration. +* An ``IRQF_NO_SUSPEND`` interrupt continues to run its handler. The handler + or its consumer must identify a genuine wake event and report it to the + PM core. The interrupt alone does not end system suspend. This permits + transports with unrelated traffic to filter their wake events. + +SMC mailbox interrupts use the second path. The SMC input driver makes +power-button presses and lid-open events hard wake events during its sleep +window. The RTKit notification worker must remain runnable. Do not arm the +whole SMC mailbox as an ordinary wake IRQ: unrelated notifications and +command replies are not user wake requests. + +Dockchannel HID also uses the second path. Its RX child interrupt stays +live below a chained AIC parent. The HID transport classifies keyboard +reports and trackpad clicks before reporting a hard wake event; pointer +motion, touch and other interface traffic do not wake the system. On +systems using a receive DMA ring, both the coprocessor and its translations +must remain usable while the system sleeps. + +Apple platform xHCI can keep running during suspend-to-idle and report +filtered USB wake events. Peripheral remote-wake support, hub policy and +controller retention all matter. An unarmed xHCI IRQ by itself does not +prove that this path is broken. + +Bluetooth depends on the fitted radio and its transport driver. In +particular, the MT7932 driver is not used by a board with a Broadcom radio. +Check the bound driver, device wake policy, HCI event filters, PCI/MSI wake +propagation and the return value of wake IRQ arming. A driver must undo its +own suspend state if it returns an error after suspending HCI: the PM core +will not invoke its normal resume callback for that failed suspend. + +An SMC RTC that provides timekeeping is not necessarily an alarm wake +source. Enumerate RTC names, alarm support and wake policy rather than +assuming that ``rtc0`` or a PMU driver enabled in the kernel configuration +provides a working recovery alarm. A PMIC's compatible string, register +layout, offset cell and interrupt wiring must all match its RTC driver. + +Establish the failing phase +-------------------------- + +The ``PM: suspend entry (s2idle)`` message is printed before process +freezing and device suspend. It does not prove entry into the idle loop. +Likewise, loss of SSH can follow network suspension while another device +is still suspending. A black display does not establish whether CPUs have +resumed. + +Capture the console independently of the display and network under test. +Use the existing ``power:suspend_resume`` and +``power:device_pm_callback_start``/``power:device_pm_callback_end`` trace +points, PM debug messages and callback timing. A ``machine_suspend`` entry +for suspend-to-idle establishes that the idle path was reached; callback +start/end pairs locate a device suspend or resume stall. See +:doc:`basic-pm-debugging` for staged PM tests. For suspend-to-idle, use +``freezer``, ``devices`` and ``platform``. The idle loop runs before the +``processors`` and ``core`` test checkpoints, so those levels do not +provide an automatic return from suspend-to-idle. + +Before actual sleep, record: + +* Board compatible strings, kernel release, source commit, configuration + and the DTB used by the boot loader. +* ``/sys/power/mem_sleep`` and ``/sys/power/pm_test``. +* The wake policy of the SMC input, dockchannel HID, Bluetooth PCI function + and USB devices; the bound drivers; relevant IRQ names and counts. +* ``/sys/kernel/debug/wakeup_sources`` and + ``/sys/power/suspend_stats`` where available. +* Each RTC's name, alarm support and device wake policy. + +Use ``/sys/power/pm_wakeup_irq`` for ordinary armed-IRQ attribution. For a +filtered transport, also compare its wakeup-source counters and interrupt +trace. An empty IRQ result is compatible with a valid filtered wake. + +Qualification matrix +-------------------- + +Test one wake stimulus at a time, first after a short sleep and then after +repeated cycles: + +* A short power-button press, and a lid close/open cycle. +* The built-in keyboard and a trackpad click. Separately verify that + motion and touch do not cause an unwanted wake. +* A wake-enabled, paired Bluetooth keyboard with the fitted radio driver. + Repeat with the PCI function's wake policy disabled. +* A directly connected USB keyboard, followed by the intended hub/dock, + with the necessary device and hub wake policies enabled. +* A timed RTC alarm only on a device known to provide that functionality. + +Also test a wake during entry, an aborted suspend, Bluetooth wake IRQ +arming failure, and suspend with Bluetooth wake disabled. A wake-arming +failure must abort suspend and leave the running transport recoverable; +successful cycles must balance wake IRQ enable/disable calls. + +For every cycle record the stimulus, idle entry, wake event, resume +completion and post-resume input, storage and network health separately. +Preserve a failed cycle's console and trace before recovery. Keep a known +bootable fallback and an independently available recovery transport, and +restore any temporary debug settings and wake policies after testing. diff --git a/Documentation/power/index.rst b/Documentation/power/index.rst index b4581e4ae785b..ce6d50693040f 100644 --- a/Documentation/power/index.rst +++ b/Documentation/power/index.rst @@ -8,6 +8,7 @@ Power Management :maxdepth: 1 apm-acpi + apple-s2idle basic-pm-debugging charger-manager drivers-testing diff --git a/Documentation/rust/bindgen-layout-checks.rst b/Documentation/rust/bindgen-layout-checks.rst index aaac5b24fb742..4e0a996bd701d 100644 --- a/Documentation/rust/bindgen-layout-checks.rst +++ b/Documentation/rust/bindgen-layout-checks.rst @@ -3,22 +3,21 @@ Checking generated binding layouts ================================== -bindgen can emit compile-time assertions that compare the size, alignment and -field offsets of every generated Rust type with the values Clang computes for -the C type. As upstream, the kernel build passes ``--no-layout-tests`` to -bindgen, so the generated bindings do not contain these assertions and any -supported bindgen version can build the kernel. - -To check the layouts of a configuration, build with ``BINDGEN_LAYOUT_TESTS=1``:: - - make LLVM=1 BINDGEN_LAYOUT_TESTS=1 prepare - -The bindings are then generated with the assertions, which are checked while -the ``bindings`` and ``uapi`` crates are compiled; nothing is executed. A -mismatch stops the build with an ``E0080`` error naming the type and the -property (``Size of``, ``Alignment of`` or ``Offset of``). Changing the -variable regenerates the bindings. The check covers only the configuration -being built. +Every Rust build retains bindgen's unconditional compile-time assertions for +size, alignment and field offsets. A mismatch stops the bindings crate build +with an ``E0080`` error naming the C type and property. These assertions are +required even when KUnit is disabled; ``BINDGEN_LAYOUT_TESTS=0`` cannot disable +them. Build the complete Image and all modules with the same generator. + +A successful C build does not validate Rust field offsets. In particular, +``CONFIG_SCHEDSTATS=n`` leaves a zero-sized, cacheline-aligned +``sched_statistics`` in ``task_struct``. Stock bindgen 0.73.2 loses its alignment +and can shift subsequent fields, including ``mm``. The ARM64 defconfig + +Asahi configuration exposed a 40-byte difference: Rust read a scheduler list +pointer as the current mm and Asahi's RSS charge faulted in +``percpu_counter_add_batch``. The C RSS helpers and allocator agreed on the +counter layout; the pointer supplied to them was wrong. Enabling SCHEDSTATS +only masks the generator defect and is not a requirement of the driver. Known generator limitations --------------------------- @@ -37,26 +36,27 @@ Unmodified bindgen 0.73 does not reproduce every C layout used by the kernel: * It can drop the explicit alignment of a defined zero-sized C struct, such as ``struct empty {} __attribute__((aligned(64)));``. -With the assertions enabled and stock bindgen 0.73.2, an arm64 Apple -silicon configuration of this tree fails on the size of ``ns_common`` and of -the namespace structures that embed it; bindgen 0.73.1 also fails on -``task_struct``, ``zone`` and ``pglist_data``. In those builds every offset -and alignment assertion passes: bindgen pads each field to the offset Clang -reports, so only the end of the Rust type is short. Rust code that reaches -such a type through a pointer and reads its fields gets the right data, but -must not rely on ``size_of`` for it, copy it by value or index an array of -it. The affected types depend on the bindgen version and the configuration, -which is what the check is for. +The earlier Fedora configuration exposed only trailing size differences for +some namespace types. That observation does not extend to defconfig: an +aligned empty member can also shift field offsets. Neither size nor offset +mismatches are safe to ignore. The affected types depend on the generator +version and the resolved kernel configuration, so validate each build. Corrected generator ------------------- ``tools/aurora-bindgen/`` contains pinned patches for bindgen 0.73.2 that -repair both cases, a private build recipe and compile-only tests; see -``tools/aurora-bindgen/README.rst``. It installs nothing system-wide. To check -the layouts with it:: +repair both cases, a local build recipe and compile-only tests; see +``tools/aurora-bindgen/README.rst``. Prepare it once before configuration:: + + python3 tools/aurora-bindgen/build.py + make LLVM=1 ... - make LLVM=1 BINDGEN=/path/to/bindgen BINDGEN_LAYOUT_TESTS=1 ... +The top-level Makefile automatically selects +``tools/aurora-bindgen/build/bin/bindgen`` from the source tree, also for ``O=`` +builds. Packagers prepare this entry point in their build roots. Make does not +fetch tools or install anything system-wide. If the entry point is absent, +Kbuild uses ``bindgen`` from PATH and the same mandatory assertions apply. The generated bindings are consumed by the Rust kernel crate and by several drivers. After changing the bindings generator, rebuild the kernel Image and @@ -64,5 +64,12 @@ all modules together. The assertions need bindgen 0.71.1 or later (the kernel minimum) together with the configured ``--rust-target 1.85``: that selects unconditional constant -assertions rather than ``#[test]`` functions, so a build with -``BINDGEN_LAYOUT_TESTS=1`` checks them without a Rust test configuration. +assertions rather than ``#[test]`` functions. They are checked without a +Rust test configuration. + +Check the recorded bindings command, not just the toolchain version banner. +Command-line overrides remain available for generator regression tests. +The defect can affect any generated aligned empty record and containing types +under other configurations; all emitted size/alignment/offset checks remain +mandatory. They do not cover C-only types or opaque internal fields. See the +generator README for the upstream status and related empty-struct issues. diff --git a/GAPS.md b/GAPS.md new file mode 100644 index 0000000000000..c8d2c7c753261 --- /dev/null +++ b/GAPS.md @@ -0,0 +1,1302 @@ +# T6040 GPU parity audit + +Audit baseline: `integration/aurora` at +`9cbdd0734d9e`, with GPU integration #463 (`a0a9485847f8`), the +r5 display series (`905dff774561`), camera #462 (`2114ed0c6245`), and the +two r6 display patches. The reconstructed r6 branch is +`wip/j616s-display-r6` at `af0a664046fb`. + +This series repairs regression coverage and a shared KUNIT=m WERROR build defect, +ports GPU-driven memory/fabric demand and PMP voter cooperation using +T6040 data, honors the existing DT idle-delay policy, and closes a compute +release-witness gap across recovery. GPU voltages, firmware features and the submission algorithm remain +unchanged from the reconstructed base. The major +T6040 calibration and image-admission changes requested by this audit are +already in the baseline. Software parity does not establish all-workload benchmark or CTS +parity. The operator has selected g3 as the measured J616s baseline; g4 +retains it and adds the qualified shared ports and diagnostics below. + +## Gap table + +| Gap | Neo behavior | T6040 before this series | After this series | Evidence / remaining gate | +| --- | --- | --- | --- | --- | +| Per-chip leakage and power ceiling | T8140 has its own platform data | #463 requires a versioned DT handoff, finite positive leakage, a power ceiling and the measured core mask; no single-unit fallback | Same runtime behavior | `g17/calibration.rs`, `g17/initdata/v25g83.rs`; #463 commits `a8fb1380b414`, `53877eaebd4b`, `415fd2e6ee2d` | +| Raw OPP count versus logical states | Firmware consumes 11 logical states | 16 raw GPU OPPs; 11 selectors in each A/B map; both end at raw OPP 15; unused handoff slots must be zero | KUnit now uses valid complete maps and checks the final state's second voltage lane | `g17/calibration.rs`; a count of 15 in an ADT property is not evidence for expanding this firmware layout | +| Full clock eligibility | Platform OPPs feed firmware performance tables | DT supplies both cluster voltages and GPU/CS/AFR frequencies; maps and relative-B are checked against the GPU OPP table | Same runtime behavior | `g17/initdata/v25g83.rs`, `g17/fw/initdata/v25g83.rs`; confirm sustained clocks and limiting reasons on hardware | +| DVFS ramp / effort | Full effort on publication, cleared after 16 ms of continuous idle | Same `Firmware::note_submission`, direct-port accounting and race-checked idle clear | Same runtime behavior | `g17/mod.rs`, `g17/port.rs`, `g17/power.rs`; opaque firmware power words are not a documented ramp control | +| GPU-driven fabric/memory floor | #440 uses an activity-following graphics PMGR input | T6040 CPU-only demand; g3 port sampled the firmware-lifetime GFX supplier | g4 uses DT-selected publication/continuous-idle demand, preserving DCS 5/fabric 2 and 32 ms CLPC hold | `clpc.c`, `g17/power.rs`, `g17/port.rs`, T6040 DT; blank-panel release and bandwidth retention require hardware qualification | +| PMP PMC vote cooperation | PMP/CLPC/AP agents enabled | T6040 static voters omit PMP 0 from pre-working-PMP bring-up | Port 772a9d3104ff adds the existing PMP supplier dependency and voter 0 on T6040 DCS/SOC rails | pmc.c and T6040 DT; live votes, idle release and thermal qualification required | +| Idle / off policy | Shared host hold and firmware device-control path | Same host implementation; source documents a 25G83 effective 2 ms off delay and approximately 1 ms wake cost | J616s requests a bounded 16 ms host hold through existing `apple,idle-off-delay-ms`; absent policy retains 2 ms | `g17/power.rs`, `g17/mod.rs`, J616s DT and binding; compare short/long gaps, depth-one latency and idle power | +| Fast-die / PMP | Platform-specific firmware power graph | 25G83 AON table and separate power-state target/release (101/96 C) are already populated; DT enables the GFX suppliers | Same runtime behavior | `g17/fw/initdata/v25g83.rs::POWER_STATE_WORDS`, `g17/initdata/v25g83.rs`, J616s DT; verify clocks with PMP running | +| Periodic preemption | Context-switch timer mask is zero | Same zero mask; two-cluster operand-state allocation differs by hardware layout | Same runtime behavior | `g17/fw/initdata.rs::CONTEXT_SWITCH_TIMER_MASK`, `g17/fw/compute.rs::OperandState`; no evidence supports changing the mask | +| High-priority queues | HIGH / MEDIUM / LOW firmware scheduling profiles | Same scheduling-policy selector and priority work rings | Same runtime behavior | `g17/mod.rs::scheduling_policy`, `g17/fw/channels.rs`, `g17/qos.rs` | +| Timestamp publication | Retains one CPU mapping for each firmware timestamp buffer | Same shared timestamp buffer and completion publication | Host timestamp suite again compiles and executes the production retained-mapping writer | `timestamp.rs`, `mmu.rs::TimestampVmap`, `tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py` | +| Statistics | Drains firmware statistics; no decoded `agx_stats` snapshot | Same; payloads discarded | g4 preserves full per-type/per-role raw snapshots and static-key report tracing | `g17/reports.rs`, `g17/channel.rs`, `g17/trace.*`; achieved-frequency/power decoding remains gated on firmware semantics | +| Firmware startup / registration | Two instances, message acknowledgement, POWER_ON_DONE and MMIO render-pair registration; no lead commands | One merged 25G83 graph, status acknowledgement, no POWER_ON_DONE, no MMIO pair registration; lead commands required | Same runtime behavior | `g17/fw/variant.rs`, `g17/initdata/v25g83.rs`; startup differences are ABI requirements, while MMIO pair registration on 25G83 remains unqualified | +| Queue pointer / restart layout | Producer at +0x40, recovery target at +0x50 | Producer at +0x50, no recovery-target word | Same runtime behavior | `g17/fw/variant.rs::PointerLayout`, `g17/fw/queue.rs`; do not copy another firmware build's offsets | +| Core topology / mapping ownership | One six-core cluster; two firmware roots and fabric bridge | Qualified two-cluster, 20-core layout; reserved firmware tables, one root and no fabric bridge | Same runtime behavior | `hw/t6040.rs`, `hw/t8140.rs`, `g17/mod.rs`; other fused core counts are still refused, and this audit does not expand that admission | +| Alternate loaded image | T8140 uses fixed firmware selection | Both exact T6040 normalized TEXT digests are admitted in one `Build.images` slice | Same runtime behavior | #453 (`26f5eb68dbce`), #463 (`804bba86ca55`), `hw/t6040.rs`, `g17/fw/variant.rs`, `fwid.rs`; unknown images remain refused | +| Submission/completion improvements | #423 shared G17 runtime | #423 is already an ancestor of the baseline; direct compute/render publication, admission domains and completion collection are shared | Same runtime behavior | #423; `g17/runtime.rs`, `g17/runtime/render/direct.rs`, `g17/port.rs`, `g17/completion.rs` | +| G13/G14 `submit.empty` regressions | Neo uses the G17 runtime | G16S uses that same G17 runtime, not the maintained G13/G14 workqueue | Same runtime behavior | `driver.rs`, `file.rs`, `g17/queue/mod.rs`; user-space empty submits may never reach this ioctl; trace and benchmark the paired Mesa stack | +| G13/G14 idle wake regressions | Firmware-specific cost plus shared host overhead | G16S shares common ioctl/GEM/sync infrastructure, but has different firmware, power handling and queue backend | Same runtime behavior | `driver.rs`, `g17/power.rs`; G13/G14 benchmark ratios cannot be transferred to G16S | +| QID capacity | Firmware floor 0; reserve protects dedicated render pairs | 25G83 reserves QIDs 0..31; public queue limit is reduced accordingly | KUnit checks floor 32 in dynamic growth and full logical queue budget tests; host resource runner follows current reserve helper | `g17/kick.rs`, `g17/runtime.rs`, `tools/asahi/g17-resource-tests.py` | +| KUnit / WERROR config matrix | Shared test modules require KUNIT=y | Four G17 test helpers used the broader CONFIG_KUNIT guard and failed WERROR with the shipping KUNIT=m config | Helpers now use CONFIG_KUNIT = "y", matching the Rust macro; config and warning policy unchanged | `rust/macros/kunit.rs`, `g17/kick.rs`, `g17/preparation.rs`, `g17/queue/admission.rs`, `g17/runtime.rs`; verify both KUNIT=m release and KUNIT=y QEMU builds | +| Compute ownership / retained mappings | Recovery and retirement witnesses protect mapping lifetime | Same production queue ownership, source loans, growth rollback and deferred release | Aged-record fallback now requires the original recovery generation; source loans and host negative controls remain | `tools/asahi/g17-capacity-fault-tests.py`, constructor/growth/pool/release-order fixtures | +| Restart and fault classification | Shared recovery handshake plus validated T8140 per-slot keys | Shared handshake uses 25G83 status offsets; `recovery_slot_keys=None` preserves firmware blame when keys are unknown | Bounded generation/slot/progress diagnostics retain unknown ownership; any missing selected key preserves firmware blame | `hw/t6040.rs`, `g17/initdata.rs`, `g17/recovery/attribution.rs`; successful 25G83 fault/restart qualification is still required | +| DART / UAT | Shared mapping ownership and checked page-table borrowing | Shared MMU, with T6040 reserved tables, PoC cleaning, per-page TLBI and native GPU permission quirks | Same runtime behavior | `hw/t6040.rs`, `mmu.rs`, `pgtable.rs`; do not remove coherency or handoff requirements for a benchmark | +| Memory accounting | Creator-attributed committed/resident GEM accounting and capacity admission | Same `memacct::Client`, `File::memory_stats` and shared capacity machinery | Same runtime behavior | `file.rs`, `gem.rs`, `memacct.rs`, `g17/capacity.rs`; verify multi-client high-water and post-close behavior on hardware | +| MCache / few-primitives / ray traversal | Enabled by the 26A428 variant | Disabled by 25G83 variant; source records hangs for few-primitives and stalled ray traversal, while MCache is unqualified | Same runtime behavior | `g17/fw/variant.rs::V25G83`; enabling these requires independent ABI evidence and hardware tests | +| M5 Pro comparison | No M5 Pro hardware configuration exists in this public baseline | No T605x/G17S KMD path to compare | No claim of M5 parity | Pinned public tree contains T6040 and T8140; a licensed public M5 Pro reference is required | +| CTS and Linux/macOS performance ratio | Hardware results belong to their exact kernel/Mesa pair | No result for this candidate | Pending hardware agent | Run gpubench with matching suite, configuration and conditions; review CTS failures and kernel faults before claiming parity | + +## GPU memory demand and interactive latency + +The GFX PMP report entry and PMC performance votes are separate mechanisms. +The existing T6040 `pmp_gfx` soc-device 13 entry follows the powered GPU domain; +`apple_pmp_report_set_state()` publishes a power-state request bit and waits +for its acknowledgement. It does not encode a DCS/fabric performance request. +The GPU firmware owns the PMP's performance demand. Before this series, PMC's +static voter list omitted PMP agent 0, and CLPC lacked a GPU power-state input. +The two existing ports enable PMP agent 0 on T6040's DCS/SOC rails and make +CLPC request the already qualified DCS 5 / fabric 2 floor. G3 incorrectly +used the T6040 supplier lifetime as activity. G4 replaces that input with +DT-selected publication/continuous-idle demand and preserves the existing +32 ms memory-floor release. Prove live +request, enabled-agent and actual-state agreement; supplier binding alone +is not proof of an honoured vote. + +For dispatch latency, G17 now reads the existing `apple,idle-off-delay-ms` DT +policy, validates the existing 2..1000 ms bounds, and starts the existing host +hold with that policy. J616s selects 16 ms, matching the existing effort-idle +interval; other boards without the property retain the native 2 ms behavior. +This avoids power-off across short interactive gaps and releases after real +idle, without changing firmware layout or adding a parameter/platform flag. +KUnit covers absent/valid/invalid policy and repeated depth-one gaps followed +by idle release. A shorter software delay is not itself a measured latency +win: compare actual power transitions, firmware execution, IRQ/retirement and +fence wakeup boundaries. Keep empty CPU fence transfers separate from GPU +round trips and require low-duty/long-idle power acceptance. + +## Remaining 25G83 facts and recovery qualification + +No firmware capability is inferred from another GPU's successful CTS result. +The following are the exact T6040/25G83 facts needed to open the remaining +paths. A reference capture of achieved frequency establishes a target, not +an offset, voltage, descriptor ABI, or Linux residency measurement. + +| Gated path | Exact facts needed for T6040/25G83 | Current evidence and disposition | +| --- | --- | --- | +| Sustained top clock | DT OPP count, both voltage lanes, hardware A/B selector maps, selected logical-state limit; independently decoded achieved frequency or residency, counter units/timebase/wrap, request versus achieved-state semantics and limiting reason under sustained load | Per-chip DT handoff and maps are already mandatory; both maps select the final raw OPP. `max_frequency_khz` is an advertised maximum, not achieved telemetry. G17 `stats_snapshot()` returns None; g4 raw `firmware_reports` and `asahi_g17_report` preserve payloads but establish no decoded residency. Keep voltages/maps unchanged and require a measured Linux load trace. | +| Raw versus logical state count | Meaning of the ADT count properties for this firmware, both SGX map banks and number of entries actually consumed by the 25G83 power tables | Public `HwData25G83`, calibration validation and loader handoff select eleven logical states from sixteen raw OPPs. Do not resize the firmware structure from the ADT property name or discard its unsorted OPPs. Compare actual top selection and achieved clock. | +| DVFS ramp / idle / fast-die | 25G83 meaning and units of ramp/effort controls, idle/off delays, temperature limits and hysteresis; PMP/AP/CLPC request ownership, achieved floors and release under idle and load | Existing shared submission/idle policy, DT suppliers and T6040 power-state graph are enabled. Power-state presence is not proof of load tracking. Require live requests, actual floor release and thermal traces; opaque power words are not tunable parameters. | +| Recovery slot attribution | Exact 25G83 slot-key storage range, lifetime, cache policy and slot stride; key QID/stamp bit fields; slot-mask meanings, progress monitor semantics and association with an actual started render | `recovery_slot_keys=None`; never copy T8140's physical address. Preserve firmware blame. Progress-view reads no longer depend on knowing the key address; bounded diagnostics report unknown ownership explicitly. | +| Recovery table / reason | 25G83 recovery-info offset/extent, valid/guilty/cancelled bits, stamp sequence encoding; reason-code semantics; ordering and reset of records relative to states 1/2/3/0; interrupted-command drain and visibility guarantees | Existing checked 256-entry table and handshake remain unchanged. Reason 3 is treated as page fault by existing attribution. New generation/slot diagnostics aid qualification; missing key evidence cannot clear blame. An aged compute record may no longer substitute for a stamp across recovery. | +| Compute completion / visibility | Exact compute record identity and timestamp fields, stamp write/cache-maintenance order, ring consumption during restart, late/missing-stamp behavior and a release witness for interrupted work | Same-generation 50 ms fallback retained. Crossing recovery now requires a covering stamp for ordinary retirement; quarantine, kill/drain and processor-stop ownership remain. A completion record plus elapsed time after restart is not independent visibility evidence. | +| MMIO pair registration | 25G83 MMIO port addresses and access-power witness; pair registration write order, ACK and coexistence with lead commands/bootstrap QID reservation | Keep disabled. The 26A428 sequence, POWER_ON_DONE message and recovery-target pointer are distinct ABI facts. | +| MCache | 25G83 table-entry format, sizes/alignment, descriptor pointers and enable bits, ownership/coherency rules, partial-render/restart behavior | Keep disabled until a licensed public implementation or independently supplied facts establish the whole contract and tests validate it. | +| Few-primitives | T6040 enable-bit semantics, required auxiliary buffer sizes/registers and partial-store/load/resume handling for all tiling/fragment paths | Keep disabled; previously observed hangs are not grounds to enable it for performance. | +| Ray traversal / preemption | T6040 register program, scratch sizing/alignment by enabled cores/clusters, saved operand-state layout, resume/checkpoint semantics, kill/restart contract, scheduler timer mask and priority profiles | Existing two-by-ten operand layout and priorities retained; periodic timer mask remains zero on both variants. Native traversal remains disabled pending bounded execution/fault/recovery qualification. | +| TLB range operations | T6040 opcode/operand encoding, VA/ASID granules, ordering, completion and negative-translation invalidation across every mapping alias | Existing page TLBI and PoC table cleaning retained. Copying Neo range encoding cannot close a performance gap safely. | +| Large vertex/barrier and tess/geometry faults | Faulting engine/access/VA/VM, exact bound intervals/backings and mapping lifetime; render/compute descriptor identity, TVB/GTP capacities, pass-start/stamp state and recovery reason before acknowledgment | Isolate exact public CTS names with one process and compare kernels with the same Mesa. Buffer size alone does not establish VM-bind, timeout or TVB causality. Retention fix is independently justified, not a claim to have cured a CTS fault. | + +The compute stamp-wait KUnit suite now checks that an aged exact record and +consumed items cannot retire a pre-recovery kick without its stamp, while an +exact/later stamp, busy-ring control and same-generation fallback retain their +expected behavior. Recovery-source tests cover unknown keys, incomplete reads +and page-fault fallback. Diagnostic counters explicitly identify lifetime +stampless totals; they are not a count of commands retired by one restart. + + +## g5 large-target capacity correction + +Public CTS `vulkan-cts-1.4.6.2` uses a 10240-square framebuffer for +`rasterization.primitive_size.points.point_size_10000`. The existing 25G83 +GTP layout appends a sub-buffer of 64 times the tile map after the TPC. +Applying the inherited 32 MiB cache bound to this combined allocation rejects +that valid target with ERANGE, despite its cache and tile record each fitting +their original limits. This is reproducible in the production layout code +without GPU execution. The normalized hardware summary contains ERANGE and +reason-2 recoveries but cannot establish their order or one-to-one association. + +G5 applies the original TPC_MAX to the aligned cache, preserving the original +tile-record bound before allocation or growth. The tile-map bound also caps +the derived auxiliary extent. It changes no firmware buffer-sizing formula, +numeric limit, VA window, generation/retention rule or allocation accounting. +Neo has no auxiliary tail and keeps the same policy. KUnit covers both utile +shapes at the CTS target size, oversized cache/layered-record rejection and +Neo capacity. Fresh and growing targets use the same preflight. Large targets +need their full required metadata through the existing memory/VA mechanisms; +allocation failure still propagates. Hardware execution/readback and post-close +memory/capacity baselines are mandatory acceptance gates. + +The separate 1 MiB vertex/barrier case uses a 256-square target, not a large +framebuffer. Its point count is actual vertex-buffer bytes divided by stride. +It cannot be explained by the large-target capacity guard. Preserve its first +operation/exception, generated sequence and the halted-state TVB/growth/pass +snapshots before choosing a coherence, VM-bind or firmware-timeout fix. The +point-target correction is not a claim that firmware reason 2 is decoded. + +## g5 recovery evidence and host-hop timing + +The shared recovery service previously captured slot keys and monitors at +halted state 1, then called `render_pass_started()` during classification +after acknowledging state 3 to 0. A resumed render could change that pass +state. G5 computes attribution from the state-1 pass snapshot and carries +only that result through the handshake. KUnit drives the real service and +rejects any pass read after the first acknowledgement. Unknown 25G83 keys +still retain firmware blame. This is an independently demonstrated ordering +fix; it does not establish the cause of either deterministic reason-2 fault. + +G5 adds the existing declared TVB state (inventory, page capacity, producer, +consumer, high-water and reset) to the halted snapshot without resetting any +counter. The blamed pair's first four commands include render geometry and +the computed GTP layout, alongside the existing pass/cursor/stamp records. +Pending/active growth and its reply remain visible. The next isolated run +can distinguish exhausted inventory or delayed growth from a pass that never +started; no buffer enlargement or firmware timeout change is justified yet. + +Static-key `asahi_g17_inputs` records compute input conversion entry/result, +source count and publication waits. `asahi_g17_schedule` records enqueue, +inline/fallback, preparation and run boundaries. Together with the existing +publish/complete, DMA-fence and scheduler tracepoints these split host cost +per hop into input resolution, scheduler setup/wait/dispatch and firmware +execution. They make no timing claim until a matched trace is captured. + +The available macOS reference uses 26A428 on T6040 and reports a software +request P10 while hardware residency is 1578 MHz at raw state 14. Linux runs +the admitted 25G83 image. Thus neither a literal P10 conversion nor copying +another firmware's statistics layout proves achieved MHz. The reference +contains aggregate frequency/residency and public DT/calibration facts, +not a Linux 25G83 tag/field decoder. The inspected licensed public G17 source +at `jgoyvaerts/linux` `cc32473a09dd` also has no decoded statistics adapter. +Required input remains a fresh Linux raw report series under labeled idle, +ALU and fill load, and independently established field/timebase/achieved-state +semantics for that exact firmware. No captured raw payload has been promoted +to public implementation data. Achieved clock/power and the CTS cure remain +open; g3's measured memory/latency policy is preserved. + +## g4 shared runtime ports and observability + +The operator has selected g3 as the hardware baseline. This follow-on retains +its CLPC/PMP ports, DT idle hold and recovery-generation release guard. +Individual hardware records remain in the private qualification workspace; +no raw lab capture is a public implementation source. + +| Gap | Public source behavior | T6040 g4 disposition | Qualification | +| --- | --- | --- | --- | +| #504 fragment notification | `de0b22a8370e` selects a render pair on its fragment notification; final retirement still requires both stored stamps | Included for both firmware graphs; KUnit covers each mask bank and unrelated queues | Render-light latency/IRQ rate, growth/partial renders, recovery and CTS | +| #504 tiling first-kick | `5073eac15192` changes the word only for the 26A428 register program | Included unchanged; 25G83 retains 1 on every kick | No T6040 notification-halving claim; requires 25G83 descriptor/notification facts and controlled qualification | +| #496 compute-fault witness | `9a76756bf2d7` uses event 7 words 3/4 as QID/stamp on Neo, with consumed-ring guard | Imported prerequisite, but the existing firmware graph keeps 25G83 event 7 unknown | Exact 25G83 layout, queue identity, stamp semantics and DMA/ring visibility must be established before it can release memory | +| #505 closed-context teardown | `078d744a260c` kills before freeing executed unstamped render graphs, retains firmware-linked work storage, and reuses killed pairs last with QID capacity | Shared safety/retention paths included; compute-fault-driven kill depends on the graph-qualified #496 witness | Closed mixed compute/render faults, acknowledgement ordering, memory pressure and slot/QID reuse; does not claim to cure a live render's first fault | +| #499 long-job sharing | `3db24da5c830` recognizes another published, unobserved long compute job as sharing, without changing numeric renewal bounds | Included in the shared host path; T6040 bounded AS-build classification can use it, while native traversal remains gated | KUnit verifies renewals stop at the existing bound; no claim that this cures a firmware reason-2 render fault or makes skipped traversal CTS pass | +| First fault | Reason and blame alone cannot identify engine progress | At halted state 1, before acknowledgement, g4 logs the blamed pair's stored stamps, free-list balance and up to four commands' pass/cursor snapshots; borrowed pairs are explicitly unavailable | Correlate exact CTS operation and original error with raw event report and snapshots; do not label reason 2 as page fault without semantics | +| Raw firmware statistics | Complete 18-word statistics records already have checked ring geometry | Read-only `firmware_reports` exposes last full sample, count and host monotonic time for each role/type. Static-key `asahi_g17_report` copies trace/statistics/event words before slot return | Match payloads to independently decoded achieved-clock/power/residency and freshness/units; legacy statistics tags are a different ABI | +| Idle memory floor | Neo graphics PMGR follows activity; T6040 GFX is held for firmware lifetime | g4 replaces T6040 PMGR demand with a DT-linked CLPC request, asserted at publication and released by the existing race-checked 16 ms continuous-idle clear; 32 ms CLPC hold remains | Read-only last vote/reason/time, host request and last submission, safe core power/host hold; require blank-panel quiet release and load/release cycles | + +Remaining exact facts for telemetry are the 25G83/26A428 tag-to-layout map, +field width/signedness/endianness, sample/timebase/wrap and freshness, +power units/averaging interval, logical/raw/cluster pstate selection, requested +versus achieved-state semantics, and limiting/thermal reason meanings. Raw +samples and the DT maximum are not MHz. Reads issue no firmware request and +never touch gated core registers. `agx_stats` remains unsupported for decoded +G17 data until those semantics are established. + +For the two deterministic CTS cases, retain the first failing operation before +cleanup, full unnormalized recovery logs, raw event trace, and pre-ack render +snapshots. The large-buffer and extreme-point-size cases can stress tiling, +but neither case name proves a TLB, timeout or TVB cause. No timeout, voltage, +feature-enable or buffer-size guess is included in g4. Multi-queue sync needs +its publish/GPU-span/event/fence timeline; #504's compute control does not +promise to halve that path's latency. + +### T6040 idle-floor release correction + +The PMGR DT explicitly documents that GFX cannot power down while firmware +runs. This is a source-established difference from the Neo CLPC input, so +using GFX as T6040 work demand can never establish idle release. G4 removes +that input on T6040 and adds `apple,clpc` from the GPU consumer to the existing +host performance controller. This is a provider relationship, not a feature +flag or user knob. A requested but unavailable provider defers GPU probe. + +Publication asserts the independent, refcounted request through a leaf lock; +o sleeping callback or allocation runs at submission. Both mutex-backed and +off-lock direct paths are covered. The existing effort clear checks in-flight +work and 16 ms continuous idle, withdraws its flag, then rechecks the direct +submission counter. A racing publisher reasserts demand. The CLPC vote keeps +its existing 32 ms hold; CPU arbitration and Neo's PMGR input are unchanged. +Firmware stop and request destruction clear demand. Provider removal cannot +free storage retained by a late consumer, and a device link orders dependencies. + +CLPC status now shows the last requested floor, reason bits and monotonic +time, plus the host request's activity/update sequence. Actual PMC readback +and write errors remain distinct. Read-only `asahi-g17/power` reports the +existing always-accessible core power witness, host hold, effort and last +publication time, without waking firmware. Generic runtime PM may remain +unsupported because autonomous core gating is firmware-managed; no fictitious +runtime-suspend implementation is enabled. Raw stats still do not decode MHz. + +The production CLPC host suite tests an always-powered supplier with absent, +busy and ended GPU demand, 32 ms hold, CPU coexistence, reason/time and legacy +Neo behavior; removing demand or idle release fails its negative controls. +C KUnit checks actual request lifetime and a late consumer after provider +removal. Hardware acceptance requires a locked/blank panel with publications +observably quiet, release after the hold, then three load/idle cycles and +retention of g3's benchmark gains. An active compositor alone proves neither +quiescence nor a latch. Keep failed measurements and rollback candidates. + +## Source and validation boundaries + +The implementation references are the licensed public Aurora/Asahi kernel and +loader history. Existing Gravity Linux-derived layouts and attribution remain +unchanged. The public Gravity Linux `gravity-m4` reference inspected at +`4fc326f6c25ccceb7515e2635c22e01fde5d3bac` was checked in `g16.rs`, +`g16_runtime.rs` and `g16_power.rs`. Those files do not supply validated +T6040 recovery slot keys; that G16 runtime explicitly leaves compute +non-preemptible. No private implementation, firmware bytes, voltage table or +lab record is included here. + +The build handoff must identify the exact tested source and loader, verify boot +payload checksums, retain r6g rollback, and distinguish CPU/QEMU tests from GPU +execution. Missing J616s measurements are not passing results. + +## Neo KMD improvements on T6040 + +This ledger audits refinements and handling, not just feature parity. It uses +`integration/aurora` at `9cbdd0734d9e186c5f5f5949a21b909d10c2571b` and +`aurora-wip` at `927a4a4480cf90ce8e3b4ab1d1ce58d84bf32fd4`. The conservative +history window starts 2026-09-26 (Neo platform prerequisites), before the public +T8140 firmware interface of 2026-10-03 and hardware bring-up of 2026-10-06. +Committer dates retain rebased older-author changes. The inventory also includes +the complete 105-commit public #171 history, all 65 commits of #423, and the +related PR versions used to identify original contributions and later ports. + +The path log covers `drivers/gpu/drm/asahi`, Apple PMP/PMGR/PMC/CLPC/MCC, +RTKit/mailbox, CPU-frequency/thermal and DART code, related AIC and DRM +scheduler/fence support, Rust DRM interfaces, and Neo DT dependencies. Broad +path/PR neighbors and imported baseline changes are explicitly marked N/A; +they are not represented as measured Neo optimizations. Old/rebased hashes are +listed as encountered aliases of the same subject/class, not asserted to be +byte-identical. The coverage inventory lists 1,551 physical commits (including +imported neighbors and PR-original versions), with 851 distinct subjects; all +65 #423 commits have separate rows. **Active** describes the surviving mechanism on T6040, including +its 7.3 port/refinement, rather than claiming that every historical SHA is an +ancestor. Multi-purpose firmware-introduction records retain their shared +parts while explicitly identifying dormant MCache/native-feature portions. A commit link is its exact diff; the evidence key states the current +reachability/gate. No per-commit speedup is invented from a stack measurement. + +### Applicable ports found by the commit audit + +| Neo mechanism | T6040 in g1 | g2 port and per-chip data | Required acceptance | +| --- | --- | --- | --- | +| #440 GPU-driven CLPC memory/fabric floor | CPU demand only; `gpu_demand=false`, no GPU power-state phandle | `0d63308820ab`: existing GPU-demand mechanism and DT phandle, T6040 DCS 5/fabric 2 and existing 32 ms release; CPU sustain=3 retained | C/R/H/I/P; actual PMGR tracking, floor release, video/browsing and sustained-load power | +| #219 PMP voter cooperation | Static voters 1/3 omit PMP 0 from pre-working-PMP bring-up | `772a9d3104ff`: existing `apple,pmp` supplier dependency, voters 0/1/3, existing T6040 DCS/SOC rail mask; no display rail or voltage change | P/B; PMP bound/ready, live request/honoured floors, idle and thermal behavior | + +Other audited gated paths are listed explicitly below. The T8140 lazy CPU +verification cost is absent on T6040's already-fast-switch profile. MMIO pair +registration, range-only TLB encoding, native RT/few-primitives and optional DCS +cooling cannot be enabled by copying a different firmware/hardware protocol. +The recovery slot-key attribution gate still needs validated 25G83 identity +facts; shared recovery, deadlines and retirement handling remain enabled. + +### Reported measurements and their scope + +These are public PR results, not new J616s measurements. Private qualification +reports were checked for scope and regressions; raw lab records are not copied +here. The older GPU profiling report's cost estimates are not measurements. + +| Evidence | Relevant commits/mechanism | Reported Neo result | J616s confirmation | +| --- | --- | --- | --- | +| [#171](https://github.com/aurora-silicon/linux/pull/171) | Retention, off-lock cleanup, reclaim quiet window and shared lifetime stack | Cold first-submit median 224 to 194–198 us, p99 338 to 295–305 us; menu-launch stalls below 100 ms versus 260–280 ms before quiet-window work. Stack results, not isolated gains. | B/M/C/R, repeated fresh contexts and foreground launch under bounded pressure | +| [#171](https://github.com/aurora-silicon/linux/pull/171) | Completion/recovery isolation | Earlier mixed render/compute lost its device within 2 s; later fixture ran over 568k verified checks in 60 s. Historical 1 ms stamp fallback was subsequently refined to 50 ms. | F/H/T; exact data and fences, peer liveness and no premature mapping release | +| [#423](https://github.com/aurora-silicon/linux/pull/423) | Cumulative scopes and submission stack | Same-boot submit API elapsed roughly 14 to 10 us; throughput stayed near 20 us. Combined candidate C-d64 19.44 us, R-d32 41.48 us, hop 42.12 us; workload/Mesa dependent. | C/R/H; preserve wall-time versus thread-CPU distinction | +| [#423](https://github.com/aurora-silicon/linux/pull/423) | `9bf3039873a1`, completion identity index | Scanner self samples 4.87% to 2.24% at depth 64, 3.16% to 1.93% on hops. CPU profile result. | O/C/H | +| [#423](https://github.com/aurora-silicon/linux/pull/423) | `26446182da3a`, 128-interval validation working set | C-d64 API elapsed 10.06 to 7.46 us; R-d32 12.29 to 8.16 us. Not a thread-CPU clock measurement. | C/R/D with rotating 64 buffers and remap/permission invalidation | +| [#423](https://github.com/aurora-silicon/linux/pull/423) | `b6248acf900c`, private-fence fast status | Submit API median 7.52 to 5.79 us; frontier samples 1.83% to 0.25%; throughput remained near 19.5 us. | C/H/O; errors, foreign inputs and delayed host publication | +| [#440](https://github.com/aurora-silicon/linux/pull/440) | `d69f14f4e4fb`, GPU fabric floor | C-d1 158.9 to 108.2 us; C-d64 19.3 to 14.7 us; R-d32 42.7 to 29.7 us; hop 42.2 to 31.9 us. Same driver/Mesa. | P/C/R/H, compare g1/g2 and keep heavy/idle checks | +| [#219](https://github.com/aurora-silicon/linux/pull/219) | GFX report/PMP vote and PMC enables | Short manual-bit glmark2 build experiment: 1453 with GFX report off to 2199 with it on; 2218 with all interface enables. Single-run 7–10 s experiment, not an isolated final-kernel A/B. | P/R; distinguish reported request, enabled agent and honoured floor | +| [#443](https://github.com/aurora-silicon/linux/pull/443) | Lazy verification and fast switch | C-d2 58.8–59.7 to 46.4–47.3 us; C-d1 107.8–108.4 to 102.6–102.7 us. T8140-specific spin removed. | C/I observation only: T6040 already lacks that spin | +| [#413](https://github.com/aurora-silicon/linux/pull/413) | Existing optional idle hold | The roughly 1.1 ms wake and 1.2–1.4 ms tiny-render-after-gap observations are **M4 Pro**, not Neo measurements. Default 2 ms adds no extended hold. | I at defaults; optional hold is a separate policy experiment | + +The provided current Neo Linux/macOS report has no completed paired Linux +record, so an overall Neo margin is still undefined. Historical PR ratios do +not establish the requested current all-workload margin. Use matched suite, +Mesa, clock/power conditions and repeats. Run only workloads admitted by the +controller; queue depth is not permission for extra process workers. + +### Reachability and gate evidence keys + +| Key | Current source/gate and interpretation | +| --- | --- | +| SHARED — Shared G17 execution | driver.rs selects g17::Gpu for T6040. g17/queue/mod.rs, runtime.rs, port.rs, runtime/render/direct.rs and runtime/render/submit.rs have no T8140-only gate on these mechanisms. The variant supplies QID floor 32, producer +0x50, two-cluster operands and 25G83 lead commands. | +| DRM — Shared DRM/scheduler/fences | G17 queues use job.rs, fence.rs and the common DRM scheduler/syncobj/GPUVM/shmem interfaces. CPU_FREQ and UCLAMP_TASK are enabled in the release config. Private cumulative scopes are advertised on both G17-backend targets at feature bit 29; foreign fences retain generic handling. | +| MEM — Common memory and lifetime | file.rs::memory_stats, gem.rs, memacct.rs, cleanup.rs, pgtable.rs and mmu.rs serve the T6040 G17 backend. Creator accounting, pins, revocable backing, canonical bindings and deferred destruction are not gated to T8140. T6040 keeps its explicit UAT coherency policy. | +| GMEM — Shared retained GPU resources | g17/freelist.rs, context.rs, object.rs, queue/compute.rs and runtime/render/pools.rs retain and reclaim the same resource owners. Allocations use T6040 core-layout sizes and its reduced QID budget; no Neo-only admission branch disables them. | +| REC — Shared recovery and deadlines | g17/recovery.rs, recovery/safety.rs, timeout.rs and runtime/events.rs serve both firmware variants. T6040 uses its status-B base and one firmware role. Exact retirement, interrupted-work failure, withheld-doorbell replay, progress-aware budgets and sticky VM reporting remain active. Current missing-stamp fallback is 50 ms, not the historical 1 ms. | +| POWER — Shared feed/effort/idle code | g17/feed.rs is instantiated for T6040 and derives CPU floors from each discovered policy maximum. g17/mod.rs::note_submission/idle_check and power.rs share effort 1.0 while work streams and a 16 ms idle clear. The existing optional idle hold is available on T6040 but its default 2 ms adds no extended hold. | +| TIMESTAMP — Shared timestamp publication | timestamp.rs::FirmwareBuffer retains a CPU vmap for the buffer lifetime; g17 completion publishes through it before signalling. T6040 uses this shared firmware-buffer path and its 24 MHz timestamp conversion, not a per-command vmap. | +| PRIORITY — Shared firmware priority profiles | g17/mod.rs::scheduling_policy selects HIGH/MEDIUM/LOW profiles for both targets; file.rs checks CAP_SYS_NICE or DRM master for HIGH and refuses REALTIME. Priority work-ring selection and QoS ownership are shared. | +| FW — Per-chip firmware adaptation | hw/t6040.rs selects V25G83. g17/initdata/v25g83.rs supplies one merged root and status acknowledgement, per-chip OPP/calibration input, separate views and no POWER_ON_DONE. Pointer and operand layouts are selected from Config/Variant rather than copied from the two-instance 26A428 graph. MCache is explicitly gated by V25G83.mcache=false; its firmware table/protocol is not part of admitted T6040 support. | +| PMGR — Shared PMGR state handling | pmdomain/apple/pmgr-pwrstate.c binds T6041 states through the t8103-compatible state protocol. BUSY masking, timeout unwind and firmware-owned/no-auto-PM properties apply through DT; no T8140-only register address is copied. | +| PMP — PMP/report per-chip data | soc/apple/pmp.rs and pmdomain/apple/pmp-report.c already contain T6041 match data: 42-bit DMA, version-5 bootargs checks, CoreAnalytics EP7, T6041 report offsets and three fast-die lanes. T6040 DT supplies the bootargs/report entries and CPU effort phandles. T8132/J713-only telemetry policy is excluded separately. | +| PMC — Shared PMC, T6040 rail mask | pmdomain/apple/pmc.c::apple_pmc_probe/agent_enable use T6040 agent stride, DCS maximum 5 and agent_rails=GENMASK(1,0). CPU/CLPC/AP voting already worked. Port 772a9d3104ff adds apple,pmp and static voter 0 after its supplier binds; display rails remain excluded. | +| CLPC — Shared CLPC, T6040 demand data | soc/apple/clpc.c uses T6040 DCS {1,2,5,5}, fabric {0,1,2,2}, sustain=3, 4 ms sampling and 32 ms hold. CPU demand/lifecycle refinements are shared. GPU demand was missing and is enabled by 0d63308820ab with the existing apple,gpu-power-state phandle. | +| GPUFLOOR — Applicable gate ported | Before g2, apple_clpc_hw_t6040.gpu_demand was false and its CLPC DT node had no apple,gpu-power-state. Port 0d63308820ab enables the same mechanism as #440 with T6040 DCS 5/fabric 2, not Neo 4/3. Hardware gain and idle/PMP cooperation remain unqualified. | +| MCC — MCC translated to 16 planes | soc/apple/mcc.c accepts the T6041/T8140-compatible controller; T6040 DT describes 16 planes and its display boot-DMA dependency. The J616s enables this node. Generic partial-enable unwind, lifetime and SLC allocation rules apply with that data. | +| CPU — Shared CPU frequency/limit handling | T6041 CPU nodes bind the t8112 fallback profile. Generic OPP cleanup, fast-switch and PMP effort QoS use its DT frequencies and three effort lanes. No Neo frequency or thermal curve is copied. | +| LAZY — Different CPU verification protocol | The lazy_verify refinements are T8140-only. T6040 binds soc_t8112_info, whose verify_transition is false; policy->fast_switch_possible is already true. It does not pay the T8140 blocking verification loop that these commits remove. Adding lazy verification would not port a missing gain. | +| DART — Related DARTs; native GPU uses UAT | iommu/apple-dart.c common publication/error/resume/stream logic is used by T6041 DARTs where their DT ownership mode selects it. T6040 display mirroring uses its own compatible/data. Native GFX translations use asahi/mmu.rs UAT, so this is related PMP/display robustness, not a claim that Neo DART addresses fit the G16 GPU. | +| RTKIT — Shared RTKit transport/lifetime | G17 Coprocessor uses the Rust RTKit wrapper over soc/apple/rtkit.c. Shared buffer retention, barriers, crash handling, bounded logs and shutdown ownership apply. T6040 supplies its firmware-data crash buffers and one ASC instance; MTP/J713-only adoption is excluded. | +| MBOX — ASCWrap transport, per-chip registers | soc/apple/mailbox.c and rust/kernel/soc/apple/mailbox.rs provide the common queue, IRQ and receiver ownership rules. T6040 binds its ASCWrap-v6 data; its wrapper/VBAR windows and IRQs come from DT. Neo-specific mailbox addresses are not reused. | +| AIC — Shared effective-affinity reporting | irq-apple-aic.c applies AICv2+ reporting to the T6041 AIC3 compatible too. It reports actual effective routing rather than manufacturing CPU migration. Record the IRQ/worker CPU on J616s; no isolated throughput gain is claimed. | +| DISPLAY — Related shared display hardening | Generic apple/dcp.c, parser and AFK lifecycle/bounds fixes are inherited by the reconstructed display stack where those helpers are used. They protect the display consumer of GPU buffers; they are not standalone compute performance claims. | +| OBS — Observability only when selected | Existing G17 tracepoints/diagnostics and first-failure logging are shared. Trace register sampling is conditional on the tracepoint being enabled; disabled tracing is not a new runtime policy or a measured throughput gain. | +| MMIO — Different registration ABI | runtime/render.rs::install_pair returns before registration power holds when V25G83.mmio_pair_registration=false. 25G83 installs from queue configuration and uses lead commands; 26A428 uses MMIO pair registration. Do not add its two control round trips or register writes to the admitted 25G83 protocol. | +| FEW — 25G83 rejects this fast path | V25G83.few_primitives=false; command.rs and feature reporting enforce it. The supported-source comment records a fragment hang with the flag. It is not an applicable refinement of the admitted 25G83 render ABI without new validated facts. | +| RT — RT specialization not admitted | V25G83.ray_traversal=false; command.rs rejects native scratch/intersection programs and get_params omits the feature. Generic watchdog/recovery infrastructure remains shared, but native RT pools/kill guards/RT-specific long-job use are dormant on the admitted 25G83 path. | +| KEY — Missing validated recovery identities | hw/t6040.rs has recovery_slot_keys=None. recovery/attribution.rs falls back to firmware blame when slot identity evidence is incomplete. Neo slot-key addresses must not be invented for 25G83; this specific attribution refinement remains gated pending validated T6040 facts. | +| UAT — Qualified alternate invalidation policy | hw/t6040.rs keeps reserved_tables, clean_tables and page_tlbi=true. mem.rs::tlbi_range takes the page/ASID alternative before its range encoder. The range-only field fix is dormant; per-SoC coherency/ownership support is active. Removing these working firmware-table requirements is not a justified performance port. | +| DCS — Optional cooling actor, no T6040 curve | PMC cooling/IPA paths require #cooling-cells and power-model DT data. J616s does not select them, and the Neo DCS thermal zone was subsequently removed because it throttled unrelated GPU/CPU heat. No T6040 power curve is invented to re-enable that policy. | +| DCSOFF — Already equivalent inactive zone | Neo removed its oscillating DCS thermal zone. J616s has no such zone to remove. Its PMP CPU/GPU limits remain; this does not justify disabling thermal protection. | +| IRQENTRY — DT-selected report context | IRQ-safe report entries are selected by apple,irq-safe for PMGR-parent use. GFX report consumption does not require copying the external-display parent topology; use the actual T6040 entry/consumer hierarchy. | +| STAT — Not the Neo backend | StatsChannel/GpuManager agx_stats decoding belongs to the maintained backend. g17::Gpu::stats_snapshot returns None on both Neo and T6040. It is not a Neo optimization waiting to be ported to G16S. | +| G15 — Different backend/hardware | T6030/T6031/T8122/G15 and M3-only runtime commits do not execute in T6040 g17::Gpu. Their firmware records, generation-specific power policy and native-G15 admission cannot be copied into G16S/25G83. | +| G16 — Different G16 backend/owner ABI | T8132/J713 uses the separate g16 backend and retained-owner PMP/TVM protocol. T6040 is G16S but dispatches to the g17 backend with V25G83, not that G16G runtime. Match/board gates and firmware ownership differ. | +| LEGACY — Maintained G13/G14 path | The workqueue/manager/versioned AGX firmware path is not T6040's G17 backend. G13/G14 automatic preemption, timestamp-copy and old InitData changes are not missing G16S mechanisms. | +| OTHERCHIP — Other CPU/guest hardware | T8142/T8152/T6050-specific transition, ownership or guest-timer data is not selected by T6041 CPU/GPU match data. No unsupported register/profile change is ported. | +| TUNNEL — Other peripheral topology | M1 tunneled PCIe/Thunderbolt recovery is not a Neo native-GPU improvement. T6040 has its own topology; these records are retained for coverage, not presented as a gated G16S optimization. | +| PERIPH — Not a GPU refinement | This scoped-history/PR neighbor concerns NVMe, HID, audio, SEP, USB, RTC or another peripheral, not a Neo native-GPU performance/handling mechanism. No port is implied. | +| BOARD — Neo-specific board route | The literal J700 topology/display source/domain-cell fix is not T6040 board data. Its independent J616s display/PMP/GPU composition is retained; copying a Neo route or physical address would be wrong. | +| RAWDIAG — Superseded raw diagnostics | The raw T8140 PMP DVFS debugfs dump was later removed. Current reporting uses validated per-SoC fields; no new raw interface or platform knob is added. | +| OLDTHERMAL — Superseded thermal policy | The interim J700 SMC CPU policy was removed/replaced by thermal-framework and PMP handling. It is not the policy selected for T6040 and no Neo temperature/power curve is copied. | +| SUPERSEDED — Superseded inside the series | The unconditional-doorbell/unbound-inline policies were refined by later #423 commits; the early disabled-J616s admission was replaced by calibrated #463 enablement. Current code preserves low-pressure probes and defers/coalesces eligible doorbells with explicit release/stall/recovery handling; do not reapply the obsolete intermediate policy. | +| VERIFY — Build/test/documentation only | This record changes tests, descriptions, bindings, layout/build checks or source style rather than a runtime performance mechanism. Its contract is retained where applicable; there is no isolated hardware speedup or new platform behavior to port. | +| MERGE — Integration record | This merge is provenance/composition, not an isolated performance mechanism. Its parent changes are enumerated below, including variant preservation and subsequent fixes; no separate speedup is assigned to a merge. | + +### Hardware measurements referenced by each commit + +All hardware actions require the controller's explicit J616s identity, policy +approval and ownership/session/resource gates. These are a measurement plan, +not permission to bypass them. Pair g1/g2 with the same loader and Mesa and +preserve failed runs, logs and r6g rollback. + +| Code | Measurement | +| --- | --- | +| C | compute.light d1/d2/d64; submit API wall and thread-CPU time, scheduler hops, faults and CPU-frequency/IRQ context; matching Mesa/suite. | +| R | render.light d1/d2/d32 and 8 queues, first render, heavy fill and tiler growth; API p50/p99, throughput and memory high-water. | +| H | same/cross-queue binary and timeline hops; p50/p95/p99, doorbells per command and 100k order/data checks; include eligible and fallback dependencies. | +| I | idle.wake after 2/20/100 ms, default policy, first-frame latency and clocks; 60/120 Hz frame pacing and idle power. Keep optional hold experiments separate. | +| F | controller-approved bounded stall/fault/long-job stress with innocent peers; no wrong data or false successful fence, recovery phase/time, VM errors and post-close reuse. | +| M | multi-client create/close/rearm and approved bounded pressure; committed/resident bytes, retained pools/QIDs, restoration time and leak slope. | +| P | PMP readiness, PMC per-agent requests/honoured floors and CLPC readbacks during GPU-only, mixed CPU/GPU, 90 s heavy load, idle, video and browsing; verify idle release and no sustained thermal regression. | +| T | timestamp/query correctness, 24 MHz conversion, aligned/unaligned publication where supported, buffer-map reuse and query CPU cost. | +| Q | privileged/unprivileged priority admission and concurrent LOW/MEDIUM/HIGH progress, global-priority transition cases and timeout attribution. | +| D | approved bounded VM_BIND/unmap/remap and DART ownership/resume boundaries; CPU cost, invalidation errors and zero unexpected DART/UAT faults. | +| B | exact boot/kernel/loader/Mesa pins, firmware admission, first queue/install, panel/display regression, supplier readiness and rollback. | +| O | existing tracepoints/counters under approved instrumentation; IRQ and event-worker CPU, pass duration, notifications and failure site. Tracing off remains the reference. | +| V | static/build/contract or capability-refusal check; no isolated hardware improvement claimed. | + +### Every commit in #423 + +All 65 entries are retained, including intermediate policies, tests and the +integration merge. Superseded steps are not mistaken for current behavior. + +| Commit | Author | Improvement / record | T6040 after ports — evidence key and changed source | Measurement | +| --- | --- | --- | --- | --- | +| [ad691cd5e9f7](https://github.com/aurora-silicon/linux/commit/ad691cd5e9f76aa9e486d93b560c58d5c8ff88c4) | DjDeveloperr | drm/asahi: publish bound G17 compute work without the admission passes [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | C,H | +| [1121ae36b8d4](https://github.com/aurora-silicon/linux/commit/1121ae36b8d426be24c41ae360281b5f0709a69d) | DjDeveloperr | drm/asahi: wake G17 render publication when its pair is returned [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | R,H | +| [abd85935bd4d](https://github.com/aurora-silicon/linux/commit/abd85935bd4db7db6be23b8194b1173c9a8988c5) | DjDeveloperr | drm/asahi: clear each G17 compute descriptor only once [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/compute.rs` | C,H | +| [3ef3720890f8](https://github.com/aurora-silicon/linux/commit/3ef3720890f8c085f18e4f5e35db6853075b121c) | DjDeveloperr | drm/asahi: retain compute pool backing through pending channel admission [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — GMEM; `drivers/gpu/drm/asahi/g17/capacity.rs` | M,C,R | +| [f35d7ea1b373](https://github.com/aurora-silicon/linux/commit/f35d7ea1b37395a9bc1241d0fafcb698e0c12d46) | DjDeveloperr | dma-buf: stop polling an array after its first pending member [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — DRM; `drivers/dma-buf/dma-fence-array.c` | C,H,M,F | +| [319805fed41b](https://github.com/aurora-silicon/linux/commit/319805fed41bb181db8e90378beaf905f38f6297) | DjDeveloperr | drm/asahi: avoid unbound compute inline attempts on compute-only queues [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — SUPERSEDED; `drivers/gpu/drm/asahi/g17/context.rs` | C,H | +| [5a4822c6fb3d](https://github.com/aurora-silicon/linux/commit/5a4822c6fb3d1fc0a83d9b754b477bf1db35d782) | DjDeveloperr | drm/asahi: preserve low-pressure compute inline probes [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | C,H | +| [026065c43526](https://github.com/aurora-silicon/linux/commit/026065c435260844e39d05a182e99b128c5f9b2d) | DjDeveloperr | drm/asahi: submit same-VM compute input waits through firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/capacity.rs` | C,H | +| [89c8873651b7](https://github.com/aurora-silicon/linux/commit/89c8873651b729fdd4e981e87b9d0df0d0ad0017) | DjDeveloperr | drm/asahi: treat a firmware source as complete once its retirement is queued [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | C,R,H | +| [3ff4f4baa05a](https://github.com/aurora-silicon/linux/commit/3ff4f4baa05a88d5f99d2fee76089cf72b5617a4) | DjDeveloperr | drm/asahi: publish G17 commands from the ioctl at any queue depth [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [a0dfc3c0b5a4](https://github.com/aurora-silicon/linux/commit/a0dfc3c0b5a4fced66119fded07ed141990a9025) | DjDeveloperr | drm/asahi: map G17 compute descriptors cached for the firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [a36e352d8564](https://github.com/aurora-silicon/linux/commit/a36e352d8564947071fef7e11dba709f399cd106) | DjDeveloperr | drm/asahi: size the G17 compute QID budget by open logical queues [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/kick.rs` | C,H | +| [b141ca9b16c3](https://github.com/aurora-silicon/linux/commit/b141ca9b16c33abb26238782cd7c0b751b5f52fe) | DjDeveloperr | drm/asahi: order same-VM render attachment overlaps in firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | R,H | +| [a475f4f9f1f7](https://github.com/aurora-silicon/linux/commit/a475f4f9f1f721b6636a4147dacbcf9f6224763b) | DjDeveloperr | drm/asahi: map G17 compute command records cached for the firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [ca71e4449b4a](https://github.com/aurora-silicon/linux/commit/ca71e4449b4a23d5f4e0149db76cf5198bfa7e5f) | DjDeveloperr | drm/asahi: stop the G17 compute completion scan once every record is found [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | C,H | +| [1cdac9ba0ddd](https://github.com/aurora-silicon/linux/commit/1cdac9ba0dddec96f59eb02a5067ee343aae43f4) | DjDeveloperr | drm/asahi: publish G17 compute into a bound queue without the device mutex [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/context.rs` | C,H | +| [7242bf87efd2](https://github.com/aurora-silicon/linux/commit/7242bf87efd2b3f4f8bf2a31fadb79b3e687f195) | DjDeveloperr | drm/asahi: send a G17 compute queue configuration only when it changes [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [014605a36795](https://github.com/aurora-silicon/linux/commit/014605a3679581351913c3ab137653297f7a0cf7) | DjDeveloperr | drm/asahi: add G17 resource wait domains and use them for queue creation [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/availability.rs` | C,R,H | +| [64f717dcfd1e](https://github.com/aurora-silicon/linux/commit/64f717dcfd1e4aa5856882181f5c2ac040c5ce4e) | DjDeveloperr | drm/asahi: wait for G17 compute capacity on the release that frees it [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/availability.rs` | C,H | +| [327e50fc45b5](https://github.com/aurora-silicon/linux/commit/327e50fc45b57acf1954e8c2d78977198286feb0) | DjDeveloperr | drm/asahi: wait for a G17 compute source only through its completion record [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | C,H | +| [6f4d3bbb47c1](https://github.com/aurora-silicon/linux/commit/6f4d3bbb47c1a7b19abef118b41b131440a8f414) | DjDeveloperr | drm/asahi: add G17 command lifecycle tracepoints [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — OBS; `drivers/gpu/drm/asahi/g17/mod.rs` | O,F | +| [e24e3bad9e7d](https://github.com/aurora-silicon/linux/commit/e24e3bad9e7d3c6cf7079d683c9364d619cd3a55) | DjDeveloperr | drm/asahi: ring the G17 compute pipe doorbell for every publication [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — SUPERSEDED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [94c66ecf24e6](https://github.com/aurora-silicon/linux/commit/94c66ecf24e6d931a434914d6a513ee36ff6ffd7) | DjDeveloperr | drm/asahi: map the G17 compute queue graph cached for the firmware [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [db365ecc299f](https://github.com/aurora-silicon/linux/commit/db365ecc299f0663db8a629e70dfdb4d117eb95e) | DjDeveloperr | drm/asahi: wait for G17 render capacity on the release that frees it [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/context.rs` | R,H | +| [06bc290c266a](https://github.com/aurora-silicon/linux/commit/06bc290c266abcb8b0d45f75c3369e231ae8c4a7) | DjDeveloperr | drm/asahi: give the G17 render scheduler the pair's depth as credits [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/job.rs` | R,H | +| [3d1f2047766c](https://github.com/aurora-silicon/linux/commit/3d1f2047766c2d6e8d165a4af2cfe4adc0edd366) | DjDeveloperr | drm/asahi: keep a G17 render free list registered while the firmware is busy [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/freelist.rs` | R,H | +| [b7fa0d482eea](https://github.com/aurora-silicon/linux/commit/b7fa0d482eea670f9004422eaf57703a7ec8d4e5) | DjDeveloperr | drm/asahi: take the G17 device mutex less often to publish a render [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/admission.rs` | R,H | +| [d272a7060d20](https://github.com/aurora-silicon/linux/commit/d272a7060d20a0beb635ca8426c8822b8e823a43) | DjDeveloperr | drm/asahi: publish a G17 render into its pair without the device mutex [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | R,H | +| [6428f7fd42fe](https://github.com/aurora-silicon/linux/commit/6428f7fd42fe075981984da569b0fa076f59a127) | DjDeveloperr | drm/asahi: lend a G17 render pair without the identity check first [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | R,H | +| [8cf02f589bce](https://github.com/aurora-silicon/linux/commit/8cf02f589bce34b13002cbdf0f3b0361c4a8e66b) | DjDeveloperr | drm/asahi: wait for G17 render admission before run_job [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/admission.rs` | R,H | +| [550fb355e9a3](https://github.com/aurora-silicon/linux/commit/550fb355e9a30c0db9c59fe35a931f1db9c6b5c4) | DjDeveloperr | drm/asahi: trace the G17 core power state at each work doorbell [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — OBS; `drivers/gpu/drm/asahi/g17/mod.rs` | O,F | +| [f381ac9b71eb](https://github.com/aurora-silicon/linux/commit/f381ac9b71ebcb70eb72497ec4115553856a31fc) | DjDeveloperr | drm/asahi: trace G17 host-to-firmware messages [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — OBS; `drivers/gpu/drm/asahi/g17/channel.rs` | O,F | +| [adcd56e02036](https://github.com/aurora-silicon/linux/commit/adcd56e020362664c7bc3af288e1ad812df7b4c7) | DjDeveloperr | drm/asahi: maintain cumulative completion scopes without repeated fence merges [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — DRM; `drivers/gpu/drm/asahi/g17/fence.rs` | C,H,M,F | +| [bcdfdafd80cf](https://github.com/aurora-silicon/linux/commit/bcdfdafd80cf93fadc79df688dd0109b9c4ae8d2) | DjDeveloperr | drm/asahi: test G17 queue budgets with firmware-reserved identities [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/kick.rs` | V | +| [971d4316d91c](https://github.com/aurora-silicon/linux/commit/971d4316d91cce97b92db72c4f29e5155b76d626) | DjDeveloperr | drm/asahi: size cached compute records for every native wait [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [9bf3039873a1](https://github.com/aurora-silicon/linux/commit/9bf3039873a1e241991b5999aceff5610c660ade) | DjDeveloperr | drm/asahi: index pending compute completion identities before reading payloads [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs` | C,H | +| [26446182da3a](https://github.com/aurora-silicon/linux/commit/26446182da3aa8d98ce799478a025117b021eaba) | DjDeveloperr | drm/asahi: retain a deep working set of validated GPU page ranges [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/validation.rs` | C,R,D | +| [b6248acf900c](https://github.com/aurora-silicon/linux/commit/b6248acf900c1cf30deac52791c2c50d10055d7b) | DjDeveloperr | drm/asahi: avoid locking pending private completion fences [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/job.rs` | C,R,H | +| [b08c81722d89](https://github.com/aurora-silicon/linux/commit/b08c81722d896eee8da9771e99e38e6986a2a806) | DjDeveloperr | drm/asahi: give G17 firmware restarts time to drain long work [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/recovery.rs` | F,Q | +| [f1db16a83586](https://github.com/aurora-silicon/linux/commit/f1db16a83586e4bec596a71e12e0d8b87dd3b16c) | DjDeveloperr | drm/asahi: fail commands a GPU restart interrupted instead of completing them [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [30f85560fd28](https://github.com/aurora-silicon/linux/commit/30f85560fd28ce05151c16eab7a58433d125a81e) | DjDeveloperr | drm/asahi: stop the G17 device on a watchdog storm only without progress [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [2c0abe2d090a](https://github.com/aurora-silicon/linux/commit/2c0abe2d090a9701b13b22d5ac56fb24d87cfe12) | DjDeveloperr | drm/asahi: scan only the G17 compute completion records written since the last pass [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | C,H | +| [39bdd5b8e2d5](https://github.com/aurora-silicon/linux/commit/39bdd5b8e2d584fff0d342a71a8945e64c8a9673) | DjDeveloperr | drm/asahi: release pruned G17 frontier entries after the ioctl publishes [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [b41ecb56c077](https://github.com/aurora-silicon/linux/commit/b41ecb56c0775a43b747adaa37661b353651dbff) | DjDeveloperr | drm/asahi: publish both G17 render QoS owners in one pass [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/port.rs` | R,H | +| [b86c445ba282](https://github.com/aurora-silicon/linux/commit/b86c445ba2829f644075ca663217abe65844e8c0) | DjDeveloperr | drm/asahi: stop copying G17 fragment registers to the unused client page [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render/memory.rs` | C,R,H | +| [5a55068193e2](https://github.com/aurora-silicon/linux/commit/5a55068193e213d7115a63f2222ffc304a4ced98) | DjDeveloperr | drm/asahi: read the G17 render work rings once per direct publication [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/port.rs` | R,H | +| [b67018ed5974](https://github.com/aurora-silicon/linux/commit/b67018ed5974069c13c13249834dbc3b84e806ed) | DjDeveloperr | drm/asahi: request the G17 tiling descriptor slot before preparing a render [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render.rs` | R,H | +| [4d0362d0c18d](https://github.com/aurora-silicon/linux/commit/4d0362d0c18d288d3bd520fdfb69c2e7624cf6b0) | DjDeveloperr | drm/asahi: store G17 register arrays without an intermediate copy [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/kick.rs` | C,R,H | +| [861b39d948f6](https://github.com/aurora-silicon/linux/commit/861b39d948f6c0588b3646e1246e660b52230e5b) | DjDeveloperr | drm/asahi: also request the G17 queue configuration records early [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render/memory.rs` | C,R,H | +| [66ef051d8bd4](https://github.com/aurora-silicon/linux/commit/66ef051d8bd4e66bdc145eb7fb7a920235a8e668) | DjDeveloperr | drm/asahi: scan only render pool slots for G17 free-list leases [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — GMEM; `drivers/gpu/drm/asahi/g17/runtime/render/pools.rs` | M,C,R | +| [1aec5be232da](https://github.com/aurora-silicon/linux/commit/1aec5be232da7bacbbe38b7f9b22c27906eac8c6) | DjDeveloperr | drm/asahi: omit G17 render queue configurations that only reload the held completion [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render.rs` | R,H | +| [d0df244b8f02](https://github.com/aurora-silicon/linux/commit/d0df244b8f023037bb0f9cc9793f1d0f285cfc87) | DjDeveloperr | drm/asahi: keep one CPU mapping per G17 timestamp buffer [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/completion.rs` | T,C | +| [3ff8c49c5f12](https://github.com/aurora-silicon/linux/commit/3ff8c49c5f12b908a72e6c076ff0d7336ffddcb8) | DjDeveloperr | drm/asahi: withhold G17 compute doorbells behind two unretired commands [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [b1f5dc6df0ba](https://github.com/aurora-silicon/linux/commit/b1f5dc6df0ba5650c8424027a025db233c6544c4) | DjDeveloperr | drm/asahi: drop G17 compute doorbells that another doorbell announced [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/port.rs` | C,H | +| [d38cce2d8be5](https://github.com/aurora-silicon/linux/commit/d38cce2d8be54bdbc01e90e1a92ef3dd65ac0640) | DjDeveloperr | drm/asahi: announce withheld G17 compute doorbells whenever retirement stalls [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [2d8286101c86](https://github.com/aurora-silicon/linux/commit/2d8286101c8631939cb13ce1b7b14c9041856ca6) | DjDeveloperr | drm/asahi: build the G17 driver without warnings [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | V | +| [9a0ed807e4d3](https://github.com/aurora-silicon/linux/commit/9a0ed807e4d3691a5ea46fce2a2424295f8af6ea) | DjDeveloperr | drm/asahi: renew G17 job deadlines while the GPU keeps retiring work [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/job.rs` | F,Q | +| [afbfbc0b89ca](https://github.com/aurora-silicon/linux/commit/afbfbc0b89ca4779b13cefff5bed0b4036751b2c) | DjDeveloperr | drm/asahi: kill closed G17 contexts only while no other work runs [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs` | F,Q | +| [31279abf0648](https://github.com/aurora-silicon/linux/commit/31279abf0648c00e454b181f01d65b277a32fb90) | DjDeveloperr | drm/asahi: fail G17 render groups a recovery dropped out of order at once [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/render.rs` | F,Q | +| [7012a59ed129](https://github.com/aurora-silicon/linux/commit/7012a59ed129baf969d83a3aa4d90f2a6cb410f2) | DjDeveloperr | drm/asahi: announce withheld G17 compute doorbells after a recovery [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — REC; `drivers/gpu/drm/asahi/g17/recovery.rs` | F,Q | +| [8e257af33a6c](https://github.com/aurora-silicon/linux/commit/8e257af33a6c4036ee56bc8a628e733dba2bd525) | DjDeveloperr | drm/asahi: move the G17 cumulative-signals feature to bit 29 [#423](https://github.com/aurora-silicon/linux/pull/423) | Active — SHARED; `drivers/gpu/drm/asahi/g17/sync_output.rs` | C,R,H | +| [76d4e880149e](https://github.com/aurora-silicon/linux/commit/76d4e880149e5592396001f728f7803efb616377) | DjDeveloperr | drm/asahi: build the G17 compute QID budget test against the variant constructor [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/runtime.rs` | V | +| [d0da7e79dc9c](https://github.com/aurora-silicon/linux/commit/d0da7e79dc9c99020d109e95d18256cd5b3d6bc2) | DjDeveloperr | drm/asahi: check the G17 QID floor test against the open-queue compute budget [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/runtime.rs` | V | +| [18b408b7368b](https://github.com/aurora-silicon/linux/commit/18b408b7368b9470c22d92c9afe1991d0fe608ba) | DjDeveloperr | drm/asahi: check the G17 admission churn test against its deadline, not the clock [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/preparation.rs` | V | +| [de1be1b4b5b1](https://github.com/aurora-silicon/linux/commit/de1be1b4b5b110ae60877f77aa1637a920333579) | DjDeveloperr | Merge integration/aurora into fix/g17-recovery-dropped-groups-20261025 [#423](https://github.com/aurora-silicon/linux/pull/423) | Not applicable — MERGE | V | + +### Other Neo and shared-support commits + +Each listed alias has the stated current mechanism/gate. Device-specific data +uses the T6040 counterpart, not literal Neo register/DT values. + +| Commits / encountered aliases | Author | Improvement / record | T6040 after ports — evidence key and changed source | Measurement | +| --- | --- | --- | --- | --- | +| [6a5d96e61fa5](https://github.com/aurora-silicon/linux/commit/6a5d96e61fa52810e709a0b8bb5e9988df78a7d7) | Sasha Finkelstein | cpufreq: apple-soc: Calculate frequency as a 64-bit value | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [a861779b92c2](https://github.com/aurora-silicon/linux/commit/a861779b92c259c0a5ba7ec3fd512ad7bda1b8d4) | Asahi Lina | rust: soc: apple: rtkit: Add Apple RTKit abstraction | Active — RTKIT; `drivers/soc/apple/Kconfig` | B,F | +| [4b42187aef53](https://github.com/aurora-silicon/linux/commit/4b42187aef53c266b472e731e4f8e0a1fe0de3a7) | Janne Grunau | rust: soc: apple: rtkit: Add ASC CPU control register | Active — RTKIT; `rust/kernel/soc/apple/rtkit.rs` | B,F | +| [6a2c57b5a719](https://github.com/aurora-silicon/linux/commit/6a2c57b5a719f7c71fe6252865e4f036f6d27a64) | Sasha Finkelstein | soc: apple: rtkit: Add apple_rtkit_has_endpoint() | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [b7fbb2fde4ea](https://github.com/aurora-silicon/linux/commit/b7fbb2fde4eaa0ac53e46dcb76a835fdc9cd9477) | Sasha Finkelstein | rust: soc: apple: rtkit: Add apple_rtkit_has_endpoint | Active — RTKIT; `rust/kernel/soc/apple/rtkit.rs` | B,F | +| [bba00880a59b](https://github.com/aurora-silicon/linux/commit/bba00880a59b144155716928066b12cc5751dd52) | Sasha Finkelstein | rust: soc: apple: Add Apple mailbox abstractions | Active — MBOX; `drivers/soc/apple/Kconfig` | B,O | +| [883657fe64b3](https://github.com/aurora-silicon/linux/commit/883657fe64b3a62fa36c90666b0a00a4e5093166) | Sasha Finkelstein | soc: apple: Add support for the PMP co-processor | Active — PMP; `drivers/soc/apple/Kconfig` | P,B | +| [fccd1dbe8ba8](https://github.com/aurora-silicon/linux/commit/fccd1dbe8ba820d4d9f5d1a624987e2820035204), [04841b120bbf](https://github.com/aurora-silicon/linux/commit/04841b120bbf7a2fb454cbfa984647e867b64848), [da7aa98b1aae](https://github.com/aurora-silicon/linux/commit/da7aa98b1aae834db5f7f4a8396afc39099d1b2f) | Janne Grunau | fixup! soc: apple: Add support for the PMP co-processor | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [c81971a6c4c7](https://github.com/aurora-silicon/linux/commit/c81971a6c4c706b2f5783653a2df8d79782d4efe) | Asahi Lina | drm/scheduler: Fix UAF in drm_sched_fence_get_timeline_name | Active — DRM; `drivers/gpu/drm/scheduler/sched_entity.c` | C,H,M,F | +| [94bfb56baeeb](https://github.com/aurora-silicon/linux/commit/94bfb56baeeb02bc9c9ce8616aef0bd72a5de3f2) | Asahi Lina | rust: drm: sched: Add GPU scheduler abstraction | Active — DRM; `rust/kernel/drm/mod.rs` | B,M | +| [1340031e447c](https://github.com/aurora-silicon/linux/commit/1340031e447c56ec9d0f8ea7a68436a623fafc91) | Asahi Lina | drm/asahi: Add the Asahi driver for Apple AGX GPUs | Active — MEM; `drivers/gpu/drm/Kconfig` | M,D | +| [af74e2af96bc](https://github.com/aurora-silicon/linux/commit/af74e2af96bc483417f5015f98f4aa54e355851d) | Janne Grunau | drm/asahi: v6.19 probe change | Active — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,D | +| [8c048a0ab849](https://github.com/aurora-silicon/linux/commit/8c048a0ab84971117a5f641ba2185b51e0b12059) | Janne Grunau | drm/asahi: Move unsafe data initialization to driver code | Active — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,D | +| [a95f0620d24c](https://github.com/aurora-silicon/linux/commit/a95f0620d24c456c74d11261e329df054bf70e7b) | Janne Grunau | drm/asahi: RiiR page tables | Active — MEM; `drivers/gpu/drm/asahi/alloc.rs` | M,D | +| [19e9dc7ccb94](https://github.com/aurora-silicon/linux/commit/19e9dc7ccb9499ac3c92e7764edc2312656dc9d5) | Janne Grunau | drm/asahi: Implement ASAHI_BIND_SINGLE_PAGE (mmu/pgtbl) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [96ce16e432a2](https://github.com/aurora-silicon/linux/commit/96ce16e432a213f7ae185870826697900c9acc60) | Asahi Lina | drm/asahi: pgtable: Add dumper | Active — MEM; `drivers/gpu/drm/asahi/pgtable.rs` | M,D | +| [ad2fb060c5dc](https://github.com/aurora-silicon/linux/commit/ad2fb060c5dcce503bc099062ff54c237b6866d0) | Asahi Lina | drm/asahi: pgtable: Add helpers for decoding PTE perms | Active — MEM; `drivers/gpu/drm/asahi/pgtable.rs` | M,D | +| [7b55670c3a59](https://github.com/aurora-silicon/linux/commit/7b55670c3a59219e37192a1f5771a86217008808) | Janne Grunau | drm/asahi: Copy tyr's mmu/vm/range.rs | Active — MEM; `drivers/gpu/drm/asahi/asahi.rs` | M,D | +| [9d16f582e208](https://github.com/aurora-silicon/linux/commit/9d16f582e2085935f893f99326fbcc0346c998bb) | Janne Grunau | drm/asahi: Switch gpuvm to DRM_GPUVM_IMMEDIATE_MODE | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [829551e64f1a](https://github.com/aurora-silicon/linux/commit/829551e64f1a350adddb71751be16a0243e1b666) | Janne Grunau | drm/asahi: MemFlag change in v6.19 | Active — MEM; `drivers/gpu/drm/asahi/gpu.rs` | M,D | +| [f5b02e71cd67](https://github.com/aurora-silicon/linux/commit/f5b02e71cd671ac4e3ceff54bbd1b25e7f20ae9d) | Janne Grunau | drm/asahi: rtkit pin adaptation | Active — MEM; `drivers/gpu/drm/asahi/gpu.rs` | M,D | +| [4220285e8415](https://github.com/aurora-silicon/linux/commit/4220285e8415cc772736b57f0dface3dcdbbee4f) | Janne Grunau | drm/asahi: select APPLE_PMP_REPORT | Active — PMP; `drivers/gpu/drm/asahi/Kconfig` | P,B | +| [30d957799282](https://github.com/aurora-silicon/linux/commit/30d9577992828e8d2f8345cc4b34a19098ca068c) | Janne Grunau | drm/asahi: Improve VM bind performance for large BOs | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [22e9a0b69598](https://github.com/aurora-silicon/linux/commit/22e9a0b69598bc23e66d72d1e51bd40396966dc1) | Janne Grunau | drm/asahi: `use` formatting annd rename changes | Active — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,D | +| [f014fcba9d71](https://github.com/aurora-silicon/linux/commit/f014fcba9d71972198f28c3d5315605669073bea) | Janne Grunau | drm/asahi: v7.1 Mmio relaxed changes | Active — MEM; `drivers/gpu/drm/asahi/regs.rs` | M,D | +| [20dec7b1eaa9](https://github.com/aurora-silicon/linux/commit/20dec7b1eaa9a31dc67a8216d338bb10a452659d) | DesktopECHO | drm/asahi: Clean up deferred BOs when dropping a VM | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M | +| [e07ff81ee812](https://github.com/aurora-silicon/linux/commit/e07ff81ee812fd84603a498beaa1bb25eb629caa) | Janne Grunau | fixup! rust: soc: apple: rtkit: Add Apple RTKit abstraction | Active — RTKIT; `rust/kernel/soc/apple/rtkit.rs` | B,F | +| [44a01c258b11](https://github.com/aurora-silicon/linux/commit/44a01c258b111368f3c75848553ae1a658f46013) | Janne Grunau | drm/asahi: Adjustment for v7.2 + partial drm-rust v7.3 | Active — MEM; `drivers/gpu/drm/asahi/alloc.rs` | M,D | +| [23b564dd6c09](https://github.com/aurora-silicon/linux/commit/23b564dd6c09982106e2b5cc5ce80c25010c354a) | Janne Grunau | drm/asahi: pgtable: Drop more dev coredump related code | Active — MEM; `drivers/gpu/drm/asahi/pgtable.rs` | M,D | +| [b7f0faf4da36](https://github.com/aurora-silicon/linux/commit/b7f0faf4da367be4a29146f5eb82b795dc2f270d), [afffea42ac29](https://github.com/aurora-silicon/linux/commit/afffea42ac29a1f6f8b3ef3819691c1d7b0a259a) | Hojun Cho | pmdomain: apple: Add missing spin_lock_init | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [0de0a3740a9a](https://github.com/aurora-silicon/linux/commit/0de0a3740a9a26973e7e49ad1cd87981914c457a), [919b934ff719](https://github.com/aurora-silicon/linux/commit/919b934ff7191fa72fab73ecf4ac785dd570c3c3), [ae512e4edf8b](https://github.com/aurora-silicon/linux/commit/ae512e4edf8b96d5eacd79b11fb190837d311b1b) | Ryan Murray | arm64: dts: apple: t8140-pmgr: Add ANS, SPMI, PMP and SMC I2C domains | Active — PMP | P,B | +| [2740578bc68c](https://github.com/aurora-silicon/linux/commit/2740578bc68c410528172a54f9e2e19d35c255d1), [c96a7ffae393](https://github.com/aurora-silicon/linux/commit/c96a7ffae39306507c405d49a3a19927eab289fe), [5578a4e73d10](https://github.com/aurora-silicon/linux/commit/5578a4e73d10d63615ed1eaaf5a7215310df19b8) | Ryan Murray | arm64: dts: apple: t8140: Add CPU frequency scaling | Active translated data — FW | B,P | +| [e7e641d03183](https://github.com/aurora-silicon/linux/commit/e7e641d03183aa76578a1504a4758da6b71c968f), [b58c399f44d7](https://github.com/aurora-silicon/linux/commit/b58c399f44d7499ed62c907efda7b0017f421baf), [f9ec3820d63d](https://github.com/aurora-silicon/linux/commit/f9ec3820d63d57ddb9790ac168a5642efc11f21c) | Ryan Murray | arm64: dts: apple: t8140: Use the Tahiti CPU compatibles and L1 cache sizes | Active translated data — FW | B,P | +| [c702ccc166bb](https://github.com/aurora-silicon/linux/commit/c702ccc166bbe593718a8b4d8a51063e6c5bc102), [f104bb09ce2a](https://github.com/aurora-silicon/linux/commit/f104bb09ce2abf48baa4cf5028b9224141cfdd00), [118ffba5b7b0](https://github.com/aurora-silicon/linux/commit/118ffba5b7b06b9786e24dfab819caa1fb1a2333) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable ANS, SMC and CPU frequency scaling | Active translated data — FW | B,P | +| [5e5d5289f17e](https://github.com/aurora-silicon/linux/commit/5e5d5289f17ede272d495e7e4db117d47c6523a3), [dd366e9d3a8e](https://github.com/aurora-silicon/linux/commit/dd366e9d3a8e05ab209c7ebe1b28413820f018d4) | Marcelo Alcantara | iommu: apple-dart: raise MAX_DARTS_PER_DEVICE to 8 | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [e1817d4152f5](https://github.com/aurora-silicon/linux/commit/e1817d4152f57baca92148c8bbc48fe9b0a8e9f9), [97cdb08431f3](https://github.com/aurora-silicon/linux/commit/97cdb08431f3eef20c2e24c41166bc5dee9e9976), [162b64627bbc](https://github.com/aurora-silicon/linux/commit/162b64627bbcb2fa0ae83cbb6cbf86825ced3e33), [70dfd8f93d3c](https://github.com/aurora-silicon/linux/commit/70dfd8f93d3cac6d6ac7ad26909ca6378fd6a2b4) | Ryan Murray | iommu/apple-dart: Check devm_memremap() for an error pointer | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [f57d90040433](https://github.com/aurora-silicon/linux/commit/f57d9004043387ed6c437ddf0afcfcc1bb49f2fd), [6fa300933bb1](https://github.com/aurora-silicon/linux/commit/6fa300933bb1f05c9a49bddc05f8ea3b42c47d35), [79379798f12f](https://github.com/aurora-silicon/linux/commit/79379798f12fdb32ba79f74b0524cfc4f0170471) | Ryan Murray | iommu/apple-dart: Support T8140 DARTs and firmware-owned stream roots | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [6ec845b2f381](https://github.com/aurora-silicon/linux/commit/6ec845b2f3812090ed39893f8e26999c757950c9), [3bd878053ca7](https://github.com/aurora-silicon/linux/commit/3bd878053ca7aed1e4af56bc14c339b8bddc6d9c), [6898f34ac7bd](https://github.com/aurora-silicon/linux/commit/6898f34ac7bd349ce29b60fb9785168950c5ed69), [fb7b8edf4ee1](https://github.com/aurora-silicon/linux/commit/fb7b8edf4ee19a35c07c1a2c6aa6b8e65c71777b) | DjDeveloperr | arm64: dts: apple: t8140-pmgr: Add the GPU power domains [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data — FW | B,P | +| [cc2d03580b0a](https://github.com/aurora-silicon/linux/commit/cc2d03580b0a68ca0f9779adebd646645813d673), [ea4abd222b2f](https://github.com/aurora-silicon/linux/commit/ea4abd222b2fa2473dba79ca4a91a220e8cb20ba), [52fac306b907](https://github.com/aurora-silicon/linux/commit/52fac306b90783087af17af7ba3e2fc6570122e5), [e77d74a6be76](https://github.com/aurora-silicon/linux/commit/e77d74a6be7645e64b9b50bb51713e72a08c9520) | DjDeveloperr | arm64: dts: apple: t8140: Add the GPU [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data — FW | B,P | +| [8284bd4d3914](https://github.com/aurora-silicon/linux/commit/8284bd4d3914ce296a83c38753a69b07e1c10a0e), [29b0b3a54664](https://github.com/aurora-silicon/linux/commit/29b0b3a546641438b14da95054ae136540465de8), [54124e3efacb](https://github.com/aurora-silicon/linux/commit/54124e3efacba7ba530e742cb2f297bbdeb719fa), [f4a46cd97b2a](https://github.com/aurora-silicon/linux/commit/f4a46cd97b2a3e70875d76e3cf2b82f3959fd0f5) | DjDeveloperr | arm64: dts: apple: t8140-j700: Enable the GPU [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data — FW | B,P | +| [31ee8e23124d](https://github.com/aurora-silicon/linux/commit/31ee8e23124d77700e47cabc0e8645404187fd74), [5b85b301d4ee](https://github.com/aurora-silicon/linux/commit/5b85b301d4ee38256c757299a0c58c4a79a531a7), [727284277d33](https://github.com/aurora-silicon/linux/commit/727284277d335215edd48b5f74a6f4d4e949ff3a), [9c10d7198c8e](https://github.com/aurora-silicon/linux/commit/9c10d7198c8e56b16da35914ffde0eecb6bfb525) | DjDeveloperr | arm64: dts: apple: t8140: describe the GPU mailboxes as ASCWrap v6 [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MBOX | B | +| [dbc9f011ec91](https://github.com/aurora-silicon/linux/commit/dbc9f011ec91fb72089a45487891bfd7c5a8939d), [86fba21bf7c9](https://github.com/aurora-silicon/linux/commit/86fba21bf7c926033306cb2dc5b6d5bcfbbe30ce), [018ab982644a](https://github.com/aurora-silicon/linux/commit/018ab982644a6eff94a6020d8910e9c8c187d6a2) | Ryan Murray | arm64: dts: apple: t8140: Add CPU energy model and thermal sensor cells | Active translated data — FW | B,P | +| [b15c2faba082](https://github.com/aurora-silicon/linux/commit/b15c2faba08274f02bf9a11e0e15eef470017dbd), [d693f725d774](https://github.com/aurora-silicon/linux/commit/d693f725d77422774ae683701732562a84789439), [d69c31212d2b](https://github.com/aurora-silicon/linux/commit/d69c31212d2b93b6396775c7890d5734f962c486) | Ryan Murray | arm64: dts: apple: t8140-j700: Add CPU thermal zones | Active translated data — FW | B,P | +| [25b99e9b6a75](https://github.com/aurora-silicon/linux/commit/25b99e9b6a75a9e783d7b5f04ee60b4f97fe24ed), [1ffe37e7af24](https://github.com/aurora-silicon/linux/commit/1ffe37e7af241fb91ecd7a8d17c946fd0a5d8ecc), [64eff8a46206](https://github.com/aurora-silicon/linux/commit/64eff8a46206613ff354cbeaff49d1354e4c1f52) | Ryan Murray | arm64: dts: apple: t8140: Drop the J700 CPU thermal policy property | Active translated data — FW | B,P | +| [db87f30fa4e7](https://github.com/aurora-silicon/linux/commit/db87f30fa4e7a322d655989316aef41a00fc46e5), [cf7dc9d0474c](https://github.com/aurora-silicon/linux/commit/cf7dc9d0474cc53e3716948365b72bacc848d5e9), [8ba3c01c426a](https://github.com/aurora-silicon/linux/commit/8ba3c01c426a8efd643289689493835c51b8937d) | Ryan Murray | arm64: dts: apple: t8140: Add measured CPU capacity values | Active translated data — FW | B,P | +| [6bb7669702ed](https://github.com/aurora-silicon/linux/commit/6bb7669702edf26c05f2be84f303d0fd612487b2), [2623391b533d](https://github.com/aurora-silicon/linux/commit/2623391b533de7f038408e57e3084a75f765b56c), [3dd2554de0c5](https://github.com/aurora-silicon/linux/commit/3dd2554de0c5b6e9d16a3809186872847987de6a) | Ryan Murray | arm64: dts: apple: enable the PMP fast-die limiter on J700 [#179](https://github.com/aurora-silicon/linux/pull/179) | Active — PMP | P,B | +| [47ed624515f4](https://github.com/aurora-silicon/linux/commit/47ed624515f40f37b39230c3b1b20bb6678c788a), [66988937c0d6](https://github.com/aurora-silicon/linux/commit/66988937c0d62aeb1ed828379cb83bbc68581afc), [39a036ead934](https://github.com/aurora-silicon/linux/commit/39a036ead934c701af76e05eebd72b102810861c) | Ryan Murray | arm64: dts: apple: t8140-j700: enable the PMP nodes once | Active — PMP | P,B | +| [a539d62f765f](https://github.com/aurora-silicon/linux/commit/a539d62f765fb0e8153f2be0fa8fb33058f6df9a), [c1af50661b44](https://github.com/aurora-silicon/linux/commit/c1af50661b44940df0c5747ae7dfca25c871bbcb), [d30b0dfcf358](https://github.com/aurora-silicon/linux/commit/d30b0dfcf35813ce52d935e24e7e6d46349ec3cd) | Ryan Murray | arm64: dts: apple: t8140-j700: Keep the idle E cluster out of its lowest OPP | Active translated data — FW | B,P | +| [fb68b124b886](https://github.com/aurora-silicon/linux/commit/fb68b124b8865e39b72b2387e7f739856f72cc26), [76dd6fb9e643](https://github.com/aurora-silicon/linux/commit/76dd6fb9e643747eb61c3b409c93b1ce2975184e), [2d51c6aada40](https://github.com/aurora-silicon/linux/commit/2d51c6aada40a6c97ae81464eea6935ebf70ba95) | Ryan Murray | arm64: dts: apple: t8140: Describe the memory cache controller | Active — MCC | M,P | +| [5811648de37d](https://github.com/aurora-silicon/linux/commit/5811648de37d1bcd66a5294f44191da8880947b2), [840606faf824](https://github.com/aurora-silicon/linux/commit/840606faf824b4f6ea862f8475e7edaff9af4150), [b5b71dfc50b2](https://github.com/aurora-silicon/linux/commit/b5b71dfc50b2bfbf5800a76c540f878924a678cb), [ed31b37fb032](https://github.com/aurora-silicon/linux/commit/ed31b37fb0327ee7c20961fce2785ed3d4ddb154) | DjDeveloperr | arm64: dts: apple: t8140: describe the PMC and report GFX to the PMP [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Active after port — PMC | P,C,R | +| [15f21cbc0d4b](https://github.com/aurora-silicon/linux/commit/15f21cbc0d4bbbc901454d1e69f53084e1c5dc18), [6cab4decfb66](https://github.com/aurora-silicon/linux/commit/6cab4decfb664524936da59558bc39407b633b9a), [0272b63e30f0](https://github.com/aurora-silicon/linux/commit/0272b63e30f07b8c1a496cb01f5aa74e2cc1ff93) | Ryan Murray | arm64: dts: apple: t8140: keep the GPU on its PMGR state [#219](https://github.com/aurora-silicon/linux/pull/219) | Active translated data — FW | B,P | +| [0a44c956dde2](https://github.com/aurora-silicon/linux/commit/0a44c956dde28aad0bd37b66c6ff5d8096e5676d), [8fa836c5de81](https://github.com/aurora-silicon/linux/commit/8fa836c5de8111ab90d8f2a746526d2cd487afb7), [ae8e780a5b7d](https://github.com/aurora-silicon/linux/commit/ae8e780a5b7dedcb4a31adbd106fb9f9dbff8be0) | Ryan Murray | arm64: dts: apple: t8140: describe the PMC's DCS limit [#219](https://github.com/aurora-silicon/linux/pull/219) | Gated — DCS | P | +| [84eb6b53da34](https://github.com/aurora-silicon/linux/commit/84eb6b53da3453d7c2838bd2a41779e0a9bce181), [f01e1cde7d9f](https://github.com/aurora-silicon/linux/commit/f01e1cde7d9f270e156ee77c6819466757b938f6), [a037985ac960](https://github.com/aurora-silicon/linux/commit/a037985ac9605eb76fe51a05b172b022ac1f0005) | Ryan Murray | arm64: dts: apple: t8140-j700: limit the DCS vote when the die runs hot [#219](https://github.com/aurora-silicon/linux/pull/219) | Gated — DCS | P | +| [4f6bac29d934](https://github.com/aurora-silicon/linux/commit/4f6bac29d9349ced88686239f259ab2747207708), [e10ab48a4590](https://github.com/aurora-silicon/linux/commit/e10ab48a4590c23c664546f524650310222b3a35), [c1bae63595e8](https://github.com/aurora-silicon/linux/commit/c1bae63595e8fc7b18e79664ee6aa6b4536f5d95) | Ryan Murray | arm64: dts: apple: t8140: Keep the GPU power state on across sleep | Active translated data — FW | B,P | +| [01d1be596840](https://github.com/aurora-silicon/linux/commit/01d1be596840358a7258d87fc91d3201a3287313), [8b6371b5500d](https://github.com/aurora-silicon/linux/commit/8b6371b5500d062c00f71d46a0bccbcbed111edd) | Ryan Murray | iommu: apple-dart: Program T8140 and T8142 streams individually | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [2398a03f445e](https://github.com/aurora-silicon/linux/commit/2398a03f445e8f9f65fa9f86748a3deeab3e1b1f), [69c39f85461e](https://github.com/aurora-silicon/linux/commit/69c39f85461e8944dff387eab703c58ea6c6ed49), [1d2904fd3301](https://github.com/aurora-silicon/linux/commit/1d2904fd3301c63620f07c81ae79f2486a81a9a6) | Dj | iommu/apple-dart: defer locked root publication until attach | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [ae5849165749](https://github.com/aurora-silicon/linux/commit/ae58491657494b0f56ec0113a3aa43612648e242), [44c7f32baad0](https://github.com/aurora-silicon/linux/commit/44c7f32baad0511e6f3ae5c2ffdd863eec8e5d23), [dc9d13edb0e5](https://github.com/aurora-silicon/linux/commit/dc9d13edb0e5b18c61d25240ed4ba532afb8d11f) | Acelogic | iommu/apple-dart: accept T8142 firmware slots that mirror Linux mappings | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [1917cbce81cb](https://github.com/aurora-silicon/linux/commit/1917cbce81cb806498b0a5a3f5e636c851255408), [61dfeb8f659d](https://github.com/aurora-silicon/linux/commit/61dfeb8f659d36149d3dc6162d304136b108c5b9), [c361702757bf](https://github.com/aurora-silicon/linux/commit/c361702757bfccb9183ed79be0c6200fb3722e6f) | Ryan Murray | iommu/apple-dart: defer publication until locked roots are known | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [c74dc14619ec](https://github.com/aurora-silicon/linux/commit/c74dc14619ec5467789855d89c0ef224a3655093), [35f59a797e91](https://github.com/aurora-silicon/linux/commit/35f59a797e914308f4ae289e87551ad97997a497), [d957b0bd713b](https://github.com/aurora-silicon/linux/commit/d957b0bd713bbf4617f54f5ed0d274964e9af6c9) | Ryan Murray | iommu/apple-dart: validate software mappings against inherited slots | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [f89a3c873999](https://github.com/aurora-silicon/linux/commit/f89a3c87399911e0faa4f66091b2e7dcc1145825), [64013b9557ab](https://github.com/aurora-silicon/linux/commit/64013b9557abadab17883a7e697f32f32e0c4160), [707f28a4c399](https://github.com/aurora-silicon/linux/commit/707f28a4c399092ba01bd281a3f0b6623f7bb223) | Ryan Murray | iommu/apple-dart: import firmware translations on T8140 | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [99633bd5454e](https://github.com/aurora-silicon/linux/commit/99633bd5454eac0e455812358be63b4f08d78a98), [c29b618f6340](https://github.com/aurora-silicon/linux/commit/c29b618f6340b4ede94747a266f86a844344e769), [ebcff4ba64dd](https://github.com/aurora-silicon/linux/commit/ebcff4ba64dd90983cc8150b0f46fda82b5fb9f0) | Ryan Murray | iommu: apple-dart: retain inherited translations before reservation setup | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [8d2d7790d8d5](https://github.com/aurora-silicon/linux/commit/8d2d7790d8d552f01ce2bf10f922aaccbf7918aa), [dbd410951406](https://github.com/aurora-silicon/linux/commit/dbd4109514067a2ed8140d8138bee68453272f02), [8478ae014201](https://github.com/aurora-silicon/linux/commit/8478ae0142014d170a62df6dd94cc66bd989627b) | Chris Kearney | iommu/apple-dart: restore legacy locked display domain handoff | Active — DART; `drivers/iommu/Kconfig` | D,B | +| [0297adcecc72](https://github.com/aurora-silicon/linux/commit/0297adcecc72a563b5cfb3ca3e483f71422fb77c), [b71446526889](https://github.com/aurora-silicon/linux/commit/b71446526889b27c60461685c3c320b7e123155d), [2bad3dba632f](https://github.com/aurora-silicon/linux/commit/2bad3dba632ff85537392b79255fc527baf01746) | Ryan Murray | iommu: apple-dart: keep inherited firmware translations visible before attach | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [80ce713f5251](https://github.com/aurora-silicon/linux/commit/80ce713f52511abe4d35b35bfac0f10f9072d401), [8ece17f482f1](https://github.com/aurora-silicon/linux/commit/8ece17f482f11da6e81915b98fc8b93ed796504a), [a64a4771b276](https://github.com/aurora-silicon/linux/commit/a64a4771b27602bf5a72b413b6bc9a868892992a) | Ryan Murray | iommu: apple-dart: invalidate T8110 ranges by device DVA [#221](https://github.com/aurora-silicon/linux/pull/221) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [cef9a23aa45e](https://github.com/aurora-silicon/linux/commit/cef9a23aa45e3ac6b352e6c2bd839935374ae351), [253ba9bebf47](https://github.com/aurora-silicon/linux/commit/253ba9bebf470c0d78845103c93b937f2d9cdfb3), [dd5ac6fdffb0](https://github.com/aurora-silicon/linux/commit/dd5ac6fdffb03d048df510198af6e8d53dceb17d) | Ryan Murray | iommu: apple-dart: leave live firmware translations out of invalidates [#221](https://github.com/aurora-silicon/linux/pull/221) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [0a73b0299574](https://github.com/aurora-silicon/linux/commit/0a73b0299574da4c42659024b4a0e8390c36d2d8), [bc6682c215ad](https://github.com/aurora-silicon/linux/commit/bc6682c215adc6febfb86c9fa2ed0fb69657dcc2), [9c512b25f55e](https://github.com/aurora-silicon/linux/commit/9c512b25f55eaf6332b590e5a205ca7d55de12c8) | Ryan Murray | iommu: apple-dart: invalidate client-ranged T8140 streams again [#232](https://github.com/aurora-silicon/linux/pull/232) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [ae812d47ea17](https://github.com/aurora-silicon/linux/commit/ae812d47ea176c9bb0b4ddccc17349fee5afbf1a), [fe15676f6e03](https://github.com/aurora-silicon/linux/commit/fe15676f6e03395f4947cbffb2d49fbee17ea5f9), [4ca4ba380552](https://github.com/aurora-silicon/linux/commit/4ca4ba3805528f5370bbe6bab3d8dbb8f7f9226c) | Ryan Murray | iommu: apple-dart: invalidate four-level T8140 streams by DVA range [#237](https://github.com/aurora-silicon/linux/pull/237) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [a17634424697](https://github.com/aurora-silicon/linux/commit/a17634424697b082221d141ef61d9689460eebf8), [d1a5c65245b8](https://github.com/aurora-silicon/linux/commit/d1a5c65245b87c457a8fc4b4190d440e9bc38b8d) | Sasha Finkelstein | soc: apple: rtkit: Add tracekit endpoint. | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [ff7d81511780](https://github.com/aurora-silicon/linux/commit/ff7d81511780770e944ebe6e66ed56a06b497a31) | Ryan Murray | drm/asahi: Adapt to the Linux 7.3 Rust APIs [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — MEM; `drivers/gpu/drm/asahi/debug.rs` | M,D | +| [588c231eaa54](https://github.com/aurora-silicon/linux/commit/588c231eaa54f4a34872519517fbbcc079b41175) | Ryan Murray | soc: apple: Adapt the Rust SEP, PMP and AOP drivers to Linux 7.3 | Active — PMP; `drivers/soc/apple/aop.rs` | P,B | +| [c025e6cd0615](https://github.com/aurora-silicon/linux/commit/c025e6cd0615e416e0a06ba997e8d9f417a3e185), [af0890cf33cf](https://github.com/aurora-silicon/linux/commit/af0890cf33cf899cf0005daf6138da1dfe573c19) | Ryan Murray | soc: apple: rtkit: Harden syslog buffer handling | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [3a8760c28c33](https://github.com/aurora-silicon/linux/commit/3a8760c28c3341380e105c75bb48e76c895d3f3d), [c01af1b31c52](https://github.com/aurora-silicon/linux/commit/c01af1b31c523b09106c4bed43f8bb7cba65cbfc) | Ryan Murray | soc: apple: rtkit: Keep a live shared buffer on a repeated request | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [495b2f037c28](https://github.com/aurora-silicon/linux/commit/495b2f037c284f4b7b10f54c90a7d34fbf5e99fa), [6b7e37786882](https://github.com/aurora-silicon/linux/commit/6b7e37786882db803e9de1352a2af623044f9237), [1fc9ea993905](https://github.com/aurora-silicon/linux/commit/1fc9ea9939058bc1307565f036506da30dd62908) | Ryan Murray | soc: apple: rtkit: Serialize receiver ownership through teardown | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [335eaeb9b9f1](https://github.com/aurora-silicon/linux/commit/335eaeb9b9f147230f464a140287e820b1d78518), [0593ad398a6e](https://github.com/aurora-silicon/linux/commit/0593ad398a6eb83e2e2ce9dd25bf1fd0768e5bcd) | Ryan Murray | soc: apple: rtkit: Add apple_rtkit_free_retaining_buffers() | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [e796f5fbaf06](https://github.com/aurora-silicon/linux/commit/e796f5fbaf0698b478c43ba6eb15e046c9cc6244), [8527354650e8](https://github.com/aurora-silicon/linux/commit/8527354650e810479ceb0a58417e683b2dee852c) | Ryan Murray | soc: apple: rtkit: Allow streaming-mapped shared buffers | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [9191296f1898](https://github.com/aurora-silicon/linux/commit/9191296f1898d1acf2d0cd500230ae80c9e3e0ca), [88e3c5c07943](https://github.com/aurora-silicon/linux/commit/88e3c5c07943fedf73e45f88b270f8550d7691ae) | Ryan Murray | soc: apple: rtkit: Support adopting a running co-processor | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [1932e1e1b184](https://github.com/aurora-silicon/linux/commit/1932e1e1b18437408e77dcc1d23d6d966b0fdb98), [f232f22bef54](https://github.com/aurora-silicon/linux/commit/f232f22bef54eb6fbd848a1374e49fe91035b397) | Ryan Murray | soc: apple: rtkit: Report crashes of an adopted co-processor | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [b38944203ab0](https://github.com/aurora-silicon/linux/commit/b38944203ab0ad8ec01c26bd67df67455fcc98c3), [4f0416f2c43b](https://github.com/aurora-silicon/linux/commit/4f0416f2c43ba895b3c34a263cf5d0e070095a55), [2768025e0f1d](https://github.com/aurora-silicon/linux/commit/2768025e0f1d9792f84e9fbb99f6c0b155c0a22a), [35b2acbf474d](https://github.com/aurora-silicon/linux/commit/35b2acbf474d8505c3edc1a2749a8d6fbfaf7a09) | Ryan Murray | pmdomain: apple: Print the read-back state on a power state timeout | Active — OBS; `drivers/pmdomain/apple/pmgr-pwrstate.c` | O,F | +| [768a665d0ca1](https://github.com/aurora-silicon/linux/commit/768a665d0ca138d0f2c80029fd1d272c11468e2e), [9c5b8fe6d6d6](https://github.com/aurora-silicon/linux/commit/9c5b8fe6d6d6a93f7c4bbbd14026fd93d5f6aa73), [afe19efeff9c](https://github.com/aurora-silicon/linux/commit/afe19efeff9c466486fb723f7066f86b9a3e71d5), [9dc79fb25c92](https://github.com/aurora-silicon/linux/commit/9dc79fb25c9294bd7a8ecb00acc9bcea379c3d1a) | Ryan Murray | pmdomain: apple: Mask BUSY in power state writes and ratelimit errors | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [a6aa625a9b35](https://github.com/aurora-silicon/linux/commit/a6aa625a9b35b96ba4ffc0749d55a0f21fe9e426), [03f4792761f4](https://github.com/aurora-silicon/linux/commit/03f4792761f42c3a4581be4b585a479cfc65bfc3), [f82e88f4049e](https://github.com/aurora-silicon/linux/commit/f82e88f4049ed6ea278717be6d922f8e347aab13), [928641d26be3](https://github.com/aurora-silicon/linux/commit/928641d26be3083dc06c0de072ce99537fa059b7) | DjDeveloperr | soc: apple: rtkit: Support co-processors that place their own buffers [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [e52b589758bd](https://github.com/aurora-silicon/linux/commit/e52b589758bd9c72db728ef41947aadc2d1eec3f), [a8e468104169](https://github.com/aurora-silicon/linux/commit/a8e46810416977c034c45d805c0b504cfdab9a6a), [c07d044a0311](https://github.com/aurora-silicon/linux/commit/c07d044a03117705721c78500bc36489ea34e6c4) | Ryan Murray | soc: apple: rtkit: recognise an initial crash log in adopted sessions | Active — RTKIT; `drivers/soc/apple/rtkit-crashlog.c` | B,F | +| [8420f4272083](https://github.com/aurora-silicon/linux/commit/8420f42720830b9e5bad37647b6eead5303bc566), [352b68249894](https://github.com/aurora-silicon/linux/commit/352b682498947d0062635916a9d6902165ab38b5), [2f434459eb09](https://github.com/aurora-silicon/linux/commit/2f434459eb094edc046cd2ff71f1901302d7ef3d) | Ryan Murray | soc: apple: rtkit: reject an already owned mailbox receiver | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [791f8c37bad3](https://github.com/aurora-silicon/linux/commit/791f8c37bad39ada4186e7db9efb74472ef1670b), [44a668c87df1](https://github.com/aurora-silicon/linux/commit/44a668c87df18147ca0c9ace22e897e4c65b7620), [41e7892e40eb](https://github.com/aurora-silicon/linux/commit/41e7892e40eb21ddb08b2391a7940ed6499daf8d) | Ryan Murray | soc: apple: pmp: use a 42-bit DMA mask on T8140 [#179](https://github.com/aurora-silicon/linux/pull/179) | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [7a9913ea25e2](https://github.com/aurora-silicon/linux/commit/7a9913ea25e2d95d3ebe8f0392fec92979d08728), [493080d07b91](https://github.com/aurora-silicon/linux/commit/493080d07b918a2025fb79ff987d0dc9aeb294fe), [f69f1b76fcb0](https://github.com/aurora-silicon/linux/commit/f69f1b76fcb04e6bd7d7d066654e612be9761af0) | Ryan Murray | pmdomain: apple: pmp-report: expose T8140 fast-die effort [#179](https://github.com/aurora-silicon/linux/pull/179) | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [1a665f53f540](https://github.com/aurora-silicon/linux/commit/1a665f53f540a523d8df2c11d11a824f933b0d14), [8410c0eac2d9](https://github.com/aurora-silicon/linux/commit/8410c0eac2d9419a33c6a8d4a00bd14059f94610), [4f3e0ed14a9c](https://github.com/aurora-silicon/linux/commit/4f3e0ed14a9c280aafb3642619e7335413592e06) | Ryan Murray | pmdomain: apple: drain PMP report users before teardown | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [50b59e9b8f43](https://github.com/aurora-silicon/linux/commit/50b59e9b8f43e49c14a8a5e426f7891ac5f5e8c7), [061cf8fae73f](https://github.com/aurora-silicon/linux/commit/061cf8fae73f12de8d6720602ccac2aec250a20f), [f4ec00ff19db](https://github.com/aurora-silicon/linux/commit/f4ec00ff19dbfdc6d9bdbf03e8d7ec2798502f1a) | Ryan Murray | soc: apple: Enable T8140 system level cache allocation | Active — MCC; `drivers/soc/apple/Kconfig` | M,P | +| [628dbbe941d2](https://github.com/aurora-silicon/linux/commit/628dbbe941d25e5153dc62cb04104f846f3ffc48), [af915d02f5f6](https://github.com/aurora-silicon/linux/commit/af915d02f5f6022114a46e302642e915f9b22a01), [8b7d7f837ff6](https://github.com/aurora-silicon/linux/commit/8b7d7f837ff68f7fa37f6aaae55114bbd15efb43), [132cee4161dc](https://github.com/aurora-silicon/linux/commit/132cee4161dcff4307c55314f2a6189a00e7b0df) | DjDeveloperr | pmdomain: apple: add PMC voter interface driver [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Active after port — PMC; `drivers/pmdomain/apple/Kconfig` | P,C,R | +| [698f429f4256](https://github.com/aurora-silicon/linux/commit/698f429f42566c059e7788e4efd44978a5db33a9), [9e840cf32336](https://github.com/aurora-silicon/linux/commit/9e840cf323360eabdb61223cf73198f4f4f3179d), [6454e02db2a5](https://github.com/aurora-silicon/linux/commit/6454e02db2a562d82adfbc4447ffe093f43dae0a), [5bd867f5c339](https://github.com/aurora-silicon/linux/commit/5bd867f5c33919ed65c81ead1e31595bb6bb91c5) | DjDeveloperr | soc: apple: add host performance controller floors [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — CLPC; `drivers/soc/apple/Kconfig` | P,C,R,I | +| [faff1f7acf75](https://github.com/aurora-silicon/linux/commit/faff1f7acf752284972af5d469f91186c1e28552), [1c69f57e2c3e](https://github.com/aurora-silicon/linux/commit/1c69f57e2c3e5ba7cea2db4708242b7b538b15b2), [e736b2a049bf](https://github.com/aurora-silicon/linux/commit/e736b2a049bf708512f845093f712c431c20da3b) | Ryan Murray | pmdomain: apple: pmc: report the honoured floor of each agent [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — PMC; `drivers/pmdomain/apple/pmc.c` | P,C,R | +| [9afc8195e8e2](https://github.com/aurora-silicon/linux/commit/9afc8195e8e2b3b4da25805f0b0c53baac16f1de), [e3f7747b88a3](https://github.com/aurora-silicon/linux/commit/e3f7747b88a38a8f2db51a63b323bb6fe3a25157), [cf8edfc522ce](https://github.com/aurora-silicon/linux/commit/cf8edfc522ce82871acc421119e324b56ab2813f) | Ryan Murray | pmdomain: apple: pmc: limit the DCS rail as a cooling device [#219](https://github.com/aurora-silicon/linux/pull/219) | Gated — DCS; `drivers/pmdomain/apple/pmc.c` | P | +| [c809017f2926](https://github.com/aurora-silicon/linux/commit/c809017f2926613cc9b683189c5e37aa6236c647), [bbbc7f0f2526](https://github.com/aurora-silicon/linux/commit/bbbc7f0f2526501df99e7d40c7a14a5bcb3c168d), [99bf4fc4c39c](https://github.com/aurora-silicon/linux/commit/99bf4fc4c39cbe59493531b7682f0a7eb60ac2e0) | Ryan Murray | pmdomain: apple: build the PMC driver by default [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — PMC; `drivers/pmdomain/apple/Kconfig` | P,C,R | +| [413512c8c6c7](https://github.com/aurora-silicon/linux/commit/413512c8c6c7503e21b2d845b7072f3e67985e80), [afbf48da68ef](https://github.com/aurora-silicon/linux/commit/afbf48da68ef244414db4e174b630cf95ed33bd2), [d0f012d7b92f](https://github.com/aurora-silicon/linux/commit/d0f012d7b92f9967d42c35a70bc09c2b87274c0b) | Ryan Murray | soc: apple: clpc: do not count offline CPUs as busy [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [a1517450f0d6](https://github.com/aurora-silicon/linux/commit/a1517450f0d6afa99ed5a5f2acbbc46350fdccd5), [a0301cd2e0fb](https://github.com/aurora-silicon/linux/commit/a0301cd2e0fb788836d8ef4d09b397a478891a72), [ddc216cd42cb](https://github.com/aurora-silicon/linux/commit/ddc216cd42cb7c7ebc831d3fafca17df244b280e) | Ryan Murray | soc: apple: clpc: stop waking idle CPUs and release the floor in sleep [#219](https://github.com/aurora-silicon/linux/pull/219) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [17c6097b09dd](https://github.com/aurora-silicon/linux/commit/17c6097b09ddd81a31a618e1d2447c3fdfed87ce), [ebd97e7441ff](https://github.com/aurora-silicon/linux/commit/ebd97e7441ff7ca5f7169f6e2bf550e7fb1438e0), [4f13ab717cb3](https://github.com/aurora-silicon/linux/commit/4f13ab717cb3a5ab19e5c097084ff2c676457f37) | Ryan Murray | pmdomain: apple: pmp-report: keep entry domains bound [#223](https://github.com/aurora-silicon/linux/pull/223) | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [bd2a439af1fe](https://github.com/aurora-silicon/linux/commit/bd2a439af1feefbb19cec4af61a28ae81a7fe479), [6733b19981f1](https://github.com/aurora-silicon/linux/commit/6733b19981f1d70f51264f609582025889e083db), [59540c7f72b3](https://github.com/aurora-silicon/linux/commit/59540c7f72b3721d293fb36cc5fd5bf79e1f47bc) | Ryan Murray | pmdomain: apple: Undo a power state transition that times out | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [afa52a4aacd2](https://github.com/aurora-silicon/linux/commit/afa52a4aacd24be61e6963bcfa96a673fcd0f7fd), [b1dadbe56c1a](https://github.com/aurora-silicon/linux/commit/b1dadbe56c1ac98c1687aacd6f9a4ffd45d44802) | Acelogic | soc: apple: rtkit: Allow AP readiness before the IOP power acknowledgement | Active — RTKIT; `drivers/soc/apple/rtkit-helper.c` | B,F | +| [80223777e1c0](https://github.com/aurora-silicon/linux/commit/80223777e1c0fcdd18cdf7dbb693389f18bffe66), [7de139272456](https://github.com/aurora-silicon/linux/commit/7de1392724567cdab869ed7102a23642d6e64320), [b5c2960b4b44](https://github.com/aurora-silicon/linux/commit/b5c2960b4b441fa51b7f0ccdbaa17eae8a9de4bc) | Ryan Murray | soc: apple: rtkit: identify failed boot handshake stages | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [e02a7f13d77d](https://github.com/aurora-silicon/linux/commit/e02a7f13d77d0c450e6e663da89160b8e2e6a9de), [3a165a5f418d](https://github.com/aurora-silicon/linux/commit/3a165a5f418df4ca2445c14657bdc6dbe39bb223), [50a711417171](https://github.com/aurora-silicon/linux/commit/50a711417171e48a07340999bf43bb8c281b14d7) | Eryk Wieliczko | pmdomain: apple: leave firmware-owned power domains untouched | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [15a254a94f16](https://github.com/aurora-silicon/linux/commit/15a254a94f1685a5550aec6c99a3270bfb389951), [e271de959b7d](https://github.com/aurora-silicon/linux/commit/e271de959b7de2e436f15415329d175e2e158517) | Ryan Murray | soc: apple: rtkit: Recognise opaque OSLog events 3 to 5 | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [faadb28854ca](https://github.com/aurora-silicon/linux/commit/faadb28854caaf534df60f6d629eca87c963344f), [aa486de92319](https://github.com/aurora-silicon/linux/commit/aa486de923198c933849bb2172f5ddad4f4710b6), [6559da7d50ab](https://github.com/aurora-silicon/linux/commit/6559da7d50ab10da722b3719f447836b12427159), [7810f444ae5a](https://github.com/aurora-silicon/linux/commit/7810f444ae5a403773baf8d4f460e29cb2375cf5) | Ryan Murray | cpufreq: apple-soc: Use related_cpus when removing OPP tables at exit | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [f630f4e6a724](https://github.com/aurora-silicon/linux/commit/f630f4e6a724b62182875eeb75e3559f6304b286), [4df394b4c36b](https://github.com/aurora-silicon/linux/commit/4df394b4c36be04ca63fabae2c630bfdda20188a), [31ce0bec7173](https://github.com/aurora-silicon/linux/commit/31ce0bec7173eb3fbc1067e6dba46c2ca2f4dc8e) | Ryan Murray | cpufreq: apple-soc: Add T8140 support | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [116ec26bc074](https://github.com/aurora-silicon/linux/commit/116ec26bc074456b670823002c81ccffc5fbbecc), [2e97d372534e](https://github.com/aurora-silicon/linux/commit/2e97d372534e73b72b9b36b9debaa3caa9ae5ab6), [b0a02dcbfded](https://github.com/aurora-silicon/linux/commit/b0a02dcbfded7f1485db7f37694712892025a4d9) | Ryan Murray | cpufreq: apple-soc: enforce PMP fast-die limits through QoS [#179](https://github.com/aurora-silicon/linux/pull/179) | Active — PMP; `drivers/cpufreq/apple-soc-cpufreq.c` | P,B | +| [3a1ff1c99f6a](https://github.com/aurora-silicon/linux/commit/3a1ff1c99f6a35163c77f9c2cc5f6b19d4fe6242), [9d74a86460df](https://github.com/aurora-silicon/linux/commit/9d74a86460df995f3522f3267c3f3fa34d79a47e) | Ryan Murray | cpufreq: apple-soc: keep required fallback limits with an older DT | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [1248cb920813](https://github.com/aurora-silicon/linux/commit/1248cb9208135326d9724eb1a83e3c67d25c3664), [a40bdafc42e5](https://github.com/aurora-silicon/linux/commit/a40bdafc42e5dcf8c0c4bc728f883c7e3d48bffe), [a61bca6382a7](https://github.com/aurora-silicon/linux/commit/a61bca6382a765f5b74134dea3b094039f2a15bd) | Ryan Murray | cpufreq: apple-soc: tidy includes and the ->exit() callback [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,P | +| [c52554b353b4](https://github.com/aurora-silicon/linux/commit/c52554b353b4fcecf684ea648e4b1ab5f896b677), [143dc7bec441](https://github.com/aurora-silicon/linux/commit/143dc7bec44131ac467fa21049f2d63c52783d52), [e7d3d687d207](https://github.com/aurora-silicon/linux/commit/e7d3d687d20745259fcb83af83c9ed377ff6a1f3) | Ryan Murray | cpufreq: apple-soc: describe the unmanaged P-state ceiling as data [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,P | +| [e71cc1ccab4e](https://github.com/aurora-silicon/linux/commit/e71cc1ccab4eaefdf63d66104107c9604ab04121), [cd8875cfd1bf](https://github.com/aurora-silicon/linux/commit/cd8875cfd1bfaa80b1b1dbd7e27ea234d129471b), [cfe897d4abac](https://github.com/aurora-silicon/linux/commit/cfe897d4abac70a3d9703ea7f4c082ed89da7a96) | Ryan Murray | rust: soc: apple: rtkit: Add shutdown() and a buffer-retaining drop | Active — RTKIT; `rust/kernel/soc/apple/rtkit.rs` | B,F | +| [10dd5c21ef72](https://github.com/aurora-silicon/linux/commit/10dd5c21ef7211836beaa47f6f3fd3e250d36866), [8779ecb38c89](https://github.com/aurora-silicon/linux/commit/8779ecb38c89c103617c4ea24207fef96472ebc4), [06914e1d4995](https://github.com/aurora-silicon/linux/commit/06914e1d499555a96e0beccccc0b5485051108cd) | Ryan Murray | soc: apple: rtkit: Handle oslog notifications quietly | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [9a4cf171ba9e](https://github.com/aurora-silicon/linux/commit/9a4cf171ba9edabdd0f0f715468d42689791adb7), [aa18e91ac6fc](https://github.com/aurora-silicon/linux/commit/aa18e91ac6fce29dcc8e69aac619fab3fda7ee85), [904945425463](https://github.com/aurora-silicon/linux/commit/904945425463c0450cb19582afeb4b935175ae51), [201afd84ae50](https://github.com/aurora-silicon/linux/commit/201afd84ae50008095068d028af22ecf4c1356c5) | DjDeveloperr | drm/asahi: use const float bit conversion supported by Rust minimum [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/float.rs` | M,D | +| [c9be90533ae0](https://github.com/aurora-silicon/linux/commit/c9be90533ae06ac64dfe5e35b121cf30a1cc37f0), [6b610ee95063](https://github.com/aurora-silicon/linux/commit/6b610ee95063a8d047c1ee49c65ee3599ee24a92), [94b86f96bd9e](https://github.com/aurora-silicon/linux/commit/94b86f96bd9ef2275c99ddf5238374e615b2f510), [12f0a07a6166](https://github.com/aurora-silicon/linux/commit/12f0a07a61666305a1efe1a1f4c4c88a013abafb) | DjDeveloperr | rust: drm: sched: Fix job accounting when cancelling jobs [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [7ba04b60d2ca](https://github.com/aurora-silicon/linux/commit/7ba04b60d2caf298bb9119b359629feedfec38c5), [43d53c0d4f56](https://github.com/aurora-silicon/linux/commit/43d53c0d4f5660bc6d300e6626d1f9a05d832c79), [15a7eae7964f](https://github.com/aurora-silicon/linux/commit/15a7eae7964f94a16fd273fc3b25a22ad2c27bab), [f8102be460b6](https://github.com/aurora-silicon/linux/commit/f8102be460b66b12af865d6250e73ffc6f99c73a) | DjDeveloperr | rust: drm: sched: Keep the scheduler alive while jobs reference it [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [d1ebc542fe8e](https://github.com/aurora-silicon/linux/commit/d1ebc542fe8e1082a1c6e311b72d4d6c609c940d), [e4c63b3dbab0](https://github.com/aurora-silicon/linux/commit/e4c63b3dbab04bda2a798b913dd5e46b8799290a), [7d650853ea8e](https://github.com/aurora-silicon/linux/commit/7d650853ea8e5de3f512a36b69f8653014de83b4), [f1f985d57681](https://github.com/aurora-silicon/linux/commit/f1f985d576810c7183ea9bcd1b701cb284927ac6) | DjDeveloperr | rust: drm: sched: Do not stop the scheduler before tearing it down [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [45ade507d593](https://github.com/aurora-silicon/linux/commit/45ade507d593cb69929a4daf5d1d50159d729126), [1bc200c44668](https://github.com/aurora-silicon/linux/commit/1bc200c446687bc2eb2b6abc19f1d9b5a3ad59ee), [9171e855e6d5](https://github.com/aurora-silicon/linux/commit/9171e855e6d58a12059e265a7e3907f44ffa70a9), [14d7b3c379da](https://github.com/aurora-silicon/linux/commit/14d7b3c379da4c764581d4c34aff495d7346af72) | DjDeveloperr | rust: drm: sched: let drivers handle timeouts without a reset [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [996d255ea104](https://github.com/aurora-silicon/linux/commit/996d255ea104c999554b5c5584d2b59c6eed3cd9), [59b1e2a30011](https://github.com/aurora-silicon/linux/commit/59b1e2a300116dec7170b0b4a44e4b94a39c372b), [630c43dac20e](https://github.com/aurora-silicon/linux/commit/630c43dac20e9621d3c2cd687c98ab85d51c8b9e), [e0a5c5ad84cb](https://github.com/aurora-silicon/linux/commit/e0a5c5ad84cbc6e48c0194882b8c1c67d72538a8) | DjDeveloperr | drm/asahi: uapi: Add the G17 additions [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data — FW; `drivers/gpu/drm/asahi/file.rs` | B,C,R | +| [2f298100e9d5](https://github.com/aurora-silicon/linux/commit/2f298100e9d59b05f3c71d9e27d8079a63a0b6bc), [4a9cf4453a2d](https://github.com/aurora-silicon/linux/commit/4a9cf4453a2d0349848c89bd2028e8e6757263a7), [e46d8cfd391b](https://github.com/aurora-silicon/linux/commit/e46d8cfd391b00b302d5a9ff4608b82c3714c910), [aae9f7d3525f](https://github.com/aurora-silicon/linux/commit/aae9f7d3525f93a7a5b5ae0529f8e85219735d08) | DjDeveloperr | drm/asahi: separate DRM-facing GPU operations from the firmware manager [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,D | +| [3291ed172be2](https://github.com/aurora-silicon/linux/commit/3291ed172be2a63252d34367f8f5d8baebf2e537), [cac96891a49f](https://github.com/aurora-silicon/linux/commit/cac96891a49fd1416cb3bb0f68922cbebb7618c1), [54721b0f0bee](https://github.com/aurora-silicon/linux/commit/54721b0f0bee7be5b3094112ffcfa36e76dec7ed), [7d3ddb6c35e1](https://github.com/aurora-silicon/linux/commit/7d3ddb6c35e1c478f68c78ac8bb8b3ee9d314e58) | DjDeveloperr | drm/asahi: mmu: support per-SoC UAT geometry and user VA range [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [a0ae891ae8a0](https://github.com/aurora-silicon/linux/commit/a0ae891ae8a00e183ddbc753347a7ea2ef26d7dc), [79aa2038d24c](https://github.com/aurora-silicon/linux/commit/79aa2038d24c495775338c6d72b638885e2cd5e1), [49e3d35945c3](https://github.com/aurora-silicon/linux/commit/49e3d35945c397f7af5a6477540e2ee254b3bd76), [5e21536704b5](https://github.com/aurora-silicon/linux/commit/5e21536704b56c38b478516d1f65bdcfef748a17) | DjDeveloperr | drm/asahi: pgtable: track ownership below firmware-shared roots [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [524c57538f14](https://github.com/aurora-silicon/linux/commit/524c57538f142ef38c8b081f5148ffb515e96266), [52ef95b9e763](https://github.com/aurora-silicon/linux/commit/52ef95b9e7635d564449ec08b33a62c6ed26dd12), [87cab74a2498](https://github.com/aurora-silicon/linux/commit/87cab74a2498f5df62f7697786a2743e84125b99), [c0e36ff2a57f](https://github.com/aurora-silicon/linux/commit/c0e36ff2a57f2a461a96b80cebb741f5b76de7ad) | DjDeveloperr | drm/asahi: add the T8140 firmware interface [#171](https://github.com/aurora-silicon/linux/pull/171) | Active translated data; MCache gated by V25G83.mcache=false — FW; `drivers/gpu/drm/asahi/g17/buffer.rs` | B,C,R | +| [ed303abfff0f](https://github.com/aurora-silicon/linux/commit/ed303abfff0fd9e29e8094b828cd3054969bcf97), [0386eb2184a2](https://github.com/aurora-silicon/linux/commit/0386eb2184a268ed6178a6a93a12880806a8022f), [d7d77474e459](https://github.com/aurora-silicon/linux/commit/d7d77474e4590fcd9c1d0e30ab16dc9ca0c73346), [bc4c50abf8ed](https://github.com/aurora-silicon/linux/commit/bc4c50abf8ede16fec7f2a79546a310134b208df) | DjDeveloperr | drm/asahi: request temporary CPU capacity for sustained GPU feeds [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/completion.rs` | I,C,R,P | +| [4a77604eb003](https://github.com/aurora-silicon/linux/commit/4a77604eb003ed141f28781769b28953fdabfb1f), [9fdd32f35641](https://github.com/aurora-silicon/linux/commit/9fdd32f3564105f1d012ef20e1c64de912cade1d), [15d553b170f1](https://github.com/aurora-silicon/linux/commit/15d553b170f157558da0343d477c39161f9870f1), [e1e0c5c90faf](https://github.com/aurora-silicon/linux/commit/e1e0c5c90faf0307f95374c83efe3009d58f0bdb) | DjDeveloperr | drm/asahi: observe compute completions without a queue mask [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/events.rs` | C,H | +| [593e44ca35f7](https://github.com/aurora-silicon/linux/commit/593e44ca35f708e48e0d9b9efde35bfe1052ef35), [e32d6efe3891](https://github.com/aurora-silicon/linux/commit/e32d6efe3891821dce2e6c0e27219ed676ea4f2f), [7dcd8d1467e2](https://github.com/aurora-silicon/linux/commit/7dcd8d1467e2bc1e90eeb04489d4ab7b392351e8), [0c69741cdc26](https://github.com/aurora-silicon/linux/commit/0c69741cdc26f2dbb26754e87f1b53db1f4bed38) | DjDeveloperr | drm/asahi: avoid repeated submission bookkeeping [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [0ee6827563de](https://github.com/aurora-silicon/linux/commit/0ee6827563deb2e2519534dd938334c90606fb33), [ce33535500cc](https://github.com/aurora-silicon/linux/commit/ce33535500cc78ed2f6695050ce21487902806bc), [ffa3234a92d7](https://github.com/aurora-silicon/linux/commit/ffa3234a92d789514f3e6b05213f417454b73639), [e0d2761bd854](https://github.com/aurora-silicon/linux/commit/e0d2761bd854136076d642b614c9b6078699243e) | DjDeveloperr | drm/asahi: settle unpublished jobs discarded by entity teardown [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/job.rs` | C,R,H | +| [7eb278622690](https://github.com/aurora-silicon/linux/commit/7eb27862269055d524de513e380572b59171a390), [aa659e9a3710](https://github.com/aurora-silicon/linux/commit/aa659e9a3710765dc87d06e50227bdd8476cbd72), [0a72b08543fd](https://github.com/aurora-silicon/linux/commit/0a72b08543fdec99aa8117c8b4983eeee9b8c856), [d41b760af0bc](https://github.com/aurora-silicon/linux/commit/d41b760af0bcc10744b4145cfdbc19dab7625e8e) | DjDeveloperr | drm/asahi: reserve completion capacity for global compute replay [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [06c4e4a3193e](https://github.com/aurora-silicon/linux/commit/06c4e4a3193e5b0630e343d9730cad097471440a), [1a53f784e14f](https://github.com/aurora-silicon/linux/commit/1a53f784e14f219a44f5faa7a33947c3f4dedcb1), [945285b800d3](https://github.com/aurora-silicon/linux/commit/945285b800d30fcc9a60461b70249b96b4c053ad), [868c80c92152](https://github.com/aurora-silicon/linux/commit/868c80c921527154df5e73a271583a4b0b63fdeb) | DjDeveloperr | drm/asahi: filter compute completion identities before payload reads [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | C,H | +| [304da56f2b1b](https://github.com/aurora-silicon/linux/commit/304da56f2b1b927ac837671897273835eeccb91b), [dc74efcea722](https://github.com/aurora-silicon/linux/commit/dc74efcea72275420878d60c358b3af182006eef), [513f7568ad7e](https://github.com/aurora-silicon/linux/commit/513f7568ad7eb30b97ac1309f37bbb3a11229409), [f61c86ab2830](https://github.com/aurora-silicon/linux/commit/f61c86ab28302e46cba8fbdbaa8339aaa4e17b40) | DjDeveloperr | drm/asahi: sample opening counters at the polling deadline [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — REC; `drivers/gpu/drm/asahi/g17/mod.rs` | F,Q | +| [d012beaabd09](https://github.com/aurora-silicon/linux/commit/d012beaabd0985acd8fa07a5e605f9d0c5b50590), [ff5769e16bea](https://github.com/aurora-silicon/linux/commit/ff5769e16bea501f52ff724effb309f107da39e9), [26ba1aa06add](https://github.com/aurora-silicon/linux/commit/26ba1aa06add76aa7e97a843d752fe0d08afd617), [99dd8deec12f](https://github.com/aurora-silicon/linux/commit/99dd8deec12fe28abb844854059bce6a87764199) | DjDeveloperr | drm/asahi: commit canonical binding metadata with each user mapping [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [fef7f56d2bb3](https://github.com/aurora-silicon/linux/commit/fef7f56d2bb33e5bb627a6dc8872b271a5c504cd), [6753db1b2896](https://github.com/aurora-silicon/linux/commit/6753db1b289605b5cc007a8dd8912d948897f86b), [906cacc20dfc](https://github.com/aurora-silicon/linux/commit/906cacc20dfcccb95c6ba7809f8d3ccac4624791), [11b50b74597c](https://github.com/aurora-silicon/linux/commit/11b50b74597c2c3e1f5687d5db50ce0eaa103c21) | DjDeveloperr | drm/asahi: order canonical object invalidation with mapping removal [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [31b8abbea796](https://github.com/aurora-silicon/linux/commit/31b8abbea79664748c2e8a17745bda9e82b119d0), [477cea7fca6e](https://github.com/aurora-silicon/linux/commit/477cea7fca6eab5f5460c2c5e2459fa9cf6e1fa4), [eb1d380fc62d](https://github.com/aurora-silicon/linux/commit/eb1d380fc62d3f8febbcf3a19d173eca44e49f19), [f594e2be7f49](https://github.com/aurora-silicon/linux/commit/f594e2be7f492d0cb4fdbdf5197122418f330036) | DjDeveloperr | drm/asahi: advertise G17 queue and input-sync capabilities [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [ee8060be8a84](https://github.com/aurora-silicon/linux/commit/ee8060be8a84b43d483ac9d4f76d51f1f5a5e35c), [80c1cad754d8](https://github.com/aurora-silicon/linux/commit/80c1cad754d8711b46ece6be4d7e00c6fd27b6df), [4810fcc3be49](https://github.com/aurora-silicon/linux/commit/4810fcc3be491f1eaa0c78a183c800d08fecbc1d), [98783a0fc38d](https://github.com/aurora-silicon/linux/commit/98783a0fc38dbac9eecb4164ed6c090e3adf591d) | DjDeveloperr | drm/asahi: acquire event records after observing their producer [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/channel.rs` | C,R,H | +| [eaebe2b1f691](https://github.com/aurora-silicon/linux/commit/eaebe2b1f691e89c991bafee6013b2e0d0b5070d), [63c2157467ec](https://github.com/aurora-silicon/linux/commit/63c2157467ec2530afd39ac823a75b74c5d210c9), [7703d07ccd6d](https://github.com/aurora-silicon/linux/commit/7703d07ccd6dbb8c50cbd993a325e0d6a61cad33), [267dc9ba9ea8](https://github.com/aurora-silicon/linux/commit/267dc9ba9ea8e068a3d9d0683a58b72a51291ece) | DjDeveloperr | drm/asahi: discard partial G17 mappings when binding fails [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [7f498ba60f1a](https://github.com/aurora-silicon/linux/commit/7f498ba60f1a44401e434975d8d9433f57212539), [56d0e6d6c8b3](https://github.com/aurora-silicon/linux/commit/56d0e6d6c8b340a20cab5869ab6c516170614aa4), [bdfbf517e1fb](https://github.com/aurora-silicon/linux/commit/bdfbf517e1fb478c202e1531a8a084dc7557d4c5), [9f204371cd61](https://github.com/aurora-silicon/linux/commit/9f204371cd6118ecaa4635b76a763e6c4d53b728) | DjDeveloperr | drm/asahi: reuse the reserved output fence for submitted work [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fence.rs` | C,R,H | +| [1c200975b7c1](https://github.com/aurora-silicon/linux/commit/1c200975b7c1ccbe890e6bdeb097fb9c5d59bfac), [7c55851883a6](https://github.com/aurora-silicon/linux/commit/7c55851883a670690b2e354802b0e265c0430930), [c97c4be0ce4e](https://github.com/aurora-silicon/linux/commit/c97c4be0ce4e1a265fa915b9ec4e634a7db2c3af), [fa771fbd7629](https://github.com/aurora-silicon/linux/commit/fa771fbd7629800795268b560e9e2ff1b0cb806c) | DjDeveloperr | drm/asahi: wake render growth when its prerequisites become ready [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/events.rs` | R,H | +| [3b92fcd8c068](https://github.com/aurora-silicon/linux/commit/3b92fcd8c068445739b69393a29c7ee7de0bc5b4), [7d4176528d08](https://github.com/aurora-silicon/linux/commit/7d4176528d08bba02e84e23f43b006a7fdbda51f), [4fe42ca7a853](https://github.com/aurora-silicon/linux/commit/4fe42ca7a85341ec777ca2f19e5ed0eb8fdafa82), [956a8e2fd2dd](https://github.com/aurora-silicon/linux/commit/956a8e2fd2ddcb7e9dc596796b79f62b4e017c55) | DjDeveloperr | drm/asahi: skip frontier scans without a cross-engine prefix [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [cc1601720b58](https://github.com/aurora-silicon/linux/commit/cc1601720b58ceedb0a76606922d7a554da13e17), [63a3c2d02dac](https://github.com/aurora-silicon/linux/commit/63a3c2d02dac4acb23d299c6a00d096f04bf5520), [75ea155f8b2e](https://github.com/aurora-silicon/linux/commit/75ea155f8b2ed4da38c39179a9a653fec0eb1c37), [5026a187d811](https://github.com/aurora-silicon/linux/commit/5026a187d811c441374d11c71a3fa0df58e118fd) | DjDeveloperr | drm/asahi: track CPU feed requests through policy lifetime [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/feed.rs` | I,C,R,P | +| [848b36596112](https://github.com/aurora-silicon/linux/commit/848b36596112673711945126a51efd79d0cfa625), [547707b268e9](https://github.com/aurora-silicon/linux/commit/547707b268e96ecff8cadbc6f8b78652ab52d0ba), [a23fed471fe1](https://github.com/aurora-silicon/linux/commit/a23fed471fe1d97b7d75c738cb63d045ad434754), [c42fd8c45c3a](https://github.com/aurora-silicon/linux/commit/c42fd8c45c3a20ed2ec94c40bf5fbcf782220e7c) | DjDeveloperr | drm/asahi: bound dependency scans to the ordered command prefix [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [7a205aa6b8eb](https://github.com/aurora-silicon/linux/commit/7a205aa6b8eb17488b9882f49beece4f24b49030), [b1087fb75ab1](https://github.com/aurora-silicon/linux/commit/b1087fb75ab153f2cb017c3a08922ece0da18ca7), [b42e20b3a8d3](https://github.com/aurora-silicon/linux/commit/b42e20b3a8d3e03bdf184be067a0149ed6fbee79), [e4d41aaa9c0c](https://github.com/aurora-silicon/linux/commit/e4d41aaa9c0cdd145d5d07fe0b3d0eb209bf0879) | DjDeveloperr | drm/asahi: wait for host timestamp writes in compute prefixes [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | T,C | +| [23e316ffe7e5](https://github.com/aurora-silicon/linux/commit/23e316ffe7e5a3fa108eb271778222bdc7f21ee0), [2a2aff37cd8e](https://github.com/aurora-silicon/linux/commit/2a2aff37cd8e3771dea46ebdc3e45dc7b4415b4c), [55df4049246b](https://github.com/aurora-silicon/linux/commit/55df4049246b6da9482b15411e173a11cc7f04e8), [81d324022d0f](https://github.com/aurora-silicon/linux/commit/81d324022d0f97346639b06a6abaa8b86d480ead) | DjDeveloperr | drm/asahi: release successful submission work-state ownership [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | C,R,H | +| [18ef3a813070](https://github.com/aurora-silicon/linux/commit/18ef3a813070620821371e437d1557a1dd1c79cf), [cfe7880a5d16](https://github.com/aurora-silicon/linux/commit/cfe7880a5d167059b871475fbc5abe60248fcf3f), [1660e75c7e3c](https://github.com/aurora-silicon/linux/commit/1660e75c7e3ccb4b906662db275d6c5ccaebd452), [e0f7410fd5b3](https://github.com/aurora-silicon/linux/commit/e0f7410fd5b368d06ab17d802720f9d76e12880c) | DjDeveloperr | drm/asahi: preserve exact historical command prefixes [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/command.rs` | C,R,H | +| [5c02e806d300](https://github.com/aurora-silicon/linux/commit/5c02e806d3004d2bb01706362298d8a6802e7433), [6507ba3ca670](https://github.com/aurora-silicon/linux/commit/6507ba3ca670d34720c6c8bfea44836b8228772b), [c7986b27e066](https://github.com/aurora-silicon/linux/commit/c7986b27e066b3d3b4fd81e95b6e174cd6c21807), [fbfb5d96f25e](https://github.com/aurora-silicon/linux/commit/fbfb5d96f25efbf2b1571ca635b195e0ad4a54be) | DjDeveloperr | drm/asahi: bring the cleanroom driver up on Neo hardware [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/buffer.rs` | C,R,H | +| [d0b66e8a8778](https://github.com/aurora-silicon/linux/commit/d0b66e8a877898097d40a380483c09e6be471532), [5fa0b934070c](https://github.com/aurora-silicon/linux/commit/5fa0b934070c615c3c745f5759d2fb7f90eaee09), [01d0b0bed7a7](https://github.com/aurora-silicon/linux/commit/01d0b0bed7a763fa775654a0a115c01f38b34584), [66af386568f7](https://github.com/aurora-silicon/linux/commit/66af386568f748d2499be3e3587ce1e7ca0ae9cd) | Ryan Murray | drm/asahi: report live GEM object accounting [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/asahi.rs` | M,D | +| [722064f1be0d](https://github.com/aurora-silicon/linux/commit/722064f1be0d171bc5d496cca352f2db6285c6c0), [f313fc4bd1a1](https://github.com/aurora-silicon/linux/commit/f313fc4bd1a1c91bad6657f7a136cc7b0f88dbe4), [2c076e0de6fe](https://github.com/aurora-silicon/linux/commit/2c076e0de6fe0a0a38a2041b02d5b9e75347375a), [f7fcc50568b9](https://github.com/aurora-silicon/linux/commit/f7fcc50568b9467f241a425b20b92ff1de10f641) | Ryan Murray | drm/asahi: port j700 queue accounting and compute recovery isolation [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — REC; `drivers/gpu/drm/asahi/g17/context.rs` | F,Q | +| [b1e288fb887d](https://github.com/aurora-silicon/linux/commit/b1e288fb887dc74d1b428d478493c6c568d3b02d), [b3b17a139f25](https://github.com/aurora-silicon/linux/commit/b3b17a139f25e8164dfdfe31d7a0c03fc2a4fa85), [2d84d7470c0f](https://github.com/aurora-silicon/linux/commit/2d84d7470c0f0d60c11c9df9e9665ee6a6a2bf71), [3c65418ee698](https://github.com/aurora-silicon/linux/commit/3c65418ee698735d33f299fe208aa8c22ff310a4) | DjDeveloperr | drm/asahi: account once per compute queue transition [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [4273fb6241bb](https://github.com/aurora-silicon/linux/commit/4273fb6241bb6f9083dd8feb263fee0924fffa94), [fa2e76747a7f](https://github.com/aurora-silicon/linux/commit/fa2e76747a7f3e264fc9c8dfaaa616627cf4e70f), [2ebb1eb92796](https://github.com/aurora-silicon/linux/commit/2ebb1eb927960a96456df1369294d3d6e392a591), [982df2dcc66e](https://github.com/aurora-silicon/linux/commit/982df2dcc66e2cb2012df476c4325c8fce016dda) | DjDeveloperr | drm: carry shared GPUVM and scheduler interfaces used by the G17 review [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/drm_gpuvm.c` | C,H,M,F | +| [c163a8df57f9](https://github.com/aurora-silicon/linux/commit/c163a8df57f986f9cb2f3c3275a1dd2b627d7dba), [751ee0b363e7](https://github.com/aurora-silicon/linux/commit/751ee0b363e7d2733cb452989b6c5fcca182f087), [84f4e1fd0eaf](https://github.com/aurora-silicon/linux/commit/84f4e1fd0eaf13c985e6535d2906b68f1c519947), [ca389fba9b6c](https://github.com/aurora-silicon/linux/commit/ca389fba9b6c7c9354c2f765fe81c5ef5f069f3b) | DjDeveloperr | drm/asahi: correct reviewed lifetime, failure and submission paths [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — RTKIT; `drivers/gpu/drm/asahi/g17/channel.rs` | B,F | +| [3a61b700f19d](https://github.com/aurora-silicon/linux/commit/3a61b700f19d1b2a63017c02359a0eb30360c496), [d297d20ba207](https://github.com/aurora-silicon/linux/commit/d297d20ba207abd5b9c4d7b80aa370dd5a379a4f), [ceb8f96ba823](https://github.com/aurora-silicon/linux/commit/ceb8f96ba8232067893d3956bcc4c335bda7e268), [d9b7b82975e2](https://github.com/aurora-silicon/linux/commit/d9b7b82975e2dad4b9a5941ade2f4debadd61c6c) | DjDeveloperr | drm/asahi: order render timestamp publication independently of GPU retirement [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/completion.rs` | T,C | +| [0f89327272be](https://github.com/aurora-silicon/linux/commit/0f89327272be80ecf44b643c4198614255024367), [212a7b2ccc2d](https://github.com/aurora-silicon/linux/commit/212a7b2ccc2d7a972478f4feed4c19d77cf7e01d), [3bf578fd8958](https://github.com/aurora-silicon/linux/commit/3bf578fd8958a40af5ab8b3ea892749a1dc5b4bf), [e22376a0ca67](https://github.com/aurora-silicon/linux/commit/e22376a0ca679b5359fdef009b2479616619677b) | DjDeveloperr | drm/sched: separate Rust job timeout state from submission state [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/asahi/g17/job.rs` | C,H,M,F | +| [ca7ec4ac8767](https://github.com/aurora-silicon/linux/commit/ca7ec4ac8767714b3a4c28a8bc4e3121e5a346bf), [3810bcb5e35c](https://github.com/aurora-silicon/linux/commit/3810bcb5e35cb34abd3e80d5998c94ddc747cb34), [20560fe15a23](https://github.com/aurora-silicon/linux/commit/20560fe15a23c2dd8db188a074e245c20bcf9858), [3e464649d372](https://github.com/aurora-silicon/linux/commit/3e464649d37234b0f47dfe3c22d3f73cbb6ef45b) | DjDeveloperr | drm/asahi: observe completed render work before a deferred VM failure [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/render/submit.rs` | R,H | +| [6a57525c17e8](https://github.com/aurora-silicon/linux/commit/6a57525c17e8cdf0fcb5965e367c8bca29f6a2f4), [7b30ae9dd4eb](https://github.com/aurora-silicon/linux/commit/7b30ae9dd4eb5a5da852deae9709ef4c10ac09c8), [d20cdd0dba5a](https://github.com/aurora-silicon/linux/commit/d20cdd0dba5a51899ea79cb8ac2df9e14cd3cd8a), [83cf9f5ad0ba](https://github.com/aurora-silicon/linux/commit/83cf9f5ad0baf71001cc0aa9c25c83f27f4a219a) | DjDeveloperr | drm/asahi: publish child page tables before linking them [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/pgtable.rs` | M,D | +| [c6754b181576](https://github.com/aurora-silicon/linux/commit/c6754b181576b5fb5467f9735e7abe4451271681), [03f169480b45](https://github.com/aurora-silicon/linux/commit/03f169480b452a052c707ddc07b36c2be478d645), [da35a7e8b950](https://github.com/aurora-silicon/linux/commit/da35a7e8b9502f0afb30feac9aa41b03b0b7d080), [b321f0e70539](https://github.com/aurora-silicon/linux/commit/b321f0e7053949d9144028d0a465a4f03eb6cc83) | DjDeveloperr | drm/asahi: retire deferred mapping cleanup without allocating [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/asahi/mmu.rs` | C,H,M,F | +| [26b6f8056c9a](https://github.com/aurora-silicon/linux/commit/26b6f8056c9a913656c75753e1d719f5771a2496), [85b9304050b9](https://github.com/aurora-silicon/linux/commit/85b9304050b9331ed8206ab69532c1bb42fe9582), [982da05e5f83](https://github.com/aurora-silicon/linux/commit/982da05e5f831a54800a778df91464ab6d6372b2), [7d57556a8962](https://github.com/aurora-silicon/linux/commit/7d57556a8962dd49aadbb24af772ee5240b8c1c1) | DjDeveloperr | drm/asahi: keep compute timestamps outside shared queue aliases [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/completion.rs` | T,C | +| [a244ed25307c](https://github.com/aurora-silicon/linux/commit/a244ed25307c3d03df0a57def5fffa80d19209fa), [ee1b5a0211c1](https://github.com/aurora-silicon/linux/commit/ee1b5a0211c14b5a799304948bd91fbfebaed35c), [733634bcb0c4](https://github.com/aurora-silicon/linux/commit/733634bcb0c4de6e9885878dc8b1aad744353c83), [e3cfe6674874](https://github.com/aurora-silicon/linux/commit/e3cfe66748744b6aadf6b863b57818adb0721cdd) | DjDeveloperr | drm/shmem-helper: add revocable CPU mappings and idle backing release [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/asahi/gem.rs` | C,H,M,F | +| [9afbd440e531](https://github.com/aurora-silicon/linux/commit/9afbd440e5316ca0831289cd5dec955003554737), [97bab816b9d2](https://github.com/aurora-silicon/linux/commit/97bab816b9d20dc0b7433baf2077bc2225d9e36a), [449bb6888c6f](https://github.com/aurora-silicon/linux/commit/449bb6888c6feeb79e6390cfe00dd7d4d58b6261), [e2b08ebbcadc](https://github.com/aurora-silicon/linux/commit/e2b08ebbcadc3a9ebe2733140e1a88c62af87ba1) | DjDeveloperr | drm/asahi: reclaim idle private VM backing under memory pressure [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [18e1bce60e1a](https://github.com/aurora-silicon/linux/commit/18e1bce60e1a82f3872012d13fe6f5b51684eb38), [babe2ba0c5a6](https://github.com/aurora-silicon/linux/commit/babe2ba0c5a68f0d3b81085736f52f57f32cbbfe), [717d6fe9e152](https://github.com/aurora-silicon/linux/commit/717d6fe9e152af9bdda4a73a81151145cb2681ca), [e82b27902f91](https://github.com/aurora-silicon/linux/commit/e82b27902f91d8c833395a966366d0ffc30769c3) | DjDeveloperr | drm/asahi: free a closed context's render pool once its release is consumed [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/context.rs` | M,C,R | +| [177e22791d7c](https://github.com/aurora-silicon/linux/commit/177e22791d7cfecb3b6c6b6315adf674ef1c9b30), [431b18cf9072](https://github.com/aurora-silicon/linux/commit/431b18cf9072a8618cbab77cfcbde3952cdb5623), [20a55bacd2fb](https://github.com/aurora-silicon/linux/commit/20a55bacd2fb91e52b6bb5cbb3e88e5218b2f726), [fa210660c43c](https://github.com/aurora-silicon/linux/commit/fa210660c43c1e8ffc4c0705792febb70c29e779) | DjDeveloperr | drm/asahi: hold idle descent for every first render pair install [#171](https://github.com/aurora-silicon/linux/pull/171) | Gated — MMIO; `drivers/gpu/drm/asahi/g17/runtime/render.rs` | B,I | +| [06175c4cc1bc](https://github.com/aurora-silicon/linux/commit/06175c4cc1bccd44765a75a70278fedb27ed9fe5), [2f10cc462301](https://github.com/aurora-silicon/linux/commit/2f10cc462301b9d1a1e64bd31468268d90a5d96d), [e94491e80ac1](https://github.com/aurora-silicon/linux/commit/e94491e80ac12de2fb9b741739b560ab17ecbb34), [d7715ebbd2d3](https://github.com/aurora-silicon/linux/commit/d7715ebbd2d3397958f08442d096a2476d32cb6a) | DjDeveloperr | drm/asahi: add GEM_MADVISE and purge advised objects under memory pressure [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/asahi/driver.rs` | C,H,M,F | +| [f338bb052975](https://github.com/aurora-silicon/linux/commit/f338bb05297509f7b87714c5727fb99a8fea373a), [c2e8f1ce84f1](https://github.com/aurora-silicon/linux/commit/c2e8f1ce84f1f72a14e6395fa5246a133211432c), [85271d321506](https://github.com/aurora-silicon/linux/commit/85271d321506ec35cc7e6a82665649e7fd2e4f8c), [c8c442ae6a83](https://github.com/aurora-silicon/linux/commit/c8c442ae6a83502a90fca56d1b8e08e669ce03cb) | DjDeveloperr | drm/asahi: drop the sysfs object and queue-slot counters [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/g17/context.rs` | M,D | +| [3bb318ea84a1](https://github.com/aurora-silicon/linux/commit/3bb318ea84a1cd2d7661948b347270c8eaf71931), [8b1bcc708f82](https://github.com/aurora-silicon/linux/commit/8b1bcc708f820baa04c2998acf8513a0146d59cf), [ed235501b653](https://github.com/aurora-silicon/linux/commit/ed235501b6531eb218d3b058bab11a807ae17713), [152092e32a7f](https://github.com/aurora-silicon/linux/commit/152092e32a7fd7a7b3a96b8e45be7db78934b7d8) | DjDeveloperr | drm/asahi: drop the stalled scheduler-release warning [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/teardown.rs` | C,R,H | +| [110dd19ab7e0](https://github.com/aurora-silicon/linux/commit/110dd19ab7e0263538c8301f903471b1700eac12), [815912e331c3](https://github.com/aurora-silicon/linux/commit/815912e331c32ab6468a108ab34c6b26d60faecb), [9d8357e27590](https://github.com/aurora-silicon/linux/commit/9d8357e275907651adbb7022b812f789ece7d040), [24ce1bcdd8eb](https://github.com/aurora-silicon/linux/commit/24ce1bcdd8ebf0f8664c043a639c0201286f94e5) | DjDeveloperr | drm/asahi: build constant firmware objects from word tables [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | C,R,H | +| [b5a72b8cb8a5](https://github.com/aurora-silicon/linux/commit/b5a72b8cb8a581d84a4c35c2b4ecc11cd50fabfb), [cea457b72a30](https://github.com/aurora-silicon/linux/commit/cea457b72a308addcffbef0555408e3c2f021c7b), [8e98d0432927](https://github.com/aurora-silicon/linux/commit/8e98d0432927d780061fcc55516769f3d830291f), [7e317cd1218e](https://github.com/aurora-silicon/linux/commit/7e317cd1218e5461124fbfc8db7dd72e71253f8e) | DjDeveloperr | drm/asahi: grow the scheduler-release table instead of failing a close [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/teardown.rs` | C,R,H | +| [8afbe186d27a](https://github.com/aurora-silicon/linux/commit/8afbe186d27aab81bc71008838387799f1d9d4df), [c4f9aff1f720](https://github.com/aurora-silicon/linux/commit/c4f9aff1f720075ed3b059e6d82a5d8be5663768), [be6a3b5d938f](https://github.com/aurora-silicon/linux/commit/be6a3b5d938f2c51ef5b6d60990dcefd64cb677f), [18dbc2de6f21](https://github.com/aurora-silicon/linux/commit/18dbc2de6f21c92c49ffafce39a38c8d6439a5b5) | DjDeveloperr | drm/asahi: add a render flag for passes with at most four known primitives [#171](https://github.com/aurora-silicon/linux/pull/171) | Gated — FEW; `drivers/gpu/drm/asahi/g17/command.rs` | R | +| [7d34f2f65865](https://github.com/aurora-silicon/linux/commit/7d34f2f658658c1fd829700f2e14211a1b150ccd), [b1ec7881bf69](https://github.com/aurora-silicon/linux/commit/b1ec7881bf69ab6b1678761dec5552b28aa414e1), [3318d3e23f8e](https://github.com/aurora-silicon/linux/commit/3318d3e23f8e49a5721f9e2da0eea674eece1d2e), [c00f3a06ed2d](https://github.com/aurora-silicon/linux/commit/c00f3a06ed2d0400a5ec210c856f3a382971f033) | DjDeveloperr | drm/asahi: add a render flag that orders only the fragment stage on barriers [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/command.rs` | R,H | +| [e2b1485ec599](https://github.com/aurora-silicon/linux/commit/e2b1485ec599011654ac1f6ccc4dc78350b50566), [763771c2f3d0](https://github.com/aurora-silicon/linux/commit/763771c2f3d0613ecacaeb20d1cfd21809ead7d5), [0751d1c543c9](https://github.com/aurora-silicon/linux/commit/0751d1c543c933f65ec2ce2606d5effd25206721), [64820e0cb952](https://github.com/aurora-silicon/linux/commit/64820e0cb952592fff5708712a4b4766a8a12f4c) | DjDeveloperr | drm/asahi: give back the USC backing of idle retained compute queues [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/freelist.rs` | I,C,R,P | +| [6825b2e8b6f7](https://github.com/aurora-silicon/linux/commit/6825b2e8b6f7d51be5f21a46821541ae8828c6e5), [9e693050e446](https://github.com/aurora-silicon/linux/commit/9e693050e44633bcf0503ec5b864a292d513b563), [ffbf4b465de9](https://github.com/aurora-silicon/linux/commit/ffbf4b465de966c4a5bf3b2e2881fd5678f98bfa), [3db48c0ac600](https://github.com/aurora-silicon/linux/commit/3db48c0ac600b5e919c4d2713dbd84e9b12b4ec4) | DjDeveloperr | drm/asahi: drop an exited owner's context from idle retained compute queues [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/context.rs` | I,C,R,P | +| [dbea31c253f3](https://github.com/aurora-silicon/linux/commit/dbea31c253f3d1845a4998abed2db88177e82d54), [e85d3c35771a](https://github.com/aurora-silicon/linux/commit/e85d3c35771a27fc3721f13d572c2538e898837f), [9964d01e50ad](https://github.com/aurora-silicon/linux/commit/9964d01e50add98a41985adc69ace3bc5dc867f1), [5427403e7135](https://github.com/aurora-silicon/linux/commit/5427403e7135dd886ee52a78d73639bd5a2ccb17) | DjDeveloperr | drm/asahi: keep reclaim away from VMs that are in use or were just restored [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu/lifetime.rs` | M,D | +| [34de15489113](https://github.com/aurora-silicon/linux/commit/34de15489113f71dd93695681d735afaddef10db), [c5fdbf93b7c8](https://github.com/aurora-silicon/linux/commit/c5fdbf93b7c8280dd1d266df8b4298b6b5c0645c), [5eb07ab2539b](https://github.com/aurora-silicon/linux/commit/5eb07ab2539bf7628366644c96a4742c934b9a78), [1c62cd0853b6](https://github.com/aurora-silicon/linux/commit/1c62cd0853b670104424bcfddeee7f7d9279304e) | DjDeveloperr | drm/asahi: count bindings and VM creation as reclaim activity [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu/lifetime.rs` | M,D | +| [9bca711b6542](https://github.com/aurora-silicon/linux/commit/9bca711b65427b7dc0c384e6de2a783cde6e0160), [84e172f23883](https://github.com/aurora-silicon/linux/commit/84e172f23883c27a8eb0cdc85fa81de9c8879eb1), [3202a8b8912f](https://github.com/aurora-silicon/linux/commit/3202a8b8912f39ff7b7c06acc5fd604d5d0c4769), [dec3bc9eb622](https://github.com/aurora-silicon/linux/commit/dec3bc9eb622df05eca30716834c35506ee31f9f) | DjDeveloperr | drm/asahi: set the idle-VM reclaim quiet window to 2000 ms [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu/residency.rs` | M,D | +| [f1018934472b](https://github.com/aurora-silicon/linux/commit/f1018934472b655ba44f1fcbfa2f3be2fdedb1fd), [d2046dcecf61](https://github.com/aurora-silicon/linux/commit/d2046dcecf61aceec6af3d013da811e147e3c410), [4963fa334b24](https://github.com/aurora-silicon/linux/commit/4963fa334b2439a0fc831ee3a5a8db008712a307), [1c1abce16316](https://github.com/aurora-silicon/linux/commit/1c1abce16316282e15f5e471b8ef0b5a33acc038) | DjDeveloperr | drm/asahi: release the VM status mirror object when the VM handle closes [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/g17/status.rs` | M,D | +| [cc0e08ecbe61](https://github.com/aurora-silicon/linux/commit/cc0e08ecbe61eae09d5be1619a0e6373888f7d02), [503bc0e39d66](https://github.com/aurora-silicon/linux/commit/503bc0e39d665ac97ac53e0b78b2851ca61d2242), [8057982690f1](https://github.com/aurora-silicon/linux/commit/8057982690f1f1954a83f62382873a8baf03bb8e), [6af79e6e57ef](https://github.com/aurora-silicon/linux/commit/6af79e6e57ef80d2b93d4c428956ec6cdfb11a3a) | DjDeveloperr | drm/asahi: unmap detached compute owners and free vacated pools off-lock [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/freelist.rs` | M,C,R | +| [90e691e8fe49](https://github.com/aurora-silicon/linux/commit/90e691e8fe49fa13756b6a64d22074c6e4e4a6cb), [b00ba3dcbfeb](https://github.com/aurora-silicon/linux/commit/b00ba3dcbfebf63d761a8018109bef6067a2cf8b), [6f6bef1b9fd7](https://github.com/aurora-silicon/linux/commit/6f6bef1b9fd7c46f0885eea0ece01a811e08c7d1), [0b4bd1537cc7](https://github.com/aurora-silicon/linux/commit/0b4bd1537cc71a1796b976c05cdab24e76549303) | DjDeveloperr | drm/asahi: retire a compute command on its record when its stamp never follows [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [f227c7671c0f](https://github.com/aurora-silicon/linux/commit/f227c7671c0fa54ab73b1477aaf6bbc482e29b8b), [2ceb94e6a1a7](https://github.com/aurora-silicon/linux/commit/2ceb94e6a1a7939d520401fcbb0f41ce5ce9f47a), [e978503f0047](https://github.com/aurora-silicon/linux/commit/e978503f00475864733aefc433d758f0254f401f), [f647d69cc504](https://github.com/aurora-silicon/linux/commit/f647d69cc5043bf1d3389c8e250e78935e5afa24) | DjDeveloperr | drm/asahi: log the first GPU work failure reported to each G17 VM [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — OBS; `drivers/gpu/drm/asahi/g17/status.rs` | O,F | +| [0685cf3905b7](https://github.com/aurora-silicon/linux/commit/0685cf3905b73081560c7c1adc28f76efda774fa), [e8e17790b150](https://github.com/aurora-silicon/linux/commit/e8e17790b150da70debb5e9a1c31027883ad90d3), [85ab080b193f](https://github.com/aurora-silicon/linux/commit/85ab080b193fa481276ebcb9393a93b25024384f), [c8ea00a9ed62](https://github.com/aurora-silicon/linux/commit/c8ea00a9ed621e2e5c7c84461091fda3a9b7cb7b) | DjDeveloperr | drm/asahi: name the completion path in the first-failure log line [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — OBS; `drivers/gpu/drm/asahi/g17/completion.rs` | O,F | +| [ef4fa278cd1a](https://github.com/aurora-silicon/linux/commit/ef4fa278cd1abc81d229ec5853175c35537a962c), [bbbbb8aba0a3](https://github.com/aurora-silicon/linux/commit/bbbbb8aba0a3a08da0b31f31dd0fa1655089c2b0), [ec743d4d34f8](https://github.com/aurora-silicon/linux/commit/ec743d4d34f89fa2ea639801062c957f807c54aa), [5d2a444327af](https://github.com/aurora-silicon/linux/commit/5d2a444327afc38543beb78a252e23661e4d4ecc) | DjDeveloperr | drm/asahi: use one monotonic clock helper across the G17 runtime [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [860e86da2994](https://github.com/aurora-silicon/linux/commit/860e86da2994a4584ba5ce013c5ef5d79cbda111), [bca35f80a993](https://github.com/aurora-silicon/linux/commit/bca35f80a99330132651e21c61f4d0e5da583cd9), [388a083cca9a](https://github.com/aurora-silicon/linux/commit/388a083cca9a1ff7b98f14dc0e598a63494e7d89), [6850c91310a9](https://github.com/aurora-silicon/linux/commit/6850c91310a913b5ff9646261250e62b129f4ce8) | DjDeveloperr | drm/asahi: grow the scheduler-release table silently [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/teardown.rs` | C,R,H | +| [3cd28842a502](https://github.com/aurora-silicon/linux/commit/3cd28842a502864d73cbfeb7018d6f6e2e00dcdc), [033ec9a3b717](https://github.com/aurora-silicon/linux/commit/033ec9a3b71756814007305dfaf437f3e26c1195), [0c8194353943](https://github.com/aurora-silicon/linux/commit/0c8194353943469158f8651c5ec51723e130afa3), [e60f49eede0e](https://github.com/aurora-silicon/linux/commit/e60f49eede0ecf38cb206c4f59ff832b2aacc1d6) | DjDeveloperr | drm/asahi: give back the descriptor ring of vacated compute queues [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | M,C,R | +| [5a98c966960a](https://github.com/aurora-silicon/linux/commit/5a98c966960a4310f741538fea1fc04724acffdb), [c9c1859181ab](https://github.com/aurora-silicon/linux/commit/c9c1859181ab17b15b76c74dc36ef00ad7cac96e), [75535b36cc0e](https://github.com/aurora-silicon/linux/commit/75535b36cc0ea75d394b20e11b993bf22e1efe20), [5d630414e13f](https://github.com/aurora-silicon/linux/commit/5d630414e13f4e017628430820fff73a86e97de9) | DjDeveloperr | drm/asahi: return killed compute queues to service with fresh backing [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | M,C,R | +| [6cae1d77a10a](https://github.com/aurora-silicon/linux/commit/6cae1d77a10aee3e2cd7f52c92eb62b957f6e7ff), [b138b1c78a0e](https://github.com/aurora-silicon/linux/commit/b138b1c78a0ebbba6cef6b3976fc39bfd8b1e3d4), [4457298833f1](https://github.com/aurora-silicon/linux/commit/4457298833f1001a90c15d9277dee87ad6e11927), [16822976f9d3](https://github.com/aurora-silicon/linux/commit/16822976f9d375ddbdc788d968077a15660be5d7) | DjDeveloperr | drm/asahi: give back the render pool backing of evicted idle clients [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/g17/context.rs` | M,D | +| [8d77b4f20652](https://github.com/aurora-silicon/linux/commit/8d77b4f20652f18c7fb4e6ae9184eb88ab7e779e), [39c1fc51692c](https://github.com/aurora-silicon/linux/commit/39c1fc51692c3bfcb37c274ce84dfe812bceaaae), [7e8fe19766fd](https://github.com/aurora-silicon/linux/commit/7e8fe19766fd1b895120a8e7bd5fe78f7aca2a7c), [bff3853f260b](https://github.com/aurora-silicon/linux/commit/bff3853f260b02bdd9605bc6690905cb70aca29f) | DjDeveloperr | drm/asahi: carry a scheduling profile from DRM queue priority to firmware [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — PRIORITY; `drivers/gpu/drm/asahi/g17/context.rs` | Q | +| [a15128aa41ec](https://github.com/aurora-silicon/linux/commit/a15128aa41ecb760c5669ec03d8ffee55213bd6c), [87744d8c30b2](https://github.com/aurora-silicon/linux/commit/87744d8c30b28d86a4cc2c45b2e5009b730ed6b4), [fe676c6f4915](https://github.com/aurora-silicon/linux/commit/fe676c6f4915ec15bb3ea0880101eb5b29cfc255), [009b0fb93ba3](https://github.com/aurora-silicon/linux/commit/009b0fb93ba35380563055aa8bc725f36c505262) | DjDeveloperr | drm/asahi: drop two defensive clippy allowances in the VM binding paths [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [1567322c2517](https://github.com/aurora-silicon/linux/commit/1567322c251710c365592380a195ceb22979d781), [408df60b8f35](https://github.com/aurora-silicon/linux/commit/408df60b8f35a1f510750df51f5e46bfb51d51a4), [3d1be9ce9669](https://github.com/aurora-silicon/linux/commit/3d1be9ce966988190d72dcad1d0fa4dd1b9204c9), [410d8f80e9ab](https://github.com/aurora-silicon/linux/commit/410d8f80e9ab6b405d7f918901fcbe883bb2d9b2) | DjDeveloperr | drm/asahi: build the G17 boot objects region by region [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/initdata.rs` | C,R,H | +| [0e3bd3afd897](https://github.com/aurora-silicon/linux/commit/0e3bd3afd897637d7c42d54c8ca9adc25e5a691c), [2fa38fc95677](https://github.com/aurora-silicon/linux/commit/2fa38fc956777c4e357d44db60d1b7aafaac273f), [456e6601e69d](https://github.com/aurora-silicon/linux/commit/456e6601e69d15067bcccde93498fd58e22977a1), [f5bd61786e3b](https://github.com/aurora-silicon/linux/commit/f5bd61786e3b537fd811f0ea27d2ec33874232b0) | DjDeveloperr | drm/asahi: split the G17 render pair install paths out of build_render_pair [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/render/prepare.rs` | R,H | +| [fe926de1f191](https://github.com/aurora-silicon/linux/commit/fe926de1f191b798b49cbe57e36dfba0e0cc4ac7), [e89e97a5241c](https://github.com/aurora-silicon/linux/commit/e89e97a5241cd22b95cb60304f1e2426698c45af), [ef1251b62428](https://github.com/aurora-silicon/linux/commit/ef1251b62428a647c7ed4311cc6223ca1c8fc80a), [e0fa28b70ca9](https://github.com/aurora-silicon/linux/commit/e0fa28b70ca92d7942809827a7cff7e230d68826) | DjDeveloperr | drm/asahi: split the G17 render publication into preflight, barriers and announce [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render.rs` | R,H | +| [311b9ea8bf83](https://github.com/aurora-silicon/linux/commit/311b9ea8bf835bc7a53866f34b8520ffdc17ef01), [1235a9ee13fe](https://github.com/aurora-silicon/linux/commit/1235a9ee13fe9ed0bf846970e5f5cf9a3049e290), [f76a38f8511f](https://github.com/aurora-silicon/linux/commit/f76a38f8511fd9df3d53e30b899a780c17353d28), [74bc2f09ab74](https://github.com/aurora-silicon/linux/commit/74bc2f09ab74832be661557cacf374ee5fff11d7) | DjDeveloperr | drm/asahi: split the G17 compute publication preflight and queue configuration [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [f22f4cfb5b97](https://github.com/aurora-silicon/linux/commit/f22f4cfb5b97b17cf49e7ca9feb877313156f246), [74e91a7f9ad5](https://github.com/aurora-silicon/linux/commit/74e91a7f9ad5d2f61bd5846713e27838c6f5324a), [49e9337b68a4](https://github.com/aurora-silicon/linux/commit/49e9337b68a41b5f9a3a26d8d72e8b0eecc16844), [5ad7b191f7e6](https://github.com/aurora-silicon/linux/commit/5ad7b191f7e698e3ef140163c6ecf15d3e57ff85) | DjDeveloperr | drm/asahi: size the compute descriptor ring to the kicks that can be in flight [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | M,C,R | +| [bb5149df6a74](https://github.com/aurora-silicon/linux/commit/bb5149df6a74ead7da49be6953c63f4033229ff3), [9d92af630ad2](https://github.com/aurora-silicon/linux/commit/9d92af630ad2f55fe898d653e29c363c131ee4e3), [6d54c0fd885c](https://github.com/aurora-silicon/linux/commit/6d54c0fd885ceded70867de874f27987fc8f69b4), [5f6490414a68](https://github.com/aurora-silicon/linux/commit/5f6490414a683eef149ba6de38cbc27ba62f9a26) | DjDeveloperr | drm/asahi: pack G17 tiler heap blocks contiguously in their backing [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/buffer.rs` | M,C,R | +| [46c70ecebde4](https://github.com/aurora-silicon/linux/commit/46c70ecebde4be2fbd3e82e8311946a156504bf1), [f024202d33e8](https://github.com/aurora-silicon/linux/commit/f024202d33e8832b01e0cff6006fbb9e2b1178a3), [29ff1f70b488](https://github.com/aurora-silicon/linux/commit/29ff1f70b4880e79a8b980adc4d44d4eacb7c7e1), [d2a2c2fa97b9](https://github.com/aurora-silicon/linux/commit/d2a2c2fa97b994595209843720ad78b99b93f16a) | DjDeveloperr | drm/asahi: allocate only the G17 boot pool blocks that are used [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — GMEM; `drivers/gpu/drm/asahi/g17/initdata.rs` | M,C,R | +| [56b8769a1364](https://github.com/aurora-silicon/linux/commit/56b8769a1364bc6c46ab48463afa203a5aec92bb), [614f11f2eb30](https://github.com/aurora-silicon/linux/commit/614f11f2eb30471e02d42f7eab08e9bf4ec4cd41), [c304acb009f0](https://github.com/aurora-silicon/linux/commit/c304acb009f0264d1485596f651495d66efb0fec), [272955b009fe](https://github.com/aurora-silicon/linux/commit/272955b009fe844d4d06361365f166d75b6b451b) | DjDeveloperr | drm/asahi: give back idle G17 tiler heap growth beyond recent use [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — POWER; `drivers/gpu/drm/asahi/g17/buffer.rs` | I,C,R,P | +| [3434c1aab8a6](https://github.com/aurora-silicon/linux/commit/3434c1aab8a6c5d305198347040b8237c4bdce06), [35fe32db0e80](https://github.com/aurora-silicon/linux/commit/35fe32db0e806643f201fc6871095da0e68cfaf8), [1464c4bb1ade](https://github.com/aurora-silicon/linux/commit/1464c4bb1ade2f3fe0625eb93776b26a7f91db96), [c01af7a9e73c](https://github.com/aurora-silicon/linux/commit/c01af7a9e73c789c6a9f98547585a7048927af30) | DjDeveloperr | drm/asahi: give back warm G17 compute pools after ten idle seconds | Active — POWER; `drivers/gpu/drm/asahi/g17/runtime.rs` | I,C,R,P | +| [43a409830c67](https://github.com/aurora-silicon/linux/commit/43a409830c673f190213d2643d50f7500bbf69ab), [882abd400823](https://github.com/aurora-silicon/linux/commit/882abd400823eedce71e3def3c17a3143cd7318b), [57e6637d9f08](https://github.com/aurora-silicon/linux/commit/57e6637d9f087775ee87dcab22dd6c4bb0d4f685), [2d7de7690cc0](https://github.com/aurora-silicon/linux/commit/2d7de7690cc0f9c8d3a74b08c33999593b720aac) | DjDeveloperr | drm/asahi: keep compaction away from GEM pages held for the GPU | Active — MEM; `drivers/gpu/drm/asahi/gem.rs` | M,D | +| [dd109eec40ac](https://github.com/aurora-silicon/linux/commit/dd109eec40ace8e09c436eed1997e302e2b26842), [8bc791526797](https://github.com/aurora-silicon/linux/commit/8bc791526797872caece1891ed792e69dce48438), [76b1755a0610](https://github.com/aurora-silicon/linux/commit/76b1755a06109a66416af0282ec8310dc8a3292b), [6dd0f73fd3b2](https://github.com/aurora-silicon/linux/commit/6dd0f73fd3b2bade5415b31346aadeb2f38d446e) | DjDeveloperr | drm/asahi: complete page-table stores before GPU TLB invalidation | Active — MEM; `drivers/gpu/drm/asahi/g17/kick.rs` | M,D | +| [bec1201676a6](https://github.com/aurora-silicon/linux/commit/bec1201676a6f3b073e8d93791d8c9c7e6c99d7f), [36b80077e7c4](https://github.com/aurora-silicon/linux/commit/36b80077e7c45c57469f9d8cd6ba3e0aa6007cd6), [91e15ed8e7a6](https://github.com/aurora-silicon/linux/commit/91e15ed8e7a681ca293f94e337f59563cd03879d), [019ad857ddfa](https://github.com/aurora-silicon/linux/commit/019ad857ddfa08ed2473eae34db5b8b8d98b068e) | DjDeveloperr | drm/asahi: remove tiler-shrink diagnostic logging | Active — OBS; `drivers/gpu/drm/asahi/g17/buffer.rs` | O,F | +| [692752fb7bf6](https://github.com/aurora-silicon/linux/commit/692752fb7bf60c9e5f83e80dba56779fa48961c1), [3f6a8c31626f](https://github.com/aurora-silicon/linux/commit/3f6a8c31626fc077dbfcea147cd6e30cb28c5e70), [27178c4e373a](https://github.com/aurora-silicon/linux/commit/27178c4e373a23ba1015647f774a3102265c1b27), [d128b5e48465](https://github.com/aurora-silicon/linux/commit/d128b5e484658066c82504df550f6f13abcbd1cc) | DjDeveloperr | drm/asahi: reuse the render state mapping for auxiliary framebuffers | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render/memory.rs` | R,H | +| [a2e5af9f5f03](https://github.com/aurora-silicon/linux/commit/a2e5af9f5f0334dbcdd069cc2944f11313f5d67c), [54101717e1de](https://github.com/aurora-silicon/linux/commit/54101717e1dee1b6fd64f32ae8888ec8429b821e), [c0e47197c71c](https://github.com/aurora-silicon/linux/commit/c0e47197c71c80b57974130a06386e101bbdc7ee) | Ryan Murray | drm/asahi: allocate imported and dumb GEM objects as asahi objects | Active — MEM; `drivers/gpu/drm/asahi/gem.rs` | M,D | +| [c29c639e0934](https://github.com/aurora-silicon/linux/commit/c29c639e09349ce24a2bf768c33476e2e27b2512), [c3a21491589b](https://github.com/aurora-silicon/linux/commit/c3a21491589bb62b2279867805b8e87fd2629e4d), [f2760d2f42ec](https://github.com/aurora-silicon/linux/commit/f2760d2f42ec4ede63abb18918ef4abeb4b3de35) | Dj | drm/asahi: preserve TA prefix dependencies across render pairs | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render.rs` | R,H | +| [7598420667b6](https://github.com/aurora-silicon/linux/commit/7598420667b6be8788d768316e4218ff3c3fc1ff), [d2f283ac0589](https://github.com/aurora-silicon/linux/commit/d2f283ac058952ea84da562a7f6e6a911ebf879a), [1af8de012d22](https://github.com/aurora-silicon/linux/commit/1af8de012d2221144395f900729619b5065b14fd) | Dj | drm/asahi: provision G17P ray traversal pools from Gen1 SCS demand | Gated — RT; `drivers/gpu/drm/asahi/g17/command.rs` | F | +| [e864801fe49c](https://github.com/aurora-silicon/linux/commit/e864801fe49c9dc6d1fd6e4d1b6c2a607e672f71), [a075a1fab446](https://github.com/aurora-silicon/linux/commit/a075a1fab446a49270d387274f7793d3e9ee6560), [6ffb2dc7a658](https://github.com/aurora-silicon/linux/commit/6ffb2dc7a658e2e6bcaf48a75461b8d7e84549bf) | DjDeveloperr | drm/asahi: recycle settled quarantined G17 compute queues [#305](https://github.com/aurora-silicon/linux/pull/305) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [ae76590d6ea0](https://github.com/aurora-silicon/linux/commit/ae76590d6ea0313a4741fdce5025f6c336eaf90c), [b9fd334a9c96](https://github.com/aurora-silicon/linux/commit/b9fd334a9c96f58640e0ef6ce2010749b85e7c4f), [8ad79fc25869](https://github.com/aurora-silicon/linux/commit/8ad79fc25869a9a5cd36262cc8a702b4327cf437) | DjDeveloperr | drm/asahi: g17: replay render groups firmware retired without executing [#307](https://github.com/aurora-silicon/linux/pull/307) | Active — REC; `drivers/gpu/drm/asahi/g17/job.rs` | F,Q | +| [41786756b43d](https://github.com/aurora-silicon/linux/commit/41786756b43da732fa85c6ac3e23a33e3baa9335), [0a2cdd456013](https://github.com/aurora-silicon/linux/commit/0a2cdd456013347efd1bc2a73d79e291d2d77fbc), [167852510065](https://github.com/aurora-silicon/linux/commit/1678525100652694b0aaee977ea2012b487d6a51) | DjDeveloperr | drm/asahi: g17: never kill compute commands with ray traversal scratch [#308](https://github.com/aurora-silicon/linux/pull/308) | Gated — RT; `drivers/gpu/drm/asahi/g17/queue/compute/exit.rs` | F | +| [d3f4f5dd1b59](https://github.com/aurora-silicon/linux/commit/d3f4f5dd1b592ba925f469133ebac876a101ddb7), [ec3aa2ff51b8](https://github.com/aurora-silicon/linux/commit/ec3aa2ff51b8ad6f9f39c70bf563866ee81baecb), [48b2631fdaab](https://github.com/aurora-silicon/linux/commit/48b2631fdaab66dc7bd850b3006c0f85a73aba0e) | DjDeveloperr | drm/asahi: g17: ray queries in render stages [#309](https://github.com/aurora-silicon/linux/pull/309) | Gated — RT; `drivers/gpu/drm/asahi/g17/command.rs` | F | +| [079f81d5a637](https://github.com/aurora-silicon/linux/commit/079f81d5a637e459a33e8f75a97b91f2454f1499), [2d1fcbe1a980](https://github.com/aurora-silicon/linux/commit/2d1fcbe1a9808f52f2427e4c1237b2b0eba7b7fb), [03426fb46c9c](https://github.com/aurora-silicon/linux/commit/03426fb46c9ce29e5b58db52cfefd09fac156fea) | Ryan Murray | soc: apple: mailbox, rtkit: Allow holding received messages | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [a98bb5de4b2e](https://github.com/aurora-silicon/linux/commit/a98bb5de4b2e6dd5d54150008bd322f97c8dbd0d), [c623f821fdfa](https://github.com/aurora-silicon/linux/commit/c623f821fdfaada5df34d2a79fcc4586b0d7323b), [23b2b6f4a1c9](https://github.com/aurora-silicon/linux/commit/23b2b6f4a1c93dbc90768e271c92bc914e5d676f) | Ryan Murray | soc: apple: pmp: log tunables the boot loader does not provide at debug level [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [689592f3219f](https://github.com/aurora-silicon/linux/commit/689592f3219f4733c038cdf72e5a911084d22521), [bc1d13129d6d](https://github.com/aurora-silicon/linux/commit/bc1d13129d6d88ee93515eb547967d87170e5da3), [f80758084279](https://github.com/aurora-silicon/linux/commit/f80758084279064eb1c60e6bfa56f7649bb86a4d) | Ryan Murray | iommu: apple-dart: Demote informational probe messages to debug [#314](https://github.com/aurora-silicon/linux/pull/314) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [e28469b8a38e](https://github.com/aurora-silicon/linux/commit/e28469b8a38e4102fd5c5b877bf51439fc00fca3), [00baae48e464](https://github.com/aurora-silicon/linux/commit/00baae48e464c44cb754d52552d3bd1f431d52f7) | Ryan Murray | iommu: apple-dart: Invalidate every stream in a domain bitmap | Active — DART; `drivers/iommu/Kconfig` | D,B | +| [aade7be0a12a](https://github.com/aurora-silicon/linux/commit/aade7be0a12a38ae8bb276fac80df27eaea97e32), [b13ede3059b0](https://github.com/aurora-silicon/linux/commit/b13ede3059b0bc51bca6033e405606e525ec2259), [4cf2113dc363](https://github.com/aurora-silicon/linux/commit/4cf2113dc36329544edc54b09dc775c5888c3975) | Ryan Murray | soc: apple: mcc: factor out disabling and flushing planes [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — MCC; `drivers/soc/apple/mcc.c` | M,P | +| [ef010a009b3f](https://github.com/aurora-silicon/linux/commit/ef010a009b3ffb15af9794ed37083d1ad2c4facb), [0cba4dd60674](https://github.com/aurora-silicon/linux/commit/0cba4dd60674aa9ded2c27eaae1ff7bf4f1e243e), [8bf158a74ea7](https://github.com/aurora-silicon/linux/commit/8bf158a74ea7e2a5a7e42082fc882d3508119a09) | Ryan Murray | soc: apple: mcc: disable the planes already enabled when enabling fails [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — MCC; `drivers/soc/apple/mcc.c` | M,P | +| [a099186592d6](https://github.com/aurora-silicon/linux/commit/a099186592d6aa010ab7da6d71c7c88d1801345d), [e46e30ff7457](https://github.com/aurora-silicon/linux/commit/e46e30ff7457d01b6e9cb97e8d7a5b0fd8fe20e6), [4653ef5a873f](https://github.com/aurora-silicon/linux/commit/4653ef5a873f16c68a1e9e9080a41395628ca9d4) | Ryan Murray | pmdomain: apple: pmc: undo a partial agent enable [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — PMC; `drivers/pmdomain/apple/pmc.c` | P,C,R | +| [49bb592b9e9f](https://github.com/aurora-silicon/linux/commit/49bb592b9e9f626d06af3296172be8d3aa99a320), [43f4894e7c18](https://github.com/aurora-silicon/linux/commit/43f4894e7c18a46f5e379dd994f0fbd5def67ed7), [a86e8e5637c0](https://github.com/aurora-silicon/linux/commit/a86e8e5637c07504ca09b7bfa5639492af065d8c) | Ryan Murray | pmdomain: apple: pmc: restore the AP's own DCS vote when a limit is lifted [#317](https://github.com/aurora-silicon/linux/pull/317) | Gated — DCS; `drivers/pmdomain/apple/Kconfig` | P | +| [75984fc70c8f](https://github.com/aurora-silicon/linux/commit/75984fc70c8f252d96910dad4cd8635f4977b74f), [6351fe2e5e29](https://github.com/aurora-silicon/linux/commit/6351fe2e5e29f4447d997214d785f10885ef7538), [dba445e67326](https://github.com/aurora-silicon/linux/commit/dba445e6732685313e036e73dc2753c7ed8cbcb6) | Ryan Murray | soc: apple: clpc: remove the debugfs override of the floor [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [9e4e936ed101](https://github.com/aurora-silicon/linux/commit/9e4e936ed10105188f37f3f8b2c27042e2e2ec58), [c47472a2343b](https://github.com/aurora-silicon/linux/commit/c47472a2343b6c35235e48db178cf76ae94c1f73), [9dfd51203aa8](https://github.com/aurora-silicon/linux/commit/9dfd51203aa80ce8867d98ba03c79fbb283039c2) | Ryan Murray | soc: apple: clpc: make the demand policy per-SoC data, drop the GPU input [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [64290b216821](https://github.com/aurora-silicon/linux/commit/64290b21682151a3eb4c93d5f666d07f56aae87c), [d19ce09e3508](https://github.com/aurora-silicon/linux/commit/d19ce09e35089ec81bcc82f55198498901df10ac), [10e182579152](https://github.com/aurora-silicon/linux/commit/10e1825791523747c5f0d4ac44570fe4418ff586) | Ryan Murray | pmdomain: apple: pmgr-pwrstate: log always-on domains left off at info level [#317](https://github.com/aurora-silicon/linux/pull/317) | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [57dafd4b412f](https://github.com/aurora-silicon/linux/commit/57dafd4b412f8b49be5362a98503be11138cd388), [f86db4089cee](https://github.com/aurora-silicon/linux/commit/f86db4089cee44d2f54de73f60aa32c8a07ffd3a), [1ef9c2113ecd](https://github.com/aurora-silicon/linux/commit/1ef9c2113ecdf2851908e4bd9793876d92152d27) | Ryan Murray | soc: apple: rtkit: Do not warn when buffers are retained on purpose [#314](https://github.com/aurora-silicon/linux/pull/314) | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [5ebe92d17a32](https://github.com/aurora-silicon/linux/commit/5ebe92d17a32112b13e74553bab5b0e0b7c0e147), [7b29e7caccc4](https://github.com/aurora-silicon/linux/commit/7b29e7caccc4896949b0dff4dd8caf7c5fa3f0c0), [9779b2457e8f](https://github.com/aurora-silicon/linux/commit/9779b2457e8f13b0f257964570c9883f079e65f6) | Ryan Murray | pmdomain: apple: pmc: keep the DCS limit state when the rail enable fails [#345](https://github.com/aurora-silicon/linux/pull/345) | Gated — DCS; `drivers/pmdomain/apple/pmc.c` | P | +| [962dacdbb6ad](https://github.com/aurora-silicon/linux/commit/962dacdbb6ad38d9fc723d9f8ea3b3d8dee0e6a9), [4209657d7d94](https://github.com/aurora-silicon/linux/commit/4209657d7d948671c9872e1cbb53f66774793607), [1c797d3eb5cb](https://github.com/aurora-silicon/linux/commit/1c797d3eb5cb79f7e80696b4d7c93800172771c0) | Ryan Murray | cpufreq: apple-soc: say why a cluster with verified transitions fails init [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [854b2e6ec82e](https://github.com/aurora-silicon/linux/commit/854b2e6ec82ea030f3dcfd8ff024393019dd3818), [5a02a0cd2865](https://github.com/aurora-silicon/linux/commit/5a02a0cd28651e4eb3f2eede40af991d8d265a51), [13857139ed46](https://github.com/aurora-silicon/linux/commit/13857139ed4643ef532281808bc8035ad64c9a10) | Ryan Murray | soc: apple: mcc: print the boot DMA device before dropping its node [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — MCC; `drivers/soc/apple/mcc.c` | M,P | +| [e157c990ad98](https://github.com/aurora-silicon/linux/commit/e157c990ad98e3008b552e45d0c85448f95e4266), [ec4fc264985c](https://github.com/aurora-silicon/linux/commit/ec4fc264985c5505be71434f96cc949d0037801f), [0b65493ccd0f](https://github.com/aurora-silicon/linux/commit/0b65493ccd0f4a779e592258032cb40ce03d1a1c) | Ryan Murray | pmdomain: apple: pmgr-pwrstate: tidy the rate-limited error messages [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [3a9f56606ec3](https://github.com/aurora-silicon/linux/commit/3a9f56606ec3001c7cd7df137cf49a6ae2db0157), [3762bb8f345d](https://github.com/aurora-silicon/linux/commit/3762bb8f345de262f1efca74de864457a3adc9aa), [a516c3f7c3f8](https://github.com/aurora-silicon/linux/commit/a516c3f7c3f85aeb05efe5e3aff5b7890c7d022c) | Ryan Murray | iommu: apple-dart: Invalidate every DART even if a TLB sync step fails [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [8a2875a6789d](https://github.com/aurora-silicon/linux/commit/8a2875a6789d2a4f1452beafd50e4de134b30683), [0f997e1c9354](https://github.com/aurora-silicon/linux/commit/0f997e1c9354b9aa18c7ae13cdf61328b1f50d9d), [df5213734e31](https://github.com/aurora-silicon/linux/commit/df5213734e31c372fc36a2375539e4d69b5f815a) | Ryan Murray | soc: apple: rtkit: Only parse an initial crash log in inherited sessions [#345](https://github.com/aurora-silicon/linux/pull/345) | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [d6caa25b65d2](https://github.com/aurora-silicon/linux/commit/d6caa25b65d2ca7fc7315ee568e0cbd250a9593c), [f501cf5f491d](https://github.com/aurora-silicon/linux/commit/f501cf5f491da7527d71b86cd164e094aac050fd), [82494bf5c6b0](https://github.com/aurora-silicon/linux/commit/82494bf5c6b07739782eeac837b9f6a96c70ca36) | Ryan Murray | iommu/apple-dart: Do not reset a DART that kept its state across sleep [#336](https://github.com/aurora-silicon/linux/pull/336) | Active — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [91d9f93cf842](https://github.com/aurora-silicon/linux/commit/91d9f93cf842a892913e7ce56218ddc27292a6c1), [dc02d89a1465](https://github.com/aurora-silicon/linux/commit/dc02d89a1465719e6ab3b3a5e31dbea08e1a8f68), [39bc76211b30](https://github.com/aurora-silicon/linux/commit/39bc76211b30ca23b29eb740ffdf0ae8cf2ed5d3) | Ryan Murray | iommu/apple-dart: Keep a retained DART in bypass from being reset [#336](https://github.com/aurora-silicon/linux/pull/336) | Active — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [b6b28c111cff](https://github.com/aurora-silicon/linux/commit/b6b28c111cff20b5312fee34ca544ce6f7fd00a5), [469fd41db0a3](https://github.com/aurora-silicon/linux/commit/469fd41db0a33df014d0523648c5e1f052895c9a), [d9fc21447419](https://github.com/aurora-silicon/linux/commit/d9fc214474191909db140a5ba565a014819bd6de) | Ryan Murray | iommu/apple-dart: Restore the translations when the reset on resume fails [#336](https://github.com/aurora-silicon/linux/pull/336) | Active — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [4bfe19d0ad80](https://github.com/aurora-silicon/linux/commit/4bfe19d0ad8042e95b784fe2e439e15c8f869ac5), [cd1ff978d660](https://github.com/aurora-silicon/linux/commit/cd1ff978d6600a354b93fd829d9c9588715c44f1), [43c7fc8f82a6](https://github.com/aurora-silicon/linux/commit/43c7fc8f82a65f2e3807f41f03421df2b27ed3de) | Ryan Murray | irqchip/apple-aic: Allow interrupts to wake the system | Active — AIC; `drivers/irqchip/irq-apple-aic.c` | O,C,I | +| [345f6329c88b](https://github.com/aurora-silicon/linux/commit/345f6329c88b1ea97ecf1c373bc573a9c20947e7), [eee098196369](https://github.com/aurora-silicon/linux/commit/eee098196369f3a2aebe4f99333c53d538caa1c5) | Ryan Murray | drm/asahi: queue background work on system_dfl_wq | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [e04141b6136c](https://github.com/aurora-silicon/linux/commit/e04141b6136c63f61679eda781cabe349297d58e), [a741120e3ca0](https://github.com/aurora-silicon/linux/commit/a741120e3ca0d349dffab6cc32086935ecb98d81) | Chris Kearney | soc: apple: validate PMP boot arguments before patching | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [8a74c2db9581](https://github.com/aurora-silicon/linux/commit/8a74c2db9581fd106dc90be11fb1a1e075bf07fa), [d67dbeedca63](https://github.com/aurora-silicon/linux/commit/d67dbeedca63d1389fb5754d419fffae0c678155) | Chris Kearney | soc: apple: bound PMP arguments by the mapped SRAM size | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [6c53c0ff9715](https://github.com/aurora-silicon/linux/commit/6c53c0ff9715f055de8af928ed57277f1f0f30a2), [687f1c4f2190](https://github.com/aurora-silicon/linux/commit/687f1c4f21906c698f38c29c467f4aa74425509e) | Eryk Wieliczko | soc: apple: acknowledge serialized PMP device power commands | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [5a68774e87a8](https://github.com/aurora-silicon/linux/commit/5a68774e87a8baa711c211757b709966b2193be5), [fefec69b9fca](https://github.com/aurora-silicon/linux/commit/fefec69b9fca69f98a026a4dbdf9bde89b64544b) | Chris Kearney | soc: apple: retry unsent RTKit endpoint starts once | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [dc7d0b49a07d](https://github.com/aurora-silicon/linux/commit/dc7d0b49a07df88aab2ee9cbb3fae17db8694f2b), [5bd0a91f565c](https://github.com/aurora-silicon/linux/commit/5bd0a91f565cf815b9f999b3e704edba2563aac8) | Marius Gehrmann | soc: apple: rtkit: Honour apple,sync-syslog-ack | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [d67d380d1519](https://github.com/aurora-silicon/linux/commit/d67d380d1519babe4b9d080830e31bca56da4d8f), [9d0290148561](https://github.com/aurora-silicon/linux/commit/9d029014856181eb6190324b8300d82590ab2e8b) | Chris Kearney | soc: apple: rtkit: cap the kdebug buffer, and take a late completion | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [1369d18b4ec7](https://github.com/aurora-silicon/linux/commit/1369d18b4ec7bf0a631bc68fbdf7d894f2a4c542), [0d9ac5799dd4](https://github.com/aurora-silicon/linux/commit/0d9ac5799dd4dffe26513b100e93930bb3833a53) | Eryk Wieliczko | soc: apple: acknowledge OSLog progress in negotiated and inherited sessions | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [e47d06ceb56f](https://github.com/aurora-silicon/linux/commit/e47d06ceb56fd73444f3e17954f961dce5edfdcf), [47e137e98667](https://github.com/aurora-silicon/linux/commit/47e137e986675ca44843155e66b731c167932749) | Chris Kearney | iommu/apple-dart: reserve retained firmware tables and root slots | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [e663049674c8](https://github.com/aurora-silicon/linux/commit/e663049674c81f6818a88b594f029d0bf9f45ccc), [058d2b7355cf](https://github.com/aurora-silicon/linux/commit/058d2b7355cffe72dad9053486a26ae2822d51a2) | DjDeveloperr | soc: apple: finish the merge of DART, QSPI and SMC hwmon | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [d97a27e94fd8](https://github.com/aurora-silicon/linux/commit/d97a27e94fd8967ce1c3846dcc71628a8b39db36), [6dbfbcb21eb0](https://github.com/aurora-silicon/linux/commit/6dbfbcb21eb09d07ae59037a1ceca8cc3500841f) | Eryk Wieliczko | drm/asahi: validate GEM size before page rounding | Active — MEM; `drivers/gpu/drm/asahi/gem.rs` | M,D | +| [6d457ae0a70c](https://github.com/aurora-silicon/linux/commit/6d457ae0a70c583dc760d05ac552829d7370060f), [b038d2767d6e](https://github.com/aurora-silicon/linux/commit/b038d2767d6e08c0dae43c87da89dfd77d719f7c) | Chris Kearney | arm64: dts: apple: t8140: drop the t6000 fallback from the GFX report | Active translated data — FW | B,P | +| [496040e469aa](https://github.com/aurora-silicon/linux/commit/496040e469aa1ea3457c89cbd677d9f964614cca), [17bf156987c5](https://github.com/aurora-silicon/linux/commit/17bf156987c5f7be583bd5efc86430b1055efc36) | Chris Kearney | pmdomain: apple: pmp-report: match T8140 report entries by compatible | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [e033fe71b24d](https://github.com/aurora-silicon/linux/commit/e033fe71b24da2b2aabc90b9c5faa5bd0f497d9f), [5f7925d83ca6](https://github.com/aurora-silicon/linux/commit/5f7925d83ca680ba7760dbd16b9e0cb61029161a) | Chris Kearney | drm/asahi: keep the range TLBI base address in its field | Gated — UAT; `drivers/gpu/drm/asahi/mem.rs` | D | +| [6582e3d5ef6f](https://github.com/aurora-silicon/linux/commit/6582e3d5ef6f47ff7e0a11d3fffa47f15ef22a44), [f7132eb38e81](https://github.com/aurora-silicon/linux/commit/f7132eb38e81e5af14711b3da87aa6cf96b3ba98) | Ryan Murray | iommu: apple-dart: Factor out the DT DMA window parse | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [fc7981e78871](https://github.com/aurora-silicon/linux/commit/fc7981e78871a2ec91b38835bc84e03611b4e860), [ff11ec1083af](https://github.com/aurora-silicon/linux/commit/ff11ec1083af04c76c1d91fcebc78333681cf519) | Chris Kearney | pmdomain: apple: validate PMP startup tables before seeding | Active — PMP; `drivers/pmdomain/apple/.kunitconfig.apple-pmp` | P,B | +| [4c673adf2c11](https://github.com/aurora-silicon/linux/commit/4c673adf2c110c8264da8b0130eaf181fd6bd560), [782906a65540](https://github.com/aurora-silicon/linux/commit/782906a65540c569f6afa8b49b8c6ac22c9e8dc5) | Ryan Murray | pmdomain: apple: pmp-report: select OF_DYNAMIC | Active — PMP; `drivers/pmdomain/apple/Kconfig` | P,B | +| [f78d7690bfd2](https://github.com/aurora-silicon/linux/commit/f78d7690bfd2d927462c7af34242d6476a0cf59c), [a41d634596e5](https://github.com/aurora-silicon/linux/commit/a41d634596e536031e719b5ea8a3082220e8ec61) | Ryan Murray | drm/asahi: mmu: preallocate enough GPUVAs to unmap across two mappings | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [10fc7af5e0b8](https://github.com/aurora-silicon/linux/commit/10fc7af5e0b897112afaa5fc9ab791f241244969), [755ac9ed1425](https://github.com/aurora-silicon/linux/commit/755ac9ed142576a6e27da54a39b68290e861b34b) | DjDeveloperr | drm/asahi: g17: move the G17P UAPI past the M3 and J613 numbers | Active translated data — FW; `drivers/gpu/drm/asahi/g17/command.rs` | B,C,R | +| [6d5b51d6e818](https://github.com/aurora-silicon/linux/commit/6d5b51d6e818fc0b8915b43a030854d6d96ff330), [62aae2f0f8cb](https://github.com/aurora-silicon/linux/commit/62aae2f0f8cb52a40b2cf4ea21212ad2badd267c) | Ryan Murray | drm/asahi: uapi: use integration/aurora's wording | Active translated data — FW | B,C,R | +| [ff25cb1691ff](https://github.com/aurora-silicon/linux/commit/ff25cb1691ffa6e285dbcb014992e3838f4ad6fe) | Ryan Murray | drm/asahi: Adapt the lifetime and module-ownership changes to Linux 7.3 [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — DRM; `drivers/gpu/drm/asahi/g17/job.rs` | C,H,M,F | +| [2348e8c3acd2](https://github.com/aurora-silicon/linux/commit/2348e8c3acd2920ca583d801645e8387fd80c7f2) | Ryan Murray | drm/asahi: regs: Hold the G17 SGX mapping as a DevresIoMem [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — MEM; `drivers/gpu/drm/asahi/regs.rs` | M,D | +| [d4128b7c28ce](https://github.com/aurora-silicon/linux/commit/d4128b7c28ce177d227ff5488145e8eedd970610) | Ryan Murray | drm/asahi: make SG backing leases revocable [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — DRM; `drivers/gpu/drm/asahi/gem.rs` | C,H,M,F | +| [464cb2e30ae3](https://github.com/aurora-silicon/linux/commit/464cb2e30ae395d13723d161f2aef41066dbb79d) | Ryan Murray | drm/asahi: attribute GPU memory to its creator and limit it [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — MEM; `drivers/gpu/drm/asahi/asahi_core.rs` | M,D | +| [47f408da5996](https://github.com/aurora-silicon/linux/commit/47f408da5996da161de66282da83550bf436450e) | Ryan Murray | drm/asahi: clean up Clippy diagnostics in Aurora additions [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — OBS; `drivers/gpu/drm/asahi/g17/command.rs` | O,F | +| [e44a6b59f5d2](https://github.com/aurora-silicon/linux/commit/e44a6b59f5d248ee1a026b9dbb6dbd244e35a1d7) | Ryan Murray | soc: apple: pmp: use lossless command and bootargs conversions | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [761626d47af3](https://github.com/aurora-silicon/linux/commit/761626d47af3eb6da3ff8c998373646349aa7169) | Ryan Murray | drm/apple: quiet routine H17P external run-mode syslog records | Active — DISPLAY; `drivers/gpu/drm/apple/Makefile` | B | +| [ac38eb0f57a1](https://github.com/aurora-silicon/linux/commit/ac38eb0f57a17f59eb2ce8158df1fb81aeff2955) | Ryan Murray | drm/asahi: g17: wait for a compute command's stamp before releasing it [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [1e3b9caebc7b](https://github.com/aurora-silicon/linux/commit/1e3b9caebc7b1924acc3643222ff95b85e62bdb7) | Ryan Murray | drm/asahi: memacct: Read the module parameters by value on Linux 7.3 [#375](https://github.com/aurora-silicon/linux/pull/375) | Active — MEM; `drivers/gpu/drm/asahi/memacct.rs` | M,D | +| [db2943a27f88](https://github.com/aurora-silicon/linux/commit/db2943a27f88dcd011ae97498cbf2250a4245463) | Jonathan Goyvaerts | soc: apple: rtkit: Log unknown oslog messages at debug level | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [1797c344df7f](https://github.com/aurora-silicon/linux/commit/1797c344df7f7631b2f9be7c9be1a55d8852b164) | Jonathan Goyvaerts | pmdomain: apple: pmp-report: Add T6040 support [#374](https://github.com/aurora-silicon/linux/pull/374) | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [927c31856676](https://github.com/aurora-silicon/linux/commit/927c318566760e7b4845d36904022c38271b925d) | Jonathan Goyvaerts | soc: apple: pmp: Add T6040 support [#374](https://github.com/aurora-silicon/linux/pull/374) | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [7fcc7fd38c43](https://github.com/aurora-silicon/linux/commit/7fcc7fd38c43b6aa64ea2270dcd32cdd57d19f37) | Ryan Murray | drm/asahi: carry G17 admission prerequisites to 7.3 [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — MEM; `drivers/gpu/drm/asahi/g17/freelist.rs` | M,D | +| [c9f72dc51116](https://github.com/aurora-silicon/linux/commit/c9f72dc51116c12b3aa840185eee5a45f4b298b5) | Ryan Murray | drm/asahi: carry G17 pool-release recovery and stamp-wait fixup to 7.3 [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [10e9da2303fc](https://github.com/aurora-silicon/linux/commit/10e9da2303fc79dc205989075855350020bf115b) | Ryan Murray | drm/asahi: hand abandoned compute growth back to witness settlement [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — SHARED; `drivers/gpu/drm/asahi/g17/freelist.rs` | C,H | +| [e933ad436de0](https://github.com/aurora-silicon/linux/commit/e933ad436de06981d95e9de6ce5626f4ba99e6ff) | Ryan Murray | drm/asahi: borrow the compute pool mutably for header replacement [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — GMEM; `drivers/gpu/drm/asahi/g17/freelist.rs` | M,C,R | +| [04358c1ebe86](https://github.com/aurora-silicon/linux/commit/04358c1ebe86cfbfdc926cfb41ff3e3549248279) | Ryan Murray | drm/asahi: hold failed growth reservations until aliases have gone [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | C,R,H | +| [d765e50ada90](https://github.com/aurora-silicon/linux/commit/d765e50ada90e7f00a4ad608c9ff027d0a07a82a) | Ryan Murray | drm/asahi: drop tentative growth aliases before returning a fresh QID [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime.rs` | C,R,H | +| [780956abdb5f](https://github.com/aurora-silicon/linux/commit/780956abdb5fe59c76ca7343bc7f19770df658dd) | Ryan Murray | drm/asahi: compose shared capacity admission with accepted recovery port [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — MEM; `drivers/gpu/drm/asahi/g17/buffer.rs` | M,D | +| [b52bd36805e0](https://github.com/aurora-silicon/linux/commit/b52bd36805e0c35bac5c8255aae8cfad7cce5dd7) | Ryan Murray | drm/asahi: give native ray-query jobs a bounded execution allowance [#377](https://github.com/aurora-silicon/linux/pull/377) | Gated — RT; `drivers/gpu/drm/asahi/g17/job.rs` | F | +| [a5bc25b2ce19](https://github.com/aurora-silicon/linux/commit/a5bc25b2ce1997452676894fc8c9b401c7396e6c) | Ryan Murray | drm/asahi: cover render helpers and AS builds with bounded execution [#377](https://github.com/aurora-silicon/linux/pull/377) | Gated — RT; `drivers/gpu/drm/asahi/g17/command.rs` | F | +| [9827b6bb277a](https://github.com/aurora-silicon/linux/commit/9827b6bb277af5912490229f6fe56d3c575dafe3) | Ryan Murray | drm/asahi: bound repeated timeout recovery and G17 mailbox stalls [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — MBOX; `drivers/gpu/drm/asahi/g17/dependency.rs` | B,O | +| [c314dbb5bd42](https://github.com/aurora-silicon/linux/commit/c314dbb5bd4284697bd065db87eb038021b18125) | Ryan Murray | drm/asahi: expire background pool-release and render-replay polls [#377](https://github.com/aurora-silicon/linux/pull/377) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [6c2d17ff4079](https://github.com/aurora-silicon/linux/commit/6c2d17ff40796899d7190580cabaeea749c73d0a) | Ryan Murray | drm/sched: initialise free_guilty in drm_sched_init() [#381](https://github.com/aurora-silicon/linux/pull/381) | Active — DRM; `drivers/gpu/drm/scheduler/sched_main.c` | C,H,M,F | +| [0d1871c3cfde](https://github.com/aurora-silicon/linux/commit/0d1871c3cfde9cc45b8bc244dde4775dbf4e05c6) | Ryan Murray | rust: drm: sched: zero the scheduler before drm_sched_init() [#381](https://github.com/aurora-silicon/linux/pull/381) | Active — DRM; `rust/kernel/drm/sched.rs` | C,H,M,F | +| [24399672582c](https://github.com/aurora-silicon/linux/commit/24399672582c155df35cadf8cb81a3397958f9e2) | Ryan Murray | drm/asahi: retry G17 retained compute publication pressure [#382](https://github.com/aurora-silicon/linux/pull/382) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,H | +| [5ae95851753d](https://github.com/aurora-silicon/linux/commit/5ae95851753d377e13b7e3ac9012452a7fdaccc9) | Jonathan Goyvaerts | pmdomain: apple: pmc: Add the T6040 voter interface [#385](https://github.com/aurora-silicon/linux/pull/385) | Active — PMC; `drivers/pmdomain/apple/pmc-test.c` | P,C,R | +| [7bbd2ad37554](https://github.com/aurora-silicon/linux/commit/7bbd2ad375543683c070aca6487fae75a79fc747) | Jonathan Goyvaerts | soc: apple: clpc: Add the T6040 demand table [#385](https://github.com/aurora-silicon/linux/pull/385) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [b3765bd38ecb](https://github.com/aurora-silicon/linux/commit/b3765bd38ecbfaa5368b760c9d908964abf8c7a5) | Jonathan Goyvaerts | soc: apple: mcc: Allow up to 16 cache planes [#385](https://github.com/aurora-silicon/linux/pull/385) | Active — MCC; `drivers/soc/apple/Kconfig` | M,P | +| [f236c33daca6](https://github.com/aurora-silicon/linux/commit/f236c33daca696a326bd652799bd189ac2b6421f) | Jonathan Goyvaerts | soc: apple: clpc: Request a state only after sustained load [#385](https://github.com/aurora-silicon/linux/pull/385) | Active — CLPC; `drivers/soc/apple/clpc.c` | P,C,R,I | +| [32960bbd0e9c](https://github.com/aurora-silicon/linux/commit/32960bbd0e9cf24931f18d25131eef24f74f3d31) | Jonathan Goyvaerts | iommu/apple-dart: Mirror the firmware's display mappings on T6040 [#386](https://github.com/aurora-silicon/linux/pull/386) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [833c6ed10ffc](https://github.com/aurora-silicon/linux/commit/833c6ed10ffc2ac2d38b7bbb0d85dcc2b8e171df) | DjDeveloperr | drm/asahi: reduce minimum G17 target record backing [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — MEM; `drivers/gpu/drm/asahi/hw/t8140.rs` | M,D | +| [145361fdc90e](https://github.com/aurora-silicon/linux/commit/145361fdc90e82f4bddf6234ec46d0e023f41f86) | DjDeveloperr | drm/asahi: order G17 queue owner changes after scheduler releases [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | C,R,H | +| [1d478ee6c06b](https://github.com/aurora-silicon/linux/commit/1d478ee6c06b56b48782d08b693d9aa9e5c43435) | DjDeveloperr | drm/asahi: keep G17 render kick ring storage for the life of the device [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/kick.rs` | R,H | +| [7304ddbf6d4d](https://github.com/aurora-silicon/linux/commit/7304ddbf6d4d47b7976efcd20623524c9c53b0b1) | DjDeveloperr | drm/asahi: keep G17 render capacity waits from stopping the runtime [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/freelist.rs` | R,H | +| [72d57c0b9ac9](https://github.com/aurora-silicon/linux/commit/72d57c0b9ac9b8d5fcd58c59eebfa7f9c605399c) | DjDeveloperr | drm/asahi: wait for G17 contexts of closing queues at queue creation [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — REC; `drivers/gpu/drm/asahi/g17/capacity.rs` | F,Q | +| [a4351b50cd8d](https://github.com/aurora-silicon/linux/commit/a4351b50cd8d6f194bb71252acf9c9e2cf636db4) | DjDeveloperr | drm/asahi: keep G17 publication waits out of the submit ioctl [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/completion.rs` | C,R,H | +| [c556dacd085b](https://github.com/aurora-silicon/linux/commit/c556dacd085b148da86b47792dc34d14f02ace86) | DjDeveloperr | drm/asahi: rerun the G17 event pass for completions missed while a pair is borrowed [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/render.rs` | C,R,H | +| [8744e9417249](https://github.com/aurora-silicon/linux/commit/8744e9417249175511480765dbcc99f0a09c0fb9) | DjDeveloperr | drm/asahi: reuse G17 compute timestamp pages within a context [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — TIMESTAMP; `drivers/gpu/drm/asahi/g17/completion.rs` | T,C | +| [6f7faeecea0e](https://github.com/aurora-silicon/linux/commit/6f7faeecea0eccc8ee317ba163867df339d54fa5) | Jonathan Goyvaerts | drm/asahi: g17: do not advertise soft faults [#391](https://github.com/aurora-silicon/linux/pull/391) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [b0912f542fd0](https://github.com/aurora-silicon/linux/commit/b0912f542fd0655f10631977a8c63abc51a85ada) | Jonathan Goyvaerts | drm/asahi: g17: reread a torn borrowed render snapshot [#391](https://github.com/aurora-silicon/linux/pull/391) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/render/retirement.rs` | R,H | +| [30ea4b130473](https://github.com/aurora-silicon/linux/commit/30ea4b13047377972cb75b78a4cbe5885cf46d1d) | Jonathan Goyvaerts | drm/asahi: g17: do not fail a blamed render that attribution cleared [#391](https://github.com/aurora-silicon/linux/pull/391) | Gated — KEY; `drivers/gpu/drm/asahi/g17/recovery.rs` | F | +| [e3d8d1b2fc1f](https://github.com/aurora-silicon/linux/commit/e3d8d1b2fc1feada69ec028203f727f642a150ac) | Jonathan Goyvaerts | drm/asahi: g17: skip already-signalled successful input fences [#391](https://github.com/aurora-silicon/linux/pull/391) | Active — SHARED; `drivers/gpu/drm/asahi/g17/queue/mod.rs` | C,R,H | +| [2321a1bc6ea6](https://github.com/aurora-silicon/linux/commit/2321a1bc6ea6c8a62a275685c0542a293c9b5438) | Jonathan Goyvaerts | drm/asahi: allow HIGH queue priority with CAP_SYS_NICE or DRM master [#391](https://github.com/aurora-silicon/linux/pull/391) | Active — DRM; `drivers/gpu/drm/asahi/g17/mod.rs` | B,M | +| [77a2bbe1a951](https://github.com/aurora-silicon/linux/commit/77a2bbe1a9512960e8c9ba17b466b2c9ab51fb5f) | Ryan Murray | soc: apple: rtkit: Keep warning about unknown oslog messages | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [b2adcfc94046](https://github.com/aurora-silicon/linux/commit/b2adcfc94046a10a989c6fd61fdb1bf7131ef78a) | Ryan Murray | drm/asahi: fail large G17 backing population without direct reclaim [#397](https://github.com/aurora-silicon/linux/pull/397) | Active — DRM; `drivers/gpu/drm/asahi/file.rs` | C,H,M,F | +| [632e4676500b](https://github.com/aurora-silicon/linux/commit/632e4676500bab6fd88817b1cf767afb1bf5da52) | Ryan Murray | drm/asahi: expose memory admission capacities in GET_PARAMS [#397](https://github.com/aurora-silicon/linux/pull/397) | Active — MEM; `drivers/gpu/drm/asahi/file.rs` | M,D | +| [ffe8011a479c](https://github.com/aurora-silicon/linux/commit/ffe8011a479ccd74dde747225c9baf67e2a02ba6) | Chris Kearney | drm/asahi: g15: reject errored input dependencies before publication [#395](https://github.com/aurora-silicon/linux/pull/395) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [faef7fd12e22](https://github.com/aurora-silicon/linux/commit/faef7fd12e22a4d56873b91981d412fd981cfd21) | Chris Kearney | drm/asahi: expose the inherited-error policy only for native G15 [#395](https://github.com/aurora-silicon/linux/pull/395) | Active — SHARED; `drivers/gpu/drm/asahi/g17/mod.rs` | C,R,H | +| [8736001f2b13](https://github.com/aurora-silicon/linux/commit/8736001f2b130a9e477fdef7d1db45f31591bfec) | Eryk Wieliczko | pmdomain: apple: pmgr-pwrstate: Honour apple,no-auto-pm [#395](https://github.com/aurora-silicon/linux/pull/395) | Active — PMGR; `drivers/pmdomain/apple/pmgr-pwrstate.c` | B,F | +| [2aff1b95c550](https://github.com/aurora-silicon/linux/commit/2aff1b95c55039846beb6451c737154254aa3ae0) | Eryk Wieliczko | soc: apple: rtkit: describe and adopt retained system buffers | Active — RTKIT; `drivers/soc/apple/rtkit-internal.h` | B,F | +| [71500694f45b](https://github.com/aurora-silicon/linux/commit/71500694f45b1fd27b05c9ed030b2cc803fb0f2f) | Eryk Wieliczko | drm/asahi: share tracked VM lifetime and native query support [#400](https://github.com/aurora-silicon/linux/pull/400) | Active — MEM; `drivers/gpu/drm/asahi/g17/completion.rs` | M,D | +| [444c52644826](https://github.com/aurora-silicon/linux/commit/444c52644826e99eecf65e9f643744c9e380bca9) | Ryan Murray | soc: apple: pmp: Keep power commands after an unhandled message on other boards | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [76c60f146885](https://github.com/aurora-silicon/linux/commit/76c60f1468853b4d28e3fed6e9c207c8a2f28410) | Ryan Murray | iommu/apple-dart: Map retained DARTs non-posted like every other DART [#400](https://github.com/aurora-silicon/linux/pull/400) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [d5df65ffcf66](https://github.com/aurora-silicon/linux/commit/d5df65ffcf66e54d9f23de05635512a2cf0ad41d) | Jonathan Goyvaerts | soc: apple: rtkit: add apple_rtkit_resume_idle() | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [f82267ce1172](https://github.com/aurora-silicon/linux/commit/f82267ce117278fb915879abbbd0f355286cecf3) | Jonathan Goyvaerts | soc: apple: rtkit: resume the AP power state before the IOP's | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [e772e7f87659](https://github.com/aurora-silicon/linux/commit/e772e7f87659243ab7b94c2b5bb77ab485dc1128) | DjDeveloperr | iommu/apple-dart: accept mirrored pages in handed-off firmware slots [#404](https://github.com/aurora-silicon/linux/pull/404) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [ff5c39ae1782](https://github.com/aurora-silicon/linux/commit/ff5c39ae1782cd1d826eddf48e0b08b1b2af61eb) | DjDeveloperr | iommu/apple-dart: keep stream invalidates out of handed-off slots [#404](https://github.com/aurora-silicon/linux/pull/404) | Active — DART; `drivers/iommu/apple-dart.c` | D,B | +| [c2bace5bb285](https://github.com/aurora-silicon/linux/commit/c2bace5bb28512471318d71abd5073b2bc7e2af1) | Ryan Murray | soc: apple: pmp: start endpoint 7 on every T6041 PMP [#402](https://github.com/aurora-silicon/linux/pull/402) | Active — PMP; `drivers/soc/apple/pmp.rs` | P,B | +| [2d8c0fe879e2](https://github.com/aurora-silicon/linux/commit/2d8c0fe879e26583b62655534a3aba86b1dfc80f) | Jonathan Goyvaerts | drm/asahi: mmu: make the secondary root of PublishedRoots optional [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — MEM; `drivers/gpu/drm/asahi/g17/mod.rs` | M,D | +| [40507bad232c](https://github.com/aurora-silicon/linux/commit/40507bad232c7539f3c7c2540bac818a7aa90f26) | Jonathan Goyvaerts | drm/asahi: g17: support a single firmware coprocessor [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/event.rs` | C,R,H | +| [750dd1a8e590](https://github.com/aurora-silicon/linux/commit/750dd1a8e5902e4e7478ce9213278b2131b63266) | Jonathan Goyvaerts | drm/asahi: g17: describe the firmware build in a variant table [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data; MCache gated by V25G83.mcache=false — FW; `drivers/gpu/drm/asahi/g17/command.rs` | B,C,R | +| [9038ef8d328c](https://github.com/aurora-silicon/linux/commit/9038ef8d328cef14e038513ccaf55dc03869006b) | Jonathan Goyvaerts | drm/asahi: g17: take the per-SoC tables from Config [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/initdata.rs` | B,C,R | +| [24d1e5f4d8d3](https://github.com/aurora-silicon/linux/commit/24d1e5f4d8d31dd320048546cd53230a61203a9f) | Jonathan Goyvaerts | drm/asahi: g17: operating points with one voltage per cluster [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | C,R,H | +| [1ef423a08b3c](https://github.com/aurora-silicon/linux/commit/1ef423a08b3cbc81856afbe3c5854ac25b0893b6) | Jonathan Goyvaerts | drm/asahi: g17: register windows with several instances [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | C,R,H | +| [f23df96e697b](https://github.com/aurora-silicon/linux/commit/f23df96e697bc05a47029efd4fe786f900b08a83) | Jonathan Goyvaerts | drm/asahi: g17: let the variant choose the init acknowledgement [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/variant.rs` | B,C,R | +| [39e990b44482](https://github.com/aurora-silicon/linux/commit/39e990b44482f46fb6bed6c3b7eb2413e0f0331a) | Jonathan Goyvaerts | drm/asahi: mmu: firmware tables in no-map reserved memory [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — MEM; `drivers/gpu/drm/asahi/g17/mod.rs` | M,D | +| [54eccc50cc77](https://github.com/aurora-silicon/linux/commit/54eccc50cc770bdbe9306be4337c548f399e2eb8) | Jonathan Goyvaerts | drm/asahi: mmu: per-SoC UAT cache, TLB and handoff policy [#420](https://github.com/aurora-silicon/linux/pull/420) | Gated — UAT; `drivers/gpu/drm/asahi/mem.rs` | D | +| [abb36e8aa320](https://github.com/aurora-silicon/linux/commit/abb36e8aa3203f4cb28c1b2498d636617d01111d) | Jonathan Goyvaerts | drm/asahi: g17: 25G83 initialization layout [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | B,C,R | +| [0d9efa515188](https://github.com/aurora-silicon/linux/commit/0d9efa515188d93cd775e240b20bf4ad29a258c8) | Jonathan Goyvaerts | drm/asahi: g17: render lead commands for the 25G83 firmware [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/buffer.rs` | B,C,R | +| [f8dc19862078](https://github.com/aurora-silicon/linux/commit/f8dc19862078a7ad72cd3e10fe22052a02609e76) | Jonathan Goyvaerts | drm/asahi: g17: 25G83 render and compute commands [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/buffer/target.rs` | B,C,R | +| [20e51646e036](https://github.com/aurora-silicon/linux/commit/20e51646e036432fa9498a24e5330b8d02068612) | Jonathan Goyvaerts | drm/asahi: g17: add the T6040 (M4 Pro) [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/variant.rs` | B,C,R | +| [244f347d7dee](https://github.com/aurora-silicon/linux/commit/244f347d7deedb8a67eda1f0175ce001a5e7509c) | Jonathan Goyvaerts | soc: apple: rtkit: drop apple_rtkit_resume_idle() | Active — RTKIT; `drivers/soc/apple/rtkit.c` | B,F | +| [a214e242a034](https://github.com/aurora-silicon/linux/commit/a214e242a03404fc7324ff0efdc595a6b37a53c5) | Jonathan Goyvaerts | drm/asahi: g17: bounded cross-VM grace for render timeouts [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — REC; `drivers/gpu/drm/asahi/g17/job.rs` | F,Q | +| [24349f4a4593](https://github.com/aurora-silicon/linux/commit/24349f4a4593fa4e02ce731f5e73a228aa27aed8) | Jonathan Goyvaerts | drm/asahi: g17: retire abandoned spared renders without failing clients [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [9af21ada7212](https://github.com/aurora-silicon/linux/commit/9af21ada7212737042705f3081d153332516a182) | Jonathan Goyvaerts | drm/asahi: g17: spare innocent renders once the cross-VM grace runs out [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — SHARED; `drivers/gpu/drm/asahi/g17/runtime/backend.rs` | R,H | +| [29f38d83f6a6](https://github.com/aurora-silicon/linux/commit/29f38d83f6a6208d4ff3c4187042b3b0b476d242) | Jonathan Goyvaerts | drm/asahi: g17: keep a VM usable behind its spared render quarantine [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [c71f1177e773](https://github.com/aurora-silicon/linux/commit/c71f1177e7730db49e9a7ec9cdf150a0feeb05ab) | Jonathan Goyvaerts | drm/asahi: g17: hold the GPU powered for idle_off_delay_ms after work [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — POWER; `drivers/gpu/drm/asahi/g17/mod.rs` | I,C,R,P | +| [645b4e55cf16](https://github.com/aurora-silicon/linux/commit/645b4e55cf16c1cedffba87258bdaafd5b15dcd8) | Jonathan Goyvaerts | drm/asahi: g17: replay every render group firmware dropped unexecuted [#413](https://github.com/aurora-silicon/linux/pull/413) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [94e719a0cec2](https://github.com/aurora-silicon/linux/commit/94e719a0cec2a65502ce5413d2ee9a105c1cc8d9) | Ryan Murray | drm/asahi: g17: never replay a render group a recovery blamed [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/completion.rs` | F,Q | +| [1054f6763014](https://github.com/aurora-silicon/linux/commit/1054f6763014fe4b4032a08ae7ccce721be4d385) | Ryan Murray | drm/asahi: g17: fix the render timeout head and replay documentation [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/render.rs` | F,Q | +| [45ac35fa2796](https://github.com/aurora-silicon/linux/commit/45ac35fa279642f98ea7326144716755fb3a39a2) | Ryan Murray | drm/asahi: g17: move the fw-data crash log mapping to its own file [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/data_segment.rs` | C,R,H | +| [fd21bf6faed6](https://github.com/aurora-silicon/linux/commit/fd21bf6faed6f978223e61444424ee918c199838) | Ryan Murray | drm/asahi: g17: take the core-layout buffers from Config [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/compute.rs` | B,C,R | +| [c0b9fbfa9fb0](https://github.com/aurora-silicon/linux/commit/c0b9fbfa9fb048d5bc67ffb9e17168b686b1962d) | Ryan Murray | drm/asahi: g17: check the voltage cells of the GPU operating points [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/initdata.rs` | B,C,R | +| [1c5a2ef5783a](https://github.com/aurora-silicon/linux/commit/1c5a2ef5783a565e6b27da871d30e4e637e1ce69) | Ryan Murray | drm/asahi: g17: accept only the two host words of a pointer block [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/queue.rs` | B,C,R | +| [df6b70b85cbf](https://github.com/aurora-silicon/linux/commit/df6b70b85cbfada36604f511b9ba8da21b13eb7d) | Ryan Murray | drm/asahi: g17: pass the fragment ID to the barrier unchanged [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — SHARED; `drivers/gpu/drm/asahi/g17/fw/queue.rs` | C,R,H | +| [496a59f89601](https://github.com/aurora-silicon/linux/commit/496a59f8960191dc511b92328b13ccc7e31ad1ff) | Ryan Murray | drm/asahi: g17: do not spare a timeout behind work that already failed [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [fc1a16b4722c](https://github.com/aurora-silicon/linux/commit/fc1a16b4722ce9a9eb6b77fc0af4163873cacc9e) | Ryan Murray | drm/asahi: g17: name the blocking work in the watchdog lines [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — OBS; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | O,F | +| [894ec76dc892](https://github.com/aurora-silicon/linux/commit/894ec76dc89219caae27f9d6845521337b16e8bf) | Ryan Murray | drm/asahi: g17: treat a torn render sample in the timeout fallback as no proof [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/render.rs` | F,Q | +| [7d167e382f40](https://github.com/aurora-silicon/linux/commit/7d167e382f40542b25ffceb596d0982ee002b7ca) | Ryan Murray | drm/asahi: g17: fail the dropped successors of an exhausted render replay [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/queue/render.rs` | F,Q | +| [5ee97891f61f](https://github.com/aurora-silicon/linux/commit/5ee97891f61f552c8c1d677db50725c413fb3ed7) | Ryan Murray | drm/asahi: g17: retire finished renders before attributing a render timeout [#418](https://github.com/aurora-silicon/linux/pull/418) | Active — REC; `drivers/gpu/drm/asahi/g17/runtime/backend.rs` | F,Q | +| [25f4c6c3ab18](https://github.com/aurora-silicon/linux/commit/25f4c6c3ab18f329ba2af41b102c30d53e361ae6) | Ryan Murray | thermal: apple: Describe the PMP policy KUnit test module [#417](https://github.com/aurora-silicon/linux/pull/417) | Active — PMP; `drivers/thermal/apple-pmp-policy-test.c` | P,B | +| [b9a8f3938f3d](https://github.com/aurora-silicon/linux/commit/b9a8f3938f3daf5926b7c86204113f6730aec136) | Ryan Murray | drm/asahi: g17: pass the queue count to Pool::new() in the fault admission test [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — GMEM; `drivers/gpu/drm/asahi/g17/queue/admission.rs` | M,C,R | +| [bec833e0bc58](https://github.com/aurora-silicon/linux/commit/bec833e0bc581f74744265f0613832e8291db0cc) | Jonathan Goyvaerts | drm/asahi: pgtable: check the struct page of a table outside the reserved regions [#427](https://github.com/aurora-silicon/linux/pull/427) | Active — MEM; `drivers/gpu/drm/asahi/mmu.rs` | M,D | +| [a335c77de52a](https://github.com/aurora-silicon/linux/commit/a335c77de52a274029bb4e42f96d6afde5712a2f) | Jonathan Goyvaerts | drm/asahi: identify the T6040 GPU firmware by its loaded text [#428](https://github.com/aurora-silicon/linux/pull/428) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/variant.rs` | B,C,R | +| [d69f14f4e4fb](https://github.com/aurora-silicon/linux/commit/d69f14f4e4fbe9af21b70ee5c1b3ff9125c7a9cf) | DjDeveloperr | soc: apple: clpc: raise the T8140 fabric floor for GPU work [#440](https://github.com/aurora-silicon/linux/pull/440) | Active after port; 0d63308820ab — GPUFLOOR; `drivers/soc/apple/clpc.c` | P,C,R,H,I | +| [d1468a5f9a50](https://github.com/aurora-silicon/linux/commit/d1468a5f9a50a23cf741f56d83e6690441b90482) | Ryan | drm/asahi: gpu: drop the unused module_parameters import [#395](https://github.com/aurora-silicon/linux/pull/395) | Active — MEM; `drivers/gpu/drm/asahi/gpu.rs` | M,D | +| [26f5eb68dbce](https://github.com/aurora-silicon/linux/commit/26f5eb68dbce039de470b789d754d2ec3541c15e) | Ryan Murray | drm/asahi: admit the alternate T6040 loaded 25G83 image [#453](https://github.com/aurora-silicon/linux/pull/453) | Active translated data — FW; `drivers/gpu/drm/asahi/g17/fw/variant.rs` | B,C,R | +| [88aebfe0c656](https://github.com/aurora-silicon/linux/commit/88aebfe0c656676c530faa8f85df8d7b69a7ddb0) | DjDeveloperr | irqchip/apple-aic: Report the effective affinity of AICv2+ IRQs [#447](https://github.com/aurora-silicon/linux/pull/447) | Active — AIC; `drivers/irqchip/irq-apple-aic.c` | O,C,I | +| [eb346c1a25d8](https://github.com/aurora-silicon/linux/commit/eb346c1a25d813a1411a34fafc7d4bdee418e0d4), [42a176b951d9](https://github.com/aurora-silicon/linux/commit/42a176b951d90e746e33d27d6eb06a1773579afe) | Haoxiang Li | cpufreq: apple-soc: Fix OPP table cleanup | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [ae37f03d4163](https://github.com/aurora-silicon/linux/commit/ae37f03d4163df8143b2144e742916b91bf90654) | Ryan Murray | thermal: apple-smc-cpu: Add the J700 CPU thermal policy [#22](https://github.com/aurora-silicon/linux/pull/22) | Active — CPU; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I,P | +| [583b9fa52fbf](https://github.com/aurora-silicon/linux/commit/583b9fa52fbf6a47f150736ad4a1aaa0d616ac02), [a8872354b695](https://github.com/aurora-silicon/linux/commit/a8872354b695a066ac6411791a691f26fcde55bf) | Oliver Lukschander | pmdomain: apple: pmp-report: initialize the report lock [#56](https://github.com/aurora-silicon/linux/pull/56) | Active — PMP; `drivers/pmdomain/apple/pmp-report.c` | P,B | +| [8ca5289bec3a](https://github.com/aurora-silicon/linux/commit/8ca5289bec3a2b6f3629c9df516b357778273a72) | Joshua Warren | drm/asahi: connect the snapshot pointer to the C sysfs static [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — MEM; `drivers/gpu/drm/asahi/gpu.rs` | M,D | +| [628bf1df4312](https://github.com/aurora-silicon/linux/commit/628bf1df43123ead994b028fd7b82a209e67aa30), [983575d7b492](https://github.com/aurora-silicon/linux/commit/983575d7b492f6824401cd21fe02278812211e27) | Ryan Murray | drm/asahi: initialize driver data before claiming destructor ownership [#171](https://github.com/aurora-silicon/linux/pull/171) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,B | +| [b10d60ce3916](https://github.com/aurora-silicon/linux/commit/b10d60ce3916c44dfb19b1f1b197bdca40bbac34) | Ryan Murray | drm/asahi: retain two-phase data ownership with stats registration [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — MEM; `drivers/gpu/drm/asahi/driver.rs` | M,B | +| [9266541b8d35](https://github.com/aurora-silicon/linux/commit/9266541b8d35c1cd7146eec9a9b31b39fb7f29cc) | Ryan Murray | treewide: describe hardware behaviour in comments [#233](https://github.com/aurora-silicon/linux/pull/233) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — MBOX; `drivers/gpu/drm/asahi/g17/buffer.rs` | B,O | +| [8533f072dc30](https://github.com/aurora-silicon/linux/commit/8533f072dc30e076206631f68f41fb4f8d7512a7) | Janne Grunau | iommu: apple-dart: Support specifying the DMA aperture in the DT | Active; carried/refined form — DART; `drivers/iommu/apple-dart.c` | D,B | +| [a9e24ae824ec](https://github.com/aurora-silicon/linux/commit/a9e24ae824ec205cf90f3545b0125631adf1326a) | Ryan Murray | merge: retain the 7.3 implementations where older ports duplicate them [#470](https://github.com/aurora-silicon/linux/pull/470) | Active; carried/refined form — PMGR; `drivers/bluetooth/hci_bcm4377.c` | B,F | +| [00248157ec1f](https://github.com/aurora-silicon/linux/commit/00248157ec1fc4e6369cd26cb8304bb56df7415d), [b2258cfadd69](https://github.com/aurora-silicon/linux/commit/b2258cfadd697cbe10c1e29b54f512c8f297bbe6) | Joshua Warren | soc: apple: mailbox: Make the send-empty IRQ optional (poll TX) | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [00e21c12c05a](https://github.com/aurora-silicon/linux/commit/00e21c12c05af1d540a2dc256a8cea3d17d63edc) | Lyude Paul | rust: drm: gem: Add export() callback | Active — DRM; `drivers/gpu/drm/nova/gem.rs` | C,H,M,F | +| [03329eff23bd](https://github.com/aurora-silicon/linux/commit/03329eff23bd3a72bdcfa09a3b98d0ac15107e6e), [35b057e57222](https://github.com/aurora-silicon/linux/commit/35b057e572224c39f79367df9e7c464f1822d15a), [e68c60f81285](https://github.com/aurora-silicon/linux/commit/e68c60f81285248fb50578f268343b06a6452a67) | DjDeveloperr | rust: drm: gpuvm: add non-blocking private mapping traversal [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `rust/kernel/drm/gpuvm.rs` | C,H,M,F | +| [033943b2b6ed](https://github.com/aurora-silicon/linux/commit/033943b2b6edb176aaebffa917e2273c3bf8f625) | Ryan Murray | drm: apple: parser: cap the number of timing modes [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/parser.c` | B | +| [08e4dd7596c9](https://github.com/aurora-silicon/linux/commit/08e4dd7596c91cdb5b761a0d272e40d75320306b), [29db1d8ab517](https://github.com/aurora-silicon/linux/commit/29db1d8ab517933c2e0114fb3c6bf9beda5417aa), [437f51c11728](https://github.com/aurora-silicon/linux/commit/437f51c11728f3183b0c17af6264547e44a60c26) | Ryan Murray | soc: apple: synchronize the ASCWrap mailbox provider with j700 [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [0db024825eb1](https://github.com/aurora-silicon/linux/commit/0db024825eb15d5ffd51ae6bb6b71ea4899ff3c2) | Ryan Murray | rust: drm: update shmem KUnit fixtures for the 7.2 file API | Active — DRM; `rust/kernel/drm/gem/shmem.rs` | C,H,M,F | +| [17f9a8193f55](https://github.com/aurora-silicon/linux/commit/17f9a8193f553f12afb69a14c849e12889842e8b) | Ryan Murray | rust: drm: gem: shmem: leave an imported object's SG table to the attachment | Active — DRM; `rust/kernel/drm/gem/shmem.rs` | C,H,M,F | +| [1f6c1fbb7057](https://github.com/aurora-silicon/linux/commit/1f6c1fbb7057db9206dce7187cfd3ec73233cd67) | Ryan Murray | drm/apple: dcp: Release the fixed display route with the device [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [29a2e2acec42](https://github.com/aurora-silicon/linux/commit/29a2e2acec42f8361d72bfd9506e3a0527afaf40) | DjDeveloperr | sched/core: add scoped utilization floors for kernel waiters [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM | C,H,M,F | +| [29a913a5c222](https://github.com/aurora-silicon/linux/commit/29a913a5c222f2bec2ecae62a4dc4af223e5962d) | Jonathan Goyvaerts | soc: apple: mailbox: support the T6040 ASCWrap v6 [#420](https://github.com/aurora-silicon/linux/pull/420) | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [2b59aed7191c](https://github.com/aurora-silicon/linux/commit/2b59aed7191cccf0f6239e50e9a165f49b649529) | Ryan Murray | drm/asahi: avoid fence locks when pruning G17 frontiers [#201](https://github.com/aurora-silicon/linux/pull/201) | Active; carried/refined form — SHARED; `drivers/gpu/drm/asahi/g17/fence.rs` | C,R,H | +| [2dc22a191a5c](https://github.com/aurora-silicon/linux/commit/2dc22a191a5cf3109fb18dcfda0fec94bd4df18b), [a45a44cc6e2b](https://github.com/aurora-silicon/linux/commit/a45a44cc6e2b6bbdb189debda045a2174d07e039) | Ryan Murray | rust: drm: gem: shmem: let drivers allocate the objects the core creates | Active — DRM; `rust/kernel/drm/gem/mod.rs` | C,H,M,F | +| [32418c60d4cd](https://github.com/aurora-silicon/linux/commit/32418c60d4cdd6b37e2e9c7e88c7e299d473cf19) | Ryan Murray | drm/apple: dcp: Do not hold the mailbox of an unbound DCP for sleep [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [351fc1af825c](https://github.com/aurora-silicon/linux/commit/351fc1af825c49264de7e171a983d2c0c83f1601) | Ryan Murray | dt-bindings: power: apple,pmc: describe the DCS limit [#219](https://github.com/aurora-silicon/linux/pull/219) | Gated — DCS | P | +| [3b9a0383f589](https://github.com/aurora-silicon/linux/commit/3b9a0383f589ebaaa5bcd5c3242e294d6051f0e0) | Janne Grunau | FIXME: drm/gpuvm: Add DRM_GPUVA_REPEAT flag and logic | Active — DRM; `rust/kernel/drm/gpuvm/sm_ops.rs` | C,H,M,F | +| [3c102dba66df](https://github.com/aurora-silicon/linux/commit/3c102dba66df1119625c11c80dca301fb308563c) | Asahi Lina | rust: drm: mm: Add DRM MM Range Allocator abstraction | Active — DRM; `rust/kernel/drm/mm.rs` | C,H,M,F | +| [3d0c9c889919](https://github.com/aurora-silicon/linux/commit/3d0c9c8899198a8ad6d0d0241283ba04d2286fd7) | Ryan Murray | drm: apple: parser: bound the timing catalog allocation [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/parser.c` | B | +| [3d3fe96509d1](https://github.com/aurora-silicon/linux/commit/3d3fe96509d102770f72ce5f7829baa3c0f7d1ce), [df2e3cd3b778](https://github.com/aurora-silicon/linux/commit/df2e3cd3b77882ec523b42184d05596e60094072) | Ryan Murray | rust: soc: apple: mailbox: Fix receiver teardown and expose the device | Active — MBOX; `rust/kernel/soc/apple/mailbox.rs` | B,O | +| [3fd71b22d86c](https://github.com/aurora-silicon/linux/commit/3fd71b22d86c3e2397ed1da8a15123053d46a423), [536484539477](https://github.com/aurora-silicon/linux/commit/53648453947771b7ca24a61a585bffdbe1853242) | Ryan Murray | soc: apple: mailbox: Add T8140 ASC and AOP setup mailboxes | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [43fc98be783b](https://github.com/aurora-silicon/linux/commit/43fc98be783b0e9833242c1d8d111b49366b134b), [7c99d8debcc5](https://github.com/aurora-silicon/linux/commit/7c99d8debcc54ebf3f76ede14ced9b845db18215), [b0cc4eeb554a](https://github.com/aurora-silicon/linux/commit/b0cc4eeb554a74c2db220e35ceb14955142a79ed) | DjDeveloperr | drm/syncobj: apply expiring capacity hints during blocking waits [#171](https://github.com/aurora-silicon/linux/pull/171) | Active — DRM; `drivers/gpu/drm/drm_syncobj.c` | C,H,M,F | +| [4a577fcddddb](https://github.com/aurora-silicon/linux/commit/4a577fcddddbefec7d13bfd4ae3503893602d29b) | DjDeveloperr | rust: expose the DRM duplicate-reservation flag used by GPUVM [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM | B,M | +| [4aa16bb5021e](https://github.com/aurora-silicon/linux/commit/4aa16bb5021e427507e15a9a48848d0484a4f3d6) | Eryk Wieliczko | rust: expose fence and DRM lifecycle helpers for native GPU backends | Active — DRM; `rust/kernel/dma_fence.rs` | C,H,M,F | +| [4f7a3272df96](https://github.com/aurora-silicon/linux/commit/4f7a3272df9627d98bc11a7da0c8189ca4aba196) | DjDeveloperr | drm/shmem-helper: populate revocable CPU mappings one page at a time [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM; `drivers/gpu/drm/drm_gem_shmem_helper.c` | C,H,M,F | +| [5443fc6aeb7a](https://github.com/aurora-silicon/linux/commit/5443fc6aeb7a0fc574805b8b0b1513bbd02dcab4) | Ryan Murray | soc: apple: mailbox: only include reachable KUnit tests | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [5611637f6851](https://github.com/aurora-silicon/linux/commit/5611637f685115048c64231e7ac729f23ab20001), [5eff5323da02](https://github.com/aurora-silicon/linux/commit/5eff5323da0282c8fb9adfc57acdc9217e6d7995) | Ryan Murray | soc: apple: mailbox: drain only a present send-empty IRQ | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [56920e3e6752](https://github.com/aurora-silicon/linux/commit/56920e3e675230ced9a2ccab81bb7e9aa9c2ccf0) | Janne Grunau | rust: drm: gpuvm: Add sm_can_merge_flags | Active — DRM; `rust/kernel/drm/gpuvm/mod.rs` | C,H,M,F | +| [6154ed6d0f41](https://github.com/aurora-silicon/linux/commit/6154ed6d0f410dd69cbb3e218981b6dc549ac5df) | Janne Grunau | rust: drm: file: Use Registered device in DriverFile::open trait | Active — DRM; `drivers/gpu/drm/nova/file.rs` | C,H,M,F | +| [63d3b0f41939](https://github.com/aurora-silicon/linux/commit/63d3b0f419399d027c865d4ccffa12e3617a73b8) | Ryan Murray | drm: apple: afk: factor the receive bounds check into a helper [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/afk.c` | B | +| [6740c25f03c1](https://github.com/aurora-silicon/linux/commit/6740c25f03c1f552b0bdc0ad5b02926d96dd99ad) | Ryan Murray | drm/asahi: Log G17 firmware recovery causes before acknowledgement [#200](https://github.com/aurora-silicon/linux/pull/200) | Active; carried/refined form — OBS; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | O,F | +| [6803b9328c48](https://github.com/aurora-silicon/linux/commit/6803b9328c483c492e0a26fadf0a7cd03a5fe7cc) | Janne Grunau | rust: drm: gpuvm: GpuVa flags and REPEAT logic | Active — DRM; `rust/kernel/drm/gpuvm/sm_ops.rs` | C,H,M,F | +| [6a127c6ff62c](https://github.com/aurora-silicon/linux/commit/6a127c6ff62cde78267225a7094cda2771504c28) | Janne Grunau | rust: drm: file: Add as_raw() | Active — DRM; `drivers/gpu/drm/nova/file.rs` | C,H,M,F | +| [6db05cf4b231](https://github.com/aurora-silicon/linux/commit/6db05cf4b231f020020f31a479bd45ccff83520f) | DjDeveloperr | drm/shmem-helper: index fault pages by object offset [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM; `drivers/gpu/drm/drm_gem_shmem_helper.c` | C,H,M,F | +| [70d9b6f64ee7](https://github.com/aurora-silicon/linux/commit/70d9b6f64ee76e9da96b752ee3933e2d0a66af0b) | Ryan Murray | drm: apple: free property dictionaries the connector does not keep [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/connector.c` | B | +| [72c42b6ddae6](https://github.com/aurora-silicon/linux/commit/72c42b6ddae68c8c58c5f5d720fda5079ae24302) | Janne Grunau | rust: drm: driver: Add feature flags used by asahi | Active — DRM; `rust/kernel/drm/device.rs` | C,H,M,F | +| [765577e012da](https://github.com/aurora-silicon/linux/commit/765577e012da1ee83e6eb56029bdbb9f1f799dce) | Ryan Murray | drm/apple: dptxep: Remove call counters and tidy tunnel comments [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dptxep.c` | B | +| [77a9ab842a02](https://github.com/aurora-silicon/linux/commit/77a9ab842a02e980873a2298caa4dfe8d56faaff) | Ryan Murray | rust: drm: gpuvm: do not leak the VM when exec_lock() fails | Active — DRM; `rust/kernel/drm/gpuvm/mod.rs` | C,H,M,F | +| [7a155e383d2d](https://github.com/aurora-silicon/linux/commit/7a155e383d2d8e70fd76075ffb7427f4c7a3c326), [c98d2b872a2b](https://github.com/aurora-silicon/linux/commit/c98d2b872a2b02241ce4a34d9fe6841cd32b38c3) | Ryan Murray | soc: apple: mailbox: Keep the send-empty IRQ enable balanced | Active — MBOX; `drivers/soc/apple/mailbox.c` | B,O | +| [7c57598ac57c](https://github.com/aurora-silicon/linux/commit/7c57598ac57cb7804a9f2e89d0975e2005985b96) | Janne Grunau | rust: drm: gem: Support locking gpuva.lock | Active — DRM; `rust/kernel/drm/gem/mod.rs` | C,H,M,F | +| [7dce231ad73d](https://github.com/aurora-silicon/linux/commit/7dce231ad73d98e102b4b9e0b362cbab21750854) | DjDeveloperr | drm/syncobj: skip wait hint lookup for ineligible waits [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM; `drivers/gpu/drm/drm_syncobj.c` | C,H,M,F | +| [7ed160b9b5f2](https://github.com/aurora-silicon/linux/commit/7ed160b9b5f28df712e98088d4368188b2e28072), [c7df21582810](https://github.com/aurora-silicon/linux/commit/c7df2158281045e6fa9bfa6b7c170910f1089d2e) | Ryan Murray | soc: apple: mailbox: Tidy T8140 comments and resume error logging [#314](https://github.com/aurora-silicon/linux/pull/314) | Active — OBS; `drivers/soc/apple/mailbox.c` | O,F | +| [887533ef3bd8](https://github.com/aurora-silicon/linux/commit/887533ef3bd88de5ec05f93be23d2fefd3570cb5) | Ryan Murray | rust: drm: gem: shmem: wait for backing release instead of failing a lease | Active — DRM; `rust/kernel/drm/gem/shmem.rs` | C,H,M,F | +| [8cdeb29b542a](https://github.com/aurora-silicon/linux/commit/8cdeb29b542af3cd5ffb3b535330ad4fd67b97a7) | Janne Grunau | HACK: rust: drm: gem: shmem: Make VMap.ptr pub | Active — DRM; `rust/kernel/drm/gem/shmem.rs` | C,H,M,F | +| [9142d428cb69](https://github.com/aurora-silicon/linux/commit/9142d428cb6956aeb36ffebabe42e1a5c2396c2c) | Ryan Murray | drm: apple: afk: bound received queue entries without 32-bit wrap [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/afk.c` | B | +| [92d02170df1e](https://github.com/aurora-silicon/linux/commit/92d02170df1ecf2273d13ba63c0cedbb891996d3) | Jonathan Goyvaerts | arm64: dts: apple: t6040: Add the GPU [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW | B,P | +| [9429b186dc3e](https://github.com/aurora-silicon/linux/commit/9429b186dc3edb6658bcdd02e451a5cb1c5f0844) | Ryan Murray | rust: drm: report client memory in fdinfo | Active — DRM; `rust/kernel/drm/device.rs` | C,H,M,F | +| [9523159a7288](https://github.com/aurora-silicon/linux/commit/9523159a72886c5169e7bfd566de9c0aa545ba85) | Ryan Murray | soc: apple: clpc: drop the syscon select and the GPU from Kconfig [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — CLPC; `drivers/soc/apple/Kconfig` | P,C,R,I | +| [a3828ea8a63d](https://github.com/aurora-silicon/linux/commit/a3828ea8a63d2488f2080dd1f4f6ce8ebd133623) | Asahi Lina | rust: drm: syncobj: Add DRM Sync Object abstraction | Active — DRM; `rust/kernel/drm/mod.rs` | B,M | +| [a6f8817ebbf6](https://github.com/aurora-silicon/linux/commit/a6f8817ebbf6c420aee66897f717ab4e94a4a717) | Ryan Murray | drm/asahi: Reuse fully retired guilty G17 compute queues [#200](https://github.com/aurora-silicon/linux/pull/200) | Active; carried/refined form — REC; `drivers/gpu/drm/asahi/g17/queue/compute.rs` | F,Q | +| [b147e1f460b5](https://github.com/aurora-silicon/linux/commit/b147e1f460b56e398bc085b2457b1ba163cf4505) | Jonathan Goyvaerts | arm64: dts: apple: t6040-j616s: Add the gpu alias [#420](https://github.com/aurora-silicon/linux/pull/420) | Active translated data — FW | B,P | +| [b1f7d09dcf33](https://github.com/aurora-silicon/linux/commit/b1f7d09dcf33e3bb06e81d846783b698d604be5b) | Ryan Murray | drm/apple: dcp: Initialise the start completion before checking RTKit [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [b965be95b07f](https://github.com/aurora-silicon/linux/commit/b965be95b07f46ef0be02f47a3a02017d912e4cc) | Asahi Lina | rust: drm: gpuvm: Add part of the asahi downstream gpuvm abstractions | Active — DRM; `rust/kernel/drm/gpuvm/mod.rs` | B,M | +| [ba69d4dde975](https://github.com/aurora-silicon/linux/commit/ba69d4dde9751976c323055ef683d76febf82a2b) | Ryan Murray | drm: apple: afk: validate the receive write pointer and bound a drain [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/afk.c` | B | +| [c0e5730f8058](https://github.com/aurora-silicon/linux/commit/c0e5730f8058f14c8fdeb5cd96213e57822f7bbb) | Ryan Murray | drm/apple: dcp: Tidy RTKit start-up logging [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [c4722f38cdcc](https://github.com/aurora-silicon/linux/commit/c4722f38cdccca6dbbbc446dfa2d715ab96e9554) | Ryan Murray | arm64: dts: apple: t6040: Do not claim the GPU's PMP report entry at boot [#402](https://github.com/aurora-silicon/linux/pull/402) | Active — PMP | P,B | +| [cd71c1e7ff90](https://github.com/aurora-silicon/linux/commit/cd71c1e7ff901c3c35492ebe5b8bebb0702e5480) | Jonathan Goyvaerts | arm64: dts: apple: t6040-j616s: describe the GPU firmware text segment [#428](https://github.com/aurora-silicon/linux/pull/428) | Active translated data — FW | B,P | +| [d50642d23a7e](https://github.com/aurora-silicon/linux/commit/d50642d23a7edb9ace6837d6471981e84d0d4ef6) | Ryan Murray | iommu: apple-dart: Fix the KUnit test after deferred root publication [#314](https://github.com/aurora-silicon/linux/pull/314) | Active; carried/refined form — DART; `drivers/iommu/apple-dart-test.c` | D,B | +| [d6799a78db13](https://github.com/aurora-silicon/linux/commit/d6799a78db13ad06a2f26b66aad97c7e7626714d) | Ryan Murray | drm/apple: dcp: Initialise work items at probe [#345](https://github.com/aurora-silicon/linux/pull/345) | Active; carried/refined form — DISPLAY; `drivers/gpu/drm/apple/dcp.c` | B | +| [d8b45282c1e7](https://github.com/aurora-silicon/linux/commit/d8b45282c1e70cb4b947d2aadfb2636fd4ee763a) | Lyude Paul | rust: drm: gem: Add BaseObject::prime_export() | Active — DRM; `rust/kernel/drm/gem/mod.rs` | C,H,M,F | +| [defdcb801774](https://github.com/aurora-silicon/linux/commit/defdcb8017744fe0aa57061ed304e793935b52bc) | Ryan Murray | soc: apple: build the CLPC floors by default [#219](https://github.com/aurora-silicon/linux/pull/219) | Active; carried/refined form — CLPC; `drivers/soc/apple/Kconfig` | P,C,R,I | +| [e1468f83c1da](https://github.com/aurora-silicon/linux/commit/e1468f83c1daf236b69c6868a3a05a9d6f994f9b) | Janne Grunau | rust: drm: gem: Add missing trailing '//' for kernel vertical style | Active — DRM; `rust/kernel/drm/gem/mod.rs` | C,H,M,F | +| [e6d369606edf](https://github.com/aurora-silicon/linux/commit/e6d369606edff3c24dc3b1f5dda036cc77a8887c) | Ryan Murray | drm/shmem: revoke CPU PFNs before releasing purge backing [#171](https://github.com/aurora-silicon/linux/pull/171) | Active; carried/refined form — DRM; `drivers/gpu/drm/drm_gem_shmem_helper.c` | C,H,M,F | +| [f0a8afc04957](https://github.com/aurora-silicon/linux/commit/f0a8afc0495743e502fc860fa2bc0fc96e95b22a) | Sami Tolvanen | rust: drm: add a registration guard for the Normal device context | Active — DRM; `rust/kernel/drm/device.rs` | C,H,M,F | +| [fd2a0321db11](https://github.com/aurora-silicon/linux/commit/fd2a0321db11572ac201663697c83c9330ef65a3) | DjDeveloperr | driver core: track devres node membership for direct removal [#390](https://github.com/aurora-silicon/linux/pull/390) | Active — DRM; `drivers/base/base.h` | B,M | + +### N/A, superseded, other-backend and imported-neighbor records + +These scoped candidates are included to make exclusions reviewable. A shared +path query or an omnibus PR is not evidence of a Neo performance improvement. + +| Commits / encountered aliases | Author | Improvement / record | T6040 after ports — evidence key and changed source | Measurement | +| --- | --- | --- | --- | --- | +| [4f772d2c2d6c](https://github.com/aurora-silicon/linux/commit/4f772d2c2d6cb336d4df08ae6c554b8387510c20) | Janne Grunau | drm/asahi: initdata: Fold GlobalsSub struct into Globals | Not applicable — LEGACY; `drivers/gpu/drm/asahi/fw/initdata.rs` | V | +| [532ac90e08af](https://github.com/aurora-silicon/linux/commit/532ac90e08affc3bbcf59e273e00f1573b7a05d0) | Janne Grunau | drm/asahi: Avoid variable/field ref shadowing in pin-init | Not applicable — LEGACY; `drivers/gpu/drm/asahi/initdata.rs` | V | +| [253d85ab9854](https://github.com/aurora-silicon/linux/commit/253d85ab98543f838ecc6be70edb7a43d966d505) | Sasha Finkelstein | drm/asahi: starlight-debug | Not applicable — LEGACY; `drivers/gpu/drm/asahi/gpu.rs` | V | +| [83526ea43d4f](https://github.com/aurora-silicon/linux/commit/83526ea43d4fffdb10a3dff36a37a587ffaa6c14) | Janne Grunau | drm/asahi: workqueue: Drop completed work items | Not applicable — LEGACY; `drivers/gpu/drm/asahi/workqueue.rs` | V | +| [b1e32124e9e9](https://github.com/aurora-silicon/linux/commit/b1e32124e9e94ebd3e1345eb54f687fcfbfee509) | Janne Grunau | drm/asahi: rustfmt | Not applicable — VERIFY; `drivers/gpu/drm/asahi/mmu.rs` | V | +| [8508e5105997](https://github.com/aurora-silicon/linux/commit/8508e5105997a43be75e8a3ffe0c3d8ddc00155c), [e092d51bc9d7](https://github.com/aurora-silicon/linux/commit/e092d51bc9d773f7058fe578515c5f915c0adf2c) | Yureka Lilian | pmdomain: add pmp-report offsets for t6030,t8122 | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [357978884c89](https://github.com/aurora-silicon/linux/commit/357978884c895e5fc35af84fed8adf0583bc3353), [a89f7da611ad](https://github.com/aurora-silicon/linux/commit/a89f7da611ade4b9d5f40dd45fe5b41d059e3399) | Yureka Lilian | arm64: dts: apple: add minimal MacBook Neo device tree | Not applicable — PERIPH | V | +| [dfe1431f40cf](https://github.com/aurora-silicon/linux/commit/dfe1431f40cf15ec4aebd1864b4c69707b3c43c2), [b796a047ec25](https://github.com/aurora-silicon/linux/commit/b796a047ec253074e2cd6b5f38a8f19949b50271), [9359ea48b718](https://github.com/aurora-silicon/linux/commit/9359ea48b7184cdee39b7c76b5f0d4a923807cf3) | Ryan Murray | arm64: dts: apple: Add T8140/J700 device trees with audio and camera | Not applicable — PERIPH | V | +| [0d31fa4418ec](https://github.com/aurora-silicon/linux/commit/0d31fa4418ec7c18ab984d6cfb0c4d10b80a0af1), [bffb1ed450d5](https://github.com/aurora-silicon/linux/commit/bffb1ed450d58d0c19f9e312c8b0c2314b9f1c53), [d575cca893bc](https://github.com/aurora-silicon/linux/commit/d575cca893bce9bedb5375be6bb8c209991faec6) | Ryan Murray | arm64: dts: apple: t8140: Add SPMI controller and PMIC NVMEM cells | Not applicable — PERIPH | V | +| [780ab337fd92](https://github.com/aurora-silicon/linux/commit/780ab337fd92e4511f4e4ee698820ae15d007cbc), [368b5343b9f7](https://github.com/aurora-silicon/linux/commit/368b5343b9f7c69683ee5a0c5239a6f96e8177fd), [c3874394cec6](https://github.com/aurora-silicon/linux/commit/c3874394cec613afc6b5c8615eae5cdd013e558c) | Ryan Murray | arm64: dts: apple: t8140: Add SMC node | Not applicable — PERIPH | V | +| [53296a0f9b6e](https://github.com/aurora-silicon/linux/commit/53296a0f9b6eae07ebce8a55c8b16309d87b2656), [985ee0213ef3](https://github.com/aurora-silicon/linux/commit/985ee0213ef3154c4d8c9763157f9620a7ce0243), [b8850ad18ff8](https://github.com/aurora-silicon/linux/commit/b8850ad18ff8d0a489c810fdafece6b8349f8694) | Ryan Murray | arm64: dts: apple: t8140: Add ANS2 NVMe controller | Not applicable — PERIPH | V | +| [4767bc0b221c](https://github.com/aurora-silicon/linux/commit/4767bc0b221cce5fb0c20dbacffe6f6bca7bc4a0), [5b9145de86c1](https://github.com/aurora-silicon/linux/commit/5b9145de86c13364ed3b9d465334674e7dc87083), [0b9461d4be5a](https://github.com/aurora-silicon/linux/commit/0b9461d4be5a77c0c74138900b8b92a7e193f8bb) | Ryan Murray | arm64: dts: apple: t8140-j700: Name the ALS calibration file | Not applicable — PERIPH | V | +| [2b45c6011879](https://github.com/aurora-silicon/linux/commit/2b45c601187962fca0b9289fdb1408e36ef5f838), [613b821cb4bb](https://github.com/aurora-silicon/linux/commit/613b821cb4bb48ccb999f75c4d5815f094f095be), [aefc56975b57](https://github.com/aurora-silicon/linux/commit/aefc56975b57d0e07a4a3e92d7e7af85f0a45cb4) | Ryan Murray | arm64: dts: apple: t8140-pmgr: Add ATC0 and nub SPMI A0 power domains | Not applicable — PERIPH | V | +| [e5992bd6f02c](https://github.com/aurora-silicon/linux/commit/e5992bd6f02c42e2e7404be54a41d8acaf685bb4), [dc6bc1f1613f](https://github.com/aurora-silicon/linux/commit/dc6bc1f1613f7e836b9514f40c07cb424622fca1), [0e335bcc4a79](https://github.com/aurora-silicon/linux/commit/0e335bcc4a799cc546ff5a6e9d5cdde644a53eea) | Ryan Murray | arm64: dts: apple: t8140: Add the MTP coprocessor and DockChannel HID nodes | Not applicable — PERIPH | V | +| [601fe7359e94](https://github.com/aurora-silicon/linux/commit/601fe7359e94f9f527487f2d1725cbfad8ea1ff2), [2d6ae0d0f507](https://github.com/aurora-silicon/linux/commit/2d6ae0d0f5073b6e29cb970926855de13d24a770), [895980a58c46](https://github.com/aurora-silicon/linux/commit/895980a58c46a6a1ec2a4e2bb2d5b9e815d4b349) | Ryan Murray | arm64: dts: apple: t8140: Add the USB-C controller, ATC PHY and SPMI A0 bus | Not applicable — PERIPH | V | +| [addbb17c2a81](https://github.com/aurora-silicon/linux/commit/addbb17c2a810bcaa1d68f6f9bc8e7fb401a508a), [7d1387799cba](https://github.com/aurora-silicon/linux/commit/7d1387799cba4442ba7ea99a95a962535cf26fc2), [7ded6b918bd3](https://github.com/aurora-silicon/linux/commit/7ded6b918bd3436fa2dc81b8b65f336bbe66ae85) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable the keyboard and trackpad | Not applicable — PERIPH | V | +| [70f40a78d8ea](https://github.com/aurora-silicon/linux/commit/70f40a78d8ea8db8b300d3d084b4228b3e172f19), [c94e793fea2a](https://github.com/aurora-silicon/linux/commit/c94e793fea2ad398c3aade3d3649ebc377189ec2), [b8f170b9347b](https://github.com/aurora-silicon/linux/commit/b8f170b9347ba943a11a30f6642a9532758965e4) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable the USB-C ports | Not applicable — PERIPH | V | +| [ea013658bf93](https://github.com/aurora-silicon/linux/commit/ea013658bf93d944617d01034cd7a5a3ece105a8), [e5a7b83611cc](https://github.com/aurora-silicon/linux/commit/e5a7b83611ccc434923524ea7bfb27f4e9c2a2ec), [09b788437876](https://github.com/aurora-silicon/linux/commit/09b788437876d50bf0d13d0d8cf7bfe9487a934b) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable the USB DARTs | Not applicable — PERIPH | V | +| [a97766963d24](https://github.com/aurora-silicon/linux/commit/a97766963d247d039cd76be83fd896b895e1ad2b), [e499013c56ad](https://github.com/aurora-silicon/linux/commit/e499013c56ad475dd45866c64e28dd33f95235c3), [d880e057bc1e](https://github.com/aurora-silicon/linux/commit/d880e057bc1e250d54a0b7dc2db23928f802a1d0) | Ryan Murray | arm64: dts: apple: t8140: Add the SMC RTC | Not applicable — PERIPH | V | +| [50e24690d43e](https://github.com/aurora-silicon/linux/commit/50e24690d43e525c1b2b217aa828a18e4c6503b9), [21ab6edf3da3](https://github.com/aurora-silicon/linux/commit/21ab6edf3da3111ed771155a9dbd2c83118cd0dc), [e90d32748ea7](https://github.com/aurora-silicon/linux/commit/e90d32748ea7857d242afc714365949e9ad86a3b) | Ryan Murray | arm64: dts: apple: t8140-j700: Describe the remaining SMC temperatures | Not applicable — PERIPH | V | +| [2cb796ddf423](https://github.com/aurora-silicon/linux/commit/2cb796ddf423b6d76e9d2ab1231f0b48ddbc2614), [e8d16b77d690](https://github.com/aurora-silicon/linux/commit/e8d16b77d6901f18a468d3d1b636990aa97f89cd), [23b613dfe8e7](https://github.com/aurora-silicon/linux/commit/23b613dfe8e757e1b599dc7cdfb7f9717687dc21) | Ryan Murray | arm64: dts: apple: t8140-j700: Describe the SMC current, power and voltage rails | Not applicable — PERIPH | V | +| [0cd1a28f2113](https://github.com/aurora-silicon/linux/commit/0cd1a28f2113c72a53052fb2733c2aced2f54476), [c2845ec53a91](https://github.com/aurora-silicon/linux/commit/c2845ec53a915dcadc4e68bbd59f97ca0764ddd6) | DjDeveloperr | iommu: apple-dart: support tunneled PCIe-C DMA aliases | Not applicable — TUNNEL; `drivers/iommu/Kconfig` | V | +| [6ff009379755](https://github.com/aurora-silicon/linux/commit/6ff00937975526780a22aa763211ba612f042997), [ee60f6cdcff8](https://github.com/aurora-silicon/linux/commit/ee60f6cdcff8561bff5ec48b10ba2a536a23ed9f) | DjDeveloperr | iommu: apple-dart: support retained PCIe-C tunnel domains | Not applicable — TUNNEL; `drivers/iommu/apple-dart.c` | V | +| [05ae2b2dfc28](https://github.com/aurora-silicon/linux/commit/05ae2b2dfc2853d7ae0f797d6299661e957116f3), [5987e8a08203](https://github.com/aurora-silicon/linux/commit/5987e8a08203974fe539aed3ce2de8ad5a3990a7) | Ryan Murray | arm64: dts: apple: t8140: Describe observed behaviour, not other implementations | Not applicable — VERIFY | V | +| [61f979fc1825](https://github.com/aurora-silicon/linux/commit/61f979fc18256a03db72933dde7f356a44f31eb2), [bf3cb50f012f](https://github.com/aurora-silicon/linux/commit/bf3cb50f012f9ecaeb465e861d8e896e580a69a0), [4fa6c2b053a6](https://github.com/aurora-silicon/linux/commit/4fa6c2b053a65088ca20641480fd9ed8da55716e) | Ace | arm64: dts: apple: Add diagnostic J700 PCIe root-port topology | Not applicable — PERIPH | V | +| [c309c0ebb058](https://github.com/aurora-silicon/linux/commit/c309c0ebb058bf6cd1df2503d121ace539448ecc), [5a4974fde407](https://github.com/aurora-silicon/linux/commit/5a4974fde40748a151a4c23c0df730f1148bc2df) | Ace | arm64: dts: apple: t8140-j700: enable bounded PCIe endpoint bootstrap and enumeration | Not applicable — PERIPH | V | +| [56e442c7cbbe](https://github.com/aurora-silicon/linux/commit/56e442c7cbbe49c2613a5209fbcf772e90bec10e), [716b60de9620](https://github.com/aurora-silicon/linux/commit/716b60de96207e43d3c06eeb9c424f147a5d16a3), [8a03c5341e27](https://github.com/aurora-silicon/linux/commit/8a03c5341e27e9066e15f924e88aa44f848b5294) | DjDeveloperr | arm64: dts: apple: enable AVD on T8140 | Not applicable — PERIPH | V | +| [2b9ef0954320](https://github.com/aurora-silicon/linux/commit/2b9ef0954320320edbee5b75e742176aef3ee8b3), [6f57627a710e](https://github.com/aurora-silicon/linux/commit/6f57627a710e655f30ec73ece845d18c52663d78), [4088c3b1854c](https://github.com/aurora-silicon/linux/commit/4088c3b1854cdbaf106d8bfe72510551bf9fd43c) | Ryan Murray | arm64: dts: apple: t8140: add disabled display power domains | Not applicable — PERIPH | V | +| [7e14b8e9e9a2](https://github.com/aurora-silicon/linux/commit/7e14b8e9e9a2bcb983725fc9a0cb3c016a5ed12b), [1b05c65c4276](https://github.com/aurora-silicon/linux/commit/1b05c65c42763d955c7ca69ef849d6bf5fac7355), [004200ec1cc8](https://github.com/aurora-silicon/linux/commit/004200ec1cc8005fa692d797d2c5fb009ec9e05a) | Ryan Murray | arm64: dts: apple: t8140: add disabled internal display DARTs | Not applicable — PERIPH | V | +| [d7de8bb51e4f](https://github.com/aurora-silicon/linux/commit/d7de8bb51e4f654b1c1d33c8cabd3290a9e231ea), [0c29d4d4a4ea](https://github.com/aurora-silicon/linux/commit/0c29d4d4a4ea28e024596f605676dc0d1d1e3ba0), [d2f162f6d7c2](https://github.com/aurora-silicon/linux/commit/d2f162f6d7c24ed783657112086f7b56e1fb0cbe) | Ryan Murray | arm64: dts: apple: t8140: add disabled external display DARTs | Not applicable — PERIPH | V | +| [7a28ede30a1f](https://github.com/aurora-silicon/linux/commit/7a28ede30a1ff3894d8f2407ba657d51e8d9bd95), [042b2a224bf6](https://github.com/aurora-silicon/linux/commit/042b2a224bf671bacb56b6e5345b3eb399c7ec02), [aec8cb1999f7](https://github.com/aurora-silicon/linux/commit/aec8cb1999f731bbe1895509dbcc6e2fed977348) | Ryan Murray | arm64: dts: apple: t8140: Add the SEP, its DART and mailbox, and SPI2 | Not applicable — PERIPH | V | +| [07029412ae8d](https://github.com/aurora-silicon/linux/commit/07029412ae8d5832c1323d3eeb8a08b09f7b5be0), [1eb7bfe18b01](https://github.com/aurora-silicon/linux/commit/1eb7bfe18b01c0a088dd8e25a72ee5af3a09531a), [29987787b923](https://github.com/aurora-silicon/linux/commit/29987787b9238b15b41b92da87e5fcbf8a571e8c) | Ryan Murray | arm64: dts: apple: t8140-j700: Enable the SEP and the Touch ID sensor | Not applicable — PERIPH | V | +| [7d84216f74e8](https://github.com/aurora-silicon/linux/commit/7d84216f74e8e9e5827eabc9f173524ce5520c9b), [6049af950206](https://github.com/aurora-silicon/linux/commit/6049af950206644d95b8cd3ca11fe0722ff71257), [52f1ba512244](https://github.com/aurora-silicon/linux/commit/52f1ba51224431d256250c69784e1996f6ee63cc) | Acelogic | arm64: dts: apple: wire the J700 AVE encoder | Not applicable — PERIPH | V | +| [5f1fbb7bd670](https://github.com/aurora-silicon/linux/commit/5f1fbb7bd670ca8a26c303594f9ed54535e51f02), [519558a43644](https://github.com/aurora-silicon/linux/commit/519558a43644d79c2cc15135554748ffe6fadc81), [f898b116beaa](https://github.com/aurora-silicon/linux/commit/f898b116beaaab7add264a0f074b21743875d3ae) | Ryan Murray | arm64: dts: apple: Describe additional T8140 peripheral power states | Not applicable — PERIPH | V | +| [555932c62f03](https://github.com/aurora-silicon/linux/commit/555932c62f03ec2833c5db9b39e2d2dc956501cb), [ed2ef28706b7](https://github.com/aurora-silicon/linux/commit/ed2ef28706b7c2ed13aef2f90eb7191b4e34338e), [f75a1041fca4](https://github.com/aurora-silicon/linux/commit/f75a1041fca487774f29b133f736296f4d73a4e3) | Ryan Murray | arm64: dts: apple: Wire up remaining supported J700 peripherals | Not applicable — PERIPH | V | +| [a3d7465380ff](https://github.com/aurora-silicon/linux/commit/a3d7465380ffbf5f9cb4eddb550fabfdd71d3a17), [49fdcbcee4f4](https://github.com/aurora-silicon/linux/commit/49fdcbcee4f4569e6b65f7912e05502a38299743) | Ryan Murray | arm64: dts: apple: describe the J700 PMU alarm function | Not applicable — PERIPH | V | +| [d810cb82e82d](https://github.com/aurora-silicon/linux/commit/d810cb82e82de69013754b6031d00a83a6d15e41), [4e0543ee6caf](https://github.com/aurora-silicon/linux/commit/4e0543ee6cafb4250aaf332f894773412b86dfe2), [b9a339703e95](https://github.com/aurora-silicon/linux/commit/b9a339703e95a6a65e0e4515caa4c4f6afb0d9ce) | Ryan Murray | arm64: dts: apple: describe the disabled T8140 Neural Engine | Not applicable — PERIPH | V | +| [19f7cbdd84fe](https://github.com/aurora-silicon/linux/commit/19f7cbdd84fef12ca56aad9b654b46465cc2e198), [6b57d5423048](https://github.com/aurora-silicon/linux/commit/6b57d5423048ac4c70cd99d7473494ac3d37b2db), [93bc234385f5](https://github.com/aurora-silicon/linux/commit/93bc234385f5bf51b276e9d2e5abe8280e2ff92c) | Ryan Murray | arm64: dts: apple: t8140: Leave display coprocessor power to firmware | Not applicable — PERIPH | V | +| [0f5548d142b1](https://github.com/aurora-silicon/linux/commit/0f5548d142b1d73c44d270c91d5c359272dae499), [7c3c5ee5a070](https://github.com/aurora-silicon/linux/commit/7c3c5ee5a0702bb5b9f0ae4cde6b837cbafa3485), [9e6acbbd21ba](https://github.com/aurora-silicon/linux/commit/9e6acbbd21ba92409dd4f24074532b0bad997792) | Ryan Murray | arm64: dts: apple: t8140: Describe the internal display coprocessor | Not applicable — PERIPH | V | +| [100ff1ebedd3](https://github.com/aurora-silicon/linux/commit/100ff1ebedd337758a4ff3ba3fd3f2f46a9f5be9), [45ae8211efea](https://github.com/aurora-silicon/linux/commit/45ae8211efeafe3bff64b569370942c5218f1fec), [061370b4c70f](https://github.com/aurora-silicon/linux/commit/061370b4c70fd888ea6dc520a8988208f4aebca8) | Ryan Murray | arm64: dts: apple: t8140: Hand the J700 panel over to the DCP | Not applicable — PERIPH | V | +| [29f8091fe351](https://github.com/aurora-silicon/linux/commit/29f8091fe351a0cdb781cf18ac5748ca15feca42), [4f3906d225f4](https://github.com/aurora-silicon/linux/commit/4f3906d225f44739df672ac0b76715407f52e586), [a7fa3d8fbf56](https://github.com/aurora-silicon/linux/commit/a7fa3d8fbf5690df6553896a4f984b8cdc8069c2) | Ryan Murray | arm64: dts: apple: t8140: describe each power state once | Not applicable — PERIPH | V | +| [89c54797ae77](https://github.com/aurora-silicon/linux/commit/89c54797ae77fea8e8878a68e0b56f382cfb0409), [07969983cb1e](https://github.com/aurora-silicon/linux/commit/07969983cb1edefaec1b4b0aabb2307b0b14dacd), [953220ab6d74](https://github.com/aurora-silicon/linux/commit/953220ab6d748bd05db3854f374b701696f6c795) | Ryan Murray | arm64: dts: apple: t8140: describe the external display DARTs once | Not applicable — PERIPH | V | +| [55a5beb425ae](https://github.com/aurora-silicon/linux/commit/55a5beb425ae7c901851c2185e3219d8c0348fcb), [f0234a79d172](https://github.com/aurora-silicon/linux/commit/f0234a79d172479cb7dbcdb713daa5739ad87f74), [c80113dfaab9](https://github.com/aurora-silicon/linux/commit/c80113dfaab9e985b9f617bfde06c481fd17376c) | Ryan Murray | arm64: dts: apple: t8140-j700: give the two RTCs fixed numbers | Not applicable — PERIPH | V | +| [9e1fb240e1df](https://github.com/aurora-silicon/linux/commit/9e1fb240e1df4064a9232007daa5c8797dfbcda4), [19e2b5adfe69](https://github.com/aurora-silicon/linux/commit/19e2b5adfe6905ab2f3fff6ad1a12801d711c607), [ea044d49c76c](https://github.com/aurora-silicon/linux/commit/ea044d49c76c18079848672c2779c7521a6b8fc3) | Ryan Murray | arm64: dts: apple: t8140: Keep the GPIO power state on for the SEP | Not applicable — PERIPH | V | +| [4334bbd7162a](https://github.com/aurora-silicon/linux/commit/4334bbd7162af085ed1c8c7b63859bed8c6646ef), [f1c076a9bf8a](https://github.com/aurora-silicon/linux/commit/f1c076a9bf8a850bec0140fb3e91a9607aadcf4e), [30812f491575](https://github.com/aurora-silicon/linux/commit/30812f491575ede27e5be059fd6afc4e985166f1) | DjDeveloperr | arm64: dts: apple: t8140-j700: keep the DCS rail out of thermal control | Not applicable — DCSOFF | P,R | +| [baacaf604632](https://github.com/aurora-silicon/linux/commit/baacaf60463212b2270f0ad0b50cdabe4e27ab6d), [071651bd5c30](https://github.com/aurora-silicon/linux/commit/071651bd5c30330ecb9980c3c1f2cc4e2f04d1a6), [40a9c929c55f](https://github.com/aurora-silicon/linux/commit/40a9c929c55f2b13c39e732fc2ae3f10e1506634) | Ryan Murray | arm64: dts: apple: describe disabled J700 external resources | Not applicable — PERIPH | V | +| [13af1cb29b95](https://github.com/aurora-silicon/linux/commit/13af1cb29b95b1fc260870868d3c54a596cc483d), [f284458cdfc3](https://github.com/aurora-silicon/linux/commit/f284458cdfc33d2746bb4ede9174adc3afaa2f7f) | Ryan Murray | arm64: dts: apple: t8140: describe the external binding the display master recognizes | Not applicable — PERIPH | V | +| [a7f9b86ea187](https://github.com/aurora-silicon/linux/commit/a7f9b86ea18778f313b73cd174debe54e4dbc09c), [cffe838aca8e](https://github.com/aurora-silicon/linux/commit/cffe838aca8e275526730bd6f3fee5ebcaec0c47), [205a56d33cb0](https://github.com/aurora-silicon/linux/commit/205a56d33cb07184d7dfe17ab8aba0996cff3efe) | Ryan Murray | arm64: dts: apple: retain the inherited external display supply | Not applicable — PERIPH | V | +| [328dacb9d1ad](https://github.com/aurora-silicon/linux/commit/328dacb9d1adcbc1be84e8dae660c72bc12a3005), [3d16013f9795](https://github.com/aurora-silicon/linux/commit/3d16013f979572f7f7f6d1573f2af739e8ac25b4), [2e6003f6db07](https://github.com/aurora-silicon/linux/commit/2e6003f6db07b0e0a6b2bf2b977753bee47556b0) | Ryan Murray | arm64: dts: apple: add an explicit external display trial tree | Not applicable — PERIPH | V | +| [6b82a41053b8](https://github.com/aurora-silicon/linux/commit/6b82a41053b87c8bd0917f72f71778293aa7f276), [70cce5cd20ef](https://github.com/aurora-silicon/linux/commit/70cce5cd20efb8a71d49563b45481c32e0de23a6), [0a0d25f4a99b](https://github.com/aurora-silicon/linux/commit/0a0d25f4a99b66a08a2ab4a22314782aaad7b3e2) | DjDeveloperr | arm64: dts: apple: t8140-j700: drive the SPI NOR on a single data line | Not applicable — PERIPH | V | +| [d56b7ce044f2](https://github.com/aurora-silicon/linux/commit/d56b7ce044f274b153a3b1f9192d3fd4c7cb2d85), [df94160d7567](https://github.com/aurora-silicon/linux/commit/df94160d756756e14d4bc818fe0652bcdb9adc30) | DjDeveloperr | arm64: dts: apple: t8140-j700: enable the validated external display profile by default | Not applicable — PERIPH | V | +| [f9e24b02bc85](https://github.com/aurora-silicon/linux/commit/f9e24b02bc8547acba1453cf462f0f210e707ba7), [be5f3162c985](https://github.com/aurora-silicon/linux/commit/be5f3162c9854d6b5ea8ca9e8f29f764b2c6251b) | Eryk Wieliczko | arm64: dts: apple: t8140: mark firmware-owned power domains | Not applicable — PERIPH | V | +| [7df682a70733](https://github.com/aurora-silicon/linux/commit/7df682a707337025aad3c093252ea7f8504e8345), [b0714e8783f6](https://github.com/aurora-silicon/linux/commit/b0714e8783f685f213c15890c7942c1c03d6c1e3) | Chris Kearney | PCI: apple: enumerate Thunderbolt PCIe on t8103 | Not applicable — LEGACY; `drivers/iommu/apple-dart.c` | V | +| [4c02ff022799](https://github.com/aurora-silicon/linux/commit/4c02ff022799be8aef47108a61671a6a6cd86038), [b388a175a612](https://github.com/aurora-silicon/linux/commit/b388a175a612be6c5d10056c6a10a7219030272d) | Chris Kearney | PCI: apple: finish tunneled IOMMU commands before the port clock stops | Not applicable — TUNNEL; `drivers/iommu/apple-dart.c` | V | +| [9e8a900fee27](https://github.com/aurora-silicon/linux/commit/9e8a900fee275872aa1019df27a4944b73c8940a), [a2b13c1f0369](https://github.com/aurora-silicon/linux/commit/a2b13c1f03696ae69101c8ed51b8901d32dffcd4), [5a7b76f31c3a](https://github.com/aurora-silicon/linux/commit/5a7b76f31c3a8ccdc48fd586e5b646fa0c49499d) | Chris Kearney | iommu/apple-dart: enforce command gating during tunnel teardown | Not applicable — TUNNEL; `drivers/iommu/apple-dart-test.c` | V | +| [39e90a052954](https://github.com/aurora-silicon/linux/commit/39e90a052954d1305a45ca0065eda42ec4aff639), [d15623fd3261](https://github.com/aurora-silicon/linux/commit/d15623fd32614870a9eb62d25afdd823a39bcde7), [65e3e0ed688a](https://github.com/aurora-silicon/linux/commit/65e3e0ed688a5a933a60d92ca79b6136c357b876) | Chris Kearney | iommu/apple-dart: report tunnel command-resume failures | Not applicable — TUNNEL; `drivers/iommu/apple-dart-test.c` | V | +| [3a94483e259c](https://github.com/aurora-silicon/linux/commit/3a94483e259cbc8d0aab638eaa4af5b72c4db6e6), [6e0c3cdf7855](https://github.com/aurora-silicon/linux/commit/6e0c3cdf78550987513b0d4c15ffec90b442b787), [64dfa24ca989](https://github.com/aurora-silicon/linux/commit/64dfa24ca989fec06367a7199164a70de7d99e35) | Chris Kearney | PCI: apple: describe tunneled PM ordering by Linux dependencies | Not applicable — TUNNEL; `drivers/iommu/apple-dart.c` | V | +| [5d4dd51e2769](https://github.com/aurora-silicon/linux/commit/5d4dd51e27695b5d9570e56b050e3574dfc1fd38), [e7292daedcaa](https://github.com/aurora-silicon/linux/commit/e7292daedcaa2186e5b8b484343dc3ec239b2805), [615be505e22c](https://github.com/aurora-silicon/linux/commit/615be505e22c592c1a87c193b22c58dc4c064840) | Chris Kearney | thunderbolt: restore M1 tunneled PCIe state and recover late link loss | Not applicable — TUNNEL; `drivers/iommu/apple-dart-test.c` | V | +| [95caa2cf7ac7](https://github.com/aurora-silicon/linux/commit/95caa2cf7ac7244080527b90da0d43ec917e1513), [92f33b680514](https://github.com/aurora-silicon/linux/commit/92f33b6805149c955b543a6e2c29f88c42886f6d), [fc559d251fca](https://github.com/aurora-silicon/linux/commit/fc559d251fcac7e904607a36fb4b17770e29f239) | Acelogic | irqchip/apple-aic: use architectural guest timer masks on T8142 | Not applicable — OTHERCHIP; `drivers/irqchip/irq-apple-aic.c` | V | +| [4d1bcf62c1af](https://github.com/aurora-silicon/linux/commit/4d1bcf62c1af6fa29a32e94d821f4174098cbd29), [4ae1f9604a1a](https://github.com/aurora-silicon/linux/commit/4ae1f9604a1aa503dd1b452578bf694dbadaca25), [40bc3b97c1d6](https://github.com/aurora-silicon/linux/commit/40bc3b97c1d6a75ff5f8af9bc75322388a5381e1) | DjDeveloperr | soc: apple: rtkit-helper: adopt a running T8140 MTP session | Not applicable — PERIPH; `drivers/soc/apple/rtkit-helper.c` | V | +| [ad7993e04cd2](https://github.com/aurora-silicon/linux/commit/ad7993e04cd2e39c4fe011740268a537868bb8c0), [58a792863c4f](https://github.com/aurora-silicon/linux/commit/58a792863c4fdd79342e5e83ece8f652993084ab), [80cabfefcdc5](https://github.com/aurora-silicon/linux/commit/80cabfefcdc52036a7902268ff42f2a9370fe5c2) | Acelogic | cpufreq: apple: Add bounded T8152 E and P/M performance domains | Not applicable — OTHERCHIP; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [81aefe80b586](https://github.com/aurora-silicon/linux/commit/81aefe80b586fa545b20eb90090202cde51718e7), [48f073156423](https://github.com/aurora-silicon/linux/commit/48f0731564236dc79c43bbcdadf6b05cfd807e8a), [296dbb45adff](https://github.com/aurora-silicon/linux/commit/296dbb45adff8654c6f1f8029e7b6b567f990e0b) | Acelogic | cpufreq: apple: add T8142 command-state transitions | Not applicable — OTHERCHIP; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [4c3405d11d29](https://github.com/aurora-silicon/linux/commit/4c3405d11d29ec8a49a14bdc546cef4411153b32), [b08be27ef235](https://github.com/aurora-silicon/linux/commit/b08be27ef235dfedb3d18cf4a91a821be8a7effc) | Ryan Murray | cpufreq: apple: retain the unmanaged P-state ceiling on T8152 | Not applicable — OTHERCHIP; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [24f4a32e8ec9](https://github.com/aurora-silicon/linux/commit/24f4a32e8ec9438b198e7ca97658154bcaedc6f7), [129471fc83b8](https://github.com/aurora-silicon/linux/commit/129471fc83b89e08275048a812779583ef197a93), [c0bd8b9fec16](https://github.com/aurora-silicon/linux/commit/c0bd8b9fec163274b9e6056ae94fe54d53b565fb), [b4ee9bd8d923](https://github.com/aurora-silicon/linux/commit/b4ee9bd8d923949b19cc22484262670948cafc57) | DjDeveloperr | drm/asahi: clarify the opening polling budget [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/mod.rs` | V | +| [52f6e8610625](https://github.com/aurora-silicon/linux/commit/52f6e86106255b26e84671649c5f4acb0f434cde), [840488b0b60d](https://github.com/aurora-silicon/linux/commit/840488b0b60d3162e92f050cc59a2b3967036544), [66631bc63295](https://github.com/aurora-silicon/linux/commit/66631bc632956bb7dfd1b4fb44e48ae90a10140e), [d0aadc1e99e6](https://github.com/aurora-silicon/linux/commit/d0aadc1e99e6f11a54bbca5ee685d7c015866789) | DjDeveloperr | drm/asahi: describe the G17 word tables without their generation history [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/fw/initdata.rs` | V | +| [1c771d73ac6c](https://github.com/aurora-silicon/linux/commit/1c771d73ac6c5e953be9582f1b74ccb358e6322d), [d24b1a49853f](https://github.com/aurora-silicon/linux/commit/d24b1a49853fe6949b44790761678cd2d38e623e), [a6e06b47b49d](https://github.com/aurora-silicon/linux/commit/a6e06b47b49d2666a53b5bc45ba8e0fa22e74944) | Joshua Warren | drm/asahi: Add stats_export module parameter | Not applicable — STAT; `drivers/gpu/drm/asahi/Makefile` | T | +| [3f096100f658](https://github.com/aurora-silicon/linux/commit/3f096100f658c1563dafaa0f0ca8b77ea2a87fe3), [f436b4da5480](https://github.com/aurora-silicon/linux/commit/f436b4da5480140e5ba95d68e46f1e7dd7615db8) | Ryan Murray | drm/asahi: tie stats registration to a per-device snapshot owner | Not applicable — STAT; `drivers/gpu/drm/asahi/g17/mod.rs` | T | +| [fd13851e08b6](https://github.com/aurora-silicon/linux/commit/fd13851e08b689661332ee823ad65eafd4418c1a), [c91a9274e4b2](https://github.com/aurora-silicon/linux/commit/c91a9274e4b2736d5589826b07c8dc54c0e35055) | Ryan Murray | drm/asahi: describe the G17 tiler-heap shrink policy in hardware terms | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/buffer.rs` | V | +| [af5c7b24a0e5](https://github.com/aurora-silicon/linux/commit/af5c7b24a0e5932026c8cbab8633f2563bf761ba), [f0ec35d78e5a](https://github.com/aurora-silicon/linux/commit/f0ec35d78e5adf9988c1bc71418d0a764c8d4ba3), [337b14463d50](https://github.com/aurora-silicon/linux/commit/337b14463d5010296efb0b17dfaa11b8f7601aae) | Ryan Murray | arm64: dts: apple: t8140: Fold the trailing soc override into the soc node [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — PERIPH | V | +| [3cd89c634a60](https://github.com/aurora-silicon/linux/commit/3cd89c634a60071014683a27d497e46b8e2e2fc5), [f5ba7b6ee41c](https://github.com/aurora-silicon/linux/commit/f5ba7b6ee41ca4545745bfcc312fdbc0abf3e491), [6f1b4ab6f2f0](https://github.com/aurora-silicon/linux/commit/6f1b4ab6f2f036dedd104b83df774d6284ecab74) | Ryan Murray | arm64: dts: apple: t8140: Tidy the mailbox and pinctrl comments [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — PERIPH | V | +| [553e961d56c9](https://github.com/aurora-silicon/linux/commit/553e961d56c9ecbf6d58561c98468496955ad1d3), [38dd24cdc5a3](https://github.com/aurora-silicon/linux/commit/38dd24cdc5a3e13555afcac85f28d8ca3fc77a8e), [742366203cc8](https://github.com/aurora-silicon/linux/commit/742366203cc8a1f9356d82a43d52e2cb6fa97621) | Ryan Murray | pmdomain: apple: pmp-report: remove the raw DVFS debugfs dump [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — RAWDIAG; `drivers/pmdomain/apple/pmp-report.c` | O | +| [d04cbb892d26](https://github.com/aurora-silicon/linux/commit/d04cbb892d26dca250de4ff6f8c6cde5446682fb), [34754dd5bf3b](https://github.com/aurora-silicon/linux/commit/34754dd5bf3bd3d21b3409b6768af8b8fa38a045), [21c1d3f21ac4](https://github.com/aurora-silicon/linux/commit/21c1d3f21ac49f4223993ed1beefca1d6d6e89cd) | Ryan Murray | arm64: dts: apple: t8140: state why PMGR domains off at boot are always-on [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH | V | +| [c3760b2f1cbd](https://github.com/aurora-silicon/linux/commit/c3760b2f1cbd4e17586ced9ac0d9ae7e8fe7d024), [732facd15036](https://github.com/aurora-silicon/linux/commit/732facd1503659e30fb096e0276e2efcdfa41ed7), [0d1799c7c179](https://github.com/aurora-silicon/linux/commit/0d1799c7c179d3b6e914eeaee59da7529e824565) | Ryan Murray | arm64: dts: apple: t8140-j700: trim measurement notes from comments [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH | V | +| [ac9e0f78ef56](https://github.com/aurora-silicon/linux/commit/ac9e0f78ef560ef9d7a5c5655c7253e938b08d71), [f2928aa2ef66](https://github.com/aurora-silicon/linux/commit/f2928aa2ef662ff9045367ee9910a62a0f0b4e4b) | Ryan Murray | pmdomain: apple: pmc: Test partial enables and probe unwind | Not applicable — VERIFY; `drivers/pmdomain/apple/pmc-test.c` | V | +| [e8e0eae211f9](https://github.com/aurora-silicon/linux/commit/e8e0eae211f9d97ad777522ac8bf0c331ebf20f8), [a20499ed1321](https://github.com/aurora-silicon/linux/commit/a20499ed1321d5e5054d0f3dfd3f3a622e2938be) | Ryan Murray | soc: apple: mcc: Test partial allocation enable and cache handoff | Not applicable — VERIFY; `drivers/soc/apple/Kconfig` | V | +| [f2669637e7a6](https://github.com/aurora-silicon/linux/commit/f2669637e7a6c7e24f8eac823097156728ce64a8), [e3d59133d7a7](https://github.com/aurora-silicon/linux/commit/e3d59133d7a793ac15e3fa7328d11dafff8d8c92), [d6094473be4c](https://github.com/aurora-silicon/linux/commit/d6094473be4ceae69e8385ffb03891a22c60c3e4) | Ryan Murray | soc: apple: rtkit-helper: Terminate log messages [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/rtkit-helper.c` | V | +| [8a83508a3870](https://github.com/aurora-silicon/linux/commit/8a83508a3870e8dd8d15ade42c759ed1be8fc9c1), [bee2492c2bac](https://github.com/aurora-silicon/linux/commit/bee2492c2bac7c4cf6022cffadd92b1e4fae2f54), [0afefd991028](https://github.com/aurora-silicon/linux/commit/0afefd9910288c1783a2756c4240dfc80ee8470f) | Ryan Murray | iommu: apple-dart: Return IOMEM_ERR_PTR() from the tunneled MMIO helper [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — TUNNEL; `drivers/iommu/apple-dart.c` | V | +| [d970f88dd819](https://github.com/aurora-silicon/linux/commit/d970f88dd819b24905808cac119647625e382303), [6b8a1244cb2c](https://github.com/aurora-silicon/linux/commit/6b8a1244cb2c8e0da6290fe794fb442b5cb9404e), [27da3a5d53ca](https://github.com/aurora-silicon/linux/commit/27da3a5d53ca11f2aeff4916d25f40308c378a07) | Eryk Wieliczko | arm64: dts: apple: t8140-j700: give the boot framebuffer its panel | Not applicable — PERIPH | V | +| [8a9127fdd1f5](https://github.com/aurora-silicon/linux/commit/8a9127fdd1f54bfe2913e7996073a994eb41ff04), [8feaf5d2fa71](https://github.com/aurora-silicon/linux/commit/8feaf5d2fa714a11984b1eeafd5dd2e076ec4af6), [748b2a25f2ca](https://github.com/aurora-silicon/linux/commit/748b2a25f2ca960017eef868e2323a27e07f0fe2) | Ryan Murray | drm/asahi: clean up the statistics Rust style | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [c987a1793e75](https://github.com/aurora-silicon/linux/commit/c987a1793e759da730ff84384a601d9d96d916d3), [37b3b1f7de85](https://github.com/aurora-silicon/linux/commit/37b3b1f7de8547eca29a4414465b5b4d705d413d) | Chris Kearney | drm/apple: wait for T6030 RTKit callbacks before a DCP is freed | Not applicable — G15; `drivers/gpu/drm/apple/dcp.c` | V | +| [9639e3803ffb](https://github.com/aurora-silicon/linux/commit/9639e3803ffb7f73c58dbbacad17a6e40b7596ac), [346fd6083ab2](https://github.com/aurora-silicon/linux/commit/346fd6083ab2d45a079fe512c6b3e68affd4cc6f) | Ryan Murray | drm/asahi: free completed commands on system_dfl_wq | Not applicable — LEGACY; `drivers/gpu/drm/asahi/workqueue.rs` | V | +| [3e6775c52948](https://github.com/aurora-silicon/linux/commit/3e6775c52948793951b618817b3715eec5f91924), [b9527e10b2b6](https://github.com/aurora-silicon/linux/commit/b9527e10b2b683dff7d4b718e9af6f93fb0a005f) | Ryan Murray | arm64: add an isolated T8140 graphics and power profile | Not applicable — PERIPH | V | +| [7f40cd79f058](https://github.com/aurora-silicon/linux/commit/7f40cd79f0582c4ca1abbad36f1e923d8163779f), [58db6bfedcec](https://github.com/aurora-silicon/linux/commit/58db6bfedcec7cb4a2be3843c5d3665b4680553e) | Chris Kearney | soc: apple: test every PMP integer replacement width | Not applicable — VERIFY; `drivers/soc/apple/pmp_bootargs.rs` | V | +| [0a1f130cd042](https://github.com/aurora-silicon/linux/commit/0a1f130cd0427037fc94a4ad39e54a3c66d5dbac), [f11518fc495d](https://github.com/aurora-silicon/linux/commit/f11518fc495d51dc3c5d8f67cc10d7cb02a4d739) | Eryk Wieliczko | pmdomain: apple: add the GFX TVM gate | Not applicable — G16; `drivers/pmdomain/apple/Makefile` | V | +| [4e7c8a74e4a5](https://github.com/aurora-silicon/linux/commit/4e7c8a74e4a5ef03a1d06b5cd73f01c68b75a63d), [fe7d063e4cee](https://github.com/aurora-silicon/linux/commit/fe7d063e4cee437b5196c438f9f19a6ad7e22afc) | Satya Benson | pmdomain: apple: gfx-tvm: accept the T8122 | Not applicable — G15; `drivers/pmdomain/apple/gfx-tvm.c` | V | +| [460c0a77ca86](https://github.com/aurora-silicon/linux/commit/460c0a77ca860aef48d7ed449d65a878cffab9ed), [c8aed729e9bf](https://github.com/aurora-silicon/linux/commit/c8aed729e9bf8d23f0adabff999aa16d34201d76) | Joshua Warren | gpu: let the firmware preempt compute work on G14X | Not applicable — LEGACY; `drivers/gpu/drm/asahi/asahi_core.rs` | V | +| [80797e4f1b2b](https://github.com/aurora-silicon/linux/commit/80797e4f1b2bbde71964ea2253553d2c2dbbe379), [9c7ab8b412a5](https://github.com/aurora-silicon/linux/commit/9c7ab8b412a50123bf7dbc642368aa7f930d2d41) | Ryan Murray | drm/asahi: gate compile-only M3 backends before resource setup | Not applicable — G15; `drivers/gpu/drm/asahi/asahi_core.rs` | V | +| [700bfca794e5](https://github.com/aurora-silicon/linux/commit/700bfca794e50093c6e4d2b4231aca6252451a41), [860d185ccc57](https://github.com/aurora-silicon/linux/commit/860d185ccc575a47df4962cb1cf32da8f27ff35f) | Chris Kearney | drm/asahi: add the M3 (G15S) GPU runtime (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_compute_layout.rs` | V | +| [01680f395e55](https://github.com/aurora-silicon/linux/commit/01680f395e557e93b331ad510fbf255e6622c2a0), [a846942e367d](https://github.com/aurora-silicon/linux/commit/a846942e367dade6002d2d456ae6249c50d04d39) | Eryk Wieliczko | drm/asahi: share M3 M4 host retirement accounting (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_host_progress.rs` | V | +| [9593d5f2498f](https://github.com/aurora-silicon/linux/commit/9593d5f2498f3243100c828cc7bbf7ecdaddb861), [4bcb6f1acab4](https://github.com/aurora-silicon/linux/commit/4bcb6f1acab42a1772bf62aaae00c5fb1f6007cd) | Eryk Wieliczko | drm/asahi: account M3 GEM and coherent firmware backing lifetimes (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_memory_stats.rs` | V | +| [bba3fa8f4562](https://github.com/aurora-silicon/linux/commit/bba3fa8f4562761864f8f57b1a461bcd50ec3d64), [3e963ea31aca](https://github.com/aurora-silicon/linux/commit/3e963ea31acaad3e4a78dc137f12339fa8ebb5ee) | Chris Kearney | drm/asahi: expose bounded native retirement timing snapshots (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_timing_stats.rs` | V | +| [6d24d16ce24c](https://github.com/aurora-silicon/linux/commit/6d24d16ce24c8af1902191781e1255807ccd8aae), [1324a0258bb0](https://github.com/aurora-silicon/linux/commit/1324a0258bb0d0987993a3c34c2a74b0f2093cce) | Chris Kearney | drm/asahi: expose host-tracked pending GPU activity separately from completions (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_host_progress.rs` | V | +| [a4bbcd4baf93](https://github.com/aurora-silicon/linux/commit/a4bbcd4baf933a15e5bc75b2c7c5603d8b1b6998), [8faa4de3948b](https://github.com/aurora-silicon/linux/commit/8faa4de3948b0795c068eeeb0456a6a7dfc45077) | Chris Kearney | drm/asahi: avoid repeated activity fences while GPU work is pending (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_host_progress.rs` | V | +| [2397ac8a0861](https://github.com/aurora-silicon/linux/commit/2397ac8a086119bba90f6fc0d8d430e035d27f30), [b62d349e0fc6](https://github.com/aurora-silicon/linux/commit/b62d349e0fc6e160b9dabd167166a2d87ecfdbce) | Eryk Wieliczko | drm/asahi: remove M3 lifetime submission cap with wrapping wire stamps (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_timeline.rs` | V | +| [ce78a5b0c517](https://github.com/aurora-silicon/linux/commit/ce78a5b0c5173dbb9b5d7d0017488c43a07f222f), [578aa862e357](https://github.com/aurora-silicon/linux/commit/578aa862e357f0f61e18dc8e26181eeb5c0b9d7b) | Eryk Wieliczko | drm/asahi: preserve nonzero M3 stamps and dependencies through rollover (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_timeline.rs` | V | +| [646db65b2128](https://github.com/aurora-silicon/linux/commit/646db65b212826a29bd969332760722077926d61), [1d6a90b4117f](https://github.com/aurora-silicon/linux/commit/1d6a90b4117fe9cb84aa7863f491d18d37fcf0c5) | Chris Kearney | drm/asahi: remove unused backend helpers and narrow layout visibility (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_compute_storage.rs` | V | +| [a91573de916a](https://github.com/aurora-silicon/linux/commit/a91573de916a12ddc980c75d977567e57725e449), [f939d03f703f](https://github.com/aurora-silicon/linux/commit/f939d03f703fe760f413a87d435769b3aa970d21) | Chris Kearney | treewide: apple: reword M3 comments and messages as plain technical statements (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_compute_sequence.rs` | V | +| [997a7dba8158](https://github.com/aurora-silicon/linux/commit/997a7dba81582c94d1bdcc26aaae6031a486390c), [262009a6c7f9](https://github.com/aurora-silicon/linux/commit/262009a6c7f9dc7b6ca4abb158b669870a6180c5) | Chris Kearney | drm/asahi: select M3 runtime configuration per SoC (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_init_storage.rs` | V | +| [a8065eb937ce](https://github.com/aurora-silicon/linux/commit/a8065eb937cee67032a68207827419c515e52e11), [d16a8c32954b](https://github.com/aurora-silicon/linux/commit/d16a8c32954b412bb3bf9f61a7c48a68dc9303b1) | Chris Kearney | drm/asahi: m3: derive runtime IO maps from IO mappings (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_init_storage.rs` | V | +| [c3d978ed0793](https://github.com/aurora-silicon/linux/commit/c3d978ed0793058248d9994e868f5c1043cc18f7), [7f04f81b5a19](https://github.com/aurora-silicon/linux/commit/7f04f81b5a191b8ff9b1a3d454656af3993b47be) | Satya Benson | drm/asahi: m3: size render TPC and preemption storage by cluster count (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_pass_layout.rs` | V | +| [4287d2b5166b](https://github.com/aurora-silicon/linux/commit/4287d2b5166bb92f791ae652dba8361cfc732aeb), [d94dcc88b26c](https://github.com/aurora-silicon/linux/commit/d94dcc88b26ca0e964b5298ee1298ed068cbf6eb) | Satya Benson | drm/asahi: m3: publish queued packets from the completion worker (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [6046b500e687](https://github.com/aurora-silicon/linux/commit/6046b500e6872cfe56d53cb65e9b0619cb1f066c), [6787ae6d5717](https://github.com/aurora-silicon/linux/commit/6787ae6d57170ef75c454f6f744d4c4d557cb9a7) | Satya Benson | drm/asahi: m3: bound overlapping render batches to free pass slots (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [4ea0ea7df543](https://github.com/aurora-silicon/linux/commit/4ea0ea7df5437fad2829ec8eb68be6e9f3544591), [ce9df27fd4b6](https://github.com/aurora-silicon/linux/commit/ce9df27fd4b67bd47194655b57efecdec2321081) | Satya Benson | drm/asahi: m3: budget render coalescing by measured per-VM GPU time (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [b877dfb7e3e8](https://github.com/aurora-silicon/linux/commit/b877dfb7e3e89e31fef97057c68581660b6f256e), [d57591e332b4](https://github.com/aurora-silicon/linux/commit/d57591e332b49cafc12aaf8af77845525dda760b) | Chris Kearney | drm/asahi: m3: compare waiting urgency with the active render batch (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [f9ede2dda5dd](https://github.com/aurora-silicon/linux/commit/f9ede2dda5dd7a0fa8ce0e1143724e7be01b31a0), [7c912ffa2635](https://github.com/aurora-silicon/linux/commit/7c912ffa2635dd08673a43c09442a143a39b624d) | Chris Kearney | drm/asahi: retain full firmware-log payloads and use ring entry sizes (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_init_layout.rs` | V | +| [02e89516c254](https://github.com/aurora-silicon/linux/commit/02e89516c254d55b4ae686ccae460af8ddd30901), [e2bbe50fc3fd](https://github.com/aurora-silicon/linux/commit/e2bbe50fc3fd42e5dfbd74e5aff9181af86bb096) | Chris Kearney | drm/asahi: keep firmware-log payload clear of render counter pages (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_init_storage.rs` | V | +| [eb1dec967643](https://github.com/aurora-silicon/linux/commit/eb1dec967643c6b4fbbd528812befd07434951d8), [11896702843e](https://github.com/aurora-silicon/linux/commit/11896702843eb979e00beb871090800d9c53a4e0) | Chris Kearney | drm/asahi: coalesce eligible T6030 render completion stamp flushes (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [407c4ba9d5c8](https://github.com/aurora-silicon/linux/commit/407c4ba9d5c8f14292a7ba75afdc929475a0e40c), [7feec149fdc7](https://github.com/aurora-silicon/linux/commit/7feec149fdc7795ff82698683c86dc4b3a2a486b) | Chris Kearney | drm/asahi: order opt-in T6030 mixed batches with firmware barriers (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [d8c059ddae69](https://github.com/aurora-silicon/linux/commit/d8c059ddae697f618e0b70b4b2ddee103ba9a1eb), [0377e6c2e9ad](https://github.com/aurora-silicon/linux/commit/0377e6c2e9ad549130015a52de84c0c24e47a343) | Chris Kearney | drm/asahi: make T6030 internal render measurements optional (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_batch_policy.rs` | V | +| [ad54cf1de60e](https://github.com/aurora-silicon/linux/commit/ad54cf1de60ea638b0ab96e80d9bea7d7fc726ea), [cd13ef870e00](https://github.com/aurora-silicon/linux/commit/cd13ef870e00dd86aed1b582b393c6a697bec326) | Chris Kearney | drm/asahi: use stage-specific render sequence length fields (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_command_layout.rs` | V | +| [16fef343da41](https://github.com/aurora-silicon/linux/commit/16fef343da414828fe4098a566951945838bb370), [689764765782](https://github.com/aurora-silicon/linux/commit/68976476578273e77d0d5f05f77f437f610dbee8) | Chris Kearney | drm/asahi: add bounded T6030 active power-state sampling (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_active_power.rs` | V | +| [019434491afe](https://github.com/aurora-silicon/linux/commit/019434491afe2cd57bc001515778d9412c9b0747), [cd09499b2a78](https://github.com/aurora-silicon/linux/commit/cd09499b2a78ce5e0ca1b628af94403c56467256) | Chris Kearney | drm/asahi: add opt-in T6030 consistent performance requests (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_active_power.rs` | V | +| [b09e6d9fca33](https://github.com/aurora-silicon/linux/commit/b09e6d9fca33c554a597774f1a6cd4c4ca54622b), [263fd8538d83](https://github.com/aurora-silicon/linux/commit/263fd8538d83cca3809589da9b78cb1d982c0a21) | Chris Kearney | drm/asahi: refresh an opt-in T6030 busy performance request (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_consistent_perf.rs` | V | +| [9bc784357fe8](https://github.com/aurora-silicon/linux/commit/9bc784357fe8affe0875fb911be88ac953ca30db), [db732ce1f244](https://github.com/aurora-silicon/linux/commit/db732ce1f24488887872abc3b61b45de95285252) | Chris Kearney | drm/asahi: add an opt-in T6030 Globals debug-word clear (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_globals_debug.rs` | V | +| [02bde1b70c31](https://github.com/aurora-silicon/linux/commit/02bde1b70c312f6d4d964c7a2b1737be0de5a909), [651fbc49524d](https://github.com/aurora-silicon/linux/commit/651fbc49524d677ff86766dd2d85bb1bbf6ab3eb) | Chris Kearney | drm/asahi: m3: groundwork for more than two clusters and T6031 tables (M3-only split) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_pass_layout.rs` | V | +| [0f47fbf31934](https://github.com/aurora-silicon/linux/commit/0f47fbf31934d0b1684b5412e235315304687d4a), [783f9478d373](https://github.com/aurora-silicon/linux/commit/783f9478d3737cfae945aad01e02e9dea30c2249) | Chris Kearney | drm/asahi: reject unsupported command fields on the M1/M2 queue path | Not applicable — LEGACY; `drivers/gpu/drm/asahi/queue/mod.rs` | V | +| [1de2dbd89acb](https://github.com/aurora-silicon/linux/commit/1de2dbd89acb80f37aec08fa897374dfebe56b10), [294e9f9ebfb4](https://github.com/aurora-silicon/linux/commit/294e9f9ebfb42dbc77a85f635b5bfabaa9315003) | Chris Kearney | drm/asahi: excise unbound G16 and G17 runtimes (M3-only split: TA/3D command encoders) | Not applicable — G15; `drivers/gpu/drm/asahi/m3/agx_render.rs` | V | +| [fd1a2a3d4e60](https://github.com/aurora-silicon/linux/commit/fd1a2a3d4e60ce3f3965d2a47d1904ce3a6ed158), [e91bc024eef8](https://github.com/aurora-silicon/linux/commit/e91bc024eef8a6fde320f7fa062a034a2fe0bccb) | Eryk Wieliczko | drm/asahi: carry validated M3 depth compression bindings | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_fragment_command.rs` | V | +| [bc0a841680f3](https://github.com/aurora-silicon/linux/commit/bc0a841680f3b36193e83e18c339d269d142de03), [2c301fab2412](https://github.com/aurora-silicon/linux/commit/2c301fab241280c36f20c57d7d240ff0f48ad88f) | garymeyerca | drm/asahi: gate T8122 G15G identity before startup | Not applicable — G15; `drivers/gpu/drm/asahi/m3/mod.rs` | V | +| [e10c013299bf](https://github.com/aurora-silicon/linux/commit/e10c013299bf639ca0c8f08574150970e381f203), [be90c110d9df](https://github.com/aurora-silicon/linux/commit/be90c110d9dfcae3918efad4ba9a3d5015bb5bf4) | Chris Kearney | drm/asahi: select M3 runtime configuration per SoC | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_soc.rs` | V | +| [ed90e7bfde51](https://github.com/aurora-silicon/linux/commit/ed90e7bfde5160c20b1e4165f577e874e60fc102), [a6b472702f24](https://github.com/aurora-silicon/linux/commit/a6b472702f24666ee52febc1c2eb93bf21c8d44a) | Satya Benson | drm/asahi: m3: T8122: the G15G compute register list | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_compute_layout.rs` | V | +| [278d0abfc612](https://github.com/aurora-silicon/linux/commit/278d0abfc6128a83aa00d9181bdf2c716b608c89), [7949c0d6139c](https://github.com/aurora-silicon/linux/commit/7949c0d6139c53f8e016cd773744386407a8c9cf) | Satya Benson | drm/asahi: m3: T8122: the G15G TA and 3D register lists | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_fragment_command.rs` | V | +| [7231c0beb83e](https://github.com/aurora-silicon/linux/commit/7231c0beb83ec1976716d28456e09df3e159f2e5), [bfd3e0bf0bbc](https://github.com/aurora-silicon/linux/commit/bfd3e0bf0bbc64ee99213b4b5b3824b588411bba) | Satya Benson | drm/asahi: m3: T8122: the runtime HwDataB words | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_soc.rs` | V | +| [6ad35705d81d](https://github.com/aurora-silicon/linux/commit/6ad35705d81dd1e65f8ef9e1f4136a0080ead760), [f6e58511c69a](https://github.com/aurora-silicon/linux/commit/f6e58511c69ae5cdef73b3ba0d87de8de9a74e01) | Chris Kearney | drm/asahi: m3: T8122: the remaining runtime InitData words | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_soc.rs` | V | +| [2a8cdd0ce1d4](https://github.com/aurora-silicon/linux/commit/2a8cdd0ce1d4a8ceef673922b9517e4780996a0c), [d109a5c9e597](https://github.com/aurora-silicon/linux/commit/d109a5c9e59768669f2d1750ab1abd4560a42e28) | Chris Kearney | drm/asahi: coalesce eligible T6030 render completion stamp flushes | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_fragment_command.rs` | V | +| [bb1f75a18b32](https://github.com/aurora-silicon/linux/commit/bb1f75a18b3265ab5e4a06e424df43ed0371c002), [d4dba3df31cf](https://github.com/aurora-silicon/linux/commit/d4dba3df31cff8f96ddeafad6a4b2709fe942d62) | Chris Kearney | drm/asahi: use stage-specific render sequence length fields | Not applicable — G15; `drivers/gpu/drm/asahi/m3/m3_fragment_command.rs` | V | +| [1e8f813be7c6](https://github.com/aurora-silicon/linux/commit/1e8f813be7c681b558ffcd501ea278d6848099f4), [3d7945cbbd0f](https://github.com/aurora-silicon/linux/commit/3d7945cbbd0f9dd9d7806c402e70a53815c30738) | Satya Benson | drm/asahi: m3: T8122: write the HwDataA +0x418c block on 14.8.3 | Not applicable — G15; `drivers/gpu/drm/asahi/m3/mod.rs` | V | +| [8730d3dce2b7](https://github.com/aurora-silicon/linux/commit/8730d3dce2b7e8d9f05ad437c4cf89169e0c5b78), [453d66a437b0](https://github.com/aurora-silicon/linux/commit/453d66a437b02027ad0b6fb194242dd29bb0d31d) | Chris Kearney | pmdomain: apple: pmp-report: add the T6031 report | Not applicable — G15; `drivers/pmdomain/apple/pmp-report-test.c` | V | +| [a66386d03830](https://github.com/aurora-silicon/linux/commit/a66386d03830555c976850001c12644921b0a028), [a8e5d3a57a0e](https://github.com/aurora-silicon/linux/commit/a8e5d3a57a0e19360cffa4dc04ae500ba832e0ea) | Marius Gehrmann | pmdomain: apple: pmp-report: Request T8122 devices by soc-device record | Not applicable — G15; `drivers/pmdomain/apple/pmp-report-test.c` | V | +| [3b9bb4563bb8](https://github.com/aurora-silicon/linux/commit/3b9bb4563bb8b9f16761fe5842ff3bb6a09d8661), [c7bbd7faf778](https://github.com/aurora-silicon/linux/commit/c7bbd7faf778be8ab87515e81caf06571e527ad8) | Chris Kearney | pmdomain: apple: pmp-report: start a handed-over T8122 or T6031 PMP | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [3157c614a661](https://github.com/aurora-silicon/linux/commit/3157c614a66119b47100a6a8ac1ad9232bacbf81), [2b115cc04222](https://github.com/aurora-silicon/linux/commit/2b115cc04222553f4bcde86a70f5a2f9c8f5d086) | Ryan Murray | pmdomain: apple: pmp-report: document a failed PMP start and its recovery | Not applicable — VERIFY; `drivers/pmdomain/apple/Kconfig` | V | +| [c332c6fbd56a](https://github.com/aurora-silicon/linux/commit/c332c6fbd56adddb1f6d5635b4010a940c780e7e), [54b90aaf82e9](https://github.com/aurora-silicon/linux/commit/54b90aaf82e9aa7241747d747a5e79c46841a2dd) | Ryan Murray | pmdomain: apple: pmp-report: add KUnit fault injection for the PMP start | Not applicable — VERIFY; `drivers/pmdomain/apple/.kunitconfig.apple-pmp-start` | V | +| [7d101f700326](https://github.com/aurora-silicon/linux/commit/7d101f7003268e2e459ed86b07c9bc3ec5a000f2), [ac234af7c851](https://github.com/aurora-silicon/linux/commit/ac234af7c851e08e6da06a4dffeb912786611ed8) | Chris Kearney | drm/asahi: uapi: add the M3 (G15S) runtime numbers | Not applicable — G15 | V | +| [70f4bebd69b0](https://github.com/aurora-silicon/linux/commit/70f4bebd69b070d37022bca55228879bb7ec4e32), [577249de3221](https://github.com/aurora-silicon/linux/commit/577249de322139ceb84aea9daffcf54d437da52b) | Eryk Wieliczko | drm/asahi: uapi: add the J613 25G83 runtime numbers | Not applicable — G15 | V | +| [f3de3ac043ac](https://github.com/aurora-silicon/linux/commit/f3de3ac043acd22bf4ded712f2999b5693d824ae), [744d0129b6c0](https://github.com/aurora-silicon/linux/commit/744d0129b6c008708c2d9d019ca0a95ee1c04874) | Ryan Murray | drm/asahi: reject timestamp copies on M1 and M2 | Not applicable — LEGACY; `drivers/gpu/drm/asahi/queue/mod.rs` | V | +| [d2cdf2ff6b35](https://github.com/aurora-silicon/linux/commit/d2cdf2ff6b353a72f5f33239a4a7f2b7d87e8cf4), [1e52a05edada](https://github.com/aurora-silicon/linux/commit/1e52a05edadad288cfc17120f37c721f506abcd5) | Chris Kearney | gpu: preserve compute overrides and limit automatic preemption to M2 Max | Not applicable — LEGACY; `drivers/gpu/drm/asahi/asahi_core.rs` | V | +| [a22a6bfc2fa0](https://github.com/aurora-silicon/linux/commit/a22a6bfc2fa049f249effd677a512d682870b171) | Ryan Murray | rust: drm: document Aurora ownership and simplify pointer conversions | Not applicable — VERIFY; `rust/kernel/drm/gpuvm/mod.rs` | V | +| [e8f94ff7af1c](https://github.com/aurora-silicon/linux/commit/e8f94ff7af1cd4104091ac851109fdae946aa585) | Ryan Murray | arm64: dts: apple: j700: Use the raw xHCI SuperSpeed port number | Not applicable — PERIPH | V | +| [58d75c388d55](https://github.com/aurora-silicon/linux/commit/58d75c388d55030f432e93f42ff8871ffe9390f3) | Chris Kearney | soc: apple: opt native T6030 GPUs into the admitted PMP supplier [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [19538d922267](https://github.com/aurora-silicon/linux/commit/19538d9222679bc5ff3f17da845a10f60591e82c) | Chris Kearney | pmdomain: apple: read readiness from the passive T6030 report [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [1633fb96515e](https://github.com/aurora-silicon/linux/commit/1633fb96515e3b733a0e3514e17f841ef344ab4c) | Eryk Wieliczko | drm/asahi: g15: Add the UAPI glue and register the G15 generation [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [4d6d40767ae2](https://github.com/aurora-silicon/linux/commit/4d6d40767ae2a1cdbb697d3471dbb2c4a7ef9f9d) | Naeem Malik | drm/asahi: g15: Add the T6031 (M3 Max, G15C) configuration row [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [99a4031d5828](https://github.com/aurora-silicon/linux/commit/99a4031d58282e9d2d0f9211b3ba50547d4fc643) | Naeem Malik | drm/asahi: g15: Pin the J516C GPU firmware build [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/hw/t6031.rs` | V | +| [4abf7048277d](https://github.com/aurora-silicon/linux/commit/4abf7048277dd6ec8364811002adeafe326046d6) | Naeem Malik | drm/asahi: g15: Build the J516C initdata from its board images [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board.rs` | V | +| [5fc412af6085](https://github.com/aurora-silicon/linux/commit/5fc412af608586b6beb6a567544f3440f42fe7eb) | Naeem Malik | drm/asahi: g15: Encode compute commands with the G15C register values [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/hw/t6031.rs` | V | +| [377ce9d4f32a](https://github.com/aurora-silicon/linux/commit/377ce9d4f32a42a3b87e0885f48777140c4a2488) | Naeem Malik | drm/asahi: g15: Admit and map UAT regions that are reserved System RAM [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [3a5c855a7219](https://github.com/aurora-silicon/linux/commit/3a5c855a7219aaf45d18a3449bfd4a263ed781c2) | Naeem Malik | drm/asahi: g15: Accept the J516C 14.8 GPU firmware build [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/hw/t6031.rs` | V | +| [f0d7c15b4769](https://github.com/aurora-silicon/linux/commit/f0d7c15b47696e3bbf168f1c8bce9c4817ea28da) | DjDeveloperr | drm/asahi: g15: Reject a scissor base that overflows the address space [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/command.rs` | V | +| [2510cc337f15](https://github.com/aurora-silicon/linux/commit/2510cc337f1551b53e9de38b5c69d6d365e373f3) | DjDeveloperr | drm/asahi: g15: Do not fail a published prefix on a group allocation failure [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/engine.rs` | V | +| [5a592f22b23a](https://github.com/aurora-silicon/linux/commit/5a592f22b23a83ec106b50d5f9ec88f9d885e980) | DjDeveloperr | drm/asahi: g15: Release a closed VM's state when its handle goes [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/mod.rs` | V | +| [e2b962c42e40](https://github.com/aurora-silicon/linux/commit/e2b962c42e40c5ee92b81495436c11b5832d056c) | DjDeveloperr | drm/asahi: g15: Release the power hold and the PMP vote when a start fails early [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/boot.rs` | V | +| [c4d01d363c1d](https://github.com/aurora-silicon/linux/commit/c4d01d363c1d25c33fee168114f369288e24e55d) | DjDeveloperr | drm/asahi: g15: Only mark used pass slots dirty on release [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/slots.rs` | V | +| [35bbcb1bca7a](https://github.com/aurora-silicon/linux/commit/35bbcb1bca7a9dac71f9785aed900d4cf6665d9f) | DjDeveloperr | tools/asahi: Drop the removed T6031 knobs from the M3 runtime tests [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/glue.c` | V | +| [f6da2c8fd38a](https://github.com/aurora-silicon/linux/commit/f6da2c8fd38a75723a7569ff6046d08572900559) | Chris Kearney | drm/asahi-m3: adapt the native G15 driver to the 7.2 driver core and DRM device API [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [42f91c2f9eae](https://github.com/aurora-silicon/linux/commit/42f91c2f9eae615bc12335e37f2016ec09398f63) | Naeem Malik | drm/asahi: g15: Say what to report when the GPU firmware build is unknown [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/identity.rs` | V | +| [f3b867b04c1a](https://github.com/aurora-silicon/linux/commit/f3b867b04c1a50f46d034844754e39f8031ffae3) | Naeem Malik | drm/asahi: g15: Move the board template fill into a core-only submodule [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board.rs` | V | +| [7375309d764f](https://github.com/aurora-silicon/linux/commit/7375309d764f899d8eb4328e30447ec2959efa02) | Naeem Malik | drm/asahi: g15: t6031: Build Globals and Power from the device tree [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board.rs` | V | +| [b840b9944ad9](https://github.com/aurora-silicon/linux/commit/b840b9944ad9e171cf5b13b4e0e0fe8d018778c9) | Naeem Malik | drm/asahi: g15: board: Review fixes for the device-tree Globals and Power [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board/words.rs` | V | +| [3f02f6c358ab](https://github.com/aurora-silicon/linux/commit/3f02f6c358abd1630f1342cbd4dbca755c78023c) | Naeem Malik | drm/asahi: g15: t6031: Build the hardware record from the device tree [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [f1f5a81b756f](https://github.com/aurora-silicon/linux/commit/f1f5a81b756f99b30baefcd967a924167b2a0c12) | Naeem Malik | drm/asahi: t6031: take the idle standby time from the GPU node [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/hw.rs` | V | +| [de646267a725](https://github.com/aurora-silicon/linux/commit/de646267a725f94ea67864c2695eb1082f60dc33) | Chris Kearney | drm/asahi: g15: t6031: refuse board-data values that do not fit in u32 [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [a9a0e4be0546](https://github.com/aurora-silicon/linux/commit/a9a0e4be0546ee7876924cee8e3478727f89cb41) | Chris Kearney | selftests: asahi-g15: run the T6031 board-data and firmware-lookup tests [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board/record.rs` | V | +| [0b06992d812c](https://github.com/aurora-silicon/linux/commit/0b06992d812ce75667740514a5a472fc8d0e091b) | Chris Kearney | drm/asahi: g15: t6031: test that no GPU node value panics the record builder [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board/record.rs` | V | +| [68b780fc7ced](https://github.com/aurora-silicon/linux/commit/68b780fc7ced9e39a5d4573993564ebda26f3649) | Naeem Malik | drm/asahi: g15: Read back the GPU's performance state in debugfs [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board.rs` | V | +| [cd970562fa97](https://github.com/aurora-silicon/linux/commit/cd970562fa979059c6dea0e7f0a7f9df63c3b1c7) | Naeem Malik | drm/asahi: g15: t6031: Leave the 42 record words the firmware does not need zero [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/board/record.rs` | V | +| [2a0319927108](https://github.com/aurora-silicon/linux/commit/2a0319927108ee823a82bb1884d2cc6512b515bb) | Chris Kearney | drm/asahi: g15: m3: complete the stats record loads before consuming them [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/channel.rs` | V | +| [73047c7ee59e](https://github.com/aurora-silicon/linux/commit/73047c7ee59e45b8d16e8ec308fb15683caefc6e) | Naeem Malik | drm/asahi: g15: Seed the compute UMA freelist with every pool [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/fw/graph.rs` | V | +| [345f1d8467a4](https://github.com/aurora-silicon/linux/commit/345f1d8467a46f679eb2f2fc2852adb844911033) | Naeem Malik | drm/asahi: g15: Leave PB growth requests unanswered after two refusals at the maximum [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/params.rs` | V | +| [bd0201fad2e0](https://github.com/aurora-silicon/linux/commit/bd0201fad2e02429de6e265c51a223e4209920fe) | Naeem Malik | drm/asahi: g15: Keep PB blocks above the firmware's growth maximum for partial renders [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/params.rs` | V | +| [ff0d1cd692b8](https://github.com/aurora-silicon/linux/commit/ff0d1cd692b838934972a646fa7d1411c54b4864) | Naeem Malik | drm/asahi: g15: Map a 512 MiB parameter buffer heap [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/fw/buffer.rs` | V | +| [e1cdc12eeb97](https://github.com/aurora-silicon/linux/commit/e1cdc12eeb97fc9f4da88343054e6a964e8d2e40) | Chris Kearney | drm/asahi: g15: m3: keep every PB ledger below the block list's K [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/pools.rs` | V | +| [b1adafb49e1c](https://github.com/aurora-silicon/linux/commit/b1adafb49e1c49b71221f7c069dde6045ccce32c) | Chris Kearney | drm/asahi: g15: m3: say what a PB refusal does and size the heap right [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/fw/buffer.rs` | V | +| [3ea5cb6af82b](https://github.com/aurora-silicon/linux/commit/3ea5cb6af82bda7fca665b0902b22275ae02d996) | Chris Kearney | drm/asahi: g15: m3: check firmware-object backing geometry with checked arithmetic [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/object.rs` | V | +| [1eced02cd6c3](https://github.com/aurora-silicon/linux/commit/1eced02cd6c3efe9c43cfc9cf0993ee427e849b9) | Chris Kearney | drm/asahi: g15: m3: map the PB backing into each VM when it first runs [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/engine.rs` | V | +| [73b9ade12e01](https://github.com/aurora-silicon/linux/commit/73b9ade12e0123ef2168abd7eb4969d6865622a3) | Chris Kearney | drm/asahi: g15: m3: back the PB on demand, before an answer posts it [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/pools.rs` | V | +| [716d2fbe9254](https://github.com/aurora-silicon/linux/commit/716d2fbe9254f5ba13a8bd6a6ac687f5904a2f8a) | Chris Kearney | drm/asahi: adapt retained-G15 hooks to the maintained shared APIs [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/file.rs` | V | +| [a4000925920a](https://github.com/aurora-silicon/linux/commit/a4000925920a0e338e0bfa2b726f3b4de90b8cd7) | Chris Kearney | drm/asahi: port the native G15 adapter onto maintained DRM registration [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/Kconfig` | V | +| [23ae3eaf8377](https://github.com/aurora-silicon/linux/commit/23ae3eaf8377dbbd5c51541d77fb4a542fdb384b) | Chris Kearney | drm/asahi: g15: allocate driver data before touching firmware [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/mod.rs` | V | +| [a8289266db94](https://github.com/aurora-silicon/linux/commit/a8289266db94cf7069ab26420bd9d6b60a92dce6) | Chris Kearney | drm/asahi: g15: defer firmware start until module registration completes [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/asahi_core.rs` | V | +| [bc962412f16e](https://github.com/aurora-silicon/linux/commit/bc962412f16e1ca6d93ababf971c68011294c5e3) | Chris Kearney | drm/asahi: g15: make maintained loader contracts independent of SF profiles [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/adt.rs` | V | +| [6c6f0e62156c](https://github.com/aurora-silicon/linux/commit/6c6f0e62156cd9e35f5db47af226dd9821a26b0f) | Chris Kearney | drm/asahi: use the native private wire in G15 command consumers [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/boot.rs` | V | +| [627c501db65e](https://github.com/aurora-silicon/linux/commit/627c501db65ed9bbe22e853f37f247dc579db4af) | Chris Kearney | drm/asahi: isolate the native G15 command ABI behind a private facade [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/wire.rs` | V | +| [9d5c0ee71512](https://github.com/aurora-silicon/linux/commit/9d5c0ee715129fda8cbb8a1420315cf6a7d25cc4) | Chris Kearney | drm/asahi: g15: bound device-wide live logical queue capacity [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/mod.rs` | V | +| [4f6aba6a3598](https://github.com/aurora-silicon/linux/commit/4f6aba6a35980bf152ce5803d7bb5940a2cb11a2) | Chris Kearney | pmdomain: apple: pmgr-pwrstate: Test apple,no-auto-pm on power on and off [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — VERIFY; `drivers/pmdomain/apple/Kconfig` | V | +| [b9d3cf70f1e8](https://github.com/aurora-silicon/linux/commit/b9d3cf70f1e8b488a9ad032dadad7cbe432e164c) | Chris Kearney | drm/asahi: export a T6030 module alias for the native G15 driver [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/mod.rs` | V | +| [af2c5c09ba9f](https://github.com/aurora-silicon/linux/commit/af2c5c09ba9fae7fbf9f165fc6aad73e9aadce0b) | Chris K | pmdomain: apple: restore gated T6030 managed PMP startup [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/pmdomain/apple/pmp-report.c` | V | +| [2a182741b7f7](https://github.com/aurora-silicon/linux/commit/2a182741b7f7d80a7a743eafbf57bd9dbca3c6b4) | Chris Kearney | test: exercise the actual T6030 halted-PMP consumer graph [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/pmdomain/apple/Kconfig` | V | +| [70f41a7944ee](https://github.com/aurora-silicon/linux/commit/70f41a7944eeaf9afdd63f9246ebc5bc41df23f1) | Chris Kearney | drm/apple: admit T6030 G15 only after native display registration [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/apple/apple_drv.c` | V | +| [9632ccd283f9](https://github.com/aurora-silicon/linux/commit/9632ccd283f96ad98321238b6da4718b62f7671a) | Chris Kearney | drm/asahi: require a callable native display provider for G15 [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/Kconfig` | V | +| [e9d92ce6a5c2](https://github.com/aurora-silicon/linux/commit/e9d92ce6a5c27b36266de99d252db029476cb3f1) | Chris Kearney | drm/asahi: retain live G15 storage after allocation failures [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/boot.rs` | V | +| [fc13d05b1db9](https://github.com/aurora-silicon/linux/commit/fc13d05b1db9515fa9481d1362ff530fbeb01ca4) | DjDeveloperr | drm/asahi: g15: adapt the native G15 backend to the Linux 7.3 Rust APIs [#395](https://github.com/aurora-silicon/linux/pull/395) | Not applicable — G15; `drivers/gpu/drm/asahi/g15/boot.rs` | V | +| [0ffd22188c3d](https://github.com/aurora-silicon/linux/commit/0ffd22188c3dbb2e8bb2df216ee8e03d71e14bfb) | Ryan Murray | drm/asahi: Keep refusing HIGH queue priority on G13 and G14 [#399](https://github.com/aurora-silicon/linux/pull/399) | Not applicable — LEGACY; `drivers/gpu/drm/asahi/file.rs` | V | +| [1fac0605f061](https://github.com/aurora-silicon/linux/commit/1fac0605f061ce757917781940a5e0aec1225019) | Eryk Wieliczko | soc: apple: rtkit-helper: preserve the inherited J713 input session | Not applicable — G16; `drivers/soc/apple/rtkit-helper.c` | V | +| [751b9e08ca3d](https://github.com/aurora-silicon/linux/commit/751b9e08ca3d8fa673927984d45b22697cc26edf) | Eryk Wieliczko | iommu: apple-dart: retain J713 input and radio ownership [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/iommu/apple-dart.c` | V | +| [939a993d46dd](https://github.com/aurora-silicon/linux/commit/939a993d46dd36392155cc05f692472d2d1c881e) | Eryk Wieliczko | pmdomain: apple: pmc: support T8132 floor requests [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmc-test.c` | V | +| [c26dfe75ac9b](https://github.com/aurora-silicon/linux/commit/c26dfe75ac9be0fb75dfe937a15246b4e12bc84a) | Eryk Wieliczko | soc: apple: clpc: retain qualified T8132 memory floors | Not applicable — G16; `drivers/soc/apple/clpc.c` | V | +| [15e0eb00b83b](https://github.com/aurora-silicon/linux/commit/15e0eb00b83b783d7fca9720530942020722cd6a) | Eryk Wieliczko | soc: apple: mcc: support the T8132 memory controller | Not applicable — G16; `drivers/soc/apple/mcc.c` | V | +| [df3e4970e686](https://github.com/aurora-silicon/linux/commit/df3e4970e68677d56eca3e9e9608c338abe52cdd) | Eryk Wieliczko | soc: apple: pmp: publish owner-managed T8132 telemetry [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmp-report.c` | V | +| [f1b75932e61d](https://github.com/aurora-silicon/linux/commit/f1b75932e61d7d193801a7f22ea628cb94d9dd56) | Eryk Wieliczko | cpufreq: apple: track T8132 thermal ownership per policy | Not applicable — G16; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [1e23ae3aee82](https://github.com/aurora-silicon/linux/commit/1e23ae3aee824c65cbb6d3c94780e44a2e3fc6c2) | Eryk Wieliczko | thermal: apple: add the J713 PMP CPU policy [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/thermal/Kconfig` | V | +| [332eea1e9005](https://github.com/aurora-silicon/linux/commit/332eea1e9005b0cf731a16695c9c87c34e8f13aa) | Eryk Wieliczko | arm64: apple: activate J713 with its PMGR ownership policy [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmgr-pwrstate.c` | V | +| [3949e3640d94](https://github.com/aurora-silicon/linux/commit/3949e3640d94d2b5434c84e9e1f6d7b81f650b70) | Eryk Wieliczko | arm64: apple: enable qualified J713 SuperSpeed host support [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/phy/apple/atc.c` | V | +| [0877cd766126](https://github.com/aurora-silicon/linux/commit/0877cd766126503ad02cc11565b85be37517a62c) | Eryk Wieliczko | arm64: apple: enable J713 Bluetooth with scoped radio DMA [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/bluetooth/hci_bcm4377.c` | V | +| [1ae4d49f3592](https://github.com/aurora-silicon/linux/commit/1ae4d49f35921f356d831da974c43dd5fb67f5d7) | Eryk Wieliczko | soc: apple: expose retained PMP display and GPU ownership [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmp-report.c` | V | +| [a5f2ee806da2](https://github.com/aurora-silicon/linux/commit/a5f2ee806da2931b10d69044e4a40609d4924201) | Eryk Wieliczko | thermal: apple: integrate J713 measured-rail GPU budgets [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/thermal/Kconfig` | V | +| [52d846f6b8d3](https://github.com/aurora-silicon/linux/commit/52d846f6b8d3664ea6135a660c0b3ece39937209) | Eryk Wieliczko | iommu/apple-dart: adopt J713 display streams with retained ownership [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/iommu/Kconfig` | V | +| [24f67337c9d5](https://github.com/aurora-silicon/linux/commit/24f67337c9d586100ca4a4f438e83310644c1903) | Eryk Wieliczko | drm/asahi: add the T8132 G16 firmware backend [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/gpu/drm/asahi/Kconfig` | V | +| [fb96882f6cb4](https://github.com/aurora-silicon/linux/commit/fb96882f6cb4ce464fcf3f8b3f27f0c3eba891ed) | Ryan Murray | soc: apple: rtkit-helper: Keep serving buffer requests on T8140 adopted sessions | Not applicable — PERIPH; `drivers/soc/apple/rtkit-helper.c` | V | +| [4d88194af46e](https://github.com/aurora-silicon/linux/commit/4d88194af46e4eec12e57c3e7ff8bbcf7ac5b856) | Ryan Murray | pmdomain: apple: pmgr-pwrstate: Honour apple,inherited-on only on T8132 [#400](https://github.com/aurora-silicon/linux/pull/400) | Not applicable — G16; `drivers/pmdomain/apple/pmgr-pwrstate.c` | V | +| [d14b566cc309](https://github.com/aurora-silicon/linux/commit/d14b566cc309cf6840199c6394dc8f5f75f82f54) | Ryan Murray | drm/asahi: g17: describe the 25G83 barrier and parameter block plainly [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/g17/buffer/target.rs` | V | +| [f0f00dca4b09](https://github.com/aurora-silicon/linux/commit/f0f00dca4b09c9caf6f379928827e0d10451c66b) | Ryan Murray | drm/asahi: mem: document that page-by-page invalidation is global [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/mem.rs` | V | +| [8eacef90ff89](https://github.com/aurora-silicon/linux/commit/8eacef90ff8952b6fc75a85cb20abce488bcd4ed) | Erik Bourget | drm/asahi: g16: accept the 8-core J713 loaded 25G83 image [#442](https://github.com/aurora-silicon/linux/pull/442) | Not applicable — G16; `drivers/gpu/drm/asahi/g16/hw.rs` | V | +| [da42c7cf4fc8](https://github.com/aurora-silicon/linux/commit/da42c7cf4fc83e68261f1c238b2a3b870f94a128) | Ryan Murray | drm/asahi: test shared firmware identity normalization [#428](https://github.com/aurora-silicon/linux/pull/428) | Not applicable — VERIFY; `drivers/gpu/drm/asahi/fwid.rs` | V | +| [b11a5e9e8cd8](https://github.com/aurora-silicon/linux/commit/b11a5e9e8cd83d4ac3ac40bc620de254b095c75d) | DjDeveloperr | cpufreq: apple: verify T8140 transitions on the next request [#443](https://github.com/aurora-silicon/linux/pull/443) | Not applicable — LAZY; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I | +| [e2a2d8ecbb24](https://github.com/aurora-silicon/linux/commit/e2a2d8ecbb24146ff61e1649ff503f234e846b30) | DjDeveloperr | cpufreq: apple: verify T8140 fast-switch requests from a timer [#443](https://github.com/aurora-silicon/linux/pull/443) | Not applicable — LAZY; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I | +| [b7ea3ca9de07](https://github.com/aurora-silicon/linux/commit/b7ea3ca9de073d66f0e031013b7853c3e5702479) | DjDeveloperr | cpufreq: apple: settle T8140 lazy requests before suspend [#443](https://github.com/aurora-silicon/linux/pull/443) | Not applicable — LAZY; `drivers/cpufreq/apple-soc-cpufreq.c` | C,I | +| [6f5d208b4ce0](https://github.com/aurora-silicon/linux/commit/6f5d208b4ce01ff8cfea04fa21f14ac9a7cb610c) | DjDeveloperr | cpufreq: apple: apply a lowered T8140 fast die limit at once [#443](https://github.com/aurora-silicon/linux/pull/443) | Not applicable — LAZY; `drivers/cpufreq/apple-soc-cpufreq.c` | P | +| [b884471f5b13](https://github.com/aurora-silicon/linux/commit/b884471f5b13e62de9105bb0f06362fe01663a75) | Ryan Murray | thermal: apple-smc-cpu-thermal: walk the OPP table by die temperature | Not applicable — OLDTHERMAL; `drivers/thermal/apple-smc-cpu-thermal.c` | P | +| [ca1dab3dde89](https://github.com/aurora-silicon/linux/commit/ca1dab3dde89c57cc0b111fd096478241e6565e5) | Ryan Murray | cpufreq: apple-soc: Drop the SMC CPU thermal policy hooks | Not applicable — OLDTHERMAL; `drivers/cpufreq/apple-soc-cpufreq.c` | P | +| [52006c8e0bc5](https://github.com/aurora-silicon/linux/commit/52006c8e0bc5750331643c2f36ebad06ef300079) | Ryan Murray | thermal: Remove the Apple SMC CPU thermal policy [#43](https://github.com/aurora-silicon/linux/pull/43) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — OLDTHERMAL; `drivers/thermal/Kconfig` | P | +| [1ce75ee9e797](https://github.com/aurora-silicon/linux/commit/1ce75ee9e797c296f742abf955764fa00b77e03d) | Ryan Murray | treewide: apple: t8140: Describe observed behaviour, not other implementations | Not applicable — VERIFY; `drivers/media/platform/apple/isp/isp-cam.c` | V | +| [3b0af1da96c2](https://github.com/aurora-silicon/linux/commit/3b0af1da96c28777073ebf99d65f1498a5f9fe94) | Ace | PCI: apple: enable bounded J700 endpoint bootstrap and enumeration | Not applicable — PERIPH; `drivers/pci/controller/Kconfig` | V | +| [e179042ff067](https://github.com/aurora-silicon/linux/commit/e179042ff0673c4b5adc749cb0fc3fcb0f909106) | Acelogic | HID: dockchannel: support firmware-managed M5 trackpad power | Not applicable — OTHERCHIP; `drivers/hid/dockchannel-hid/dockchannel-hid.c` | V | +| [f19bcf1a1e37](https://github.com/aurora-silicon/linux/commit/f19bcf1a1e373fb5bae2c9a5a0153d7820b13645) | Joshua Warren | drm/asahi: stats: Add StatsSnapshot and firmware-stats decoder [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/stats.rs` | T | +| [72d21d654534](https://github.com/aurora-silicon/linux/commit/72d21d654534decaecc58af0f8fc813064e6f500) | Joshua Warren | drm/asahi: Wire StatsSnapshot into StatsChannel and GpuManager [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [e8c789f9f77b](https://github.com/aurora-silicon/linux/commit/e8c789f9f77baf73f21857ca29a527930ed12aab) | Joshua Warren | drm/asahi: Add sysfs shim for agx_stats [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/Makefile` | T | +| [1b02b0b40af3](https://github.com/aurora-silicon/linux/commit/1b02b0b40af3822e16b5842e21964be40a42025f) | Joshua Warren | drm/asahi: Register agx_stats sysfs file on probe and unregister on drop [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/driver.rs` | T | +| [742f54c4855d](https://github.com/aurora-silicon/linux/commit/742f54c4855d0e3b4db54da4663d421f63d1165a) | Joshua Warren | drm/asahi: count completed submissions in agx_stats jobs [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/gpu.rs` | T | +| [bbfb96f309c8](https://github.com/aurora-silicon/linux/commit/bbfb96f309c8dd264540281469bba6c4a48ee789) | Joshua Warren | drm/asahi: link the sysfs shim into the driver module correctly [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/Makefile` | T | +| [01d19b8a4f55](https://github.com/aurora-silicon/linux/commit/01d19b8a4f55bb202b5c9d85a36996a90537602c) | Joshua Warren | drm/asahi: propagate the stats snapshot allocation result [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/gpu.rs` | T | +| [91c24dc11b09](https://github.com/aurora-silicon/linux/commit/91c24dc11b093be4868234ac2e00eed670d1e741) | Joshua Warren | drm/asahi: decode the stats messages where StatsMsg is nameable [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [dbc8b48aeb06](https://github.com/aurora-silicon/linux/commit/dbc8b48aeb06f85c9e7f9b63be9db7afef074580) | Joshua Warren | drm/asahi: qualify the versioned StatsMsg variant patterns [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [4189501b643f](https://github.com/aurora-silicon/linux/commit/4189501b643f404faa0ea37fc3bb379226300aa9) | Joshua Warren | drm/asahi: fix the agx_stats readout layout and the off-arm string [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/driver.rs` | T | +| [0b923fb29c84](https://github.com/aurora-silicon/linux/commit/0b923fb29c840b9bb675ba8ad9e81c513903b516) | Joshua Warren | drm/asahi: integrate utilization-weighted time into agx_stats busy_ns [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [9250872b1f33](https://github.com/aurora-silicon/linux/commit/9250872b1f33f357c01ccd3bf79be7835fc5c982) | Joshua Warren | drm/asahi: convert stats timestamps from base-clock ticks to ns [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [993288b37ec4](https://github.com/aurora-silicon/linux/commit/993288b37ec4368bbf2debbbd73124d8a2652410) | Joshua Warren | drm/asahi: split the tick window to avoid 128-bit division [#197](https://github.com/aurora-silicon/linux/pull/197) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — STAT; `drivers/gpu/drm/asahi/channel.rs` | T | +| [9aebc2952fb5](https://github.com/aurora-silicon/linux/commit/9aebc2952fb51f6b0b9c03222109fe7f0ac107c2) | Ryan Murray | arm64: dts: apple: retain the T8140 P-cluster domain cells [#179](https://github.com/aurora-silicon/linux/pull/179) | Not applicable — BOARD | B | +| [e3163ca622d6](https://github.com/aurora-silicon/linux/commit/e3163ca622d6c55979d963929edbdf47f2c4c38c) | Ryan Murray | soc: apple: expose the T8140 PMP DVFS report for diagnosis [#182](https://github.com/aurora-silicon/linux/pull/182) [#380](https://github.com/aurora-silicon/linux/pull/380) [#470](https://github.com/aurora-silicon/linux/pull/470) | Not applicable — RAWDIAG; `drivers/pmdomain/apple/pmp-report.c` | O | +| [14e101ee2b8b](https://github.com/aurora-silicon/linux/commit/14e101ee2b8b599cf627015368e16ff8b3d2b95f) | Ryan Murray | cpufreq: apple: retain the unmanaged T8152 P-state ceiling | Not applicable — OTHERCHIP; `drivers/cpufreq/apple-soc-cpufreq.c` | V | +| [19d6664ea33a](https://github.com/aurora-silicon/linux/commit/19d6664ea33affcceef42d5deef488ce1ba7d50d) | Ryan Murray | arm64: dts: apple: retain a single J700 PMP alias | Not applicable — BOARD | B | +| [98038492dc48](https://github.com/aurora-silicon/linux/commit/98038492dc481d784c76fb2c52f3dd030dfad8f5) | Ryan Murray | drm: apple: recognize the T8140 external binding in the display master | Not applicable — BOARD; `drivers/gpu/drm/apple/apple_drv.c` | B | +| [78fc11047efa](https://github.com/aurora-silicon/linux/commit/78fc11047efa814a238113c46b4bb07aa7d641fb) | Ryan Murray | arm64: dts: apple: give the external J700 trial its firmware DMA window | Not applicable — PERIPH | V | +| [bc3b8aaa560f](https://github.com/aurora-silicon/linux/commit/bc3b8aaa560f23e069a7ea2d774645ea5fec39cc) | Ryan Murray | arm64: dts: apple: select the second external DCP source on J700 | Not applicable — PERIPH | V | +| [d08fd52e1619](https://github.com/aurora-silicon/linux/commit/d08fd52e161953a481a464716dd68aae85338044) | DjDeveloperr | drm/apple: enable the validated J700 external display profile by default | Not applicable — BOARD; `drivers/gpu/drm/apple/dcp-external-bw.h` | B | +| [01c8fe5e98f4](https://github.com/aurora-silicon/linux/commit/01c8fe5e98f4ab8805957ca3206d6d46a65b62e7) | Ryan Murray | soc: apple: aop: Do not overrun unread entries in the transmit ring [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/aop.rs` | V | +| [02694d65655b](https://github.com/aurora-silicon/linux/commit/02694d65655baae0536d47beef4f0356145069f4) | Ryan Murray | spi: apple-qspi: Resume the controller even if its reset fails [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/spi/spi-apple-qspi.c` | V | +| [033fa5e3c095](https://github.com/aurora-silicon/linux/commit/033fa5e3c0959f772e8464a25b8b93773f39b5b9) | Ryan Murray | soc: apple: aop: Accept a transmit entry at a write pointer of zero [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/aop_ring.rs` | V | +| [0790f8a546fe](https://github.com/aurora-silicon/linux/commit/0790f8a546fea7ae46871d857643568c1b3c0c64) | Ryan Murray | hwmon: macsmc: drop the check for the undefined apple,cpu-thermal-policy [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH; `drivers/hwmon/macsmc-hwmon.c` | V | +| [0943ae25f9bd](https://github.com/aurora-silicon/linux/commit/0943ae25f9bd82917a7a4813962d624d22056fd3) | Ryan Murray | HID: dockchannel-hid: Reject unaligned packet lengths before checksumming [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hid/dockchannel-hid/dockchannel-hid-parser-test.c` | V | +| [0c55425a8157](https://github.com/aurora-silicon/linux/commit/0c55425a8157326aa0bafd20a1c30a0f34af8650) | Ryan Murray | dt-bindings: gpu: apple,agx: Require three cells in apple,firmware-version [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY | V | +| [152cc6b4d737](https://github.com/aurora-silicon/linux/commit/152cc6b4d737c3523b56d1c4b0b4cd3928e514b5) | Ryan Murray | spi: apple-qspi: Do not print a banner on probe [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — PERIPH; `drivers/spi/spi-apple-qspi.c` | V | +| [1baee970e0a9](https://github.com/aurora-silicon/linux/commit/1baee970e0a9d5bf0343f72eed61badfc5918159) | Jonathan Goyvaerts | dt-bindings: mailbox: apple,ascwrap-v6: Add the T6040 [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY | V | +| [1df5bd529669](https://github.com/aurora-silicon/linux/commit/1df5bd529669987e8804b6ab38dbd1d5175f508d) | Ryan Murray | HID: dockchannel-hid: Test MTP message bounds with KUnit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/hid/dockchannel-hid/Kconfig` | V | +| [1f7383d5a281](https://github.com/aurora-silicon/linux/commit/1f7383d5a2811b99a9f2950581ec392837bfc469) | DjDeveloperr | rust: check generated layouts and provide corrected private bindgen [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY | V | +| [232faf36664c](https://github.com/aurora-silicon/linux/commit/232faf36664c7e23aa9950bc3019dbf6ba534ee8) | Ryan Murray | HID: dockchannel-hid: Keep parser tests in the subsystem menu [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hid/dockchannel-hid/Kconfig` | V | +| [2d5c7b6bc705](https://github.com/aurora-silicon/linux/commit/2d5c7b6bc705f0ba8eef8f3d1ea23a53d4248eba) | Ryan Murray | ASoC: apple: t8140-aop-audio: Refuse card names that do not fit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH | V | +| [3006ca1a3835](https://github.com/aurora-silicon/linux/commit/3006ca1a383525a092ad1cbe9c268786292a32de) | Ryan Murray | mfd: macsmc: Test key and fixed-width readers with KUnit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/mfd/Kconfig` | V | +| [3024185f2efa](https://github.com/aurora-silicon/linux/commit/3024185f2efa34c4ea8ac9eebebf06b921795f0b), [aaea0b3a4fcf](https://github.com/aurora-silicon/linux/commit/aaea0b3a4fcf5cb371b735c6b595a2919a6a4d07) | DjDeveloperr | dt-bindings: power: add Apple PMC voter interface [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Not applicable — VERIFY | V | +| [3132675b256e](https://github.com/aurora-silicon/linux/commit/3132675b256e07ad782280c42c853c63f6d18ef1) | Ryan Murray | selftests: apple-dcp: Run the queue recovery control through the RX drain [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY | V | +| [315f6dc9944d](https://github.com/aurora-silicon/linux/commit/315f6dc9944de65990476dae34079d3e1313d605) | Chris Kearney | soc: apple: mailbox: match the T8122 GPU mailbox without the v4 fallback | Not applicable — G15; `drivers/soc/apple/mailbox.c` | V | +| [3407efe83df6](https://github.com/aurora-silicon/linux/commit/3407efe83df680534e10c0e66ffa36b75cae69ba) | Jonathan Goyvaerts | dt-bindings: soc: apple,t8140-mcc: Add the T6040 MCC [#385](https://github.com/aurora-silicon/linux/pull/385) | Not applicable — VERIFY | V | +| [35d6b26e5c9d](https://github.com/aurora-silicon/linux/commit/35d6b26e5c9d8bf22ce6284bf0594084ffd2dcdf) | Ryan Murray | soc: apple: aop: Test that a wrapped entry is refused below the read pointer [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/soc/apple/aop_ring.rs` | V | +| [4b8d29e13da6](https://github.com/aurora-silicon/linux/commit/4b8d29e13da638bf7ba7f532844c5e368505bf20) | Jonathan Goyvaerts | dt-bindings: gpu: apple,agx: allow a fw-text region on the T6040 [#428](https://github.com/aurora-silicon/linux/pull/428) | Not applicable — VERIFY | V | +| [4c5aad430f62](https://github.com/aurora-silicon/linux/commit/4c5aad430f6283d1d6b3b961a87d1629b397f22f) | DjDeveloperr | dt-bindings: mailbox: apple,mailbox: Add the T8140 GPU mailboxes [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY | V | +| [543ac70dce46](https://github.com/aurora-silicon/linux/commit/543ac70dce46b749f1a446cb80d4e63cf08949cd) | Ryan Murray | drm: apple: parser: add KUnit tests for malformed dictionaries [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/gpu/drm/apple/Makefile` | V | +| [5b62d0c81387](https://github.com/aurora-silicon/linux/commit/5b62d0c81387e8d0d68f2a88244eeb7a618f9ec1) | Ryan Murray | mfd: macsmc: Preserve the key output on lookup failure [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/mfd/macsmc.c` | V | +| [5e03e15dae17](https://github.com/aurora-silicon/linux/commit/5e03e15dae1740326b41df4777a5ee4b7502c02e), [94f215eaa642](https://github.com/aurora-silicon/linux/commit/94f215eaa6424e8f077f1447e78e35b3ebeb6542) | DjDeveloperr | dt-bindings: power: apple,t8140-pmp-report: describe report entries [#212](https://github.com/aurora-silicon/linux/pull/212) [#219](https://github.com/aurora-silicon/linux/pull/219) | Not applicable — VERIFY | V | +| [66db357bd614](https://github.com/aurora-silicon/linux/commit/66db357bd614c7f8e25dab080574c31dab6c0ed2) | Ryan Murray | tools/asahi: measure CPU cost of small render submissions [#201](https://github.com/aurora-silicon/linux/pull/201) | Not applicable — VERIFY | V | +| [6e2835ae7bd6](https://github.com/aurora-silicon/linux/commit/6e2835ae7bd6d16570464106bdfb740b6fed27c4) | Naeem Malik | soc: apple: mailbox: match the T6031 GPU mailbox without the v4 fallback | Not applicable — G15; `drivers/soc/apple/mailbox.c` | V | +| [712530b9753f](https://github.com/aurora-silicon/linux/commit/712530b9753fd8692b31382b3e9c6aaaa720ffe0) | Ryan Murray | gpio: macsmc: tidy the J700 pcIO helpers [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/gpio/gpio-macsmc.c` | V | +| [7a6e327251cc](https://github.com/aurora-silicon/linux/commit/7a6e327251cc51d0a483483f0b81dda99a811791) | Ryan Murray | soc: apple: aop: Validate the receive ring before taking entries [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/aop.rs` | V | +| [807cb8976697](https://github.com/aurora-silicon/linux/commit/807cb8976697ff272433278b272444ced7e91d57) | Ryan Murray | hwmon: macsmc: account for temperature channels in one helper [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH; `drivers/hwmon/macsmc-hwmon.c` | V | +| [83039e319faf](https://github.com/aurora-silicon/linux/commit/83039e319faf3e49ceabc424c7c69180ac997dfd) | Ryan Murray | dt-bindings: apple: use the canonical SPDX form in PMP, PMC and report bindings [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — VERIFY | V | +| [87314d48cc08](https://github.com/aurora-silicon/linux/commit/87314d48cc080bcf535291dc636f1ec6164a06f9) | Ryan Murray | drm/apple: Test firmware message bounds with KUnit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/gpu/drm/apple/Kconfig` | V | +| [910327707f85](https://github.com/aurora-silicon/linux/commit/910327707f852c060b5ada81e64a2a4563cb64b4) | Ryan Murray | ASoC: apple: Test T8140 AOP producer reports with KUnit [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY | V | +| [95b26591a1c4](https://github.com/aurora-silicon/linux/commit/95b26591a1c4b446656c3bc8a257a17792917a78) | Ryan Murray | spi: apple: Reword the hardware CS delay comment [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — VERIFY; `drivers/spi/spi-apple.c` | V | +| [966e777fd568](https://github.com/aurora-silicon/linux/commit/966e777fd568565b88c5f8ba4acb36463ae31e47) | Ryan Murray | MAINTAINERS: keep T8140 PMP coverage under Aurora [#179](https://github.com/aurora-silicon/linux/pull/179) | Not applicable — VERIFY | V | +| [a1f9aabbe6a9](https://github.com/aurora-silicon/linux/commit/a1f9aabbe6a9771a4dbf3259bc808713f0daac46) | Ryan Murray | dt-bindings: apple: describe PMP fast-die reporting on T8140 [#179](https://github.com/aurora-silicon/linux/pull/179) | Not applicable — VERIFY | V | +| [a239464d233d](https://github.com/aurora-silicon/linux/commit/a239464d233d2e94b89dea159f298b70e015b6a6) | Ryan Murray | kunit: Add an Apple firmware parser test configuration [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY | V | +| [a27007e6ace3](https://github.com/aurora-silicon/linux/commit/a27007e6ace3815d11c340c25a3ee90cfb2981b0) | Ryan Murray | HID: dockchannel: Log routine interface set-up at debug level [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hid/dockchannel-hid/dockchannel-hid.c` | V | +| [a494d618f5a9](https://github.com/aurora-silicon/linux/commit/a494d618f5a9af40ae466166176ae36476b4638a) | Ryan Murray | mfd: macsmc: Hold the mutex while decoding key metadata [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/mfd/macsmc.c` | V | +| [b8f892b79561](https://github.com/aurora-silicon/linux/commit/b8f892b79561ea085c0ab2e44368f873f15b9183) | Ryan Murray | soc: apple: aop: Decode ring entry headers without pointer casts [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/soc/apple/aop.rs` | V | +| [ba6eb20e2ed9](https://github.com/aurora-silicon/linux/commit/ba6eb20e2ed9065a2925d0f16ae030acfaeceefe) | Ryan Murray | soc: apple: mailbox: Describe the ASCWrap v6 CPU start and stop in hardware terms | Not applicable — VERIFY; `drivers/soc/apple/mailbox.c` | V | +| [bc7394673cbe](https://github.com/aurora-silicon/linux/commit/bc7394673cbed4ef22971a49926be6070a7312be) | DjDeveloperr | tools: preserve bindgen recipe validation across Python modes [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY | V | +| [bd7739d6232e](https://github.com/aurora-silicon/linux/commit/bd7739d6232e818ef7cf30acbcc50d24723e061c) | Ryan Murray | mfd: macsmc: Reject short fixed-width replies [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/mfd/macsmc.c` | V | +| [c49ab96deb3c](https://github.com/aurora-silicon/linux/commit/c49ab96deb3c378015d1db27bf2122c837fd3bc0) | DjDeveloperr | dt-bindings: gpu: apple,agx: Add apple,agx-t8140 [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — VERIFY | V | +| [c9c779d6a2ba](https://github.com/aurora-silicon/linux/commit/c9c779d6a2bae406a9d48b7f733b16fd253730c7) | Jonathan Goyvaerts | dt-bindings: soc: apple,pmp: Add the T6040 PMP [#374](https://github.com/aurora-silicon/linux/pull/374) | Not applicable — VERIFY | V | +| [cbd157ef2a12](https://github.com/aurora-silicon/linux/commit/cbd157ef2a12a46e3caf5367de912a525d8d4874) | Ryan Murray | hwmon: macsmc: keep the channel of a temperature sensor that fails to set up [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH; `drivers/hwmon/Kconfig` | V | +| [cf4ce5f41f4b](https://github.com/aurora-silicon/linux/commit/cf4ce5f41f4b86c4084b49ec6585377e16fd53b7) | Chris Kearney | thunderbolt, phy, soc: apple: T6030 USB4/Thunderbolt, Type-C and DP tunnel support | Not applicable — G15; `drivers/iommu/of_iommu.c` | V | +| [d36196335065](https://github.com/aurora-silicon/linux/commit/d36196335065346ef57a323e483002151670320c) | Ryan Murray | arm64: dts: apple: t6040-j616s: Keep the GPU disabled [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — SUPERSEDED | B | +| [d717a601430a](https://github.com/aurora-silicon/linux/commit/d717a601430aea6711ffc7486f5edb16ca7e7f66) | Jonathan Goyvaerts | dt-bindings: gpu: apple,agx: Add apple,agx-t6040 [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — VERIFY | V | +| [d90daf04de84](https://github.com/aurora-silicon/linux/commit/d90daf04de8431f70eed8b590a532f38a6249095) | Ryan Murray | media: apple: isp: Do not trust the batch count when a batch fails [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/media/platform/apple/isp/isp-v4l2.c` | V | +| [d9aface7ea85](https://github.com/aurora-silicon/linux/commit/d9aface7ea8533965e33476631c8dbc44dfeb439) | Ryan Murray | HID: dockchannel: Build the protocol KUnit test only with the driver [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hid/dockchannel-hid/Kconfig` | V | +| [e65283c42678](https://github.com/aurora-silicon/linux/commit/e65283c426783b12298f72896ef6f0dc33c4222c) | Ryan Murray | ASoC: apple: t8140-aop-audio: Let power request sequences wrap [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH | V | +| [e666e50ccfe8](https://github.com/aurora-silicon/linux/commit/e666e50ccfe8c58e8fa72bdf7ef6bedffec6551a) | Ryan Murray | rtc: macsmc: correct the comment on the whole-second offset [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — PERIPH; `drivers/rtc/rtc-macsmc.c` | V | +| [e99a6adbeba7](https://github.com/aurora-silicon/linux/commit/e99a6adbeba77b7ed032ef61f864afb3da8fe3a9) | Ryan Murray | dt-bindings: hwmon: apple,smc-hwmon: drop apple,cpu-thermal-policy [#317](https://github.com/aurora-silicon/linux/pull/317) | Not applicable — VERIFY | V | +| [ec34fb1f2835](https://github.com/aurora-silicon/linux/commit/ec34fb1f2835b291f978bd091ed73347cba7ceae) | Ryan Murray | mfd: macsmc: Reject out-of-range key indices [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/mfd/macsmc.c` | V | +| [ed0925cdf091](https://github.com/aurora-silicon/linux/commit/ed0925cdf091083ba1efee97bbb288e9c331f812) | Ryan Murray | spi: apple-qspi: Restore the controller configuration after system sleep [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/spi/spi-apple-qspi.c` | V | +| [ee6f48ce60bd](https://github.com/aurora-silicon/linux/commit/ee6f48ce60bdf68d596c4bedd31e35f55cd6bb03) | Ryan Murray | MAINTAINERS: Cover Apple firmware parser tests [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY | V | +| [f3324e0de0e2](https://github.com/aurora-silicon/linux/commit/f3324e0de0e26bd43df402001c2e3f0f16821520) | Jonathan Goyvaerts | dt-bindings: power: apple,pmc: Add the T6040 PMC and CLPC [#385](https://github.com/aurora-silicon/linux/pull/385) | Not applicable — VERIFY | V | +| [f52e01d6925d](https://github.com/aurora-silicon/linux/commit/f52e01d6925de5022176c5038ff13cc9ad17559b) | Ryan Murray | dt-bindings: gpu: apple,agx: Match the T8140 register layout [#314](https://github.com/aurora-silicon/linux/pull/314) | Not applicable — VERIFY | V | +| [fdd138bbc722](https://github.com/aurora-silicon/linux/commit/fdd138bbc7228b8b1a232268e2a02cc126b24d95) | Ryan Murray | hwmon: macsmc: move the module help text back to its option [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — PERIPH; `drivers/hwmon/Kconfig` | V | +| [fec5e40ebade](https://github.com/aurora-silicon/linux/commit/fec5e40ebade9f7cf3684acfbb9d603d5b82127a) | Jonathan Goyvaerts | dt-bindings: power: apple,t8140-pmp-report: Add the T6040 aperture [#374](https://github.com/aurora-silicon/linux/pull/374) | Not applicable — VERIFY | V | +| [ff3ebdbad58c](https://github.com/aurora-silicon/linux/commit/ff3ebdbad58c1c50dadbf9cecd7f67aada516dc1) | Ryan Murray | nvme-apple: Reword two post-M4 handoff comments [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — VERIFY; `drivers/nvme/host/apple.c` | V | + +### Merge/provenance records + +Parent mechanisms are listed above; no isolated speedup is assigned here. + +| Commits / encountered aliases | Author | Improvement / record | T6040 after ports — evidence key and changed source | Measurement | +| --- | --- | --- | --- | --- | +| [00463d1d4031](https://github.com/aurora-silicon/linux/commit/00463d1d40317155246a13573aeab60ff773b51b), [f566e6098c67](https://github.com/aurora-silicon/linux/commit/f566e6098c676566b02a191eb58e5783fa7bf0b6) | Janne Grunau | Merge branch 'bits/010-soc' into asahi-wip | Not applicable — MERGE | V | +| [80390ef034fc](https://github.com/aurora-silicon/linux/commit/80390ef034fcfaf212674bc53b0a91e596409bcf), [5e35a79dac7f](https://github.com/aurora-silicon/linux/commit/5e35a79dac7f0958af6bd3ae9ea4517d22769cb2) | Janne Grunau | Merge branch 'bits/020-dart' into asahi-wip | Not applicable — MERGE | V | +| [4eea6c6d47ba](https://github.com/aurora-silicon/linux/commit/4eea6c6d47bab3a334b9c1633279c4392e2a4de7), [57710743dc8c](https://github.com/aurora-silicon/linux/commit/57710743dc8c0e9286115f38ad19a298cd36cc50) | Janne Grunau | Merge branch 'bits/030-misc' into asahi-wip | Not applicable — MERGE | V | +| [59f7e8be4e3b](https://github.com/aurora-silicon/linux/commit/59f7e8be4e3b693a96a1dd3b2355aa65dd784b0e), [466efd223bbf](https://github.com/aurora-silicon/linux/commit/466efd223bbf43f9e0f4f7065c67d6d4e97fc04c) | Janne Grunau | Merge branch 'bits/090-spi-hid' into asahi-wip | Not applicable — MERGE | V | +| [2871ed01752e](https://github.com/aurora-silicon/linux/commit/2871ed01752ef74924608f23b5edd7927ae4b71c), [f55090a2d12a](https://github.com/aurora-silicon/linux/commit/f55090a2d12a6fd3dad485a7621392b8f508f8a8) | Janne Grunau | Merge branch 'bits/190-rust' into asahi-wip | Not applicable — MERGE | V | +| [cd36abb1e1d2](https://github.com/aurora-silicon/linux/commit/cd36abb1e1d2bcead322aedaed3cab7cbbab5d4b), [3f3dfd6ef171](https://github.com/aurora-silicon/linux/commit/3f3dfd6ef171f4c3801e1bd8b7c880774e17f1ad) | Janne Grunau | Merge branch 'bits/210-gpu' into asahi-wip | Not applicable — MERGE | V | +| [d6c7409e354f](https://github.com/aurora-silicon/linux/commit/d6c7409e354fee96c74a0aa68c5cbd0cdc0b8d92), [39b401626267](https://github.com/aurora-silicon/linux/commit/39b4016262672c0f87d7e5af835f42b9632dcc6c) | Janne Grunau | Merge branch 'bits/250-aop' into asahi-wip | Not applicable — MERGE | V | +| [072195f87df8](https://github.com/aurora-silicon/linux/commit/072195f87df8ca94580cf5ca0d9a801e5bd217e4), [11ee430b3cf2](https://github.com/aurora-silicon/linux/commit/11ee430b3cf20a6611bea0462335f0ae975a40d7) | Janne Grunau | Merge branch 'bits/500-backports' into asahi-wip | Not applicable — MERGE | V | +| [fe8324184dd8](https://github.com/aurora-silicon/linux/commit/fe8324184dd8c6d3b09a532a9cb6a533c561a324) | Ryan Murray | Merge tag 'v7.3' into unified-7.3/base | Not applicable — MERGE | V | +| [cb8e5e495b88](https://github.com/aurora-silicon/linux/commit/cb8e5e495b88f638c22782136a8721a4f26f4346) | Ryan Murray | Merge branch 'unified/bits/010-soc' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [6de20e534e47](https://github.com/aurora-silicon/linux/commit/6de20e534e473dd9efd9e14c727883df6036d60b) | Ryan Murray | Merge branch 'unified/bits/020-dart' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [15fe1cbe72df](https://github.com/aurora-silicon/linux/commit/15fe1cbe72df8d8eb79b2dec500e58bbc5f57647) | Ryan Murray | Merge branch 'unified/bits/060-power-thermal' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [939cccf33380](https://github.com/aurora-silicon/linux/commit/939cccf333802c32a319f0ddb4b0d01c85aab990) | Ryan Murray | Merge branch 'unified/bits/070-audio' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [2409e9da1efb](https://github.com/aurora-silicon/linux/commit/2409e9da1efb9494fc9437ce9f3bd909c0f7dd36) | Ryan Murray | Merge branch 'unified/bits/100-sep' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [2bc8aa67f178](https://github.com/aurora-silicon/linux/commit/2bc8aa67f17817c22c6bacaecd7ecf4df8e8c63d) | Ryan Murray | Merge branch 'unified/bits/200-dcp' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [c00586c09e2b](https://github.com/aurora-silicon/linux/commit/c00586c09e2bab26f92ddd5b943cbcc66c047ff5) | Ryan Murray | Merge branch 'unified/bits/210-gpu' into rebase-wip/7.2.10-unified | Not applicable — MERGE | V | +| [14ca49f98175](https://github.com/aurora-silicon/linux/commit/14ca49f98175b54c37fa16af766e11f163d3a80f) | Ryan Murray | Merge branch 'unified-7.3/bits/010-soc' into unified-7.3/canonical | Not applicable — MERGE | V | +| [665a5ca84508](https://github.com/aurora-silicon/linux/commit/665a5ca845081049335f69d0bf27c66c90898b7b) | Ryan Murray | Merge branch 'unified-7.3/bits/020-dart' into unified-7.3/canonical | Not applicable — MERGE | V | +| [c217f4f47bf0](https://github.com/aurora-silicon/linux/commit/c217f4f47bf003c8fb39400d1af8179b552c497e) | Ryan Murray | Merge branch 'unified-7.3/bits/060-power-thermal' into unified-7.3/canonical | Not applicable — MERGE | V | +| [5bdd6c5b2c80](https://github.com/aurora-silicon/linux/commit/5bdd6c5b2c80a6f16c8835a57a5d56d26af67839) | Ryan Murray | Merge branch 'unified-7.3/bits/070-audio' into unified-7.3/canonical | Not applicable — MERGE | V | +| [f3c8cf25dd5d](https://github.com/aurora-silicon/linux/commit/f3c8cf25dd5d01f3871267c1b77df343dbef045d) | Ryan Murray | Merge branch 'unified-7.3/bits/100-sep' into unified-7.3/canonical | Not applicable — MERGE | V | +| [c15790e20e01](https://github.com/aurora-silicon/linux/commit/c15790e20e01e8562e0b54a00bee8a8ef9ded84b) | Ryan Murray | Merge branch 'unified-7.3/bits/200-dcp' into unified-7.3/canonical | Not applicable — MERGE | V | +| [ea36d4240e40](https://github.com/aurora-silicon/linux/commit/ea36d4240e40fc7607d568a8fa56f1fa5cf01c03) | Ryan Murray | Merge branch 'unified-7.3/bits/210-gpu' into unified-7.3/canonical | Not applicable — MERGE | V | +| [94141d397eae](https://github.com/aurora-silicon/linux/commit/94141d397eaede2ba95eab7279f4bc18f75bb9d1) | Ryan | Merge pull request #374 from jgoyvaerts/m4pro/pmp-t6041 | Not applicable — MERGE | V | +| [4e5eea54ce7e](https://github.com/aurora-silicon/linux/commit/4e5eea54ce7e1c9d21867d81c5e5672e1cbd1c34) | Ryan | Merge pull request #368 from jgoyvaerts/m4pro/small-fixes | Not applicable — MERGE | V | +| [815438428a9e](https://github.com/aurora-silicon/linux/commit/815438428a9e45f0facbb10c4f9bd77047ef7282) | Ryan | Merge pull request #385 from jgoyvaerts/m4pro/pmc-clpc-mcc-t6040 | Not applicable — MERGE | V | +| [49273fd6921c](https://github.com/aurora-silicon/linux/commit/49273fd6921c9b39101cd1ee8c8d2f2ca0153440) | Ryan | Merge pull request #386 from jgoyvaerts/m4pro/apple-dart-t6040 | Not applicable — MERGE | V | +| [50a912fe81fc](https://github.com/aurora-silicon/linux/commit/50a912fe81fcdbd770d231db8eda856a1678c194) | Ryan | Merge pull request #391 from jgoyvaerts/m4pro/g17-generic | Not applicable — MERGE | V | +| [311471f73e99](https://github.com/aurora-silicon/linux/commit/311471f73e9919764bfb24acfe7eeb7c5230795a) | Ryan | Merge pull request #402 from aurora-silicon/j616s/pmp-ep7-20261024 | Not applicable — MERGE | V | +| [1aa2857509d5](https://github.com/aurora-silicon/linux/commit/1aa2857509d50f7f787541280aba7b274721919c) | Ryan | Merge pull request #403 from aurora-silicon/j616s/usb-20261023 | Not applicable — MERGE | V | +| [04231230fd76](https://github.com/aurora-silicon/linux/commit/04231230fd76fadd12a8b44707159f37f0e498b1), [e9a9b647d0f3](https://github.com/aurora-silicon/linux/commit/e9a9b647d0f36e222f16de732f78f2d77ea2d661), [b9a73d410ff4](https://github.com/aurora-silicon/linux/commit/b9a73d410ff48c65624c9283c634b4d06f4dfc6a), [4433181e4eb6](https://github.com/aurora-silicon/linux/commit/4433181e4eb649033f07751001fbb72621752647) | Jonathan Goyvaerts, Ryan Murray | Merge integration/aurora into the T6040 GPU series [#420](https://github.com/aurora-silicon/linux/pull/420) | Not applicable — MERGE | V | +| [2a868cf22d90](https://github.com/aurora-silicon/linux/commit/2a868cf22d90f0de72b2ad3c339b93460fdb9927) | Ryan | Merge pull request #413 from jgoyvaerts/m4pro/g17-robustness | Not applicable — MERGE | V | +| [2e9e457c922d](https://github.com/aurora-silicon/linux/commit/2e9e457c922d799dcc27975ff6f5d114d2719f3f) | Ryan | Merge pull request #418 from aurora-silicon/ryan/g17-innocent-clients-413 | Not applicable — MERGE | V | +| [127df1be31af](https://github.com/aurora-silicon/linux/commit/127df1be31af90613079dcedaeaf0a2aa9ae7d10) | Ryan Murray | Merge integration/aurora into aurora-wip-sync-177 | Not applicable — MERGE | V | +| [e5dad6df06f6](https://github.com/aurora-silicon/linux/commit/e5dad6df06f66b944037770e81e6a481a0b8302f), [d1b62dc403c6](https://github.com/aurora-silicon/linux/commit/d1b62dc403c6f9ed534850f204f069befca149f1) | Ryan Murray | Merge integration/aurora into m4pro/g17-t6040-text-identity [#428](https://github.com/aurora-silicon/linux/pull/428) | Not applicable — MERGE | V | +| [a1b269f79e35](https://github.com/aurora-silicon/linux/commit/a1b269f79e35844660892b9779adb2f68abc38b7) | Ryan | Merge pull request #395: sync omacom #177 (G15, T6030 dock, platform fixes) onto 7.3 | Not applicable — MERGE | V | +| [8bc8f12e95ed](https://github.com/aurora-silicon/linux/commit/8bc8f12e95edbbd5b7d5431973eb1f08c872e87c) | Ryan | Merge pull request #427 from jgoyvaerts/m4pro/asahi-pgtable-checked-borrow | Not applicable — MERGE | V | +| [18012befd69c](https://github.com/aurora-silicon/linux/commit/18012befd69ceed9892c28291132ebd4c05a6076) | Ryan | Merge pull request #428 from jgoyvaerts/m4pro/g17-t6040-text-identity | Not applicable — MERGE | V | +| [41ef596ab373](https://github.com/aurora-silicon/linux/commit/41ef596ab3731ef695fcce2fdfb666bc47da21b7) | Dj | Merge pull request #443 from aurora-silicon/fix/t8140-cpufreq-lazy-verify-20261025 | Not applicable — MERGE | V | +| [af80fbe9094b](https://github.com/aurora-silicon/linux/commit/af80fbe9094b631c5d734883bd259ad0925e88ba) | Dj | Merge pull request #447 from aurora-silicon/fix/aic2-effective-affinity-20261025 | Not applicable — MERGE | V | +| [67fdc27bdadb](https://github.com/aurora-silicon/linux/commit/67fdc27bdadb58520839376a2b85d1265c3fa78f), [b4e5e4cff710](https://github.com/aurora-silicon/linux/commit/b4e5e4cff710c9961a05d4ba6c16f0f8c47c3776) | Chris Kearney | Merge current aurora-wip into the combined Thunderbolt series | Not applicable — MERGE | V | +| [99a554596d0a](https://github.com/aurora-silicon/linux/commit/99a554596d0a4960120b78f9a09ab30ffd4d0fc1) | Chris Kearney | Merge aurora-wip 3bb0a6104a11 into tb-dp-tunnel-t8103 | Not applicable — MERGE | V | +| [5b55206ca89b](https://github.com/aurora-silicon/linux/commit/5b55206ca89bbeb8bb54a208a9b9a36b80d6360b) | Acelogic | Merge M5 CPU support while retaining per-chip frequency safeguards | Not applicable — MERGE | V | +| [95f70d759093](https://github.com/aurora-silicon/linux/commit/95f70d7590935fdfb63c889d73598996dbf6c695) | Ryan Murray | Merge pull request #172 from aurora-silicon/j700-dcp-foundation | Not applicable — MERGE | V | +| [b37df4bdd552](https://github.com/aurora-silicon/linux/commit/b37df4bdd552dca6fa180e7826d0ec22b5e264f4) | Ryan Murray | Merge pull request #8 from iconidentify/tb-dp-tunnel-t8103 | Not applicable — MERGE | V | +| [c9a55efdff8f](https://github.com/aurora-silicon/linux/commit/c9a55efdff8f9505114fab2c89e934ce363e9b3b) | Ryan Murray | Merge pull request #42 from aurora-silicon/feat/sep-neo-rebase | Not applicable — MERGE | V | +| [026b4131cf05](https://github.com/aurora-silicon/linux/commit/026b4131cf0506b0974e6a0ad0b13cfa15f80cc6) | Ryan Murray | Merge pull request #43 from aurora-silicon/feat/j700-thermal-framework-v2 | Not applicable — MERGE | V | +| [e9c631c62252](https://github.com/aurora-silicon/linux/commit/e9c631c6225261125aae251b6a0b743e0663ed37) | Ryan Murray | Merge pull request #46 from aurora-silicon/fix/j414s-dual-dock-dp | Not applicable — MERGE | V | +| [b8fef4b5b0b5](https://github.com/aurora-silicon/linux/commit/b8fef4b5b0b5d1ed5401db1249fc230fc9ce3f60) | Ryan Murray | Merge pull request #156 from aurora-silicon/codex/neo-ave-bringup | Not applicable — MERGE | V | +| [527179cf0856](https://github.com/aurora-silicon/linux/commit/527179cf085663164d17e4d8e8abf2c3e33b5070) | Ryan Murray | Merge pull request #168 from joshuaswarren/agent/jw16-agx-stats4 | Not applicable — MERGE | V | +| [918c0ff6270c](https://github.com/aurora-silicon/linux/commit/918c0ff6270c673e67067453a2e727b5f3e26331) | Ryan Murray | Merge pull request #55 from aurora-silicon/t8140-cpu-capacity | Not applicable — MERGE | V | +| [db4752825c93](https://github.com/aurora-silicon/linux/commit/db4752825c935257fb77f6b8aacf3b4ee003e9fa) | Ryan Murray | Merge pull request #174 from aurora-silicon/j700/t8140-dt-complete | Not applicable — MERGE | V | +| [d8ad51f6b8c8](https://github.com/aurora-silicon/linux/commit/d8ad51f6b8c809444fe4e3f1ef0a1ebb3ba6af5e) | Ryan Murray | Merge pull request #179 from aurora-silicon/j700/pmp-mcc-thermal | Not applicable — MERGE | V | +| [5de43d36cd52](https://github.com/aurora-silicon/linux/commit/5de43d36cd520eaf5df6fc16d047ee413ac54968) | Ryan Murray | Merge pull request #181 from aurora-silicon/j700/t8140-ane-topology | Not applicable — MERGE | V | +| [564976d8e4a6](https://github.com/aurora-silicon/linux/commit/564976d8e4a60dd64a4dc77b2b82f2a0f25f3f45) | Ryan Murray | Merge pull request #182 from aurora-silicon/j700/mcc-telemetry | Not applicable — MERGE | V | +| [4f3a64e7baa9](https://github.com/aurora-silicon/linux/commit/4f3a64e7baa91c6616ae955695e8a5c847dff968) | Ryan | Merge pull request #215 from aurora-silicon/j700/t8140-slc-allocation | Not applicable — MERGE | V | +| [ccedac0c6a30](https://github.com/aurora-silicon/linux/commit/ccedac0c6a30b11b9b1926522a98d4079c9acbf1) | Ryan | Merge pull request #221 from aurora-silicon/j700/display-handoff-race | Not applicable — MERGE | V | +| [123c0d909a8a](https://github.com/aurora-silicon/linux/commit/123c0d909a8af07363e3e885691f0e9feb10c9bc) | Ryan | Merge pull request #223 from aurora-silicon/apple/sashiko-pmp-entry-unbind | Not applicable — MERGE | V | +| [1702229de78f](https://github.com/aurora-silicon/linux/commit/1702229de78f1acd92808b228034839fb7e6c0bc) | Ryan | Merge pull request #232 from aurora-silicon/j700/aop-dart-invalidate | Not applicable — MERGE | V | +| [5c06b1998b8e](https://github.com/aurora-silicon/linux/commit/5c06b1998b8eefdf785b5320a6f8cec31f8da29a) | Ryan | Merge pull request #272 from aurora-silicon/j700/pmgr-failed-power-on | Not applicable — MERGE | V | +| [92ad2a772af8](https://github.com/aurora-silicon/linux/commit/92ad2a772af82a81fc3c73c915a48b9b611db55b) | Ryan Murray | Merge public J700 DP Alt qualification stack into current aurora-wip | Not applicable — MERGE | V | +| [f5afac94ca29](https://github.com/aurora-silicon/linux/commit/f5afac94ca2999c858fb61ebab0bf4b4c168fc6d) | Ryan Murray | drm/apple: compose lean J700 display and automatic recovery candidate | Not applicable — MERGE | V | +| [08e0d0a90be3](https://github.com/aurora-silicon/linux/commit/08e0d0a90be3bb6d777cc1a285ba22bfce12a4d9) | Ryan Murray | drm/apple: integrate reviewed fixed-host lifecycle and scoped boot fixes | Not applicable — MERGE | V | +| [47c99edf4878](https://github.com/aurora-silicon/linux/commit/47c99edf48782ec6078624f579108e243f8cbb4c) | Ryan | Merge pull request #342 from aurora-silicon/cleanup/rust-clippy-20261017 | Not applicable — MERGE | V | +| [3abf4a56b59e](https://github.com/aurora-silicon/linux/commit/3abf4a56b59e323026f9a6e2fe9a0d4307c251d5) | Ryan | Merge pull request #345 from aurora-silicon/fix/harden-all-20261017 | Not applicable — MERGE | V | +| [c20b9d96ba77](https://github.com/aurora-silicon/linux/commit/c20b9d96ba77da9a32c751ab0e63f283d10eea4a) | Ryan Murray | Merge branch 'integration/aurora' into aurora-wip | Not applicable — MERGE | V | +| [8080772b8377](https://github.com/aurora-silicon/linux/commit/8080772b83773a6ca224718de3fdb8bddc1b8511) | Ryan Murray | Merge branch 'integration/aurora' at a1b269f7 into aurora-wip | Not applicable — MERGE | V | +| [42c59170fe98](https://github.com/aurora-silicon/linux/commit/42c59170fe98eb0266c4ca5ea5e1abd0cbac97aa) | Ryan Murray | Merge integration/aurora at 3b77f0f9 into aurora-wip | Not applicable — MERGE | V | +| [1a3c5ca41f8f](https://github.com/aurora-silicon/linux/commit/1a3c5ca41f8f0215a355625ad7c4d456c78f2802) | Ryan Murray | Merge branch 'unified-7.3/bits/170-usb' into unified-7.3/canonical | Not applicable — MERGE | V | +| [2e1656f93c27](https://github.com/aurora-silicon/linux/commit/2e1656f93c27f76171fa265dadcb5ba019bce421) | Ryan Murray | Merge pull request #65 from joshuaswarren/ane-dt-polltx-aurora | Not applicable — MERGE | V | +| [9dbd46baf1b2](https://github.com/aurora-silicon/linux/commit/9dbd46baf1b289a30b5f9be6c4b5f6341135d7ce) | Ryan | Merge pull request #198 from aurora-silicon/fix/pr171-review [#171](https://github.com/aurora-silicon/linux/pull/171) | Not applicable — MERGE | V | +| [f1f334c2ec72](https://github.com/aurora-silicon/linux/commit/f1f334c2ec724b8c96f534a561df9480aa4310ee) | Ryan Murray | Merge branch 'backlog/kunit-parsers-fixes-20261017' into backlog/harden-all-20261017 [#345](https://github.com/aurora-silicon/linux/pull/345) | Not applicable — MERGE | V | + +### Public measured follow-ups outside the audited branch heads + +[#464](https://github.com/aurora-silicon/linux/pull/464), +[#468](https://github.com/aurora-silicon/linux/pull/468) and +[#494](https://github.com/aurora-silicon/linux/pull/494) were also read. Their +heads are not in either pinned branch snapshot; they are not silently counted +as inherited or qualified ports. They remain separate proposals for review: + +- #464 limits the T8140 GPU floor to streams and yields to PMP demand. Its + reported video power is 5.151 to 4.975 W, with light-submit gains retained. + Check the same video/browsing/heavy-load failure modes on g2, including actual + T6040 PMP voter behavior, before accepting its unconditional powered floor. +- #468 reports 7409 to 4625 doorbells in a traced hop, slow hops 19.6% to 13.9%, + and C-d64 13.89 to 13.11 us. Its shared compute-doorbell mechanism would need + no T6040 feature gate; it is an additional unmerged source dependency. +- #494 reports event passes 8.2 to 6.2 us and submit.empty d2 1.78 to 1.69 us. + Its shared event housekeeping change is another unmerged source dependency; + it is not evidence that the audited branches already provide those gains. + +No register, voltage, slot-key address, power curve, module parameter or new +platform option is invented to close an evidence gap. Applicability of dormant +25G83-specific feature/identity paths must come from validated facts; successful +compilation is not that qualification. diff --git a/MAINTAINERS b/MAINTAINERS index 694a8e2c2c78f..60fe5dc017d21 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2062,6 +2062,7 @@ F: Documentation/gpu/apple-dcp-*.rst F: drivers/gpu/drm/apple/ F: drivers/gpu/drm/apple/dcp-fabric* F: tools/testing/selftests/drivers/gpu/apple-afkv2/ +F: tools/testing/selftests/drivers/gpu/apple-dcp/ APPLE PCIE CONTROLLER DRIVER M: Marc Zyngier @@ -2745,6 +2746,7 @@ F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml F: Documentation/devicetree/bindings/usb/apple,sn201202x.yaml F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml F: Documentation/hwmon/macsmc-hwmon.rst +F: Documentation/power/apple-s2idle.rst F: arch/arm64/boot/dts/apple/ F: drivers/bluetooth/hci_bcm4377.c F: drivers/clk/clk-apple-nco.c diff --git a/Makefile b/Makefile index c61ea07ee5051..956665f4965fe 100644 --- a/Makefile +++ b/Makefile @@ -550,7 +550,10 @@ RUSTC = rustc RUSTDOC = rustdoc RUSTFMT = rustfmt CLIPPY_DRIVER = clippy-driver -BINDGEN = bindgen +# Prefer the locally prepared, layout-corrected generator, including O= builds. +# Keep command-line overrides for toolchain testing; layouts are always checked. +aurora_bindgen := $(abs_srctree)/tools/aurora-bindgen/build/bin/bindgen +BINDGEN = $(if $(wildcard $(aurora_bindgen)),$(aurora_bindgen),bindgen) PAHOLE = pahole RESOLVE_BTFIDS = $(objtree)/tools/bpf/resolve_btfids/resolve_btfids LEX = flex diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile index 007e204fdbc56..0f6e6714f7de7 100644 --- a/arch/arm64/boot/dts/apple/Makefile +++ b/arch/arm64/boot/dts/apple/Makefile @@ -95,6 +95,7 @@ dtb-$(CONFIG_ARCH_APPLE) += t6032-j575d.dtb dtb-$(CONFIG_ARCH_APPLE) += t6034-j514m.dtb dtb-$(CONFIG_ARCH_APPLE) += t6034-j516m.dtb dtb-$(CONFIG_ARCH_APPLE) += t6040-j616s.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6040-j616s-tether.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j415.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j473.dtb diff --git a/arch/arm64/boot/dts/apple/t6040-dpaudio.dtsi b/arch/arm64/boot/dts/apple/t6040-dpaudio.dtsi new file mode 100644 index 0000000000000..68d346a4e1160 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-dpaudio.dtsi @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * DPA interfaces are fed by SIO and have no AP-side MMIO registers. + * Keep them disabled until the T6041 SIO firmware and DCP audio services + * are supported. DPA3/4 have no assigned DCP in this SoC's firmware tree. + */ + +/ { + dpaudio0: audio-controller-0 { + compatible = "apple,t6041-dpaudio", "apple,dpaudio"; + dmas = <&sio 0x64>; + dma-names = "tx"; + power-domains = <&ps_dpa0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + dpaudio0_dcp: endpoint { + remote-endpoint = <&dcp_audio>; + }; + }; + }; + }; + + dpaudio1: audio-controller-1 { + compatible = "apple,t6041-dpaudio", "apple,dpaudio"; + dmas = <&sio 0x66>; + dma-names = "tx"; + power-domains = <&ps_dpa1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + dpaudio1_dcp: endpoint { + remote-endpoint = <&dcpext0_audio>; + }; + }; + }; + }; + + dpaudio2: audio-controller-2 { + compatible = "apple,t6041-dpaudio", "apple,dpaudio"; + dmas = <&sio 0x68>; + dma-names = "tx"; + power-domains = <&ps_dpa2>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + dpaudio2_dcp: endpoint { + remote-endpoint = <&dcpext1_audio>; + }; + }; + }; + }; + + dpaudio3: audio-controller-3 { + compatible = "apple,t6041-dpaudio", "apple,dpaudio"; + dmas = <&sio 0x6a>; + dma-names = "tx"; + power-domains = <&ps_dpa3>; + status = "disabled"; + }; + + dpaudio4: audio-controller-4 { + compatible = "apple,t6041-dpaudio", "apple,dpaudio"; + dmas = <&sio 0x6c>; + dma-names = "tx"; + power-domains = <&ps_dpa4>; + status = "disabled"; + }; +}; + +&dcp { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + dcp_audio: endpoint { + remote-endpoint = <&dpaudio0_dcp>; + }; + }; + }; +}; + +&dcpext0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + dcpext0_audio: endpoint { + remote-endpoint = <&dpaudio1_dcp>; + }; + }; + }; +}; + +&dcpext1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + dcpext1_audio: endpoint { + remote-endpoint = <&dpaudio2_dcp>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-hdmi.dtsi b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-hdmi.dtsi new file mode 100644 index 0000000000000..b414a0bddee7d --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-hdmi.dtsi @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * The fixed HDMI path of t6040-j616s-extdisp.dtsi (dcpext0 -> atc3 crossbar + * -> atcphy3 -> DP-to-HDMI converter) with every node left DISABLED. The + * nodes must not be enabled at boot in the device tree: powering the DISPEXT + * chain without the PMP acknowledgement hangs the machine before root. The + * enable gate (drivers/soc/apple/t6041-display-gate.c) switches them on in + * order once the PMP is bound. + * + * dispext0's DART is not in the display node's iommus: the gate appends it + * when it enables the chain, so the internal display's DART domain stays as + * it is without the external displays. + */ + +/* + * DISPEXT0 may only be powered once the PMP has acknowledged it in its + * device-state table (SOC-DEV-PS-REQ/ACK, PTD entries 480/488 on T6041): the + * soc-device record of DISPEXT0 has ID 18 (ADT iop-pmp-nub soc-device, ack + * flag set), so its request bit is 17. Without it, enabling the DISPEXT0 PMGR + * chain hung the machine before root. + * Same contract as the T6030 PMP device-state provider of + * aurora-silicon/linux#358, through the existing pmp-report entries here. + */ +&pmp_report { + pmp_dispext0: report@11 { + compatible = "apple,t6041-pmp-v2-report-entry", + "apple,t6000-pmp-v2-report-entry"; + reg = <0x11>; + label = "pmp-dispext0"; + #power-domain-cells = <0>; + /* parent of the IRQ-safe PMGR chain */ + apple,irq-safe; + status = "disabled"; + }; +}; + +&ps_dispext0_sys { + power-domains = <&pmp_dispext0>; + status = "disabled"; +}; + +&ps_dispext0_fe { + status = "disabled"; +}; + +&ps_dispext0_cpu { + /* + * NEVER enable: the ADT marks DISPEXT0_CPU no_ps (no PMGR power-state + * register, like the internal DISP_CPU). Its probe reads offset 0x350, + * and that read resets the SoC. DCPEXT0's own power gate (ADT power-gates 431) is the + * virtual DISPEXT0-CPU-V; the last real register of the chain is FE. + */ + /delete-property/ apple,always-on; + status = "disabled"; +}; + +&ps_atc3_common { + status = "disabled"; +}; + +&ps_atc3_common_dp { + status = "disabled"; +}; + +&ps_atc3_usb_aon { + status = "disabled"; +}; + +&ps_atc3_phymxwrap { + status = "disabled"; +}; + +&pmgr_mini { + /* ADT ATC3_USB: +0x148, parents ATC3_COMMON and ATC3_USB_AON; the crossbar's domain as on T6030 */ + ps_atc3_usb: power-controller@148 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_usb"; + power-domains = <&ps_atc3_common>, <&ps_atc3_usb_aon>; + status = "disabled"; + }; +}; + +&pinctrl_aop { + status = "disabled"; +}; + +&dcpext0_dart { + /* in the DISPEXT0 block: powered (after the PMP request) before probing */ + power-domains = <&ps_dispext0_fe>; + status = "disabled"; +}; + +&dispext0_dart { + /* in the DISPEXT0 block: powered (after the PMP request) before probing */ + power-domains = <&ps_dispext0_fe>; + status = "disabled"; +}; + +&dcpext0_mbox { + power-domains = <&ps_dispext0_fe>; + status = "disabled"; +}; + +&dcpext0 { + power-domains = <&ps_dispext0_fe>; + /* + * The IOMFB surface layout of the internal DCP; + * without apple,iomfb-surfaces apple-dcp's probe fails with -EINVAL. + */ + apple,iomfb-surfaces = <1 1 0 0>; + status = "disabled"; +}; + +&atcphy3_mbox { + status = "disabled"; +}; + +&atcphy3_xbar { + power-domains = <&ps_atc3_usb>; + status = "disabled"; +}; + +/* + * atcphy3 apply_tunables tables (aciophy_phy_init mode 3), from this machine's ADT /arm-io/atc-phy3 + * (privateartifacts/omarchy-m4-unit/adt.bin): AXI2AF converted entry by entry; every other table is + * byte-identical to atc-phy0 in the ADT, so the atcphy0 values of t6040-j616s-usb3.dtsi are reused + * (AUS40CMN_SHM differs: 0x50). Only the first six (axi2af..lane1) are applied in DP mode. + */ +&atcphy3 { + /* ATC3 ADT tunable_ATC0AXI2AF (port-specific), entries */ + apple,tunable-axi2af = + <0x0 0x800000ff 0x80000025>, + <0x4 0x80ff00ff 0x80250003>, + <0x8 0x80ff00ff 0x80250003>, + <0xc 0x80ff00ff 0x80250005>, + <0x10 0x80ff00ff 0x80250003>, + <0x14 0x80ff00ff 0x80250003>, + <0x18 0x80ff00ff 0x80250003>, + <0x1c 0x80ff00ff 0x80250003>, + <0x20 0x800000ff 0x8000001e>, + <0x24 0x800000ff 0x8000004a>, + <0x28 0x80ff00ff 0x804a0006>, + <0x2c 0x80ff00ff 0x804a0006>, + <0x30 0x80ff00ff 0x804a0006>, + <0x34 0x80ff00ff 0x804a0006>, + <0x38 0x80ff00ff 0x804a0006>, + <0x3c 0x80ff00ff 0x804a0006>, + <0x40 0x800000ff 0x8000001e>, + <0x44 0x800000ff 0x8000001e>, + <0x48 0x800000ff 0x8000001e>, + <0x100 0x000000ff 0x000000ff>, + <0x108 0xffff0006 0x00100000>, + <0x304 0x3f3f0000 0x04040000>, + <0x308 0x000fffff 0x00001681>, + <0x30c 0x000fffff 0x00001680>, + <0x310 0x70000fff 0x000001e0>, + <0x314 0xf0000fff 0x800001e0>, + <0x318 0x70000fff 0x000001e0>, + <0x31c 0xf0000fff 0x800001e0>, + <0x320 0x70000fff 0x000005a0>, + <0x324 0xf0000fff 0x800005a0>, + <0x328 0x70000fff 0x000005a0>, + <0x32c 0xf0000fff 0x800005a0>, + <0x330 0x70000fff 0x000005a0>, + <0x334 0xf0000fff 0x800005a0>, + <0x338 0x70000fff 0x000005a0>, + <0x33c 0x70000fff 0x000005a0>, + <0x340 0x70000fff 0x000005a0>, + <0x344 0x70000fff 0x000005a0>, + <0x348 0x70000fff 0x000005a0>, + <0x34c 0x70000fff 0x000005a0>, + <0x350 0x70000fff 0x000005a0>, + <0x358 0xffffffff 0x0000ffff>, + <0x35c 0xffffffff 0x0000ffff>, + <0x408 0xffff0020 0x00210020>, + <0x40c 0x000fffff 0x00001681>, + <0x410 0x000fffff 0x00001680>, + <0x414 0x70000fff 0x000001e0>, + <0x418 0x70000fff 0x000001e0>, + <0x41c 0x70000fff 0x000001e0>, + <0x420 0x70000fff 0x000001e0>, + <0x424 0x70000fff 0x000005a0>, + <0x428 0x70000fff 0x000005a0>, + <0x42c 0x70000fff 0x000005a0>, + <0x430 0x70000fff 0x000005a0>, + <0x434 0x70000fff 0x000005a0>, + <0x438 0x70000fff 0x000005a0>, + <0x43c 0x70000fff 0x000005a0>, + <0x440 0x70000fff 0x000005a0>, + <0x444 0x70000fff 0x000005a0>, + <0x448 0x70000fff 0x000005a0>, + <0x44c 0x70000fff 0x000005a0>, + <0x450 0x70000fff 0x000005a0>, + <0x454 0x70000fff 0x000005a0>, + <0x948 0x07ff07ff 0x00280000>, + <0xa48 0x07ff07ff 0x00400040>, + <0xb48 0x07ff07ff 0x00b8010e>, + <0xc48 0x07ff07ff 0x00400020>, + <0xd48 0x07ff07ff 0x008a008a>, + <0x1000 0xffffffff 0x7fffffff>, + <0x4008 0x00000001 0x00000001>, + <0x400c 0x00000001 0x00000001>, + <0x4014 0x00000001 0x00000001>, + <0x4018 0x00000001 0x00000000>, + <0x401c 0x00000001 0x00000001>; + /* the remaining tables equal atc-phy0 in the ADT, except AUS40CMN_SHM (0x50) */ + apple,tunable-fabric = + <0x4 0x003f003f 0x00200020>, + <0xc 0x000000f1 0x000000a1>, + <0x10 0x0000801f 0x00000004>, + <0x18 0x003f003f 0x00200020>, + <0x20 0x000000f1 0x000000a1>, + <0x24 0x0000801f 0x00000004>, + <0x2c 0x00ff00ff 0x00960096>, + <0x34 0x000000f1 0x00000071>, + <0x38 0x0000801f 0x00000004>, + <0x40 0x0000003f 0x00000020>, + <0x48 0x0000801f 0x00000004>, + <0x50 0x007f007f 0x00450045>, + <0x58 0x0000801f 0x00000004>, + <0x60 0x0000003f 0x00000020>, + <0x68 0x0000801f 0x00000004>, + <0x70 0x00000001 0x00000001>, + <0x74 0x0000801f 0x00000004>, + <0x7c 0x007f007f 0x00400040>, + <0x80 0x000000f1 0x00000011>, + <0x84 0x00000001 0x00000001>, + <0x88 0x00000001 0x00000001>, + <0x8c 0x0000801f 0x00000004>, + <0x94 0x001f001f 0x00100010>, + <0x98 0x000000f1 0x00000011>, + <0x9c 0x0000801f 0x00000004>, + <0xa4 0x000000ff 0x000000e0>, + <0xa8 0x0000801f 0x00000004>, + <0xb0 0x0000003f 0x00000020>, + <0xb8 0x0000801f 0x00000004>, + <0xc0 0x0000003f 0x00000020>, + <0xc8 0x0000801f 0x00000004>, + <0xd0 0x001f001f 0x00100010>, + <0xd8 0x00000001 0x00000001>, + <0xdc 0x00000001 0x00000001>, + <0xe0 0x0000801f 0x00000004>, + <0xe8 0x0000000f 0x00000008>, + <0xec 0x0000801f 0x00000004>, + <0xf4 0xffffffff 0x01010101>, + <0xf8 0x0000801f 0x00000004>, + <0x100 0x00000001 0x00000001>, + <0x104 0x00000001 0x00000001>, + <0x108 0x0000801f 0x00000004>, + <0x110 0x0000801f 0x00000004>, + <0x118 0x0000801f 0x00000004>, + <0x120 0x00000001 0x00000001>, + <0x124 0x00000001 0x00000001>, + <0x128 0x0000801f 0x00000004>, + <0x130 0xffffffff 0x01010101>, + <0x134 0x0000801f 0x00000004>, + <0x13c 0x00000001 0x00000001>, + <0x140 0x00000001 0x00000001>, + <0x144 0x0000801f 0x00000004>, + <0x14c 0x00000001 0x00000001>, + <0x150 0x00000001 0x00000001>, + <0x154 0x0000801f 0x00000004>, + <0x15c 0x0000801f 0x00000004>, + <0x164 0xffffffff 0x01010101>, + <0x168 0x0000ffff 0x00000101>, + <0x16c 0x0000801f 0x00000004>, + <0x174 0x00000001 0x00000001>, + <0x178 0x00000001 0x00000001>, + <0x17c 0x0000801f 0x00000004>, + <0x184 0x00000001 0x00000001>, + <0x188 0x00000001 0x00000001>, + <0x18c 0x0000801f 0x00000004>; + apple,tunable-common-cfg = + <0x8 0x00000001 0x00000001>; + apple,tunable-axi2af-lioa = + <0x28 0x0000fe2f 0x00000000>, + <0x2c 0x00000007 0x00000000>, + <0x30 0x0000007f 0x00000000>, + <0x34 0x000000ff 0x00000000>, + <0x58 0x0000010b 0x00000000>; + apple,tunable-lane0 = + <0xd094 0x00080000 0x00080000>, + <0xd098 0x00080000 0x00080000>, + <0xd0a4 0x00800000 0x00800000>, + <0xd0a8 0x00200000 0x00200000>; + apple,tunable-lane1 = + <0x1d094 0x00080000 0x00080000>, + <0x1d098 0x00080000 0x00080000>, + <0x1d0a4 0x00800000 0x00800000>, + <0x1d0a8 0x00200000 0x00200000>; + apple,tunable-aus40cmn-shm = + <0xa04 0x0000007c 0x00000050>; + apple,tunable-auspll = + <0x3224 0x00000c00 0x00000800>, + <0x3230 0x0000001f 0x0000000f>, + <0x3240 0x07d8007f 0x04000059>, + <0x32b0 0x0b800000 0x01800000>, + <0x32e8 0x00060001 0x00040001>, + <0x3338 0x0f800000 0x07800000>; + apple,tunable-cio4pll = + <0x3a30 0x0000001f 0x0000000f>, + <0x3a4c 0x0000fe7f 0x0000501e>, + <0x3ab0 0x00700000 0x00300000>, + <0x3ae8 0x00060000 0x00020000>; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-mem-loader.dtsi b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-mem-loader.dtsi new file mode 100644 index 0000000000000..08d4768494634 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-mem-loader.dtsi @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * The firmware memory of the external DCPs, for a boot loader that fills it + * (m4-loader m4_dcpext_fixup()). + * + * The loader reads /arm-io/dcpextN/iop-dcpextN-nub segment-ranges of the + * booting Mac (__TEXT, __DATA and the heap; __OS_LOG is left out as for the + * internal DCP), gives each placeholder below its reg, iommu-addresses and + * name, enables it, names the regions in the node's memory-region-names and + * sets apple,firmware-version/compat. A placeholder it does not fill is + * deleted together with the node's memory-region. A boot loader without this + * fix-up leaves them disabled, and the enable gate then keeps the external + * displays off. + * + * Nothing here enables dcpext0/1 or their DART/mailbox. + */ + +/ { + reserved-memory { + dcpext0_text: dcpext0-text { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext0_data: dcpext0-data { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext0_heap: dcpext0-heap { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext1_text: dcpext1-text { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext1_data: dcpext1-data { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + + dcpext1_heap: dcpext1-heap { + compatible = "apple,asc-mem"; + reg = <0x0 0x0 0x0 0x4000>; + no-map; + status = "disabled"; + }; + }; +}; + +&dcpext0 { + memory-region = <&dcpext0_text>, <&dcpext0_data>, <&dcpext0_heap>; + memory-region-names = "text", "data", "heap"; +}; + +&dcpext1 { + memory-region = <&dcpext1_text>, <&dcpext1_data>, <&dcpext1_heap>; + memory-region-names = "text", "data", "heap"; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-usbc.dtsi b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-usbc.dtsi new file mode 100644 index 0000000000000..3d375fd93a98e --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp-usbc.dtsi @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * USB-C DisplayPort: dcpext1 over any of the three Type-C ports. As for + * HDMI, the DISPEXT1 chain and dcpext1 stay DISABLED in the DTB and are + * enabled by the enable gate (drivers/soc/apple/t6041-display-gate.c). The + * routes go through the display fabric (typec-routes, end of this file): + * tipd's mode switch reaches dcpext1, which claims the port. Boot-time + * changes: mode-switch on atcphy0..2 (the PHY registers a Type-C mode + * switch; tipd drives it) and the port@3 route endpoints of the connectors. + */ + +/* PMP device-state acknowledgement for DISPEXT1 (soc-device 19, request bit 18), as DISPEXT0. */ +&pmp_report { + pmp_dispext1: report@12 { + compatible = "apple,t6041-pmp-v2-report-entry", + "apple,t6000-pmp-v2-report-entry"; + reg = <0x12>; + label = "pmp-dispext1"; + #power-domain-cells = <0>; + apple,irq-safe; + status = "disabled"; + }; +}; + +/* + * DISPEXT1_SYS sits on FAB7_SOC (ADT pmgr device id2 134, pmgr_aux 0x488, parent + * FAB6_SOC, off at boot), which t6040-pmgr.dtsi does not describe. m4.62 enabled + * DISPEXT1_SYS without it and the first DCPEXT1 register access (its DART) reset + * the SoC. DISPEXT0_SYS's parent FAB2_SOC is on anyway through ATC0..2_COMMON. + */ +&pmgr_aux { + ps_fab7_soc: power-controller@488 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x488 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab7_soc"; + power-domains = <&ps_fab6_soc>; + status = "disabled"; + }; +}; + +&ps_dispext1_sys { + power-domains = <&ps_fab7_soc>, <&pmp_dispext1>; + status = "disabled"; +}; + +&ps_dispext1_fe { + status = "disabled"; +}; + +&ps_dispext1_cpu { + /* NEVER enable: ADT no_ps, as DISPEXT0_CPU (its register read resets the SoC). */ + /delete-property/ apple,always-on; + status = "disabled"; +}; + +/* The per-ATC crossbars of the Type-C ports: the ATC USB domain, as atcphy3's. */ +&atcphy0_xbar { + power-domains = <&ps_atc0_usb>; + status = "disabled"; +}; + +&atcphy1_xbar { + power-domains = <&ps_atc1_usb>; + status = "disabled"; +}; + +&atcphy2_xbar { + power-domains = <&ps_atc2_usb>; + status = "disabled"; +}; + +/* tipd switches the PHY into DP alt mode through the connector graph. */ +&atcphy0 { + mode-switch; +}; + +&atcphy1 { + mode-switch; +}; + +&atcphy2 { + mode-switch; +}; + +/* + * Type-C display routes through the display fabric, as T6030 and J700: + * dcpext1 offers one route per port, each a mode switch the port controller + * drives over the connector graph (port@3). The gate enables the routes + * before devices exist, so the port controllers wait for dcpext1's mode + * switches, and probes the controllers again once dcpext1 is bound. A route + * without a registered mode switch would make the port wait for one forever. + */ +&typec0 { + ports { + port@3 { + #address-cells = <1>; + #size-cells = <0>; + reg = <3>; + + typec0_dcpext1: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec0>; + }; + + typec0_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec0>; + }; + }; + }; +}; + +&typec1 { + ports { + port@3 { + #address-cells = <1>; + #size-cells = <0>; + reg = <3>; + + typec1_dcpext1: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec1>; + }; + + typec1_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec1>; + }; + }; + }; +}; + +&typec2 { + ports { + port@3 { + #address-cells = <1>; + #size-cells = <0>; + reg = <3>; + + typec2_dcpext1: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec2>; + }; + + typec2_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec2>; + }; + }; + }; +}; + +&dcpext1 { + apple,connector-type = "USB-C"; + /* as dcpext0: without it apple-dcp's probe fails with -EINVAL (m4.63) */ + apple,iomfb-surfaces = <1 1 0 0>; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, <&atcphy2_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2"; + /* ADT: atcN-dpphy dp-switch-dfp-endpoint N; dcpext1 ufp 1 port 0 = crossbar state 2 */ + apple,typec-dptx-phys = <0 1 2>; + apple,typec-mux-indices = <2 2 2>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcpext1>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcpext1>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcpext1>; + }; + }; + }; + }; +}; + +/* + * dcpext0 also offers a route to every Type-C port, next to its fixed HDMI + * path (a hybrid pipeline, as the T6030 J514s). It is the only pipe that + * can be the primary of a two-pipe display (firmware endpoint 1; the pipes + * merge upwards only, m4.138), so a 7680 px sink on USB-C needs it: the + * fabric takes it for a Type-C port with appledrm.typec_hybrid_first. + * ADT: atcN-dpphy dp-switch-dfp-endpoint 0 = dispext0 = crossbar state 0, + * as its HDMI route on atcphy3 (mux-index 0). + */ +&dcpext0 { + phys = <&atcphy3 PHY_TYPE_DP>, + <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, <&atcphy2 PHY_TYPE_DP>; + phy-names = "dp-phy", "typec0", "typec1", "typec2"; + mux-controls = <&atcphy3_xbar 0>, + <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, <&atcphy2_xbar 0>; + mux-control-names = "dp-xbar", "typec0", "typec1", "typec2"; + apple,typec-dptx-phys = <0 1 2>; + apple,typec-mux-indices = <0 0 0>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcpext0_typec0: endpoint { + remote-endpoint = <&typec0_dcpext0>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + status = "disabled"; + + port { + dcpext0_typec1: endpoint { + remote-endpoint = <&typec1_dcpext0>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + status = "disabled"; + + port { + dcpext0_typec2: endpoint { + remote-endpoint = <&typec2_dcpext0>; + }; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-extdisp.dtsi b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp.dtsi new file mode 100644 index 0000000000000..b6486f293da9d --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-extdisp.dtsi @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * External display resources of the J616s. Everything here is disabled in + * the device tree: the enable gate (drivers/soc/apple/t6041-display-gate.c) + * switches the chain on once the boot loader has provided the firmware + * memory of the external display processors, so without that the booted + * hardware state is the same as without this file. + * + * Values come from this unit's ADT (25G83); CPU address = ADT address + + * 0x2_0000_0000. Path of the picture: + * HDMI: dcpext0 -> atc3 display crossbar -> atcphy3 (fixed DP PHY, no + * USB controller) -> on-board DP-to-HDMI converter + * USB-C: dcpext1 -> atcN display crossbar -> atcphyN (DP alt mode) + * + * Needs the DP mode of the T6040 Type-C PHY driver (phy-apple-t6040-usb2) + * with its probe path for a PHY without a USB controller (atcphy3), the + * atc-dpxbar,t604x display crossbar driver and the dcpext firmware memory + * (dcp-map style memory-region with iommu-addresses, t6040-j616s-extdisp- + * mem-loader.dtsi). + */ + +/ { + pinctrl_aop: pinctrl@510824000 { + compatible = "apple,t6041-pinctrl", "apple,t8103-pinctrl"; + reg = <0x5 0x10824000 0x0 0x4000>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_aop 0 0 85>; + apple,npins = <85>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + status = "disabled"; + }; + + dispext0_dart: iommu@3a2300000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0xa2300000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext0_dart: iommu@3a2340000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0xa2340000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext0: dcp@3a2e00000 { + compatible = "apple,t6041-dcpext", "apple,dcpext"; + reg = <0x3 0xa2e00000 0x0 0x4000>, + <0x3 0xa0000000 0x0 0x4000000>, + <0x3 0xa2240000 0x0 0x10000>, + <0x3 0xa23a8000 0x0 0x4000>, + <0x3 0xa2800000 0x0 0x800000>; + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + mboxes = <&dcpext0_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext0_dart 23>; + /* guessed: the internal DCP uses 0x20000, J700 DISPEXT0 is +0x8000 */ + apple,bw-scratch = <&pmgr_dcp 0 4 0x28000>; + /* firmware Disp_Index, the dual-pipe endpoint ID */ + apple,dcp-endpoint = <1>; + /* the only pipe that can be the primary of a two-pipe display */ + apple,typec-hybrid-first; + status = "disabled"; + + dispext0_piodma: piodma { + iommus = <&dispext0_dart 16>; + }; + }; + + dcpext0_mbox: mailbox@3a2e08000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0xa2e08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + dispext1_dart: iommu@602300000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x6 0x02300000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext1_dart: iommu@602340000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x6 0x02340000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext1: dcp@602e00000 { + compatible = "apple,t6041-dcpext", "apple,dcpext"; + reg = <0x6 0x02e00000 0x0 0x4000>, + <0x6 0x00000000 0x0 0x4000000>, + <0x6 0x02240000 0x0 0x10000>, + <0x6 0x023a8000 0x0 0x4000>, + <0x6 0x02800000 0x0 0x800000>; + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + mboxes = <&dcpext1_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext1_dart 23>; + /* guessed, as dcpext0 */ + apple,bw-scratch = <&pmgr_dcp 0 4 0x30000>; + apple,dcp-endpoint = <2>; + status = "disabled"; + + dispext1_piodma: piodma { + iommus = <&dispext1_dart 16>; + }; + }; + + dcpext1_mbox: mailbox@602e08000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x6 0x02e08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + /* ATC3: the HDMI converter's fixed DP PHY; no USB controller, no pipehandler. */ + atcphy3: phy@39aa90000 { + compatible = "apple,t6040-atcphy"; + reg = <0x3 0x9aa90000 0x0 0x4000>, + <0x3 0x9a800000 0x0 0x4000>, + <0x3 0x9b000000 0x0 0x30000>, + <0x3 0x9b200000 0x0 0x4000>, + <0x3 0x9b140000 0x0 0x4000>, + <0x3 0x9b0c0000 0x0 0x10000>, + <0x3 0x98000000 0x0 0x8000>, + <0x3 0x98020000 0x0 0x4000>, + <0x3 0x9b244000 0x0 0x4000>, + <0x3 0x9b250000 0x0 0x4000>; + reg-names = "bank0", "bank1", "core", "cfg", "cpu", "cpu-data", + "axi2af", "axi2af-lioa", "fabric", "common-cfg"; + #phy-cells = <1>; + power-domains = <&ps_atc3_common_dp>; + mboxes = <&atcphy3_mbox>; + mbox-names = "phy-cpu"; + nonposted-mmio; + status = "disabled"; + }; + + atcphy3_mbox: mailbox@39b148000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x9b148000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_atc3_phymxwrap>; + status = "disabled"; + }; + + /* Display crossbars (ADT atcN-dpxbar, atc-dpxbar,t604x): no driver yet. */ + atcphy0_xbar: mux@38324c000 { + compatible = "apple,t6041-display-crossbar"; + reg = <0x3 0x8324c000 0x0 0x4000>; + #mux-control-cells = <1>; + status = "disabled"; + }; + + atcphy1_xbar: mux@38b24c000 { + compatible = "apple,t6041-display-crossbar"; + reg = <0x3 0x8b24c000 0x0 0x4000>; + #mux-control-cells = <1>; + status = "disabled"; + }; + + atcphy2_xbar: mux@39324c000 { + compatible = "apple,t6041-display-crossbar"; + reg = <0x3 0x9324c000 0x0 0x4000>; + #mux-control-cells = <1>; + status = "disabled"; + }; + + atcphy3_xbar: mux@39b24c000 { + compatible = "apple,t6041-display-crossbar"; + reg = <0x3 0x9b24c000 0x0 0x4000>; + #mux-control-cells = <1>; + status = "disabled"; + }; +}; + +/* + * PMGR domains from the ADT device table (+16 offset, +19 block; block 0 = + * pmgr_aux, block 3 = pmgr_mini; checked against the ATC0 nodes). Disabled + * so the pwrstate driver does not touch them yet: DISPEXT0/1 run the + * firmware iBoot loaded, and an always-on domain that happens to be off + * would be powered on by the driver. + */ +&pmgr_aux { + ps_dispext0_sys: power-controller@340 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x340 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_sys"; + status = "disabled"; + }; + + ps_dispext0_fe: power-controller@348 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x348 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_fe"; + power-domains = <&ps_dispext0_sys>; + status = "disabled"; + }; + + ps_dispext0_cpu: power-controller@350 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x350 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_cpu"; + power-domains = <&ps_dispext0_fe>; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_common: power-controller@408 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x408 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_common"; + apple,always-on; + status = "disabled"; + }; + + ps_dispext1_sys: power-controller@490 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x490 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_sys"; + status = "disabled"; + }; + + ps_dispext1_fe: power-controller@498 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x498 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_fe"; + power-domains = <&ps_dispext1_sys>; + status = "disabled"; + }; + + ps_dispext1_cpu: power-controller@4a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_cpu"; + power-domains = <&ps_dispext1_fe>; + apple,always-on; + status = "disabled"; + }; +}; + +&pmgr_mini { + ps_atc3_common_dp: power-controller@b8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xb8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_common_dp"; + power-domains = <&ps_atc3_phymxwrap>; + status = "disabled"; + }; + + ps_atc3_usb_aon: power-controller@e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xe0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_usb_aon"; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_phymxwrap: power-controller@168 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_phymxwrap"; + power-domains = <&ps_atc3_common>, <&ps_atc3_usb_aon>; + status = "disabled"; + }; +}; + +/* + * HDMI port (disabled with dcpext0): converter power SMC gP0f/gP1a (as on + * the M3 Pro J514s), HPD on AOP GPIO 0x24. The converter's reset (gP10), + * force-dpfu (AOP GPIO 0x2a) and its I2C side channel (i2c8 0x08) are left + * undescribed, as on the J514s. + */ +&dcpext0 { + apple,connector-type = "HDMI-A"; + hdmi-hpd-gpios = <&pinctrl_aop 36 GPIO_ACTIVE_HIGH>; + hdmi-pwren-gpios = <&smc_gpio 15 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; + phys = <&atcphy3 PHY_TYPE_DP>; + phy-names = "dp-phy"; + mux-controls = <&atcphy3_xbar 0>; + mux-control-names = "dp-xbar"; + mux-index = <0>; + apple,dptx-phy = <3>; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts b/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts new file mode 100644 index 0000000000000..8027360847204 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j616s-tether.dts @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* J616s tether console; the host owns ATC0 during the debug session. */ +#include "t6040-j616s.dts" + +/ { + aliases { + serial0 = &serial0; + }; +}; + +&serial0 { + status = "okay"; +}; + +&ps_uart_p { + status = "okay"; +}; + +&ps_uart0 { + status = "okay"; +}; + +&dcuart { + status = "okay"; +}; + +&hpm0 { + status = "disabled"; +}; + +&atcphy0 { + status = "disabled"; +}; + +&atcphy0_mbox { + status = "disabled"; +}; + +&usb_drd0 { + status = "disabled"; +}; + +&usb_drd0_dart_0 { + status = "disabled"; +}; + +&usb_drd0_dart_1 { + status = "disabled"; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index a40a3fa1e1dc0..3995f5d4db16a 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -29,6 +29,9 @@ disp0-piodma = &disp0_piodma; /* the boot loader finds the GPU node through this alias */ gpu = &gpu; + pmp = &pmp; + /* the boot loader reserves the SEP firmware through this alias */ + sep = &sep; }; chosen { @@ -79,8 +82,9 @@ * Text segment of the loaded GPU firmware (ADT gfx-asc * segment-ranges[0]); the driver identifies the build from it. */ - gpu_fw_text: gpu-fw-text@10000be4000 { - reg = <0x100 0x00be4000 0x0 0x60000>; + gpu_fw_text: gpu-fw-text { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; @@ -88,8 +92,9 @@ * Data segment of the loaded GPU firmware (ADT sgx gfx-data-*); it * places its crash log here. */ - gpu_fw_data: gpu-fw-data@10001d44000 { - reg = <0x100 0x01d44000 0x0 0x138000>; + gpu_fw_data: gpu-fw-data { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; @@ -98,23 +103,27 @@ * gfx-shared-l2-region-*, gfx-handoff-*). They stay no-map until * the boot loader adds them to /memory. */ - uat_ttbs: uat-ttbs@10bfffb8000 { - reg = <0x10b 0xfffb8000 0x0 0x4000>; + uat_ttbs: uat-ttbs { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_pagetables: uat-pagetables@10bfff78000 { - reg = <0x10b 0xfff78000 0x0 0x40000>; + uat_pagetables: uat-pagetables { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_l2_pagetables: uat-l2-pagetables@10bfff74000 { - reg = <0x10b 0xfff74000 0x0 0x4000>; + uat_l2_pagetables: uat-l2-pagetables { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_handoff: uat-handoff@10bfff70000 { - reg = <0x10b 0xfff70000 0x0 0x4000>; + uat_handoff: uat-handoff { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; }; @@ -139,76 +148,76 @@ /* * GPU performance states, one opp-microvolt cell per cluster (ADT * /arm-io/sgx perf-states and perf-states1). The operating points are - * binned per device: these are this unit's values until the boot - * loader fills them in. Entries keep the order of the GPU performance - * states, which is not sorted by frequency. + * binned per device: the loader fills these placeholders. + * The driver requires a completed calibration handoff. Entries keep the + * order of the GPU performance states, which is not sorted by frequency. */ gpu_opp: opp-table-gpu { compatible = "operating-points-v2"; opp00 { opp-hz = /bits/ 64 <0>; - opp-microvolt = <125000 125000>; + opp-microvolt = <0 0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <605000 585000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <655000 640000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <690000 690000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp04 { - opp-hz = /bits/ 64 <924000000>; - opp-microvolt = <715000 725000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp05 { - opp-hz = /bits/ 64 <952000000>; - opp-microvolt = <755000 770000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp06 { - opp-hz = /bits/ 64 <1056000000>; - opp-microvolt = <755000 770000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp07 { - opp-hz = /bits/ 64 <1062000000>; - opp-microvolt = <795000 810000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp08 { - opp-hz = /bits/ 64 <1182000000>; - opp-microvolt = <795000 810000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp09 { - opp-hz = /bits/ 64 <1182000000>; - opp-microvolt = <845000 860000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp10 { - opp-hz = /bits/ 64 <1312000000>; - opp-microvolt = <845000 860000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp11 { - opp-hz = /bits/ 64 <1242000000>; - opp-microvolt = <900000 915000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp12 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <900000 915000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp13 { - opp-hz = /bits/ 64 <1326000000>; - opp-microvolt = <945000 960000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp14 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <945000 960000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp15 { - opp-hz = /bits/ 64 <1578000000>; - opp-microvolt = <1030000 1045000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; }; @@ -217,36 +226,36 @@ compatible = "operating-points-v2"; opp00 { - opp-hz = /bits/ 64 <24000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <620000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <660000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp04 { - opp-hz = /bits/ 64 <992000000>; - opp-microvolt = <715000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp05 { - opp-hz = /bits/ 64 <1188000000>; - opp-microvolt = <780000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp06 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <870000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp07 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <910000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; }; @@ -255,275 +264,3534 @@ compatible = "operating-points-v2"; opp00 { - opp-hz = /bits/ 64 <24000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <620000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <660000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp04 { - opp-hz = /bits/ 64 <992000000>; - opp-microvolt = <715000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp05 { - opp-hz = /bits/ 64 <1188000000>; - opp-microvolt = <780000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp06 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <870000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp07 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <910000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + }; + + /* + * CPU die temperature: SMC key TCMz (CPU die max), sensor 0 of + * smc_hwmon. The PMP's fast die-temperature limit regulates (TCMz + * settles near 111 C); this zone is the backstop above it: passive at + * 113 C, critical (orderly shutdown) at 118 C, polled once a second as + * the SMC refreshes TCMz. The cpufreq cooling device sits on a + * cluster's policy CPU, so each map lists every CPU of its cluster. + * + * Per-cluster and GPU zones follow, one per SMC die-maximum sensor + * (smc_hwmon channels 1-23, in the order of the temperature nodes + * below): Tex0-3 throttle the E cluster, Tpx0-5 the first P cluster + * and Tpx8-D the second, with the same 113/118 C backstop as TCMz. + * Unlike the fanless J700, this machine has two SMC-driven fans and + * the PMP regulates the dies itself, so these zones only catch a + * failure of that loop; they never act in normal use. + * + * The GPU has no cooling device of its own: its firmware sets the GPU + * performance state and starts limiting at 101 C. The SMC runs the + * fans in its own mode (3), only at their minimum speed in every load + * measured under Linux, and refuses manual fan modes from the OS. So + * when a GPU die sensor reaches the GPU firmware's limit, the seven + * hottest ones (Tg05, Tg0S, Tg0Y, Tg0k, Tg0z, Tg1V, Tg1l) also shed + * CPU power through the CPU clusters, from the same 101 C, leaving the + * shared heat budget to the GPU. They shut down at 118 C. While the + * GPU is powered off its sensors report placeholder values from -4 to + * 4 C. + */ + thermal-zones { + cpu-die-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 0>; /* TCMz */ + + trips { + cpu_die_passive: cpu-die-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + cpu-die-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e0-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 1>; /* Tex0 */ + + trips { + cpu_e0_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e0_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e1-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 2>; /* Tex1 */ + + trips { + cpu_e1_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e1_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e2-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 3>; /* Tex2 */ + + trips { + cpu_e2_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e2_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e3-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 4>; /* Tex3 */ + + trips { + cpu_e3_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e3_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p00-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 5>; /* Tpx0 */ + + trips { + cpu_p00_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p00_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p01-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 6>; /* Tpx1 */ + + trips { + cpu_p01_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p01_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; }; + + cpu-p02-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 7>; /* Tpx2 */ + + trips { + cpu_p02_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p02_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p03-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 8>; /* Tpx3 */ + + trips { + cpu_p03_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p03_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p04-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 9>; /* Tpx4 */ + + trips { + cpu_p04_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p04_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p05-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 10>; /* Tpx5 */ + + trips { + cpu_p05_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p05_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p10-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 11>; /* Tpx8 */ + + trips { + cpu_p10_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p10_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p11-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 12>; /* Tpx9 */ + + trips { + cpu_p11_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p11_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p12-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 13>; /* TpxA */ + + trips { + cpu_p12_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p12_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p13-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 14>; /* TpxB */ + + trips { + cpu_p13_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p13_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p14-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 15>; /* TpxC */ + + trips { + cpu_p14_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p14_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p15-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 16>; /* TpxD */ + + trips { + cpu_p15_passive: trip-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p15_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu0-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 17>; /* Tg05 */ + + trips { + gpu0_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu1-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 18>; /* Tg0S */ + + trips { + gpu1_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu2-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 19>; /* Tg0Y */ + + trips { + gpu2_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu3-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 20>; /* Tg0k */ + + trips { + gpu3_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu4-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 21>; /* Tg0z */ + + trips { + gpu4_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu5-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 22>; /* Tg1V */ + + trips { + gpu5_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu6-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 23>; /* Tg1l */ + + trips { + gpu6_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + }; + + /* + * The speaker amps' shared shutdown line (SDZ, AP pin 54, low = + * shutdown). One fixed regulator, the SDZ supply of all six, not + * per-amp shutdown-gpios: with GPIO_SHARED six references become a + * vote, and one amp driving the line low would not shut the other + * five down. + */ + speaker_sdz: regulator-sn012776-sdz { + compatible = "regulator-fixed"; + regulator-name = "sn012776-sdz"; + gpios = <&pinctrl 54 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + + /* + * The AOP firmware runs the stream (MCA cluster 0, one serializer for + * all six amps); the J616 variant of macaudio links the AOP audio + * component's speaker back-end (DAI 3) to the amps. The codecs are + * listed in the ADT provider order, so each gets its native playback + * slot (LW1 0, RW1 1, LW2 2, RW2 3, LT 4, RT 5). The idle masks are the + * ADT amp-tx-zd-config rows 0 and 1: LW1 drives zeros in the right + * amps' sense slots, RW1 in the left amps'. + */ + sound: sound { + compatible = "apple,j616-macaudio", "apple,macaudio"; + model = "MacBook Pro J616"; + speaker-id-gpios = <&pinctrl 52 GPIO_ACTIVE_HIGH>, + <&pinctrl 53 GPIO_ACTIVE_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Speakers"; + dai-tdm-idle-mode-0 = "zero"; + dai-tdm-idle-mode-1 = "zero"; + dai-tdm-slot-tx-idle-mask-0 = <0xf0f0f0>; + dai-tdm-slot-tx-idle-mask-1 = <0x0f0f0f>; + + cpu { + sound-dai = <&aop_audio 3>; + }; + + codec { + sound-dai = <&speaker_left_woof1>, <&speaker_right_woof1>, + <&speaker_left_woof2>, <&speaker_right_woof2>, + <&speaker_left_tweet>, <&speaker_right_tweet>; + }; + }; + }; + + /* + * The headphone jack: the AOP audio component's jack back-end (DAI 4) + * to the CS42L84. A card of its own, so a codec that does not answer + * costs the jack only, never the speakers. + */ + sound_jack: sound-jack { + compatible = "apple,j616-macaudio-jack", "apple,macaudio"; + model = "MacBook Pro J616 Jack"; + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&aop_audio 4>; + }; + + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + +&cpuidle { + status = "okay"; +}; + +&smc { + status = "okay"; +}; + +&smc_mbox { + status = "okay"; +}; + +&nub_spmi0 { + status = "okay"; +}; + +&pinctrl { + status = "okay"; + + wifi_clkreq_pins: wifi-clkreq-pins { + pinmux = ; + }; +}; + +/* + * The AP takes none of the always-on bank's interrupts: the AOP firmware + * owns some of its pins and interrupt registers, and a chained handler here + * would see their pending bits without a handler of ours. + */ +&pinctrl_nub { + status = "okay"; + /delete-property/ interrupt-controller; + /delete-property/ #interrupt-cells; + /delete-property/ interrupts; +}; + +&fpwm0 { + status = "okay"; +}; + +/* apple_sep enables the SEP and its DART when it loads. */ +&sep_mbox { + status = "okay"; +}; + +/* + * Touch ID sensor (Mesa 0x3356). Its power is NUB GPIO 53 and its + * data-ready line is AP GPIO 116, as the J616s ADT describes them. + */ +&spi2 { + status = "okay"; +}; + +&mesa { + status = "okay"; + enable-gpios = <&pinctrl_nub 53 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl>; + interrupts = <116 IRQ_TYPE_EDGE_RISING>; + spi-cs-setup-delay-ns = <20>; + spi-cs-hold-delay-ns = <20>; + /* + * Per-device factory data from this Mac's own iBoot system container: + * the sealed calibration and the sealed root key. + */ + firmware-name = "apple/mesacal-j616s.bin", "apple/msrk-j616s.bin"; +}; + +/* The Type-C retimers (ADT atcrt at 0x18/0x19/0x1a) sit on this bus; no driver yet. */ +&i2c6 { + status = "okay"; +}; + +&mtp { + status = "okay"; +}; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + /* SMC key gp1c powers the trackpad analog front end. */ + apple,afe-smc = <&smc>; + + multi-touch { + firmware-name = "apple/tpmtfw-j616s.bin"; + }; + + keyboard: keyboard { + hid-country-code = <0>; + apple,keyboard-layout-id = <2>; + }; + + stm { + }; + + actuator { + }; + + tp-accel { + }; + + mtp { + }; +}; + +&smc_hwmon { + /* + * TCMz and the 23 zone sensors after it must stay the first + * temperature children: the thermal zones name a sensor by its index, + * so the shared hwmon includes below only append after them. + * + * Every key below is in this machine's SMC key table as a readable + * float and was read on the J616s (SMC firmware of macOS 26.6.2). + * Keys that only ever read a fixed placeholder (Tz1*, Ta0*) or a + * fixed limit (TVMx, TVmS, TVms, PHPB, PHPM) are left out; rails that + * read 0 W while idle are kept. GPU (Tg*), P-cluster (Tp*) and + * E-cluster (Te*) labels follow load tests: Tg* only read live while + * the GPU is powered and rise with GPU load, Tp* and Te* rise with CPU + * load. Other names follow the SMC key groups and are not otherwise + * verified. SBA1-3 are the three cells of the 3S battery. + */ + temperature-TCMz { + apple,key-id = "TCMz"; + label = "CPU Die Max"; + }; + + temperature-Tex0 { + apple,key-id = "Tex0"; + label = "E-cluster Tex0"; + }; + + temperature-Tex1 { + apple,key-id = "Tex1"; + label = "E-cluster Tex1"; + }; + + temperature-Tex2 { + apple,key-id = "Tex2"; + label = "E-cluster Tex2"; + }; + + temperature-Tex3 { + apple,key-id = "Tex3"; + label = "E-cluster Tex3"; + }; + + temperature-Tpx0 { + apple,key-id = "Tpx0"; + label = "P-cluster Tpx0"; + }; + + temperature-Tpx1 { + apple,key-id = "Tpx1"; + label = "P-cluster Tpx1"; + }; + + temperature-Tpx2 { + apple,key-id = "Tpx2"; + label = "P-cluster Tpx2"; + }; + + temperature-Tpx3 { + apple,key-id = "Tpx3"; + label = "P-cluster Tpx3"; + }; + + temperature-Tpx4 { + apple,key-id = "Tpx4"; + label = "P-cluster Tpx4"; + }; + + temperature-Tpx5 { + apple,key-id = "Tpx5"; + label = "P-cluster Tpx5"; + }; + + temperature-Tpx8 { + apple,key-id = "Tpx8"; + label = "P-cluster Tpx8"; + }; + + temperature-Tpx9 { + apple,key-id = "Tpx9"; + label = "P-cluster Tpx9"; + }; + + temperature-TpxA { + apple,key-id = "TpxA"; + label = "P-cluster TpxA"; + }; + + temperature-TpxB { + apple,key-id = "TpxB"; + label = "P-cluster TpxB"; + }; + + temperature-TpxC { + apple,key-id = "TpxC"; + label = "P-cluster TpxC"; + }; + + temperature-TpxD { + apple,key-id = "TpxD"; + label = "P-cluster TpxD"; + }; + + temperature-Tg05 { + apple,key-id = "Tg05"; + label = "GPU Tg05"; + }; + + temperature-Tg0S { + apple,key-id = "Tg0S"; + label = "GPU Tg0S"; + }; + + temperature-Tg0Y { + apple,key-id = "Tg0Y"; + label = "GPU Tg0Y"; + }; + + temperature-Tg0k { + apple,key-id = "Tg0k"; + label = "GPU Tg0k"; + }; + + temperature-Tg0z { + apple,key-id = "Tg0z"; + label = "GPU Tg0z"; + }; + + temperature-Tg1V { + apple,key-id = "Tg1V"; + label = "GPU Tg1V"; + }; + + temperature-Tg1l { + apple,key-id = "Tg1l"; + label = "GPU Tg1l"; + }; + + temperature-TAOL { + apple,key-id = "TAOL"; + label = "SMC TAOL"; + }; + + temperature-TB1T { + apple,key-id = "TB1T"; + label = "Battery 1"; + }; + + temperature-TB2T { + apple,key-id = "TB2T"; + label = "Battery 2"; + }; + + temperature-TCMb { + apple,key-id = "TCMb"; + label = "SMC TCMb"; + }; + + temperature-TD00 { + apple,key-id = "TD00"; + label = "SMC TD00"; + }; + + temperature-TD01 { + apple,key-id = "TD01"; + label = "SMC TD01"; + }; + + temperature-TD02 { + apple,key-id = "TD02"; + label = "SMC TD02"; + }; + + temperature-TD03 { + apple,key-id = "TD03"; + label = "SMC TD03"; + }; + + temperature-TD04 { + apple,key-id = "TD04"; + label = "SMC TD04"; + }; + + temperature-TD10 { + apple,key-id = "TD10"; + label = "SMC TD10"; + }; + + temperature-TD11 { + apple,key-id = "TD11"; + label = "SMC TD11"; + }; + + temperature-TD12 { + apple,key-id = "TD12"; + label = "SMC TD12"; + }; + + temperature-TD13 { + apple,key-id = "TD13"; + label = "SMC TD13"; + }; + + temperature-TD14 { + apple,key-id = "TD14"; + label = "SMC TD14"; + }; + + temperature-TD20 { + apple,key-id = "TD20"; + label = "SMC TD20"; + }; + + temperature-TD21 { + apple,key-id = "TD21"; + label = "SMC TD21"; + }; + + temperature-TD22 { + apple,key-id = "TD22"; + label = "SMC TD22"; + }; + + temperature-TD23 { + apple,key-id = "TD23"; + label = "SMC TD23"; + }; + + temperature-TD24 { + apple,key-id = "TD24"; + label = "SMC TD24"; + }; + + temperature-TDBP { + apple,key-id = "TDBP"; + label = "SMC TDBP"; + }; + + temperature-TDCR { + apple,key-id = "TDCR"; + label = "SMC TDCR"; + }; + + temperature-TDEC { + apple,key-id = "TDEC"; + label = "SMC TDEC"; + }; + + temperature-TDEL { + apple,key-id = "TDEL"; + label = "SMC TDEL"; + }; + + temperature-TDER { + apple,key-id = "TDER"; + label = "SMC TDER"; + }; + + temperature-TDTP { + apple,key-id = "TDTP"; + label = "SMC TDTP"; + }; + + temperature-TDVx { + apple,key-id = "TDVx"; + label = "SMC TDVx"; + }; + + temperature-TDeL { + apple,key-id = "TDeL"; + label = "SMC TDeL"; + }; + + temperature-TDeR { + apple,key-id = "TDeR"; + label = "SMC TDeR"; + }; + + temperature-TF2S { + apple,key-id = "TF2S"; + label = "SMC TF2S"; + }; + + temperature-TH0a { + apple,key-id = "TH0a"; + label = "SMC TH0a"; + }; + + temperature-TH0b { + apple,key-id = "TH0b"; + label = "SMC TH0b"; + }; + + temperature-TMVR { + apple,key-id = "TMVR"; + label = "SMC TMVR"; + }; + + temperature-TPD0 { + apple,key-id = "TPD0"; + label = "SMC TPD0"; + }; + + temperature-TPD1 { + apple,key-id = "TPD1"; + label = "SMC TPD1"; + }; + + temperature-TPD2 { + apple,key-id = "TPD2"; + label = "SMC TPD2"; + }; + + temperature-TPD3 { + apple,key-id = "TPD3"; + label = "SMC TPD3"; + }; + + temperature-TPD4 { + apple,key-id = "TPD4"; + label = "SMC TPD4"; + }; + + temperature-TPD5 { + apple,key-id = "TPD5"; + label = "SMC TPD5"; + }; + + temperature-TPD6 { + apple,key-id = "TPD6"; + label = "SMC TPD6"; + }; + + temperature-TPD7 { + apple,key-id = "TPD7"; + label = "SMC TPD7"; + }; + + temperature-TPD8 { + apple,key-id = "TPD8"; + label = "SMC TPD8"; + }; + + temperature-TPD9 { + apple,key-id = "TPD9"; + label = "SMC TPD9"; + }; + + temperature-TPDX { + apple,key-id = "TPDX"; + label = "SMC TPDX"; + }; + + temperature-TPDa { + apple,key-id = "TPDa"; + label = "SMC TPDa"; + }; + + temperature-TPDb { + apple,key-id = "TPDb"; + label = "SMC TPDb"; + }; + + temperature-TPDc { + apple,key-id = "TPDc"; + label = "SMC TPDc"; + }; + + temperature-TPDd { + apple,key-id = "TPDd"; + label = "SMC TPDd"; + }; + + temperature-TPDe { + apple,key-id = "TPDe"; + label = "SMC TPDe"; + }; + + temperature-TPDf { + apple,key-id = "TPDf"; + label = "SMC TPDf"; + }; + + temperature-TPH3 { + apple,key-id = "TPH3"; + label = "SMC TPH3"; + }; + + temperature-TPMP { + apple,key-id = "TPMP"; + label = "SMC TPMP"; + }; + + temperature-TPSP { + apple,key-id = "TPSP"; + label = "SMC TPSP"; + }; + + temperature-TRD0 { + apple,key-id = "TRD0"; + label = "SMC TRD0"; + }; + + temperature-TRD1 { + apple,key-id = "TRD1"; + label = "SMC TRD1"; + }; + + temperature-TRD2 { + apple,key-id = "TRD2"; + label = "SMC TRD2"; + }; + + temperature-TRD3 { + apple,key-id = "TRD3"; + label = "SMC TRD3"; + }; + + temperature-TRD4 { + apple,key-id = "TRD4"; + label = "SMC TRD4"; + }; + + temperature-TRD5 { + apple,key-id = "TRD5"; + label = "SMC TRD5"; + }; + + temperature-TRD6 { + apple,key-id = "TRD6"; + label = "SMC TRD6"; + }; + + temperature-TRD7 { + apple,key-id = "TRD7"; + label = "SMC TRD7"; + }; + + temperature-TRD8 { + apple,key-id = "TRD8"; + label = "SMC TRD8"; + }; + + temperature-TRD9 { + apple,key-id = "TRD9"; + label = "SMC TRD9"; + }; + + temperature-TRDX { + apple,key-id = "TRDX"; + label = "SMC TRDX"; + }; + + temperature-TRDa { + apple,key-id = "TRDa"; + label = "SMC TRDa"; + }; + + temperature-TRDb { + apple,key-id = "TRDb"; + label = "SMC TRDb"; + }; + + temperature-TRDc { + apple,key-id = "TRDc"; + label = "SMC TRDc"; + }; + + temperature-TRDd { + apple,key-id = "TRDd"; + label = "SMC TRDd"; + }; + + temperature-TRDe { + apple,key-id = "TRDe"; + label = "SMC TRDe"; + }; + + temperature-TRDf { + apple,key-id = "TRDf"; + label = "SMC TRDf"; + }; + + temperature-TS0P { + apple,key-id = "TS0P"; + label = "SMC TS0P"; + }; + + temperature-TSG1 { + apple,key-id = "TSG1"; + label = "SMC TSG1"; + }; + + temperature-TUD0 { + apple,key-id = "TUD0"; + label = "SMC TUD0"; + }; + + temperature-TUD1 { + apple,key-id = "TUD1"; + label = "SMC TUD1"; + }; + + temperature-TUD2 { + apple,key-id = "TUD2"; + label = "SMC TUD2"; + }; + + temperature-TUD3 { + apple,key-id = "TUD3"; + label = "SMC TUD3"; + }; + + temperature-TUD4 { + apple,key-id = "TUD4"; + label = "SMC TUD4"; + }; + + temperature-TUD5 { + apple,key-id = "TUD5"; + label = "SMC TUD5"; + }; + + temperature-TUD6 { + apple,key-id = "TUD6"; + label = "SMC TUD6"; + }; + + temperature-TUD7 { + apple,key-id = "TUD7"; + label = "SMC TUD7"; + }; + + temperature-TUD8 { + apple,key-id = "TUD8"; + label = "SMC TUD8"; + }; + + temperature-TUD9 { + apple,key-id = "TUD9"; + label = "SMC TUD9"; + }; + + temperature-TUDX { + apple,key-id = "TUDX"; + label = "SMC TUDX"; + }; + + temperature-TUDa { + apple,key-id = "TUDa"; + label = "SMC TUDa"; + }; + + temperature-TUDb { + apple,key-id = "TUDb"; + label = "SMC TUDb"; + }; + + temperature-TUDc { + apple,key-id = "TUDc"; + label = "SMC TUDc"; + }; + + temperature-TUDd { + apple,key-id = "TUDd"; + label = "SMC TUDd"; + }; + + temperature-TUDe { + apple,key-id = "TUDe"; + label = "SMC TUDe"; + }; + + temperature-TUDf { + apple,key-id = "TUDf"; + label = "SMC TUDf"; + }; + + temperature-TVA0 { + apple,key-id = "TVA0"; + label = "SMC TVA0"; + }; + + temperature-TVD0 { + apple,key-id = "TVD0"; + label = "SMC TVD0"; + }; + + temperature-TVMR { + apple,key-id = "TVMR"; + label = "SMC TVMR"; + }; + + temperature-TVMX { + apple,key-id = "TVMX"; + label = "SMC TVMX"; + }; + + temperature-TVMr { + apple,key-id = "TVMr"; + label = "SMC TVMr"; + }; + + temperature-TVS0 { + apple,key-id = "TVS0"; + label = "SMC TVS0"; + }; + + temperature-TVS1 { + apple,key-id = "TVS1"; + label = "SMC TVS1"; + }; + + temperature-TVS2 { + apple,key-id = "TVS2"; + label = "SMC TVS2"; + }; + + temperature-TVV0 { + apple,key-id = "TVV0"; + label = "SMC TVV0"; + }; + + temperature-TVXX { + apple,key-id = "TVXX"; + label = "SMC TVXX"; + }; + + temperature-TVXh { + apple,key-id = "TVXh"; + label = "SMC TVXh"; + }; + + temperature-TVXm { + apple,key-id = "TVXm"; + label = "SMC TVXm"; + }; + + temperature-TVh0 { + apple,key-id = "TVh0"; + label = "SMC TVh0"; + }; + + temperature-TVh1 { + apple,key-id = "TVh1"; + label = "SMC TVh1"; + }; + + temperature-TVm0 { + apple,key-id = "TVm0"; + label = "SMC TVm0"; + }; + + temperature-TVxx { + apple,key-id = "TVxx"; + label = "SMC TVxx"; + }; + + temperature-TaLP { + apple,key-id = "TaLP"; + label = "SMC TaLP"; + }; + + temperature-TaLT { + apple,key-id = "TaLT"; + label = "SMC TaLT"; + }; + + temperature-TaLW { + apple,key-id = "TaLW"; + label = "SMC TaLW"; + }; + + temperature-TaRF { + apple,key-id = "TaRF"; + label = "SMC TaRF"; + }; + + temperature-TaRT { + apple,key-id = "TaRT"; + label = "SMC TaRT"; + }; + + temperature-TaRW { + apple,key-id = "TaRW"; + label = "SMC TaRW"; + }; + + temperature-TaTP { + apple,key-id = "TaTP"; + label = "SMC TaTP"; + }; + + temperature-Te04 { + apple,key-id = "Te04"; + label = "E-cluster Te04"; + }; + + temperature-Te05 { + apple,key-id = "Te05"; + label = "E-cluster Te05"; + }; + + temperature-Te06 { + apple,key-id = "Te06"; + label = "E-cluster Te06"; + }; + + temperature-Te0R { + apple,key-id = "Te0R"; + label = "E-cluster Te0R"; + }; + + temperature-Te0S { + apple,key-id = "Te0S"; + label = "E-cluster Te0S"; + }; + + temperature-Te0T { + apple,key-id = "Te0T"; + label = "E-cluster Te0T"; + }; + + temperature-TfC0 { + apple,key-id = "TfC0"; + label = "SMC TfC0"; + }; + + temperature-TfC1 { + apple,key-id = "TfC1"; + label = "SMC TfC1"; + }; + + temperature-TfC2 { + apple,key-id = "TfC2"; + label = "SMC TfC2"; + }; + + temperature-TfC3 { + apple,key-id = "TfC3"; + label = "SMC TfC3"; + }; + + temperature-TfC4 { + apple,key-id = "TfC4"; + label = "SMC TfC4"; + }; + + temperature-Tg04 { + apple,key-id = "Tg04"; + label = "GPU Tg04"; + }; + + temperature-Tg0K { + apple,key-id = "Tg0K"; + label = "GPU Tg0K"; + }; + + temperature-Tg0L { + apple,key-id = "Tg0L"; + label = "GPU Tg0L"; + }; + + temperature-Tg0R { + apple,key-id = "Tg0R"; + label = "GPU Tg0R"; + }; + + temperature-Tg0X { + apple,key-id = "Tg0X"; + label = "GPU Tg0X"; + }; + + temperature-Tg0d { + apple,key-id = "Tg0d"; + label = "GPU Tg0d"; + }; + + temperature-Tg0e { + apple,key-id = "Tg0e"; + label = "GPU Tg0e"; + }; + + temperature-Tg0j { + apple,key-id = "Tg0j"; + label = "GPU Tg0j"; + }; + + temperature-Tg0y { + apple,key-id = "Tg0y"; + label = "GPU Tg0y"; + }; + + temperature-Tg1E { + apple,key-id = "Tg1E"; + label = "GPU Tg1E"; + }; + + temperature-Tg1F { + apple,key-id = "Tg1F"; + label = "GPU Tg1F"; + }; + + temperature-Tg1U { + apple,key-id = "Tg1U"; + label = "GPU Tg1U"; + }; + + temperature-Tg1c { + apple,key-id = "Tg1c"; + label = "GPU Tg1c"; + }; + + temperature-Tg1d { + apple,key-id = "Tg1d"; + label = "GPU Tg1d"; + }; + + temperature-Tg1k { + apple,key-id = "Tg1k"; + label = "GPU Tg1k"; + }; + + temperature-Tp00 { + apple,key-id = "Tp00"; + label = "P-cluster Tp00"; + }; + + temperature-Tp01 { + apple,key-id = "Tp01"; + label = "P-cluster Tp01"; + }; + + temperature-Tp02 { + apple,key-id = "Tp02"; + label = "P-cluster Tp02"; + }; + + temperature-Tp04 { + apple,key-id = "Tp04"; + label = "P-cluster Tp04"; + }; + + temperature-Tp05 { + apple,key-id = "Tp05"; + label = "P-cluster Tp05"; + }; + + temperature-Tp06 { + apple,key-id = "Tp06"; + label = "P-cluster Tp06"; + }; + + temperature-Tp08 { + apple,key-id = "Tp08"; + label = "P-cluster Tp08"; + }; + + temperature-Tp09 { + apple,key-id = "Tp09"; + label = "P-cluster Tp09"; + }; + + temperature-Tp0A { + apple,key-id = "Tp0A"; + label = "P-cluster Tp0A"; + }; + + temperature-Tp0C { + apple,key-id = "Tp0C"; + label = "P-cluster Tp0C"; + }; + + temperature-Tp0D { + apple,key-id = "Tp0D"; + label = "P-cluster Tp0D"; + }; + + temperature-Tp0E { + apple,key-id = "Tp0E"; + label = "P-cluster Tp0E"; + }; + + temperature-Tp0G { + apple,key-id = "Tp0G"; + label = "P-cluster Tp0G"; + }; + + temperature-Tp0H { + apple,key-id = "Tp0H"; + label = "P-cluster Tp0H"; + }; + + temperature-Tp0I { + apple,key-id = "Tp0I"; + label = "P-cluster Tp0I"; + }; + + temperature-Tp0K { + apple,key-id = "Tp0K"; + label = "P-cluster Tp0K"; + }; + + temperature-Tp0L { + apple,key-id = "Tp0L"; + label = "P-cluster Tp0L"; + }; + + temperature-Tp0M { + apple,key-id = "Tp0M"; + label = "P-cluster Tp0M"; + }; + + temperature-Tp0O { + apple,key-id = "Tp0O"; + label = "P-cluster Tp0O"; + }; + + temperature-Tp0P { + apple,key-id = "Tp0P"; + label = "P-cluster Tp0P"; + }; + + temperature-Tp0Q { + apple,key-id = "Tp0Q"; + label = "P-cluster Tp0Q"; + }; + + temperature-Tp0S { + apple,key-id = "Tp0S"; + label = "P-cluster Tp0S"; + }; + + temperature-Tp0T { + apple,key-id = "Tp0T"; + label = "P-cluster Tp0T"; + }; + + temperature-Tp0U { + apple,key-id = "Tp0U"; + label = "P-cluster Tp0U"; + }; + + temperature-Tp0W { + apple,key-id = "Tp0W"; + label = "P-cluster Tp0W"; + }; + + temperature-Tp0X { + apple,key-id = "Tp0X"; + label = "P-cluster Tp0X"; + }; + + temperature-Tp0Y { + apple,key-id = "Tp0Y"; + label = "P-cluster Tp0Y"; + }; + + temperature-Tp0a { + apple,key-id = "Tp0a"; + label = "P-cluster Tp0a"; + }; + + temperature-Tp0b { + apple,key-id = "Tp0b"; + label = "P-cluster Tp0b"; + }; + + temperature-Tp0c { + apple,key-id = "Tp0c"; + label = "P-cluster Tp0c"; + }; + + temperature-Tp0d { + apple,key-id = "Tp0d"; + label = "P-cluster Tp0d"; + }; + + temperature-Tp0e { + apple,key-id = "Tp0e"; + label = "P-cluster Tp0e"; + }; + + temperature-Tp0f { + apple,key-id = "Tp0f"; + label = "P-cluster Tp0f"; + }; + + temperature-Tp0g { + apple,key-id = "Tp0g"; + label = "P-cluster Tp0g"; + }; + + temperature-Tp0i { + apple,key-id = "Tp0i"; + label = "P-cluster Tp0i"; + }; + + temperature-Tp0k { + apple,key-id = "Tp0k"; + label = "P-cluster Tp0k"; + }; + + temperature-Tp0l { + apple,key-id = "Tp0l"; + label = "P-cluster Tp0l"; + }; + + temperature-Tp0m { + apple,key-id = "Tp0m"; + label = "P-cluster Tp0m"; + }; + + temperature-Tp0n { + apple,key-id = "Tp0n"; + label = "P-cluster Tp0n"; + }; + + temperature-Tp0o { + apple,key-id = "Tp0o"; + label = "P-cluster Tp0o"; + }; + + temperature-Tp0q { + apple,key-id = "Tp0q"; + label = "P-cluster Tp0q"; + }; + + temperature-Tp0t { + apple,key-id = "Tp0t"; + label = "P-cluster Tp0t"; + }; + + temperature-Tp0u { + apple,key-id = "Tp0u"; + label = "P-cluster Tp0u"; + }; + + temperature-Tp0v { + apple,key-id = "Tp0v"; + label = "P-cluster Tp0v"; + }; + + temperature-Tp0w { + apple,key-id = "Tp0w"; + label = "P-cluster Tp0w"; + }; + + temperature-Tp1i { + apple,key-id = "Tp1i"; + label = "P-cluster Tp1i"; + }; + + temperature-Tp1j { + apple,key-id = "Tp1j"; + label = "P-cluster Tp1j"; + }; + + temperature-Tp1k { + apple,key-id = "Tp1k"; + label = "P-cluster Tp1k"; + }; + + temperature-Tp1m { + apple,key-id = "Tp1m"; + label = "P-cluster Tp1m"; + }; + + temperature-Tp1n { + apple,key-id = "Tp1n"; + label = "P-cluster Tp1n"; + }; + + temperature-Tp1o { + apple,key-id = "Tp1o"; + label = "P-cluster Tp1o"; + }; + + temperature-Tp1q { + apple,key-id = "Tp1q"; + label = "P-cluster Tp1q"; + }; + + temperature-Tp1t { + apple,key-id = "Tp1t"; + label = "P-cluster Tp1t"; + }; + + temperature-Tp1u { + apple,key-id = "Tp1u"; + label = "P-cluster Tp1u"; + }; + + temperature-Tp1v { + apple,key-id = "Tp1v"; + label = "P-cluster Tp1v"; + }; + + temperature-Tp1w { + apple,key-id = "Tp1w"; + label = "P-cluster Tp1w"; + }; + + temperature-Tp1x { + apple,key-id = "Tp1x"; + label = "P-cluster Tp1x"; + }; + + temperature-Tp1y { + apple,key-id = "Tp1y"; + label = "P-cluster Tp1y"; + }; + + temperature-Tp1z { + apple,key-id = "Tp1z"; + label = "P-cluster Tp1z"; + }; + + temperature-Tp20 { + apple,key-id = "Tp20"; + label = "P-cluster Tp20"; + }; + + temperature-Tp21 { + apple,key-id = "Tp21"; + label = "P-cluster Tp21"; + }; + + temperature-Tp22 { + apple,key-id = "Tp22"; + label = "P-cluster Tp22"; + }; + + temperature-Tp23 { + apple,key-id = "Tp23"; + label = "P-cluster Tp23"; + }; + + temperature-Tp24 { + apple,key-id = "Tp24"; + label = "P-cluster Tp24"; + }; + + temperature-Tp25 { + apple,key-id = "Tp25"; + label = "P-cluster Tp25"; + }; + + temperature-Tp26 { + apple,key-id = "Tp26"; + label = "P-cluster Tp26"; + }; + + temperature-Tp27 { + apple,key-id = "Tp27"; + label = "P-cluster Tp27"; + }; + + temperature-Tp28 { + apple,key-id = "Tp28"; + label = "P-cluster Tp28"; + }; + + temperature-Tp29 { + apple,key-id = "Tp29"; + label = "P-cluster Tp29"; + }; + + temperature-Tp2A { + apple,key-id = "Tp2A"; + label = "P-cluster Tp2A"; + }; + + temperature-Tp2B { + apple,key-id = "Tp2B"; + label = "P-cluster Tp2B"; + }; + + temperature-Tp2C { + apple,key-id = "Tp2C"; + label = "P-cluster Tp2C"; + }; + + temperature-Tp2D { + apple,key-id = "Tp2D"; + label = "P-cluster Tp2D"; + }; + + temperature-Tp2E { + apple,key-id = "Tp2E"; + label = "P-cluster Tp2E"; + }; + + temperature-Tp2G { + apple,key-id = "Tp2G"; + label = "P-cluster Tp2G"; + }; + + temperature-Tp2I { + apple,key-id = "Tp2I"; + label = "P-cluster Tp2I"; + }; + + temperature-Tp2J { + apple,key-id = "Tp2J"; + label = "P-cluster Tp2J"; + }; + + temperature-Tp2K { + apple,key-id = "Tp2K"; + label = "P-cluster Tp2K"; + }; + + temperature-Tp2L { + apple,key-id = "Tp2L"; + label = "P-cluster Tp2L"; + }; + + temperature-Tp2M { + apple,key-id = "Tp2M"; + label = "P-cluster Tp2M"; + }; + + temperature-Tp2N { + apple,key-id = "Tp2N"; + label = "P-cluster Tp2N"; + }; + + temperature-Tp2O { + apple,key-id = "Tp2O"; + label = "P-cluster Tp2O"; + }; + + temperature-Tp2Q { + apple,key-id = "Tp2Q"; + label = "P-cluster Tp2Q"; + }; + + temperature-Tp2R { + apple,key-id = "Tp2R"; + label = "P-cluster Tp2R"; + }; + + temperature-Tp2S { + apple,key-id = "Tp2S"; + label = "P-cluster Tp2S"; + }; + + temperature-Tp2T { + apple,key-id = "Tp2T"; + label = "P-cluster Tp2T"; + }; + + temperature-Tp2U { + apple,key-id = "Tp2U"; + label = "P-cluster Tp2U"; + }; + + temperature-Tp2V { + apple,key-id = "Tp2V"; + label = "P-cluster Tp2V"; + }; + + temperature-Tp2W { + apple,key-id = "Tp2W"; + label = "P-cluster Tp2W"; + }; + + temperature-Tp2X { + apple,key-id = "Tp2X"; + label = "P-cluster Tp2X"; + }; + + temperature-Ts00 { + apple,key-id = "Ts00"; + label = "SMC Ts00"; + }; + + temperature-Ts01 { + apple,key-id = "Ts01"; + label = "SMC Ts01"; + }; + + temperature-Ts02 { + apple,key-id = "Ts02"; + label = "SMC Ts02"; + }; + + temperature-Ts04 { + apple,key-id = "Ts04"; + label = "SMC Ts04"; + }; + + temperature-Ts05 { + apple,key-id = "Ts05"; + label = "SMC Ts05"; + }; + + temperature-Ts06 { + apple,key-id = "Ts06"; + label = "SMC Ts06"; + }; + + temperature-Ts08 { + apple,key-id = "Ts08"; + label = "SMC Ts08"; + }; + + temperature-Ts09 { + apple,key-id = "Ts09"; + label = "SMC Ts09"; + }; + + temperature-Ts0A { + apple,key-id = "Ts0A"; + label = "SMC Ts0A"; + }; + + temperature-Ts0C { + apple,key-id = "Ts0C"; + label = "SMC Ts0C"; + }; + + temperature-Ts0D { + apple,key-id = "Ts0D"; + label = "SMC Ts0D"; + }; + + temperature-Ts0E { + apple,key-id = "Ts0E"; + label = "SMC Ts0E"; + }; + + temperature-Ts0G { + apple,key-id = "Ts0G"; + label = "SMC Ts0G"; + }; + + temperature-Ts0H { + apple,key-id = "Ts0H"; + label = "SMC Ts0H"; + }; + + temperature-Ts0I { + apple,key-id = "Ts0I"; + label = "SMC Ts0I"; + }; + + temperature-Ts0K { + apple,key-id = "Ts0K"; + label = "SMC Ts0K"; + }; + + temperature-Ts0L { + apple,key-id = "Ts0L"; + label = "SMC Ts0L"; + }; + + temperature-Ts0M { + apple,key-id = "Ts0M"; + label = "SMC Ts0M"; + }; + + temperature-Ts0O { + apple,key-id = "Ts0O"; + label = "SMC Ts0O"; + }; + + temperature-Ts0P { + apple,key-id = "Ts0P"; + label = "SMC Ts0P"; + }; + + temperature-Ts0Q { + apple,key-id = "Ts0Q"; + label = "SMC Ts0Q"; + }; + + temperature-Ts0R { + apple,key-id = "Ts0R"; + label = "SMC Ts0R"; + }; + + temperature-Ts0T { + apple,key-id = "Ts0T"; + label = "SMC Ts0T"; + }; + + temperature-Ts0U { + apple,key-id = "Ts0U"; + label = "SMC Ts0U"; + }; + + temperature-Ts0W { + apple,key-id = "Ts0W"; + label = "SMC Ts0W"; + }; + + temperature-Ts1P { + apple,key-id = "Ts1P"; + label = "SMC Ts1P"; + }; + + temperature-Tsx0 { + apple,key-id = "Tsx0"; + label = "SMC Tsx0"; + }; + + temperature-Tsx1 { + apple,key-id = "Tsx1"; + label = "SMC Tsx1"; + }; + + voltage-VDM2 { + apple,key-id = "VDM2"; + label = "SMC VDM2"; + }; + + voltage-VDMA { + apple,key-id = "VDMA"; + label = "SMC VDMA"; + }; + + voltage-VDMM { + apple,key-id = "VDMM"; + label = "SMC VDMM"; + }; + + voltage-VP0R { + apple,key-id = "VP0R"; + label = "SMC VP0R"; + }; + + voltage-VP0b { + apple,key-id = "VP0b"; + label = "SMC VP0b"; + }; + + voltage-VP1b { + apple,key-id = "VP1b"; + label = "SMC VP1b"; + }; + + voltage-VP1l { + apple,key-id = "VP1l"; + label = "SMC VP1l"; + }; + + voltage-VP2b { + apple,key-id = "VP2b"; + label = "SMC VP2b"; + }; + + voltage-VP3b { + apple,key-id = "VP3b"; + label = "SMC VP3b"; + }; + + voltage-VP4b { + apple,key-id = "VP4b"; + label = "SMC VP4b"; + }; + + voltage-VP5l { + apple,key-id = "VP5l"; + label = "SMC VP5l"; + }; + + voltage-VP7b { + apple,key-id = "VP7b"; + label = "SMC VP7b"; + }; + + voltage-VP7l { + apple,key-id = "VP7l"; + label = "SMC VP7l"; + }; + + voltage-VP9b { + apple,key-id = "VP9b"; + label = "SMC VP9b"; + }; + + voltage-VP9l { + apple,key-id = "VP9l"; + label = "SMC VP9l"; + }; + + voltage-VPal { + apple,key-id = "VPal"; + label = "SMC VPal"; + }; + + voltage-VPbb { + apple,key-id = "VPbb"; + label = "SMC VPbb"; + }; + + voltage-VPdb { + apple,key-id = "VPdb"; + label = "SMC VPdb"; + }; + + voltage-VPeb { + apple,key-id = "VPeb"; + label = "SMC VPeb"; + }; + + voltage-VR0b { + apple,key-id = "VR0b"; + label = "SMC VR0b"; + }; + + voltage-VR1b { + apple,key-id = "VR1b"; + label = "SMC VR1b"; + }; + + voltage-VR2b { + apple,key-id = "VR2b"; + label = "SMC VR2b"; + }; + + voltage-VR3b { + apple,key-id = "VR3b"; + label = "SMC VR3b"; + }; + + voltage-VR3l { + apple,key-id = "VR3l"; + label = "SMC VR3l"; + }; + + voltage-VR4b { + apple,key-id = "VR4b"; + label = "SMC VR4b"; + }; + + voltage-VR7b { + apple,key-id = "VR7b"; + label = "SMC VR7b"; + }; + + voltage-VR8l { + apple,key-id = "VR8l"; + label = "SMC VR8l"; + }; + + voltage-VR9b { + apple,key-id = "VR9b"; + label = "SMC VR9b"; + }; + + voltage-VR9l { + apple,key-id = "VR9l"; + label = "SMC VR9l"; + }; + + voltage-VRal { + apple,key-id = "VRal"; + label = "SMC VRal"; + }; + + voltage-VRbb { + apple,key-id = "VRbb"; + label = "SMC VRbb"; + }; + + voltage-VRdb { + apple,key-id = "VRdb"; + label = "SMC VRdb"; + }; + + voltage-VReb { + apple,key-id = "VReb"; + label = "SMC VReb"; + }; + + voltage-VS0b { + apple,key-id = "VS0b"; + label = "SMC VS0b"; + }; + + voltage-VS1b { + apple,key-id = "VS1b"; + label = "SMC VS1b"; + }; + + voltage-VS2b { + apple,key-id = "VS2b"; + label = "SMC VS2b"; + }; + + voltage-VS3b { + apple,key-id = "VS3b"; + label = "SMC VS3b"; + }; + + voltage-VS4b { + apple,key-id = "VS4b"; + label = "SMC VS4b"; + }; + + voltage-VS7b { + apple,key-id = "VS7b"; + label = "SMC VS7b"; + }; + + voltage-VS9b { + apple,key-id = "VS9b"; + label = "SMC VS9b"; + }; + + voltage-VS9l { + apple,key-id = "VS9l"; + label = "SMC VS9l"; + }; + + voltage-VSal { + apple,key-id = "VSal"; + label = "SMC VSal"; + }; + + voltage-VSbb { + apple,key-id = "VSbb"; + label = "SMC VSbb"; + }; + + voltage-VSeb { + apple,key-id = "VSeb"; + label = "SMC VSeb"; + }; + + voltage-Vb0f { + apple,key-id = "Vb0f"; + label = "SMC Vb0f"; + }; + + voltage-Vb1f { + apple,key-id = "Vb1f"; + label = "SMC Vb1f"; + }; + + voltage-SBA1 { + apple,key-id = "SBA1"; + label = "Battery Cell 1"; + }; + + voltage-SBA2 { + apple,key-id = "SBA2"; + label = "Battery Cell 2"; + }; + + voltage-SBA3 { + apple,key-id = "SBA3"; + label = "Battery Cell 3"; + }; + + current-I3F2 { + apple,key-id = "I3F2"; + label = "SMC I3F2"; + }; + + current-I5SR { + apple,key-id = "I5SR"; + label = "SMC I5SR"; + }; + + current-IALR { + apple,key-id = "IALR"; + label = "SMC IALR"; + }; + + current-IARR { + apple,key-id = "IARR"; + label = "SMC IARR"; + }; + + current-ICMC { + apple,key-id = "ICMC"; + label = "SMC ICMC"; + }; + + current-ID3C { + apple,key-id = "ID3C"; + label = "SMC ID3C"; + }; + + current-IDBC { + apple,key-id = "IDBC"; + label = "SMC IDBC"; + }; + + current-IDBR { + apple,key-id = "IDBR"; + label = "SMC IDBR"; + }; + + current-IFLR { + apple,key-id = "IFLR"; + label = "SMC IFLR"; + }; + + current-IH0R { + apple,key-id = "IH0R"; + label = "SMC IH0R"; + }; + + current-IHCR { + apple,key-id = "IHCR"; + label = "SMC IHCR"; + }; + + current-IIDR { + apple,key-id = "IIDR"; + label = "SMC IIDR"; + }; + + current-IINC { + apple,key-id = "IINC"; + label = "SMC IINC"; + }; + + current-IMVC { + apple,key-id = "IMVC"; + label = "SMC IMVC"; + }; + + current-IMVR { + apple,key-id = "IMVR"; + label = "SMC IMVR"; + }; + + current-IP0b { + apple,key-id = "IP0b"; + label = "SMC IP0b"; + }; + + current-IP1b { + apple,key-id = "IP1b"; + label = "SMC IP1b"; + }; + + current-IP1l { + apple,key-id = "IP1l"; + label = "SMC IP1l"; + }; + + current-IP2b { + apple,key-id = "IP2b"; + label = "SMC IP2b"; + }; + + current-IP3b { + apple,key-id = "IP3b"; + label = "SMC IP3b"; + }; + + current-IP4b { + apple,key-id = "IP4b"; + label = "SMC IP4b"; + }; + + current-IP5l { + apple,key-id = "IP5l"; + label = "SMC IP5l"; + }; + + current-IP7b { + apple,key-id = "IP7b"; + label = "SMC IP7b"; + }; + + current-IP7l { + apple,key-id = "IP7l"; + label = "SMC IP7l"; + }; + + current-IP9b { + apple,key-id = "IP9b"; + label = "SMC IP9b"; + }; + + current-IP9l { + apple,key-id = "IP9l"; + label = "SMC IP9l"; + }; + + current-IPBR { + apple,key-id = "IPBR"; + label = "SMC IPBR"; + }; + + current-IPMC { + apple,key-id = "IPMC"; + label = "SMC IPMC"; + }; + + current-IPSC { + apple,key-id = "IPSC"; + label = "SMC IPSC"; + }; + + current-IPal { + apple,key-id = "IPal"; + label = "SMC IPal"; + }; + + current-IPbb { + apple,key-id = "IPbb"; + label = "SMC IPbb"; + }; + + current-IPdb { + apple,key-id = "IPdb"; + label = "SMC IPdb"; + }; + + current-IPeb { + apple,key-id = "IPeb"; + label = "SMC IPeb"; + }; + + current-IR0b { + apple,key-id = "IR0b"; + label = "SMC IR0b"; + }; + + current-IR1b { + apple,key-id = "IR1b"; + label = "SMC IR1b"; + }; + + current-IR2b { + apple,key-id = "IR2b"; + label = "SMC IR2b"; + }; + + current-IR3b { + apple,key-id = "IR3b"; + label = "SMC IR3b"; + }; + + current-IR3l { + apple,key-id = "IR3l"; + label = "SMC IR3l"; + }; + + current-IR4b { + apple,key-id = "IR4b"; + label = "SMC IR4b"; + }; + + current-IR7b { + apple,key-id = "IR7b"; + label = "SMC IR7b"; + }; + + current-IR8l { + apple,key-id = "IR8l"; + label = "SMC IR8l"; + }; + + current-IR9b { + apple,key-id = "IR9b"; + label = "SMC IR9b"; + }; + + current-IR9l { + apple,key-id = "IR9l"; + label = "SMC IR9l"; + }; + + current-IRal { + apple,key-id = "IRal"; + label = "SMC IRal"; + }; + + current-IRbb { + apple,key-id = "IRbb"; + label = "SMC IRbb"; + }; + + current-IRdb { + apple,key-id = "IRdb"; + label = "SMC IRdb"; + }; + + current-IReb { + apple,key-id = "IReb"; + label = "SMC IReb"; + }; + + current-IS0b { + apple,key-id = "IS0b"; + label = "SMC IS0b"; + }; + + current-IS1b { + apple,key-id = "IS1b"; + label = "SMC IS1b"; + }; + + current-IS2b { + apple,key-id = "IS2b"; + label = "SMC IS2b"; + }; + + current-IS3b { + apple,key-id = "IS3b"; + label = "SMC IS3b"; + }; + + current-IS4b { + apple,key-id = "IS4b"; + label = "SMC IS4b"; + }; + + current-IS7b { + apple,key-id = "IS7b"; + label = "SMC IS7b"; + }; + + current-IS9b { + apple,key-id = "IS9b"; + label = "SMC IS9b"; + }; + + current-IS9l { + apple,key-id = "IS9l"; + label = "SMC IS9l"; + }; + + current-ISDR { + apple,key-id = "ISDR"; + label = "SMC ISDR"; + }; + + current-ISal { + apple,key-id = "ISal"; + label = "SMC ISal"; + }; + + current-ISbb { + apple,key-id = "ISbb"; + label = "SMC ISbb"; + }; + + current-ISeb { + apple,key-id = "ISeb"; + label = "SMC ISeb"; + }; + + current-IU1C { + apple,key-id = "IU1C"; + label = "SMC IU1C"; + }; + + current-IU1R { + apple,key-id = "IU1R"; + label = "SMC IU1R"; + }; + + current-IU2C { + apple,key-id = "IU2C"; + label = "SMC IU2C"; + }; + + current-IU2R { + apple,key-id = "IU2R"; + label = "SMC IU2R"; + }; + + current-IW3C { + apple,key-id = "IW3C"; + label = "SMC IW3C"; + }; + + current-Ib0f { + apple,key-id = "Ib0f"; + label = "SMC Ib0f"; + }; + + current-Ib8f { + apple,key-id = "Ib8f"; + label = "SMC Ib8f"; + }; + + current-SBAC { + apple,key-id = "SBAC"; + label = "Battery Current"; + }; + + power-P3F2 { + apple,key-id = "P3F2"; + label = "SMC P3F2"; + }; + + power-P5SR { + apple,key-id = "P5SR"; + label = "SMC P5SR"; + }; + + power-PALR { + apple,key-id = "PALR"; + label = "SMC PALR"; + }; + + power-PARR { + apple,key-id = "PARR"; + label = "SMC PARR"; + }; + + power-PCMC { + apple,key-id = "PCMC"; + label = "SMC PCMC"; + }; + + power-PD3C { + apple,key-id = "PD3C"; + label = "SMC PD3C"; + }; + + power-PDBC { + apple,key-id = "PDBC"; + label = "SMC PDBC"; + }; + + power-PDBR { + apple,key-id = "PDBR"; + label = "SMC PDBR"; + }; + + power-PFLR { + apple,key-id = "PFLR"; + label = "SMC PFLR"; + }; + + power-PH0R { + apple,key-id = "PH0R"; + label = "SMC PH0R"; + }; + + power-PHCR { + apple,key-id = "PHCR"; + label = "SMC PHCR"; + }; + + power-PIDR { + apple,key-id = "PIDR"; + label = "SMC PIDR"; + }; + + power-PINC { + apple,key-id = "PINC"; + label = "SMC PINC"; + }; + + power-PMVC { + apple,key-id = "PMVC"; + label = "SMC PMVC"; + }; + + power-PP0b { + apple,key-id = "PP0b"; + label = "SMC PP0b"; + }; + + power-PP1b { + apple,key-id = "PP1b"; + label = "SMC PP1b"; + }; + + power-PP1l { + apple,key-id = "PP1l"; + label = "SMC PP1l"; + }; + + power-PP2b { + apple,key-id = "PP2b"; + label = "SMC PP2b"; + }; + + power-PP3b { + apple,key-id = "PP3b"; + label = "SMC PP3b"; + }; + + power-PP4b { + apple,key-id = "PP4b"; + label = "SMC PP4b"; + }; + + power-PP5l { + apple,key-id = "PP5l"; + label = "SMC PP5l"; + }; + + power-PP7b { + apple,key-id = "PP7b"; + label = "SMC PP7b"; + }; + + power-PP7l { + apple,key-id = "PP7l"; + label = "SMC PP7l"; + }; + + power-PP9b { + apple,key-id = "PP9b"; + label = "SMC PP9b"; + }; + + power-PP9l { + apple,key-id = "PP9l"; + label = "SMC PP9l"; + }; + + power-PPBR { + apple,key-id = "PPBR"; + label = "SMC PPBR"; + }; + + power-PPMC { + apple,key-id = "PPMC"; + label = "SMC PPMC"; + }; + + power-PPSC { + apple,key-id = "PPSC"; + label = "SMC PPSC"; + }; + + power-PPal { + apple,key-id = "PPal"; + label = "SMC PPal"; + }; + + power-PPbb { + apple,key-id = "PPbb"; + label = "SMC PPbb"; + }; + + power-PPdb { + apple,key-id = "PPdb"; + label = "SMC PPdb"; + }; + + power-PPeb { + apple,key-id = "PPeb"; + label = "SMC PPeb"; + }; + + power-PR0b { + apple,key-id = "PR0b"; + label = "SMC PR0b"; }; - /* - * CPU die temperature: SMC key TCMz (CPU die max), sensor 0 of - * smc_hwmon. The PMP's fast die-temperature limit regulates (TCMz - * settles near 111 C); this zone is the backstop above it: passive at - * 113 C, critical (orderly shutdown) at 118 C, polled once a second as - * the SMC refreshes TCMz. The cpufreq cooling device sits on a - * cluster's policy CPU, so each map lists every CPU of its cluster. - */ - thermal-zones { - cpu-die-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - thermal-sensors = <&smc_hwmon 0>; /* TCMz */ + power-PR1b { + apple,key-id = "PR1b"; + label = "SMC PR1b"; + }; - trips { - cpu_die_passive: cpu-die-passive { - temperature = <113000>; - hysteresis = <5000>; - type = "passive"; - }; + power-PR2b { + apple,key-id = "PR2b"; + label = "SMC PR2b"; + }; - cpu-die-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; + power-PR3b { + apple,key-id = "PR3b"; + label = "SMC PR3b"; + }; - cooling-maps { - map-e { - trip = <&cpu_die_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; + power-PR3l { + apple,key-id = "PR3l"; + label = "SMC PR3l"; + }; - map-p0 { - trip = <&cpu_die_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; + power-PR4b { + apple,key-id = "PR4b"; + label = "SMC PR4b"; + }; - map-p1 { - trip = <&cpu_die_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; + power-PR7b { + apple,key-id = "PR7b"; + label = "SMC PR7b"; }; - /* - * The speaker amps' shared shutdown line (SDZ, AP pin 54, low = - * shutdown). One fixed regulator, the SDZ supply of all six, not - * per-amp shutdown-gpios: with GPIO_SHARED six references become a - * vote, and one amp driving the line low would not shut the other - * five down. - */ - speaker_sdz: regulator-sn012776-sdz { - compatible = "regulator-fixed"; - regulator-name = "sn012776-sdz"; - gpios = <&pinctrl 54 GPIO_ACTIVE_HIGH>; - enable-active-high; + power-PR8l { + apple,key-id = "PR8l"; + label = "SMC PR8l"; }; - /* - * The AOP firmware runs the stream (MCA cluster 0, one serializer for - * all six amps); the J616 variant of macaudio links the AOP audio - * component's speaker back-end (DAI 3) to the amps. The codecs are - * listed in the ADT provider order, so each gets its native playback - * slot (LW1 0, RW1 1, LW2 2, RW2 3, LT 4, RT 5). The idle masks are the - * ADT amp-tx-zd-config rows 0 and 1: LW1 drives zeros in the right - * amps' sense slots, RW1 in the left amps'. - */ - sound: sound { - compatible = "apple,j616-macaudio", "apple,macaudio"; - model = "MacBook Pro J616"; - #address-cells = <1>; - #size-cells = <0>; + power-PR9b { + apple,key-id = "PR9b"; + label = "SMC PR9b"; + }; - dai-link@0 { - reg = <0>; - link-name = "Speakers"; - dai-tdm-idle-mode-0 = "zero"; - dai-tdm-idle-mode-1 = "zero"; - dai-tdm-slot-tx-idle-mask-0 = <0xf0f0f0>; - dai-tdm-slot-tx-idle-mask-1 = <0x0f0f0f>; + power-PR9l { + apple,key-id = "PR9l"; + label = "SMC PR9l"; + }; - cpu { - sound-dai = <&aop_audio 3>; - }; + power-PRal { + apple,key-id = "PRal"; + label = "SMC PRal"; + }; - codec { - sound-dai = <&speaker_left_woof1>, <&speaker_right_woof1>, - <&speaker_left_woof2>, <&speaker_right_woof2>, - <&speaker_left_tweet>, <&speaker_right_tweet>; - }; - }; + power-PRbb { + apple,key-id = "PRbb"; + label = "SMC PRbb"; }; - /* - * The headphone jack: the AOP audio component's jack back-end (DAI 4) - * to the CS42L84. A card of its own, so a codec that does not answer - * costs the jack only, never the speakers. - */ - sound_jack: sound-jack { - compatible = "apple,j616-macaudio-jack", "apple,macaudio"; - model = "MacBook Pro J616 Jack"; - #address-cells = <1>; - #size-cells = <0>; + power-PRdb { + apple,key-id = "PRdb"; + label = "SMC PRdb"; + }; - dai-link@0 { - reg = <0>; - link-name = "Headphone Jack"; + power-PReb { + apple,key-id = "PReb"; + label = "SMC PReb"; + }; - cpu { - sound-dai = <&aop_audio 4>; - }; + power-PS0b { + apple,key-id = "PS0b"; + label = "SMC PS0b"; + }; - codec { - sound-dai = <&jack_codec>; - }; - }; + power-PS1b { + apple,key-id = "PS1b"; + label = "SMC PS1b"; }; -}; -&cpuidle { - status = "okay"; -}; + power-PS2b { + apple,key-id = "PS2b"; + label = "SMC PS2b"; + }; -&smc { - status = "okay"; -}; + power-PS3b { + apple,key-id = "PS3b"; + label = "SMC PS3b"; + }; -&smc_mbox { - status = "okay"; -}; + power-PS4b { + apple,key-id = "PS4b"; + label = "SMC PS4b"; + }; -&nub_spmi0 { - status = "okay"; -}; + power-PS7b { + apple,key-id = "PS7b"; + label = "SMC PS7b"; + }; -&pinctrl { - status = "okay"; + power-PS9b { + apple,key-id = "PS9b"; + label = "SMC PS9b"; + }; - wifi_clkreq_pins: wifi-clkreq-pins { - pinmux = ; + power-PS9l { + apple,key-id = "PS9l"; + label = "SMC PS9l"; }; -}; -/* - * The AP takes none of the always-on bank's interrupts: the AOP firmware - * owns some of its pins and interrupt registers, and a chained handler here - * would see their pending bits without a handler of ours. - */ -&pinctrl_nub { - status = "okay"; - /delete-property/ interrupt-controller; - /delete-property/ #interrupt-cells; - /delete-property/ interrupts; -}; + power-PSDR { + apple,key-id = "PSDR"; + label = "SMC PSDR"; + }; -&fpwm0 { - status = "okay"; -}; + power-PSal { + apple,key-id = "PSal"; + label = "SMC PSal"; + }; -/* The Type-C retimers (ADT atcrt at 0x18/0x19/0x1a) sit on this bus; no driver yet. */ -&i2c6 { - status = "okay"; -}; + power-PSbb { + apple,key-id = "PSbb"; + label = "SMC PSbb"; + }; -&mtp { - status = "okay"; -}; + power-PSeb { + apple,key-id = "PSeb"; + label = "SMC PSeb"; + }; -&mtp_mbox { - status = "okay"; -}; + power-PTHS { + apple,key-id = "PTHS"; + label = "SMC PTHS"; + }; -&mtp_dart { - status = "okay"; -}; + power-PU1C { + apple,key-id = "PU1C"; + label = "SMC PU1C"; + }; -&mtp_dockchannel { - status = "okay"; -}; + power-PU1R { + apple,key-id = "PU1R"; + label = "SMC PU1R"; + }; -&mtp_hid { - /* SMC key gp1c powers the trackpad analog front end. */ - apple,afe-smc = <&smc>; + power-PU2C { + apple,key-id = "PU2C"; + label = "SMC PU2C"; + }; - multi-touch { - firmware-name = "apple/tpmtfw-j616s.bin"; + power-PU2R { + apple,key-id = "PU2R"; + label = "SMC PU2R"; }; - keyboard: keyboard { - hid-country-code = <0>; - apple,keyboard-layout-id = <2>; + power-PW3C { + apple,key-id = "PW3C"; + label = "SMC PW3C"; }; - stm { + power-PZC0 { + apple,key-id = "PZC0"; + label = "SMC PZC0"; }; - actuator { + power-PZC1 { + apple,key-id = "PZC1"; + label = "SMC PZC1"; }; - tp-accel { + power-PZCB { + apple,key-id = "PZCB"; + label = "SMC PZCB"; }; - mtp { + power-PZCU { + apple,key-id = "PZCU"; + label = "SMC PZCU"; }; -}; -&smc_hwmon { - /* - * TCMz must stay the first temperature child: the thermal zone names - * the sensor by its index, so the shared hwmon includes below only - * append after it. - */ - temperature-TCMz { - apple,key-id = "TCMz"; - label = "CPU Die Max"; + power-PZD1 { + apple,key-id = "PZD1"; + label = "SMC PZD1"; + }; + + power-PZT0 { + apple,key-id = "PZT0"; + label = "SMC PZT0"; + }; + + power-Pb0f { + apple,key-id = "Pb0f"; + label = "SMC Pb0f"; + }; + + power-SBAP { + apple,key-id = "SBAP"; + label = "Battery Power"; }; }; -/* Read-only fan speeds and more SMC sensors; every key exists on this machine's SMC. */ +/* + * Fan speeds, limits, targets and modes, and the common SMC sensors; every + * key exists on this machine's SMC. Both fans list their target (F0Tg, + * F1Tg) and mode (F0Md, F1Md) keys, so with the driver's fan_control + * parameter fanN_target becomes writable; by default the SMC drives the + * fans and the attributes are read-only. + */ #include "hwmon-common.dtsi" #include "hwmon-fan-dual.dtsi" #include "hwmon-laptop.dtsi" @@ -569,7 +3837,6 @@ device_type = "pci"; port00: pci@0,0 { - compatible = "pciclass,0604"; reg = <0 0 0 0 0>; ranges; interrupt-map = <0 0 0 1 &port00 0 0 0 0 0 0 0 2 &port00 0 0 0 1 0 0 0 3 &port00 0 0 0 2 0 0 0 4 &port00 0 0 0 3>; @@ -592,7 +3859,7 @@ }; wifi@0,0 { - compatible = "brcm,bcm4329-fmac"; + compatible = "pci14e4,4434", "brcm,bcm4329-fmac"; reg = <0 0 0 0 0>; brcm,ccode-map-trivial; brcm,board-type = "apple,hostomel"; @@ -634,6 +3901,7 @@ compatible = "apple,panel-j616", "apple,panel-mini-led", "apple,panel"; width-mm = <345>; height-mm = <223>; + adj-height-mm = <216>; status = "disabled"; }; }; @@ -822,12 +4090,23 @@ }; /* - * The GPU stays disabled until a boot loader fills in this unit's operating - * points, leakage and regions from the ADT; the values here are one unit's. - * Enabling it also takes gfx_mbox, keeps ps_afr and ps_gfx always on and - * claims pmp_gfx always-on, so that the PMP sees the GPU on exactly while it - * is powered. + * The loaded GPU firmware keeps running across system sleep. Like the Neo's + * GFX domain, these domains must not be switched off while that image lives. + * The PMP report vote is still owned by the GPU consumer. */ +&ps_afr { + apple,always-on; +}; + +&ps_gfx { + apple,always-on; +}; + +/* The loader fills the operating points, calibration and all six memory regions. */ +&gfx_mbox { + status = "okay"; +}; + &gpu { memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>, <&gpu_fw_data>, <&uat_l2_pagetables>, <&gpu_fw_text>; @@ -836,7 +4115,9 @@ operating-points-v2 = <&gpu_opp>; apple,cs-opp = <&gpu_cs_opp>; apple,afr-opp = <&gpu_afr_opp>; - status = "disabled"; + /* Avoid a core power cycle between interactive submissions, then release. */ + apple,idle-off-delay-ms = <16>; + status = "okay"; }; /* @@ -1086,3 +4367,72 @@ }; #include "t6040-j616s-usb3.dtsi" + +/* External display chain, all disabled: the gate in drivers/soc/apple/t6041-display-gate.c enables it. */ +#include "t6040-j616s-extdisp.dtsi" +#include "t6040-j616s-extdisp-mem-loader.dtsi" +#include "t6040-j616s-extdisp-hdmi.dtsi" +#include "t6040-j616s-extdisp-usbc.dtsi" +/* Additional Abbey PMICs on the two independent NUB SPMI buses. */ +&nub_spmi1 { + status = "okay"; + + pmic@e { + compatible = "apple,abbey-pmic", "apple,spmi-nvmem"; + reg = <0xe SPMI_USID>; + }; +}; + +&nub_spmi2 { + status = "okay"; + + pmic@e { + compatible = "apple,abbey-pmic", "apple,spmi-nvmem"; + reg = <0xe SPMI_USID>; + }; +}; + +&ps_nub_spmi_ahb { + status = "okay"; +}; + +&ps_nub_spmi1 { + status = "okay"; +}; + +&ps_nub_spmi2 { + status = "okay"; +}; + +&pinctrl { + sd_clkreq_pins: sd-clkreq-pins { + pinmux = ; + }; +}; + +&pcie0 { + interrupts = , + ; + /* Widen bus-range only with the SD power/port ownership handoff. */ + iommu-map = <0x100 &pcie_dart 1 2>, <0x200 &apcie1_dart 1 1>; + + port01: pci@1,0 { + device_type = "pci"; + reg = <0x800 0 0 0 0>; + reset-gpios = <&pinctrl 5 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&sd_clkreq_pins>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port01 0 0 0 0>, + <0 0 0 2 &port01 0 0 0 1>, + <0 0 0 3 &port01 0 0 0 2>, + <0 0 0 4 &port01 0 0 0 3>; + /* The SD power switch needs an SMC service binding. */ + status = "disabled"; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi index 8c05fb4d4d5d3..e0af3e1903cfb 100644 --- a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi @@ -140,6 +140,24 @@ power-domains = <&ps_sio>; }; + ps_spi_p: power-controller@198 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi_p"; + power-domains = <&ps_sio>; + }; + + ps_spi2: power-controller@1c8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi2"; + power-domains = <&ps_spi_p>; + }; + ps_ans: power-controller@370 { compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; reg = <0x370 4>; @@ -178,6 +196,16 @@ power-domains = <&ps_ans>, <&ps_apcie_st1>; apple,always-on; }; + + ps_sep: power-controller@c00 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc00 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sep"; + power-domains = <&ps_fab3_soc>; + apple,always-on; + }; }; /* @@ -269,6 +297,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "avd_sys"; + power-domains = <&ps_fab0_soc>; }; ps_afc: power-controller@178 { @@ -287,6 +316,7 @@ #reset-cells = <0>; label = "pmp"; apple,always-on; + power-domains = <&ps_pms>; }; ps_pms_sram: power-controller@418 { @@ -296,6 +326,7 @@ #reset-cells = <0>; label = "pms_sram"; apple,always-on; + power-domains = <&ps_pms>; }; ps_fab6_soc: power-controller@480 { @@ -330,6 +361,7 @@ #reset-cells = <0>; label = "atc0_common"; apple,always-on; + power-domains = <&ps_fab2_soc>; }; ps_atc1_common: power-controller@3b8 { @@ -339,6 +371,7 @@ #reset-cells = <0>; label = "atc1_common"; apple,always-on; + power-domains = <&ps_fab2_soc>; }; ps_atc2_common: power-controller@3e0 { @@ -348,6 +381,7 @@ #reset-cells = <0>; label = "atc2_common"; apple,always-on; + power-domains = <&ps_fab2_soc>; }; }; @@ -478,3 +512,1735 @@ power-domains = <&ps_atc2_phymxwrap>; }; }; + +/* + * Remaining ADT power states. Keep providers without a qualified consumer + * disabled: registering an unused genpd can power down firmware-owned blocks. + * Enable the provider and its parents together with the consumer. + */ +&pmgr { + ps_apcie_gp: power-controller@100 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_gp"; + status = "disabled"; + }; + + ps_fab4_soc: power-controller@128 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab4_soc"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_sio_cpu: power-controller@130 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sio_cpu"; + power-domains = <&ps_sio>; + apple,always-on; + status = "disabled"; + }; + + ps_fpwm1: power-controller@140 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x140 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm1"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_fpwm2: power-controller@148 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm2"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_i2c0: power-controller@150 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x150 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c0"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_i2c5: power-controller@178 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x178 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c5"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_i2c7: power-controller@188 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c7"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + + ps_uart_p: power-controller@1a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart_p"; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ps_dpa_p: power-controller@1a8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa_p"; + power-domains = <&ps_sio>; + apple,always-on; + status = "disabled"; + }; + + ps_aes: power-controller@1b0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aes"; + power-domains = <&ps_sio>; + apple,always-on; + status = "disabled"; + }; + + ps_spi0: power-controller@1b8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi0"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_spi1: power-controller@1c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi1"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + + ps_spi3: power-controller@1d0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi3"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_spi4: power-controller@1d8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi4"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_spi5: power-controller@1e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi5"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_qspi: power-controller@1e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "qspi"; + power-domains = <&ps_spi_p>; + status = "disabled"; + }; + + ps_uart_n: power-controller@1f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart_n"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart0: power-controller@1f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart0"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart1: power-controller@200 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x200 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart1"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart2: power-controller@208 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x208 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart2"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart3: power-controller@210 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x210 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart3"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart4: power-controller@218 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x218 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart4"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart5: power-controller@220 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x220 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart5"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_uart6: power-controller@228 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x228 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart6"; + power-domains = <&ps_uart_p>; + status = "disabled"; + }; + + ps_dpa0: power-controller@230 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x230 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa0"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_dpa1: power-controller@238 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x238 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa1"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_dpa2: power-controller@240 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x240 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa2"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_dpa3: power-controller@248 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x248 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa3"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_dpa4: power-controller@250 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x250 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa4"; + power-domains = <&ps_dpa_p>; + status = "disabled"; + }; + + ps_disp_sys: power-controller@258 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x258 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_sys"; + power-domains = <&ps_fab6_soc>; + status = "disabled"; + }; + + ps_amcc4: power-controller@260 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x260 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc4"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc5: power-controller@268 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x268 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc5"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc6: power-controller@270 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x270 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc6"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc7: power-controller@278 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x278 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc7"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc12: power-controller@280 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x280 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc12"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc13: power-controller@288 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x288 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc13"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc14: power-controller@290 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x290 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc14"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc15: power-controller@298 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x298 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc15"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_04: power-controller@2e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_04"; + power-domains = <&ps_amcc4>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_05: power-controller@2e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_05"; + power-domains = <&ps_amcc5>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_06: power-controller@2f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_06"; + power-domains = <&ps_amcc6>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_07: power-controller@2f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_07"; + power-domains = <&ps_amcc7>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_12: power-controller@300 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x300 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_12"; + power-domains = <&ps_amcc12>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_13: power-controller@308 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x308 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_13"; + power-domains = <&ps_amcc13>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_14: power-controller@310 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x310 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_14"; + power-domains = <&ps_amcc14>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_15: power-controller@318 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x318 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_15"; + power-domains = <&ps_amcc15>; + apple,always-on; + status = "disabled"; + }; + + ps_disp_fe: power-controller@368 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x368 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_fe"; + power-domains = <&ps_disp_sys>; + status = "disabled"; + }; + + ps_apcie_sys_gp: power-controller@390 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x390 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_sys_gp"; + power-domains = <&ps_fab3_soc>, <&ps_apcie_gp>; + status = "disabled"; + }; + + ps_apcie_phy_sw: power-controller@398 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x398 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_phy_sw"; + power-domains = <&ps_apcie_sys_st0>, <&ps_apcie_sys_st1>; + status = "disabled"; + }; + + ps_jpg: power-controller@3a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "jpg"; + power-domains = <&ps_fab4_soc>; + status = "disabled"; + }; + + ps_ane_sys: power-controller@3c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_sys"; + power-domains = <&ps_fab5_soc>, <&ps_fab4_soc>; + status = "disabled"; + }; + +}; + +&pmgr_aux { + ps_ecpu_0: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpu_0"; + status = "disabled"; + }; + + ps_ecpu_1: power-controller@8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpu_1"; + status = "disabled"; + }; + + ps_ecpu_2: power-controller@10 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x10 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpu_2"; + status = "disabled"; + }; + + ps_ecpu_3: power-controller@18 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x18 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpu_3"; + status = "disabled"; + }; + + ps_pcpu0_0: power-controller@20 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x20 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_0"; + status = "disabled"; + }; + + ps_pcpu0_1: power-controller@28 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x28 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_1"; + status = "disabled"; + }; + + ps_pcpu0_2: power-controller@30 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x30 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_2"; + status = "disabled"; + }; + + ps_pcpu0_3: power-controller@38 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x38 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_3"; + status = "disabled"; + }; + + ps_pcpu0_4: power-controller@40 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x40 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_4"; + status = "disabled"; + }; + + ps_pcpu0_5: power-controller@48 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x48 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu0_5"; + status = "disabled"; + }; + + ps_pcpu1_0: power-controller@50 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x50 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_0"; + status = "disabled"; + }; + + ps_pcpu1_1: power-controller@58 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x58 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_1"; + status = "disabled"; + }; + + ps_pcpu1_2: power-controller@60 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x60 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_2"; + status = "disabled"; + }; + + ps_pcpu1_3: power-controller@68 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x68 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_3"; + status = "disabled"; + }; + + ps_pcpu1_4: power-controller@70 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x70 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_4"; + status = "disabled"; + }; + + ps_pcpu1_5: power-controller@78 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x78 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpu1_5"; + status = "disabled"; + }; + + ps_ecpm: power-controller@80 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x80 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpm"; + status = "disabled"; + }; + + ps_pcpm0: power-controller@88 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x88 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpm0"; + status = "disabled"; + }; + + ps_pcpm1: power-controller@90 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x90 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpm1"; + status = "disabled"; + }; + + ps_dwi: power-controller@108 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dwi"; + status = "disabled"; + }; + + ps_aic: power-controller@118 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x118 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aic"; + power-domains = <&ps_pms>; + status = "disabled"; + }; + + ps_pmc: power-controller@120 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x120 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pmc"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_c1ppt: power-controller@128 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_c1ppt"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_soc_dpe: power-controller@130 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "soc_dpe"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pmgr_soc_ocla: power-controller@138 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x138 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pmgr_soc_ocla"; + status = "disabled"; + }; + + ps_pms_fpwm0: power-controller@140 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x140 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm0"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_fpwm1: power-controller@148 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm1"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_fpwm2: power-controller@150 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x150 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm2"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_fpwm3: power-controller@158 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x158 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm3"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_pms_fpwm4: power-controller@160 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x160 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm4"; + power-domains = <&ps_pms>; + apple,always-on; + status = "disabled"; + }; + + ps_fab0_soc: power-controller@168 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab0_soc"; + power-domains = <&ps_afi>; + apple,always-on; + }; + + ps_amcc0: power-controller@180 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x180 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc0"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_00: power-controller@188 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_00"; + power-domains = <&ps_amcc0>; + apple,always-on; + status = "disabled"; + }; + + ps_rom: power-controller@190 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x190 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "rom"; + power-domains = <&ps_amcc0>; + status = "disabled"; + }; + + ps_amcc1: power-controller@198 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc1"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_01: power-controller@1a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_01"; + power-domains = <&ps_amcc1>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc2: power-controller@1a8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc2"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_02: power-controller@1b0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_02"; + power-domains = <&ps_amcc2>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc3: power-controller@1b8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc3"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_03: power-controller@1c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_03"; + power-domains = <&ps_amcc3>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc8: power-controller@1c8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc8"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_08: power-controller@1d0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_08"; + power-domains = <&ps_amcc8>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc9: power-controller@1d8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc9"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_09: power-controller@1e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_09"; + power-domains = <&ps_amcc9>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc10: power-controller@1e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc10"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_10: power-controller@1f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_10"; + power-domains = <&ps_amcc10>; + apple,always-on; + status = "disabled"; + }; + + ps_amcc11: power-controller@1f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "amcc11"; + power-domains = <&ps_afc>; + apple,always-on; + status = "disabled"; + }; + + ps_dcs_11: power-controller@200 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x200 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs_11"; + power-domains = <&ps_amcc11>; + apple,always-on; + status = "disabled"; + }; + + ps_venc0_sys: power-controller@278 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x278 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_sys"; + power-domains = <&ps_fab0_soc>; + status = "disabled"; + }; + + ps_fab_gw_0: power-controller@288 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x288 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab_gw_0"; + apple,always-on; + status = "disabled"; + }; + + ps_fab_gw_1: power-controller@290 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x290 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab_gw_1"; + apple,always-on; + status = "disabled"; + }; + + ps_fab_afr: power-controller@298 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x298 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab_afr"; + apple,always-on; + status = "disabled"; + }; + + ps_fab1_soc: power-controller@2d0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab1_soc"; + power-domains = <&ps_afi>; + apple,always-on; + status = "disabled"; + }; + + ps_scodec: power-controller@328 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x328 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "scodec"; + power-domains = <&ps_fab1_soc>; + status = "disabled"; + }; + + ps_scodec_streaming: power-controller@330 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x330 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "scodec_streaming"; + power-domains = <&ps_scodec>; + status = "disabled"; + }; + + ps_fab2_soc: power-controller@338 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x338 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab2_soc"; + power-domains = <&ps_afi>; + apple,always-on; + }; + + ps_dispext0_sys: power-controller@340 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x340 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_sys"; + power-domains = <&ps_fab2_soc>; + status = "disabled"; + }; + + ps_dispext0_fe: power-controller@348 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x348 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_fe"; + power-domains = <&ps_dispext0_sys>; + status = "disabled"; + }; + + ps_dispext0_cpu: power-controller@350 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x350 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext0_cpu"; + power-domains = <&ps_dispext0_fe>; + status = "disabled"; + }; + + ps_atc0_cio: power-controller@398 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x398 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio"; + power-domains = <&ps_atc0_common>; + status = "disabled"; + }; + + ps_atc0_pcie: power-controller@3a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_pcie"; + power-domains = <&ps_atc0_common>; + status = "disabled"; + }; + + ps_atc0_cio_pcie: power-controller@3a8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio_pcie"; + power-domains = <&ps_atc0_cio>; + status = "disabled"; + }; + + ps_atc0_cio_usb: power-controller@3b0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio_usb"; + power-domains = <&ps_atc0_cio>; + status = "disabled"; + }; + + ps_atc1_cio: power-controller@3c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio"; + power-domains = <&ps_atc1_common>; + status = "disabled"; + }; + + ps_atc1_pcie: power-controller@3c8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_pcie"; + power-domains = <&ps_atc1_common>; + status = "disabled"; + }; + + ps_atc1_cio_pcie: power-controller@3d0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio_pcie"; + power-domains = <&ps_atc1_cio>; + status = "disabled"; + }; + + ps_atc1_cio_usb: power-controller@3d8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio_usb"; + power-domains = <&ps_atc1_cio>; + status = "disabled"; + }; + + ps_atc2_cio: power-controller@3e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc2_cio"; + power-domains = <&ps_atc2_common>; + status = "disabled"; + }; + + ps_atc2_pcie: power-controller@3f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc2_pcie"; + power-domains = <&ps_atc2_common>; + status = "disabled"; + }; + + ps_atc2_cio_pcie: power-controller@3f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc2_cio_pcie"; + power-domains = <&ps_atc2_cio>; + status = "disabled"; + }; + + ps_atc2_cio_usb: power-controller@400 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x400 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc2_cio_usb"; + power-domains = <&ps_atc2_cio>; + status = "disabled"; + }; + + ps_atc3_common: power-controller@408 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x408 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_common"; + power-domains = <&ps_fab2_soc>; + status = "disabled"; + }; + + ps_atc3_cio: power-controller@420 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x420 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_cio"; + power-domains = <&ps_atc3_common>; + status = "disabled"; + }; + + ps_atc3_pcie: power-controller@428 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x428 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_pcie"; + power-domains = <&ps_atc3_common>; + status = "disabled"; + }; + + ps_atc3_cio_pcie: power-controller@430 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x430 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_cio_pcie"; + power-domains = <&ps_atc3_cio>; + status = "disabled"; + }; + + ps_atc3_cio_usb: power-controller@438 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x438 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_cio_usb"; + power-domains = <&ps_atc3_cio>; + status = "disabled"; + }; + + ps_fab5_soc: power-controller@440 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x440 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab5_soc"; + power-domains = <&ps_afi>; + apple,always-on; + status = "disabled"; + }; + + ps_sbr: power-controller@448 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x448 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sbr"; + power-domains = <&ps_fab5_soc>; + apple,always-on; + status = "disabled"; + }; + + ps_gpio: power-controller@450 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x450 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gpio"; + power-domains = <&ps_sbr>; + status = "disabled"; + }; + + ps_msg: power-controller@458 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x458 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msg"; + power-domains = <&ps_sbr>; + status = "disabled"; + }; + + ps_ap_tmm: power-controller@468 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x468 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ap_tmm"; + power-domains = <&ps_sbr>; + status = "disabled"; + }; + + ps_msr0: power-controller@470 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x470 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msr0"; + power-domains = <&ps_fab5_soc>; + status = "disabled"; + }; + + ps_msr0_ase_core: power-controller@478 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x478 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msr0_ase_core"; + power-domains = <&ps_msr0>; + status = "disabled"; + }; + + ps_fab7_soc: power-controller@488 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x488 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fab7_soc"; + power-domains = <&ps_afi>; + apple,always-on; + status = "disabled"; + }; + + ps_dispext1_sys: power-controller@490 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x490 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_sys"; + power-domains = <&ps_fab7_soc>; + status = "disabled"; + }; + + ps_dispext1_fe: power-controller@498 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x498 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_fe"; + power-domains = <&ps_dispext1_sys>; + status = "disabled"; + }; + + ps_dispext1_cpu: power-controller@4a0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext1_cpu"; + power-domains = <&ps_dispext1_fe>; + status = "disabled"; + }; +}; + +&pmgr_ane { + ps_ane_mpm: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_mpm"; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ps_ane_cpu: power-controller@8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_cpu"; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ps_ane_td: power-controller@10 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x10 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_td"; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ps_ane_base: power-controller@18 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x18 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_base"; + power-domains = <&ps_ane_td>; + status = "disabled"; + }; +}; + +&pmgr_mini { + ps_debug_gated: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "debug_gated"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi_ahb: power-controller@58 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x58 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi_ahb"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi0: power-controller@60 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x60 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi0"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi_a2: power-controller@78 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x78 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi_a2"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spi0: power-controller@80 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x80 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spi0"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_aon: power-controller@88 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x88 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_aon"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_ocla: power-controller@98 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x98 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_ocla"; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_common_dp: power-controller@b8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xb8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_common_dp"; + status = "disabled"; + }; + + ps_nub_spmi1: power-controller@c0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi1"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_usb_aon: power-controller@e0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xe0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_usb_aon"; + status = "disabled"; + }; + + ps_nub_spmi2: power-controller@e8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xe8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi2"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi3: power-controller@f0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xf0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi3"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_nub_spmi4: power-controller@f8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xf8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi4"; + power-domains = <&ps_nub_spmi_ahb>; + apple,always-on; + status = "disabled"; + }; + + ps_nub_fabric: power-controller@100 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_fabric"; + apple,always-on; + status = "disabled"; + }; + + ps_nub_sram: power-controller@108 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_sram"; + power-domains = <&ps_nub_fabric>; + apple,always-on; + status = "disabled"; + }; + + ps_debug_switch: power-controller@110 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x110 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "debug_switch"; + apple,always-on; + status = "disabled"; + }; + + ps_atc3_usb: power-controller@148 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_usb"; + power-domains = <&ps_atc3_common>, <&ps_atc3_usb_aon>; + status = "disabled"; + }; + + ps_atc3_phymxwrap: power-controller@168 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc3_phymxwrap"; + power-domains = <&ps_atc3_common>, <&ps_atc3_usb_aon>; + status = "disabled"; + }; +}; + +&pmgr_disp { + ps_disp_cpu: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_cpu"; + power-domains = <&ps_disp_fe>; + status = "disabled"; + }; +}; + +&pmgr_gfx { + ps_gpx: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gpx"; + apple,always-on; + status = "disabled"; + }; +}; + +&pmgr_venc { + ps_venc0_dma: power-controller@0 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_dma"; + power-domains = <&ps_venc0_sys>; + status = "disabled"; + }; + + ps_venc0_pipe4: power-controller@8 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_pipe4"; + power-domains = <&ps_venc0_dma>; + status = "disabled"; + }; + + ps_venc0_pipe5: power-controller@10 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x10 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_pipe5"; + power-domains = <&ps_venc0_dma>; + status = "disabled"; + }; + + ps_venc0_me0: power-controller@18 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x18 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_me0"; + power-domains = <&ps_venc0_dma>; + status = "disabled"; + }; + + ps_venc0_me1: power-controller@20 { + compatible = "apple,t6041-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x20 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc0_me1"; + power-domains = <&ps_venc0_me0>; + status = "disabled"; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 213723f3c34b2..961af1b7b5bd4 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -616,7 +616,8 @@ <0x2 0x88000000 0x0 0x3758000>; reg-names = "asc", "sgx"; mboxes = <&gfx_mbox>; - power-domains = <&ps_gfx>; + power-domains = <&pmp_gfx>; + apple,clpc = <&clpc>; status = "disabled"; }; @@ -701,6 +702,48 @@ status = "disabled"; }; + /* + * iBoot hands over a running SEP. Keep Linux off its DART at boot; + * apple_sep enables it when the driver binds. The SEP only accepts + * IOVAs in the window iBoot programmed (ADT vm-base 0x10000004000, + * vm-size 0x3ff00000), which carries the BIT(40) alias the DART + * translates. + */ + sep_dart: iommu@404c80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x04c80000 0x0 0x20000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = /bits/ 64 <0x10000004000 0x3ff00000>; + apple,dma-offset = /bits/ 64 <0x0>; + status = "disabled"; + }; + + sep: sep@406600000 { + compatible = "apple,sep"; + reg = <0x4 0x06600000 0x0 0x88000>; + mboxes = <&sep_mbox>; + mbox-names = "mbox"; + iommus = <&sep_dart 0>; + power-domains = <&ps_sep>; + status = "disabled"; + }; + + sep_mbox: mailbox@406608000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x4 0x06608000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + ans_mbox: mailbox@409608000 { compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x4 0x09608000 0x0 0x4000>; @@ -826,6 +869,30 @@ status = "disabled"; }; + /* + * Touch ID. iBoot leaves the pads muxed and the controller uses its + * native chip select, so there is no pinctrl here. + */ + spi2: spi@429108000 { + compatible = "apple,t6041-spi", "apple,t8103-spi"; + reg = <0x4 0x29108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>; + power-domains = <&ps_spi2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + mesa: fingerprint@0 { + compatible = "apple,mesa-fingerprint"; + reg = <0>; + spi-max-frequency = <8000000>; + spi-cpol; + status = "disabled"; + }; + }; + nvme: nvme@44dcc0000 { compatible = "apple,t6041-nvme-ans2"; reg = <0x4 0x4dcc0000 0x0 0x10000>, @@ -909,8 +976,8 @@ * the GPU as off and holds it at its lowest state, but claiming * it while nothing powers the GPU crashes the PMP firmware * (SError) right after it starts. It sits on the GPU's own - * power state, and a board that powers the GPU (ps_gfx stays on - * while the GPU firmware runs) claims it always-on. + * power state. The GPU consumer takes this domain, so the request + * follows the GFX domain's power lifetime. */ pmp_gfx: report@d { compatible = "apple,t6041-pmp-v2-report-entry", @@ -963,6 +1030,27 @@ /* child nodes are added in t6040-pmgr.dtsi */ }; + pmgr_ane: power-management@50228c000 { + compatible = "apple,t6041-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; + reg = <0x5 0x0228c000 0x0 0x4000>; + #address-cells = <1>; + #size-cells = <1>; + }; + + pmgr_disp: power-management@502290000 { + compatible = "apple,t6041-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; + reg = <0x5 0x02290000 0x0 0x4000>; + #address-cells = <1>; + #size-cells = <1>; + }; + + pmgr_venc: power-management@502808000 { + compatible = "apple,t6041-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; + reg = <0x5 0x02808000 0x0 0x4000>; + #address-cells = <1>; + #size-cells = <1>; + }; + pmgr_isp: power-management@502284000 { compatible = "apple,t6041-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; #address-cells = <1>; @@ -1005,9 +1093,13 @@ * only hands to its firmware, so the two do not conflict; "rails" * comes first so that this node's unit address differs from * pmgr_dcp's. - * Static voters are CLPC (1) and AP (3). The PMP's agent (0) stays - * off while the PMP does not run: iBoot leaves its floor at the top - * DCS state. + * Static voters are the PMP (0), CLPC (1) and AP (3). The PMP votes + * the DCS and fabric for memory clients other than the CPUs, the GPU + * among them; without its agent, GPU work runs at a low DCS + * state unless a CPU is busy. iBoot leaves the PMP's floor at the top + * DCS state, so its agent must stay off while the PMP does not run: + * apple,pmp holds off the PMC until the PMP driver has bound, and + * unbinds the PMC first. */ pmc: power-management@5048aa000 { compatible = "apple,t6041-pmc"; @@ -1017,12 +1109,14 @@ nonposted-mmio; #address-cells = <1>; #size-cells = <0>; - apple,static-voters = <1 3>; + apple,pmp = <&pmp>; + apple,static-voters = <0 1 3>; status = "disabled"; clpc: performance-controller@1 { compatible = "apple,t6041-clpc"; reg = <1>; + }; }; @@ -1717,6 +1811,461 @@ phy-names = "usb2-phy", "usb3-phy"; status = "disabled"; }; + /* Additional DARTs; enabled together with their DMA consumers. */ + ave0_dart1: iommu@283080000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x2 0x83080000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_venc0_sys>; + status = "disabled"; + }; + + ave0_dart0: iommu@2830a0000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x2 0x830a0000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_venc0_sys>; + status = "disabled"; + }; + + apr0_dart: iommu@301020000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x01020000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_scodec>; + status = "disabled"; + }; + + apciec0_dart: iommu@384000000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x84000000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + apciec1_dart: iommu@38c000000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x8c000000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + apciec2_dart: iommu@394000000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x94000000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dispext0_dart: iommu@3a2300000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0xa2300000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext0_dart: iommu@3a2340000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0xa2340000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + apcie1_dart: iommu@411000000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x11000000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + sio_dart: iommu@429060000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x29060000 0x0 0x20000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_sio>; + status = "disabled"; + }; + + ane0_dart0: iommu@485800000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x85800000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ane0_dart1: iommu@485820000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x85820000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + ane0_dart2: iommu@485840000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x85840000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + jpeg0_dart: iommu@487020000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x87020000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_jpg>; + status = "disabled"; + }; + + jpeg1_dart: iommu@487080000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x4 0x87080000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_jpg>; + status = "disabled"; + }; + + scaler0_dart: iommu@521260000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x5 0x21260000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_msr0>; + status = "disabled"; + }; + + dispext1_dart: iommu@602300000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x6 0x02300000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcpext1_dart: iommu@602340000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x6 0x02340000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + pinctrl_aop: pinctrl@510824000 { + compatible = "apple,t6041-pinctrl", "apple,t8103-pinctrl"; + reg = <0x5 0x10824000 0x0 0x4000>; + clocks = <&clkref>; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_aop 0 0 85>; + apple,npins = <85>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + /* Coprocessor pins and interrupt groups need an ownership handoff. */ + status = "disabled"; + }; + + pinctrl_smc: pinctrl@50c824000 { + compatible = "apple,t6041-pinctrl", "apple,t8103-pinctrl"; + reg = <0x5 0x0c824000 0x0 0x4000>; + clocks = <&clkref>; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_smc 0 0 24>; + apple,npins = <24>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + /* Coprocessor pins and interrupt groups need an ownership handoff. */ + status = "disabled"; + }; + + nub_spmi1: spmi@509054000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x09054000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + power-domains = <&ps_nub_spmi1>; + status = "disabled"; + }; + + nub_spmi2: spmi@509094000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x09094000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + power-domains = <&ps_nub_spmi2>; + status = "disabled"; + }; + + nub_spmi3: spmi@5090d4000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x090d4000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + power-domains = <&ps_nub_spmi3>; + status = "disabled"; + }; + + nub_spmi4: spmi@509114000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x09114000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + power-domains = <&ps_nub_spmi4>; + status = "disabled"; + }; + + aop_spmi0: spmi@510914000 { + compatible = "apple,t6041-spmi", "apple,t8103-spmi"; + reg = <0x5 0x10914000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + /* Shared with AOP firmware; no AP bus ownership handoff yet. */ + status = "disabled"; + }; + + /* The QSPIMC input is ADT clock 434 (200 MHz). */ + clk_200m: clock-200m { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <200000000>; + clock-output-names = "clk_200m"; + }; + + spi4: spi@429110000 { + compatible = "apple,t6041-spi", "apple,t8103-spi"; + reg = <0x4 0x29110000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>; + power-domains = <&ps_spi4>; + #address-cells = <1>; + #size-cells = <0>; + /* Panel/TCON control belongs to the display firmware. */ + status = "disabled"; + }; + + qspi: spi@429118000 { + compatible = "apple,t6041-qspi"; + reg = <0x4 0x29118000 0x0 0x4800>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clk_200m>; + power-domains = <&ps_qspi>; + #address-cells = <1>; + #size-cells = <0>; + /* The firmware NOR needs a qualified partition/write policy. */ + status = "disabled"; + }; + + /* The loader supplies the SIO firmware regions and protocol tables. */ + sio: dma-controller@429e00000 { + compatible = "apple,t6041-sio"; + reg = <0x4 0x29e00000 0x0 0x8000>; + dma-channels = <128>; + #dma-cells = <1>; + mboxes = <&sio_mbox>; + iommus = <&sio_dart 0>; + power-domains = <&ps_sio_cpu>; + status = "disabled"; + }; + + sio_mbox: mailbox@429e08000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x4 0x29e08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_sio_cpu>; + status = "disabled"; + }; + + /* Firmware interface support is required before enabling the ANE. */ + ane: ane@484000000 { + compatible = "apple,t6041-ane"; + reg = <0x4 0x84000000 0x0 0x2000000>; + reg-names = "engine"; + interrupt-parent = <&aic>; + interrupts = ; + iommus = <&ane0_dart0 0>, <&ane0_dart1 0>, <&ane0_dart2 0>; + power-domains = <&ps_ane_cpu>; + status = "disabled"; + }; + + /* The video encoder stays disabled until its firmware backend is available. */ + ave: video-codec@283100000 { + compatible = "apple,t6041-ave"; + reg = <0x2 0x83100000 0x0 0x45c000>, + <0x2 0x83050000 0x0 0x4000>, + <0x2 0x83800000 0x0 0x800000>; + reg-names = "codec", "mailbox", "coproc"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + ; + power-domains = <&ps_venc0_dma>, <&ps_venc0_pipe4>, + <&ps_venc0_pipe5>, <&ps_venc0_me0>, <&ps_venc0_me1>; + power-domain-names = "dma", "pipe4", "pipe5", "me0", "me1"; + iommus = <&ave0_dart0 0>, <&ave0_dart1 0>; + status = "disabled"; + }; + + serial0: serial@429200000 { + compatible = "apple,s5l-uart"; + reg = <0x4 0x29200000 0x0 0x4000>; + reg-io-width = <4>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>, <&clkref>; + clock-names = "uart", "clk_uart_baud0"; + power-domains = <&ps_uart0>; + status = "disabled"; + }; + + dcuart: serial@50883c000 { + compatible = "apple,dockchannel-uart"; + reg = <0x5 0x0883c000 0x0 0x4000>, + <0x5 0x08838000 0x0 0x4000>, + <0x5 0x0880c000 0x0 0x4000>; + reg-names = "data", "config", "irq"; + interrupt-parent = <&aic>; + interrupts = ; + apple,channel = <2>; + apple,sw-drain; + status = "disabled"; + }; + + /* ACIO 0 remains disabled until its host-router handoff is supported. */ + usb4_0_mbox: mailbox@381108000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x81108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_atc0_cio>; + status = "disabled"; + }; + + usb4_0_dart: iommu@381a80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x81a80000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + /* ACIO 1 remains disabled until its host-router handoff is supported. */ + usb4_1_mbox: mailbox@389108000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x89108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_atc1_cio>; + status = "disabled"; + }; + + usb4_1_dart: iommu@389a80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x89a80000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + /* ACIO 2 remains disabled until its host-router handoff is supported. */ + usb4_2_mbox: mailbox@391108000 { + compatible = "apple,t6041-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x91108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_atc2_cio>; + status = "disabled"; + }; + + usb4_2_dart: iommu@391a80000 { + compatible = "apple,t6041-dart", "apple,t8110-dart"; + reg = <0x3 0x91a80000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + }; #include "t6040-pmgr.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi index 5606ce4d2d3e4..2e329cecdbbfe 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi @@ -190,7 +190,7 @@ }; &ps_atc0_common { - status = "disabled"; + status = "okay"; }; &ps_spi2 { @@ -279,7 +279,6 @@ &ps_atc1_common { status = "okay"; - apple,inherited-on; }; &ps_atc1_pcie { @@ -378,6 +377,11 @@ status = "disabled"; }; +/* No camera driver yet; genpd powers the ISP down as an unused domain. */ +&ps_isp_sys { + status = "okay"; +}; + &ps_ans { status = "okay"; }; @@ -501,19 +505,21 @@ }; &ps_atc0_usb_aon { - status = "disabled"; + status = "okay"; + apple,inherited-on; }; &ps_atc1_usb_aon { - status = "disabled"; + status = "okay"; + apple,inherited-on; }; &ps_atc0_usb { - status = "disabled"; + status = "okay"; }; &ps_atc1_usb { - status = "disabled"; + status = "okay"; }; &ps_atc2_usb_aon { diff --git a/arch/arm64/boot/dts/apple/t8132-j713.dts b/arch/arm64/boot/dts/apple/t8132-j713.dts index d878b72fd39ba..da55888e6d5e4 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713.dts +++ b/arch/arm64/boot/dts/apple/t8132-j713.dts @@ -232,7 +232,6 @@ <0x4 0x0b050000 0x0 0x4000>; reg-names = "usb2phy", "pipehandler", "core", "axi2af", "lioa", "fabric", "common-config"; - power-domains = <&ps_atc1_common>; ports { #address-cells = <1>; #size-cells = <0>; @@ -325,6 +324,32 @@ hwmon { compatible = "apple,smc-hwmon"; + current-ID0R { + apple,key-id = "ID0R"; + label = "AC Input Current"; + }; + + /* Panel and backlight supply; tracks brightness, idles when blanked. */ + current-Ib8f { + apple,key-id = "Ib8f"; + label = "Display Supply Current"; + }; + + power-PDTR { + apple,key-id = "PDTR"; + label = "AC Input Power"; + }; + + power-PMVC { + apple,key-id = "PMVC"; + label = "3.8 V Rail Power"; + }; + + power-PSTR { + apple,key-id = "PSTR"; + label = "Total System Power"; + }; + temperature-TB0T { apple,key-id = "TB0T"; label = "Battery Hotspot"; @@ -344,6 +369,16 @@ apple,key-id = "TW0P"; label = "WiFi/BT Module Temp"; }; + + voltage-SBAV { + apple,key-id = "SBAV"; + label = "Battery Voltage"; + }; + + voltage-VD0R { + apple,key-id = "VD0R"; + label = "Charger Input Voltage"; + }; }; rtc { diff --git a/arch/arm64/boot/dts/apple/t8132-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8132-pmgr.dtsi index 6fbf09e8f9810..5e31d6ce69e15 100644 --- a/arch/arm64/boot/dts/apple/t8132-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8132-pmgr.dtsi @@ -790,6 +790,15 @@ status = "disabled"; /* Locked, don't touch it */ }; + ps_isp_sys: power-controller@530 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x530 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_sys"; + status = "disabled"; + }; + ps_ans: power-controller@538 { compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; reg = <0x538 4>; diff --git a/arch/arm64/boot/dts/apple/t8132.dtsi b/arch/arm64/boot/dts/apple/t8132.dtsi index c82816625efd0..e94528277a805 100644 --- a/arch/arm64/boot/dts/apple/t8132.dtsi +++ b/arch/arm64/boot/dts/apple/t8132.dtsi @@ -221,34 +221,42 @@ opp-900000000 { opp-hz = /bits/ 64 <900000000>; opp-level = <1>; + opp-microwatt = <31100>; }; opp-1080000000 { opp-hz = /bits/ 64 <1080000000>; opp-level = <2>; + opp-microwatt = <37800>; }; opp-1476000000 { opp-hz = /bits/ 64 <1476000000>; opp-level = <3>; + opp-microwatt = <54000>; }; opp-1860000000 { opp-hz = /bits/ 64 <1860000000>; opp-level = <4>; + opp-microwatt = <81500>; }; opp-2172000000 { opp-hz = /bits/ 64 <2172000000>; opp-level = <5>; + opp-microwatt = <110500>; }; opp-2496000000 { opp-hz = /bits/ 64 <2496000000>; opp-level = <6>; + opp-microwatt = <155300>; }; opp-2808000000 { opp-hz = /bits/ 64 <2808000000>; opp-level = <7>; + opp-microwatt = <192800>; }; opp-2892000000 { opp-hz = /bits/ 64 <2892000000>; opp-level = <8>; + opp-microwatt = <193800>; }; }; @@ -259,83 +267,103 @@ opp-912000000 { opp-hz = /bits/ 64 <912000000>; opp-level = <1>; + opp-microwatt = <289000>; }; opp-1260000000 { opp-hz = /bits/ 64 <1260000000>; opp-level = <2>; + opp-microwatt = <396000>; }; opp-1512000000 { opp-hz = /bits/ 64 <1512000000>; opp-level = <3>; + opp-microwatt = <510000>; }; opp-1800000000 { opp-hz = /bits/ 64 <1800000000>; opp-level = <4>; + opp-microwatt = <632000>; }; opp-2088000000 { opp-hz = /bits/ 64 <2088000000>; opp-level = <5>; + opp-microwatt = <818000>; }; opp-2352000000 { opp-hz = /bits/ 64 <2352000000>; opp-level = <6>; + opp-microwatt = <1014000>; }; opp-2616000000 { opp-hz = /bits/ 64 <2616000000>; opp-level = <7>; + opp-microwatt = <1188000>; }; opp-2868000000 { opp-hz = /bits/ 64 <2868000000>; opp-level = <8>; + opp-microwatt = <1456000>; }; opp-3096000000 { opp-hz = /bits/ 64 <3096000000>; opp-level = <9>; + opp-microwatt = <1733000>; }; opp-3300000000 { opp-hz = /bits/ 64 <3300000000>; opp-level = <10>; + opp-microwatt = <2031000>; }; opp-3468000000 { opp-hz = /bits/ 64 <3468000000>; opp-level = <11>; + opp-microwatt = <2337000>; }; opp-3624000000 { opp-hz = /bits/ 64 <3624000000>; opp-level = <12>; + opp-microwatt = <2485000>; }; opp-3756000000 { opp-hz = /bits/ 64 <3756000000>; opp-level = <13>; + opp-microwatt = <2763000>; }; opp-3876000000 { opp-hz = /bits/ 64 <3876000000>; opp-level = <14>; + opp-microwatt = <3192000>; }; opp-3936000000 { opp-hz = /bits/ 64 <3936000000>; opp-level = <15>; + opp-microwatt = <3077000>; }; opp-3984000000 { opp-hz = /bits/ 64 <3984000000>; opp-level = <16>; + opp-microwatt = <3067000>; }; opp-4044000000 { opp-hz = /bits/ 64 <4044000000>; opp-level = <17>; + opp-microwatt = <3062000>; }; opp-4104000000 { opp-hz = /bits/ 64 <4104000000>; opp-level = <18>; + opp-microwatt = <3062000>; }; opp-4416000000 { opp-hz = /bits/ 64 <4416000000>; opp-level = <19>; + opp-microwatt = <5389000>; turbo-mode; }; opp-4464000000 { opp-hz = /bits/ 64 <4464000000>; opp-level = <20>; + opp-microwatt = <5006000>; turbo-mode; }; }; @@ -895,6 +923,7 @@ #phy-cells = <1>; #reset-cells = <0>; + power-domains = <&ps_atc0_usb>; orientation-switch; mode-switch; @@ -908,6 +937,7 @@ interrupt-parent = <&aic>; interrupts = ; #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; }; dwc3_0_dart_1: iommu@402f80000 { @@ -917,6 +947,7 @@ interrupt-parent = <&aic>; interrupts = ; #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; }; dwc3_0: usb@402280000 { @@ -934,6 +965,7 @@ role-switch-default-mode = "host"; iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>; + power-domains = <&ps_atc0_usb>; resets = <&atcphy0>; phys = <&atcphy0 PHY_TYPE_USB2>; phy-names = "usb2-phy"; @@ -950,6 +982,7 @@ #phy-cells = <1>; #reset-cells = <0>; + power-domains = <&ps_atc1_usb>; orientation-switch; mode-switch; @@ -963,6 +996,7 @@ interrupt-parent = <&aic>; interrupts = ; #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; }; dwc3_1_dart_1: iommu@40af80000 { @@ -972,6 +1006,7 @@ interrupt-parent = <&aic>; interrupts = ; #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; }; dwc3_1: usb@40a280000 { @@ -989,6 +1024,7 @@ role-switch-default-mode = "host"; iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>; + power-domains = <&ps_atc1_usb>; resets = <&atcphy1>; phys = <&atcphy1 PHY_TYPE_USB2>; phy-names = "usb2-phy"; diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index 783e41230238c..1ca03ba6bdbdc 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -3,40 +3,27 @@ # olddefconfig and verify that the requested settings survive resolution. # Device-specific boot, debug and distribution policy stays in the base. CONFIG_RUST=y -CONFIG_ARM64_ACTLR_STATE=y -CONFIG_ARCH_APPLE=y -# CONFIG_ARM64_4K_PAGES is not set CONFIG_ARM64_16K_PAGES=y -# CONFIG_ARM64_64K_PAGES is not set +CONFIG_ARM64_VA_BITS_48=y +CONFIG_ARM64_USER4K_EXPERIMENTAL=y CONFIG_ARM64_MEMORY_MODEL_CONTROL=y CONFIG_ARM_APPLE_CPUIDLE=y -CONFIG_ARM_APPLE_SOC_CPUFREQ=m +CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR=y CONFIG_BT_BRCMEXT=y CONFIG_BT_HCIBCM4377=m CONFIG_BT_MTK7932_PCIE=m CONFIG_PCIE_APPLE=y -CONFIG_NVME_APPLE=m CONFIG_MMC_SDHCI_PCI=m -CONFIG_BRCMFMAC=m CONFIG_BRCMFMAC_PCIE=y CONFIG_PCIE_APPLE_PIODMA_DIAG=y CONFIG_PCIEASPM=y CONFIG_NET_VENDOR_INTEL=y -CONFIG_IGB=m CONFIG_IGC=m CONFIG_MT7932_FULLMAC=m -CONFIG_TOUCHSCREEN_APPLE_Z2=m CONFIG_INPUT_MACSMC_INPUT=m -CONFIG_I2C_APPLE=m -CONFIG_SPI_APPLE=m CONFIG_SPI_APPLE_QSPI=m -CONFIG_SPMI_APPLE=m -CONFIG_PINCTRL_APPLE_GPIO=m -CONFIG_GPIO_MACSMC=m -CONFIG_POWER_RESET_MACSMC=m CONFIG_MACSMC_POWER=m -CONFIG_SENSORS_MACSMC_HWMON=m CONFIG_SENSORS_APPLE_PMP_J613=m CONFIG_THERMAL_NETLINK=y CONFIG_NVME_HWMON=y @@ -45,6 +32,7 @@ CONFIG_VIDEO_APPLE_AVD=m CONFIG_VIDEO_APPLE_ISP=m CONFIG_DRM=y CONFIG_DRM_ASAHI=m +CONFIG_DRM_ASAHI_G15=y CONFIG_DRM_ASAHI_G16=y CONFIG_DRM_ADP=m CONFIG_DRM_APPLE=m @@ -56,76 +44,49 @@ CONFIG_DRM_SIMPLEDRM=y CONFIG_SYSFB_SIMPLEFB=y CONFIG_SND_SOC_APPLE_AOP_AUDIO=m CONFIG_SND_SOC_APPLE_T8140_AOP_AUDIO=m -CONFIG_SND_SOC_APPLE_MCA=m CONFIG_SND_SOC_APPLE_MACAUDIO=m CONFIG_SND_SOC_CS42L83=m CONFIG_SND_SOC_CS42L84=m -CONFIG_SND_SOC_MAX98357A=m CONFIG_SND_SOC_TAS2764=m CONFIG_SND_SOC_TAS2770=m CONFIG_HID_APPLE=m CONFIG_HID_MAGICMOUSE=m CONFIG_HID_APPLE_MTP_HAPTIC=m -CONFIG_SERIAL_SAMSUNG=y -CONFIG_SERIAL_SAMSUNG_CONSOLE=y CONFIG_HID_DOCKCHANNEL=m CONFIG_SPI_HID_APPLE_OF=m -CONFIG_SPI_HID_APPLE_CORE=m CONFIG_USB_DWC3_APPLE=m -CONFIG_TYPEC=m CONFIG_USB4=m CONFIG_USB4_APPLE_SOC=m CONFIG_USB_XHCI_PCI_ASMEDIA=y CONFIG_TYPEC_SN201202X=m -CONFIG_RTC_DRV_MACSMC=m CONFIG_RTC_DRV_APPLE_PMU=m -CONFIG_APPLE_ADMAC=m CONFIG_APPLE_SIO=m -CONFIG_APPLE_SIO_T6030=y -CONFIG_MFD_MACSMC=m -CONFIG_COMMON_CLK_APPLE_NCO=m CONFIG_APPLE_DART=y CONFIG_APPLE_DART_J713_DCP=y CONFIG_APPLE_DOCKCHANNEL=m CONFIG_SERIAL_APPLE_DOCKCHANNEL=y CONFIG_SERIAL_APPLE_DOCKCHANNEL_EARLYCON=y -CONFIG_APPLE_MAILBOX=y CONFIG_APPLE_PMGR_MISC=y -CONFIG_APPLE_RTKIT=y CONFIG_APPLE_RTKIT_HELPER=m -CONFIG_APPLE_SART=m -CONFIG_RUST_APPLE_RTKIT=y -CONFIG_APPLE_AOP=m -CONFIG_HW_RANDOM=y -CONFIG_KEYS=y CONFIG_TRUSTED_KEYS=y # CONFIG_TRUSTED_KEYS_TPM is not set # CONFIG_TRUSTED_KEYS_TEE is not set CONFIG_APPLE_SEP=m CONFIG_APPLE_PMGR_PWRSTATE=y CONFIG_APPLE_PMP=m -CONFIG_APPLE_PMP_REPORT=y CONFIG_APPLE_PMP_THERMAL=m CONFIG_APPLE_T8132_PLATFORM_HANDOFF=y CONFIG_IIO_AOP_SENSOR_LAS=m CONFIG_IIO_AOP_SENSOR_ALS=m -CONFIG_RUST_FW_LOADER_ABSTRACTIONS=y -CONFIG_PWM_APPLE=m -CONFIG_APPLE_AIC=y CONFIG_PHY_APPLE_ATC=m CONFIG_PHY_APPLE_T6040_USB2=m CONFIG_PHY_APPLE_DPTX=m -CONFIG_APPLE_M1_CPU_PMU=y -CONFIG_NVMEM_APPLE_EFUSES=m -CONFIG_NVMEM_APPLE_SPMI=m CONFIG_MUX_APPLE_DPXBAR=m -CONFIG_TYPEC_TPS6598X=m CONFIG_RESET_APPLE_CIO=y CONFIG_INFINIBAND=m CONFIG_INFINIBAND_USER_ACCESS=m CONFIG_INFINIBAND_USER_MAD=m CONFIG_RDMA_RXE=m CONFIG_RDMA_SIW=m -CONFIG_DMABUF_HEAPS=y CONFIG_DMABUF_HEAPS_SYSTEM=y CONFIG_UDMABUF=y diff --git a/arch/arm64/configs/aurora_j700_defconfig b/arch/arm64/configs/aurora_j700_defconfig deleted file mode 100644 index 49e5c03d106ea..0000000000000 --- a/arch/arm64/configs/aurora_j700_defconfig +++ /dev/null @@ -1,2895 +0,0 @@ -CONFIG_WERROR=y -CONFIG_LOCALVERSION="-aurora" -# CONFIG_LOCALVERSION_AUTO is not set -CONFIG_SYSVIPC=y -CONFIG_POSIX_MQUEUE=y -CONFIG_AUDIT=y -CONFIG_NO_HZ_IDLE=y -CONFIG_HIGH_RES_TIMERS=y -CONFIG_BPF_SYSCALL=y -CONFIG_BPF_JIT=y -CONFIG_PREEMPT=y -CONFIG_IRQ_TIME_ACCOUNTING=y -CONFIG_BSD_PROCESS_ACCT=y -CONFIG_BSD_PROCESS_ACCT_V3=y -CONFIG_TASKSTATS=y -CONFIG_TASK_XACCT=y -CONFIG_TASK_IO_ACCOUNTING=y -CONFIG_IKCONFIG=y -CONFIG_IKCONFIG_PROC=y -CONFIG_UCLAMP_TASK=y -CONFIG_NUMA_BALANCING=y -CONFIG_MEMCG=y -CONFIG_BLK_CGROUP=y -CONFIG_UCLAMP_TASK_GROUP=y -CONFIG_CGROUP_PIDS=y -CONFIG_CGROUP_FREEZER=y -CONFIG_CGROUP_HUGETLB=y -CONFIG_CPUSETS=y -CONFIG_CGROUP_DEVICE=y -CONFIG_CGROUP_CPUACCT=y -CONFIG_CGROUP_PERF=y -CONFIG_CGROUP_BPF=y -CONFIG_CGROUP_MISC=y -CONFIG_USER_NS=y -CONFIG_CHECKPOINT_RESTORE=y -CONFIG_SCHED_AUTOGROUP=y -CONFIG_BLK_DEV_INITRD=y -CONFIG_KALLSYMS_ALL=y -CONFIG_PROFILING=y -CONFIG_RUST=y -CONFIG_ARCH_ACTIONS=y -CONFIG_ARCH_AIROHA=y -CONFIG_ARCH_SUNXI=y -CONFIG_ARCH_ALPINE=y -CONFIG_ARCH_APPLE=y -CONFIG_ARCH_ARTPEC=y -CONFIG_ARCH_AXIADO=y -CONFIG_ARCH_BCM=y -CONFIG_ARCH_BCM2835=y -CONFIG_ARCH_BCM_IPROC=y -CONFIG_ARCH_BCMBCA=y -CONFIG_ARCH_BRCMSTB=y -CONFIG_ARCH_BERLIN=y -CONFIG_ARCH_BLAIZE=y -CONFIG_ARCH_BST=y -CONFIG_ARCH_CIX=y -CONFIG_ARCH_EXYNOS=y -CONFIG_ARCH_K3=y -CONFIG_ARCH_LG1K=y -CONFIG_ARCH_HISI=y -CONFIG_ARCH_KEEMBAY=y -CONFIG_ARCH_MEDIATEK=y -CONFIG_ARCH_MESON=y -CONFIG_ARCH_MICROCHIP=y -CONFIG_ARCH_SPARX5=y -CONFIG_ARCH_MVEBU=y -CONFIG_ARCH_NXP=y -CONFIG_ARCH_LAYERSCAPE=y -CONFIG_ARCH_MXC=y -CONFIG_ARCH_S32=y -CONFIG_ARCH_MA35=y -CONFIG_ARCH_NPCM=y -CONFIG_ARCH_QCOM=y -CONFIG_ARCH_REALTEK=y -CONFIG_ARCH_RENESAS=y -CONFIG_ARCH_ROCKCHIP=y -CONFIG_ARCH_SEATTLE=y -CONFIG_ARCH_INTEL_SOCFPGA=y -CONFIG_ARCH_SOPHGO=y -CONFIG_ARCH_STM32=y -CONFIG_ARCH_SYNQUACER=y -CONFIG_ARCH_TEGRA=y -CONFIG_ARCH_TESLA_FSD=y -CONFIG_ARCH_SPRD=y -CONFIG_ARCH_THUNDER=y -CONFIG_ARCH_THUNDER2=y -CONFIG_ARCH_UNIPHIER=y -CONFIG_ARCH_VEXPRESS=y -CONFIG_ARCH_VISCONTI=y -CONFIG_ARCH_XGENE=y -CONFIG_ARCH_ZYNQMP=y -CONFIG_ARM64_16K_PAGES=y -CONFIG_ARM64_VA_BITS_48=y -CONFIG_NUMA=y -CONFIG_XEN=y -CONFIG_ARM64_MEMORY_MODEL_CONTROL=y -CONFIG_HIBERNATION=y -CONFIG_PM_DEBUG=y -CONFIG_PM_ADVANCED_DEBUG=y -CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y -CONFIG_ENERGY_MODEL=y -CONFIG_ARM_PSCI_CPUIDLE=y -CONFIG_CPU_FREQ=y -CONFIG_CPU_FREQ_STAT=y -CONFIG_CPU_FREQ_GOV_POWERSAVE=m -CONFIG_CPU_FREQ_GOV_USERSPACE=y -CONFIG_CPU_FREQ_GOV_ONDEMAND=y -CONFIG_CPU_FREQ_GOV_CONSERVATIVE=m -CONFIG_CPUFREQ_DT=y -CONFIG_ARM_ALLWINNER_SUN50I_CPUFREQ_NVMEM=m -CONFIG_ARM_APPLE_SOC_CPUFREQ=y -CONFIG_ARM_ARMADA_37XX_CPUFREQ=y -CONFIG_ARM_SCPI_CPUFREQ=y -CONFIG_ARM_IMX_CPUFREQ_DT=m -CONFIG_ARM_MEDIATEK_CPUFREQ=y -CONFIG_ARM_QCOM_CPUFREQ_NVMEM=y -CONFIG_ARM_QCOM_CPUFREQ_HW=y -CONFIG_ARM_RASPBERRYPI_CPUFREQ=m -CONFIG_ARM_SCMI_CPUFREQ=y -CONFIG_ARM_TEGRA186_CPUFREQ=y -CONFIG_QORIQ_CPUFREQ=y -CONFIG_ACPI_CPPC_CPUFREQ=m -CONFIG_ACPI=y -CONFIG_ACPI_HOTPLUG_MEMORY=y -CONFIG_ACPI_HMAT=y -CONFIG_ACPI_APEI=y -CONFIG_ACPI_APEI_GHES=y -CONFIG_ACPI_APEI_PCIEAER=y -CONFIG_ACPI_APEI_MEMORY_FAILURE=y -CONFIG_ACPI_APEI_EINJ=y -CONFIG_VIRTUALIZATION=y -CONFIG_KVM=y -CONFIG_COMPAT_32BIT_TIME=y -# CONFIG_RELR is not set -CONFIG_MODULES=y -CONFIG_MODULE_UNLOAD=y -CONFIG_PARTITION_ADVANCED=y -CONFIG_AIX_PARTITION=y -CONFIG_OSF_PARTITION=y -CONFIG_MAC_PARTITION=y -CONFIG_BSD_DISKLABEL=y -CONFIG_MINIX_SUBPARTITION=y -CONFIG_SOLARIS_X86_PARTITION=y -CONFIG_UNIXWARE_DISKLABEL=y -CONFIG_LDM_PARTITION=y -CONFIG_SGI_PARTITION=y -CONFIG_SUN_PARTITION=y -# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set -CONFIG_BINFMT_MISC=m -# CONFIG_COMPAT_BRK is not set -CONFIG_MEMORY_HOTPLUG=y -CONFIG_MEMORY_HOTREMOVE=y -CONFIG_KSM=y -CONFIG_MEMORY_FAILURE=y -CONFIG_TRANSPARENT_HUGEPAGE=y -CONFIG_PAGE_BLOCK_MAX_ORDER=10 -CONFIG_USERFAULTFD=y -CONFIG_NET=y -CONFIG_PACKET=y -CONFIG_PACKET_DIAG=m -CONFIG_UNIX_DIAG=m -CONFIG_TLS=m -CONFIG_XFRM_USER=m -CONFIG_XFRM_INTERFACE=m -CONFIG_XFRM_STATISTICS=y -CONFIG_NET_KEY=m -CONFIG_NET_KEY_MIGRATE=y -CONFIG_IP_MULTICAST=y -CONFIG_IP_ADVANCED_ROUTER=y -CONFIG_IP_MULTIPLE_TABLES=y -CONFIG_IP_ROUTE_MULTIPATH=y -CONFIG_IP_PNP=y -CONFIG_IP_PNP_DHCP=y -CONFIG_IP_PNP_BOOTP=y -CONFIG_NET_IPIP=m -CONFIG_NET_IPGRE_DEMUX=m -CONFIG_NET_IPGRE=m -CONFIG_NET_IPGRE_BROADCAST=y -CONFIG_NET_IPVTI=m -CONFIG_NET_FOU_IP_TUNNELS=y -CONFIG_INET_AH=m -CONFIG_INET_ESP=m -CONFIG_INET_ESP_OFFLOAD=m -CONFIG_INET_ESPINTCP=y -CONFIG_INET_IPCOMP=m -CONFIG_INET_UDP_DIAG=m -CONFIG_INET_RAW_DIAG=m -CONFIG_INET_DIAG_DESTROY=y -CONFIG_TCP_CONG_ADVANCED=y -CONFIG_TCP_CONG_HSTCP=m -CONFIG_TCP_CONG_HYBLA=m -CONFIG_TCP_CONG_NV=m -CONFIG_TCP_CONG_SCALABLE=m -CONFIG_TCP_CONG_LP=m -CONFIG_TCP_CONG_VENO=m -CONFIG_TCP_CONG_YEAH=m -CONFIG_TCP_CONG_ILLINOIS=m -CONFIG_TCP_CONG_DCTCP=m -CONFIG_TCP_CONG_CDG=m -CONFIG_TCP_CONG_BBR=m -CONFIG_INET6_AH=m -CONFIG_INET6_ESP=m -CONFIG_INET6_ESP_OFFLOAD=m -CONFIG_INET6_ESPINTCP=y -CONFIG_INET6_IPCOMP=m -CONFIG_IPV6_VTI=m -CONFIG_IPV6_GRE=m -CONFIG_IPV6_MULTIPLE_TABLES=y -CONFIG_NETFILTER=y -CONFIG_BRIDGE_NETFILTER=m -CONFIG_NETFILTER_NETLINK_HOOK=m -CONFIG_NF_CONNTRACK=m -CONFIG_NF_CONNTRACK_ZONES=y -CONFIG_NF_CONNTRACK_PROCFS=y -CONFIG_NF_CONNTRACK_EVENTS=y -CONFIG_NF_CONNTRACK_TIMEOUT=y -CONFIG_NF_CONNTRACK_TIMESTAMP=y -CONFIG_NF_CONNTRACK_AMANDA=m -CONFIG_NF_CONNTRACK_FTP=m -CONFIG_NF_CONNTRACK_H323=m -CONFIG_NF_CONNTRACK_IRC=m -CONFIG_NF_CONNTRACK_NETBIOS_NS=m -CONFIG_NF_CONNTRACK_SNMP=m -CONFIG_NF_CONNTRACK_PPTP=m -CONFIG_NF_CONNTRACK_SANE=m -CONFIG_NF_CONNTRACK_SIP=m -CONFIG_NF_CONNTRACK_TFTP=m -CONFIG_NF_CT_NETLINK=m -CONFIG_NF_CT_NETLINK_TIMEOUT=m -CONFIG_NF_CT_NETLINK_HELPER=m -CONFIG_NETFILTER_NETLINK_GLUE_CT=y -CONFIG_NF_TABLES=m -CONFIG_NF_TABLES_INET=y -CONFIG_NF_TABLES_NETDEV=y -CONFIG_NFT_NUMGEN=m -CONFIG_NFT_CT=m -CONFIG_NFT_FLOW_OFFLOAD=m -CONFIG_NFT_CONNLIMIT=m -CONFIG_NFT_LOG=m -CONFIG_NFT_LIMIT=m -CONFIG_NFT_MASQ=m -CONFIG_NFT_REDIR=m -CONFIG_NFT_NAT=m -CONFIG_NFT_TUNNEL=m -CONFIG_NFT_QUEUE=m -CONFIG_NFT_QUOTA=m -CONFIG_NFT_REJECT=m -CONFIG_NFT_COMPAT=m -CONFIG_NFT_HASH=m -CONFIG_NFT_FIB_INET=m -CONFIG_NFT_XFRM=m -CONFIG_NFT_SOCKET=m -CONFIG_NFT_OSF=m -CONFIG_NFT_TPROXY=m -CONFIG_NFT_DUP_NETDEV=m -CONFIG_NFT_FWD_NETDEV=m -CONFIG_NFT_FIB_NETDEV=m -CONFIG_NFT_REJECT_NETDEV=m -CONFIG_NF_FLOW_TABLE_INET=m -CONFIG_NF_FLOW_TABLE=m -CONFIG_NF_FLOW_TABLE_PROCFS=y -CONFIG_NETFILTER_XTABLES_LEGACY=y -CONFIG_NETFILTER_XT_SET=m -CONFIG_NETFILTER_XT_TARGET_AUDIT=m -CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m -CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m -CONFIG_NETFILTER_XT_TARGET_CONNMARK=m -CONFIG_NETFILTER_XT_TARGET_DSCP=m -CONFIG_NETFILTER_XT_TARGET_HMARK=m -CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m -CONFIG_NETFILTER_XT_TARGET_LED=m -CONFIG_NETFILTER_XT_TARGET_LOG=m -CONFIG_NETFILTER_XT_TARGET_MARK=m -CONFIG_NETFILTER_XT_TARGET_NFLOG=m -CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m -CONFIG_NETFILTER_XT_TARGET_NOTRACK=m -CONFIG_NETFILTER_XT_TARGET_TEE=m -CONFIG_NETFILTER_XT_TARGET_TPROXY=m -CONFIG_NETFILTER_XT_TARGET_TRACE=m -CONFIG_NETFILTER_XT_TARGET_TCPMSS=m -CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m -CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m -CONFIG_NETFILTER_XT_MATCH_BPF=m -CONFIG_NETFILTER_XT_MATCH_CGROUP=m -CONFIG_NETFILTER_XT_MATCH_CLUSTER=m -CONFIG_NETFILTER_XT_MATCH_COMMENT=m -CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m -CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m -CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m -CONFIG_NETFILTER_XT_MATCH_CONNMARK=m -CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m -CONFIG_NETFILTER_XT_MATCH_CPU=m -CONFIG_NETFILTER_XT_MATCH_DCCP=m -CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m -CONFIG_NETFILTER_XT_MATCH_DSCP=m -CONFIG_NETFILTER_XT_MATCH_ESP=m -CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m -CONFIG_NETFILTER_XT_MATCH_HELPER=m -CONFIG_NETFILTER_XT_MATCH_IPCOMP=m -CONFIG_NETFILTER_XT_MATCH_IPRANGE=m -CONFIG_NETFILTER_XT_MATCH_IPVS=m -CONFIG_NETFILTER_XT_MATCH_LENGTH=m -CONFIG_NETFILTER_XT_MATCH_LIMIT=m -CONFIG_NETFILTER_XT_MATCH_MAC=m -CONFIG_NETFILTER_XT_MATCH_MARK=m -CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m -CONFIG_NETFILTER_XT_MATCH_NFACCT=m -CONFIG_NETFILTER_XT_MATCH_OSF=m -CONFIG_NETFILTER_XT_MATCH_OWNER=m -CONFIG_NETFILTER_XT_MATCH_POLICY=m -CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m -CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m -CONFIG_NETFILTER_XT_MATCH_QUOTA=m -CONFIG_NETFILTER_XT_MATCH_RATEEST=m -CONFIG_NETFILTER_XT_MATCH_REALM=m -CONFIG_NETFILTER_XT_MATCH_RECENT=m -CONFIG_NETFILTER_XT_MATCH_SCTP=m -CONFIG_NETFILTER_XT_MATCH_SOCKET=m -CONFIG_NETFILTER_XT_MATCH_STATE=m -CONFIG_NETFILTER_XT_MATCH_STATISTIC=m -CONFIG_NETFILTER_XT_MATCH_STRING=m -CONFIG_NETFILTER_XT_MATCH_TCPMSS=m -CONFIG_NETFILTER_XT_MATCH_TIME=m -CONFIG_NETFILTER_XT_MATCH_U32=m -CONFIG_IP_SET=m -CONFIG_IP_SET_BITMAP_IP=m -CONFIG_IP_SET_BITMAP_IPMAC=m -CONFIG_IP_SET_BITMAP_PORT=m -CONFIG_IP_SET_HASH_IP=m -CONFIG_IP_SET_HASH_IPMARK=m -CONFIG_IP_SET_HASH_IPPORT=m -CONFIG_IP_SET_HASH_IPPORTIP=m -CONFIG_IP_SET_HASH_IPPORTNET=m -CONFIG_IP_SET_HASH_IPMAC=m -CONFIG_IP_SET_HASH_MAC=m -CONFIG_IP_SET_HASH_NETPORTNET=m -CONFIG_IP_SET_HASH_NET=m -CONFIG_IP_SET_HASH_NETNET=m -CONFIG_IP_SET_HASH_NETPORT=m -CONFIG_IP_SET_HASH_NETIFACE=m -CONFIG_IP_SET_LIST_SET=m -CONFIG_IP_VS=m -CONFIG_NFT_DUP_IPV4=m -CONFIG_NFT_FIB_IPV4=m -CONFIG_NF_TABLES_ARP=y -CONFIG_NF_LOG_ARP=m -CONFIG_NF_LOG_IPV4=m -CONFIG_IP_NF_IPTABLES=m -CONFIG_IP_NF_MATCH_AH=m -CONFIG_IP_NF_MATCH_ECN=m -CONFIG_IP_NF_MATCH_RPFILTER=m -CONFIG_IP_NF_MATCH_TTL=m -CONFIG_IP_NF_TARGET_REJECT=m -CONFIG_IP_NF_NAT=m -CONFIG_IP_NF_TARGET_MASQUERADE=m -CONFIG_IP_NF_TARGET_NETMAP=m -CONFIG_IP_NF_TARGET_REDIRECT=m -CONFIG_IP_NF_TARGET_ECN=m -CONFIG_IP_NF_TARGET_TTL=m -CONFIG_IP_NF_RAW=m -CONFIG_IP_NF_ARPFILTER=m -CONFIG_IP_NF_ARP_MANGLE=m -CONFIG_NFT_DUP_IPV6=m -CONFIG_NFT_FIB_IPV6=m -CONFIG_IP6_NF_IPTABLES=m -CONFIG_IP6_NF_MATCH_AH=m -CONFIG_IP6_NF_MATCH_EUI64=m -CONFIG_IP6_NF_MATCH_FRAG=m -CONFIG_IP6_NF_MATCH_OPTS=m -CONFIG_IP6_NF_MATCH_HL=m -CONFIG_IP6_NF_MATCH_IPV6HEADER=m -CONFIG_IP6_NF_MATCH_MH=m -CONFIG_IP6_NF_MATCH_RPFILTER=m -CONFIG_IP6_NF_MATCH_RT=m -CONFIG_IP6_NF_MATCH_SRH=m -CONFIG_IP6_NF_TARGET_HL=m -CONFIG_IP6_NF_TARGET_REJECT=m -CONFIG_IP6_NF_RAW=m -CONFIG_IP6_NF_NAT=m -CONFIG_IP6_NF_TARGET_MASQUERADE=m -CONFIG_IP6_NF_TARGET_NPT=m -CONFIG_NF_TABLES_BRIDGE=m -CONFIG_NFT_BRIDGE_META=m -CONFIG_NFT_BRIDGE_REJECT=m -CONFIG_NF_CONNTRACK_BRIDGE=m -CONFIG_BRIDGE_NF_EBTABLES_LEGACY=m -CONFIG_BRIDGE_NF_EBTABLES=m -CONFIG_BRIDGE_EBT_BROUTE=m -CONFIG_BRIDGE_EBT_T_FILTER=m -CONFIG_BRIDGE_EBT_T_NAT=m -CONFIG_BRIDGE_EBT_802_3=m -CONFIG_BRIDGE_EBT_AMONG=m -CONFIG_BRIDGE_EBT_ARP=m -CONFIG_BRIDGE_EBT_IP=m -CONFIG_BRIDGE_EBT_IP6=m -CONFIG_BRIDGE_EBT_LIMIT=m -CONFIG_BRIDGE_EBT_MARK=m -CONFIG_BRIDGE_EBT_PKTTYPE=m -CONFIG_BRIDGE_EBT_STP=m -CONFIG_BRIDGE_EBT_VLAN=m -CONFIG_BRIDGE_EBT_ARPREPLY=m -CONFIG_BRIDGE_EBT_DNAT=m -CONFIG_BRIDGE_EBT_MARK_T=m -CONFIG_BRIDGE_EBT_REDIRECT=m -CONFIG_BRIDGE_EBT_SNAT=m -CONFIG_BRIDGE_EBT_LOG=m -CONFIG_BRIDGE_EBT_NFLOG=m -CONFIG_L2TP=m -CONFIG_L2TP_V3=y -CONFIG_L2TP_IP=m -CONFIG_L2TP_ETH=m -CONFIG_BRIDGE=m -CONFIG_BRIDGE_VLAN_FILTERING=y -CONFIG_NET_DSA=m -CONFIG_VLAN_8021Q=m -CONFIG_VLAN_8021Q_GVRP=y -CONFIG_VLAN_8021Q_MVRP=y -CONFIG_NET_SCHED=y -CONFIG_NET_SCH_HTB=m -CONFIG_NET_SCH_HFSC=m -CONFIG_NET_SCH_PRIO=m -CONFIG_NET_SCH_MULTIQ=m -CONFIG_NET_SCH_RED=m -CONFIG_NET_SCH_SFB=m -CONFIG_NET_SCH_SFQ=m -CONFIG_NET_SCH_TEQL=m -CONFIG_NET_SCH_TBF=m -CONFIG_NET_SCH_CBS=m -CONFIG_NET_SCH_ETF=m -CONFIG_NET_SCH_TAPRIO=m -CONFIG_NET_SCH_GRED=m -CONFIG_NET_SCH_NETEM=m -CONFIG_NET_SCH_DRR=m -CONFIG_NET_SCH_MQPRIO=m -CONFIG_NET_SCH_CHOKE=m -CONFIG_NET_SCH_QFQ=m -CONFIG_NET_SCH_CODEL=m -CONFIG_NET_SCH_FQ_CODEL=m -CONFIG_NET_SCH_CAKE=m -CONFIG_NET_SCH_FQ=m -CONFIG_NET_SCH_HHF=m -CONFIG_NET_SCH_PIE=m -CONFIG_NET_SCH_FQ_PIE=m -CONFIG_NET_SCH_INGRESS=m -CONFIG_NET_SCH_PLUG=m -CONFIG_NET_SCH_ETS=m -CONFIG_NET_SCH_DUALPI2=m -CONFIG_NET_CLS_BASIC=m -CONFIG_NET_CLS_ROUTE4=m -CONFIG_NET_CLS_FW=m -CONFIG_NET_CLS_U32=m -CONFIG_CLS_U32_PERF=y -CONFIG_CLS_U32_MARK=y -CONFIG_NET_CLS_FLOW=m -CONFIG_NET_CLS_BPF=m -CONFIG_NET_CLS_FLOWER=m -CONFIG_NET_CLS_MATCHALL=m -CONFIG_NET_EMATCH=y -CONFIG_NET_EMATCH_CMP=m -CONFIG_NET_EMATCH_NBYTE=m -CONFIG_NET_EMATCH_U32=m -CONFIG_NET_EMATCH_META=m -CONFIG_NET_EMATCH_TEXT=m -CONFIG_NET_EMATCH_IPSET=m -CONFIG_NET_EMATCH_IPT=m -CONFIG_NET_CLS_ACT=y -CONFIG_NET_ACT_POLICE=m -CONFIG_NET_ACT_GACT=m -CONFIG_GACT_PROB=y -CONFIG_NET_ACT_MIRRED=m -CONFIG_NET_ACT_SAMPLE=m -CONFIG_NET_ACT_NAT=m -CONFIG_NET_ACT_PEDIT=m -CONFIG_NET_ACT_SIMP=m -CONFIG_NET_ACT_SKBEDIT=m -CONFIG_NET_ACT_CSUM=m -CONFIG_NET_ACT_MPLS=m -CONFIG_NET_ACT_VLAN=m -CONFIG_NET_ACT_BPF=m -CONFIG_NET_ACT_CONNMARK=m -CONFIG_NET_ACT_CTINFO=m -CONFIG_NET_ACT_SKBMOD=m -CONFIG_NET_ACT_TUNNEL_KEY=m -CONFIG_NET_ACT_CT=m -CONFIG_NET_ACT_GATE=m -CONFIG_VSOCKETS=m -CONFIG_NETLINK_DIAG=m -CONFIG_HSR=m -CONFIG_QRTR_SMD=m -CONFIG_QRTR_TUN=m -CONFIG_CAN=m -CONFIG_CAN_J1939=m -CONFIG_CAN_ISOTP=m -CONFIG_BT=m -CONFIG_BT_RFCOMM=m -CONFIG_BT_RFCOMM_TTY=y -CONFIG_BT_BNEP=m -CONFIG_BT_BNEP_MC_FILTER=y -CONFIG_BT_BNEP_PROTO_FILTER=y -CONFIG_BT_HIDP=m -CONFIG_BT_LEDS=y -CONFIG_BT_MSFTEXT=y -CONFIG_BT_AOSPEXT=y -CONFIG_BT_BRCMEXT=y -# CONFIG_BT_DEBUGFS is not set -CONFIG_BT_MTK7932_PCIE=m -CONFIG_BT_HCIBTUSB=m -CONFIG_BT_HCIBTUSB_AUTOSUSPEND=y -CONFIG_BT_HCIBTUSB_MTK=y -CONFIG_BT_HCIUART=m -CONFIG_BT_HCIUART_LL=y -CONFIG_BT_HCIUART_BCM=y -CONFIG_BT_HCIUART_QCA=y -CONFIG_BT_HCIUART_MRVL=y -CONFIG_BT_HCIBCM203X=m -CONFIG_BT_HCIBCM4377=m -CONFIG_BT_HCIBPA10X=m -CONFIG_BT_HCIBFUSB=m -CONFIG_BT_HCIVHCI=m -CONFIG_BT_MRVL=m -CONFIG_BT_MRVL_SDIO=m -CONFIG_BT_ATH3K=m -CONFIG_BT_QCOMSMD=m -CONFIG_BT_NXPUART=m -CONFIG_CFG80211=m -CONFIG_MAC80211=m -CONFIG_RFKILL=y -CONFIG_RFKILL_GPIO=m -CONFIG_NET_9P=y -CONFIG_NET_9P_VIRTIO=y -CONFIG_NFC=m -CONFIG_NFC_NCI=m -CONFIG_NFC_NXP_NCI=m -CONFIG_NFC_NXP_NCI_I2C=m -CONFIG_NFC_S3FWRN5_I2C=m -CONFIG_PCI=y -CONFIG_PCIEPORTBUS=y -CONFIG_PCIEAER=y -CONFIG_PCI_IOV=y -CONFIG_PCI_PASID=y -CONFIG_HOTPLUG_PCI=y -CONFIG_HOTPLUG_PCI_ACPI=y -CONFIG_PCI_AARDVARK=y -CONFIG_PCIE_ALTERA=y -CONFIG_PCIE_ALTERA_MSI=y -CONFIG_PCIE_APPLE=y -CONFIG_PCIE_APPLE_PIODMA_DIAG=y -CONFIG_PCIE_BRCMSTB=m -CONFIG_PCI_HOST_THUNDER_PEM=y -CONFIG_PCI_HOST_THUNDER_ECAM=y -CONFIG_PCI_HOST_GENERIC=y -CONFIG_PCIE_MEDIATEK_GEN3=m -CONFIG_PCI_TEGRA=y -CONFIG_PCIE_RCAR_HOST=y -CONFIG_PCIE_RCAR_EP=y -CONFIG_PCIE_RENESAS_RZG3S_HOST=y -CONFIG_PCIE_ROCKCHIP_HOST=m -CONFIG_PCI_XGENE=y -CONFIG_PCIE_XILINX=y -CONFIG_PCIE_XILINX_DMA_PL=y -CONFIG_PCIE_XILINX_NWL=y -CONFIG_PCIE_XILINX_CPM=y -CONFIG_PCI_J721E_HOST=m -CONFIG_PCI_IMX6_HOST=y -CONFIG_PCI_LAYERSCAPE=y -CONFIG_PCI_HISI=y -CONFIG_PCIE_KIRIN=y -CONFIG_PCIE_HISI_STB=y -CONFIG_PCIE_ARMADA_8K=y -CONFIG_PCIE_TEGRA194_HOST=m -CONFIG_PCIE_TEGRA194_EP=m -CONFIG_PCIE_QCOM=y -CONFIG_PCIE_RCAR_GEN4_HOST=m -CONFIG_PCIE_RCAR_GEN4_EP=m -CONFIG_PCIE_ROCKCHIP_DW_HOST=y -CONFIG_PCIE_VISCONTI_HOST=y -CONFIG_PCIE_LAYERSCAPE_GEN4=y -CONFIG_PCI_ENDPOINT=y -CONFIG_PCI_ENDPOINT_CONFIGFS=y -CONFIG_PCI_EPF_TEST=m -CONFIG_DEVTMPFS=y -CONFIG_DEVTMPFS_MOUNT=y -CONFIG_FW_LOADER_USER_HELPER=y -CONFIG_FW_LOADER_COMPRESS=y -CONFIG_FW_LOADER_COMPRESS_ZSTD=y -CONFIG_HISILICON_LPC=y -CONFIG_IMX_AIPSTZ=m -CONFIG_TEGRA_ACONNECT=m -CONFIG_MHI_BUS_PCI_GENERIC=m -CONFIG_ARM_SCPI_PROTOCOL=y -CONFIG_RASPBERRYPI_FIRMWARE=y -CONFIG_INTEL_STRATIX10_SERVICE=y -CONFIG_INTEL_STRATIX10_RSU=m -CONFIG_MTK_ADSP_IPC=m -CONFIG_SYSFB_SIMPLEFB=y -CONFIG_GOOGLE_FIRMWARE=y -CONFIG_GOOGLE_CBMEM=m -CONFIG_GOOGLE_COREBOOT_TABLE=m -CONFIG_EFI_CAPSULE_LOADER=y -CONFIG_IMX_SCU=y -CONFIG_QCOM_TZMEM_MODE_SHMBRIDGE=y -CONFIG_QCOM_QSEECOM=y -CONFIG_QCOM_QSEECOM_UEFISECAPP=y -CONFIG_EXYNOS_ACPM_PROTOCOL=m -CONFIG_TEGRA_BPMP=y -CONFIG_ZYNQMP_FIRMWARE_DEBUG=y -CONFIG_GNSS=m -CONFIG_GNSS_MTK_SERIAL=m -CONFIG_GNSS_USB=m -CONFIG_MTD=y -CONFIG_MTD_BLOCK=y -CONFIG_MTD_CFI=y -CONFIG_MTD_CFI_ADV_OPTIONS=y -CONFIG_MTD_CFI_INTELEXT=y -CONFIG_MTD_CFI_AMDSTD=y -CONFIG_MTD_CFI_STAA=y -CONFIG_MTD_PHYSMAP=y -CONFIG_MTD_PHYSMAP_OF=y -CONFIG_MTD_DATAFLASH=y -CONFIG_MTD_SST25L=y -CONFIG_MTD_RAW_NAND=y -CONFIG_MTD_NAND_DENALI_DT=y -CONFIG_MTD_NAND_MARVELL=y -CONFIG_MTD_NAND_BRCMNAND=m -CONFIG_MTD_NAND_FSL_IFC=y -CONFIG_MTD_NAND_QCOM=y -CONFIG_MTD_SPI_NAND=m -CONFIG_MTD_SPI_NOR=y -CONFIG_MTD_UBI=m -CONFIG_MTD_HYPERBUS=m -CONFIG_HBMC_AM654=m -CONFIG_OF_OVERLAY=y -CONFIG_ZRAM=y -CONFIG_BLK_DEV_LOOP=y -CONFIG_BLK_DEV_NBD=m -CONFIG_VIRTIO_BLK=y -CONFIG_BLK_DEV_NVME=y -CONFIG_NVME_HWMON=y -CONFIG_NVME_APPLE=y -CONFIG_QCOM_COINCELL=m -CONFIG_QCOM_FASTRPC=m -CONFIG_SRAM=y -CONFIG_PCI_ENDPOINT_TEST=m -CONFIG_XILINX_SDFEC=m -CONFIG_NTSYNC=m -CONFIG_EEPROM_AT24=m -CONFIG_EEPROM_AT25=m -CONFIG_MISC_RTSX_USB=m -CONFIG_UACCE=m -CONFIG_MISC_RP1=m -# CONFIG_SCSI_PROC_FS is not set -CONFIG_BLK_DEV_SD=y -CONFIG_BLK_DEV_SR=m -CONFIG_CHR_DEV_SG=m -CONFIG_SCSI_SAS_ATA=y -CONFIG_SCSI_HISI_SAS=y -CONFIG_SCSI_HISI_SAS_PCI=y -CONFIG_MEGARAID_SAS=y -CONFIG_SCSI_MPT3SAS=m -CONFIG_ATA=y -CONFIG_SATA_AHCI=y -CONFIG_SATA_AHCI_PLATFORM=y -CONFIG_AHCI_BRCM=m -CONFIG_AHCI_DWC=m -CONFIG_AHCI_CEVA=y -CONFIG_AHCI_MVEBU=y -CONFIG_AHCI_XGENE=y -CONFIG_AHCI_QORIQ=y -CONFIG_SATA_SIL24=y -CONFIG_SATA_RCAR=y -CONFIG_PATA_OF_PLATFORM=y -CONFIG_MD=y -CONFIG_MD_LINEAR=m -CONFIG_BLK_DEV_DM=m -CONFIG_DM_CRYPT=m -CONFIG_DM_SNAPSHOT=m -CONFIG_DM_THIN_PROVISIONING=m -CONFIG_DM_CACHE=m -CONFIG_DM_WRITECACHE=m -CONFIG_DM_MIRROR=m -CONFIG_DM_RAID=m -CONFIG_DM_ZERO=m -CONFIG_DM_UEVENT=y -CONFIG_DM_VERITY=m -CONFIG_DM_VERITY_FEC=y -CONFIG_DM_INTEGRITY=m -CONFIG_NETDEVICES=y -CONFIG_BONDING=m -CONFIG_DUMMY=m -CONFIG_WIREGUARD=m -CONFIG_OVPN=m -CONFIG_IFB=m -CONFIG_NET_TEAM=m -CONFIG_NET_TEAM_MODE_BROADCAST=m -CONFIG_NET_TEAM_MODE_ROUNDROBIN=m -CONFIG_NET_TEAM_MODE_RANDOM=m -CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m -CONFIG_NET_TEAM_MODE_LOADBALANCE=m -CONFIG_MACVLAN=m -CONFIG_MACVTAP=m -CONFIG_VXLAN=m -CONFIG_GENEVE=m -CONFIG_TUN=y -CONFIG_VETH=m -CONFIG_VIRTIO_NET=y -CONFIG_MHI_NET=m -CONFIG_NET_DSA_BCM_SF2=m -CONFIG_NET_DSA_MSCC_FELIX=m -CONFIG_ENA_ETHERNET=m -CONFIG_AMD_XGBE=y -CONFIG_NET_XGENE=y -CONFIG_ATL1C=m -CONFIG_BCMGENET=m -CONFIG_BNX2X=m -CONFIG_SYSTEMPORT=m -CONFIG_MACB=y -CONFIG_THUNDER_NIC_PF=y -CONFIG_FEC=y -CONFIG_FSL_FMAN=y -CONFIG_FSL_DPAA_ETH=y -CONFIG_FSL_DPAA2_ETH=y -CONFIG_FSL_ENETC=y -CONFIG_NXP_ENETC4=m -CONFIG_FSL_ENETC_VF=y -CONFIG_FSL_ENETC_QOS=y -CONFIG_NXP_NETC_BLK_CTRL=m -CONFIG_HIX5HD2_GMAC=y -CONFIG_HNS_DSAF=y -CONFIG_HNS_ENET=y -CONFIG_HNS3=y -CONFIG_HNS3_HCLGE=y -CONFIG_HNS3_ENET=y -CONFIG_E1000=y -CONFIG_E1000E=y -CONFIG_IGB=m -CONFIG_IGBVF=y -CONFIG_IGC=m -CONFIG_MVNETA=y -CONFIG_MVPP2=y -CONFIG_SKY2=y -CONFIG_NET_VENDOR_MEDIATEK=y -CONFIG_NET_MEDIATEK_STAR_EMAC=m -CONFIG_MLX4_EN=m -CONFIG_MLX5_CORE=m -CONFIG_MLX5_CORE_EN=y -CONFIG_QCOM_EMAC=m -CONFIG_RMNET=m -CONFIG_R8169=m -CONFIG_SH_ETH=y -CONFIG_RAVB=y -CONFIG_RENESAS_ETHER_SWITCH=y -CONFIG_RTSN=y -CONFIG_SMC91X=y -CONFIG_SMSC911X=y -CONFIG_SNI_AVE=y -CONFIG_SNI_NETSEC=y -CONFIG_STMMAC_ETH=m -CONFIG_DWMAC_MEDIATEK=m -CONFIG_DWMAC_TEGRA=m -CONFIG_TI_K3_AM65_CPSW_NUSS=y -CONFIG_TI_ICSSG_PRUETH=m -CONFIG_XILINX_AXI_EMAC=m -CONFIG_QCOM_IPA=m -CONFIG_MESON_GXL_PHY=m -CONFIG_AQUANTIA_PHY=y -CONFIG_BCM54140_PHY=m -CONFIG_MARVELL_PHY=m -CONFIG_MARVELL_10G_PHY=y -CONFIG_MARVELL_88Q2XXX_PHY=y -CONFIG_MICREL_PHY=y -CONFIG_MICROSEMI_PHY=y -CONFIG_AT803X_PHY=y -CONFIG_QCA808X_PHY=m -CONFIG_REALTEK_PHY=y -CONFIG_ROCKCHIP_PHY=y -CONFIG_DP83867_PHY=y -CONFIG_DP83869_PHY=m -CONFIG_DP83TD510_PHY=y -CONFIG_DP83TG720_PHY=m -CONFIG_VITESSE_PHY=y -CONFIG_XILINX_GMII2RGMII=m -CONFIG_CAN_FLEXCAN=m -CONFIG_CAN_XILINXCAN=m -CONFIG_CAN_M_CAN=m -CONFIG_CAN_M_CAN_PLATFORM=m -CONFIG_CAN_RCAR=m -CONFIG_CAN_RCAR_CANFD=m -CONFIG_CAN_MCP251XFD=m -CONFIG_CAN_8DEV_USB=m -CONFIG_CAN_EMS_USB=m -CONFIG_CAN_ESD_USB=m -CONFIG_CAN_F81604=m -CONFIG_CAN_GS_USB=m -CONFIG_CAN_KVASER_USB=m -CONFIG_CAN_MCBA_USB=m -CONFIG_CAN_PEAK_USB=m -CONFIG_MDIO_GPIO=y -CONFIG_MDIO_BUS_MUX_MULTIPLEXER=y -CONFIG_MDIO_BUS_MUX_MMIOREG=y -CONFIG_PPP=m -CONFIG_PPP_BSDCOMP=m -CONFIG_PPP_DEFLATE=m -CONFIG_PPP_FILTER=y -CONFIG_PPP_MPPE=m -CONFIG_PPP_MULTILINK=y -CONFIG_PPPOE=m -CONFIG_PPTP=m -CONFIG_PPPOL2TP=m -CONFIG_PPP_ASYNC=m -CONFIG_PPP_SYNC_TTY=m -CONFIG_USB_CATC=m -CONFIG_USB_KAWETH=m -CONFIG_USB_PEGASUS=m -CONFIG_USB_RTL8150=m -CONFIG_USB_RTL8152=y -CONFIG_USB_LAN78XX=m -CONFIG_USB_USBNET=y -CONFIG_USB_NET_CDC_EEM=m -CONFIG_USB_NET_HUAWEI_CDC_NCM=m -CONFIG_USB_NET_CDC_MBIM=m -CONFIG_USB_NET_DM9601=m -CONFIG_USB_NET_SR9700=m -CONFIG_USB_NET_SR9800=m -CONFIG_USB_NET_SMSC75XX=m -CONFIG_USB_NET_SMSC95XX=m -CONFIG_USB_NET_GL620A=m -CONFIG_USB_NET_NET1080=m -CONFIG_USB_NET_PLUSB=m -CONFIG_USB_NET_MCS7830=m -CONFIG_USB_NET_RNDIS_HOST=m -CONFIG_USB_NET_CDC_SUBSET=m -CONFIG_USB_ALI_M5632=y -CONFIG_USB_AN2720=y -CONFIG_USB_EPSON2888=y -CONFIG_USB_KC2190=y -CONFIG_USB_NET_ZAURUS=m -CONFIG_USB_NET_CX82310_ETH=m -CONFIG_USB_NET_KALMIA=m -CONFIG_USB_NET_QMI_WWAN=m -CONFIG_USB_HSO=m -CONFIG_USB_NET_INT51X1=m -CONFIG_USB_IPHETH=m -CONFIG_USB_SIERRA_NET=m -CONFIG_USB_VL600=m -CONFIG_USB_NET_CH9200=m -CONFIG_USB_NET_AQC111=m -CONFIG_ATH9K_HTC=m -CONFIG_CARL9170=m -CONFIG_ATH6KL=m -CONFIG_ATH6KL_USB=m -CONFIG_AR5523=m -CONFIG_ATH10K=m -CONFIG_ATH10K_PCI=m -CONFIG_ATH10K_SDIO=m -CONFIG_ATH10K_USB=m -CONFIG_ATH10K_SNOC=m -CONFIG_WCN36XX=m -CONFIG_ATH11K=m -CONFIG_ATH11K_AHB=m -CONFIG_ATH11K_PCI=m -CONFIG_ATH12K=m -CONFIG_BRCMFMAC=m -CONFIG_BRCMFMAC_USB=y -CONFIG_BRCMFMAC_PCIE=y -CONFIG_IWLWIFI=m -CONFIG_IWLDVM=m -CONFIG_IWLMVM=m -CONFIG_LIBERTAS_THINFIRM=m -CONFIG_LIBERTAS_THINFIRM_USB=m -CONFIG_MWIFIEX=m -CONFIG_MWIFIEX_SDIO=m -CONFIG_MWIFIEX_PCIE=m -CONFIG_MWIFIEX_USB=m -CONFIG_MT7601U=m -CONFIG_MT76x0U=m -CONFIG_MT76x2U=m -CONFIG_MT7663U=m -CONFIG_MT7921E=m -CONFIG_MT7921U=m -CONFIG_MT7925U=m -CONFIG_MT7932_FULLMAC=m -CONFIG_RT2X00=m -CONFIG_RT2500USB=m -CONFIG_RT73USB=m -CONFIG_RT2800USB=m -CONFIG_RT2800USB_RT3573=y -CONFIG_RT2800USB_RT53XX=y -CONFIG_RT2800USB_RT55XX=y -CONFIG_RT2800USB_UNKNOWN=y -CONFIG_RTL8187=m -CONFIG_RTL8192DU=m -CONFIG_RTL8XXXU=m -CONFIG_RTW88=m -CONFIG_RTW88_8822BU=m -CONFIG_RTW88_8822CU=m -CONFIG_RTW88_8723DU=m -CONFIG_RTW88_8821CU=m -CONFIG_RTW88_8821AU=m -CONFIG_RTW88_8812AU=m -CONFIG_RTW88_8814AU=m -CONFIG_RTW89=m -CONFIG_RTW89_8851BU=m -CONFIG_RTW89_8852AU=m -CONFIG_RTW89_8852BU=m -CONFIG_RTW89_8852CU=m -CONFIG_RSI_91X=m -CONFIG_WL18XX=m -CONFIG_WLCORE_SDIO=m -CONFIG_ZD1211RW=m -CONFIG_WWAN=m -CONFIG_MHI_WWAN_CTRL=m -CONFIG_MHI_WWAN_MBIM=m -CONFIG_INPUT_JOYDEV=m -CONFIG_INPUT_EVDEV=y -CONFIG_KEYBOARD_ADC=m -CONFIG_KEYBOARD_GPIO=y -CONFIG_KEYBOARD_GPIO_POLLED=m -CONFIG_KEYBOARD_SNVS_PWRKEY=m -CONFIG_KEYBOARD_IMX_SC_KEY=m -CONFIG_KEYBOARD_CROS_EC=y -CONFIG_KEYBOARD_MTK_PMIC=m -CONFIG_MOUSE_ELAN_I2C=m -CONFIG_MOUSE_SYNAPTICS_USB=m -CONFIG_INPUT_JOYSTICK=y -CONFIG_JOYSTICK_IFORCE=m -CONFIG_JOYSTICK_IFORCE_USB=m -CONFIG_JOYSTICK_XPAD=m -CONFIG_JOYSTICK_XPAD_FF=y -CONFIG_JOYSTICK_XPAD_LEDS=y -CONFIG_JOYSTICK_PXRC=m -CONFIG_INPUT_TABLET=y -CONFIG_TABLET_USB_ACECAD=m -CONFIG_TABLET_USB_AIPTEK=m -CONFIG_TABLET_USB_HANWANG=m -CONFIG_TABLET_USB_KBTAB=m -CONFIG_TABLET_USB_PEGASUS=m -CONFIG_INPUT_TOUCHSCREEN=y -CONFIG_TOUCHSCREEN_APPLE_Z2=m -CONFIG_TOUCHSCREEN_ATMEL_MXT=m -CONFIG_TOUCHSCREEN_GOODIX=m -CONFIG_TOUCHSCREEN_GOODIX_BERLIN_SPI=m -CONFIG_TOUCHSCREEN_ILI210X=m -CONFIG_TOUCHSCREEN_ELAN=m -CONFIG_TOUCHSCREEN_EDT_FT5X06=m -CONFIG_TOUCHSCREEN_USB_COMPOSITE=m -CONFIG_TOUCHSCREEN_HIMAX_HX83112B=m -CONFIG_INPUT_MISC=y -CONFIG_INPUT_AW86927=m -CONFIG_INPUT_BBNSM_PWRKEY=m -CONFIG_INPUT_PM8941_PWRKEY=y -CONFIG_INPUT_PM8XXX_VIBRATOR=m -CONFIG_INPUT_TPS65219_PWRBUTTON=m -CONFIG_INPUT_TPS6594_PWRBUTTON=m -CONFIG_INPUT_UINPUT=m -CONFIG_INPUT_PWM_BEEPER=m -CONFIG_INPUT_PWM_VIBRA=m -CONFIG_INPUT_RK805_PWRKEY=m -CONFIG_INPUT_DA9063_ONKEY=m -CONFIG_INPUT_HISI_POWERKEY=y -CONFIG_INPUT_MACSMC_INPUT=m -CONFIG_RMI4_F1A=y -CONFIG_RMI4_F21=y -CONFIG_RMI4_F34=y -CONFIG_RMI4_F55=y -# CONFIG_SERIO_SERPORT is not set -CONFIG_SERIO_AMBAKMI=y -CONFIG_LEGACY_PTY_COUNT=16 -CONFIG_SERIAL_8250=y -CONFIG_SERIAL_8250_CONSOLE=y -CONFIG_SERIAL_8250_NR_UARTS=8 -CONFIG_SERIAL_8250_EXTENDED=y -CONFIG_SERIAL_8250_SHARE_IRQ=y -CONFIG_SERIAL_8250_BCM2835AUX=y -CONFIG_SERIAL_8250_DW=y -CONFIG_SERIAL_8250_EM=y -CONFIG_SERIAL_8250_OMAP=y -CONFIG_SERIAL_8250_MT6577=y -CONFIG_SERIAL_8250_UNIPHIER=y -CONFIG_SERIAL_OF_PLATFORM=y -CONFIG_SERIAL_AMBA_PL011=y -CONFIG_SERIAL_AMBA_PL011_CONSOLE=y -CONFIG_SERIAL_APPLE_DOCKCHANNEL_EARLYCON=y -CONFIG_SERIAL_APPLE_DOCKCHANNEL=y -CONFIG_SERIAL_MESON=y -CONFIG_SERIAL_MESON_CONSOLE=y -CONFIG_SERIAL_SAMSUNG=y -CONFIG_SERIAL_SAMSUNG_CONSOLE=y -CONFIG_SERIAL_TEGRA=y -CONFIG_SERIAL_TEGRA_TCU=y -CONFIG_SERIAL_IMX=y -CONFIG_SERIAL_IMX_CONSOLE=y -CONFIG_SERIAL_SH_SCI=y -CONFIG_SERIAL_RSCI=y -CONFIG_SERIAL_MSM=y -CONFIG_SERIAL_MSM_CONSOLE=y -CONFIG_SERIAL_QCOM_GENI=y -CONFIG_SERIAL_QCOM_GENI_CONSOLE=y -CONFIG_SERIAL_XILINX_PS_UART=y -CONFIG_SERIAL_XILINX_PS_UART_CONSOLE=y -CONFIG_SERIAL_FSL_LPUART=y -CONFIG_SERIAL_FSL_LPUART_CONSOLE=y -CONFIG_SERIAL_FSL_LINFLEXUART=y -CONFIG_SERIAL_FSL_LINFLEXUART_CONSOLE=y -CONFIG_SERIAL_STM32=y -CONFIG_SERIAL_STM32_CONSOLE=y -CONFIG_SERIAL_MVEBU_UART=y -CONFIG_SERIAL_OWL=y -CONFIG_SERIAL_DEV_BUS=y -CONFIG_VIRTIO_CONSOLE=y -CONFIG_IPMI_HANDLER=m -CONFIG_IPMI_DEVICE_INTERFACE=m -CONFIG_IPMI_SI=m -CONFIG_HW_RANDOM=y -CONFIG_HW_RANDOM_VIRTIO=y -CONFIG_TCG_TPM=y -CONFIG_TCG_TIS=m -CONFIG_TCG_TIS_SPI=m -CONFIG_TCG_TIS_SPI_CR50=y -CONFIG_TCG_TIS_I2C_CR50=m -CONFIG_TCG_TIS_I2C_INFINEON=y -CONFIG_XILLYUSB=m -CONFIG_I2C_CHARDEV=y -CONFIG_I2C_MUX=y -CONFIG_I2C_MUX_PCA954x=y -CONFIG_I2C_MUX_PINCTRL=m -CONFIG_I2C_BCM2835=m -CONFIG_I2C_CADENCE=m -CONFIG_I2C_DESIGNWARE_CORE=y -CONFIG_I2C_GPIO=m -CONFIG_I2C_IMX=y -CONFIG_I2C_IMX_LPI2C=y -CONFIG_I2C_MESON=y -CONFIG_I2C_MT65XX=y -CONFIG_I2C_MV64XXX=y -CONFIG_I2C_OMAP=y -CONFIG_I2C_OWL=y -CONFIG_I2C_APPLE=y -CONFIG_I2C_PXA=y -CONFIG_I2C_QCOM_CCI=m -CONFIG_I2C_QCOM_GENI=m -CONFIG_I2C_QUP=y -CONFIG_I2C_RIIC=y -CONFIG_I2C_RK3X=y -CONFIG_I2C_RZV2M=m -CONFIG_I2C_S3C2410=y -CONFIG_I2C_SH_MOBILE=y -CONFIG_I2C_TEGRA=y -CONFIG_I2C_UNIPHIER_F=y -CONFIG_I2C_XILINX=m -CONFIG_I2C_RCAR=y -CONFIG_I2C_DIOLAN_U2C=m -CONFIG_I2C_DLN2=m -CONFIG_I2C_CP2615=m -CONFIG_I2C_TINY_USB=m -CONFIG_I2C_CROS_EC_TUNNEL=y -CONFIG_SPI=y -CONFIG_SPI_APPLE=m -CONFIG_SPI_APPLE_QSPI=m -CONFIG_SPI_ARMADA_3700=y -CONFIG_SPI_BCM2835=m -CONFIG_SPI_BCM2835AUX=m -CONFIG_SPI_CADENCE_QUADSPI=y -CONFIG_SPI_CH341=m -CONFIG_SPI_DESIGNWARE=m -CONFIG_SPI_DW_DMA=y -CONFIG_SPI_DW_MMIO=m -CONFIG_SPI_DLN2=m -CONFIG_SPI_FSL_LPSPI=y -CONFIG_SPI_FSL_QUADSPI=y -CONFIG_SPI_NXP_FLEXSPI=y -CONFIG_SPI_IMX=m -CONFIG_SPI_FSL_DSPI=y -CONFIG_SPI_MESON_SPICC=m -CONFIG_SPI_MESON_SPIFC=m -CONFIG_SPI_MT65XX=y -CONFIG_SPI_MTK_NOR=m -CONFIG_SPI_OMAP24XX=m -CONFIG_SPI_ORION=y -CONFIG_SPI_PL022=y -CONFIG_SPI_ROCKCHIP=y -CONFIG_SPI_ROCKCHIP_SFC=m -CONFIG_SPI_RPCIF=m -CONFIG_SPI_RSPI=m -CONFIG_SPI_RZV2H_RSPI=m -CONFIG_SPI_RZV2M_CSI=m -CONFIG_SPI_QCOM_QSPI=m -CONFIG_SPI_QUP=y -CONFIG_SPI_QCOM_GENI=m -CONFIG_SPI_S3C64XX=y -CONFIG_SPI_SH_MSIOF=m -CONFIG_SPI_STM32_OSPI=m -CONFIG_SPI_SUN6I=y -CONFIG_SPI_TEGRA210_QUAD=m -CONFIG_SPI_TEGRA114=m -CONFIG_SPI_XILINX=m -CONFIG_SPI_ZYNQMP_GQSPI=m -CONFIG_SPI_SPIDEV=m -CONFIG_SPMI=y -CONFIG_SPMI_APPLE=y -CONFIG_SPMI_MTK_PMIF=m -CONFIG_PINCTRL_APPLE_GPIO=y -CONFIG_PINCTRL_DA9062=m -CONFIG_PINCTRL_MAX77620=y -CONFIG_PINCTRL_RK805=m -CONFIG_PINCTRL_SINGLE=y -CONFIG_PINCTRL_SX150X=m -CONFIG_PINCTRL_OWL=y -CONFIG_PINCTRL_S700=y -CONFIG_PINCTRL_S900=y -CONFIG_PINCTRL_BRCMSTB=y -CONFIG_PINCTRL_BCM2712=y -CONFIG_PINCTRL_IMX_SCMI=y -CONFIG_PINCTRL_IMX91=y -CONFIG_PINCTRL_MSM=y -CONFIG_PINCTRL_ELIZA=y -CONFIG_PINCTRL_GLYMUR=y -CONFIG_PINCTRL_IPQ5018=y -CONFIG_PINCTRL_IPQ5210=y -CONFIG_PINCTRL_IPQ5332=y -CONFIG_PINCTRL_IPQ5424=y -CONFIG_PINCTRL_IPQ8074=y -CONFIG_PINCTRL_IPQ6018=y -CONFIG_PINCTRL_IPQ9574=y -CONFIG_PINCTRL_KAANAPALI=y -CONFIG_PINCTRL_MSM8916=y -CONFIG_PINCTRL_MSM8953=y -CONFIG_PINCTRL_MSM8976=y -CONFIG_PINCTRL_MSM8994=y -CONFIG_PINCTRL_MSM8996=y -CONFIG_PINCTRL_MSM8998=y -CONFIG_PINCTRL_QCM2290=y -CONFIG_PINCTRL_QCS404=y -CONFIG_PINCTRL_QCS615=y -CONFIG_PINCTRL_QCS8300=y -CONFIG_PINCTRL_QDF2XXX=y -CONFIG_PINCTRL_QDU1000=y -CONFIG_PINCTRL_SA8775P=y -CONFIG_PINCTRL_SC7180=y -CONFIG_PINCTRL_SC7280=y -CONFIG_PINCTRL_SC8180X=y -CONFIG_PINCTRL_SC8280XP=y -CONFIG_PINCTRL_SDM660=y -CONFIG_PINCTRL_SDM670=y -CONFIG_PINCTRL_SDM845=y -CONFIG_PINCTRL_SDX75=y -CONFIG_PINCTRL_SM4450=y -CONFIG_PINCTRL_SM6115=y -CONFIG_PINCTRL_SM6125=y -CONFIG_PINCTRL_SM6350=y -CONFIG_PINCTRL_SM6375=y -CONFIG_PINCTRL_MILOS=y -CONFIG_PINCTRL_SM8150=y -CONFIG_PINCTRL_SM8250=y -CONFIG_PINCTRL_SM8350=y -CONFIG_PINCTRL_SM8450=y -CONFIG_PINCTRL_SM8550=y -CONFIG_PINCTRL_SM8650=y -CONFIG_PINCTRL_SM8750=y -CONFIG_PINCTRL_X1E80100=y -CONFIG_PINCTRL_QCOM_SPMI_PMIC=y -CONFIG_PINCTRL_LPASS_LPI=m -CONFIG_PINCTRL_MILOS_LPASS_LPI=m -CONFIG_PINCTRL_SC7280_LPASS_LPI=m -CONFIG_PINCTRL_SM6115_LPASS_LPI=m -CONFIG_PINCTRL_SM8250_LPASS_LPI=m -CONFIG_PINCTRL_SM8450_LPASS_LPI=m -CONFIG_PINCTRL_SC8280XP_LPASS_LPI=m -CONFIG_PINCTRL_SM8550_LPASS_LPI=m -CONFIG_PINCTRL_SM8650_LPASS_LPI=m -CONFIG_PINCTRL_SOPHGO_SG2000=y -CONFIG_GPIO_ALTERA=m -CONFIG_GPIO_DAVINCI=y -CONFIG_GPIO_DWAPB=y -CONFIG_GPIO_MB86S7X=y -CONFIG_GPIO_MPC8XXX=y -CONFIG_GPIO_MXC=y -CONFIG_GPIO_PL061=y -CONFIG_GPIO_RCAR=y -CONFIG_GPIO_SYSCON=y -CONFIG_GPIO_UNIPHIER=y -CONFIG_GPIO_VF610=y -CONFIG_GPIO_VISCONTI=y -CONFIG_GPIO_WCD934X=m -CONFIG_GPIO_XGENE=y -CONFIG_GPIO_XGENE_SB=y -CONFIG_GPIO_XILINX=m -CONFIG_GPIO_ZYNQ=m -CONFIG_GPIO_MAX732X=y -CONFIG_GPIO_PCA953X=y -CONFIG_GPIO_PCA953X_IRQ=y -CONFIG_GPIO_PCF857X=m -CONFIG_GPIO_TPIC2810=m -CONFIG_GPIO_ADP5585=m -CONFIG_GPIO_BD9571MWV=m -CONFIG_GPIO_DLN2=m -CONFIG_GPIO_MACSMC=y -CONFIG_GPIO_MAX77620=y -CONFIG_GPIO_SL28CPLD=m -CONFIG_GPIO_MPSSE=m -CONFIG_GPIO_AGGREGATOR=m -CONFIG_W1=m -CONFIG_W1_MASTER_DS2490=m -CONFIG_W1_SLAVE_THERM=m -CONFIG_W1_SLAVE_SMEM=m -CONFIG_W1_SLAVE_DS2405=m -CONFIG_W1_SLAVE_DS2408=m -CONFIG_W1_SLAVE_DS2413=m -CONFIG_W1_SLAVE_DS2406=m -CONFIG_W1_SLAVE_DS2423=m -CONFIG_W1_SLAVE_DS2805=m -CONFIG_W1_SLAVE_DS2430=m -CONFIG_W1_SLAVE_DS2431=m -CONFIG_W1_SLAVE_DS2433=m -CONFIG_W1_SLAVE_DS2433_CRC=y -CONFIG_W1_SLAVE_DS2438=m -CONFIG_W1_SLAVE_DS2780=m -CONFIG_W1_SLAVE_DS2781=m -CONFIG_W1_SLAVE_DS28E04=m -CONFIG_POWER_RESET_GPIO=y -CONFIG_POWER_RESET_GPIO_RESTART=y -CONFIG_POWER_RESET_MACSMC=y -CONFIG_POWER_RESET_MSM=y -CONFIG_POWER_RESET_QCOM_PON=m -CONFIG_POWER_RESET_TORADEX_EC=m -CONFIG_POWER_RESET_XGENE=y -CONFIG_POWER_RESET_SYSCON=y -CONFIG_POWER_RESET_SYSCON_POWEROFF=y -CONFIG_SYSCON_REBOOT_MODE=y -CONFIG_NVMEM_REBOOT_MODE=m -CONFIG_POWER_SEQUENCING_PCIE_M2=m -CONFIG_BATTERY_QCOM_BATTMGR=m -CONFIG_BATTERY_SBS=m -CONFIG_BATTERY_BQ27XXX=y -CONFIG_BATTERY_MAX17042=m -CONFIG_CHARGER_MT6360=m -CONFIG_CHARGER_BQ25890=m -CONFIG_CHARGER_BQ25980=m -CONFIG_CHARGER_RK817=m -CONFIG_MACSMC_POWER=m -CONFIG_SENSORS_AQUACOMPUTER_D5NEXT=m -CONFIG_SENSORS_ASUS_ROG_RYUJIN=m -CONFIG_SENSORS_ARM_SCMI=y -CONFIG_SENSORS_ARM_SCPI=y -CONFIG_SENSORS_CORSAIR_CPRO=m -CONFIG_SENSORS_CORSAIR_PSU=m -CONFIG_SENSORS_GIGABYTE_WATERFORCE=m -CONFIG_SENSORS_GPIO_FAN=m -CONFIG_SENSORS_JC42=m -CONFIG_SENSORS_POWERZ=m -CONFIG_SENSORS_MACSMC_HWMON=y -CONFIG_SENSORS_LM75=m -CONFIG_SENSORS_LM90=m -CONFIG_SENSORS_NZXT_KRAKEN2=m -CONFIG_SENSORS_NZXT_KRAKEN3=m -CONFIG_SENSORS_NZXT_SMART2=m -CONFIG_SENSORS_PWM_FAN=m -CONFIG_SENSORS_RASPBERRYPI_HWMON=m -CONFIG_SENSORS_SL28CPLD=m -CONFIG_SENSORS_AMC6821=m -CONFIG_SENSORS_INA2XX=m -CONFIG_SENSORS_INA3221=m -CONFIG_SENSORS_TMP102=m -CONFIG_THERMAL_NETLINK=y -CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR=y -CONFIG_THERMAL_GOV_STEP_WISE=y -CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y -CONFIG_CPU_THERMAL=y -CONFIG_DEVFREQ_THERMAL=y -CONFIG_THERMAL_EMULATION=y -CONFIG_IMX_SC_THERMAL=m -CONFIG_IMX8MM_THERMAL=m -CONFIG_K3_THERMAL=m -CONFIG_QORIQ_THERMAL=m -CONFIG_SUN8I_THERMAL=y -CONFIG_ROCKCHIP_THERMAL=m -CONFIG_ARMADA_THERMAL=y -CONFIG_MTK_THERMAL=m -CONFIG_MTK_LVTS_THERMAL=m -CONFIG_BCM2711_THERMAL=m -CONFIG_BCM2835_THERMAL=m -CONFIG_BRCMSTB_THERMAL=m -CONFIG_EXYNOS_THERMAL=y -CONFIG_RCAR_THERMAL=y -CONFIG_RCAR_GEN3_THERMAL=y -CONFIG_RZG2L_THERMAL=y -CONFIG_RZG3E_THERMAL=y -CONFIG_RZG3S_THERMAL=m -CONFIG_TEGRA_SOCTHERM=m -CONFIG_TEGRA_BPMP_THERMAL=m -CONFIG_GENERIC_ADC_THERMAL=m -CONFIG_QCOM_TSENS=y -CONFIG_QCOM_SPMI_ADC_TM5=m -CONFIG_QCOM_SPMI_TEMP_ALARM=m -CONFIG_QCOM_LMH=m -CONFIG_UNIPHIER_THERMAL=y -CONFIG_KHADAS_MCU_FAN_THERMAL=m -CONFIG_WATCHDOG=y -CONFIG_XILINX_WATCHDOG=m -CONFIG_XILINX_WINDOW_WATCHDOG=m -CONFIG_SL28CPLD_WATCHDOG=m -CONFIG_ARM_SP805_WATCHDOG=y -CONFIG_ARM_SBSA_WATCHDOG=y -CONFIG_S3C2410_WATCHDOG=y -CONFIG_DW_WATCHDOG=y -CONFIG_K3_RTI_WATCHDOG=m -CONFIG_SUNXI_WATCHDOG=m -CONFIG_NPCM7XX_WATCHDOG=y -CONFIG_IMX2_WDT=y -CONFIG_IMX_SC_WDT=m -CONFIG_IMX7ULP_WDT=m -CONFIG_QCOM_WDT=m -CONFIG_MESON_GXBB_WATCHDOG=m -CONFIG_MESON_WATCHDOG=m -CONFIG_ARM_SMC_WATCHDOG=y -CONFIG_RENESAS_WDT=y -CONFIG_RENESAS_RZG2LWDT=y -CONFIG_RENESAS_RZV2HWDT=y -CONFIG_UNIPHIER_WATCHDOG=y -CONFIG_PM8916_WATCHDOG=m -CONFIG_APPLE_WATCHDOG=y -CONFIG_BCM2835_WDT=y -CONFIG_BCM7038_WDT=m -CONFIG_USBPCWATCHDOG=m -CONFIG_MFD_ADP5585=m -CONFIG_MFD_ALTERA_SYSMGR=y -CONFIG_MFD_BD9571MWV=y -CONFIG_MFD_AXP20X_I2C=y -CONFIG_MFD_AXP20X_RSB=y -CONFIG_MFD_MACSMC=y -CONFIG_MFD_DA9062=m -CONFIG_MFD_DLN2=m -CONFIG_MFD_EXYNOS_LPASS=m -CONFIG_MFD_HI6421_PMIC=y -CONFIG_MFD_HI655X_PMIC=y -CONFIG_MFD_MAX77620=y -CONFIG_MFD_MAX77759=m -CONFIG_MFD_MT6360=y -CONFIG_MFD_MT6397=y -CONFIG_MFD_SPMI_PMIC=y -CONFIG_MFD_RK8XX_I2C=y -CONFIG_MFD_RK8XX_SPI=y -CONFIG_MFD_SEC_ACPM=m -CONFIG_MFD_SEC_I2C=y -CONFIG_MFD_SL28CPLD=y -CONFIG_RZ_MTU3=y -CONFIG_MFD_TI_AM335X_TSCADC=m -CONFIG_MFD_TI_LP873X=m -CONFIG_MFD_TPS65219=y -CONFIG_MFD_TPS6594_I2C=m -CONFIG_MFD_ROHM_BD718XX=y -CONFIG_MFD_STM32_LPTIMER=m -CONFIG_MFD_STM32_TIMERS=m -CONFIG_MFD_WCD934X=m -CONFIG_MFD_KHADAS_MCU=m -CONFIG_MFD_QCOM_PM8008=m -CONFIG_REGULATOR_FIXED_VOLTAGE=y -CONFIG_REGULATOR_AXP20X=y -CONFIG_REGULATOR_BD718XX=y -CONFIG_REGULATOR_BD9571MWV=y -CONFIG_REGULATOR_CROS_EC=y -CONFIG_REGULATOR_DA9211=m -CONFIG_REGULATOR_FAN53555=y -CONFIG_REGULATOR_GPIO=y -CONFIG_REGULATOR_HI6421V530=y -CONFIG_REGULATOR_HI655X=y -CONFIG_REGULATOR_LP873X=m -CONFIG_REGULATOR_MAX77620=y -CONFIG_REGULATOR_MAX8973=y -CONFIG_REGULATOR_MAX20411=m -CONFIG_REGULATOR_MP8859=y -CONFIG_REGULATOR_MT6315=m -CONFIG_REGULATOR_MT6357=y -CONFIG_REGULATOR_MT6358=y -CONFIG_REGULATOR_MT6359=y -CONFIG_REGULATOR_MT6360=y -CONFIG_REGULATOR_MT6397=y -CONFIG_REGULATOR_PCA9450=y -CONFIG_REGULATOR_PF8X00=y -CONFIG_REGULATOR_PFUZE100=y -CONFIG_REGULATOR_PWM=y -CONFIG_REGULATOR_QCOM_PM8008=m -CONFIG_REGULATOR_QCOM_REFGEN=m -CONFIG_REGULATOR_QCOM_RPMH=y -CONFIG_REGULATOR_QCOM_SMD_RPM=y -CONFIG_REGULATOR_QCOM_SPMI=y -CONFIG_REGULATOR_QCOM_USB_VBUS=m -CONFIG_REGULATOR_RAA215300=y -CONFIG_REGULATOR_RK808=y -CONFIG_REGULATOR_S2MPS11=y -CONFIG_REGULATOR_TPS65132=m -CONFIG_REGULATOR_TPS65219=y -CONFIG_REGULATOR_VCTRL=m -CONFIG_RC_CORE=m -CONFIG_RC_DECODERS=y -CONFIG_RC_DEVICES=y -CONFIG_IR_GPIO_CIR=m -CONFIG_IR_MESON=m -CONFIG_IR_SUNXI=m -CONFIG_MEDIA_CEC_SUPPORT=y -CONFIG_CEC_MESON_G12A_AO=m -CONFIG_MEDIA_SUPPORT=m -CONFIG_MEDIA_CAMERA_SUPPORT=y -CONFIG_MEDIA_ANALOG_TV_SUPPORT=y -CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y -CONFIG_MEDIA_SDR_SUPPORT=y -CONFIG_MEDIA_PLATFORM_SUPPORT=y -# CONFIG_DVB_NET is not set -CONFIG_MEDIA_USB_SUPPORT=y -CONFIG_USB_GSPCA=m -CONFIG_USB_GSPCA_BENQ=m -CONFIG_USB_GSPCA_CONEX=m -CONFIG_USB_GSPCA_CPIA1=m -CONFIG_USB_GSPCA_DTCS033=m -CONFIG_USB_GSPCA_ETOMS=m -CONFIG_USB_GSPCA_FINEPIX=m -CONFIG_USB_GSPCA_JEILINJ=m -CONFIG_USB_GSPCA_JL2005BCD=m -CONFIG_USB_GSPCA_KINECT=m -CONFIG_USB_GSPCA_KONICA=m -CONFIG_USB_GSPCA_MARS=m -CONFIG_USB_GSPCA_MR97310A=m -CONFIG_USB_GSPCA_NW80X=m -CONFIG_USB_GSPCA_OV519=m -CONFIG_USB_GSPCA_OV534=m -CONFIG_USB_GSPCA_OV534_9=m -CONFIG_USB_GSPCA_PAC207=m -CONFIG_USB_GSPCA_PAC7302=m -CONFIG_USB_GSPCA_PAC7311=m -CONFIG_USB_GSPCA_SE401=m -CONFIG_USB_GSPCA_SN9C2028=m -CONFIG_USB_GSPCA_SN9C20X=m -CONFIG_USB_GSPCA_SONIXB=m -CONFIG_USB_GSPCA_SONIXJ=m -CONFIG_USB_GSPCA_SPCA1528=m -CONFIG_USB_GSPCA_SPCA500=m -CONFIG_USB_GSPCA_SPCA501=m -CONFIG_USB_GSPCA_SPCA505=m -CONFIG_USB_GSPCA_SPCA506=m -CONFIG_USB_GSPCA_SPCA508=m -CONFIG_USB_GSPCA_SPCA561=m -CONFIG_USB_GSPCA_SQ905=m -CONFIG_USB_GSPCA_SQ905C=m -CONFIG_USB_GSPCA_SQ930X=m -CONFIG_USB_GSPCA_STK014=m -CONFIG_USB_GSPCA_STK1135=m -CONFIG_USB_GSPCA_STV0680=m -CONFIG_USB_GSPCA_SUNPLUS=m -CONFIG_USB_GSPCA_T613=m -CONFIG_USB_GSPCA_TOPRO=m -CONFIG_USB_GSPCA_TOUPTEK=m -CONFIG_USB_GSPCA_TV8532=m -CONFIG_USB_GSPCA_VC032X=m -CONFIG_USB_GSPCA_VICAM=m -CONFIG_USB_GSPCA_XIRLINK_CIT=m -CONFIG_USB_GSPCA_ZC3XX=m -CONFIG_USB_GL860=m -CONFIG_USB_M5602=m -CONFIG_USB_STV06XX=m -CONFIG_USB_PWC=m -CONFIG_USB_VIDEO_CLASS=m -CONFIG_V4L_PLATFORM_DRIVERS=y -CONFIG_SDR_PLATFORM_DRIVERS=y -CONFIG_V4L_MEM2MEM_DRIVERS=y -CONFIG_VIDEO_AMPHION_VPU=m -CONFIG_VIDEO_APPLE_ISP=m -CONFIG_VIDEO_APPLE_AVD=m -CONFIG_VIDEO_CADENCE_CSI2RX=m -CONFIG_VIDEO_WAVE_VPU=m -CONFIG_VIDEO_E5010_JPEG_ENC=m -CONFIG_VIDEO_MEDIATEK_JPEG=m -CONFIG_VIDEO_MEDIATEK_VCODEC=m -CONFIG_VIDEO_MEDIATEK_MDP3=m -CONFIG_VIDEO_IMX7_CSI=m -CONFIG_VIDEO_IMX_MIPI_CSIS=m -CONFIG_VIDEO_IMX8_ISI=m -CONFIG_VIDEO_IMX8_ISI_M2M=y -CONFIG_VIDEO_IMX8_JPEG=m -CONFIG_VIDEO_QCOM_CAMSS=m -CONFIG_VIDEO_QCOM_IRIS=m -CONFIG_VIDEO_QCOM_VENUS=m -CONFIG_VIDEO_RCAR_CSI2=m -CONFIG_VIDEO_RCAR_ISP=m -CONFIG_VIDEO_RCAR_VIN=m -CONFIG_VIDEO_RZG2L_CSI2=m -CONFIG_VIDEO_RZG2L_CRU=m -CONFIG_VIDEO_RENESAS_FCP=m -CONFIG_VIDEO_RENESAS_FDP1=m -CONFIG_VIDEO_RENESAS_VSP1=m -CONFIG_VIDEO_RCAR_DRIF=m -CONFIG_VIDEO_ROCKCHIP_RGA=m -CONFIG_VIDEO_ROCKCHIP_CIF=m -CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC=m -CONFIG_VIDEO_SAMSUNG_S5P_JPEG=m -CONFIG_VIDEO_SAMSUNG_S5P_MFC=m -CONFIG_VIDEO_SUN6I_CSI=m -CONFIG_VIDEO_SYNOPSYS_HDMIRX=m -CONFIG_VIDEO_SYNOPSYS_HDMIRX_LOAD_DEFAULT_EDID=y -CONFIG_VIDEO_TI_J721E_CSI2RX=m -CONFIG_VIDEO_HANTRO=m -CONFIG_VIDEO_IMX219=m -CONFIG_VIDEO_IMX412=m -CONFIG_VIDEO_OV5640=m -CONFIG_VIDEO_OV5645=m -CONFIG_VIDEO_S5KJN1=m -CONFIG_DRM=y -CONFIG_DRM_ADP=m -CONFIG_DRM_APPLE=m -CONFIG_DRM_HDLCD=m -CONFIG_DRM_MALI_DISPLAY=m -CONFIG_DRM_KOMEDA=m -CONFIG_DRM_ASAHI=m -CONFIG_DRM_FSL_LDB=m -CONFIG_DRM_I2C_NXP_TDA998X=m -CONFIG_DRM_ITE_IT6263=m -CONFIG_DRM_LONTIUM_LT8912B=m -CONFIG_DRM_LONTIUM_LT9611=m -CONFIG_DRM_LONTIUM_LT9611UXC=m -CONFIG_DRM_LONTIUM_LT8713SX=m -CONFIG_DRM_ITE_IT66121=m -CONFIG_DRM_NWL_MIPI_DSI=m -CONFIG_DRM_PARADE_PS8640=m -CONFIG_DRM_SII902X=m -CONFIG_DRM_SIMPLE_BRIDGE=m -CONFIG_DRM_THINE_THC63LVD1024=m -CONFIG_DRM_TOSHIBA_TC358767=m -CONFIG_DRM_TOSHIBA_TC358768=m -CONFIG_DRM_TI_TFP410=m -CONFIG_DRM_TI_SN65DSI83=m -CONFIG_DRM_TI_SN65DSI86=m -CONFIG_DRM_ANALOGIX_ANX7625=m -CONFIG_DRM_I2C_ADV7511=m -CONFIG_DRM_I2C_ADV7511_AUDIO=y -CONFIG_DRM_CDNS_DSI=m -CONFIG_DRM_CDNS_MHDP8546=m -CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE=m -CONFIG_DRM_DW_HDMI_AHB_AUDIO=m -CONFIG_DRM_DW_HDMI_CEC=m -CONFIG_DRM_DW_HDMI_QP_CEC=y -CONFIG_DRM_ETNAVIV=m -CONFIG_DRM_EXYNOS=m -CONFIG_DRM_EXYNOS5433_DECON=y -CONFIG_DRM_EXYNOS7_DECON=y -CONFIG_DRM_EXYNOS_DSI=y -# CONFIG_DRM_EXYNOS_DP is not set -CONFIG_DRM_EXYNOS_HDMI=y -CONFIG_DRM_EXYNOS_MIC=y -CONFIG_DRM_GUD=m -CONFIG_DRM_HISI_HIBMC=m -CONFIG_DRM_HISI_KIRIN=m -CONFIG_DRM_POWERVR=m -CONFIG_DRM_IMX_DCSS=m -CONFIG_DRM_LIMA=m -CONFIG_DRM_MEDIATEK=m -CONFIG_DRM_MEDIATEK_DP=m -CONFIG_DRM_MEDIATEK_HDMI=m -CONFIG_DRM_MEDIATEK_HDMI_V2=m -CONFIG_DRM_MESON=m -CONFIG_DRM_MSM=m -CONFIG_DRM_MXSFB=m -CONFIG_DRM_IMX_LCDIF=m -CONFIG_DRM_NOUVEAU=m -CONFIG_DRM_PANEL_BOE_TV101WUM_NL6=m -CONFIG_DRM_PANEL_LVDS=m -CONFIG_DRM_PANEL_HIMAX_HX8279=m -CONFIG_DRM_PANEL_HIMAX_HX83112A=m -CONFIG_DRM_PANEL_HIMAX_HX83112B=m -CONFIG_DRM_PANEL_ILITEK_ILI9882T=m -CONFIG_DRM_PANEL_KHADAS_TS050=m -CONFIG_DRM_PANEL_MANTIX_MLAF057WE51=m -CONFIG_DRM_PANEL_NOVATEK_NT36672A=m -CONFIG_DRM_PANEL_NOVATEK_NT36672E=m -CONFIG_DRM_PANEL_NOVATEK_NT37801=m -CONFIG_DRM_PANEL_RAYDIUM_RM67191=m -CONFIG_DRM_PANEL_RAYDIUM_RM692E5=m -CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20=m -CONFIG_DRM_PANEL_SITRONIX_ST7703=m -CONFIG_DRM_PANEL_STARTEK_KD070FHFID015=m -CONFIG_DRM_PANEL_EDP=m -CONFIG_DRM_PANEL_SIMPLE=m -CONFIG_DRM_PANEL_SUMMIT=m -CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA=m -CONFIG_DRM_PANEL_VISIONOX_VTDR6130=m -CONFIG_DRM_PANFROST=m -CONFIG_DRM_PANTHOR=m -CONFIG_DRM_PL111=m -CONFIG_DRM_RCAR_DU=m -CONFIG_DRM_RCAR_DW_HDMI=m -CONFIG_DRM_RZG2L_DU=m -CONFIG_DRM_ROCKCHIP=m -CONFIG_ROCKCHIP_VOP2=y -CONFIG_ROCKCHIP_ANALOGIX_DP=y -CONFIG_ROCKCHIP_CDN_DP=y -CONFIG_ROCKCHIP_DW_DP=y -CONFIG_ROCKCHIP_DW_HDMI=y -CONFIG_ROCKCHIP_DW_HDMI_QP=y -CONFIG_ROCKCHIP_DW_MIPI_DSI=y -CONFIG_ROCKCHIP_INNO_HDMI=y -CONFIG_ROCKCHIP_LVDS=y -CONFIG_DRM_STM=m -CONFIG_DRM_STM_LVDS=m -CONFIG_DRM_SUN4I=m -CONFIG_DRM_SIMPLEDRM=y -CONFIG_DRM_TEGRA=m -CONFIG_DRM_TIDSS=m -CONFIG_DRM_GM12U320=m -CONFIG_DRM_UDL=m -CONFIG_DRM_V3D=m -CONFIG_DRM_VC4=m -CONFIG_DRM_ZYNQMP_DPSUB=m -CONFIG_DRM_ZYNQMP_DPSUB_AUDIO=y -CONFIG_FB=y -CONFIG_FB_EFI=y -CONFIG_BACKLIGHT_CLASS_DEVICE=y -CONFIG_BACKLIGHT_PWM=m -CONFIG_BACKLIGHT_APPLE_DWI=m -CONFIG_BACKLIGHT_QCOM_WLED=m -CONFIG_BACKLIGHT_LP855X=m -CONFIG_BACKLIGHT_GPIO=m -CONFIG_LOGO=y -CONFIG_LOGO_LINUX_CLUT224_FILE="drivers/video/logo/logo_linux_clut224.ppm" -CONFIG_DRM_ACCEL=y -CONFIG_DRM_ACCEL_ANE=m -CONFIG_SOUND=m -CONFIG_SND=m -CONFIG_SND_UMP_LEGACY_RAWMIDI=y -CONFIG_SND_HRTIMER=m -CONFIG_SND_UTIMER=y -CONFIG_SND_SEQUENCER=m -CONFIG_SND_SEQ_DUMMY=m -CONFIG_SND_DUMMY=m -CONFIG_SND_ALOOP=m -CONFIG_SND_VIRMIDI=m -CONFIG_SND_HDA_TEGRA=m -CONFIG_SND_HDA_CODEC_HDMI=m -CONFIG_SND_USB_AUDIO=m -CONFIG_SND_USB_AUDIO_MIDI_V2=y -CONFIG_SND_USB_UA101=m -CONFIG_SND_USB_CAIAQ=m -CONFIG_SND_USB_CAIAQ_INPUT=y -CONFIG_SND_USB_6FIRE=m -CONFIG_SND_USB_HIFACE=m -CONFIG_SND_BCD2000=m -CONFIG_SND_USB_AUDIO_QMI=m -CONFIG_SND_USB_POD=m -CONFIG_SND_USB_PODHD=m -CONFIG_SND_USB_TONEPORT=m -CONFIG_SND_USB_VARIAX=m -CONFIG_SND_SOC=m -CONFIG_SND_SOC_USB=m -CONFIG_SND_SOC_APPLE_AOP_AUDIO=m -CONFIG_SND_SOC_APPLE_T8140_AOP_AUDIO=m -CONFIG_SND_SOC_APPLE_MACAUDIO=m -CONFIG_SND_BCM2835_SOC_I2S=m -CONFIG_SND_SOC_FSL_ASRC=m -CONFIG_SND_SOC_FSL_MICFIL=m -CONFIG_SND_SOC_FSL_EASRC=m -CONFIG_SND_IMX_SOC=m -CONFIG_SND_SOC_IMX_SGTL5000=m -CONFIG_SND_SOC_FSL_ASOC_CARD=m -CONFIG_SND_SOC_IMX_AUDMIX=m -CONFIG_SND_SOC_MT8183=m -CONFIG_SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A=m -CONFIG_SND_SOC_MT8183_DA7219_MAX98357A=m -CONFIG_SND_SOC_MT8188=m -CONFIG_SND_SOC_MT8188_MT6359=m -CONFIG_SND_SOC_MT8192=m -CONFIG_SND_SOC_MT8192_MT6359_RT1015_RT5682=m -CONFIG_SND_SOC_MT8195=m -CONFIG_SND_SOC_MT8195_MT6359=m -CONFIG_SND_SOC_MT8365=m -CONFIG_SND_SOC_MT8365_MT6357=m -CONFIG_SND_MESON_AXG_SOUND_CARD=m -CONFIG_SND_MESON_GX_SOUND_CARD=m -CONFIG_SND_SOC_QCOM=m -CONFIG_SND_SOC_APQ8016_SBC=m -CONFIG_SND_SOC_QDSP6_USB=m -CONFIG_SND_SOC_MSM8996=m -CONFIG_SND_SOC_SDM845=m -CONFIG_SND_SOC_SM8250=m -CONFIG_SND_SOC_SC8280XP=m -CONFIG_SND_SOC_SC7180=m -CONFIG_SND_SOC_SC7280=m -CONFIG_SND_SOC_X1E80100=m -CONFIG_SND_SOC_RCAR=m -CONFIG_SND_SOC_MSIOF=m -CONFIG_SND_SOC_RZ=m -CONFIG_SND_SOC_ROCKCHIP_I2S_TDM=m -CONFIG_SND_SOC_ROCKCHIP_SAI=m -CONFIG_SND_SOC_ROCKCHIP_SPDIF=m -CONFIG_SND_SOC_ROCKCHIP_RT5645=m -CONFIG_SND_SOC_RK3399_GRU_SOUND=m -CONFIG_SND_SOC_SAMSUNG=m -CONFIG_SND_SUN8I_CODEC=m -CONFIG_SND_SUN8I_CODEC_ANALOG=m -CONFIG_SND_SUN50I_CODEC_ANALOG=m -CONFIG_SND_SUN4I_I2S=m -CONFIG_SND_SUN4I_SPDIF=m -CONFIG_SND_SOC_TEGRA=m -CONFIG_SND_SOC_TEGRA210_AHUB=m -CONFIG_SND_SOC_TEGRA210_DMIC=m -CONFIG_SND_SOC_TEGRA210_I2S=m -CONFIG_SND_SOC_TEGRA210_OPE=m -CONFIG_SND_SOC_TEGRA186_ASRC=m -CONFIG_SND_SOC_TEGRA186_DSPK=m -CONFIG_SND_SOC_TEGRA210_ADMAIF=m -CONFIG_SND_SOC_TEGRA210_MVC=m -CONFIG_SND_SOC_TEGRA210_SFC=m -CONFIG_SND_SOC_TEGRA210_AMX=m -CONFIG_SND_SOC_TEGRA210_ADX=m -CONFIG_SND_SOC_TEGRA210_MIXER=m -CONFIG_SND_SOC_TEGRA_AUDIO_GRAPH_CARD=m -CONFIG_SND_SOC_J721E_EVM=m -CONFIG_SND_SOC_XILINX_I2S=m -CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER=m -CONFIG_SND_SOC_XILINX_SPDIF=m -CONFIG_SND_SOC_SOF_TOPLEVEL=y -CONFIG_SND_SOC_SOF_OF=m -CONFIG_SND_SOC_SOF_MTK_TOPLEVEL=y -CONFIG_SND_SOC_SOF_MT8186=m -CONFIG_SND_SOC_SOF_MT8195=m -CONFIG_SND_SOC_AK4613=m -CONFIG_SND_SOC_AK4619=m -CONFIG_SND_SOC_DA7213=m -CONFIG_SND_SOC_ES7134=m -CONFIG_SND_SOC_ES7241=m -CONFIG_SND_SOC_ES8316=m -CONFIG_SND_SOC_ES8328_I2C=m -CONFIG_SND_SOC_GTM601=m -CONFIG_SND_SOC_MAX98090=m -CONFIG_SND_SOC_MSM8916_WCD_ANALOG=m -CONFIG_SND_SOC_MSM8916_WCD_DIGITAL=m -CONFIG_SND_SOC_RK3308=m -CONFIG_SND_SOC_RK817=m -CONFIG_SND_SOC_RT5640=m -CONFIG_SND_SOC_RT5659=m -CONFIG_SND_SOC_SIMPLE_AMPLIFIER=m -CONFIG_SND_SOC_SIMPLE_MUX=m -CONFIG_SND_SOC_TAS2552=m -CONFIG_SND_SOC_TAS571X=m -CONFIG_SND_SOC_TLV320AIC32X4_I2C=m -CONFIG_SND_SOC_TLV320AIC3X_I2C=m -CONFIG_SND_SOC_WCD9335=m -CONFIG_SND_SOC_WCD934X=m -CONFIG_SND_SOC_WCD937X_SDW=m -CONFIG_SND_SOC_WCD939X_SDW=m -CONFIG_SND_SOC_WM8524=m -CONFIG_SND_SOC_WM8904=m -CONFIG_SND_SOC_WM8960=m -CONFIG_SND_SOC_WM8962=m -CONFIG_SND_SOC_WM8978=m -CONFIG_SND_SOC_WSA881X=m -CONFIG_SND_SOC_WSA883X=m -CONFIG_SND_SOC_WSA884X=m -CONFIG_SND_SOC_NAU8822=m -CONFIG_SND_SOC_LPASS_WSA_MACRO=m -CONFIG_SND_SOC_LPASS_VA_MACRO=m -CONFIG_SND_SIMPLE_CARD=m -CONFIG_SND_AUDIO_GRAPH_CARD=m -CONFIG_SND_AUDIO_GRAPH_CARD2=m -CONFIG_HID_BATTERY_STRENGTH=y -CONFIG_HIDRAW=y -CONFIG_UHID=y -CONFIG_HID_A4TECH=m -CONFIG_HID_ACCUTOUCH=m -CONFIG_HID_ACRUX=m -CONFIG_HID_ACRUX_FF=y -CONFIG_HID_APPLE=y -CONFIG_HID_APPLEIR=m -CONFIG_HID_AUREAL=m -CONFIG_HID_BELKIN=m -CONFIG_HID_BETOP_FF=m -CONFIG_HID_BIGBEN_FF=m -CONFIG_HID_CHERRY=m -CONFIG_HID_CHICONY=m -CONFIG_HID_CORSAIR=m -CONFIG_HID_COUGAR=m -CONFIG_HID_MACALLY=m -CONFIG_HID_PRODIKEYS=m -CONFIG_HID_CMEDIA=m -CONFIG_HID_CP2112=m -CONFIG_HID_CREATIVE_SB0540=m -CONFIG_HID_CYPRESS=m -CONFIG_HID_DRAGONRISE=m -CONFIG_DRAGONRISE_FF=y -CONFIG_HID_EMS_FF=m -CONFIG_HID_ELAN=m -CONFIG_HID_ELECOM=m -CONFIG_HID_ELO=m -CONFIG_HID_EVISION=m -CONFIG_HID_EZKEY=m -CONFIG_HID_FT260=m -CONFIG_HID_GEMBIRD=m -CONFIG_HID_GFRM=m -CONFIG_HID_GLORIOUS=m -CONFIG_HID_HOLTEK=m -CONFIG_HOLTEK_FF=y -CONFIG_HID_GOOGLE_STADIA_FF=m -CONFIG_HID_VIVALDI=m -CONFIG_HID_GT683R=m -CONFIG_HID_KEYTOUCH=m -CONFIG_HID_KYE=m -CONFIG_HID_KYSONA=m -CONFIG_HID_UCLOGIC=m -CONFIG_HID_WALTOP=m -CONFIG_HID_VIEWSONIC=m -CONFIG_HID_XIAOMI=m -CONFIG_HID_GYRATION=m -CONFIG_HID_ICADE=m -CONFIG_HID_ITE=m -CONFIG_HID_JABRA=m -CONFIG_HID_TWINHAN=m -CONFIG_HID_KENSINGTON=m -CONFIG_HID_LCPOWER=m -CONFIG_HID_LENOVO=m -CONFIG_HID_LENOVO_GO=m -CONFIG_HID_LENOVO_GO_S=m -CONFIG_HID_LETSKETCH=m -CONFIG_HID_LOGITECH=m -CONFIG_HID_LOGITECH_DJ=m -CONFIG_LOGITECH_FF=y -CONFIG_LOGIRUMBLEPAD2_FF=y -CONFIG_LOGIG940_FF=y -CONFIG_HID_MAGICMOUSE=y -CONFIG_HID_MALTRON=m -CONFIG_HID_MAYFLASH=m -CONFIG_HID_MEGAWORLD_FF=m -CONFIG_HID_MICROSOFT=m -CONFIG_HID_MONTEREY=m -CONFIG_HID_MULTITOUCH=m -CONFIG_HID_NINTENDO=m -CONFIG_NINTENDO_FF=y -CONFIG_HID_NTI=m -CONFIG_HID_NTRIG=m -CONFIG_HID_NVIDIA_SHIELD=m -CONFIG_NVIDIA_SHIELD_FF=y -CONFIG_HID_ORTEK=m -CONFIG_HID_PANTHERLORD=m -CONFIG_PANTHERLORD_FF=y -CONFIG_HID_PENMOUNT=m -CONFIG_HID_PETALYNX=m -CONFIG_HID_PICOLCD=m -CONFIG_HID_PLANTRONICS=m -CONFIG_HID_PLAYSTATION=m -CONFIG_PLAYSTATION_FF=y -CONFIG_HID_PXRC=m -CONFIG_HID_RAPOO=m -CONFIG_HID_RAZER=m -CONFIG_HID_PRIMAX=m -CONFIG_HID_RETRODE=m -CONFIG_HID_ROCCAT=m -CONFIG_HID_SAITEK=m -CONFIG_HID_SAMSUNG=m -CONFIG_HID_SEMITEK=m -CONFIG_HID_SIGMAMICRO=m -CONFIG_HID_SONY=m -CONFIG_SONY_FF=y -CONFIG_HID_SPEEDLINK=m -CONFIG_HID_STEAM=m -CONFIG_STEAM_FF=y -CONFIG_HID_STEELSERIES=m -CONFIG_HID_SUNPLUS=m -CONFIG_HID_RMI=m -CONFIG_HID_GREENASIA=m -CONFIG_GREENASIA_FF=y -CONFIG_HID_SMARTJOYPLUS=m -CONFIG_SMARTJOYPLUS_FF=y -CONFIG_HID_TIVO=m -CONFIG_HID_TOPSEED=m -CONFIG_HID_TOPRE=m -CONFIG_HID_THINGM=m -CONFIG_HID_THRUSTMASTER=m -CONFIG_THRUSTMASTER_FF=y -CONFIG_HID_UDRAW_PS3=m -CONFIG_HID_U2FZERO=m -CONFIG_HID_UNIVERSAL_PIDFF=m -CONFIG_HID_WACOM=m -CONFIG_HID_WIIMOTE=m -CONFIG_HID_WINWING=m -CONFIG_HID_XINMO=m -CONFIG_HID_ZEROPLUS=m -CONFIG_ZEROPLUS_FF=y -CONFIG_HID_ZYDACRON=m -CONFIG_HID_ALPS=m -CONFIG_HID_MCP2221=m -CONFIG_HID_HUAWEI=m -CONFIG_I2C_HID_ACPI=m -CONFIG_I2C_HID_OF=m -CONFIG_I2C_HID_OF_ELAN=m -CONFIG_HID_PID=y -CONFIG_USB_HIDDEV=y -CONFIG_SPI_HID_APPLE_OF=m -CONFIG_HID_DOCKCHANNEL=y -CONFIG_USB=y -CONFIG_USB_OTG=y -CONFIG_USB_MON=m -CONFIG_USB_XHCI_HCD=y -CONFIG_USB_XHCI_PCI_RENESAS=m -CONFIG_USB_XHCI_RZV2M=y -CONFIG_USB_XHCI_SIDEBAND=y -CONFIG_USB_XHCI_TEGRA=y -CONFIG_USB_BRCMSTB=m -CONFIG_USB_EHCI_HCD=y -CONFIG_USB_EHCI_EXYNOS=y -CONFIG_USB_EHCI_HCD_PLATFORM=y -CONFIG_USB_OHCI_HCD=y -CONFIG_USB_OHCI_EXYNOS=y -CONFIG_USB_OHCI_HCD_PLATFORM=y -CONFIG_USB_RENESAS_USBHS_HCD=m -CONFIG_USB_RENESAS_USBHS=m -CONFIG_USB_PRINTER=m -CONFIG_USB_TMC=m -CONFIG_USB_STORAGE=y -CONFIG_USB_STORAGE_REALTEK=m -CONFIG_USB_STORAGE_DATAFAB=m -CONFIG_USB_STORAGE_FREECOM=m -CONFIG_USB_STORAGE_ISD200=m -CONFIG_USB_STORAGE_USBAT=m -CONFIG_USB_STORAGE_SDDR09=m -CONFIG_USB_STORAGE_SDDR55=m -CONFIG_USB_STORAGE_JUMPSHOT=m -CONFIG_USB_STORAGE_ALAUDA=m -CONFIG_USB_STORAGE_ONETOUCH=m -CONFIG_USB_STORAGE_KARMA=m -CONFIG_USB_STORAGE_CYPRESS_ATACB=m -CONFIG_USB_STORAGE_ENE_UB6250=m -CONFIG_USB_UAS=m -CONFIG_USB_MDC800=m -CONFIG_USB_MICROTEK=m -CONFIG_USBIP_CORE=m -CONFIG_USBIP_VHCI_HCD=m -CONFIG_USBIP_HOST=m -CONFIG_USB_CDNS_SUPPORT=m -CONFIG_USB_CDNS3=m -CONFIG_USB_CDNS3_GADGET=y -CONFIG_USB_CDNS3_HOST=y -CONFIG_USB_MTU3=y -CONFIG_USB_MUSB_HDRC=y -CONFIG_USB_MUSB_SUNXI=y -CONFIG_USB_DWC3=y -CONFIG_USB_DWC2=y -CONFIG_USB_CHIPIDEA=y -CONFIG_USB_CHIPIDEA_UDC=y -CONFIG_USB_CHIPIDEA_HOST=y -CONFIG_USB_ISP1760=y -CONFIG_USB_SERIAL=m -CONFIG_USB_SERIAL_GENERIC=y -CONFIG_USB_SERIAL_SIMPLE=m -CONFIG_USB_SERIAL_AIRCABLE=m -CONFIG_USB_SERIAL_ARK3116=m -CONFIG_USB_SERIAL_BELKIN=m -CONFIG_USB_SERIAL_CH341=m -CONFIG_USB_SERIAL_WHITEHEAT=m -CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m -CONFIG_USB_SERIAL_CP210X=m -CONFIG_USB_SERIAL_CYPRESS_M8=m -CONFIG_USB_SERIAL_EMPEG=m -CONFIG_USB_SERIAL_FTDI_SIO=m -CONFIG_USB_SERIAL_VISOR=m -CONFIG_USB_SERIAL_IPAQ=m -CONFIG_USB_SERIAL_IR=m -CONFIG_USB_SERIAL_EDGEPORT=m -CONFIG_USB_SERIAL_EDGEPORT_TI=m -CONFIG_USB_SERIAL_F81232=m -CONFIG_USB_SERIAL_F8153X=m -CONFIG_USB_SERIAL_GARMIN=m -CONFIG_USB_SERIAL_IPW=m -CONFIG_USB_SERIAL_IUU=m -CONFIG_USB_SERIAL_KEYSPAN_PDA=m -CONFIG_USB_SERIAL_KEYSPAN=m -CONFIG_USB_SERIAL_KLSI=m -CONFIG_USB_SERIAL_KOBIL_SCT=m -CONFIG_USB_SERIAL_MCT_U232=m -CONFIG_USB_SERIAL_MOS7720=m -CONFIG_USB_SERIAL_MOS7840=m -CONFIG_USB_SERIAL_NAVMAN=m -CONFIG_USB_SERIAL_PL2303=m -CONFIG_USB_SERIAL_OTI6858=m -CONFIG_USB_SERIAL_QCAUX=m -CONFIG_USB_SERIAL_QUALCOMM=m -CONFIG_USB_SERIAL_SPCP8X5=m -CONFIG_USB_SERIAL_SAFE=m -CONFIG_USB_SERIAL_SAFE_PADDED=y -CONFIG_USB_SERIAL_SIERRAWIRELESS=m -CONFIG_USB_SERIAL_SYMBOL=m -CONFIG_USB_SERIAL_TI=m -CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_OPTION=m -CONFIG_USB_SERIAL_OMNINET=m -CONFIG_USB_SERIAL_OPTICON=m -CONFIG_USB_SERIAL_XSENS_MT=m -CONFIG_USB_SERIAL_SSU100=m -CONFIG_USB_SERIAL_QT2=m -CONFIG_USB_SERIAL_UPD78F0730=m -CONFIG_USB_SERIAL_XR=m -CONFIG_USB_EMI62=m -CONFIG_USB_EMI26=m -CONFIG_USB_ADUTUX=m -CONFIG_USB_SEVSEG=m -CONFIG_USB_LEGOTOWER=m -CONFIG_USB_LCD=m -CONFIG_USB_IDMOUSE=m -CONFIG_USB_APPLEDISPLAY=m -CONFIG_USB_QCOM_EUD=m -CONFIG_APPLE_MFI_FASTCHARGE=m -CONFIG_USB_SISUSBVGA=m -CONFIG_USB_LD=m -CONFIG_USB_TRANCEVIBRATOR=m -CONFIG_USB_IOWARRIOR=m -CONFIG_USB_ISIGHTFW=m -CONFIG_USB_YUREX=m -CONFIG_USB_HSIC_USB3503=y -CONFIG_USB_CHAOSKEY=m -CONFIG_USB_ONBOARD_DEV=m -CONFIG_USB_ONBOARD_DEV_USB5744=y -CONFIG_NOP_USB_XCEIV=y -CONFIG_USB_MXS_PHY=m -CONFIG_USB_GADGET=y -CONFIG_USB_RENESAS_USBHS_UDC=m -CONFIG_USB_RZV2M_USB3DRD=y -CONFIG_USB_RENESAS_USB3=m -CONFIG_USB_TEGRA_XUDC=m -CONFIG_USB_CONFIGFS=m -CONFIG_USB_CONFIGFS_SERIAL=y -CONFIG_USB_CONFIGFS_ACM=y -CONFIG_USB_CONFIGFS_OBEX=y -CONFIG_USB_CONFIGFS_NCM=y -CONFIG_USB_CONFIGFS_ECM=y -CONFIG_USB_CONFIGFS_ECM_SUBSET=y -CONFIG_USB_CONFIGFS_RNDIS=y -CONFIG_USB_CONFIGFS_EEM=y -CONFIG_USB_CONFIGFS_MASS_STORAGE=y -CONFIG_USB_CONFIGFS_F_FS=y -CONFIG_USB_MASS_STORAGE=m -CONFIG_TYPEC=y -CONFIG_TYPEC_TCPM=m -CONFIG_TYPEC_TCPCI=m -CONFIG_TYPEC_RT1711H=m -CONFIG_TYPEC_MT6360=m -CONFIG_TYPEC_TCPCI_MAXIM=m -CONFIG_TYPEC_FUSB302=m -CONFIG_TYPEC_QCOM_PMIC=m -CONFIG_TYPEC_UCSI=m -CONFIG_UCSI_CCG=m -CONFIG_UCSI_PMIC_GLINK=m -CONFIG_TYPEC_TPS6598X=m -CONFIG_TYPEC_SN201202X=y -CONFIG_TYPEC_HD3SS3220=m -CONFIG_TYPEC_MUX_FSA4480=m -CONFIG_TYPEC_MUX_GPIO_SBU=m -CONFIG_TYPEC_MUX_IT5205=m -CONFIG_TYPEC_MUX_NB7VPQ904M=m -CONFIG_TYPEC_MUX_PS883X=m -CONFIG_TYPEC_MUX_PTN36502=m -CONFIG_TYPEC_MUX_WCD939X_USBSS=m -CONFIG_TYPEC_DP_ALTMODE=m -CONFIG_MMC=y -CONFIG_MMC_BLOCK_MINORS=32 -CONFIG_MMC_ARMMMCI=y -CONFIG_MMC_SDHCI=y -CONFIG_MMC_SDHCI_ACPI=y -CONFIG_MMC_SDHCI_PLTFM=y -CONFIG_MMC_SDHCI_OF_ARASAN=y -CONFIG_MMC_SDHCI_OF_ESDHC=y -CONFIG_MMC_SDHCI_OF_DWCMSHC=y -CONFIG_MMC_SDHCI_OF_SPARX5=y -CONFIG_MMC_SDHCI_CADENCE=y -CONFIG_MMC_SDHCI_ESDHC_IMX=y -CONFIG_MMC_SDHCI_TEGRA=y -CONFIG_MMC_SDHCI_F_SDH30=y -CONFIG_MMC_MESON_GX=y -CONFIG_MMC_SDHCI_MSM=y -CONFIG_MMC_SPI=y -CONFIG_MMC_SDHI=y -CONFIG_MMC_UNIPHIER=y -CONFIG_MMC_DW=y -CONFIG_MMC_DW_EXYNOS=y -CONFIG_MMC_DW_HI3798CV200=y -CONFIG_MMC_DW_K3=y -CONFIG_MMC_DW_ROCKCHIP=y -CONFIG_MMC_VUB300=m -CONFIG_MMC_USHC=m -CONFIG_MMC_REALTEK_USB=m -CONFIG_MMC_SUNXI=y -CONFIG_MMC_BCM2835=y -CONFIG_MMC_MTK=y -CONFIG_MMC_SDHCI_XENON=y -CONFIG_MMC_SDHCI_AM654=y -CONFIG_MMC_OWL=y -CONFIG_SCSI_UFSHCD=y -CONFIG_SCSI_UFS_BSG=y -CONFIG_SCSI_UFS_CRYPTO=y -CONFIG_SCSI_UFSHCD_PLATFORM=y -CONFIG_SCSI_UFS_CDNS_PLATFORM=m -CONFIG_SCSI_UFS_QCOM=m -CONFIG_SCSI_UFS_MEDIATEK=y -CONFIG_SCSI_UFS_HISI=y -CONFIG_SCSI_UFS_RENESAS=m -CONFIG_SCSI_UFS_TI_J721E=m -CONFIG_SCSI_UFS_EXYNOS=y -CONFIG_SCSI_UFS_ROCKCHIP=y -CONFIG_MEMSTICK=m -CONFIG_MSPRO_BLOCK=m -CONFIG_MEMSTICK_REALTEK_USB=m -CONFIG_LEDS_CLASS=y -CONFIG_LEDS_CLASS_FLASH=m -CONFIG_LEDS_CLASS_MULTICOLOR=m -CONFIG_LEDS_LM3692X=m -CONFIG_LEDS_PCA9532=m -CONFIG_LEDS_GPIO=y -CONFIG_LEDS_PWM=y -CONFIG_LEDS_SYSCON=y -CONFIG_LEDS_QCOM_FLASH=m -CONFIG_LEDS_QCOM_LPG=m -CONFIG_LEDS_TRIGGER_TIMER=y -CONFIG_LEDS_TRIGGER_DISK=y -CONFIG_LEDS_TRIGGER_HEARTBEAT=y -CONFIG_LEDS_TRIGGER_CPU=y -CONFIG_LEDS_TRIGGER_DEFAULT_ON=y -CONFIG_LEDS_TRIGGER_PANIC=y -CONFIG_ACCESSIBILITY=y -CONFIG_SPEAKUP=m -CONFIG_SPEAKUP_SYNTH_ACNTSA=m -CONFIG_SPEAKUP_SYNTH_APOLLO=m -CONFIG_SPEAKUP_SYNTH_AUDPTR=m -CONFIG_SPEAKUP_SYNTH_BNS=m -CONFIG_SPEAKUP_SYNTH_DECTLK=m -CONFIG_SPEAKUP_SYNTH_LTLK=m -CONFIG_SPEAKUP_SYNTH_SOFT=m -CONFIG_SPEAKUP_SYNTH_SPKOUT=m -CONFIG_SPEAKUP_SYNTH_TXPRT=m -CONFIG_INFINIBAND=m -CONFIG_INFINIBAND_USER_MAD=m -CONFIG_INFINIBAND_USER_ACCESS=m -CONFIG_RDMA_RXE=m -CONFIG_RDMA_SIW=m -CONFIG_EDAC=y -CONFIG_EDAC_GHES=y -CONFIG_EDAC_LAYERSCAPE=m -CONFIG_EDAC_ZYNQMP=m -CONFIG_EDAC_VERSAL=m -CONFIG_RTC_CLASS=y -CONFIG_RTC_DRV_DS1307=m -CONFIG_RTC_DRV_HYM8563=m -CONFIG_RTC_DRV_MAX77686=y -CONFIG_RTC_DRV_NVIDIA_VRS10=m -CONFIG_RTC_DRV_RK808=m -CONFIG_RTC_DRV_ISL1208=m -CONFIG_RTC_DRV_PCF85363=m -CONFIG_RTC_DRV_PCF8563=m -CONFIG_RTC_DRV_M41T80=m -CONFIG_RTC_DRV_BQ32K=m -CONFIG_RTC_DRV_RX8581=m -CONFIG_RTC_DRV_RV3028=m -CONFIG_RTC_DRV_RV8803=m -CONFIG_RTC_DRV_S5M=y -CONFIG_RTC_DRV_DS3232=y -CONFIG_RTC_DRV_PCF2127=m -CONFIG_RTC_DRV_PCF85063=m -CONFIG_RTC_DRV_DA9063=m -CONFIG_RTC_DRV_EFI=y -CONFIG_RTC_DRV_ZYNQMP=m -CONFIG_RTC_DRV_CROS_EC=y -CONFIG_RTC_DRV_FSL_FTM_ALARM=m -CONFIG_RTC_DRV_S3C=y -CONFIG_RTC_DRV_PL031=y -CONFIG_RTC_DRV_SUN6I=y -CONFIG_RTC_DRV_ARMADA38X=y -CONFIG_RTC_DRV_PM8XXX=m -CONFIG_RTC_DRV_TEGRA=y -CONFIG_RTC_DRV_SNVS=m -CONFIG_RTC_DRV_BBNSM=m -CONFIG_RTC_DRV_IMX_SC=m -CONFIG_RTC_DRV_MT6397=m -CONFIG_RTC_DRV_XGENE=y -CONFIG_RTC_DRV_TI_K3=m -CONFIG_RTC_DRV_RENESAS_RTCA3=m -CONFIG_RTC_DRV_APPLE_PMU=m -CONFIG_RTC_DRV_MACSMC=m -CONFIG_RTC_DRV_S32G=m -CONFIG_DMADEVICES=y -CONFIG_APPLE_SIO=m -CONFIG_DMA_BCM2835=y -CONFIG_DMA_SUN6I=m -CONFIG_FSL_EDMA=y -CONFIG_IMX_SDMA=m -CONFIG_K3_DMA=y -CONFIG_MV_XOR=y -CONFIG_MV_XOR_V2=y -CONFIG_OWL_DMA=y -CONFIG_PL330_DMA=y -CONFIG_TEGRA186_GPC_DMA=y -CONFIG_TEGRA20_APB_DMA=y -CONFIG_TEGRA210_ADMA=m -CONFIG_XILINX_DMA=m -CONFIG_XILINX_ZYNQMP_DMA=m -CONFIG_XILINX_ZYNQMP_DPDMA=m -CONFIG_MTK_UART_APDMA=m -CONFIG_QCOM_BAM_DMA=y -CONFIG_QCOM_GPI_DMA=m -CONFIG_QCOM_HIDMA_MGMT=y -CONFIG_QCOM_HIDMA=y -CONFIG_DW_EDMA=m -CONFIG_RCAR_DMAC=y -CONFIG_RENESAS_USB_DMAC=m -CONFIG_RZ_DMAC=y -CONFIG_TI_K3_UDMA=y -CONFIG_TI_K3_UDMA_GLUE_LAYER=y -CONFIG_STM32_DMA3=m -CONFIG_UDMABUF=y -CONFIG_DMABUF_HEAPS=y -CONFIG_DMABUF_HEAPS_SYSTEM=y -CONFIG_VFIO=y -CONFIG_VFIO_PCI=y -CONFIG_VIRTIO_PCI=y -CONFIG_VIRTIO_BALLOON=y -CONFIG_VIRTIO_MMIO=y -CONFIG_VHOST_NET=m -CONFIG_VHOST_VSOCK=m -CONFIG_XEN_GNTDEV=y -CONFIG_XEN_GRANT_DEV_ALLOC=y -CONFIG_GREYBUS=m -CONFIG_GREYBUS_BEAGLEPLAY=m -CONFIG_GPIB=m -CONFIG_GPIB_AGILENT_82357A=m -CONFIG_GPIB_NI_USB=m -CONFIG_GPIB_LPVO=m -CONFIG_STAGING=y -CONFIG_STAGING_MEDIA=y -CONFIG_VIDEO_MAX96712=m -CONFIG_VIDEO_MESON_VDEC=m -CONFIG_SND_BCM2835=m -CONFIG_CHROME_PLATFORMS=y -CONFIG_CROS_EC=y -CONFIG_CROS_EC_I2C=y -CONFIG_CROS_EC_RPMSG=m -CONFIG_CROS_EC_SPI=y -CONFIG_CROS_KBD_LED_BACKLIGHT=m -CONFIG_CROS_EC_CHARDEV=m -CONFIG_CROS_EC_TYPEC=m -CONFIG_CROS_TYPEC_SWITCH=m -CONFIG_EC_ACER_ASPIRE1=m -CONFIG_EC_HUAWEI_GAOKUN=m -CONFIG_EC_LENOVO_YOGA_C630=m -CONFIG_EC_LENOVO_THINKPAD_T14S=m -CONFIG_COMMON_CLK_RK808=y -CONFIG_COMMON_CLK_SCPI=y -CONFIG_COMMON_CLK_CS2000_CP=y -CONFIG_COMMON_CLK_FSL_SAI=y -CONFIG_COMMON_CLK_S2MPS11=y -CONFIG_COMMON_CLK_PWM=y -CONFIG_COMMON_CLK_RS9_PCIE=y -CONFIG_COMMON_CLK_VC3=y -CONFIG_COMMON_CLK_VC5=y -CONFIG_COMMON_CLK_BD718XX=m -CONFIG_CLK_RASPBERRYPI=y -CONFIG_CLK_IMX8MM=y -CONFIG_CLK_IMX8MN=y -CONFIG_CLK_IMX8MP=y -CONFIG_CLK_IMX8MQ=y -CONFIG_CLK_IMX8QXP=y -CONFIG_CLK_IMX8ULP=y -CONFIG_CLK_IMX93=y -CONFIG_CLK_IMX95_BLK_CTL=y -CONFIG_TI_SCI_CLK=y -CONFIG_COMMON_CLK_QCOM=y -CONFIG_CLK_ELIZA_DISPCC=m -CONFIG_CLK_ELIZA_GCC=y -CONFIG_CLK_ELIZA_TCSRCC=m -CONFIG_CLK_GLYMUR_DISPCC=m -CONFIG_CLK_GLYMUR_GCC=y -CONFIG_CLK_GLYMUR_TCSRCC=m -CONFIG_CLK_KAANAPALI_CAMCC=m -CONFIG_CLK_KAANAPALI_DISPCC=m -CONFIG_CLK_KAANAPALI_GCC=y -CONFIG_CLK_KAANAPALI_GPUCC=m -CONFIG_CLK_KAANAPALI_TCSRCC=m -CONFIG_CLK_KAANAPALI_VIDEOCC=m -CONFIG_CLK_X1E80100_CAMCC=m -CONFIG_CLK_X1E80100_DISPCC=m -CONFIG_CLK_X1E80100_GCC=y -CONFIG_CLK_X1E80100_GPUCC=m -CONFIG_CLK_X1E80100_TCSRCC=y -CONFIG_CLK_X1P42100_GPUCC=m -CONFIG_CLK_QCM2290_GPUCC=m -CONFIG_QCOM_A53PLL=y -CONFIG_QCOM_CLK_APCS_MSM8916=y -CONFIG_QCOM_CLK_APCC_MSM8996=y -CONFIG_QCOM_CLK_SMD_RPM=y -CONFIG_QCOM_CLK_RPMH=y -CONFIG_IPQ_APSS_6018=y -CONFIG_IPQ_CMN_PLL=m -CONFIG_IPQ_GCC_5018=y -CONFIG_IPQ_GCC_5210=y -CONFIG_IPQ_GCC_5332=y -CONFIG_IPQ_GCC_5424=y -CONFIG_IPQ_GCC_6018=y -CONFIG_IPQ_GCC_8074=y -CONFIG_IPQ_GCC_9574=y -CONFIG_IPQ_NSSCC_5424=m -CONFIG_IPQ_NSSCC_9574=m -CONFIG_MSM_GCC_8916=y -CONFIG_MSM_GCC_8953=y -CONFIG_MSM_MMCC_8994=m -CONFIG_MSM_GCC_8994=y -CONFIG_MSM_GCC_8996=y -CONFIG_MSM_MMCC_8996=m -CONFIG_MSM_GCC_8998=y -CONFIG_MSM_MMCC_8998=m -CONFIG_QCM_GCC_2290=y -CONFIG_QCM_DISPCC_2290=m -CONFIG_QCS_DISPCC_615=m -CONFIG_QCS_CAMCC_615=m -CONFIG_QCS_GCC_404=y -CONFIG_SA_CAMCC_8775P=m -CONFIG_QCS_GCC_8300=y -CONFIG_QCS_GCC_615=y -CONFIG_QCS_GPUCC_615=m -CONFIG_QCS_VIDEOCC_615=m -CONFIG_SC_CAMCC_7280=m -CONFIG_SC_CAMCC_8280XP=m -CONFIG_SA_DISPCC_8775P=m -CONFIG_SC_DISPCC_7280=m -CONFIG_SC_DISPCC_8280XP=m -CONFIG_SA_GCC_8775P=y -CONFIG_SA_GPUCC_8775P=m -CONFIG_SC_GCC_7180=y -CONFIG_SC_GCC_7280=y -CONFIG_SC_GCC_8180X=y -CONFIG_SC_GCC_8280XP=y -CONFIG_SC_GPUCC_7280=m -CONFIG_SC_GPUCC_8280XP=m -CONFIG_SC_LPASSCC_8280XP=m -CONFIG_SC_LPASS_CORECC_7280=m -CONFIG_SC_VIDEOCC_7280=m -CONFIG_SDM_CAMCC_845=m -CONFIG_QDU_GCC_1000=y -CONFIG_SDM_GPUCC_845=y -CONFIG_SDM_VIDEOCC_845=y -CONFIG_SDM_DISPCC_845=y -CONFIG_SDM_LPASSCC_845=m -CONFIG_SDX_GCC_75=y -CONFIG_SM_CAMCC_6350=m -CONFIG_SM_CAMCC_MILOS=m -CONFIG_SM_CAMCC_8250=m -CONFIG_SM_CAMCC_8550=m -CONFIG_SM_CAMCC_8650=m -CONFIG_SM_CAMCC_8750=m -CONFIG_SM_DISPCC_6115=m -CONFIG_SM_DISPCC_8250=y -CONFIG_SM_DISPCC_6350=m -CONFIG_SM_DISPCC_MILOS=m -CONFIG_SM_DISPCC_8450=m -CONFIG_SM_DISPCC_8550=m -CONFIG_SM_DISPCC_8750=m -CONFIG_SM_GCC_4450=y -CONFIG_SM_GCC_6115=y -CONFIG_SM_GCC_6350=y -CONFIG_SM_GCC_MILOS=y -CONFIG_SM_GCC_8350=y -CONFIG_SM_GCC_8450=y -CONFIG_SM_GCC_8550=y -CONFIG_SM_GCC_8650=y -CONFIG_SM_GCC_8750=y -CONFIG_SM_GPUCC_6115=m -CONFIG_SM_GPUCC_6350=m -CONFIG_SM_GPUCC_MILOS=m -CONFIG_SM_GPUCC_8150=y -CONFIG_SM_GPUCC_8250=y -CONFIG_SM_GPUCC_8350=m -CONFIG_SM_GPUCC_8450=m -CONFIG_SM_GPUCC_8550=m -CONFIG_SM_GPUCC_8650=m -CONFIG_SM_GPUCC_8750=m -CONFIG_SM_TCSRCC_8550=y -CONFIG_SM_TCSRCC_8650=y -CONFIG_SM_TCSRCC_8750=m -CONFIG_SA_VIDEOCC_8775P=m -CONFIG_SM_VIDEOCC_6350=m -CONFIG_SM_VIDEOCC_MILOS=m -CONFIG_SM_VIDEOCC_8250=y -CONFIG_SM_VIDEOCC_8550=m -CONFIG_SM_VIDEOCC_8750=m -CONFIG_QCOM_HFPLL=y -CONFIG_CLK_GFM_LPASS_SM8250=m -CONFIG_SM_VIDEOCC_8450=m -CONFIG_CLK_RCAR_USB2_CLOCK_SEL=y -CONFIG_CLK_RENESAS_VBATTB=m -CONFIG_EXYNOS_ACPM_CLK=m -CONFIG_CLK_SOPHGO_CV1800=y -CONFIG_XILINX_VCU=m -CONFIG_COMMON_CLK_XLNX_CLKWZRD=m -CONFIG_HWSPINLOCK=y -CONFIG_HWSPINLOCK_OMAP=m -CONFIG_HWSPINLOCK_QCOM=y -CONFIG_TEGRA186_TIMER=y -CONFIG_CLKSRC_STM32_LP=y -CONFIG_RENESAS_OSTM=y -CONFIG_ARM_MHU=y -CONFIG_EXYNOS_MBOX=m -CONFIG_IMX_MBOX=y -CONFIG_PLATFORM_MHU=y -CONFIG_OMAP2PLUS_MBOX=m -CONFIG_BCM2835_MBOX=y -CONFIG_QCOM_APCS_IPC=y -CONFIG_TEGRA_HSP_MBOX=y -CONFIG_MTK_ADSP_MBOX=m -CONFIG_QCOM_CPUCP_MBOX=m -CONFIG_QCOM_IPCC=y -CONFIG_CIX_MBOX=y -CONFIG_ARM_SMMU=y -CONFIG_ARM_SMMU_V3=y -CONFIG_QCOM_IOMMU=y -CONFIG_ROCKCHIP_IOMMU=y -CONFIG_TEGRA_IOMMU_SMMU=y -CONFIG_APPLE_DART=y -CONFIG_MTK_IOMMU=y -CONFIG_REMOTEPROC=y -CONFIG_IMX_REMOTEPROC=y -CONFIG_MTK_SCP=m -CONFIG_QCOM_Q6V5_ADSP=m -CONFIG_QCOM_Q6V5_MSS=m -CONFIG_QCOM_Q6V5_PAS=m -CONFIG_QCOM_SYSMON=m -CONFIG_QCOM_WCNSS_PIL=m -CONFIG_TI_K3_DSP_REMOTEPROC=m -CONFIG_TI_K3_M4_REMOTEPROC=m -CONFIG_TI_K3_R5_REMOTEPROC=m -CONFIG_RPMSG_CHAR=m -CONFIG_RPMSG_CTRL=m -CONFIG_RPMSG_QCOM_GLINK_RPM=y -CONFIG_RPMSG_QCOM_GLINK_SMEM=m -CONFIG_RPMSG_QCOM_SMD=y -CONFIG_RPMSG_VIRTIO=y -CONFIG_SOUNDWIRE=m -CONFIG_SOUNDWIRE_QCOM=m -CONFIG_APPLE_DOCKCHANNEL=y -CONFIG_APPLE_PMGR_MISC=y -CONFIG_APPLE_RTKIT_HELPER=y -CONFIG_APPLE_SART=y -CONFIG_APPLE_CLPC=m -CONFIG_FSL_DPAA=y -CONFIG_FSL_MC_DPIO=y -CONFIG_FSL_RCPM=y -CONFIG_MTK_CMDQ=m -CONFIG_MTK_DEVAPC=m -CONFIG_MTK_PMIC_WRAP=y -CONFIG_MTK_SVS=m -CONFIG_QCOM_AOSS_QMP=y -CONFIG_QCOM_COMMAND_DB=y -CONFIG_QCOM_GENI_SE=y -CONFIG_QCOM_LLCC=m -CONFIG_QCOM_OCMEM=m -CONFIG_QCOM_PMIC_GLINK=m -CONFIG_QCOM_RMTFS_MEM=m -CONFIG_QCOM_RPMH=y -CONFIG_QCOM_SMEM=y -CONFIG_QCOM_SMD_RPM=y -CONFIG_QCOM_SMP2P=y -CONFIG_QCOM_SMSM=y -CONFIG_QCOM_SOCINFO=m -CONFIG_QCOM_SPM=m -CONFIG_QCOM_STATS=m -CONFIG_QCOM_WCNSS_CTRL=m -CONFIG_QCOM_APR=m -CONFIG_QCOM_ICC_BWMON=m -CONFIG_QCOM_PBS=m -CONFIG_ROCKCHIP_IODOMAIN=y -CONFIG_TI_PRUSS=m -CONFIG_OWL_PM_DOMAINS=y -CONFIG_RASPBERRYPI_POWER=y -CONFIG_IMX_SCU_PD=y -CONFIG_QCOM_CPR=y -CONFIG_QCOM_RPMHPD=y -CONFIG_QCOM_RPMPD=y -CONFIG_TI_SCI_PM_DOMAINS=y -CONFIG_ARM_IMX_BUS_DEVFREQ=y -CONFIG_ARM_IMX8M_DDRC_DEVFREQ=m -CONFIG_ARM_MEDIATEK_CCI_DEVFREQ=m -CONFIG_PM_DEVFREQ_EVENT=y -CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI=m -CONFIG_EXTCON_PTN5150=m -CONFIG_EXTCON_USB_GPIO=y -CONFIG_EXTCON_USBC_CROS_EC=y -CONFIG_FSL_IFC=y -CONFIG_RENESAS_RPCIF=m -CONFIG_STM32_OMM=m -CONFIG_DLN2_ADC=m -CONFIG_EXYNOS_ADC=m -CONFIG_IMX8QXP_ADC=m -CONFIG_IMX93_ADC=m -CONFIG_MAX9611=m -CONFIG_MEDIATEK_MT6359_AUXADC=m -CONFIG_MEDIATEK_MT6577_AUXADC=m -CONFIG_QCOM_SPMI_VADC=m -CONFIG_QCOM_SPMI_ADC5=m -CONFIG_ROCKCHIP_SARADC=m -CONFIG_RZG2L_ADC=m -CONFIG_RZT2H_ADC=m -CONFIG_SOPHGO_CV1800B_ADC=m -CONFIG_TI_ADS1015=m -CONFIG_TI_AM335X_ADC=m -CONFIG_XILINX_XADC=m -CONFIG_XILINX_AMS=m -CONFIG_IIO_AOP_SENSOR_ALS=m -CONFIG_IIO_CROS_EC_SENSORS_CORE=m -CONFIG_IIO_CROS_EC_SENSORS=m -CONFIG_IIO_ST_LSM6DSX=m -CONFIG_IIO_CROS_EC_LIGHT_PROX=m -CONFIG_SENSORS_ISL29018=m -CONFIG_VCNL4000=m -CONFIG_IIO_ST_MAGN_3AXIS=m -CONFIG_IIO_STM32_TIMER_TRIGGER=m -CONFIG_IIO_CROS_EC_BARO=m -CONFIG_MPL3115=m -CONFIG_PWM=y -CONFIG_PWM_ADP5585=m -CONFIG_PWM_APPLE=m -CONFIG_PWM_BCM2835=m -CONFIG_PWM_BRCMSTB=m -CONFIG_PWM_CROS_EC=m -CONFIG_PWM_IMX27=m -CONFIG_PWM_MEDIATEK=m -CONFIG_PWM_MESON=m -CONFIG_PWM_MTK_DISP=m -CONFIG_PWM_RENESAS_RCAR=m -CONFIG_PWM_RENESAS_RZG2L_GPT=m -CONFIG_PWM_RENESAS_RZ_MTU3=m -CONFIG_PWM_RENESAS_TPU=m -CONFIG_PWM_ROCKCHIP=y -CONFIG_PWM_SAMSUNG=y -CONFIG_PWM_SL28CPLD=m -CONFIG_PWM_STM32=m -CONFIG_PWM_SUN4I=m -CONFIG_PWM_TEGRA=m -CONFIG_PWM_TIECAP=m -CONFIG_PWM_TIEHRPWM=m -CONFIG_PWM_VISCONTI=m -CONFIG_PWM_XILINX=m -CONFIG_SL28CPLD_INTC=y -CONFIG_XILINX_INTC=y -CONFIG_QCOM_PDC=y -CONFIG_QCOM_MPM=y -CONFIG_TI_SCI_INTR_IRQCHIP=y -CONFIG_TI_SCI_INTA_IRQCHIP=y -CONFIG_RESET_GPIO=m -CONFIG_RESET_IMX7=y -CONFIG_RESET_QCOM_AOSS=y -CONFIG_RESET_QCOM_PDC=m -CONFIG_RESET_RZG2L_USBPHY_CTRL=y -CONFIG_RESET_RZV2H_USB2PHY=m -CONFIG_RESET_TI_SCI=y -CONFIG_PHY_CAN_TRANSCEIVER=m -CONFIG_PHY_NXP_PTN3222=m -CONFIG_PHY_SNPS_EUSB2=m -CONFIG_PHY_XGENE=y -CONFIG_PHY_SUN4I_USB=y -CONFIG_PHY_APPLE_ATC=y -CONFIG_PHY_APPLE_DPTX=m -CONFIG_PHY_CADENCE_TORRENT=m -CONFIG_PHY_CADENCE_DPHY=m -CONFIG_PHY_CADENCE_DPHY_RX=m -CONFIG_PHY_CADENCE_SIERRA=m -CONFIG_PHY_CADENCE_SALVO=m -CONFIG_PHY_FSL_IMX8MQ_USB=m -CONFIG_PHY_MIXEL_MIPI_DPHY=m -CONFIG_PHY_FSL_IMX8M_PCIE=y -CONFIG_PHY_HI6220_USB=y -CONFIG_PHY_HISTB_COMBPHY=y -CONFIG_PHY_HISI_INNO_USB2=y -CONFIG_PHY_MVEBU_CP110_COMPHY=y -CONFIG_PHY_MTK_PCIE=m -CONFIG_PHY_MTK_TPHY=y -CONFIG_PHY_MTK_HDMI=m -CONFIG_PHY_MTK_MIPI_DSI=m -CONFIG_PHY_MTK_DP=m -CONFIG_PHY_QCOM_EDP=m -CONFIG_PHY_QCOM_PCIE2=m -CONFIG_PHY_QCOM_QMP=m -CONFIG_PHY_QCOM_QUSB2=m -CONFIG_PHY_QCOM_EUSB2_REPEATER=m -CONFIG_PHY_QCOM_M31_USB=m -CONFIG_PHY_QCOM_M31_EUSB=m -CONFIG_PHY_QCOM_USB_HS=m -CONFIG_PHY_QCOM_USB_SNPS_FEMTO_V2=m -CONFIG_PHY_QCOM_USB_HS_28NM=m -CONFIG_PHY_QCOM_USB_SS=m -CONFIG_PHY_QCOM_SGMII_ETH=m -CONFIG_PHY_R8A779F0_ETHERNET_SERDES=y -CONFIG_PHY_RCAR_GEN3_PCIE=y -CONFIG_PHY_RCAR_GEN3_USB2=y -CONFIG_PHY_RCAR_GEN3_USB3=m -CONFIG_PHY_RZ_G3E_USB3=m -CONFIG_PHY_ROCKCHIP_EMMC=y -CONFIG_PHY_ROCKCHIP_INNO_HDMI=m -CONFIG_PHY_ROCKCHIP_INNO_USB2=y -CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY=m -CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY=m -CONFIG_PHY_ROCKCHIP_PCIE=m -CONFIG_PHY_ROCKCHIP_SAMSUNG_HDPTX=m -CONFIG_PHY_ROCKCHIP_SNPS_PCIE3=y -CONFIG_PHY_ROCKCHIP_TYPEC=y -CONFIG_PHY_ROCKCHIP_USBDP=m -CONFIG_PHY_SAMSUNG_UFS=y -CONFIG_PHY_EXYNOS5_USBDRD=m -CONFIG_PHY_UNIPHIER_USB2=y -CONFIG_PHY_UNIPHIER_USB3=y -CONFIG_PHY_TEGRA_XUSB=y -CONFIG_PHY_AM654_SERDES=m -CONFIG_PHY_J721E_WIZ=m -CONFIG_OMAP_USB2=m -CONFIG_PHY_XILINX_ZYNQMP=m -CONFIG_ARM_CCI_PMU=m -CONFIG_ARM_CCN=m -CONFIG_ARM_CMN=m -CONFIG_ARM_SMMU_V3_PMU=m -CONFIG_ARM_DSU_PMU=m -CONFIG_FSL_IMX8_DDR_PMU=m -CONFIG_QCOM_L2_PMU=y -CONFIG_QCOM_L3_PMU=y -CONFIG_ARM_SPE_PMU=m -CONFIG_ARM_DMC620_PMU=m -CONFIG_APPLE_M1_CPU_PMU=y -CONFIG_HISI_PMU=y -CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU=m -CONFIG_NVIDIA_CORESIGHT_PMU_ARCH_SYSTEM_PMU=m -CONFIG_MESON_DDR_PMU=m -CONFIG_NVMEM_LAYOUT_SL28_VPD=m -CONFIG_NVMEM_APPLE_EFUSES=m -CONFIG_NVMEM_APPLE_SPMI=y -CONFIG_NVMEM_IMX_OCOTP=y -CONFIG_NVMEM_IMX_OCOTP_ELE=m -CONFIG_NVMEM_IMX_OCOTP_SCU=y -CONFIG_NVMEM_LAYERSCAPE_SFP=m -CONFIG_NVMEM_MTK_EFUSE=y -CONFIG_NVMEM_QCOM_QFPROM=y -CONFIG_NVMEM_QCOM_SEC_QFPROM=m -CONFIG_NVMEM_RMEM=m -CONFIG_NVMEM_ROCKCHIP_EFUSE=y -CONFIG_NVMEM_ROCKCHIP_OTP=y -CONFIG_NVMEM_SNVS_LPGPR=y -CONFIG_NVMEM_SPMI_SDAM=m -CONFIG_NVMEM_SUNXI_SID=y -CONFIG_NVMEM_UNIPHIER_EFUSE=y -CONFIG_NVMEM_ZYNQMP=m -CONFIG_FPGA=y -CONFIG_FPGA_MGR_ALTERA_CVP=m -CONFIG_FPGA_MGR_STRATIX10_SOC=m -CONFIG_FPGA_BRIDGE=m -CONFIG_ALTERA_FREEZE_BRIDGE=m -CONFIG_XILINX_PR_DECOUPLER=m -CONFIG_FPGA_REGION=m -CONFIG_OF_FPGA_REGION=m -CONFIG_FPGA_MGR_ZYNQMP_FPGA=m -CONFIG_FPGA_MGR_VERSAL_FPGA=m -CONFIG_TEE=y -CONFIG_OPTEE=y -CONFIG_QCOMTEE=m -CONFIG_MUX_APPLE_DPXBAR=y -CONFIG_MUX_GPIO=m -CONFIG_MUX_MMIO=y -CONFIG_SLIM_QCOM_NGD_CTRL=m -CONFIG_INTERCONNECT_IMX=y -CONFIG_INTERCONNECT_IMX8MM=m -CONFIG_INTERCONNECT_IMX8MN=m -CONFIG_INTERCONNECT_IMX8MQ=m -CONFIG_INTERCONNECT_IMX8MP=y -CONFIG_INTERCONNECT_QCOM=y -CONFIG_INTERCONNECT_QCOM_ELIZA=y -CONFIG_INTERCONNECT_QCOM_GLYMUR=y -CONFIG_INTERCONNECT_QCOM_KAANAPALI=y -CONFIG_INTERCONNECT_QCOM_MSM8916=m -CONFIG_INTERCONNECT_QCOM_MSM8953=y -CONFIG_INTERCONNECT_QCOM_MSM8996=y -CONFIG_INTERCONNECT_QCOM_OSM_L3=m -CONFIG_INTERCONNECT_QCOM_QCM2290=y -CONFIG_INTERCONNECT_QCOM_QCS404=m -CONFIG_INTERCONNECT_QCOM_QCS615=y -CONFIG_INTERCONNECT_QCOM_QCS8300=y -CONFIG_INTERCONNECT_QCOM_QDU1000=y -CONFIG_INTERCONNECT_QCOM_SA8775P=y -CONFIG_INTERCONNECT_QCOM_SC7180=y -CONFIG_INTERCONNECT_QCOM_SC7280=y -CONFIG_INTERCONNECT_QCOM_SC8180X=y -CONFIG_INTERCONNECT_QCOM_SC8280XP=y -CONFIG_INTERCONNECT_QCOM_SDM845=y -CONFIG_INTERCONNECT_QCOM_SDX75=y -CONFIG_INTERCONNECT_QCOM_SM6115=y -CONFIG_INTERCONNECT_QCOM_SM6350=y -CONFIG_INTERCONNECT_QCOM_MILOS=y -CONFIG_INTERCONNECT_QCOM_SM8150=y -CONFIG_INTERCONNECT_QCOM_SM8250=y -CONFIG_INTERCONNECT_QCOM_SM8350=y -CONFIG_INTERCONNECT_QCOM_SM8450=y -CONFIG_INTERCONNECT_QCOM_SM8550=y -CONFIG_INTERCONNECT_QCOM_SM8650=y -CONFIG_INTERCONNECT_QCOM_SM8750=y -CONFIG_INTERCONNECT_QCOM_X1E80100=y -CONFIG_COUNTER=m -CONFIG_RZ_MTU3_CNT=m -CONFIG_STM32_TIMER_CNT=m -CONFIG_TI_EQEP=m -CONFIG_HTE=y -CONFIG_HTE_TEGRA194=y -CONFIG_HTE_TEGRA194_TEST=m -CONFIG_EXT4_FS=y -CONFIG_EXT4_FS_POSIX_ACL=y -CONFIG_EXT4_FS_SECURITY=y -CONFIG_XFS_FS=m -CONFIG_XFS_SUPPORT_V4=y -CONFIG_XFS_SUPPORT_ASCII_CI=y -CONFIG_XFS_QUOTA=y -CONFIG_XFS_POSIX_ACL=y -CONFIG_XFS_RT=y -CONFIG_BTRFS_FS=y -CONFIG_BTRFS_FS_POSIX_ACL=y -CONFIG_F2FS_FS=m -CONFIG_F2FS_FS_SECURITY=y -CONFIG_F2FS_FS_COMPRESSION=y -CONFIG_F2FS_UNFAIR_RWSEM=y -CONFIG_FANOTIFY=y -CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y -CONFIG_QUOTA=y -CONFIG_AUTOFS_FS=y -CONFIG_FUSE_FS=m -CONFIG_CUSE=m -CONFIG_OVERLAY_FS=m -CONFIG_ISO9660_FS=m -CONFIG_JOLIET=y -CONFIG_ZISOFS=y -CONFIG_UDF_FS=m -CONFIG_MSDOS_FS=m -CONFIG_VFAT_FS=y -CONFIG_EXFAT_FS=m -CONFIG_NTFS3_FS=m -CONFIG_NTFS3_LZX_XPRESS=y -CONFIG_NTFS3_FS_POSIX_ACL=y -CONFIG_TMPFS_POSIX_ACL=y -CONFIG_HUGETLBFS=y -CONFIG_EFIVAR_FS=y -CONFIG_HFS_FS=m -CONFIG_HFSPLUS_FS=m -CONFIG_UBIFS_FS=m -CONFIG_SQUASHFS=y -CONFIG_SQUASHFS_XATTR=y -CONFIG_SQUASHFS_LZ4=y -CONFIG_SQUASHFS_LZO=y -CONFIG_SQUASHFS_XZ=y -CONFIG_SQUASHFS_ZSTD=y -CONFIG_PSTORE_RAM=m -CONFIG_EROFS_FS=m -CONFIG_EROFS_FS_ZIP_DEFLATE=y -CONFIG_EROFS_FS_ZIP_ZSTD=y -CONFIG_EROFS_FS_ZIP_ACCEL=y -CONFIG_NFS_FS=y -CONFIG_NFS_V4=y -CONFIG_NFS_V4_2=y -CONFIG_ROOT_NFS=y -CONFIG_CIFS=m -CONFIG_CIFS_UPCALL=y -CONFIG_CIFS_XATTR=y -CONFIG_CIFS_POSIX=y -CONFIG_CIFS_DFS_UPCALL=y -CONFIG_CIFS_SWN_UPCALL=y -CONFIG_9P_FS=y -CONFIG_NLS_CODEPAGE_437=y -CONFIG_NLS_CODEPAGE_737=m -CONFIG_NLS_CODEPAGE_775=m -CONFIG_NLS_CODEPAGE_850=m -CONFIG_NLS_CODEPAGE_852=m -CONFIG_NLS_CODEPAGE_855=m -CONFIG_NLS_CODEPAGE_857=m -CONFIG_NLS_CODEPAGE_860=m -CONFIG_NLS_CODEPAGE_861=m -CONFIG_NLS_CODEPAGE_862=m -CONFIG_NLS_CODEPAGE_863=m -CONFIG_NLS_CODEPAGE_864=m -CONFIG_NLS_CODEPAGE_865=m -CONFIG_NLS_CODEPAGE_866=m -CONFIG_NLS_CODEPAGE_869=m -CONFIG_NLS_CODEPAGE_936=m -CONFIG_NLS_CODEPAGE_950=m -CONFIG_NLS_CODEPAGE_932=m -CONFIG_NLS_CODEPAGE_949=m -CONFIG_NLS_CODEPAGE_874=m -CONFIG_NLS_ISO8859_8=m -CONFIG_NLS_CODEPAGE_1250=m -CONFIG_NLS_CODEPAGE_1251=m -CONFIG_NLS_ASCII=m -CONFIG_NLS_ISO8859_1=y -CONFIG_NLS_ISO8859_2=m -CONFIG_NLS_ISO8859_3=m -CONFIG_NLS_ISO8859_4=m -CONFIG_NLS_ISO8859_5=m -CONFIG_NLS_ISO8859_6=m -CONFIG_NLS_ISO8859_7=m -CONFIG_NLS_ISO8859_9=m -CONFIG_NLS_ISO8859_13=m -CONFIG_NLS_ISO8859_14=m -CONFIG_NLS_ISO8859_15=m -CONFIG_NLS_KOI8_R=m -CONFIG_NLS_KOI8_U=m -CONFIG_NLS_MAC_ROMAN=m -CONFIG_NLS_MAC_CELTIC=m -CONFIG_NLS_MAC_CENTEURO=m -CONFIG_NLS_MAC_CROATIAN=m -CONFIG_NLS_MAC_CYRILLIC=m -CONFIG_NLS_MAC_GAELIC=m -CONFIG_NLS_MAC_GREEK=m -CONFIG_NLS_MAC_ICELAND=m -CONFIG_NLS_MAC_INUIT=m -CONFIG_NLS_MAC_ROMANIAN=m -CONFIG_NLS_MAC_TURKISH=m -CONFIG_PERSISTENT_KEYRINGS=y -CONFIG_TRUSTED_KEYS=y -# CONFIG_TRUSTED_KEYS_TPM is not set -# CONFIG_TRUSTED_KEYS_TEE is not set -CONFIG_SECURITY=y -CONFIG_SECURITY_YAMA=y -CONFIG_SECURITY_LANDLOCK=y -CONFIG_CRYPTO_USER=y -CONFIG_CRYPTO_NULL=m -CONFIG_CRYPTO_CRYPTD=m -CONFIG_CRYPTO_BENCHMARK=m -CONFIG_CRYPTO_ECDH=y -CONFIG_CRYPTO_AES=y -CONFIG_CRYPTO_BLOWFISH=m -CONFIG_CRYPTO_CAMELLIA=m -CONFIG_CRYPTO_CAST5=m -CONFIG_CRYPTO_CAST6=m -CONFIG_CRYPTO_SERPENT=m -CONFIG_CRYPTO_TWOFISH=m -CONFIG_CRYPTO_ADIANTUM=m -CONFIG_CRYPTO_CTS=m -CONFIG_CRYPTO_HCTR2=m -CONFIG_CRYPTO_LRW=m -CONFIG_CRYPTO_PCBC=m -CONFIG_CRYPTO_AEGIS128=m -CONFIG_CRYPTO_CCM=y -CONFIG_CRYPTO_GCM=y -CONFIG_CRYPTO_ECHAINIV=y -CONFIG_CRYPTO_BLAKE2B=m -CONFIG_CRYPTO_CMAC=y -CONFIG_CRYPTO_HMAC=y -CONFIG_CRYPTO_MD4=m -CONFIG_CRYPTO_RMD160=m -CONFIG_CRYPTO_SHA256=y -CONFIG_CRYPTO_STREEBOG=m -CONFIG_CRYPTO_WP512=m -CONFIG_CRYPTO_XCBC=m -CONFIG_CRYPTO_XXHASH=m -CONFIG_CRYPTO_CRC32C=m -CONFIG_CRYPTO_CRC32=m -CONFIG_CRYPTO_LZ4HC=m -CONFIG_CRYPTO_USER_API_HASH=m -CONFIG_CRYPTO_USER_API_SKCIPHER=m -CONFIG_CRYPTO_USER_API_RNG=m -CONFIG_CRYPTO_USER_API_AEAD=m -CONFIG_CRYPTO_GHASH_ARM64_CE=y -CONFIG_CRYPTO_AES_ARM64_BS=m -CONFIG_CRYPTO_AES_ARM64_CE_CCM=y -CONFIG_CRYPTO_DEV_SUN8I_CE=m -CONFIG_CRYPTO_DEV_FSL_CAAM=m -CONFIG_CRYPTO_DEV_FSL_DPAA2_CAAM=m -CONFIG_CRYPTO_DEV_QCE=m -CONFIG_CRYPTO_DEV_QCOM_RNG=m -CONFIG_CRYPTO_DEV_TEGRA=m -CONFIG_CRYPTO_DEV_XILINX_TRNG=m -CONFIG_CRYPTO_DEV_ZYNQMP_AES=m -CONFIG_CRYPTO_DEV_ZYNQMP_SHA3=m -CONFIG_CRYPTO_DEV_CCREE=m -CONFIG_CRYPTO_DEV_HISI_SEC2=m -CONFIG_CRYPTO_DEV_HISI_ZIP=m -CONFIG_CRYPTO_DEV_HISI_HPRE=m -CONFIG_CRYPTO_DEV_HISI_TRNG=m -CONFIG_CRYPTO_DEV_SA2UL=m -CONFIG_DMA_RESTRICTED_POOL=y -CONFIG_CMA_SIZE_MBYTES=32 -CONFIG_PRINTK_TIME=y -CONFIG_DYNAMIC_DEBUG=y -CONFIG_DEBUG_KERNEL=y -CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y -CONFIG_DEBUG_INFO_REDUCED=y -CONFIG_MAGIC_SYSRQ=y -CONFIG_DEBUG_FS=y -CONFIG_ENABLE_DEFAULT_TRACERS=y -CONFIG_CORESIGHT=m -CONFIG_CORESIGHT_LINK_AND_SINK_TMC=m -CONFIG_CORESIGHT_CATU=m -CONFIG_CORESIGHT_SINK_TPIU=m -CONFIG_CORESIGHT_SINK_ETBV10=m -CONFIG_CORESIGHT_STM=m -CONFIG_CORESIGHT_CPU_DEBUG=m -CONFIG_CORESIGHT_CTI=m -CONFIG_ARM64_USER4K_EXPERIMENTAL=y -CONFIG_MEMTEST=y diff --git a/arch/arm64/configs/j700-device-parity.config b/arch/arm64/configs/j700-device-parity.config deleted file mode 100644 index 55bce7a9ac0af..0000000000000 --- a/arch/arm64/configs/j700-device-parity.config +++ /dev/null @@ -1,1053 +0,0 @@ -# J700 (MacBook Neo, T8140) device-parity fragment. -# -# Brings the J700 configuration to device and feature parity with a general-purpose -# distribution kernel (reference: Fedora Asahi Remix 44, 7.1.13-402.asahi.fc44.aarch64+16k): -# drivers for devices that can attach over USB or Bluetooth, and common protocols, -# filesystems and features. Most entries are modules. Nothing here changes an option -# that defconfig, asahi.config, j700-radio.config or j700.config set. -# -# Merge after j700.config; see that file for the full sequence. - -# Prerequisites of the entries below: nftables core, hidraw, uinput and the ALSA sequencer -CONFIG_NF_TABLES=m -CONFIG_NF_TABLES_INET=y -CONFIG_NF_TABLES_IPV4=y -CONFIG_NF_TABLES_IPV6=y -CONFIG_NF_TABLES_NETDEV=y -CONFIG_NFT_CT=m -CONFIG_NFT_LOG=m -CONFIG_NFT_LIMIT=m -CONFIG_NFT_MASQ=m -CONFIG_NFT_NAT=m -CONFIG_NFT_REJECT=m -CONFIG_NFT_REJECT_INET=m -CONFIG_NFT_FIB_INET=m -CONFIG_NFT_FIB_IPV4=m -CONFIG_NFT_FIB_IPV6=m -CONFIG_HIDRAW=y -CONFIG_INPUT_UINPUT=m -CONFIG_SND_SEQUENCER=m -CONFIG_SND_HRTIMER=m -CONFIG_SND_SEQ_DUMMY=m -CONFIG_SND_SEQ_HRTIMER_DEFAULT=y - -# HID / input / game controllers (USB + Bluetooth HID devices, xpad, joydev, USB tablets/touchscreens) -CONFIG_DRAGONRISE_FF=y -CONFIG_GREENASIA_FF=y -CONFIG_HID_A4TECH=m -CONFIG_HID_ACCUTOUCH=m -CONFIG_HID_ACRUX=m -CONFIG_HID_ACRUX_FF=y -CONFIG_HID_ALPS=m -CONFIG_HID_APPLEIR=m -CONFIG_HID_AUREAL=m -CONFIG_HID_BATTERY_STRENGTH=y -CONFIG_HID_BELKIN=m -CONFIG_HID_BETOP_FF=m -CONFIG_HID_BIGBEN_FF=m -CONFIG_HID_CHERRY=m -CONFIG_HID_CHICONY=m -CONFIG_HID_CMEDIA=m -CONFIG_HID_CORSAIR=m -CONFIG_HID_COUGAR=m -CONFIG_HID_CP2112=m -CONFIG_HID_CREATIVE_SB0540=m -CONFIG_HID_CYPRESS=m -CONFIG_HID_DRAGONRISE=m -CONFIG_HID_ELAN=m -CONFIG_HID_ELECOM=m -CONFIG_HID_ELO=m -CONFIG_HID_EMS_FF=m -CONFIG_HID_EVISION=m -CONFIG_HID_EZKEY=m -CONFIG_HID_FT260=m -CONFIG_HID_GEMBIRD=m -CONFIG_HID_GFRM=m -CONFIG_HID_GLORIOUS=m -CONFIG_HID_GOOGLE_STADIA_FF=m -CONFIG_HID_GREENASIA=m -CONFIG_HID_GT683R=m -CONFIG_HID_GYRATION=m -CONFIG_HID_HOLTEK=m -CONFIG_HID_HUAWEI=m -CONFIG_HID_ICADE=m -CONFIG_HID_ITE=m -CONFIG_HID_JABRA=m -CONFIG_HID_KENSINGTON=m -CONFIG_HID_KEYTOUCH=m -CONFIG_HID_KYE=m -CONFIG_HID_KYSONA=m -CONFIG_HID_LCPOWER=m -CONFIG_HID_LED=m -CONFIG_HID_LENOVO=m -CONFIG_HID_LENOVO_GO=m -CONFIG_HID_LENOVO_GO_S=m -CONFIG_HID_LETSKETCH=m -CONFIG_HID_LOGITECH=m -CONFIG_HID_LOGITECH_DJ=m -CONFIG_HID_LOGITECH_HIDPP=m -CONFIG_HID_MACALLY=m -CONFIG_HID_MALTRON=m -CONFIG_HID_MAYFLASH=m -CONFIG_HID_MCP2221=m -CONFIG_HID_MEGAWORLD_FF=m -CONFIG_HID_MICROSOFT=m -CONFIG_HID_MONTEREY=m -CONFIG_HID_NINTENDO=m -CONFIG_HID_NTI=m -CONFIG_HID_NTRIG=m -CONFIG_HID_NVIDIA_SHIELD=m -CONFIG_HID_ORTEK=m -CONFIG_HID_PANTHERLORD=m -CONFIG_HID_PENMOUNT=m -CONFIG_HID_PETALYNX=m -CONFIG_HID_PICOLCD=m -CONFIG_HID_PICOLCD_BACKLIGHT=y -CONFIG_HID_PICOLCD_FB=y -CONFIG_HID_PICOLCD_LEDS=y -CONFIG_HID_PID=y -CONFIG_HID_PLANTRONICS=m -CONFIG_HID_PLAYSTATION=m -CONFIG_HID_PRIMAX=m -CONFIG_HID_PRODIKEYS=m -CONFIG_HID_PXRC=m -CONFIG_HID_RAPOO=m -CONFIG_HID_RAZER=m -CONFIG_HID_RETRODE=m -CONFIG_HID_RMI=m -CONFIG_HID_ROCCAT=m -CONFIG_HID_SAITEK=m -CONFIG_HID_SAMSUNG=m -CONFIG_HID_SEMITEK=m -CONFIG_HID_SIGMAMICRO=m -CONFIG_HID_SMARTJOYPLUS=m -CONFIG_HID_SONY=m -CONFIG_HID_SPEEDLINK=m -CONFIG_HID_STEAM=m -CONFIG_HID_STEELSERIES=m -CONFIG_HID_SUNPLUS=m -CONFIG_HID_THINGM=m -CONFIG_HID_THRUSTMASTER=m -CONFIG_HID_TIVO=m -CONFIG_HID_TOPRE=m -CONFIG_HID_TOPSEED=m -CONFIG_HID_TWINHAN=m -CONFIG_HID_U2FZERO=m -CONFIG_HID_UCLOGIC=m -CONFIG_HID_UDRAW_PS3=m -CONFIG_HID_UNIVERSAL_PIDFF=m -CONFIG_HID_VIEWSONIC=m -CONFIG_HID_VIVALDI=m -CONFIG_HID_WACOM=m -CONFIG_HID_WALTOP=m -CONFIG_HID_WIIMOTE=m -CONFIG_HID_WINWING=m -CONFIG_HID_XIAOMI=m -CONFIG_HID_XINMO=m -CONFIG_HID_ZEROPLUS=m -CONFIG_HID_ZYDACRON=m -CONFIG_HOLTEK_FF=y -CONFIG_INPUT_JOYDEV=m -CONFIG_INPUT_JOYSTICK=y -CONFIG_INPUT_TABLET=y -CONFIG_JOYSTICK_IFORCE=m -CONFIG_JOYSTICK_IFORCE_USB=m -CONFIG_JOYSTICK_PXRC=m -CONFIG_JOYSTICK_XPAD=m -CONFIG_JOYSTICK_XPAD_FF=y -CONFIG_JOYSTICK_XPAD_LEDS=y -CONFIG_LOGIG940_FF=y -CONFIG_LOGIRUMBLEPAD2_FF=y -CONFIG_LOGITECH_FF=y -CONFIG_LOGIWHEELS_FF=y -CONFIG_MOUSE_SYNAPTICS_USB=m -CONFIG_NINTENDO_FF=y -CONFIG_NVIDIA_SHIELD_FF=y -CONFIG_PANTHERLORD_FF=y -CONFIG_PLAYSTATION_FF=y -CONFIG_RMI4_CORE=m -CONFIG_RMI4_F03=y -CONFIG_RMI4_F11=y -CONFIG_RMI4_F12=y -CONFIG_RMI4_F1A=y -CONFIG_RMI4_F21=y -CONFIG_RMI4_F30=y -CONFIG_RMI4_F34=y -CONFIG_RMI4_F3A=y -CONFIG_RMI4_F55=y -CONFIG_SMARTJOYPLUS_FF=y -CONFIG_SONY_FF=y -CONFIG_STEAM_FF=y -CONFIG_TABLET_USB_ACECAD=m -CONFIG_TABLET_USB_AIPTEK=m -CONFIG_TABLET_USB_HANWANG=m -CONFIG_TABLET_USB_KBTAB=m -CONFIG_TABLET_USB_PEGASUS=m -CONFIG_THRUSTMASTER_FF=y -CONFIG_TOUCHSCREEN_USB_3M=y -CONFIG_TOUCHSCREEN_USB_COMPOSITE=m -CONFIG_TOUCHSCREEN_USB_DMC_TSC10=y -CONFIG_TOUCHSCREEN_USB_E2I=y -CONFIG_TOUCHSCREEN_USB_EASYTOUCH=y -CONFIG_TOUCHSCREEN_USB_EGALAX=y -CONFIG_TOUCHSCREEN_USB_ELO=y -CONFIG_TOUCHSCREEN_USB_ETT_TC45USB=y -CONFIG_TOUCHSCREEN_USB_ETURBO=y -CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH=y -CONFIG_TOUCHSCREEN_USB_GOTOP=y -CONFIG_TOUCHSCREEN_USB_GUNZE=y -CONFIG_TOUCHSCREEN_USB_IDEALTEK=y -CONFIG_TOUCHSCREEN_USB_IRTOUCH=y -CONFIG_TOUCHSCREEN_USB_ITM=y -CONFIG_TOUCHSCREEN_USB_JASTEC=y -CONFIG_TOUCHSCREEN_USB_NEXIO=y -CONFIG_TOUCHSCREEN_USB_PANJIT=y -CONFIG_TOUCHSCREEN_USB_ZYTRONIC=y -CONFIG_USB_HIDDEV=y -CONFIG_ZEROPLUS_FF=y - -# USB device classes: serial adapters, storage sub-drivers, printers/TMC/WDM, misc USB gadgets, usbmon, USB/IP, card readers, maker adapters -CONFIG_APPLE_MFI_FASTCHARGE=m -CONFIG_DLN2_ADC=m -CONFIG_GNSS_USB=m -CONFIG_GPIB=m -CONFIG_GPIB_AGILENT_82357A=m -CONFIG_GPIB_COMMON=m -CONFIG_GPIB_LPVO=m -CONFIG_GPIB_NI_USB=m -CONFIG_GPIO_DLN2=m -CONFIG_GPIO_MPSSE=m -CONFIG_I2C_CP2615=m -CONFIG_I2C_DIOLAN_U2C=m -CONFIG_I2C_DLN2=m -CONFIG_I2C_TINY_USB=m -CONFIG_MEMSTICK=m -CONFIG_MEMSTICK_REALTEK_USB=m -CONFIG_MFD_DLN2=m -CONFIG_MISC_RTSX_USB=m -CONFIG_MMC_REALTEK_USB=m -CONFIG_MMC_USHC=m -CONFIG_MMC_VUB300=m -CONFIG_MSPRO_BLOCK=m -CONFIG_REALTEK_AUTOPM=y -CONFIG_SENSORS_AQUACOMPUTER_D5NEXT=m -CONFIG_SENSORS_ASUS_ROG_RYUJIN=m -CONFIG_SENSORS_CORSAIR_CPRO=m -CONFIG_SENSORS_CORSAIR_PSU=m -CONFIG_SENSORS_GIGABYTE_WATERFORCE=m -CONFIG_SENSORS_NZXT_KRAKEN2=m -CONFIG_SENSORS_NZXT_KRAKEN3=m -CONFIG_SENSORS_NZXT_SMART2=m -CONFIG_SENSORS_POWERZ=m -CONFIG_SPI_CH341=m -CONFIG_SPI_DLN2=m -CONFIG_USBIP_CORE=m -CONFIG_USBIP_HOST=m -CONFIG_USBIP_VHCI_HCD=m -CONFIG_USBPCWATCHDOG=m -CONFIG_USB_ADUTUX=m -CONFIG_USB_APPLEDISPLAY=m -CONFIG_USB_CHAOSKEY=m -CONFIG_USB_EMI26=m -CONFIG_USB_EMI62=m -CONFIG_USB_EZUSB_FX2=m -CONFIG_USB_IDMOUSE=m -CONFIG_USB_IOWARRIOR=m -CONFIG_USB_ISIGHTFW=m -CONFIG_USB_LCD=m -CONFIG_USB_LD=m -CONFIG_USB_LEGOTOWER=m -CONFIG_USB_MDC800=m -CONFIG_USB_MICROTEK=m -CONFIG_USB_MON=m -CONFIG_USB_PRINTER=m -CONFIG_USB_SERIAL_AIRCABLE=m -CONFIG_USB_SERIAL_ARK3116=m -CONFIG_USB_SERIAL_BELKIN=m -CONFIG_USB_SERIAL_CH341=m -CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_CYPRESS_M8=m -CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m -CONFIG_USB_SERIAL_EDGEPORT=m -CONFIG_USB_SERIAL_EDGEPORT_TI=m -CONFIG_USB_SERIAL_EMPEG=m -CONFIG_USB_SERIAL_F81232=m -CONFIG_USB_SERIAL_F8153X=m -CONFIG_USB_SERIAL_GARMIN=m -CONFIG_USB_SERIAL_GENERIC=y -CONFIG_USB_SERIAL_IPAQ=m -CONFIG_USB_SERIAL_IPW=m -CONFIG_USB_SERIAL_IR=m -CONFIG_USB_SERIAL_IUU=m -CONFIG_USB_SERIAL_KEYSPAN=m -CONFIG_USB_SERIAL_KEYSPAN_PDA=m -CONFIG_USB_SERIAL_KLSI=m -CONFIG_USB_SERIAL_KOBIL_SCT=m -CONFIG_USB_SERIAL_MCT_U232=m -CONFIG_USB_SERIAL_MOS7720=m -CONFIG_USB_SERIAL_MOS7840=m -CONFIG_USB_SERIAL_NAVMAN=m -CONFIG_USB_SERIAL_OMNINET=m -CONFIG_USB_SERIAL_OPTICON=m -CONFIG_USB_SERIAL_OTI6858=m -CONFIG_USB_SERIAL_PL2303=m -CONFIG_USB_SERIAL_QCAUX=m -CONFIG_USB_SERIAL_QT2=m -CONFIG_USB_SERIAL_QUALCOMM=m -CONFIG_USB_SERIAL_SAFE=m -CONFIG_USB_SERIAL_SAFE_PADDED=y -CONFIG_USB_SERIAL_SIERRAWIRELESS=m -CONFIG_USB_SERIAL_SIMPLE=m -CONFIG_USB_SERIAL_SPCP8X5=m -CONFIG_USB_SERIAL_SSU100=m -CONFIG_USB_SERIAL_SYMBOL=m -CONFIG_USB_SERIAL_TI=m -CONFIG_USB_SERIAL_UPD78F0730=m -CONFIG_USB_SERIAL_VISOR=m -CONFIG_USB_SERIAL_WHITEHEAT=m -CONFIG_USB_SERIAL_XR=m -CONFIG_USB_SERIAL_XSENS_MT=m -CONFIG_USB_SEVSEG=m -CONFIG_USB_SISUSBVGA=m -CONFIG_USB_STORAGE_ALAUDA=m -CONFIG_USB_STORAGE_CYPRESS_ATACB=m -CONFIG_USB_STORAGE_DATAFAB=m -CONFIG_USB_STORAGE_ENE_UB6250=m -CONFIG_USB_STORAGE_FREECOM=m -CONFIG_USB_STORAGE_ISD200=m -CONFIG_USB_STORAGE_JUMPSHOT=m -CONFIG_USB_STORAGE_KARMA=m -CONFIG_USB_STORAGE_ONETOUCH=m -CONFIG_USB_STORAGE_REALTEK=m -CONFIG_USB_STORAGE_SDDR09=m -CONFIG_USB_STORAGE_SDDR55=m -CONFIG_USB_STORAGE_USBAT=m -CONFIG_USB_TMC=m -CONFIG_USB_TRANCEVIBRATOR=m -CONFIG_USB_WDM=m -CONFIG_USB_YUREX=m -CONFIG_W1=m -CONFIG_W1_MASTER_DS2490=m -CONFIG_W1_SLAVE_DS2405=m -CONFIG_W1_SLAVE_DS2406=m -CONFIG_W1_SLAVE_DS2408=m -CONFIG_W1_SLAVE_DS2413=m -CONFIG_W1_SLAVE_DS2423=m -CONFIG_W1_SLAVE_DS2430=m -CONFIG_W1_SLAVE_DS2431=m -CONFIG_W1_SLAVE_DS2433=m -CONFIG_W1_SLAVE_DS2433_CRC=y -CONFIG_W1_SLAVE_DS2438=m -CONFIG_W1_SLAVE_DS2780=m -CONFIG_W1_SLAVE_DS2781=m -CONFIG_W1_SLAVE_DS2805=m -CONFIG_W1_SLAVE_DS28E04=m -CONFIG_W1_SLAVE_SMEM=m -CONFIG_W1_SLAVE_THERM=m -CONFIG_XILLYUSB=m - -# USB network adapters: USB Ethernet/tethering/mobile broadband, USB Wi-Fi dongles, USB CAN adapters -CONFIG_AR5523=m -CONFIG_ATH10K_USB=m -CONFIG_ATH6KL=m -CONFIG_ATH6KL_USB=m -CONFIG_ATH9K_HTC=m -CONFIG_BRCMFMAC_USB=y -CONFIG_CAN_8DEV_USB=m -CONFIG_CAN_EMS_USB=m -CONFIG_CAN_ESD_USB=m -CONFIG_CAN_F81604=m -CONFIG_CAN_GS_USB=m -CONFIG_CAN_ISOTP=m -CONFIG_CAN_J1939=m -CONFIG_CAN_KVASER_USB=m -CONFIG_CAN_MCBA_USB=m -CONFIG_CAN_PEAK_USB=m -CONFIG_CARL9170=m -CONFIG_CARL9170_LEDS=y -CONFIG_LIBERTAS_THINFIRM=m -CONFIG_LIBERTAS_THINFIRM_USB=m -CONFIG_MT7601U=m -CONFIG_MT7663U=m -CONFIG_MT76x0U=m -CONFIG_MT76x2U=m -CONFIG_MT7921U=m -CONFIG_MT7925U=m -CONFIG_RT2500USB=m -CONFIG_RT2800USB=m -CONFIG_RT2800USB_RT33XX=y -CONFIG_RT2800USB_RT3573=y -CONFIG_RT2800USB_RT35XX=y -CONFIG_RT2800USB_RT53XX=y -CONFIG_RT2800USB_RT55XX=y -CONFIG_RT2800USB_UNKNOWN=y -CONFIG_RT2X00=m -CONFIG_RT73USB=m -CONFIG_RTL8187=m -CONFIG_RTL8192DU=m -CONFIG_RTL8XXXU=m -CONFIG_RTW88=m -CONFIG_RTW88_8723DU=m -CONFIG_RTW88_8812AU=m -CONFIG_RTW88_8814AU=m -CONFIG_RTW88_8821AU=m -CONFIG_RTW88_8821CU=m -CONFIG_RTW88_8822BU=m -CONFIG_RTW88_8822CU=m -CONFIG_RTW89=m -CONFIG_RTW89_8851BU=m -CONFIG_RTW89_8852AU=m -CONFIG_RTW89_8852BU=m -CONFIG_RTW89_8852CU=m -CONFIG_USB_ALI_M5632=y -CONFIG_USB_AN2720=y -CONFIG_USB_CATC=m -CONFIG_USB_EPSON2888=y -CONFIG_USB_HSO=m -CONFIG_USB_IPHETH=m -CONFIG_USB_KAWETH=m -CONFIG_USB_KC2190=y -CONFIG_USB_NET_AQC111=m -CONFIG_USB_NET_CDC_EEM=m -CONFIG_USB_NET_CDC_MBIM=m -CONFIG_USB_NET_CH9200=m -CONFIG_USB_NET_CX82310_ETH=m -CONFIG_USB_NET_GL620A=m -CONFIG_USB_NET_HUAWEI_CDC_NCM=m -CONFIG_USB_NET_INT51X1=m -CONFIG_USB_NET_KALMIA=m -CONFIG_USB_NET_QMI_WWAN=m -CONFIG_USB_NET_RNDIS_HOST=m -CONFIG_USB_NET_SR9700=m -CONFIG_USB_SIERRA_NET=m -CONFIG_USB_VL600=m -CONFIG_ZD1211RW=m - -# Storage: optical/SCSI generic, device-mapper (LUKS, LVM, verity, integrity), MD RAID, partition tables -CONFIG_AIX_PARTITION=y -CONFIG_BLK_DEV_SR=m -CONFIG_BSD_DISKLABEL=y -CONFIG_CHR_DEV_SG=m -CONFIG_DM_CACHE=m -CONFIG_DM_CACHE_SMQ=m -CONFIG_DM_CRYPT=m -CONFIG_DM_INTEGRITY=m -CONFIG_DM_RAID=m -CONFIG_DM_SNAPSHOT=m -CONFIG_DM_THIN_PROVISIONING=m -CONFIG_DM_UEVENT=y -CONFIG_DM_VERITY=m -CONFIG_DM_VERITY_FEC=y -CONFIG_DM_WRITECACHE=m -CONFIG_LDM_PARTITION=y -CONFIG_MAC_PARTITION=y -CONFIG_MD_LINEAR=m -CONFIG_MD_RAID0=m -CONFIG_MD_RAID1=m -CONFIG_MD_RAID10=m -CONFIG_MD_RAID456=m -CONFIG_MINIX_SUBPARTITION=y -CONFIG_OSF_PARTITION=y -CONFIG_PARTITION_ADVANCED=y -CONFIG_SGI_PARTITION=y -CONFIG_SOLARIS_X86_PARTITION=y -CONFIG_SUN_PARTITION=y -CONFIG_UNIXWARE_DISKLABEL=y - -# Filesystems for removable media and images, binfmt_misc, SMB client -CONFIG_BINFMT_MISC=m -CONFIG_CIFS=m -CONFIG_CIFS_ALLOW_INSECURE_LEGACY=y -CONFIG_CIFS_DFS_UPCALL=y -CONFIG_CIFS_POSIX=y -CONFIG_CIFS_SWN_UPCALL=y -CONFIG_CIFS_UPCALL=y -CONFIG_CIFS_XATTR=y -CONFIG_EROFS_FS=m -CONFIG_EROFS_FS_BACKED_BY_FILE=y -CONFIG_EROFS_FS_POSIX_ACL=y -CONFIG_EROFS_FS_SECURITY=y -CONFIG_EROFS_FS_XATTR=y -CONFIG_EROFS_FS_ZIP=y -CONFIG_EROFS_FS_ZIP_ACCEL=y -CONFIG_EROFS_FS_ZIP_DEFLATE=y -CONFIG_EROFS_FS_ZIP_LZMA=y -CONFIG_EROFS_FS_ZIP_ZSTD=y -CONFIG_EXFAT_FS=m -CONFIG_F2FS_FS=m -CONFIG_F2FS_FS_COMPRESSION=y -CONFIG_F2FS_FS_LZ4=y -CONFIG_F2FS_FS_LZ4HC=y -CONFIG_F2FS_FS_LZO=y -CONFIG_F2FS_FS_LZORLE=y -CONFIG_F2FS_FS_POSIX_ACL=y -CONFIG_F2FS_FS_SECURITY=y -CONFIG_F2FS_FS_XATTR=y -CONFIG_F2FS_FS_ZSTD=y -CONFIG_F2FS_IOSTAT=y -CONFIG_F2FS_STAT_FS=y -CONFIG_F2FS_UNFAIR_RWSEM=y -CONFIG_HFSPLUS_FS=m -CONFIG_HFS_FS=m -CONFIG_ISO9660_FS=m -CONFIG_JOLIET=y -CONFIG_MSDOS_FS=m -CONFIG_NLS_ASCII=m -CONFIG_NLS_CODEPAGE_1250=m -CONFIG_NLS_CODEPAGE_1251=m -CONFIG_NLS_CODEPAGE_737=m -CONFIG_NLS_CODEPAGE_775=m -CONFIG_NLS_CODEPAGE_850=m -CONFIG_NLS_CODEPAGE_852=m -CONFIG_NLS_CODEPAGE_855=m -CONFIG_NLS_CODEPAGE_857=m -CONFIG_NLS_CODEPAGE_860=m -CONFIG_NLS_CODEPAGE_861=m -CONFIG_NLS_CODEPAGE_862=m -CONFIG_NLS_CODEPAGE_863=m -CONFIG_NLS_CODEPAGE_864=m -CONFIG_NLS_CODEPAGE_865=m -CONFIG_NLS_CODEPAGE_866=m -CONFIG_NLS_CODEPAGE_869=m -CONFIG_NLS_CODEPAGE_874=m -CONFIG_NLS_CODEPAGE_932=m -CONFIG_NLS_CODEPAGE_936=m -CONFIG_NLS_CODEPAGE_949=m -CONFIG_NLS_CODEPAGE_950=m -CONFIG_NLS_ISO8859_13=m -CONFIG_NLS_ISO8859_14=m -CONFIG_NLS_ISO8859_15=m -CONFIG_NLS_ISO8859_2=m -CONFIG_NLS_ISO8859_3=m -CONFIG_NLS_ISO8859_4=m -CONFIG_NLS_ISO8859_5=m -CONFIG_NLS_ISO8859_6=m -CONFIG_NLS_ISO8859_7=m -CONFIG_NLS_ISO8859_8=m -CONFIG_NLS_ISO8859_9=m -CONFIG_NLS_KOI8_R=m -CONFIG_NLS_KOI8_U=m -CONFIG_NLS_MAC_CELTIC=m -CONFIG_NLS_MAC_CENTEURO=m -CONFIG_NLS_MAC_CROATIAN=m -CONFIG_NLS_MAC_CYRILLIC=m -CONFIG_NLS_MAC_GAELIC=m -CONFIG_NLS_MAC_GREEK=m -CONFIG_NLS_MAC_ICELAND=m -CONFIG_NLS_MAC_INUIT=m -CONFIG_NLS_MAC_ROMAN=m -CONFIG_NLS_MAC_ROMANIAN=m -CONFIG_NLS_MAC_TURKISH=m -CONFIG_NLS_UTF8=m -CONFIG_NTFS3_FS=m -CONFIG_NTFS3_FS_POSIX_ACL=y -CONFIG_NTFS3_LZX_XPRESS=y -CONFIG_SQUASHFS_LZ4=y -CONFIG_SQUASHFS_LZO=y -CONFIG_SQUASHFS_XATTR=y -CONFIG_SQUASHFS_XZ=y -CONFIG_SQUASHFS_ZSTD=y -CONFIG_UDF_FS=m -CONFIG_XFS_FS=m -CONFIG_XFS_ONLINE_REPAIR=y -CONFIG_XFS_ONLINE_SCRUB=y -CONFIG_XFS_ONLINE_SCRUB_STATS=y -CONFIG_XFS_POSIX_ACL=y -CONFIG_XFS_QUOTA=y -CONFIG_XFS_RT=y -CONFIG_XFS_SUPPORT_ASCII_CI=y -CONFIG_XFS_SUPPORT_V4=y -CONFIG_ZISOFS=y - -# Network protocols: WireGuard/OpenVPN-DCO/L2TP/PPP/IPsec/kTLS, tunnels, policy routing, nftables+xtables+ipset+ebtables, tc, TCP CC, ss diag, virtual links -CONFIG_BONDING=m -CONFIG_BRIDGE_EBT_802_3=m -CONFIG_BRIDGE_EBT_AMONG=m -CONFIG_BRIDGE_EBT_ARP=m -CONFIG_BRIDGE_EBT_ARPREPLY=m -CONFIG_BRIDGE_EBT_BROUTE=m -CONFIG_BRIDGE_EBT_DNAT=m -CONFIG_BRIDGE_EBT_IP=m -CONFIG_BRIDGE_EBT_IP6=m -CONFIG_BRIDGE_EBT_LIMIT=m -CONFIG_BRIDGE_EBT_LOG=m -CONFIG_BRIDGE_EBT_MARK=m -CONFIG_BRIDGE_EBT_MARK_T=m -CONFIG_BRIDGE_EBT_NFLOG=m -CONFIG_BRIDGE_EBT_PKTTYPE=m -CONFIG_BRIDGE_EBT_REDIRECT=m -CONFIG_BRIDGE_EBT_SNAT=m -CONFIG_BRIDGE_EBT_STP=m -CONFIG_BRIDGE_EBT_T_FILTER=m -CONFIG_BRIDGE_EBT_T_NAT=m -CONFIG_BRIDGE_EBT_VLAN=m -CONFIG_BRIDGE_NF_EBTABLES=m -CONFIG_BRIDGE_NF_EBTABLES_LEGACY=m -CONFIG_CLS_U32_MARK=y -CONFIG_CLS_U32_PERF=y -CONFIG_DEFAULT_CUBIC=y -CONFIG_DUMMY=m -CONFIG_GACT_PROB=y -CONFIG_GENEVE=m -CONFIG_IFB=m -CONFIG_INET6_AH=m -CONFIG_INET6_ESP=m -CONFIG_INET6_ESPINTCP=y -CONFIG_INET6_ESP_OFFLOAD=m -CONFIG_INET6_IPCOMP=m -CONFIG_INET_AH=m -CONFIG_INET_DIAG_DESTROY=y -CONFIG_INET_ESP=m -CONFIG_INET_ESPINTCP=y -CONFIG_INET_ESP_OFFLOAD=m -CONFIG_INET_IPCOMP=m -CONFIG_INET_RAW_DIAG=m -CONFIG_INET_UDP_DIAG=m -CONFIG_IP6_NF_FILTER=m -CONFIG_IP6_NF_IPTABLES_LEGACY=m -CONFIG_IP6_NF_MANGLE=m -CONFIG_IP6_NF_MATCH_AH=m -CONFIG_IP6_NF_MATCH_EUI64=m -CONFIG_IP6_NF_MATCH_FRAG=m -CONFIG_IP6_NF_MATCH_HL=m -CONFIG_IP6_NF_MATCH_IPV6HEADER=m -CONFIG_IP6_NF_MATCH_MH=m -CONFIG_IP6_NF_MATCH_OPTS=m -CONFIG_IP6_NF_MATCH_RPFILTER=m -CONFIG_IP6_NF_MATCH_RT=m -CONFIG_IP6_NF_MATCH_SRH=m -CONFIG_IP6_NF_NAT=m -CONFIG_IP6_NF_RAW=m -CONFIG_IP6_NF_TARGET_HL=m -CONFIG_IP6_NF_TARGET_MASQUERADE=m -CONFIG_IP6_NF_TARGET_NPT=m -CONFIG_IP6_NF_TARGET_REJECT=m -CONFIG_IPV6_GRE=m -CONFIG_IPV6_MULTIPLE_TABLES=y -CONFIG_IPV6_TUNNEL=m -CONFIG_IPV6_VTI=m -CONFIG_IP_ADVANCED_ROUTER=y -CONFIG_IP_MULTIPLE_TABLES=y -CONFIG_IP_NF_ARPFILTER=m -CONFIG_IP_NF_ARPTABLES=m -CONFIG_IP_NF_ARP_MANGLE=m -CONFIG_IP_NF_FILTER=m -CONFIG_IP_NF_IPTABLES_LEGACY=m -CONFIG_IP_NF_MANGLE=m -CONFIG_IP_NF_MATCH_AH=m -CONFIG_IP_NF_MATCH_ECN=m -CONFIG_IP_NF_MATCH_RPFILTER=m -CONFIG_IP_NF_MATCH_TTL=m -CONFIG_IP_NF_NAT=m -CONFIG_IP_NF_RAW=m -CONFIG_IP_NF_TARGET_ECN=m -CONFIG_IP_NF_TARGET_MASQUERADE=m -CONFIG_IP_NF_TARGET_NETMAP=m -CONFIG_IP_NF_TARGET_REDIRECT=m -CONFIG_IP_NF_TARGET_REJECT=m -CONFIG_IP_NF_TARGET_TTL=m -CONFIG_IP_ROUTE_MULTIPATH=y -CONFIG_IP_SET=m -CONFIG_IP_SET_BITMAP_IP=m -CONFIG_IP_SET_BITMAP_IPMAC=m -CONFIG_IP_SET_BITMAP_PORT=m -CONFIG_IP_SET_HASH_IP=m -CONFIG_IP_SET_HASH_IPMAC=m -CONFIG_IP_SET_HASH_IPMARK=m -CONFIG_IP_SET_HASH_IPPORT=m -CONFIG_IP_SET_HASH_IPPORTIP=m -CONFIG_IP_SET_HASH_IPPORTNET=m -CONFIG_IP_SET_HASH_MAC=m -CONFIG_IP_SET_HASH_NET=m -CONFIG_IP_SET_HASH_NETIFACE=m -CONFIG_IP_SET_HASH_NETNET=m -CONFIG_IP_SET_HASH_NETPORT=m -CONFIG_IP_SET_HASH_NETPORTNET=m -CONFIG_IP_SET_LIST_SET=m -CONFIG_L2TP=m -CONFIG_L2TP_ETH=m -CONFIG_L2TP_IP=m -CONFIG_L2TP_V3=y -CONFIG_NETFILTER_NETLINK_ACCT=m -CONFIG_NETFILTER_NETLINK_GLUE_CT=y -CONFIG_NETFILTER_NETLINK_HOOK=m -CONFIG_NETFILTER_NETLINK_LOG=m -CONFIG_NETFILTER_NETLINK_OSF=m -CONFIG_NETFILTER_NETLINK_QUEUE=m -CONFIG_NETFILTER_XTABLES_LEGACY=y -CONFIG_NETFILTER_XT_CONNMARK=m -CONFIG_NETFILTER_XT_MATCH_BPF=m -CONFIG_NETFILTER_XT_MATCH_CGROUP=m -CONFIG_NETFILTER_XT_MATCH_CLUSTER=m -CONFIG_NETFILTER_XT_MATCH_COMMENT=m -CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m -CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m -CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m -CONFIG_NETFILTER_XT_MATCH_CONNMARK=m -CONFIG_NETFILTER_XT_MATCH_CPU=m -CONFIG_NETFILTER_XT_MATCH_DCCP=m -CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m -CONFIG_NETFILTER_XT_MATCH_DSCP=m -CONFIG_NETFILTER_XT_MATCH_ECN=m -CONFIG_NETFILTER_XT_MATCH_ESP=m -CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m -CONFIG_NETFILTER_XT_MATCH_HELPER=m -CONFIG_NETFILTER_XT_MATCH_HL=m -CONFIG_NETFILTER_XT_MATCH_IPCOMP=m -CONFIG_NETFILTER_XT_MATCH_IPRANGE=m -CONFIG_NETFILTER_XT_MATCH_L2TP=m -CONFIG_NETFILTER_XT_MATCH_LENGTH=m -CONFIG_NETFILTER_XT_MATCH_LIMIT=m -CONFIG_NETFILTER_XT_MATCH_MAC=m -CONFIG_NETFILTER_XT_MATCH_MARK=m -CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m -CONFIG_NETFILTER_XT_MATCH_NFACCT=m -CONFIG_NETFILTER_XT_MATCH_OSF=m -CONFIG_NETFILTER_XT_MATCH_OWNER=m -CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m -CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m -CONFIG_NETFILTER_XT_MATCH_POLICY=m -CONFIG_NETFILTER_XT_MATCH_QUOTA=m -CONFIG_NETFILTER_XT_MATCH_RATEEST=m -CONFIG_NETFILTER_XT_MATCH_REALM=m -CONFIG_NETFILTER_XT_MATCH_RECENT=m -CONFIG_NETFILTER_XT_MATCH_SCTP=m -CONFIG_NETFILTER_XT_MATCH_SOCKET=m -CONFIG_NETFILTER_XT_MATCH_STATE=m -CONFIG_NETFILTER_XT_MATCH_STATISTIC=m -CONFIG_NETFILTER_XT_MATCH_STRING=m -CONFIG_NETFILTER_XT_MATCH_TCPMSS=m -CONFIG_NETFILTER_XT_MATCH_TIME=m -CONFIG_NETFILTER_XT_MATCH_U32=m -CONFIG_NETFILTER_XT_NAT=m -CONFIG_NETFILTER_XT_SET=m -CONFIG_NETFILTER_XT_TARGET_AUDIT=m -CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m -CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m -CONFIG_NETFILTER_XT_TARGET_CONNMARK=m -CONFIG_NETFILTER_XT_TARGET_CT=m -CONFIG_NETFILTER_XT_TARGET_DSCP=m -CONFIG_NETFILTER_XT_TARGET_HL=m -CONFIG_NETFILTER_XT_TARGET_HMARK=m -CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m -CONFIG_NETFILTER_XT_TARGET_LED=m -CONFIG_NETFILTER_XT_TARGET_MARK=m -CONFIG_NETFILTER_XT_TARGET_MASQUERADE=m -CONFIG_NETFILTER_XT_TARGET_NETMAP=m -CONFIG_NETFILTER_XT_TARGET_NFLOG=m -CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m -CONFIG_NETFILTER_XT_TARGET_NOTRACK=m -CONFIG_NETFILTER_XT_TARGET_RATEEST=m -CONFIG_NETFILTER_XT_TARGET_REDIRECT=m -CONFIG_NETFILTER_XT_TARGET_TCPMSS=m -CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m -CONFIG_NETFILTER_XT_TARGET_TEE=m -CONFIG_NETFILTER_XT_TARGET_TPROXY=m -CONFIG_NETFILTER_XT_TARGET_TRACE=m -CONFIG_NETLINK_DIAG=m -CONFIG_NET_ACT_BPF=m -CONFIG_NET_ACT_CONNMARK=m -CONFIG_NET_ACT_CSUM=m -CONFIG_NET_ACT_CT=m -CONFIG_NET_ACT_CTINFO=m -CONFIG_NET_ACT_MPLS=m -CONFIG_NET_ACT_NAT=m -CONFIG_NET_ACT_PEDIT=m -CONFIG_NET_ACT_POLICE=m -CONFIG_NET_ACT_SAMPLE=m -CONFIG_NET_ACT_SIMP=m -CONFIG_NET_ACT_SKBEDIT=m -CONFIG_NET_ACT_SKBMOD=m -CONFIG_NET_ACT_TUNNEL_KEY=m -CONFIG_NET_ACT_VLAN=m -CONFIG_NET_CLS_BPF=m -CONFIG_NET_CLS_FLOW=m -CONFIG_NET_CLS_FW=m -CONFIG_NET_CLS_MATCHALL=m -CONFIG_NET_CLS_ROUTE4=m -CONFIG_NET_CLS_U32=m -CONFIG_NET_EMATCH=y -CONFIG_NET_EMATCH_CMP=m -CONFIG_NET_EMATCH_IPSET=m -CONFIG_NET_EMATCH_IPT=m -CONFIG_NET_EMATCH_META=m -CONFIG_NET_EMATCH_NBYTE=m -CONFIG_NET_EMATCH_TEXT=m -CONFIG_NET_EMATCH_U32=m -CONFIG_NET_FOU=m -CONFIG_NET_FOU_IP_TUNNELS=y -CONFIG_NET_IPGRE=m -CONFIG_NET_IPGRE_BROADCAST=y -CONFIG_NET_IPGRE_DEMUX=m -CONFIG_NET_IPIP=m -CONFIG_NET_IPVTI=m -CONFIG_NET_KEY=m -CONFIG_NET_KEY_MIGRATE=y -CONFIG_NET_SCH_CAKE=m -CONFIG_NET_SCH_CHOKE=m -CONFIG_NET_SCH_CODEL=m -CONFIG_NET_SCH_DRR=m -CONFIG_NET_SCH_DUALPI2=m -CONFIG_NET_SCH_ETS=m -CONFIG_NET_SCH_FQ=m -CONFIG_NET_SCH_FQ_CODEL=m -CONFIG_NET_SCH_FQ_PIE=m -CONFIG_NET_SCH_GRED=m -CONFIG_NET_SCH_HFSC=m -CONFIG_NET_SCH_HHF=m -CONFIG_NET_SCH_HTB=m -CONFIG_NET_SCH_MULTIQ=m -CONFIG_NET_SCH_NETEM=m -CONFIG_NET_SCH_PIE=m -CONFIG_NET_SCH_PLUG=m -CONFIG_NET_SCH_PRIO=m -CONFIG_NET_SCH_QFQ=m -CONFIG_NET_SCH_RED=m -CONFIG_NET_SCH_SFB=m -CONFIG_NET_SCH_SFQ=m -CONFIG_NET_SCH_TBF=m -CONFIG_NET_SCH_TEQL=m -CONFIG_NET_TEAM=m -CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m -CONFIG_NET_TEAM_MODE_BROADCAST=m -CONFIG_NET_TEAM_MODE_LOADBALANCE=m -CONFIG_NET_TEAM_MODE_RANDOM=m -CONFIG_NET_TEAM_MODE_ROUNDROBIN=m -CONFIG_NFT_BRIDGE_META=m -CONFIG_NFT_BRIDGE_REJECT=m -CONFIG_NFT_COMPAT=m -CONFIG_NFT_CONNLIMIT=m -CONFIG_NFT_DUP_IPV4=m -CONFIG_NFT_DUP_IPV6=m -CONFIG_NFT_DUP_NETDEV=m -CONFIG_NFT_FIB_NETDEV=m -CONFIG_NFT_FLOW_OFFLOAD=m -CONFIG_NFT_FWD_NETDEV=m -CONFIG_NFT_HASH=m -CONFIG_NFT_NUMGEN=m -CONFIG_NFT_OSF=m -CONFIG_NFT_QUEUE=m -CONFIG_NFT_QUOTA=m -CONFIG_NFT_REDIR=m -CONFIG_NFT_REJECT_NETDEV=m -CONFIG_NFT_SOCKET=m -CONFIG_NFT_TPROXY=m -CONFIG_NFT_TUNNEL=m -CONFIG_NFT_XFRM=m -CONFIG_NF_CONNTRACK_AMANDA=m -CONFIG_NF_CONNTRACK_BRIDGE=m -CONFIG_NF_CONNTRACK_FTP=m -CONFIG_NF_CONNTRACK_H323=m -CONFIG_NF_CONNTRACK_IRC=m -CONFIG_NF_CONNTRACK_LABELS=y -CONFIG_NF_CONNTRACK_MARK=y -CONFIG_NF_CONNTRACK_NETBIOS_NS=m -CONFIG_NF_CONNTRACK_PPTP=m -CONFIG_NF_CONNTRACK_PROCFS=y -CONFIG_NF_CONNTRACK_SANE=m -CONFIG_NF_CONNTRACK_SIP=m -CONFIG_NF_CONNTRACK_SNMP=m -CONFIG_NF_CONNTRACK_TFTP=m -CONFIG_NF_CONNTRACK_TIMEOUT=y -CONFIG_NF_CONNTRACK_TIMESTAMP=y -CONFIG_NF_CONNTRACK_ZONES=y -CONFIG_NF_CT_NETLINK=m -CONFIG_NF_CT_NETLINK_HELPER=m -CONFIG_NF_CT_NETLINK_TIMEOUT=m -CONFIG_NF_DUP_IPV4=m -CONFIG_NF_DUP_IPV6=m -CONFIG_NF_DUP_NETDEV=m -CONFIG_NF_FLOW_TABLE=m -CONFIG_NF_FLOW_TABLE_INET=m -CONFIG_NF_FLOW_TABLE_PROCFS=y -CONFIG_NF_LOG_ARP=m -CONFIG_NF_LOG_IPV4=m -CONFIG_NF_NAT_SNMP_BASIC=m -CONFIG_NF_SOCKET_IPV4=m -CONFIG_NF_SOCKET_IPV6=m -CONFIG_NF_TABLES_ARP=y -CONFIG_NF_TABLES_BRIDGE=m -CONFIG_NF_TPROXY_IPV4=m -CONFIG_NF_TPROXY_IPV6=m -CONFIG_OVPN=m -CONFIG_PACKET_DIAG=m -CONFIG_PPP=m -CONFIG_PPPOE=m -CONFIG_PPPOE_HASH_BITS_4=y -CONFIG_PPPOL2TP=m -CONFIG_PPP_ASYNC=m -CONFIG_PPP_BSDCOMP=m -CONFIG_PPP_DEFLATE=m -CONFIG_PPP_FILTER=y -CONFIG_PPP_MPPE=m -CONFIG_PPP_MULTILINK=y -CONFIG_PPP_SYNC_TTY=m -CONFIG_PPTP=m -CONFIG_PSAMPLE=m -CONFIG_TCP_CONG_ADVANCED=y -CONFIG_TCP_CONG_BBR=m -CONFIG_TCP_CONG_BIC=m -CONFIG_TCP_CONG_CDG=m -CONFIG_TCP_CONG_DCTCP=m -CONFIG_TCP_CONG_HSTCP=m -CONFIG_TCP_CONG_HTCP=m -CONFIG_TCP_CONG_HYBLA=m -CONFIG_TCP_CONG_ILLINOIS=m -CONFIG_TCP_CONG_LP=m -CONFIG_TCP_CONG_NV=m -CONFIG_TCP_CONG_SCALABLE=m -CONFIG_TCP_CONG_VEGAS=m -CONFIG_TCP_CONG_VENO=m -CONFIG_TCP_CONG_WESTWOOD=m -CONFIG_TCP_CONG_YEAH=m -CONFIG_TLS=m -CONFIG_UNIX_DIAG=m -CONFIG_VXLAN=m -CONFIG_WIREGUARD=m -CONFIG_XFRM_INTERFACE=m -CONFIG_XFRM_MIGRATE=y -CONFIG_XFRM_STATISTICS=y -CONFIG_XFRM_USER=m - -# Bluetooth: USB dongle drivers, vhci, MSFT/AOSP vendor extensions (USB controllers only) -CONFIG_BT_AOSPEXT=y -CONFIG_BT_ATH3K=m -CONFIG_BT_HCIBCM203X=m -CONFIG_BT_HCIBFUSB=m -CONFIG_BT_HCIBPA10X=m -CONFIG_BT_HCIBTUSB_AUTOSUSPEND=y -CONFIG_BT_HCIVHCI=m -CONFIG_BT_MSFTEXT=y - -# Containers / virtualisation: CRIU/checkpoint, userfaultfd, misc cgroup, KVM host vhost-net/vhost-vsock/vsock -CONFIG_CGROUP_MISC=y -CONFIG_CHECKPOINT_RESTORE=y -CONFIG_USERFAULTFD=y -CONFIG_VHOST_NET=m -CONFIG_VHOST_VSOCK=m -CONFIG_VSOCKETS=m -CONFIG_VSOCKETS_DIAG=m -CONFIG_VSOCKETS_LOOPBACK=m - -# Crypto: AF_ALG user API (cryptsetup/iwd), dm-crypt/fscrypt modes, VeraCrypt/TrueCrypt ciphers+hashes, IPsec AEADs -CONFIG_CRYPTO_ADIANTUM=m -CONFIG_CRYPTO_AEGIS128=m -CONFIG_CRYPTO_AEGIS128_SIMD=y -CONFIG_CRYPTO_BLAKE2B=m -CONFIG_CRYPTO_BLOWFISH=m -CONFIG_CRYPTO_CAMELLIA=m -CONFIG_CRYPTO_CAST5=m -CONFIG_CRYPTO_CAST6=m -CONFIG_CRYPTO_CHACHA20POLY1305=m -CONFIG_CRYPTO_CRC32=m -CONFIG_CRYPTO_CRC32C=m -CONFIG_CRYPTO_CRYPTD=m -CONFIG_CRYPTO_CTS=m -CONFIG_CRYPTO_ESSIV=m -CONFIG_CRYPTO_HCTR2=m -CONFIG_CRYPTO_LRW=m -CONFIG_CRYPTO_LZ4HC=m -CONFIG_CRYPTO_MD4=m -CONFIG_CRYPTO_NULL=m -CONFIG_CRYPTO_PCBC=m -CONFIG_CRYPTO_RMD160=m -CONFIG_CRYPTO_SEQIV=m -CONFIG_CRYPTO_SERPENT=m -CONFIG_CRYPTO_STREEBOG=m -CONFIG_CRYPTO_TWOFISH=m -CONFIG_CRYPTO_USER_API_AEAD=m -CONFIG_CRYPTO_USER_API_HASH=m -CONFIG_CRYPTO_USER_API_SKCIPHER=m -CONFIG_CRYPTO_WP512=m -CONFIG_CRYPTO_XCBC=m -CONFIG_CRYPTO_XXHASH=m - -# Sound: USB audio/MIDI device drivers, MIDI 2.0, virtual MIDI, dummy card, userspace timers -CONFIG_SND_BCD2000=m -CONFIG_SND_DUMMY=m -CONFIG_SND_SEQ_UMP=y -CONFIG_SND_UMP_LEGACY_RAWMIDI=y -CONFIG_SND_USB_6FIRE=m -CONFIG_SND_USB_AUDIO_MIDI_V2=y -CONFIG_SND_USB_CAIAQ=m -CONFIG_SND_USB_CAIAQ_INPUT=y -CONFIG_SND_USB_HIFACE=m -CONFIG_SND_USB_POD=m -CONFIG_SND_USB_PODHD=m -CONFIG_SND_USB_TONEPORT=m -CONFIG_SND_USB_UA101=m -CONFIG_SND_USB_VARIAX=m -CONFIG_SND_UTIMER=y -CONFIG_SND_VIRMIDI=m - -# Video: non-UVC USB webcams (gspca family, Philips pwc), USB displays (DisplayLink USB2 udl, GUD, GM12U320) -CONFIG_DRM_GM12U320=m -CONFIG_DRM_GUD=m -CONFIG_DRM_UDL=m -CONFIG_USB_GL860=m -CONFIG_USB_GSPCA=m -CONFIG_USB_GSPCA_BENQ=m -CONFIG_USB_GSPCA_CONEX=m -CONFIG_USB_GSPCA_CPIA1=m -CONFIG_USB_GSPCA_DTCS033=m -CONFIG_USB_GSPCA_ETOMS=m -CONFIG_USB_GSPCA_FINEPIX=m -CONFIG_USB_GSPCA_JEILINJ=m -CONFIG_USB_GSPCA_JL2005BCD=m -CONFIG_USB_GSPCA_KINECT=m -CONFIG_USB_GSPCA_KONICA=m -CONFIG_USB_GSPCA_MARS=m -CONFIG_USB_GSPCA_MR97310A=m -CONFIG_USB_GSPCA_NW80X=m -CONFIG_USB_GSPCA_OV519=m -CONFIG_USB_GSPCA_OV534=m -CONFIG_USB_GSPCA_OV534_9=m -CONFIG_USB_GSPCA_PAC207=m -CONFIG_USB_GSPCA_PAC7302=m -CONFIG_USB_GSPCA_PAC7311=m -CONFIG_USB_GSPCA_SE401=m -CONFIG_USB_GSPCA_SN9C2028=m -CONFIG_USB_GSPCA_SN9C20X=m -CONFIG_USB_GSPCA_SONIXB=m -CONFIG_USB_GSPCA_SONIXJ=m -CONFIG_USB_GSPCA_SPCA1528=m -CONFIG_USB_GSPCA_SPCA500=m -CONFIG_USB_GSPCA_SPCA501=m -CONFIG_USB_GSPCA_SPCA505=m -CONFIG_USB_GSPCA_SPCA506=m -CONFIG_USB_GSPCA_SPCA508=m -CONFIG_USB_GSPCA_SPCA561=m -CONFIG_USB_GSPCA_SQ905=m -CONFIG_USB_GSPCA_SQ905C=m -CONFIG_USB_GSPCA_SQ930X=m -CONFIG_USB_GSPCA_STK014=m -CONFIG_USB_GSPCA_STK1135=m -CONFIG_USB_GSPCA_STV0680=m -CONFIG_USB_GSPCA_SUNPLUS=m -CONFIG_USB_GSPCA_T613=m -CONFIG_USB_GSPCA_TOPRO=m -CONFIG_USB_GSPCA_TOUPTEK=m -CONFIG_USB_GSPCA_TV8532=m -CONFIG_USB_GSPCA_VC032X=m -CONFIG_USB_GSPCA_VICAM=m -CONFIG_USB_GSPCA_XIRLINK_CIT=m -CONFIG_USB_GSPCA_ZC3XX=m -CONFIG_USB_M5602=m -CONFIG_USB_PWC=m -CONFIG_USB_PWC_INPUT_EVDEV=y -CONFIG_USB_STV06XX=m - -# Other user-facing: Kerberos persistent keyrings, Speakup screen reader, Wine/Proton ntsync -CONFIG_ACCESSIBILITY=y -CONFIG_NTSYNC=m -CONFIG_PERSISTENT_KEYRINGS=y -CONFIG_SPEAKUP=m -CONFIG_SPEAKUP_SYNTH_ACNTSA=m -CONFIG_SPEAKUP_SYNTH_APOLLO=m -CONFIG_SPEAKUP_SYNTH_AUDPTR=m -CONFIG_SPEAKUP_SYNTH_BNS=m -CONFIG_SPEAKUP_SYNTH_DECTLK=m -CONFIG_SPEAKUP_SYNTH_LTLK=m -CONFIG_SPEAKUP_SYNTH_SOFT=m -CONFIG_SPEAKUP_SYNTH_SPKOUT=m -CONFIG_SPEAKUP_SYNTH_TXPRT=m diff --git a/arch/arm64/configs/j700-radio.config b/arch/arm64/configs/j700-radio.config deleted file mode 100644 index f5c5d3c3ec0bf..0000000000000 --- a/arch/arm64/configs/j700-radio.config +++ /dev/null @@ -1,20 +0,0 @@ -# J700 retained radio bootstrap; merge after defconfig and asahi.config. -# System sleep is retained; active radio firmware cannot cross kexec. -# Firmware/calibration and runtime admission follow the MT7932 documentation. -# CONFIG_KEXEC is not set -# CONFIG_KEXEC_FILE is not set -# CONFIG_KEXEC_HANDOVER is not set -CONFIG_PCI=y -CONFIG_PCIE_APPLE=y -CONFIG_APPLE_DART=y -CONFIG_PCIE_APPLE_PIODMA_DIAG=y -CONFIG_CFG80211=m -CONFIG_WLAN=y -CONFIG_WLAN_VENDOR_MEDIATEK=y -CONFIG_MT7932_FULLMAC=m -CONFIG_BT=m -CONFIG_BT_BREDR=y -CONFIG_BT_LE=y -CONFIG_BT_MTK7932_PCIE=m -CONFIG_CRYPTO_AES=y -CONFIG_CRYPTO_CMAC=y diff --git a/arch/arm64/configs/j700.config b/arch/arm64/configs/j700.config deleted file mode 100644 index 976fd65b7161c..0000000000000 --- a/arch/arm64/configs/j700.config +++ /dev/null @@ -1,113 +0,0 @@ -# J700 (MacBook Neo, T8140) base fragment. -# -# Merge after defconfig, asahi.config and j700-radio.config, then add the device-parity fragment: -# make defconfig -# scripts/kconfig/merge_config.sh -m .config arch/arm64/configs/asahi.config arch/arm64/configs/j700-radio.config -# make olddefconfig -# scripts/kconfig/merge_config.sh -m .config arch/arm64/configs/j700.config arch/arm64/configs/j700-device-parity.config -# make olddefconfig -# The result is the configuration the J700 kernel is tested with. - -# Boot NOR (nvram) access for boot-volume switching, and the PMU RTC wake alarm -CONFIG_SPI_APPLE_QSPI=m -CONFIG_RTC_DRV_APPLE_PMU=m - -# Classic Bluetooth profiles (headsets, tethering) -CONFIG_BT_RFCOMM=m -CONFIG_BT_RFCOMM_TTY=y -CONFIG_BT_BNEP=m -CONFIG_BT_BNEP_MC_FILTER=y -CONFIG_BT_BNEP_PROTO_FILTER=y - -# Distribution firmware files are compressed -CONFIG_FW_LOADER_COMPRESS=y -CONFIG_FW_LOADER_COMPRESS_ZSTD=y - -# 48-bit virtual addresses; KASLR is untested on this machine -CONFIG_ARM64_VA_BITS_48=y -# CONFIG_RANDOMIZE_BASE is not set - -# Boot path and platform built in, so storage, keyboard, SMC and USB do not depend on the initramfs -CONFIG_BLK_DEV_NVME=y -CONFIG_NVME_APPLE=y -CONFIG_APPLE_SART=y -CONFIG_APPLE_RTKIT_HELPER=y -CONFIG_BTRFS_FS=y -CONFIG_APPLE_DOCKCHANNEL=y -CONFIG_HID_DOCKCHANNEL=y -CONFIG_HID_APPLE=y -CONFIG_HID_MAGICMOUSE=y -CONFIG_SPMI_APPLE=y -CONFIG_NVMEM_APPLE_SPMI=y -CONFIG_MFD_MACSMC=y -CONFIG_GPIO_MACSMC=y -CONFIG_POWER_RESET_MACSMC=y -CONFIG_SENSORS_MACSMC_HWMON=y -CONFIG_I2C_APPLE=y -CONFIG_PINCTRL_APPLE_GPIO=y -CONFIG_APPLE_WATCHDOG=y -CONFIG_ARM_APPLE_SOC_CPUFREQ=y -CONFIG_PHY_APPLE_ATC=y -CONFIG_USB_DWC3_APPLE=y -CONFIG_TYPEC=y -CONFIG_TYPEC_SN201202X=y -CONFIG_MUX_APPLE_DPXBAR=y -CONFIG_BACKLIGHT_CLASS_DEVICE=y -CONFIG_SYSFB_SIMPLEFB=y -CONFIG_ZRAM=y -CONFIG_UHID=y -CONFIG_RFKILL=y -CONFIG_THERMAL_NETLINK=y -CONFIG_NVME_HWMON=y - -# J700 driver choices -CONFIG_APPLE_CLPC=m -CONFIG_APPLE_PMP=y -CONFIG_DRM_ACCEL=y -CONFIG_DRM_ACCEL_ANE=m -# CONFIG_HID_APPLE_MTP_HAPTIC is not set -CONFIG_IIO=m -# CONFIG_IIO_AOP_SENSOR_LAS is not set -# CONFIG_USB4 is not set - -# Per-process 4K user pages on the 16K kernel (x86 emulation) -CONFIG_ARM64_USER4K_EXPERIMENTAL=y - -# USB Ethernet built in, for a wired link before modules load -CONFIG_USB_USBNET=y -CONFIG_USB_NET_AX8817X=y -CONFIG_USB_NET_AX88179_178A=y -CONFIG_USB_NET_CDC_NCM=y -CONFIG_USB_RTL8152=y -CONFIG_USB_UAS=m - -# Crypto that Bluetooth and Wi-Fi need early -CONFIG_CRYPTO_CCM=y -CONFIG_CRYPTO_ECDH=y -CONFIG_CRYPTO_GCM=y -CONFIG_CRYPTO_HMAC=y -CONFIG_CRYPTO_SHA256=y - -# Utilisation clamping -CONFIG_UCLAMP_TASK=y -CONFIG_UCLAMP_TASK_GROUP=y - -# LSMs named in CONFIG_LSM -CONFIG_SECURITY_LANDLOCK=y -CONFIG_SECURITY_YAMA=y - -# dma-buf heaps and udmabuf (camera and GPU buffer sharing) -CONFIG_DMABUF_HEAPS=y -CONFIG_DMABUF_HEAPS_SYSTEM=y -CONFIG_UDMABUF=y - -# Debugging aids kept in the tested configuration -CONFIG_DYNAMIC_DEBUG=y -CONFIG_FTRACE=y -CONFIG_ENABLE_DEFAULT_TRACERS=y -CONFIG_PM_DEBUG=y -CONFIG_PM_ADVANCED_DEBUG=y - -# Match the tested kernel image: no relative relocation packing, no SCM version suffix -# CONFIG_RELR is not set -# CONFIG_LOCALVERSION_AUTO is not set diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c index 69b87ed4fa6fe..9d3bc53d54607 100644 --- a/drivers/bluetooth/hci_bcm4377.c +++ b/drivers/bluetooth/hci_bcm4377.c @@ -3049,6 +3049,8 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id) hdev); } +static int bcm4377_resume(struct device *dev); + static int bcm4377_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); @@ -3071,6 +3073,21 @@ static int bcm4377_suspend(struct device *dev) } if (device_may_wakeup(dev)) { + ret = enable_irq_wake(pdev->irq); + if (ret) { + int resume_ret; + + dev_err(dev, "Failed to arm Bluetooth wake IRQ: %d\n", ret); + /* The PM core will not resume a device that failed suspend. */ + bcm4377->resume_powered = false; + resume_ret = bcm4377_resume(dev); + if (resume_ret) + dev_err(dev, "Failed to undo Bluetooth suspend: %d\n", + resume_ret); + return ret; + } + bcm4377->wake_armed = true; + /* * Remote wakeup: leave the firmware running and the function * in D0, so a connection attempt from a wake-allowed device @@ -3078,7 +3095,6 @@ static int bcm4377_suspend(struct device *dev) * core from putting the function into D3hot. */ pci_save_state(pdev); - bcm4377->wake_armed = !enable_irq_wake(pdev->irq); return 0; } diff --git a/drivers/bus/simple-pm-bus.c b/drivers/bus/simple-pm-bus.c index 6d351ca51aa4e..9a8b654fe021f 100644 --- a/drivers/bus/simple-pm-bus.c +++ b/drivers/bus/simple-pm-bus.c @@ -149,6 +149,7 @@ static const struct of_device_id simple_pm_bus_of_match[] = { { .compatible = "apple,t8015-pmgr", .data = ONLY_BUS }, { .compatible = "apple,t8103-pmgr", .data = ONLY_BUS }, { .compatible = "apple,t8112-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,t8132-pmgr", .data = ONLY_BUS }, { .compatible = "apple,t6000-pmgr", .data = ONLY_BUS }, { .compatible = "apple,t6020-pmgr", .data = ONLY_BUS },\ { .compatible = "fsl,ls1021a-scfg", }, diff --git a/drivers/cpufreq/apple-soc-cpufreq.c b/drivers/cpufreq/apple-soc-cpufreq.c index ba18f042e64b6..85e592faa0538 100644 --- a/drivers/cpufreq/apple-soc-cpufreq.c +++ b/drivers/cpufreq/apple-soc-cpufreq.c @@ -72,6 +72,17 @@ #define APPLE_DVFS_TRANSITION_TIMEOUT 400 +/* + * Lazy verification: the timer checks the last request written this long + * after it, with this much slack so that it can share an interrupt with other + * timers. A request kept pending behind a busy transition is retried about + * when that transition should finish, and then at the short interval. + */ +#define APPLE_DVFS_VERIFY_LAZY_NS (1 * NSEC_PER_MSEC) +#define APPLE_DVFS_VERIFY_SLACK_NS (500 * NSEC_PER_USEC) +#define APPLE_DVFS_TRANSITION_NS (50 * NSEC_PER_USEC) +#define APPLE_DVFS_RETRY_NS (4 * NSEC_PER_USEC) + /* * Fast die-temperature limit * @@ -110,7 +121,11 @@ * running, the hrtimer only checks every 100 ms, and every second once the * warning has been given. A nohz_full CPU can run a task with its tick * stopped, and so without sampling, so a cluster with nohz_full CPUs - * samples from the hrtimer all the time. + * samples from the hrtimer all the time. Where schedutil switches frequency + * from the scheduler (fast switch), it picks up a lower limit only at its + * next frequency update, which a nohz_full CPU running with its tick stopped + * may not have for a long time, so the sample applies the limit to the + * cluster itself, see apple_soc_cpufreq_lazy_cap(). * * A sample reads the effort through the PMP report driver; only when the * limit it maps to changes is a work item queued to update the QoS request, @@ -187,6 +202,7 @@ static int apple_fast_die_hp_state; struct apple_soc_cpufreq_info { bool has_ps2; bool verify_transition; + bool lazy_verify; /* Fail closed when the DT describes no fast die-temperature limit. */ bool needs_thermal_policy; bool needs_thermal_owner; @@ -210,6 +226,33 @@ struct apple_cpu_priv { unsigned int expected_pstate; struct freq_qos_request unmanaged_cap; bool thermal_owned; + /* + * Lazy verification, see apple_soc_cpufreq_lazy_request(): the lock + * serialises the command register, expected_pstate and + * transition_failed between the scheduler's requests, the timer that + * verifies them and the fast die-temperature samples. Waited-for + * requests cancel the timer first and do not take it; the samples + * then leave the cluster to them. + */ + raw_spinlock_t lock; + struct hrtimer verify_timer; + /* When the last request was written, and when it times out. */ + ktime_t write_time; + ktime_t verify_deadline; + /* P-state requested while a transition was busy, or -1. */ + int pending_pstate; + /* + * Highest P-state, and its frequency, that the fast die-temperature + * limit allows, the P-state the scheduler last asked for, and whether + * waited-for requests own the cluster, see apple_soc_cpufreq_lazy_cap(). + */ + unsigned int cap_pstate; + unsigned int cap_freq; + unsigned int asked_pstate; + bool waited; + /* A failure the verify timer has yet to report, and its command. */ + bool failure_unreported; + u64 failure_command; }; static struct cpufreq_driver apple_soc_cpufreq_driver; @@ -243,12 +286,16 @@ static const struct apple_soc_cpufreq_info soc_t8112_info = { /* * T8140 uses 5-bit PS1 state IDs starting at 1. The command register reads - * back the last accepted request, not a measured frequency; every transition - * is verified, see apple_soc_cpufreq_set_target(). Voltage, PLL and firmware - * throttling configuration are left as the bootloader set them. + * back the last accepted request, not a measured frequency; each request is + * verified. Requests from the scheduler's fast-switch path are verified by a + * timer rather than waited for, so schedutil can switch from the scheduler, + * see apple_soc_cpufreq_lazy_request(); requests through ->target_index + * (suspend, the static governors) are still waited for. Voltage, PLL and + * firmware throttling configuration are left as the bootloader set them. */ static const struct apple_soc_cpufreq_info soc_t8140_info = { .verify_transition = true, + .lazy_verify = true, .needs_thermal_policy = true, .min_pstate = 1, .max_pstate = 31, @@ -378,16 +425,28 @@ static unsigned int apple_soc_cpufreq_get_rate(unsigned int cpu) return 0; } -static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy, - unsigned int index) +/* Writes a request for @pstate into the command register value @reg. */ +static void apple_soc_cpufreq_write(struct apple_cpu_priv *priv, u64 reg, + unsigned int pstate) +{ + reg &= ~priv->info->ps1_mask; + reg |= pstate << priv->info->ps1_shift; + if (priv->info->has_ps2) + FIELD_MODIFY(APPLE_DVFS_CMD_PS2, ®, pstate); + reg |= APPLE_DVFS_CMD_SET; + + writeq_relaxed(reg, priv->reg_base + APPLE_DVFS_CMD); +} + +/* + * Requests @pstate and, on a SoC that verifies transitions, waits for the + * cluster to accept it, having waited for and verified the previous request. + */ +static int apple_soc_cpufreq_set_pstate(struct apple_cpu_priv *priv, + unsigned int pstate) { - struct apple_cpu_priv *priv = policy->driver_data; - unsigned int pstate = policy->freq_table[index].driver_data; u64 reg; - /* OPP level is a hardware ID, not the frequency-table array index. */ - if (pstate < priv->info->min_pstate || pstate > priv->info->max_pstate) - return -EINVAL; if (priv->transition_failed) return -EIO; @@ -416,13 +475,7 @@ static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy, return 0; } - reg &= ~priv->info->ps1_mask; - reg |= pstate << priv->info->ps1_shift; - if (priv->info->has_ps2) - FIELD_MODIFY(APPLE_DVFS_CMD_PS2, ®, pstate); - reg |= APPLE_DVFS_CMD_SET; - - writeq_relaxed(reg, priv->reg_base + APPLE_DVFS_CMD); + apple_soc_cpufreq_write(priv, reg, pstate); if (priv->info->verify_transition && readq_poll_timeout_atomic(priv->reg_base + APPLE_DVFS_CMD, reg, @@ -441,13 +494,371 @@ static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy, return 0; } +/* Requests the frequency table entry @index, see apple_soc_cpufreq_set_pstate(). */ +static int apple_soc_cpufreq_request(struct cpufreq_policy *policy, + unsigned int index) +{ + struct apple_cpu_priv *priv = policy->driver_data; + unsigned int pstate = policy->freq_table[index].driver_data; + + /* OPP level is a hardware ID, not the frequency-table array index. */ + if (pstate < priv->info->min_pstate || pstate > priv->info->max_pstate) + return -EINVAL; + + return apple_soc_cpufreq_set_pstate(priv, pstate); +} + +/* + * Lazy verification (lazy_verify SoCs) + * + * A transition takes about 50 us. The scheduler's fast-switch path must not + * wait for one, nor drop a request: schedutil records the frequency it asked + * for and does not ask again for the same one. + * + * A request finding the cluster idle requires the accepted state to be the + * one last written, so verifying the previous request, and is written at + * once. The verify timer checks the last request written in the same way + * within APPLE_DVFS_VERIFY_LAZY_NS plus slack, so the last request of a burst + * is verified like every other; a transition still busy then is followed + * until it finishes or its timeout has passed. + * + * A request finding the cluster busy is kept as pending, replacing an older + * pending one. The timer writes it as soon as the transition has finished, + * after the same check, and then verifies it as above. + * + * A failure stops all further requests, as for a request that is waited for, + * and the timer reports it. + * + * Waited-for requests (the initial frequency check, the static governors, + * suspend) never run alongside fast-switch requests: the core stops a + * governor that switches from the scheduler first. They cancel the timer, + * report a failure it had yet to report and replace a pending request, see + * apple_soc_cpufreq_drain(); suspend first writes the pending request, see + * apple_soc_cpufreq_suspend(). + * + * Called with priv->lock held. + */ +static void apple_soc_cpufreq_fail(struct apple_cpu_priv *priv, u64 reg) +{ + priv->transition_failed = true; + priv->pending_pstate = -1; + /* The scheduler may hold a runqueue lock: the timer reports it. */ + priv->failure_unreported = true; + priv->failure_command = reg; +} + +/* Reports a failure recorded by apple_soc_cpufreq_fail(), once. */ +static void apple_soc_cpufreq_report(struct apple_cpu_priv *priv, u64 reg, + unsigned int expected) +{ + if (reg & APPLE_DVFS_CMD_BUSY) + dev_err(priv->cpu_dev, + "DVFS busy timeout, command=%#llx; stopping requests\n", reg); + else + dev_err(priv->cpu_dev, + "unexpected DVFS state %u (expected %u); stopping requests\n", + (unsigned int)FIELD_GET(APPLE_DVFS_CMD_PS1, reg), expected); +} + +/* + * Writes @pstate if it differs from the accepted state, given the idle + * command register @reg, and returns whether a transition is now in flight. + * Called with priv->lock held. + */ +static bool apple_soc_cpufreq_lazy_write(struct apple_cpu_priv *priv, u64 reg, + unsigned int pstate) +{ + if (FIELD_GET(APPLE_DVFS_CMD_PS1, reg) != priv->expected_pstate) { + apple_soc_cpufreq_fail(priv, reg); + return false; + } + if (pstate == priv->expected_pstate) + return false; + + apple_soc_cpufreq_write(priv, reg, pstate); + priv->expected_pstate = pstate; + priv->write_time = ktime_get(); + priv->verify_deadline = ktime_add_us(priv->write_time, + apple_soc_cpufreq_timeout(priv->info)); + return true; +} + +/* Called with priv->lock held. */ +static void apple_soc_cpufreq_verify_later(struct apple_cpu_priv *priv) +{ + hrtimer_start_range_ns(&priv->verify_timer, + ns_to_ktime(APPLE_DVFS_VERIFY_LAZY_NS), + APPLE_DVFS_VERIFY_SLACK_NS, HRTIMER_MODE_REL_HARD); +} + +static enum hrtimer_restart apple_soc_cpufreq_verify(struct hrtimer *timer) +{ + struct apple_cpu_priv *priv = container_of(timer, struct apple_cpu_priv, + verify_timer); + unsigned int expected; + bool report = false; + unsigned long flags; + u64 reg; + + raw_spin_lock_irqsave(&priv->lock, flags); + if (priv->transition_failed) + goto out; + + /* + * Re-armed with hrtimer_start(), never forwarded: a request may have + * queued the timer again while this callback waited for the lock. + */ + reg = readq_relaxed(priv->reg_base + APPLE_DVFS_CMD); + if (reg & APPLE_DVFS_CMD_BUSY) { + ktime_t now = ktime_get(); + + if (ktime_after(now, priv->verify_deadline)) + apple_soc_cpufreq_fail(priv, reg); + else if (priv->pending_pstate >= 0) + hrtimer_start(timer, ns_to_ktime(APPLE_DVFS_RETRY_NS), + HRTIMER_MODE_REL_HARD); + else + hrtimer_start(timer, priv->verify_deadline, + HRTIMER_MODE_ABS_HARD); + } else if (priv->pending_pstate >= 0) { + unsigned int pstate = priv->pending_pstate; + + priv->pending_pstate = -1; + if (apple_soc_cpufreq_lazy_write(priv, reg, pstate)) + apple_soc_cpufreq_verify_later(priv); + } else if (FIELD_GET(APPLE_DVFS_CMD_PS1, reg) != priv->expected_pstate) { + apple_soc_cpufreq_fail(priv, reg); + } +out: + if (priv->failure_unreported) { + priv->failure_unreported = false; + report = true; + reg = priv->failure_command; + expected = priv->expected_pstate; + } + raw_spin_unlock_irqrestore(&priv->lock, flags); + if (report) + apple_soc_cpufreq_report(priv, reg, expected); + return HRTIMER_NORESTART; +} + +/* + * Requests @pstate without waiting, see "Lazy verification" above. + * Called with priv->lock held. + */ +static int apple_soc_cpufreq_lazy_set(struct apple_cpu_priv *priv, + unsigned int pstate) +{ + u64 reg; + + if (priv->transition_failed) + return -EIO; + + reg = readq_relaxed(priv->reg_base + APPLE_DVFS_CMD); + if (reg & APPLE_DVFS_CMD_BUSY) { + /* Retry about when the transition should have finished. */ + ktime_t now = ktime_get(); + ktime_t at = ktime_add_ns(priv->write_time, APPLE_DVFS_TRANSITION_NS); + + if (ktime_before(at, ktime_add_ns(now, APPLE_DVFS_RETRY_NS))) + at = ktime_add_ns(now, APPLE_DVFS_RETRY_NS); + priv->pending_pstate = pstate; + if (!hrtimer_active(&priv->verify_timer) && + ktime_after(now, priv->verify_deadline)) + priv->verify_deadline = + ktime_add_us(now, apple_soc_cpufreq_timeout(priv->info)); + if (!hrtimer_is_queued(&priv->verify_timer) || + ktime_after(hrtimer_get_softexpires(&priv->verify_timer), at)) + hrtimer_start(&priv->verify_timer, at, HRTIMER_MODE_ABS_HARD); + return 0; + } + + /* An idle cluster has accepted, or refused, the previous request. */ + priv->pending_pstate = -1; + if (apple_soc_cpufreq_lazy_write(priv, reg, pstate)) { + if (!hrtimer_is_queued(&priv->verify_timer)) + apple_soc_cpufreq_verify_later(priv); + } else if (priv->transition_failed) { + /* Let the timer report it, outside the scheduler. */ + hrtimer_start(&priv->verify_timer, 0, HRTIMER_MODE_REL_HARD); + return -EIO; + } + return 0; +} + +/* + * Requests the frequency table entry @index from the scheduler's fast-switch + * path, at most the fast die-temperature limit, and returns the frequency + * requested, or 0 on failure. The request is committed: the cluster applies + * it within microseconds. + */ +static unsigned int apple_soc_cpufreq_lazy_request(struct cpufreq_policy *policy, + unsigned int index) +{ + struct apple_cpu_priv *priv = policy->driver_data; + unsigned int pstate = policy->freq_table[index].driver_data; + unsigned int freq = policy->freq_table[index].frequency; + unsigned long flags; + int ret; + + if (pstate < priv->info->min_pstate || pstate > priv->info->max_pstate) + return 0; + + raw_spin_lock_irqsave(&priv->lock, flags); + priv->waited = false; + priv->asked_pstate = pstate; + if (pstate > priv->cap_pstate) { + pstate = priv->cap_pstate; + freq = priv->cap_freq; + } + ret = apple_soc_cpufreq_lazy_set(priv, pstate); + raw_spin_unlock_irqrestore(&priv->lock, flags); + return ret ? 0 : freq; +} + +/* + * Applies a new fast die-temperature limit, whose highest P-state is @cap at + * @freq, to a lazy_verify cluster at once. schedutil picks up a lower limit + * only at its next frequency update, which a nohz_full CPU running with its + * tick stopped may not have for a long time. The cluster is moved to the + * P-state the scheduler last asked for, at most @cap, and later requests are + * clamped to @cap. That also restores the request when the limit rises + * again before schedutil has seen it fall: the QoS request is then left + * unchanged, so schedutil does not ask again. + * + * Waited-for requests own the cluster from apple_soc_cpufreq_drain() to the + * next fast-switch request, and the core applies the limit to them. + * + * Returns the P-state the cluster was moved to, or -1. + */ +static int apple_soc_cpufreq_lazy_cap(struct apple_cpu_priv *priv, + unsigned int cap, unsigned int freq) +{ + unsigned int requested, pstate; + unsigned long flags; + int moved = -1; + + raw_spin_lock_irqsave(&priv->lock, flags); + priv->cap_pstate = cap; + priv->cap_freq = freq; + requested = priv->pending_pstate >= 0 ? priv->pending_pstate : + priv->expected_pstate; + pstate = min(priv->asked_pstate, cap); + if (!priv->waited && pstate != requested && + !apple_soc_cpufreq_lazy_set(priv, pstate)) + moved = pstate; + raw_spin_unlock_irqrestore(&priv->lock, flags); + return moved; +} + +/* + * Stops lazy verification ahead of a waited-for request: cancels the verify + * timer, reports a failure it had yet to report, and drops the pending + * request, returning its P-state or -1. No fast-switch request runs + * concurrently to queue the timer again. + */ +static int apple_soc_cpufreq_drain(struct apple_cpu_priv *priv) +{ + unsigned int expected; + unsigned long flags; + bool report; + int pstate; + u64 reg; + + /* First, so that no fast die-temperature sample queues the timer again. */ + raw_spin_lock_irqsave(&priv->lock, flags); + priv->waited = true; + raw_spin_unlock_irqrestore(&priv->lock, flags); + + hrtimer_cancel(&priv->verify_timer); + + raw_spin_lock_irqsave(&priv->lock, flags); + pstate = priv->pending_pstate; + priv->pending_pstate = -1; + report = priv->failure_unreported; + priv->failure_unreported = false; + reg = priv->failure_command; + expected = priv->expected_pstate; + raw_spin_unlock_irqrestore(&priv->lock, flags); + + if (report) + apple_soc_cpufreq_report(priv, reg, expected); + return pstate; +} + +static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy, + unsigned int index) +{ + struct apple_cpu_priv *priv = policy->driver_data; + + /* A waited-for request replaces a pending one, see "Lazy verification". */ + if (priv->info->lazy_verify) + apple_soc_cpufreq_drain(priv); + + return apple_soc_cpufreq_request(policy, index); +} + +/* Returns the frequency-table frequency of @pstate, or 0. */ +static unsigned int apple_soc_cpufreq_pstate_freq(struct cpufreq_policy *policy, + unsigned int pstate) +{ + struct cpufreq_frequency_table *pos; + + cpufreq_for_each_valid_entry(pos, policy->freq_table) + if (pos->driver_data == pstate) + return pos->frequency; + return 0; +} + +/* + * On a lazy_verify SoC policy->cur is the last fast-switch request, which the + * cluster may not hold: it may still be pending, be lost to a failure that + * stopped requests, or have been moved since by a fast die-temperature limit, + * see apple_soc_cpufreq_lazy_cap(). The core skips a suspend frequency equal + * to policy->cur, so settle the cluster first, writing and verifying the + * pending request or reporting the failure, and record the state it settled + * in as policy->cur before the generic suspend. + */ +static int apple_soc_cpufreq_suspend(struct cpufreq_policy *policy) +{ + struct apple_cpu_priv *priv = policy->driver_data; + + if (priv->info->lazy_verify) { + int pstate = apple_soc_cpufreq_drain(priv); + unsigned int freq; + int ret; + + if (pstate < 0) + pstate = priv->expected_pstate; + ret = apple_soc_cpufreq_set_pstate(priv, pstate); + if (ret) + return ret; + freq = apple_soc_cpufreq_pstate_freq(policy, pstate); + if (freq) + policy->cur = freq; + } + + return cpufreq_generic_suspend(policy); +} + static unsigned int apple_soc_cpufreq_fast_switch(struct cpufreq_policy *policy, unsigned int target_freq) { - if (apple_soc_cpufreq_set_target(policy, policy->cached_resolved_idx) < 0) + struct apple_cpu_priv *priv = policy->driver_data; + /* + * Resolve the index here: policy->cached_resolved_idx can be rewritten + * by a concurrent limits update. target_freq is already clamped. + */ + int idx = cpufreq_table_find_index_l(policy, target_freq, false); + + if (idx < 0) + return 0; + if (priv->info->lazy_verify) + return apple_soc_cpufreq_lazy_request(policy, idx); + if (apple_soc_cpufreq_request(policy, idx)) return 0; - return policy->freq_table[policy->cached_resolved_idx].frequency; + return policy->freq_table[idx].frequency; } static int apple_soc_cpufreq_find_cluster(struct cpufreq_policy *policy, @@ -538,6 +949,42 @@ static void apple_fast_die_arm(struct apple_fast_die *fd) HRTIMER_MODE_REL_SOFT); } +/* + * Applies @limit to a lazy_verify cluster at once, as its fastest state at or + * below the limit, see apple_soc_cpufreq_lazy_cap(). + */ +static void apple_fast_die_cap(struct apple_fast_die *fd, unsigned int limit) +{ + struct cpufreq_policy *policy = fd->policy; + struct apple_cpu_priv *priv = policy->driver_data; + struct cpufreq_frequency_table *pos; + unsigned int cap = 0, freq = 0; + int moved; + + if (!priv->info->lazy_verify) + return; + + cpufreq_for_each_valid_entry(pos, policy->freq_table) { + if (pos->frequency <= limit && pos->driver_data > cap) { + cap = pos->driver_data; + freq = pos->frequency; + } + } + /* Limits are frequency-table entries, see apple_fast_die_fit(). */ + if (!freq) + return; + /* + * For scaling_cur_freq and the statistics until the next fast-switch + * request, which sets policy->cur itself. + */ + moved = apple_soc_cpufreq_lazy_cap(priv, cap, freq); + if (moved < 0) + return; + freq = apple_soc_cpufreq_pstate_freq(policy, moved); + if (freq) + WRITE_ONCE(policy->cur, freq); +} + /* Take a sample and queue an update if the limit changes. Call with @lock held. */ static void apple_fast_die_sample(struct apple_fast_die *fd) { @@ -565,6 +1012,7 @@ static void apple_fast_die_sample(struct apple_fast_die *fd) if (limit != fd->limit) { WRITE_ONCE(fd->limit, limit); + apple_fast_die_cap(fd, limit); schedule_work(&fd->work); } } @@ -738,6 +1186,7 @@ static int apple_fast_die_init(struct cpufreq_policy *policy, fd->limit); if (ret < 0) goto err_free; + apple_fast_die_cap(fd, fd->limit); priv->fast_die = fd; @@ -984,6 +1433,12 @@ static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) priv->cpu_dev = cpu_dev; priv->reg_base = reg_base; priv->info = info; + raw_spin_lock_init(&priv->lock); + hrtimer_setup(&priv->verify_timer, apple_soc_cpufreq_verify, CLOCK_MONOTONIC, + HRTIMER_MODE_REL_HARD); + priv->pending_pstate = -1; + priv->cap_pstate = info->max_pstate; + priv->waited = true; policy->driver_data = priv; policy->freq_table = freq_table; @@ -1014,7 +1469,7 @@ static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) policy->cpuinfo.transition_latency = transition_latency; policy->dvfs_possible_from_any_cpu = true; - policy->fast_switch_possible = !info->verify_transition; + policy->fast_switch_possible = !info->verify_transition || info->lazy_verify; policy->suspend_freq = freq_table[0].frequency; return 0; @@ -1040,6 +1495,11 @@ static void apple_soc_cpufreq_exit(struct cpufreq_policy *policy) { struct apple_cpu_priv *priv = policy->driver_data; + /* + * The governor has stopped and the policy goes away: stop the verify + * timer, report a failure it had yet to report, drop a pending request. + */ + apple_soc_cpufreq_drain(priv); /* Drain firmware-limit updates before releasing the OPP tables. */ apple_fast_die_exit(priv); @@ -1087,7 +1547,7 @@ static struct cpufreq_driver apple_soc_cpufreq_driver = { .fast_switch = apple_soc_cpufreq_fast_switch, .register_em = cpufreq_register_em_with_opp, .set_boost = cpufreq_boost_set_sw, - .suspend = cpufreq_generic_suspend, + .suspend = apple_soc_cpufreq_suspend, }; static int __init apple_soc_cpufreq_module_init(void) diff --git a/drivers/dma-buf/dma-fence-array.c b/drivers/dma-buf/dma-fence-array.c index 5e10e8df372f1..64abdcc430580 100644 --- a/drivers/dma-buf/dma-fence-array.c +++ b/drivers/dma-buf/dma-fence-array.c @@ -107,17 +107,11 @@ static bool dma_fence_array_signaled(struct dma_fence *fence) unsigned int i; /* - * We need to read num_pending before checking the enable_signal bit - * to avoid racing with the enable_signaling() implementation, which - * might decrement the counter, and cause a partial check. - * atomic_read_acquire() pairs with atomic_dec_and_test() in - * dma_fence_array_enable_signaling() - * - * The !--num_pending check is here to account for the any_signaled case - * if we race with enable_signaling(), that means the !num_pending check - * in the is_signalling_enabled branch might be outdated (num_pending - * might have been decremented), but that's fine. The user will get the - * right value when testing again later. + * Read the counter before the enable bit so callback installation + * cannot make a partial count look complete. The acquire pairs with + * the counter decrements in dma_fence_array_enable_signaling(). + * If enabling races with the lazy scan, every child must still be + * observed complete before that scan can return true. */ num_pending = atomic_read_acquire(&array->num_pending); if (test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &array->base.flags)) { @@ -126,8 +120,16 @@ static bool dma_fence_array_signaled(struct dma_fence *fence) return false; } + /* + * Arrays require every member to complete. A pending member already + * proves the array incomplete; polling its successors only repeats work + * during timeline-chain walks. Keep the completion counter and the + * enabled-signaling path unchanged. + */ for (i = 0; i < array->num_fences; ++i) { - if (dma_fence_is_signaled(array->fences[i]) && !--num_pending) + if (!dma_fence_is_signaled(array->fences[i])) + return false; + if (!--num_pending) goto signal; } return false; @@ -256,8 +258,7 @@ EXPORT_SYMBOL(dma_fence_array_init); * and fill it with the fences it wants to add to the object. Ownership of this * array is taken and dma_fence_put() is used on each fence on release. * - * If @signal_on_any is true the fence array signals if any fence in the array - * signals, otherwise it signals when all fences in the array signal. + * The array signals when every fence in the array signals. */ struct dma_fence_array *dma_fence_array_create(int num_fences, struct dma_fence **fences, diff --git a/drivers/dma-buf/st-dma-fence-unwrap.c b/drivers/dma-buf/st-dma-fence-unwrap.c index 4d9d313b460cb..17761f8de04a7 100644 --- a/drivers/dma-buf/st-dma-fence-unwrap.c +++ b/drivers/dma-buf/st-dma-fence-unwrap.c @@ -15,6 +15,7 @@ struct mock_fence { struct dma_fence base; spinlock_t lock; + atomic_t polls; }; static const char *mock_name(struct dma_fence *f) @@ -22,9 +23,18 @@ static const char *mock_name(struct dma_fence *f) return "mock"; } +static bool mock_signaled(struct dma_fence *fence) +{ + struct mock_fence *f = container_of(fence, struct mock_fence, base); + + atomic_inc(&f->polls); + return false; +} + static const struct dma_fence_ops mock_ops = { .get_driver_name = mock_name, .get_timeline_name = mock_name, + .signaled = mock_signaled, }; static struct dma_fence *__mock_fence(u64 context, u64 seqno) @@ -36,6 +46,7 @@ static struct dma_fence *__mock_fence(u64 context, u64 seqno) return NULL; spin_lock_init(&f->lock); + atomic_set(&f->polls, 0); dma_fence_init(&f->base, &mock_ops, &f->lock, context, seqno); return &f->base; @@ -584,7 +595,71 @@ static void test_unwrap_merge_complex_seqno(struct kunit *test) dma_fence_put(f1); } +/* + * A pending all-of array needs no polls of later members. Keep its + * independent member fences so their polling and out-of-order completion + * remain observable without enabling callbacks on the array. + */ +static void test_array_pending_prefix(struct kunit *test) +{ + struct dma_fence *first = mock_fence(), *last = mock_fence(); + struct dma_fence *array; + struct mock_fence *last_mock; + + if (!first || !last) { + KUNIT_FAIL(test, "Could not allocate member fences"); + goto out; + } + last_mock = container_of(last, struct mock_fence, base); + array = mock_array(2, dma_fence_get(first), dma_fence_get(last)); + if (!array) { + KUNIT_FAIL(test, "Could not allocate fence array"); + goto out; + } + KUNIT_EXPECT_FALSE(test, dma_fence_is_signaled(array)); + KUNIT_EXPECT_EQ(test, atomic_read(&last_mock->polls), 0); + dma_fence_signal(last); + KUNIT_EXPECT_FALSE(test, dma_fence_is_signaled(array)); + dma_fence_signal(first); + KUNIT_EXPECT_TRUE(test, dma_fence_is_signaled(array)); + dma_fence_put(array); +out: + dma_fence_put(last); + dma_fence_put(first); +} + +static void test_array_pending_suffix(struct kunit *test) +{ + struct dma_fence *first = mock_fence(), *last = mock_fence(); + struct dma_fence *array; + + if (!first || !last) { + KUNIT_FAIL(test, "Could not allocate member fences"); + goto out; + } + array = mock_array(2, dma_fence_get(first), dma_fence_get(last)); + if (!array) { + KUNIT_FAIL(test, "Could not allocate fence array"); + goto out; + } + dma_fence_signal(first); + KUNIT_EXPECT_FALSE(test, dma_fence_is_signaled(array)); + /* Installing callbacks after a lazy partial poll must still account + * for the already signaled first member and wait for the last. + */ + dma_fence_enable_signaling(array); + KUNIT_EXPECT_FALSE(test, dma_fence_is_signaled(array)); + dma_fence_signal(last); + KUNIT_EXPECT_GT(test, dma_fence_wait_timeout(array, false, HZ), 0L); + dma_fence_put(array); +out: + dma_fence_put(last); + dma_fence_put(first); +} + static struct kunit_case dma_fence_unwrap_cases[] = { + KUNIT_CASE(test_array_pending_prefix), + KUNIT_CASE(test_array_pending_suffix), KUNIT_CASE(test_sanitycheck), KUNIT_CASE(test_unwrap_array), KUNIT_CASE(test_unwrap_chain), diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 94f9c2e2860d9..6ba3a178c636b 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -111,26 +111,12 @@ config APPLE_SIO_KUNIT_TEST default KUNIT_ALL_TESTS help Check which SIO device-tree nodes the probe goes on with: the - generic SIO of the M1 and M2 in every configuration, and a T6030 - SIO only with APPLE_SIO_T6030 and protocol-10 boot tables. Run it - both with and without APPLE_SIO_T6030. - -config APPLE_SIO_T6030 - bool "Apple SIO on the M3 Pro (T6030)" - depends on APPLE_SIO - help - Configure the SIO channels of the T6030 (apple,t6030-sio) the way - its firmware expects them: the DisplayPort audio channels refill - their FIFO at the threshold the system's device tree records for - them (0x600 bytes, not 0x800), and every period descriptor is - published whole. The firmware's boot tables come from the boot - loader, as on the other SoCs; the SIO is only started when they - are protocol-10 tables, which a boot loader without T6030 support - does not build. Without this option a T6030 SIO is left stopped. + generic SIO of the M1 and M2, and a T6030 SIO only with validated + protocol-10 boot tables. The compatible selects the firmware layout. config APPLE_SIO_T6030_KUNIT_TEST bool "KUnit tests for the T6030 SIO boot-table check" if !KUNIT_ALL_TESTS - depends on APPLE_SIO=y && APPLE_SIO_T6030 && KUNIT=y + depends on APPLE_SIO=y && KUNIT=y depends on OF_OVERLAY default KUNIT_ALL_TESTS help diff --git a/drivers/dma/apple-sio-probe-test.c b/drivers/dma/apple-sio-probe-test.c index 7049c8acb532a..c486c5072f07c 100644 --- a/drivers/dma/apple-sio-probe-test.c +++ b/drivers/dma/apple-sio-probe-test.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* - * Included by apple-sio.c: which SIO nodes the probe goes on with, with and - * without APPLE_SIO_T6030. The test devices have no register range, so a probe + * Included by apple-sio.c: the compatible selects which firmware tables a + * node needs. The test devices have no register range, so a probe * that goes on toward the hardware stops there with -EINVAL; a refused node * returns -ENODEV first. */ @@ -13,16 +13,17 @@ OF_OVERLAY_DECLARE(apple_sio_probe_test); static void sio_probe_nodes_test(struct kunit *test) { - const bool t6030 = IS_ENABLED(CONFIG_APPLE_SIO_T6030); const struct { const char *path; int err; } cases[] = { - /* M1/M2: the generic SIO, either way */ + /* M1/M2: the generic SIO */ { "/sio-probe-test/sio-probe-test-t8103", -EINVAL }, - /* T6030: only with its support built, and with protocol-10 tables */ - { "/sio-probe-test/sio-probe-test-t6030-v10", t6030 ? -EINVAL : -ENODEV }, + /* T6030: protocol-10 tables select the admitted path */ + { "/sio-probe-test/sio-probe-test-t6030-v10", -EINVAL }, { "/sio-probe-test/sio-probe-test-t6030-earlier", -ENODEV }, + { "/sio-probe-test/sio-probe-test-t6030-missing", -ENODEV }, + { "/sio-probe-test/sio-probe-test-unknown", -ENODEV }, }; unsigned int i; @@ -38,15 +39,44 @@ static void sio_probe_nodes_test(struct kunit *test) KUNIT_ASSERT_NOT_NULL_MSG(test, pdev->dev.of_node, "%s", cases[i].path); mask = pdev->dev.coherent_dma_mask; KUNIT_EXPECT_EQ_MSG(test, sio_probe(pdev), cases[i].err, "%s", cases[i].path); - /* Without its support, a T6030 is refused before its DMA is set up */ - if (!t6030 && cases[i].err == -ENODEV) + /* Bad protocol tables are refused before DMA setup. */ + if (cases[i].err == -ENODEV) KUNIT_EXPECT_EQ_MSG(test, pdev->dev.coherent_dma_mask, mask, "%s", cases[i].path); } } +static void sio_match_data_test(struct kunit *test) +{ + const struct of_device_id *match; + struct device_node *np; + const struct sio_hw_data *hw; + + KUNIT_ASSERT_EQ(test, 0, of_overlay_apply_kunit(test, apple_sio_probe_test)); + np = of_find_node_by_path("/sio-probe-test/sio-probe-test-t6030-v10"); + KUNIT_ASSERT_NOT_NULL(test, np); + match = of_match_node(sio_of_match, np); + of_node_put(np); + KUNIT_ASSERT_NOT_NULL(test, match); + hw = match->data; + KUNIT_EXPECT_EQ(test, hw->fifo_threshold, 0x600U); + KUNIT_EXPECT_TRUE(test, hw->clear_period_desc); + KUNIT_EXPECT_TRUE(test, hw->check_boot_tables == sio_t6030_check_boot_tables); + + np = of_find_node_by_path("/sio-probe-test/sio-probe-test-t8103"); + KUNIT_ASSERT_NOT_NULL(test, np); + match = of_match_node(sio_of_match, np); + of_node_put(np); + KUNIT_ASSERT_NOT_NULL(test, match); + hw = match->data; + KUNIT_EXPECT_EQ(test, hw->fifo_threshold, 0x800U); + KUNIT_EXPECT_FALSE(test, hw->clear_period_desc); + KUNIT_EXPECT_FALSE(test, hw->check_boot_tables); +} + static struct kunit_case sio_probe_test_cases[] = { KUNIT_CASE(sio_probe_nodes_test), + KUNIT_CASE(sio_match_data_test), {} }; diff --git a/drivers/dma/apple-sio-probe-test.dtso b/drivers/dma/apple-sio-probe-test.dtso index 79f3346beaad8..93816e9f6b202 100644 --- a/drivers/dma/apple-sio-probe-test.dtso +++ b/drivers/dma/apple-sio-probe-test.dtso @@ -17,6 +17,16 @@ dma-channels = <8>; }; + sio-probe-test-t6030-missing { + compatible = "apple,t6030-sio", "apple,sio"; + dma-channels = <8>; + }; + + sio-probe-test-unknown { + compatible = "apple,sio-probe-test-unknown"; + dma-channels = <8>; + }; + v10_w: v10-w { iommu-addresses = <&v10 0x0 0x30000 0x0 0x4000>, <&v10 0x0 0x34000 0x0 0x20000>, <&v10 0x0 0x54000 0x0 0x4000>, <&v10 0x0 0x58000 0x0 0x4000>; diff --git a/drivers/dma/apple-sio.c b/drivers/dma/apple-sio.c index 0c06e0264b7b6..65ba4511501c7 100644 --- a/drivers/dma/apple-sio.c +++ b/drivers/dma/apple-sio.c @@ -108,7 +108,14 @@ struct sio_chan { typedef void (*sio_ack_callback)(struct sio_chan *, void *, bool); +struct sio_hw_data { + u32 fifo_threshold; + bool clear_period_desc; + int (*check_boot_tables)(struct device *dev); +}; + struct sio_data { + const struct sio_hw_data *hw; void __iomem *base; struct dma_device dma; struct device *dev; @@ -130,9 +137,6 @@ struct sio_data { } tags; int nchannels; -#if IS_ENABLED(CONFIG_APPLE_SIO_T6030) - bool t6030; -#endif struct sio_chan channels[]; }; @@ -155,16 +159,6 @@ static int sio_send_siomsg_atomic(struct sio_data *sio, u64 msg, void *cookie); static int sio_call(struct sio_data *sio, u64 msg); -static inline bool sio_is_t6030(struct sio_data *sio) -{ -#if IS_ENABLED(CONFIG_APPLE_SIO_T6030) - return sio->t6030; -#else - return false; -#endif -} - -#if IS_ENABLED(CONFIG_APPLE_SIO_T6030) /* * Boot tables of the T6030 SIO firmware, protocol 10. The boot loader builds them from the ADT * and passes each one as two apple,sio-firmware-params: the table's IOVA page under its key and @@ -320,7 +314,23 @@ static int sio_t6030_check_boot_tables(struct device *dev) } return 0; } -#endif + +static const struct sio_hw_data sio_hw_generic = { + .fifo_threshold = 0x800, +}; + +static const struct sio_hw_data sio_hw_t6030 = { + .fifo_threshold = 0x600, + .clear_period_desc = true, + .check_boot_tables = sio_t6030_check_boot_tables, +}; + +static const struct of_device_id sio_of_match[] = { + { .compatible = "apple,t6030-sio", .data = &sio_hw_t6030 }, + { .compatible = "apple,sio", .data = &sio_hw_generic }, + { } +}; +MODULE_DEVICE_TABLE(of, sio_of_match); static struct sio_chan *to_sio_chan(struct dma_chan *chan) { @@ -459,7 +469,7 @@ static struct dma_async_tx_descriptor *sio_prep_dma_cyclic( * T6030: publish the whole descriptor, its other fields zero; * a slot may come back from an earlier transfer. */ - if (sio_is_t6030(siochan->host)) + if (siochan->host->hw->clear_period_desc) memset(d, 0, sizeof(*d)); d->flag = 1; /* not sure what's up with this */ d->iova = buf_addr + period_len * i; @@ -839,7 +849,7 @@ static int sio_device_config(struct dma_chan *chan, * dp-audioN dma-channels: channel, data shape, timeout, FIFO size, * threshold). T6030's DisplayPort audio channels refill at 0x600. */ - cfg.threshold = sio_is_t6030(sio) ? 0x600 : 0x800; + cfg.threshold = sio->hw->fifo_threshold; /* * Dmaengine prescribes we ought to apply the new configuration only @@ -967,20 +977,23 @@ static int sio_send_dt_params(struct sio_data *sio) static int sio_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + const struct sio_hw_data *hw; struct sio_data *sio; struct dma_device *dma; int nchannels; int err, i; - /* - * Only APPLE_SIO_T6030 checks a T6030's boot tables and configures its - * channels: without it, leave the SIO stopped rather than start it as - * the generic one. - */ - if (!IS_ENABLED(CONFIG_APPLE_SIO_T6030) && - of_device_is_compatible(np, "apple,t6030-sio")) - return dev_err_probe(&pdev->dev, -ENODEV, - "T6030 SIO support (APPLE_SIO_T6030) not built; leaving the SIO stopped\n"); + match = of_match_node(sio_of_match, np); + if (!match) + return -ENODEV; + hw = match->data; + /* Refuse incompatible firmware tables before DMA or hardware setup. */ + if (hw->check_boot_tables) { + err = hw->check_boot_tables(&pdev->dev); + if (err) + return err; + } err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42)); if (err) @@ -998,15 +1011,7 @@ static int sio_probe(struct platform_device *pdev) platform_set_drvdata(pdev, sio); sio->dev = &pdev->dev; sio->nchannels = nchannels; -#if IS_ENABLED(CONFIG_APPLE_SIO_T6030) - sio->t6030 = of_device_is_compatible(np, "apple,t6030-sio"); - /* Before any register, power or firmware access */ - if (sio->t6030) { - err = sio_t6030_check_boot_tables(&pdev->dev); - if (err) - return err; - } -#endif + sio->hw = hw; sio->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(sio->base)) @@ -1107,12 +1112,6 @@ static void sio_remove(struct platform_device *pdev) dma_async_device_unregister(&sio->dma); } -static const struct of_device_id sio_of_match[] = { - { .compatible = "apple,sio", }, - { } -}; -MODULE_DEVICE_TABLE(of, sio_of_match); - static __maybe_unused int sio_suspend(struct device *dev) { /* diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index a592e776e99ab..48a3bb9009bc7 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -7,7 +7,7 @@ appledrm-y += afk.o dcp.o dcp-fabric.o dcp_backlight.o dptxep.o iomfb.o parser.o appledrm-y += dcp-hdmi.o appledrm-$(CONFIG_DRM_APPLE_AUDIO) += audio.o appledrm-$(CONFIG_DRM_APPLE_AUDIO) += av.o -appledrm-y += connector.o dcp-display-metadata.o dcp-180hz.o +appledrm-y += connector.o dcp-panel-adt.o dcp-display-metadata.o dcp-180hz.o appledrm-y += ibootep.o appledrm-y += dcpext_scanout.o appledrm-y += dcpext_drm.o diff --git a/drivers/gpu/drm/apple/afk-parser-test.c b/drivers/gpu/drm/apple/afk-parser-test.c index fd8c5cb0c26e5..8b93e67fd3d5b 100644 --- a/drivers/gpu/drm/apple/afk-parser-test.c +++ b/drivers/gpu/drm/apple/afk-parser-test.c @@ -89,7 +89,22 @@ static void afk_reply_index_test(struct kunit *test) KUNIT_EXPECT_EQ(test, pending->retcode, 7U); } +static void afk_serialized_report_test(struct kunit *test) +{ + u8 dictionary[8] = {}; + unsigned int length; + + put_unaligned_le32(0xd3, dictionary); + put_unaligned_le32(1U << 24, dictionary + 4); + for (length = 0; length < sizeof(dictionary); length++) + KUNIT_EXPECT_FALSE(test, afk_compact_report_dictionary(dictionary, length)); + KUNIT_EXPECT_TRUE(test, afk_compact_report_dictionary(dictionary, sizeof(dictionary))); + put_unaligned_le32(0, dictionary); + KUNIT_EXPECT_FALSE(test, afk_compact_report_dictionary(dictionary, sizeof(dictionary))); +} + static struct kunit_case afk_parser_cases[] = { + KUNIT_CASE(afk_serialized_report_test), KUNIT_CASE(afk_compact_header_test), KUNIT_CASE(afk_short_epic_test), KUNIT_CASE(afk_short_reply_test), diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 248ca42640b40..688b2d2f5c622 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -1024,6 +1024,13 @@ static void afk_recv_handle_compact_call(struct apple_dcp_afkep *ep, queue_work(ep->tx_wq, &r->work); } +/* A serialized report starts at its serialization header, without a command descriptor. */ +static bool afk_compact_report_dictionary(const void *payload, size_t size) +{ + return size >= 2 * sizeof(u32) && get_unaligned_le32(payload) == 0xd3 && + ((get_unaligned_le32(payload + sizeof(u32)) >> 24) & 0x1f) == 1; +} + static void afk_recv_compact_message(struct apple_dcp_afkep *ep, u16 intf_id, const void *body, size_t size) { @@ -1046,9 +1053,10 @@ static void afk_recv_compact_message(struct apple_dcp_afkep *ep, u16 intf_id, return; } if (sub->category == 0) { - /* A standard-service report carries a descriptor first. */ + /* Preserve bare dictionaries; command-style reports have a descriptor. */ if (sub->type == EPIC_SUBTYPE_STD_SERVICE && - payload_size >= sizeof(struct epic_compact_desc)) { + payload_size >= sizeof(struct epic_compact_desc) && + !afk_compact_report_dictionary(payload, payload_size)) { payload += sizeof(struct epic_compact_desc); payload_size -= sizeof(struct epic_compact_desc); } diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 2ce9fb420f2f4..8fc8cb7f7a6ab 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -268,8 +268,11 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, /* A cable generation can change while the CRTC stays active. */ dcp_external_recover(to_apple_crtc(crtc)->dcp); } - if (crtc_state->active) - dcp_crtc_atomic_modeset(crtc, commit); + if (crtc_state->active) { + ret = dcp_crtc_atomic_modeset(crtc, commit); + if (ret) + dcp_drm_crtc_error(apple_crtc, crtc_state, ret); + } dcp_typec_follow_done(locked); return; diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.c b/drivers/gpu/drm/apple/dcp-display-metadata.c index cc4c69736d098..729eb58002f21 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.c +++ b/drivers/gpu/drm/apple/dcp-display-metadata.c @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT #include #include +#include #include #include #include #include "dcp-display-metadata.h" +#include "parser.h" static int dcp_metadata_timing(struct detailed_timing *timing, const struct drm_display_mode *mode, @@ -202,3 +204,169 @@ const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *meta } return edid; } + +/* + * This identity-only DTD is not enumerated or programmed. Keep the DCP clock, + * active area and blanking, but fit vertical sync placement into EDID's six-bit + * fields. Notch hiding expands the real sync pulse beyond those fields; the + * exact syncs remain in the authoritative TimingElements catalogue. + */ +static int dcp_panel_identity_timing(struct detailed_timing *timing, + const struct drm_display_mode *mode, + const struct dcp_display_metadata *metadata) +{ + struct drm_display_mode summary = *mode; + + /* Refuse invalid source geometry before reducing the EDID summary. */ + if (mode->vdisplay <= 0 || mode->vdisplay > 4095 || + mode->vsync_start < mode->vdisplay || mode->vsync_end <= mode->vsync_start || + mode->vsync_end > mode->vtotal) + return -EINVAL; + summary.vsync_start = mode->vdisplay + min(mode->vsync_start - mode->vdisplay, 63); + summary.vsync_end = summary.vsync_start + min(mode->vsync_end - mode->vsync_start, 63); + return dcp_metadata_timing(timing, &summary, metadata); +} + +/* EDID 1.4 range offsets extend each one-byte limit by 255. */ +static int dcp_panel_range_byte(u32 limit, u8 *out, u8 *offsets, u8 flag) +{ + if (!limit || limit > 510) + return -ERANGE; + if (limit > 255) { + limit -= 255; + *offsets |= flag; + } + *out = limit; + return 0; +} + +/* + * Supply one representable DCP timing and its catalog limits for EDID + * conformity. DRM still enumerates TimingElements, never these rounded bytes. + */ +const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadata, + const struct dcp_display_mode *modes, unsigned int count, + u32 width_mm, u32 height_mm) +{ + struct edid block = {}; + struct dcp_display_metadata dimensions = *metadata; + struct detailed_non_pixel *limits = &block.detailed_timings[3].data.other_data; + struct detailed_data_monitor_range *range = &limits->data.range; + const struct drm_edid *edid; + char serial[14], name[14]; + u32 min_v = U32_MAX, max_v = 0, min_h = U32_MAX, max_h = 0, clock = 0; + u16 manufacturer = 0; + u8 sum = 0; + bool timing = false, preferred = false; + int ret; + + if (!modes || !count || strnlen(metadata->manufacturer, 4) != 3 || + metadata->product > U16_MAX || !width_mm || width_mm > 2550 || + !height_mm || height_mm > 2550) + return ERR_PTR(-EINVAL); + for (unsigned int i = 0; i < 3; i++) { + if (metadata->manufacturer[i] < 'A' || metadata->manufacturer[i] > 'Z') + return ERR_PTR(-EINVAL); + manufacturer = manufacturer << 5 | (metadata->manufacturer[i] - '@'); + } + dimensions.width_cm = DIV_ROUND_CLOSEST(width_mm, 10); + dimensions.height_cm = DIV_ROUND_CLOSEST(height_mm, 10); + for (unsigned int i = 0; i < count; i++) { + const struct dcp_display_mode *entry = &modes[i]; + struct drm_display_mode mode = entry->mode; + struct detailed_timing candidate = {}; + u32 low, high; + u32 pixels; + + if (mode.clock <= 0 || mode.clock > 2550000 || mode.htotal <= 0 || + mode.htotal > 65535 || mode.vtotal <= 0 || mode.vtotal > 65535) + return ERR_PTR(-EINVAL); + pixels = (u32)mode.htotal * mode.vtotal; + low = div_u64((u64)mode.clock * 1000, pixels); + high = DIV_ROUND_UP_ULL((u64)mode.clock * 1000, pixels); + if (entry->vrr && entry->min_vrr && entry->min_vrr <= entry->max_vrr) { + low = entry->min_vrr >> 16; + high = DIV_ROUND_UP_ULL(entry->max_vrr, 1U << 16); + } + min_v = min(min_v, low); + max_v = max(max_v, high); + min_h = min(min_h, (u32)mode.clock / mode.htotal); + max_h = max(max_h, DIV_ROUND_UP_ULL((u32)mode.clock, mode.htotal)); + clock = max_t(u32, clock, mode.clock); + /* The EDID base timing clock has units of 10 kHz. */ + mode.clock = DIV_ROUND_CLOSEST(mode.clock, 10) * 10; + ret = dcp_panel_identity_timing(&candidate, &mode, &dimensions); + if (!ret && (!timing || (!preferred && (mode.type & DRM_MODE_TYPE_PREFERRED)))) { + struct detailed_pixel_timing *pixel = &candidate.data.pixel_data; + + /* The base size is in cm, but the DTD preserves exact panel mm. */ + pixel->width_mm_lo = width_mm; + pixel->height_mm_lo = height_mm; + pixel->width_height_mm_hi = (width_mm >> 8) << 4 | height_mm >> 8; + block.detailed_timings[0] = candidate; + timing = true; + preferred = mode.type & DRM_MODE_TYPE_PREFERRED; + } + } + if (!timing || clock > 2550000) + return ERR_PTR(-ERANGE); + + memcpy(block.header, "\x00\xff\xff\xff\xff\xff\xff\x00", 8); + block.product_id.manufacturer_name = cpu_to_be16(manufacturer); + block.product_id.product_code = cpu_to_le16(metadata->product); + block.product_id.serial_number = cpu_to_le32(metadata->serial); + /* J616 is a 2024 model; this is not a panel manufacture date. */ + block.product_id.week_of_manufacture = 0xff; + block.product_id.year_of_manufacture = 2024 - 1990; + block.version = 1; + block.revision = 4; + block.input = DRM_EDID_INPUT_DIGITAL; + block.width_cm = dimensions.width_cm; + block.height_cm = dimensions.height_cm; + block.gamma = 0xff; + /* Nominal D65 reference white, without calibrated primaries or gamma. */ + block.white_x = 320 >> 2; + block.white_y = 337 >> 2; + block.blue_white_lo = (320 & 3) << 2 | (337 & 3); + block.features = DRM_EDID_FEATURE_PREFERRED_TIMING; + memset(block.standard_timings, 1, sizeof(block.standard_timings)); + snprintf(name, sizeof(name), "%.3s %04x", metadata->manufacturer, metadata->product); + ret = dcp_metadata_text(&block.detailed_timings[1], EDID_DETAIL_MONITOR_NAME, + metadata->name[0] ? metadata->name : name); + if (ret) + return ERR_PTR(ret); + if (metadata->serial) + snprintf(serial, sizeof(serial), "%08x", metadata->serial); + else + snprintf(serial, sizeof(serial), "unspecified"); + ret = dcp_metadata_text(&block.detailed_timings[2], EDID_DETAIL_MONITOR_SERIAL, + metadata->serial_text[0] ? metadata->serial_text : serial); + if (ret) + return ERR_PTR(ret); + limits->type = EDID_DETAIL_MONITOR_RANGE; + ret = dcp_panel_range_byte(min_v, &range->min_vfreq, &limits->pad2, + DRM_EDID_RANGE_OFFSET_MIN_VFREQ); + ret = ret ?: dcp_panel_range_byte(max_v, &range->max_vfreq, &limits->pad2, + DRM_EDID_RANGE_OFFSET_MAX_VFREQ); + ret = ret ?: dcp_panel_range_byte(min_h, &range->min_hfreq_khz, &limits->pad2, + DRM_EDID_RANGE_OFFSET_MIN_HFREQ); + ret = ret ?: dcp_panel_range_byte(max_h, &range->max_hfreq_khz, &limits->pad2, + DRM_EDID_RANGE_OFFSET_MAX_HFREQ); + if (ret) + return ERR_PTR(ret); + range->pixel_clock_mhz = DIV_ROUND_UP_ULL(clock, 10000); + range->flags = DRM_EDID_RANGE_LIMITS_ONLY_FLAG; + memset(&range->formula, ' ', sizeof(range->formula)); + *((u8 *)&range->formula) = '\n'; + for (unsigned int i = 0; i < sizeof(block) - 1; i++) + sum += ((const u8 *)&block)[i]; + block.checksum = -sum; + edid = drm_edid_alloc(&block, sizeof(block)); + if (!edid) + return ERR_PTR(-ENOMEM); + if (!drm_edid_valid(edid)) { + drm_edid_free(edid); + return ERR_PTR(-EINVAL); + } + return edid; +} diff --git a/drivers/gpu/drm/apple/dcp-display-metadata.h b/drivers/gpu/drm/apple/dcp-display-metadata.h index 537994420ebd6..d97b090af699d 100644 --- a/drivers/gpu/drm/apple/dcp-display-metadata.h +++ b/drivers/gpu/drm/apple/dcp-display-metadata.h @@ -18,7 +18,12 @@ struct dcp_display_metadata { struct drm_edid; struct drm_display_mode; +struct dcp_display_mode; const struct drm_edid *dcp_metadata_edid(const struct dcp_display_metadata *metadata, const struct drm_display_mode *mode); +const struct drm_edid *dcp_panel_edid(const struct dcp_display_metadata *metadata, + const struct dcp_display_mode *modes, unsigned int count, + u32 width_mm, u32 height_mm); + #endif diff --git a/drivers/gpu/drm/apple/dcp-fabric-core.c b/drivers/gpu/drm/apple/dcp-fabric-core.c index 8c952dc024560..f703ea061fb50 100644 --- a/drivers/gpu/drm/apple/dcp-fabric-core.c +++ b/drivers/gpu/drm/apple/dcp-fabric-core.c @@ -136,8 +136,8 @@ unsigned int dcp_fabric_score(const struct dcp_fabric_pipeline *pipeline, * route would only follow it there. */ return pipeline->crtc_index + - (!policy->dual_stream && !policy->follow && pipeline->has_fixed ? - 100 : 0); + (!policy->dual_stream && !policy->follow && !policy->hybrid_first && + pipeline->has_fixed ? 100 : 0); } EXPORT_SYMBOL_GPL(dcp_fabric_score); diff --git a/drivers/gpu/drm/apple/dcp-fabric-core.h b/drivers/gpu/drm/apple/dcp-fabric-core.h index 1b8c59ac15a54..dc85dbc40c15b 100644 --- a/drivers/gpu/drm/apple/dcp-fabric-core.h +++ b/drivers/gpu/drm/apple/dcp-fabric-core.h @@ -64,6 +64,8 @@ struct dcp_fabric_policy { bool dual_stream; /* routes follow the CRTC a modeset pairs them with */ bool follow; + /* a hybrid pipeline is not kept back for a fixed display */ + bool hybrid_first; }; enum dcp_fabric_wiring { diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index b5e2eafb8c090..b3bb5982524cc 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,53 @@ MODULE_PARM_DESC(tiled_split, static bool typec_follow_crtc = true; module_param(typec_follow_crtc, bool, 0444); + +/* + * The hybrid pipeline (dcpext0: fixed HDMI PHY + Type-C routes) normally + * comes last for a Type-C port, kept for an HDMI display. On the J616s it + * is also the only pipe that can be the primary of a two-pipe display + * (firmware endpoint 1; the pipes merge upwards only): with this set a + * Type-C port takes it first, so a 7680 px sink on USB-C can pair with + * dcpext1 as its peer. -1 follows the device tree; a write applies at + * once, handing the hybrid between an HDMI display and a Type-C display + * wider than one pipe (one monitor on two inputs: the desktop's choice of + * input, the monitor cannot tell which one it shows). + */ +static int typec_hybrid_first = -1; + +static void dcp_fabric_hybrid_policy_changed(void); + +static int dcp_typec_hybrid_first_set(const char *val, const struct kernel_param *kp) +{ + int ret = param_set_int(val, kp); + + if (!ret) + dcp_fabric_hybrid_policy_changed(); + return ret; +} + +static const struct kernel_param_ops dcp_typec_hybrid_first_ops = { + .set = dcp_typec_hybrid_first_set, + .get = param_get_int, +}; +module_param_cb(typec_hybrid_first, &dcp_typec_hybrid_first_ops, &typec_hybrid_first, 0644); +MODULE_PARM_DESC(typec_hybrid_first, + "Hybrid (HDMI) pipeline: -1 from the device tree (default), 0 HDMI display first, 1 Type-C display first (dual-pipe primary)"); + +/* Set by a pipeline whose device tree asks for it (apple,typec-hybrid-first). */ +static bool typec_hybrid_first_dt; + +void dcp_typec_hybrid_first_dt(void) +{ + WRITE_ONCE(typec_hybrid_first_dt, true); +} + +static bool dcp_typec_hybrid_first(void) +{ + int policy = READ_ONCE(typec_hybrid_first); + + return policy < 0 ? READ_ONCE(typec_hybrid_first_dt) : policy > 0; +} MODULE_PARM_DESC(typec_follow_crtc, "Let direct DP-alt routes follow their selected CRTC (default: on)"); @@ -78,6 +126,12 @@ struct apple_dcp_typec_port { struct apple_dcp_typec_route *secondary_target; /* left out of the plan: its planned pipeline is held by a tunnel */ bool plan_dark; + /* + * HDMI display first (typec_hybrid_first): the display on this port + * has given both pipes to an HDMI display wider than one pipe and + * waits, routed nowhere, until that one fits one pipe or leaves. + */ + bool typec_yielded; }; /* @@ -92,6 +146,12 @@ struct apple_dcp_typec_port { static DEFINE_MUTEX(dcp_typec_fabric_lock); static LIST_HEAD(dcp_typec_ports); +/* hybrid-first hand-over (dcp_hybrid_work()), under dcp_typec_fabric_lock: */ +/* when the hybrid last changed hands, 0 if never */ +static unsigned long dcp_hybrid_moved; +/* failed takes of the hybrid since the last success or Type-C DP exit */ +static unsigned int dcp_hybrid_fails; +#define DCP_HYBRID_MAX_FAILS 2 /* Current DTs wire the same stream capacity on every physical port. */ static atomic_t dcp_dual_stream_routes = ATOMIC_INIT(0); @@ -102,9 +162,6 @@ static bool dcp_hdmi_borrowed(void *ctx); bool dcp_is_typec_output(struct apple_dcp *dcp) { - /* Desktop 26.x keeps its own Type-C route, outside this fabric. */ - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) - return false; return dcp->active_typec_route || dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB; } @@ -146,6 +203,9 @@ static bool dcp_typec_route_fixed_output_busy(struct apple_dcp_typec_route *rout if (dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB) return false; + /* Its HDMI display gave the pipeline to a Type-C display. */ + if (READ_ONCE(dcp->hdmi_yielded)) + return false; /* * The 14.7 firmware drives the internal panel on the IOMFB path. * The DPTX service is a separate output, so the live panel does not @@ -195,7 +255,8 @@ static bool dcp_typec_route_available(struct apple_dcp_typec_route *route) .fixed_busy = dcp_typec_route_busy(route), }; - struct dcp_fabric_policy policy = { .dual_stream = dcp_typec_ordered_dual_stream() }; + struct dcp_fabric_policy policy = { .dual_stream = dcp_typec_ordered_dual_stream(), + .hybrid_first = dcp_typec_hybrid_first() }; pipeline.presence = dcp_hdmi_presence(route->dcp); return dcp_fabric_available(&pipeline, &policy); @@ -306,6 +367,68 @@ static int dcp_dpxbar_tunnel_select_source(struct mux_control *mux, int state) return ret; } +/* + * Desktop 26.x firmware does not power the DP PHY of a Type-C route itself: + * the route does, once its crossbar points at the pipeline, and gives it + * back before the crossbar is released. + */ +static int dcp_typec_route_phy_on(struct apple_dcp_typec_route *route) +{ + int ret; + + if (route->dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + route->phy_powered) + return 0; + ret = phy_init(route->phy); + if (!ret) { + ret = phy_power_on(route->phy); + if (ret) + phy_exit(route->phy); + } + if (ret) { + dev_warn(route->dcp->dev, "Type-C DP PHY init/power: %d\n", ret); + return ret; + } + route->phy_powered = true; + msleep(25); + return 0; +} + +/* + * The PHY is powered when the display connects, not when the route is + * claimed: the port controller's mode switches run in connector order, and + * the PHY must have seen DP alt mode and the plug orientation first. + */ +int dcp_typec_phy_on(struct apple_dcp *dcp) +{ + struct apple_dcp_typec_route *route; + + /* + * Ordered against dcp_typec_route_deactivate(), which powers the PHY + * off and releases the crossbar under tb_lock: a connect racing the + * release either powers the PHY first and has it taken down again, or + * finds the route released and powers nothing. A desktop connect does + * not take the fabric lock, so tb_lock is the common one. + */ + guard(mutex)(&dcp->tb_lock); + route = READ_ONCE(dcp->active_typec_route); + if (!route) + return 0; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + route->deactivate_xbar_done) + return -ENODEV; + return dcp_typec_route_phy_on(route); +} + +static void dcp_typec_route_phy_off(struct apple_dcp_typec_route *route) +{ + if (!route->phy_powered) + return; + route->phy_powered = false; + phy_power_off(route->phy); + phy_exit(route->phy); +} + static int dcp_typec_route_activate(struct apple_dcp_typec_route *route, struct mux_control *xbar) { @@ -327,12 +450,26 @@ static int dcp_typec_route_activate(struct apple_dcp_typec_route *route, return ret; if (dcp->fixed_route_selected) { - ret = mux_control_deselect(dcp->xbar); + ret = mux_control_deselect(dcp->fixed_xbar); dcp->fixed_route_selected = false; if (ret) return ret; } + /* + * A pipeline reserved as the peer of a display that has not paired + * (an HDMI display on its monitor's unselected input, or a pair whose + * cable went) takes this display: drop the reservation, or this + * display could neither pair nor ask for the hybrid. A running pair + * keeps its peer (dcp_peer_release_26_6() refuses). + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + struct apple_dcp *owner = READ_ONCE(dcp->peer_owner); + + if (owner) + dcp_peer_release_26_6(owner); + } + /* * Thunderbolt DP IN: the crossbar connection may only be brought up * once DCP has configured the link and the tunnel pixel clock runs, @@ -347,19 +484,28 @@ static int dcp_typec_route_activate(struct apple_dcp_typec_route *route, ret = dcp_dpxbar_tunnel_select_source(xbar, route->mux_index); if (ret == -EOPNOTSUPP) ret = 0; - } else { - ret = xbar == route->xbar ? - mux_control_select(xbar, route->mux_index) : 0; + } else if (xbar == route->xbar) { + /* + * The control is only ever held by a route of this port, which + * lets it go under the fabric lock held here: waiting for it + * would wait for good, with the port controller's update work + * and its interrupt stuck behind us. + */ + ret = mux_control_try_select(xbar, route->mux_index); + if (ret == -EBUSY) + dev_err(dcp->dev, "Type-C crossbar control still held, route not taken\n"); } if (ret) { - if (dcp->xbar) { + if (dcp->fixed_xbar) { int restore_ret; - restore_ret = mux_control_select(dcp->xbar, + restore_ret = mux_control_select(dcp->fixed_xbar, dcp->fixed_mux_index); - if (!restore_ret) + if (!restore_ret) { dcp->fixed_route_selected = true; - else + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + WRITE_ONCE(dcp->xbar, dcp->fixed_xbar); + } else dev_err(dcp->dev, "failed to restore fixed display route: %d\n", restore_ret); @@ -370,6 +516,12 @@ static int dcp_typec_route_activate(struct apple_dcp_typec_route *route, dcp_modes_begin_attachment(dcp); apple_connector_edid_set_live(dcp->connector, false); dcp->phy = route->phy; + /* + * Desktop 26.x compact DPTX takes the crossbar link down and up around + * a link configuration (dptxport_compact_xbar_link()) through dcp->xbar. + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + WRITE_ONCE(dcp->xbar, route->xbar); dcp->dptx_phy = route->dptx_phy; dcp->connector_type = DRM_MODE_CONNECTOR_USB; WRITE_ONCE(dcp->ext_backlight, false); @@ -429,6 +581,12 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) bool ended_attachment; int ret = 0; + WRITE_ONCE(dcp->peer_starved, false); + WRITE_ONCE(dcp->typec_narrow, false); + /* A peer reserved for this route's display is not needed any more. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp_peer_release_26_6(dcp); + guard(mutex)(&dcp->external_lock); if (dcp_is_external_h17p(dcp) && (dcp->external_error || (dcp->external_power_attempted && !dcp->external_stopped))) @@ -444,6 +602,7 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) if (route->deactivate_error) return route->deactivate_error; if (!route->deactivate_xbar_done) { + dcp_typec_route_phy_off(route); if (!route->tunnel || route->xbar_up) ret = mux_control_deselect(route->active_xbar ?: route->xbar); else @@ -530,12 +689,19 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) * following DPTX call times out. Park on a Type-C PHY instead, * for the same reason the USB-C-only case does below. */ - if (dcp->hdmi_hpd && gpiod_get_value_cansleep(dcp->hdmi_hpd)) { + /* + * A yielded hybrid stays with the Type-C display through its + * sink's HPD blinks; the HDMI display waits. + */ + if (dcp->hdmi_hpd && !READ_ONCE(dcp->hdmi_yielded) && + gpiod_get_value_cansleep(dcp->hdmi_hpd)) { dcp->phy = dcp->fixed_phy; dcp->dptx_phy = dcp->fixed_dptx_phy; - if (dcp->xbar && !dcp->fixed_route_selected) { - ret = mux_control_select(dcp->xbar, + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + WRITE_ONCE(dcp->xbar, dcp->fixed_xbar); + if (dcp->fixed_xbar && !dcp->fixed_route_selected) { + ret = mux_control_select(dcp->fixed_xbar, dcp->fixed_mux_index); if (ret) return ret; @@ -544,10 +710,20 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) } else { dcp->phy = dcp->typec_routes[0].phy; dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; + /* The released route's control may go to another pipeline. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + WRITE_ONCE(dcp->xbar, NULL); } } else { - /* Keep DPTX endpoint discovery working before a cable is attached. */ - dcp->phy = dcp->typec_routes[0].phy; + /* + * Keep DPTX endpoint discovery working before a cable is + * attached. Desktop 26.x powers the PHY with the route, so an + * idle pipeline must not point at one. + */ + dcp->phy = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP ? + NULL : dcp->typec_routes[0].phy; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + WRITE_ONCE(dcp->xbar, NULL); dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; dcp->connector_type = DRM_MODE_CONNECTOR_USB; } @@ -623,6 +799,7 @@ static bool dcp_typec_keep_order(void) static void dcp_typec_pipeline_freed(void); static int dcp_fixed_output_select(struct apple_dcp *dcp); +static void dcp_typec_port_attach(struct apple_dcp_typec_port *port); static bool dcp_hdmi_settle_enabled(struct apple_dcp *dcp) { @@ -636,7 +813,8 @@ static enum dcp_fabric_presence_state dcp_hdmi_presence(struct apple_dcp *dcp) enum dcp_fabric_presence_state state; unsigned long flags; - if (!dcp_hdmi_settle_enabled(dcp)) + /* A yielded hybrid is free for Type-C, whatever HDMI shows. */ + if (!dcp_hdmi_settle_enabled(dcp) || READ_ONCE(dcp->hdmi_yielded)) return DCP_FABRIC_ABSENT; spin_lock_irqsave(&dcp->hdmi_presence_lock, flags); state = dcp->hdmi_presence.state; @@ -653,6 +831,8 @@ static u64 dcp_hdmi_edge(struct apple_dcp *dcp) if (!dcp_hdmi_settle_enabled(dcp)) return 0; spin_lock_irqsave(&dcp->hdmi_presence_lock, flags); + if (dcp->hdmi_presence.state != DCP_FABRIC_SETTLING) + dcp->hdmi_presence_prev = dcp->hdmi_presence.state; generation = dcp_fabric_presence_edge(&dcp->hdmi_presence, jiffies, msecs_to_jiffies(DCP_HDMI_HOLD_MS)); spin_unlock_irqrestore(&dcp->hdmi_presence_lock, flags); @@ -689,14 +869,149 @@ static bool dcp_hdmi_sample(struct apple_dcp *dcp, u64 generation, int level) accepted = dcp_fabric_presence_sample(&dcp->hdmi_presence, generation, level > 0, jiffies, msecs_to_jiffies(DCP_HDMI_HOLD_MS)); + /* + * The debounce guards against a glitch after an unplug. A sample that + * finds no sink where there was none before (start-up, and the sample + * after system sleep) has nothing to debounce: the pipeline is free + * now, not ten seconds later, when a Type-C port has already been + * planned or moved onto another pipeline. + */ + if (accepted && level == 0 && dcp->hdmi_presence_prev == DCP_FABRIC_ABSENT) { + dcp->hdmi_presence.state = DCP_FABRIC_ABSENT; + dcp->hdmi_presence.deadline = 0; + } spin_unlock_irqrestore(&dcp->hdmi_presence_lock, flags); dcp_hdmi_schedule(dcp); return accepted; } +/* + * A Type-C display wider than one pipe sits on a Type-C-only pipeline, + * which has no peer above it, because the hybrid was taken when it came + * (by an HDMI display, gone since). Move it onto the free hybrid, as a + * replug does, so that it gets both pipes. + */ +static bool dcp_typec_move_starved_locked(void) +{ + struct apple_dcp_typec_port *port; + bool moved = false; + + lockdep_assert_held(&dcp_typec_fabric_lock); + + list_for_each_entry(port, &dcp_typec_ports, link) { + struct apple_dcp_typec_route *owner = port->owner, *to = NULL; + struct apple_dcp_typec_route *route; + struct apple_dcp *dcp; + + if (!owner || owner->tunnel || !READ_ONCE(owner->dcp->peer_starved)) + continue; + list_for_each_entry(route, &port->routes, port_link) { + if (route != owner && route->dcp->fixed_phy && + dcp_typec_route_available(route)) { + to = route; + break; + } + } + if (!to) + continue; + + dcp = owner->dcp; + dev_info(dcp->dev, "display wider than one pipe moves to the free %s\n", + dev_name(to->dcp->dev)); + if (dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0)) + continue; + port->preferred_route = to; + port->hpd = false; + if (dcp_typec_route_deactivate(owner)) + continue; + port->owner = NULL; + if (dcp_typec_route_activate(to, to->xbar)) { + dev_warn(dcp->dev, "hybrid not taken, the display stays on its pipeline\n"); + if (dcp_typec_route_activate(owner, owner->xbar)) + continue; + to = owner; + } + port->owner = to; + dcp_typec_port_attach(port); + moved |= to != owner; + } + return moved; +} + +/* + * The Type-C display on the hybrid fits one pipe again and an HDMI display + * waits: move the Type-C display onto a free Type-C-only pipeline, as a + * replug does, and connect the HDMI display. The reclaim of the CRTC + * following machines does not apply on desktop 26.x. + */ +static bool dcp_typec_move_off_hybrid_locked(struct apple_dcp_typec_port *port, + struct apple_dcp *hyb) +{ + struct apple_dcp_typec_route *owner = port->owner, *route, *to = NULL; + + lockdep_assert_held(&dcp_typec_fabric_lock); + if (!owner || owner->dcp != hyb || owner->tunnel || port->safe_pending) + return false; + list_for_each_entry(route, &port->routes, port_link) { + if (route != owner && !route->dcp->fixed_phy && route->dcp->crtc && + dcp_typec_route_available(route)) { + to = route; + break; + } + } + if (!to) + return false; + + dev_info(hyb->dev, "Type-C display moves to %s, HDMI display connects\n", + dev_name(to->dcp->dev)); + if (dcp_dptx_disconnect_oob(to_platform_device(hyb->dev), 0)) + return false; + port->preferred_route = to; + port->hpd = false; + if (dcp_typec_route_deactivate(owner)) + return false; + port->owner = NULL; + if (dcp_typec_route_activate(to, to->xbar)) { + dev_warn(hyb->dev, "Type-C display not moved, it stays on the hybrid\n"); + if (dcp_typec_route_activate(owner, owner->xbar)) + return false; + to = owner; + } + port->owner = to; + dcp_typec_port_attach(port); + if (to != owner && hyb->active && gpiod_get_value_cansleep(hyb->hdmi_hpd) > 0) + dcp_dptx_connect(hyb, 0); + return to != owner; +} + static void dcp_hdmi_capacity_available(void *ctx) { dcp_typec_pipeline_freed(); + dcp_typec_move_starved_locked(); +} + +/* + * Desktop 26.x: the HDMI display is gone (its converter's HPD fell and + * stayed low); its firmware reports no unplug. Start the presence hold from + * here, as the edge handler of other machines does, also where presence was + * never tracked: when the hold expires, the desktop has let the HDMI CRTC go + * and the hybrid is free for a Type-C display again. + */ +void dcp_fabric_hdmi_unplugged(struct apple_dcp *dcp) +{ + unsigned long flags; + u64 generation; + + guard(mutex)(&dcp_typec_fabric_lock); + generation = dcp_hdmi_edge(dcp); + if (!generation) + return; + spin_lock_irqsave(&dcp->hdmi_presence_lock, flags); + dcp->hdmi_presence_prev = DCP_FABRIC_PRESENT; + spin_unlock_irqrestore(&dcp->hdmi_presence_lock, flags); + dcp_hdmi_sample(dcp, generation, gpiod_get_value_cansleep(dcp->hdmi_hpd)); + /* A Type-C display that gave its pipes to this one takes them back. */ + dcp_fabric_hybrid_kick(dcp, 0); } static void dcp_hdmi_settle_work(struct work_struct *work) @@ -763,14 +1078,19 @@ static void dcp_hdmi_recheck_work(struct work_struct *work) mod_delayed_work(system_freezable_wq, &dcp->hdmi_recheck_wq, 0); } +static void dcp_hybrid_work(struct work_struct *work); + void dcp_fabric_init(struct apple_dcp *dcp) { spin_lock_init(&dcp->hdmi_presence_lock); dcp->hdmi_presence.state = DCP_FABRIC_ABSENT; + dcp->hdmi_presence_prev = DCP_FABRIC_ABSENT; INIT_DELAYED_WORK(&dcp->hdmi_settle_wq, dcp_hdmi_settle_work); INIT_DELAYED_WORK(&dcp->hdmi_recheck_wq, dcp_hdmi_recheck_work); + INIT_DELAYED_WORK(&dcp->hybrid_wq, dcp_hybrid_work); disable_delayed_work(&dcp->hdmi_settle_wq); disable_delayed_work(&dcp->hdmi_recheck_wq); + disable_delayed_work(&dcp->hybrid_wq); } /* Caller enables the HPD IRQ first; a later edge invalidates this sample. */ @@ -831,10 +1151,10 @@ static void dcp_fixed_hdmi_reinit_locked(struct apple_dcp *dcp, const char *why) if (ret) dev_warn(dcp->dev, "HDMI: PHY not set up again: %d\n", ret); } - if (dcp->xbar && dcp->fixed_route_selected && !dcp->active_typec_route) { + if (dcp->fixed_xbar && dcp->fixed_route_selected && !dcp->active_typec_route) { scoped_guard(mutex, &dcp->tb_lock) dcp->direct_xbar_up = NULL; - ret = mux_control_deselect(dcp->xbar); + ret = mux_control_deselect(dcp->fixed_xbar); dcp->fixed_route_selected = false; if (!ret) ret = dcp_fixed_output_select(dcp); @@ -876,7 +1196,7 @@ static void dcp_fabric_snapshot_port(struct apple_dcp_typec_port *port, port->secondary_owner ? &port->secondary_owner->core : NULL; port->core.preferred = port->preferred_route ? &port->preferred_route->core : NULL; - port->core.wanted = port->dp_wanted; + port->core.wanted = port->dp_wanted && !port->typec_yielded; port->core.hpd = port->dp_hpd; port->core.candidate_crtcs[0] = port->connector ? port->connector->candidate_crtcs : 0; @@ -995,6 +1315,7 @@ dcp_typec_free_route(struct apple_dcp_typec_port *port) struct dcp_fabric_policy policy = { .dual_stream = dcp_typec_ordered_dual_stream(), .follow = dcp_typec_port_follows(port), + .hybrid_first = dcp_typec_hybrid_first(), }; dcp_fabric_snapshot_port(port, false); @@ -1017,7 +1338,7 @@ static void dcp_typec_route_waiting(void) list_for_each_entry(port, &dcp_typec_ports, link) { struct dcp_fabric_port state = { .owner[0] = port->owner ? &port->owner->core : NULL, - .wanted = port->dp_wanted, + .wanted = port->dp_wanted && !port->typec_yielded, .hpd = port->dp_hpd, }; @@ -1431,6 +1752,10 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, port->applied_conf = dp_conf; /* Failed route acquisition must remain retryable on the next update. */ port->applied_valid = false; + + /* did the pairing pass count this port's direct stream so far? */ + was_counted = port->dp_wanted && port->dp_hpd; + /* Keep a direct route until DCP acknowledges release of its display. */ if (!hpd && port->owner && !port->owner->tunnel) { struct apple_dcp *dcp = port->owner->dcp; @@ -1442,15 +1767,50 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, if (ret && !dcp_is_external_h17p(dcp)) return ret; } - } + /* + * Desktop 26.x has one Type-C pipeline and powers the PHY with + * the route. A sink that went away while the port stays in DP + * alt mode (a dock whose monitor was unplugged) must give back + * the pipeline, its crossbar and the PHY, or a display on + * another port is refused as busy. The route is taken again + * when HPD returns. + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + port->preferred_route = port->owner; + port->hpd = false; + /* + * Not waiting for a pipeline either until HPD returns: + * the pipeline just freed must not route this port again. + */ + port->dp_hpd = false; + ret = dcp_typec_route_deactivate(port->owner); + if (ret) + return ret; + port->owner = NULL; - /* did the pairing pass count this port's direct stream so far? */ - was_counted = port->dp_wanted && port->dp_hpd; + /* The hybrid went back to a live HDMI display. */ + if (dcp->hdmi_hpd && dcp->active && !READ_ONCE(dcp->hdmi_yielded) && + gpiod_get_value_cansleep(dcp->hdmi_hpd)) + dcp_dptx_connect(dcp, 0); + dcp_typec_pipeline_freed(); + } + } if (!is_dp) { + struct apple_dcp_typec_route *r; + port->dp_wanted = false; port->dp_hpd = false; port->busy_noted = false; + /* The Type-C display is gone: a hybrid it took goes back to HDMI. */ + dcp_hybrid_fails = 0; + port->typec_yielded = false; + list_for_each_entry(r, &port->routes, port_link) { + if (!READ_ONCE(r->dcp->hdmi_yielded)) + continue; + dev_info(r->dcp->dev, "Type-C display gone: the hybrid goes back to HDMI\n"); + WRITE_ONCE(r->dcp->hdmi_yielded, false); + } /* a Thunderbolt/USB4 DP tunnel is torn down by its own path */ if (port->owner && port->owner->tunnel) { @@ -1521,6 +1881,23 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, } } + /* + * Desktop 26.x has one Type-C pipeline: only a port with a sink (HPD) + * takes it. A dock or cable in DP alt mode without a display behind it + * would otherwise hold it against the port that has one. + */ + if (!port->owner && !hpd && + route->dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + port->applied_valid = true; + return 0; + } + + /* It gave its pipes to an HDMI display, see dcp_hybrid_work(). */ + if (!port->owner && port->typec_yielded) { + port->applied_valid = true; + return 0; + } + if (!port->owner) { best = dcp_typec_free_route(port); /* Every pipeline is driving another port's display. */ @@ -1774,7 +2151,7 @@ int dcp_direct_crossbar_link(struct apple_dcp *dcp, bool up) if (route && !route->tunnel && route->selected) xbar = route->active_xbar ?: route->xbar; else if (!route && dcp->fixed_route_selected) - xbar = dcp->xbar; + xbar = dcp->fixed_xbar; /* A deselected output went idle, and its clocks with it. */ if (dcp->direct_xbar_up && dcp->direct_xbar_up != xbar) dcp->direct_xbar_up = NULL; @@ -1943,6 +2320,7 @@ static int dcp_tb_candidate(void *data) struct dcp_fabric_policy policy = { .dual_stream = dcp_typec_ordered_dual_stream(), .follow = dcp_typec_port_follows(port), + .hybrid_first = dcp_typec_hybrid_first(), }; struct dcp_fabric_route *chosen; bool connector_present = dpin ? !!port->secondary_connector : !!port->connector; @@ -2674,7 +3052,8 @@ static int dcp_typec_follow_decide(struct apple_dcp *dcp, struct drm_crtc *crtc, struct apple_connector *connector, struct dcp_typec_follow *follow) { - struct dcp_fabric_policy policy = { .dual_stream = dcp_typec_ordered_dual_stream() }; + struct dcp_fabric_policy policy = { .dual_stream = dcp_typec_ordered_dual_stream(), + .hybrid_first = dcp_typec_hybrid_first() }; struct apple_dcp_typec_route *from, *to, *holder, *back = NULL; struct apple_dcp_typec_port *port, *other = NULL; bool secondary, off = false; @@ -3666,10 +4045,13 @@ static void dcp_fixed_restore_phy(void *ctx) static int dcp_fixed_select_mux(void *ctx) { struct apple_dcp *dcp = ctx; - int ret = mux_control_select(dcp->xbar, dcp->fixed_mux_index); + int ret = mux_control_select(dcp->fixed_xbar, dcp->fixed_mux_index); - if (!ret) + if (!ret) { dcp->fixed_route_selected = true; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + WRITE_ONCE(dcp->xbar, dcp->fixed_xbar); + } return ret; } @@ -3682,7 +4064,7 @@ static int dcp_fixed_output_select(struct apple_dcp *dcp) { lockdep_assert_held(&dcp_typec_fabric_lock); return dcp_fabric_run_fixed(!!dcp->fixed_phy, !!dcp->active_typec_route, - dcp->xbar && !dcp->fixed_route_selected, + dcp->fixed_xbar && !dcp->fixed_route_selected, &dcp_fixed_ops, dcp); } @@ -3998,6 +4380,176 @@ static const struct dcp_fabric_hdmi_ops dcp_hdmi_ops = { .connect_fixed = dcp_hdmi_connect_fixed, }; +#define DCP_HYBRID_SETTLE_MS 3000 +#define DCP_HYBRID_COOLDOWN_MS 10000 + + +void dcp_fabric_hybrid_kick(struct apple_dcp *dcp, unsigned int delay_ms) +{ + if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) + return; + mod_delayed_work(system_freezable_wq, &dcp->hybrid_wq, msecs_to_jiffies(delay_ms)); +} + +/* typec_hybrid_first was written: apply it now, past any cooldown. */ +static void dcp_fabric_hybrid_policy_changed(void) +{ + struct apple_dcp_typec_port *port; + struct apple_dcp_typec_route *route; + + guard(mutex)(&dcp_typec_fabric_lock); + dcp_hybrid_moved = 0; + dcp_hybrid_fails = 0; + list_for_each_entry(port, &dcp_typec_ports, link) + list_for_each_entry(route, &port->routes, port_link) + if (route->dcp->fixed_phy && route->dcp->hdmi_hpd) + dcp_fabric_hybrid_kick(route->dcp, 0); +} + +static struct apple_dcp *dcp_typec_port_hybrid(struct apple_dcp_typec_port *port) +{ + struct apple_dcp_typec_route *route; + + list_for_each_entry(route, &port->routes, port_link) + if (route->dcp->fixed_phy && route->dcp->hdmi_hpd) + return route->dcp; + return NULL; +} + +/* + * Who drives the hybrid of a desktop 26.x machine when an HDMI display and + * a Type-C display wider than one pipe both want it (typically one monitor + * on two inputs; it cannot tell which input it shows, so typec_hybrid_first + * says, and the desktop may switch it): + * - Type-C first: the Type-C display takes the hybrid and pairs, the HDMI + * display waits; it gets the hybrid back while the Type-C display fits + * one pipe (picture by picture) or is gone. + * - HDMI first: an HDMI display keeps the hybrid; a Type-C display moves + * off it, and takes it only while no HDMI display is there. + * A hand-over is an unplug and replug, so at most one per cooldown. + */ +static void dcp_hybrid_work(struct work_struct *work) +{ + struct apple_dcp *self = container_of(to_delayed_work(work), struct apple_dcp, + hybrid_wq); + unsigned long cooldown = msecs_to_jiffies(DCP_HYBRID_COOLDOWN_MS); + bool typec_first = dcp_typec_hybrid_first(); + struct apple_dcp_typec_port *port; + bool unyielded = false; + + guard(mutex)(&dcp_typec_fabric_lock); + if (dcp_hybrid_moved && time_before(jiffies, dcp_hybrid_moved + cooldown)) { + mod_delayed_work(system_freezable_wq, &self->hybrid_wq, + dcp_hybrid_moved + cooldown - jiffies); + return; + } + + /* A display that gave its pipes to HDMI takes one again when it may. */ + list_for_each_entry(port, &dcp_typec_ports, link) { + struct apple_dcp *hyb = dcp_typec_port_hybrid(port); + + if (!port->typec_yielded) + continue; + if (!typec_first && hyb && gpiod_get_value_cansleep(hyb->hdmi_hpd) > 0 && + hyb->connector && READ_ONCE(hyb->connector->connected) && + !READ_ONCE(hyb->typec_narrow)) + continue; + dev_info(self->dev, "the display on %pOF takes a pipe again\n", + port->connector_np); + port->typec_yielded = false; + unyielded = true; + } + if (unyielded) { + dcp_typec_route_waiting(); + dcp_hybrid_moved = jiffies ?: 1; + return; + } + + list_for_each_entry(port, &dcp_typec_ports, link) { + struct apple_dcp_typec_route *owner = port->owner, *route, *hybrid = NULL; + struct apple_dcp *hyb; + bool hdmi; + + if (!owner || owner->tunnel) + continue; + list_for_each_entry(route, &port->routes, port_link) { + if (route->dcp->fixed_phy && route->dcp->hdmi_hpd) { + hybrid = route; + break; + } + } + if (!hybrid) + continue; + hyb = hybrid->dcp; + hdmi = gpiod_get_value_cansleep(hyb->hdmi_hpd) > 0; + if (!typec_first) + WRITE_ONCE(hyb->hdmi_yielded, false); + + if (owner == hybrid) { + if (!hdmi || (typec_first && !READ_ONCE(hyb->typec_narrow))) + continue; + dev_info(hyb->dev, "%s: the hybrid goes back to the HDMI display\n", + typec_first ? "Type-C display fits one pipe" : "HDMI display first"); + WRITE_ONCE(hyb->hdmi_yielded, false); + if (dcp_typec_move_off_hybrid_locked(port, hyb)) + dcp_hybrid_moved = jiffies ?: 1; + return; + } + + /* + * HDMI first, and the HDMI display is wider than one pipe: the + * Type-C display on the other pipe gives it up and waits, and + * the HDMI display asks for both pipes again. + */ + if (!typec_first && hdmi && READ_ONCE(hyb->peer_starved) && + hyb->connector && READ_ONCE(hyb->connector->connected)) { + struct apple_dcp *dcp = owner->dcp; + + dev_info(hyb->dev, "the display on %pOF waits: the HDMI display takes both pipes\n", + port->connector_np); + if (dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0)) + continue; + port->hpd = false; + if (dcp_typec_route_deactivate(owner)) + continue; + port->owner = NULL; + port->typec_yielded = true; + WRITE_ONCE(hyb->peer_starved, false); + if (!dcp_dptx_disconnect(hyb, 0)) + dcp_dptx_connect(hyb, 0); + dcp_hybrid_moved = jiffies ?: 1; + return; + } + + if (!READ_ONCE(owner->dcp->peer_starved) || hyb->active_typec_route || + dcp_typec_safe_held(hyb) || dcp_hybrid_fails >= DCP_HYBRID_MAX_FAILS || + (!typec_first && hdmi)) + continue; + if (typec_first && !READ_ONCE(hyb->hdmi_yielded)) { + WRITE_ONCE(hyb->hdmi_yielded, true); + dev_info(hyb->dev, "hybrid yielded to the Type-C display on %pOF\n", + port->connector_np); + /* Also a link the firmware keeps without a described display. */ + if ((hyb->connector && READ_ONCE(hyb->connector->connected)) || + hyb->dptxport[0].connected) { + dev_info(hyb->dev, "HDMI display waits: the hybrid goes to a Type-C display wider than one pipe\n"); + dcp_hdmi_yield_link(hyb); + /* The desktop turns the HDMI CRTC off first. */ + mod_delayed_work(system_freezable_wq, &self->hybrid_wq, + msecs_to_jiffies(DCP_HYBRID_SETTLE_MS)); + return; + } + } + if (dcp_typec_move_starved_locked()) + dcp_hybrid_fails = 0; + else if (++dcp_hybrid_fails >= DCP_HYBRID_MAX_FAILS) + dev_warn(hyb->dev, "hybrid not taken %u times, waiting for the next plug\n", + dcp_hybrid_fails); + dcp_hybrid_moved = jiffies ?: 1; + return; + } +} + static void dcp_hdmi_update_locked(struct apple_dcp *dcp) { unsigned long flags; diff --git a/drivers/gpu/drm/apple/dcp-fabric.h b/drivers/gpu/drm/apple/dcp-fabric.h index 869a2c3716cfe..57bffb02d715f 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.h +++ b/drivers/gpu/drm/apple/dcp-fabric.h @@ -39,6 +39,8 @@ struct apple_dcp_typec_route { u32 dptx_phy; u32 mux_index; bool selected; + /* desktop 26.x: the DP PHY is initialised and powered for this route */ + bool phy_powered; /* crossbar output actually selected: xbar (dpphy) or a Thunderbolt dpin */ unsigned int tunnel_dpin; u64 tunnel_generation; @@ -51,6 +53,7 @@ struct apple_dcp_typec_route { int deactivate_error; }; +int dcp_typec_phy_on(struct apple_dcp *dcp); bool dcp_is_typec_output(struct apple_dcp *dcp); bool dcp_is_usb4_output(struct apple_dcp *dcp); bool dcp_uses_t6020_tunnel_flow(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 5950b5883d28f..da06e5e21eab5 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -5,6 +5,7 @@ #define __APPLE_DCP_INTERNAL_H__ #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include "dcp-fabric.h" +#include "dcp-peer-v26_6.h" #include "dcp-fabric-session.h" #include "dcp-relink.h" #include "dptxep.h" @@ -215,6 +217,12 @@ struct apple_dcp_hw_data { * (the T8122 dcpext), see dcp_fw_is_14_7(). */ bool fw_14_7_endpoints; + /* + * An external processor with no DCPAV service to read the EDID + * through, whose sinks' EDID is built from their firmware metadata + * (desktop 26.x), see dcp_sink_metadata(). + */ + bool sink_metadata_edid; }; enum dcp_test_point { @@ -228,6 +236,11 @@ enum dcp_test_point { * for such a DCP only. */ struct dcp_desktop_state { + /* Protected by backlight.lock, independent of framebuffer retirement. */ + struct dcp_backlight_swap backlight; + bool inherited_brightness_valid; + u32 inherited_brightness; + /* Queued swap; too large for the shared swap union. */ struct dcp_swap_submit_req_v26_6_desktop swap; /* Opaque client identity the firmware's IOMFB calls carry. */ @@ -238,6 +251,21 @@ struct dcp_desktop_state { * element before the real one is selected. */ bool mode_live; + /* Identity supplied by this fixed panel, independent of optional color data. */ + struct dcp_display_metadata panel_metadata; + struct dcp_display_metadata panel_adt_identity; + u64 panel_metadata_generation; + bool panel_metadata_valid; + atomic_t identity_log_count[4]; + u8 panel_raw_edid[4096]; + size_t panel_raw_edid_size; + /* RX records an unsupported callback until the modeset RPC has drained. */ + bool modeset_active; + bool modeset_callback_error; + /* An adaptive swap that timed out keeps fixed refresh until the next boot. */ + bool vrr_failed; + u32 present_id; + bool present_pending; /* * GAMMA_LUT: the firmware's own table, read before the first change; * the table being sent; the LUT it is composed from, if any. @@ -270,23 +298,20 @@ struct dcp_desktop_state { * display (unplug); the next swap needs a power-on and a mode set. */ bool ext_pipe_off; - /* - * Dynamic Type-C DP route (T6041 dcpext1): the DP PHYs and crossbars - * of every Type-C port, the one in use (or -1), the DPTX target PHY - * of each and the crossbar state selecting this processor. - */ - unsigned int ndyn; - int dyn_active; - struct phy *dyn_phy[3]; - struct mux_control *dyn_xbar[3]; - u32 dyn_dptx_phy[3]; - u32 dyn_mux_index; }; /* TODO: move IOMFB members to its own struct */ struct dcpext_scanout; struct dcp_hdmi; +bool dcp_desktop_present_complete(struct apple_dcp *dcp, u32 id); +void dcp_panel_metadata_replace(struct apple_dcp *dcp, + const struct dcp_display_metadata *metadata); +const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, + const struct dcp_display_mode *modes, + unsigned int count, u64 *generation); +bool dcp_panel_metadata_current(struct apple_dcp *dcp, u64 generation); + struct apple_dcp { /* Generation-specific wire/transaction state, sharing the common KMS owner. */ struct iomfb_m4 *v26_6; @@ -298,6 +323,36 @@ struct apple_dcp { bool vblank_works_disabled; struct dcp_fabric_pipeline fabric; struct device *dev; + /* Peer DCP for dual-pipe (desktop 26.6): the primary reserves an idle + * peer pipe; the peer records its owner. Protected by dcp_peer_lock. + */ + struct apple_dcp *peer_reserved; + struct apple_dcp *peer_owner; + /* + * This pipeline's Type-C display is wider than one pipe and found no + * peer above it: it needs the hybrid, which was taken when it came. + */ + bool peer_starved; + /* + * Desktop 26.x hybrid with apple,typec-hybrid-first: its HDMI display + * has given the pipeline to a Type-C display wider than one pipe and + * waits until that one fits one pipe again or leaves. + */ + bool hdmi_yielded; + /* The display on this pipeline runs a mode within one pipe. */ + bool typec_narrow; + struct delayed_work hybrid_wq; + struct dcp_peer_tiling peer_tiling; + bool peer_unbinding; + /* Firmware display endpoint (its Disp_Index), apple,dcp-endpoint; + * separate from index, which also selects the doorbell and PHY mode. */ + u32 peer_endpoint; + /* Paired DRM scanout on the primary; under dcp_peer_lock. */ + bool peer_active, peer_failed; + /* The DRM mode the pair was set to (timing, color mode IDs, minRR). */ + u32 peer_mode_timing, peer_mode_color, peer_min_vrr; + /* On the peer: its first completion since the pair was set was logged. */ + bool peer_complete_logged; struct platform_device *piodma; bool piodma_created; struct iommu_domain *iommu_dom; @@ -582,7 +637,13 @@ struct apple_dcp { /* these fields are output port specific */ struct phy *phy; struct phy *fixed_phy; + /* + * The crossbar control a link configuration takes up and down: the + * fixed output's, or on desktop 26.x the active Type-C route's. + */ struct mux_control *xbar; + /* The fixed (HDMI) output's own crossbar control, selected for it. */ + struct mux_control *fixed_xbar; struct typec_mux *typec_mux; struct apple_dcp_typec_route typec_routes[DCP_MAX_TYPEC_ROUTES]; struct apple_dcp_typec_route *active_typec_route; @@ -669,6 +730,8 @@ struct apple_dcp { void (*kick)(struct apple_dcp *dcp); /* Steps a running fade; fade_start is its start in jiffies. */ struct delayed_work fade_work; + struct work_struct notify_work; + struct delayed_work retry_work; unsigned long fade_start; /* Rate limit for firmware re-send requests. */ unsigned long resent_at; @@ -707,6 +770,17 @@ struct apple_dcp { /* Hardirq, resume and expiry share one generation under this lock. */ spinlock_t hdmi_presence_lock; struct dcp_fabric_presence hdmi_presence; + /* The state before the current series of HPD edges began. */ + enum dcp_fabric_presence_state hdmi_presence_prev; + /* + * A desktop 26.x Type-C display that came back from system sleep with a + * partial mode list (the sink was not ready for its EDID yet) is + * connected again after a retrain found no mode. + */ + struct delayed_work link_recovery_wq; + unsigned int link_recovery_tries; + unsigned int link_recovery_snap; + atomic_t modesets_done; unsigned long hdmi_edge_jiffies; bool hdmi_edge_seen; struct delayed_work hdmi_settle_wq; @@ -740,6 +814,7 @@ static inline bool dcp_fail_stop(const struct apple_dcp *dcp) iomfb_is_m4(dcp); } +bool dcp_firmware_metadata_enabled(struct apple_dcp *dcp); void dcp_firmware_metadata_connection(struct apple_dcp *dcp, u64 generation, bool connected); void dcp_firmware_metadata_hotplug(struct apple_dcp *dcp, bool connected); void dcp_firmware_metadata_announced(struct apple_dcp *dcp); @@ -864,4 +939,9 @@ static inline bool dcp_ext_dptx_607(const struct apple_dcp *dcp) #define DCP_AUDIO_MAX_CHANS 15 +void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, + size_t key_len, const void *blob, size_t len); + +void dcp_panel_edid_receive(struct apple_dcp *dcp, const void *blob, size_t len); + #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/dcp-panel-adt.c b/drivers/gpu/drm/apple/dcp-panel-adt.c new file mode 100644 index 0000000000000..5887091aa57cf --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-panel-adt.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Read-only Apple device tree decoding. The wire layout is documented by + * m1n1's public src/adt.h (GPL-2.0-or-later OR BSD-2-Clause). No firmware + * implementation or captured device identifiers are used here. + */ +#include +#include +#include +#include +#include + +#include "dcp-panel-adt.h" + +struct dcp_adt_cursor { + const u8 *data; + size_t pos, size; + u32 nodes; +}; + +static const u8 *dcp_adt_bytes(struct dcp_adt_cursor *c, size_t size) +{ + const u8 *p; + + if (c->pos > c->size || size > c->size - c->pos) + return ERR_PTR(-EINVAL); + p = c->data + c->pos; + c->pos += size; + return p; +} + +/* Hash panel serial bytes into EDID's numeric serial; never log the input. */ +static u32 dcp_adt_serial(const u8 *data, size_t size) +{ + u32 serial = 2166136261U; + + for (size_t i = 0; i < size; i++) + serial = (serial ^ data[i]) * 16777619U; + return serial ?: 1; +} + +static void dcp_adt_panel_property(struct dcp_panel_adt *panel, const char *name, + const u8 *value, u32 size) +{ + struct dcp_display_metadata *id = &panel->identity; + u32 number = size == 4 ? get_unaligned_le32(value) : 0; + + if ((!strcmp(name, "vendor-id") || !strcmp(name, "display-vendor-id")) && + size == 4 && number <= 0x7fff) { + char manufacturer[4] = { (number >> 10 & 31) + '@', + (number >> 5 & 31) + '@', (number & 31) + '@', 0 }; + + if (manufacturer[0] >= 'A' && manufacturer[0] <= 'Z' && + manufacturer[1] >= 'A' && manufacturer[1] <= 'Z' && + manufacturer[2] >= 'A' && manufacturer[2] <= 'Z') + memcpy(id->manufacturer, manufacturer, sizeof(manufacturer)); + } else if ((!strcmp(name, "product-id") || !strcmp(name, "display-product-id") || + !strcmp(name, "panel-id")) && size == 4) { + id->product = number & U16_MAX; + } else if ((!strcmp(name, "serial-number") || + !strcmp(name, "raw-panel-serial-number")) && size && size <= 256) { + id->serial = dcp_adt_serial(value, size); + } else if (!strcmp(name, "display-serial-number") && size == 4) { + id->serial = number; + } else if (!strcmp(name, "width-mm") && size == 4 && number && number <= 2550) { + panel->width_mm = number; + } else if (!strcmp(name, "height-mm") && size == 4 && number && number <= 2550) { + panel->height_mm = number; + } +} + +static int dcp_adt_node(struct dcp_adt_cursor *c, struct dcp_panel_adt *panel, + u32 depth, bool product_parent) +{ + const u8 *header = dcp_adt_bytes(c, 8); + size_t props_start, props_end; + u32 props, children; + char node_name[33] = {}; + bool product, selected; + + if (IS_ERR(header)) + return PTR_ERR(header); + if (depth > 32 || ++c->nodes > 4096) + return -E2BIG; + props = get_unaligned_le32(header); + children = get_unaligned_le32(header + 4); + if (props > 4096 || children > 4096) + return -E2BIG; + props_start = c->pos; + /* Names need not be the first property. Validate every extent first. */ + for (u32 i = 0; i < props; i++) { + const u8 *prop = dcp_adt_bytes(c, 36), *value; + u32 size; + + if (IS_ERR(prop) || !memchr(prop, 0, 32)) + return -EINVAL; + size = get_unaligned_le32(prop + 32) & 0x7fffffff; + value = dcp_adt_bytes(c, ALIGN((size_t)size, 4)); + if (IS_ERR(value)) + return PTR_ERR(value); + if (!strcmp((const char *)prop, "name") && size && size <= 32 && + memchr(value, 0, size)) + memcpy(node_name, value, size); + } + props_end = c->pos; + product = depth == 1 && !strcmp(node_name, "product"); + selected = (depth == 1 && !strcmp(node_name, "panel")) || + (product_parent && !strcmp(node_name, "panel")); + /* /product carries raw-panel-serial-number on machines without /panel. */ + c->pos = props_start; + for (u32 i = 0; i < props; i++) { + const u8 *prop = c->data + c->pos; + u32 size = get_unaligned_le32(prop + 32) & 0x7fffffff; + const char *name = (const char *)prop; + + if (selected || (product && !strcmp(name, "raw-panel-serial-number"))) + dcp_adt_panel_property(panel, name, prop + 36, size); + c->pos += 36 + ALIGN((size_t)size, 4); + } + /* A valid ADT may have neither panel node; retain the OF defaults. */ + panel->found |= selected; + c->pos = props_end; + for (u32 i = 0; i < children; i++) { + int ret = dcp_adt_node(c, panel, depth + 1, product); + + if (ret) + return ret; + } + return 0; +} + +int dcp_panel_adt_parse(const void *data, size_t size, struct dcp_panel_adt *panel) +{ + struct dcp_adt_cursor cursor = { .data = data, .size = size }; + struct dcp_panel_adt parsed = *panel; + int ret; + + if (!data || size > SZ_16M) + return -EINVAL; + ret = dcp_adt_node(&cursor, &parsed, 0, false); + if (!ret) + *panel = parsed; + return ret; +} diff --git a/drivers/gpu/drm/apple/dcp-panel-adt.h b/drivers/gpu/drm/apple/dcp-panel-adt.h new file mode 100644 index 0000000000000..2d724adb7d0e1 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-panel-adt.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCP_PANEL_ADT_H +#define APPLE_DCP_PANEL_ADT_H + +#include "dcp-display-metadata.h" + +/* Only panel properties, never the machine or account identity. */ +struct dcp_panel_adt { + struct dcp_display_metadata identity; + u32 width_mm, height_mm; + bool found; +}; + +int dcp_panel_adt_parse(const void *data, size_t size, struct dcp_panel_adt *panel); +#endif diff --git a/drivers/gpu/drm/apple/dcp-panel-test.h b/drivers/gpu/drm/apple/dcp-panel-test.h new file mode 100644 index 0000000000000..05d575d975fd5 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-panel-test.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* Synthetic J616s-shaped input: no /panel or /product/panel. */ +static const u8 panel_test_adt[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x2d, 0x74, 0x72, 0x65, 0x65, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x00, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2d, 0x69, 0x64, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x70, 0x61, 0x72, 0x74, + 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x2d, 0x6f, 0x63, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x2d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x61, 0x72, 0x6d, 0x2d, + 0x69, 0x6f, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x64, 0x69, 0x73, 0x70, 0x30, 0x00, 0x00, 0x00, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x2d, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2d, 0x69, + 0x6e, 0x66, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x63, 0x68, 0x6f, 0x73, 0x65, 0x6e, 0x00, 0x00, 0x73, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x2d, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x53, 0x59, 0x4e, 0x54, + 0x48, 0x45, 0x54, 0x49, 0x43, 0x2d, 0x4d, 0x41, 0x43, 0x48, 0x49, 0x4e, + 0x45, 0x00, 0x00, 0x00, +}; + +/* Notch-hidden mode geometry observed through the public DRM interface. */ +static const struct dcp_display_mode panel_test_mode = { + .mode = { + .clock = 521065, .hdisplay = 3456, .hsync_start = 3464, + .hsync_end = 3496, .htotal = 3536, .vdisplay = 2160, + .vsync_start = 2278, .vsync_end = 2360, .vtotal = 2456, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + .vrr = true, .min_vrr = 24U << 16, .max_vrr = 120U << 16, +}; diff --git a/drivers/gpu/drm/apple/dcp-peer-v26_6.h b/drivers/gpu/drm/apple/dcp-peer-v26_6.h new file mode 100644 index 0000000000000..3b88d5e040303 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-peer-v26_6.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCP_PEER_V26_6_H +#define APPLE_DCP_PEER_V26_6_H + +#include +#include +#include +#include + +struct dcp_peer_tiling { + u32 phase, modeset_state, x, y, width, height, display_id; + bool member, vftg, primary, ready, mode_flag, display_id_valid; + bool modeset_complete; +}; + +static inline bool dcp_peer_tiling_get( + const struct dcp_peer_tiling *own, + const struct dcp_peer_tiling *related, u32 event, u32 param, u32 *value) +{ + const struct dcp_peer_tiling *target = own; + + if (event == 2 && own->member) { + if (!related) + return false; + target = related; + } + switch (param) { + case 1: *value = target->member; return true; + case 2: + if (event == 1 || target->member) + *value = target->phase; + return true; + case 3: return true; + case 4: + if (event != 1 && !target->member) + return true; + if (!target->display_id_valid) + return false; + *value = target->display_id; + return true; + case 5: case 12: *value = target->x; return true; + case 6: + if (event == 1 || target->member) + *value = target->ready; + return true; + case 8: *value = own->vftg; return true; + case 9: *value = own->primary; return true; + case 10: + if (event == 1 || target->member) + *value = target->modeset_state; + return true; + case 11: *value = 0; return true; + case 13: *value = target->y; return true; + case 14: *value = target->mode_flag; return true; + default: return false; + } +} + +static inline bool dcp_peer_tiling_set(struct dcp_peer_tiling *own, + struct dcp_peer_tiling *related, u32 event, u32 param, u32 value) +{ + struct dcp_peer_tiling *target = event == 2 ? related : own; + + if (!target) + return false; + switch (param) { + case 2: target->phase = value; return true; + case 6: target->ready = !!value; return true; + case 10: target->modeset_state = value; return true; + default: return false; + } +} + +/* + * Display allocation hints — 160 bytes, layout shared with fw27. + * The AP fills primary/peer endpoint IDs and extra-pipe count for a sink + * wider than one pipe's maximum. + */ +struct dcp_display_hints { + u8 hints[0x6c]; + u8 padding[4]; + /* The allocation the AP fills in, or clears when it allocates no peer. */ + struct_group_attr(allocation, __packed, + __le32 primary_id, peer_id; + u8 extra_pipes, flags; + u8 allocation_reserved[22]; + ); + __le32 status; + u8 tail[12]; +} __packed; + +static_assert(sizeof(struct dcp_display_hints) == 160); +static_assert(offsetof(struct dcp_display_hints, primary_id) == 0x70); +static_assert(offsetof(struct dcp_display_hints, status) == 0x90); +static_assert(sizeof_field(struct dcp_display_hints, allocation) == 32); + +/* The widest mode one desktop 26.6 DISPEXT pipe drives (firmware: "max width: 6720"). */ +#define DCP_DESKTOP_PIPE_MAX_WIDTH 6720 + +/* + * Peer validity by firmware endpoint (apple,dcp-endpoint, the firmware's + * Disp_Index). Endpoint 0 is the internal display and never a peer; the + * external pipes merge upwards only (the T8132 rule inherited by T6050): + * dcpext0 (1) is the primary, dcpext1 (2) its peer. The other direction + * pairs, but the peer's half of the picture is noise. + */ +static inline bool dcp_peer_index_allowed_26_6(u32 primary, u32 peer) +{ + return primary && peer > primary; +} + +#endif diff --git a/drivers/gpu/drm/apple/dcp-peer-v26_6.inc b/drivers/gpu/drm/apple/dcp-peer-v26_6.inc new file mode 100644 index 0000000000000..29c4b6e697ebc --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-peer-v26_6.inc @@ -0,0 +1,1116 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Desktop 26.6 dual-pipe peer DCP — allocation, tiling rendezvous and + * paired modeset for a sink wider than one pipe's maximum (G95NC 7680x2160). + * + * The 26.6 firmware uses the same A360-A373 display-pipeline methods as + * firmware 27; its set_power_state is A473. The sequence follows the + * T6050 firmware-27 peer (aurora-silicon/linux#485), with the method and + * callback numbers of the desktop 26.6 firmware: D115 reads the tiling + * state, D116 writes it, and the peer's swaps go as a direct + * swap_start/swap_submit chain (nothing advances the IOMFB transaction + * queue on this firmware). The two DISPEXT pipes merge upwards only: + * the primary is firmware endpoint 1 (dcpext0), the peer endpoint 2. + */ + +static DEFINE_MUTEX(dcp_peer_lock); + +/* ---- tiling state (D114/D115 callbacks) ---- */ + +bool dcp_peer_tiling_state_26_6(struct apple_dcp *dcp, bool write, + u32 event, u32 param, u32 *value) +{ + struct apple_dcp *peer; + + guard(mutex)(&dcp_peer_lock); + if (dcp->peer_unbinding || dcp->crashed) + return false; + peer = dcp->peer_reserved ?: dcp->peer_owner; + if (!peer || peer->peer_unbinding || peer->crashed) + return false; + if (!dcp->peer_tiling.vftg || !peer->peer_tiling.vftg) + return false; + if (write) + return dcp_peer_tiling_set(&dcp->peer_tiling, + &peer->peer_tiling, event, param, *value); + return dcp_peer_tiling_get(&dcp->peer_tiling, &peer->peer_tiling, + event, param, value); +} + +/* ---- synchronous RPC helper ---- */ + +struct dcp_peer_wait { + struct kref refs; + struct completion done; + size_t size; + u8 reply[128]; +}; + +static void dcp_peer_wait_free(struct kref *ref) +{ + kfree(container_of(ref, struct dcp_peer_wait, refs)); +} + +static void dcp_peer_wait_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_peer_wait *wait = cookie; + + if (wait->size && out) + memcpy(wait->reply, out, min(wait->size, sizeof(wait->reply))); + complete(&wait->done); + kref_put(&wait->refs, dcp_peer_wait_free); +} + +static int dcp_peer_call(struct apple_dcp *dcp, const char *tag, + void *in, size_t in_size, void *out, size_t out_size) +{ + struct dcp_method_entry method = { .name = "peer_call" }; + struct dcp_peer_wait *wait; + int ret = 0; + + if (out_size > sizeof(wait->reply)) + return -EINVAL; + wait = kzalloc(sizeof(*wait), GFP_KERNEL); + if (!wait) + return -ENOMEM; + memcpy(method.tag, tag, 4); + wait->size = out_size; + init_completion(&wait->done); + kref_init(&wait->refs); + kref_get(&wait->refs); + dcp_push(dcp, false, &method, in_size, out_size, in, + dcp_peer_wait_done, wait); + if (!wait_for_completion_timeout(&wait->done, + msecs_to_jiffies(!memcmp(tag, "A413", 4) ? 8500 : 2000))) + ret = -ETIMEDOUT; + else if (out_size && out) + memcpy(out, wait->reply, out_size); + kref_put(&wait->refs, dcp_peer_wait_free); + return ret; +} + +static int dcp_peer_call_oob(struct apple_dcp *dcp, const char *tag, + void *in, size_t in_size, void *out, size_t out_size) +{ + struct dcp_method_entry method = { .name = "peer_call_oob" }; + struct dcp_peer_wait *wait; + int ret = 0; + + if (out_size > sizeof(wait->reply)) + return -EINVAL; + wait = kzalloc(sizeof(*wait), GFP_KERNEL); + if (!wait) + return -ENOMEM; + memcpy(method.tag, tag, 4); + wait->size = out_size; + init_completion(&wait->done); + kref_init(&wait->refs); + kref_get(&wait->refs); + dcp_push(dcp, true, &method, in_size, out_size, in, + dcp_peer_wait_done, wait); + if (!wait_for_completion_timeout(&wait->done, msecs_to_jiffies(2000))) + ret = -ETIMEDOUT; + else if (out_size && out) + memcpy(out, wait->reply, out_size); + kref_put(&wait->refs, dcp_peer_wait_free); + return ret; +} + +/* ---- peer release / forget ---- */ + +/* + * The primary's display is gone (cable removed, port moved, system sleep): + * give the reserved peer back. A merged pair is stopped first; a pair that + * still runs is kept. The next allocation reserves a peer again. Without + * this the peer stays marked as an owner's peer after its port was taken by + * another display, and swap_complete would discard that display's own swaps. + */ +/* Drop @owner's reservation of @peer unless their pair runs; peer lock held. */ +static void dcp_peer_drop_locked(struct apple_dcp *owner, struct apple_dcp *peer) +{ + lockdep_assert_held(&dcp_peer_lock); + if (owner->peer_reserved != peer || owner->peer_tiling.vftg || + owner->peer_tiling.member || peer->peer_tiling.vftg || + peer->peer_tiling.member) + return; + memset(&owner->peer_tiling, 0, sizeof(owner->peer_tiling)); + memset(&peer->peer_tiling, 0, sizeof(peer->peer_tiling)); + peer->peer_owner = NULL; + owner->peer_reserved = NULL; + owner->peer_active = false; + dev_info(owner->dev, "peer %s no longer reserved: it drives a display of its own\n", + dev_name(peer->dev)); +} + +void dcp_peer_release_26_6(struct apple_dcp *dcp) +{ + guard(mutex)(&dcp_peer_lock); + if (dcp->peer_reserved) { + struct apple_dcp *peer = dcp->peer_reserved; + + if (dcp->peer_tiling.vftg || dcp->peer_tiling.member || + peer->peer_tiling.vftg || peer->peer_tiling.member) + return; + memset(&dcp->peer_tiling, 0, sizeof(dcp->peer_tiling)); + memset(&peer->peer_tiling, 0, sizeof(peer->peer_tiling)); + peer->peer_owner = NULL; + dcp->peer_reserved = NULL; + dcp->peer_active = false; + } +} + +void dcp_peer_bound_26_6(struct apple_dcp *dcp) +{ + guard(mutex)(&dcp_peer_lock); + dcp->peer_unbinding = false; +} + +static void dcp_peer_forget(struct apple_dcp *dcp) +{ + guard(mutex)(&dcp_peer_lock); + dcp->peer_unbinding = true; + if (dcp->peer_reserved) { + dcp->peer_reserved->peer_owner = NULL; + memset(&dcp->peer_reserved->peer_tiling, 0, + sizeof(dcp->peer_reserved->peer_tiling)); + dcp->peer_reserved = NULL; + memset(&dcp->peer_tiling, 0, sizeof(dcp->peer_tiling)); + } + if (dcp->peer_owner) { + memset(&dcp->peer_owner->peer_tiling, 0, + sizeof(dcp->peer_owner->peer_tiling)); + memset(&dcp->peer_tiling, 0, sizeof(dcp->peer_tiling)); + dcp->peer_owner->peer_reserved = NULL; + dcp->peer_owner = NULL; + } +} + +/* ---- display IDs from D575 hotplug ---- */ + +void dcp_peer_display_ids(struct apple_dcp *dcp, u32 own, u32 related) +{ + struct apple_dcp *peer; + + guard(mutex)(&dcp_peer_lock); + peer = dcp->peer_reserved ?: dcp->peer_owner; + if (!peer || own == related || dcp->peer_unbinding || peer->peer_unbinding) + return; + dcp->peer_tiling.display_id = own; + dcp->peer_tiling.display_id_valid = true; + peer->peer_tiling.display_id = related; + peer->peer_tiling.display_id_valid = true; +} + +/* ---- peer allocation (DPTX call 21 handler) ---- */ + +/* + * Unlike T6050, where the peer is a pipe without outputs, the only T6041 + * candidate (dcpext0) has Type-C routes and HDMI. It qualifies while idle: + * no active route and nothing connected. Its fixed HDMI path brings the + * DPTX link up at start without a sink (dcp_desktop_dp2hdmi_hpd()); that + * link alone does not make it busy. Returns a reason mask, 0 = eligible. + */ +static bool dcp_peer_hdmi_sinkless(struct apple_dcp *p) +{ + return p->fixed_phy && p->hdmi_hpd && + !(p->connector && READ_ONCE(p->connector->connected)); +} + +static u32 dcp_peer_reject_26_6(struct apple_dcp *dcp, struct apple_dcp *p) +{ + u32 why = 0; + + if (!p->active || p->crashed || p->peer_unbinding) + why |= BIT(0); + if (p->peer_owner || p->peer_reserved) + why |= BIT(1); + if (p->active_typec_route) + why |= BIT(2); + if ((p->connector && p->connector->connected) || p->dptxport[1].connected) + why |= BIT(3); + if (p->dptxport[0].connected && !dcp_peer_hdmi_sinkless(p)) + why |= BIT(8); + if (!dcp_peer_index_allowed_26_6(dcp->peer_endpoint, p->peer_endpoint)) + why |= BIT(4); + if (p->dptx_die != dcp->dptx_die) + why |= BIT(5); + if (!p->crtc || p->crtc->base.dev != dcp->crtc->base.dev) + why |= BIT(6); + if (p->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) + why |= BIT(7); + return why; +} + +int dcp_peer_hints_26_6(struct apple_dcp *dcp, struct dptx_port *port, + const void *data, size_t size, void *reply, size_t reply_size) +{ + struct dcp_display_hints *r = reply; + struct apple_dcp *peer = NULL; + struct drm_crtc *crtc; + u32 width; + + if (size != sizeof(*r) || reply_size != sizeof(*r)) + return -EINVAL; + memcpy(reply, data, sizeof(*r)); + r->status = 0; + if (!of_device_is_compatible(dcp->dev->of_node, "apple,t6041-dcpext") || + port->unit || !dcp->crtc) + return 0; + + width = get_unaligned_le32(r->hints + 0x38); + if (!(r->hints[0] & 1) || !width || width <= DCP_DESKTOP_PIPE_MAX_WIDTH) { + memset(&r->allocation, 0, sizeof(r->allocation)); + return 0; + } + + guard(mutex)(&dcp_peer_lock); + if (dcp->peer_unbinding || dcp->crashed) + return -ENODEV; + /* + * A reservation made before a pipe got a display of its own (a Type-C + * display moved there, or an HDMI display that came) is stale: the + * pipe is nobody's peer any more. A running pair keeps it. + */ + if (dcp->peer_owner && READ_ONCE(dcp->active_typec_route)) + dcp_peer_drop_locked(dcp->peer_owner, dcp); + if (dcp->peer_reserved && + (dcp_peer_reject_26_6(dcp, dcp->peer_reserved) & ~BIT(1))) + dcp_peer_drop_locked(dcp, dcp->peer_reserved); + if (dcp->peer_owner) { + memset(&r->allocation, 0, sizeof(r->allocation)); + return 0; + } + peer = dcp->peer_reserved; + if (!peer) { + drm_for_each_crtc(crtc, dcp->crtc->base.dev) { + struct apple_dcp *p = platform_get_drvdata(to_apple_crtc(crtc)->dcp); + u32 why; + + if (!p || p == dcp || + !of_device_is_compatible(p->dev->of_node, "apple,t6041-dcpext")) + continue; + why = dcp_peer_reject_26_6(dcp, p); + dev_dbg(dcp->dev, "peer candidate %s endpoint %u: reject %#x\n", + dev_name(p->dev), p->peer_endpoint, why); + if (why) + continue; + if (!peer || p->peer_endpoint < peer->peer_endpoint) + peer = p; + } + if (!peer) { + dev_info(dcp->dev, "no idle peer pipe for a %u px display\n", width); + WRITE_ONCE(dcp->peer_starved, true); + dcp_fabric_hybrid_kick(dcp, 0); + memset(&r->allocation, 0, sizeof(r->allocation)); + return 0; + } + WRITE_ONCE(dcp->peer_starved, false); + dcp->peer_reserved = peer; + peer->peer_owner = dcp; + dev_info(dcp->dev, "reserved peer %s endpoint %u for %ux%u\n", + dev_name(peer->dev), peer->peer_endpoint, width, + get_unaligned_le32(r->hints + 0x3c)); + } + + r->primary_id = cpu_to_le32(dcp->peer_endpoint); + r->peer_id = cpu_to_le32(peer->peer_endpoint); + /* One extra pipe, full dual pipe: the limited-dual-pipe flag stays clear. */ + r->extra_pipes = 1; + r->flags &= ~3; + return 0; +} + +/* ---- paired power (A473 set_power_state) ---- */ + +/* As dcp_power_state_req(): the state, no transition flags, a result. */ +static void dcp_peer_power_req(u8 *req, bool on) +{ + memset(req, 0, 16); + put_unaligned_le64(on, req); + memset(req + 13, 0xaa, 3); +} + +static int dcp_peer_power(struct apple_dcp *dcp, struct apple_dcp *peer, bool on) +{ + struct dcp_peer_wait *wait; + struct dcp_method_entry method = { .name = "peer_power", + .tag = {'A', '4', '7', '3'} }; + u8 power[16], response[8]; + int ret; + + wait = kzalloc(sizeof(*wait), GFP_KERNEL); + if (!wait) + return -ENOMEM; + kref_init(&wait->refs); + init_completion(&wait->done); + wait->size = sizeof(response); + dcp_peer_power_req(power, on); + kref_get(&wait->refs); + dcp_push(dcp, false, &method, sizeof(power), sizeof(response), power, + dcp_peer_wait_done, wait); + ret = dcp_peer_call(peer, "A473", power, sizeof(power), + response, sizeof(response)); + if (!ret && get_unaligned_le32(response + 4)) + ret = -EIO; + if (!wait_for_completion_timeout(&wait->done, msecs_to_jiffies(2000))) + ret = ret ?: -ETIMEDOUT; + else if (!ret && get_unaligned_le32(wait->reply + 4)) + ret = -EIO; + kref_put(&wait->refs, dcp_peer_wait_free); + dev_dbg(dcp->dev, "peer power %u result=%d\n", on, ret); + return ret; +} + +/* ---- quiesce: tear down the pair ---- */ + +static int dcp_peer_quiesce(struct apple_dcp *dcp, struct apple_dcp *peer) +{ + __le32 manage = 0; + int ret; + + { + guard(mutex)(&dcp_peer_lock); + dcp->peer_tiling.modeset_complete = false; + peer->peer_tiling.modeset_complete = false; + } + ret = dcp_peer_power(dcp, peer, false); + if (ret) + return ret; + msleep(100); + ret = dcp_peer_call(dcp, "A362", &manage, sizeof(manage), NULL, 0); + if (ret) + return ret; + ret = dcp_peer_call(peer, "A362", &manage, sizeof(manage), NULL, 0); + if (ret) + return ret; + { + guard(mutex)(&dcp_peer_lock); + dcp->peer_tiling.member = peer->peer_tiling.member = false; + dcp->peer_tiling.vftg = peer->peer_tiling.vftg = false; + dcp->peer_tiling.modeset_complete = false; + peer->peer_tiling.modeset_complete = false; + dcp->peer_tiling.phase = peer->peer_tiling.phase = 0; + dcp->peer_tiling.modeset_state = peer->peer_tiling.modeset_state = 0; + dcp->peer_tiling.ready = peer->peer_tiling.ready = false; + } + return 0; +} + +/* + * Back to one pipe: quiesce the pair, then power the primary again. A DRM + * commit that only changes the mode does not power the CRTC on again. + */ +static int dcp_peer_unpair(struct apple_dcp *dcp, struct apple_dcp *peer) +{ + u8 power[16], response[8]; + int ret; + + dcp_peer_power_req(power, true); + ret = dcp_peer_quiesce(dcp, peer); + if (!ret) + ret = dcp_peer_call(dcp, "A473", power, sizeof(power), + response, sizeof(response)); + if (!ret && get_unaligned_le32(response + 4)) + ret = -EIO; + dev_info(dcp->dev, "peer unpair result=%d\n", ret); + return ret; +} + +/* + * The primary is being powered off or put to sleep (DPMS, lock screen): + * stop the pair first. On m4.135 the lock screen's DPMS-off ran the + * single-pipe power-off against a merged pair: set_power_state(0) timed + * out, the DPTX HPD deassert failed and the firmware looped until reboot. + * Returns 1 after a paired stop, 0 for an unpaired pipe, or an error. + */ +int dcp_peer_stop_26_6(struct apple_dcp *dcp) +{ + struct apple_dcp *peer; + int ret; + + scoped_guard(mutex, &dcp_peer_lock) { + peer = dcp->peer_reserved; + if (!peer || peer->peer_owner != dcp || + (!dcp->peer_tiling.vftg && !dcp->peer_tiling.member && + !dcp->peer_active)) + return 0; + dcp->peer_active = false; + } + ret = dcp_peer_quiesce(dcp, peer); + dev_info(dcp->dev, "peer stop before power-off: %d\n", ret); + return ret ? ret : 1; +} + +/* ---- DSC copy from primary to peer ---- */ + +static int dcp_peer_copy_dsc(struct apple_dcp *primary, struct apple_dcp *peer) +{ + u8 caps_req[36] = {}, caps[32], bpp_req[4] = {}, bpp[8]; + u8 hints_req[12] = {}, hints[12]; + u32 required; + int ret; + + ret = dcp_peer_call_oob(primary, "A369", NULL, 0, &required, 4); + dev_dbg(primary->dev, "peer DSC required: ret=%d value=%#x\n", ret, required); + if (ret || !(required & 1)) + return ret; + ret = dcp_peer_call_oob(primary, "A367", caps_req, 36, caps, 32); + if (ret) + return ret; + memcpy(caps_req, caps, 32); + ret = dcp_peer_call(peer, "A368", caps_req, 36, caps, 32); + if (ret) + return ret; + ret = dcp_peer_call_oob(primary, "A370", bpp_req, 4, bpp, 8); + if (ret || get_unaligned_le32(bpp + 2)) + return ret ?: -EIO; + memcpy(bpp_req, bpp, 2); + ret = dcp_peer_call(peer, "A371", bpp_req, 4, bpp, 8); + if (ret || get_unaligned_le32(bpp + 2)) + return ret ?: -EIO; + ret = dcp_peer_call_oob(primary, "A372", hints_req, 12, hints, 12); + if (ret || get_unaligned_le32(hints + 8)) + return ret ?: -EIO; + memcpy(hints_req, hints, 8); + ret = dcp_peer_call(peer, "A373", hints_req, 12, hints, 12); + if (ret || get_unaligned_le32(hints + 8)) + return ret ?: -EIO; + dev_dbg(primary->dev, "peer DSC copied: bpp=%u hints=%*ph\n", + get_unaligned_le16(bpp), 8, hints); + return 0; +} + +/* ---- split timing for the half-width derived mode ---- */ + +static void dcp_peer_split_timing(u8 *timing) +{ + static const u8 half_fields[] = { 4, 8, 12, 16, 20, 28, 68 }; + + for (unsigned int i = 0; i < sizeof(half_fields); i++) { + u8 *field = timing + half_fields[i]; + + put_unaligned_le32(get_unaligned_le32(field) >> 1, field); + } + put_unaligned_le32((s32)get_unaligned_le32(timing + 24) / 2, + timing + 24); +} + +/* ---- A364/A365: get video data and insert merge modes ---- */ + +struct dcp_peer_video_data { + u8 timing[0x50]; + u8 color[0x20]; +} __packed; + +struct dcp_peer_get_video_req { + struct dcp_peer_video_data video; + __le32 timing_id, color_id; + u8 requires_merge, timing_null, color_null, requires_merge_null; +} __packed; + +struct dcp_peer_get_video_resp { + struct dcp_peer_video_data video; + u8 requires_merge, success, padding[2]; +} __packed; + +struct dcp_peer_insert_video_req { + struct dcp_peer_video_data video; + __le32 timing_id, color_id; + u8 timing_null, color_null, padding[2]; +} __packed; + +struct dcp_peer_insert_video_resp { + struct dcp_peer_video_data video; + u8 success, padding[3]; +} __packed; + +static_assert(sizeof(struct dcp_peer_get_video_req) == 0x7c); +static_assert(sizeof(struct dcp_peer_get_video_resp) == 0x74); +static_assert(sizeof(struct dcp_peer_insert_video_req) == 0x7c); +static_assert(sizeof(struct dcp_peer_insert_video_resp) == 0x74); + +/* ---- paired modeset ---- */ + +static int dcp_peer_prepare_vftg(struct apple_dcp *dcp, struct apple_dcp *peer, + const struct dcp_peer_insert_video_req *video) +{ + guard(mutex)(&dcp_peer_lock); + if (dcp->peer_reserved != peer || peer->peer_owner != dcp) + return -ENODEV; + dcp->peer_tiling.vftg = peer->peer_tiling.vftg = true; + dcp->peer_tiling.modeset_complete = false; + peer->peer_tiling.modeset_complete = false; + dcp->peer_tiling.primary = true; + peer->peer_tiling.primary = false; + dcp->peer_tiling.width = peer->peer_tiling.width = + get_unaligned_le32(video->video.timing + 8); + dcp->peer_tiling.height = peer->peer_tiling.height = + get_unaligned_le32(video->video.timing + 0x28); + dcp->peer_tiling.x = dcp->peer_tiling.y = peer->peer_tiling.y = 0; + peer->peer_tiling.x = dcp->peer_tiling.width; + return 0; +} + +/* + * A360/A361: both engines must hand out the same swap ID for a paired + * frame. The primary consumed one more ID than the peer during the paired + * mode set on m4.134 (peer 74, primary 75), so this runs again afterwards. + */ +static int dcp_peer_sync_swap_ids(struct apple_dcp *dcp, struct apple_dcp *peer, + const char *when) +{ + u32 primary_id, peer_id, status; + int ret; + + ret = dcp_peer_call(dcp, "A360", NULL, 0, &primary_id, 4); + if (ret) + return ret; + ret = dcp_peer_call(peer, "A360", NULL, 0, &peer_id, 4); + if (ret) + return ret; + dev_dbg(dcp->dev, "peer master swap IDs %s: primary %u peer %u\n", + when, primary_id, peer_id); + if (primary_id != peer_id) { + status = max(primary_id, peer_id); + ret = dcp_peer_call(primary_id < peer_id ? dcp : peer, + "A361", &status, 4, NULL, 0); + } + return ret; +} + +/* + * Adaptive sync (IOMFBParameter 14, minimum rate in 16.16 Hz, 0 = off) goes + * to both pipes before the paired mode set, as the single-pipe mode set + * sends it before the mode is selected. A pipe keeps the parameter across + * a replug: a peer that last ran a VRR mode alone would otherwise merge + * with VRR on while the primary runs without it, which crashed its firmware. + */ +static int dcp_peer_adaptive_sync(struct apple_dcp *pipe, u32 min_vrr) +{ + struct dcp_set_parameter_dcp param = { + .param = IOMFBPARAM_ADAPTIVE_SYNC, + .value = { min_vrr }, + .count = 3, + }; + u32 status = 0; + int ret; + + ret = dcp_peer_call(pipe, "A442", ¶m, sizeof(param), &status, 4); + dev_dbg(pipe->dev, "peer adaptive sync minRR %#x: ret=%d status=%#x\n", + min_vrr, ret, status); + return ret; +} + +static int dcp_peer_modeset(struct apple_dcp *dcp, struct apple_dcp *peer, + const struct dcp_peer_insert_video_req *video, + bool firmware_requires_merge, u32 min_vrr) +{ + struct dcp_peer_wait *wait; + struct dcp_method_entry method = { .name = "peer_modeset", + .tag = {'A', '4', '1', '3'} }; + /* A413 takes the color mode first, as dcp_set_digital_out_mode_req. */ + __le32 mode[2] = {video->color_id, video->timing_id}; + u32 phase, status; + unsigned long deadline; + int ret; + + { + guard(mutex)(&dcp_peer_lock); + if (dcp->peer_reserved != peer || peer->peer_owner != dcp || + !dcp->peer_tiling.display_id_valid || + !peer->peer_tiling.display_id_valid) + return -ENODEV; + dcp->peer_tiling.member = peer->peer_tiling.member = true; + dcp->peer_tiling.mode_flag = peer->peer_tiling.mode_flag = + !firmware_requires_merge; + } + ret = dcp_peer_power(dcp, peer, true); + if (ret) + return ret; + msleep(100); + + ret = dcp_peer_adaptive_sync(dcp, min_vrr); + if (!ret) + ret = dcp_peer_adaptive_sync(peer, min_vrr); + if (ret) + return ret; + + ret = dcp_peer_sync_swap_ids(dcp, peer, "before A413"); + if (ret) + return ret; + + /* Set phase to 1 on both engines */ + phase = 1; + dcp_peer_tiling_state_26_6(dcp, true, 1, 10, &phase); + dcp_peer_tiling_state_26_6(peer, true, 1, 10, &phase); + + /* A413 on primary (async), then wait for phase >= 2 */ + wait = kzalloc(sizeof(*wait), GFP_KERNEL); + if (!wait) + return -ENOMEM; + init_completion(&wait->done); + kref_init(&wait->refs); + kref_get(&wait->refs); + wait->size = 4; + dcp_push(dcp, false, &method, sizeof(mode), 4, mode, + dcp_peer_wait_done, wait); + + deadline = jiffies + msecs_to_jiffies(8500); + for (;;) { + phase = 0; + if (!dcp_peer_tiling_state_26_6(dcp, false, 1, 10, &phase)) { + ret = -ENODEV; + goto put; + } + if (phase >= 2) + break; + /* + * The 26.6 desktop primary merges the timing and completes its + * A413 without waiting for the peer (no phase2 rendezvous seen + * on m4.132): go on to the peer once it succeeded. + */ + if (completion_done(&wait->done)) { + status = get_unaligned_le32(wait->reply); + dev_dbg(dcp->dev, + "peer primary A413 done before phase2: phase=%u status=%#x\n", + phase, status); + if (status) { + ret = -EIO; + goto put; + } + break; + } + if (time_after_eq(jiffies, deadline)) { + ret = -ETIMEDOUT; + dev_err(dcp->dev, + "peer primary timed out before phase2: phase=%u\n", phase); + goto put; + } + msleep(2); + } + dev_dbg(dcp->dev, "peer primary reached modeset phase%u\n", phase); + + /* Copy DSC parameters from primary to peer */ + ret = dcp_peer_copy_dsc(dcp, peer); + if (ret) + goto put; + + /* A413 on peer */ + ret = dcp_peer_call(peer, "A413", mode, sizeof(mode), &status, 4); + if (ret || status) { + dev_err(dcp->dev, "peer A413 failed: ret=%d status=%#x\n", ret, status); + ret = ret ?: -EIO; + goto put; + } + + /* Wait for primary's A413 to complete */ + if (!wait_for_completion_timeout(&wait->done, msecs_to_jiffies(8500))) + ret = -ETIMEDOUT; + else if (get_unaligned_le32(wait->reply)) + ret = -EIO; + if (!ret) + ret = dcp_peer_sync_swap_ids(dcp, peer, "after the paired modeset"); +put: + kref_put(&wait->refs, dcp_peer_wait_free); + if (!ret) { + guard(mutex)(&dcp_peer_lock); + dcp->peer_tiling.modeset_complete = true; + peer->peer_tiling.modeset_complete = true; + } + dev_dbg(dcp->dev, "peer paired modeset result=%d\n", ret); + return ret; +} + +/* ---- install video: A364+A365+A366+modeset ---- */ + +static int dcp_peer_install_video(struct apple_dcp *dcp, struct apple_dcp *peer, + u32 timing, u32 color, u32 min_vrr) +{ + struct dcp_peer_get_video_req req = {}; + struct dcp_peer_get_video_resp resp; + struct dcp_peer_insert_video_req insert = {}; + struct dcp_peer_insert_video_resp inserted; + __le32 enabled = cpu_to_le32(1); + int ret; + + req.timing_id = cpu_to_le32(timing); + req.color_id = cpu_to_le32(color); + ret = dcp_peer_call(dcp, "A364", &req, sizeof(req), &resp, sizeof(resp)); + if (ret) + return ret; + dev_dbg(dcp->dev, "peer video timing=%u color=%u success=%u merge=%u\n", + timing, color, resp.success, resp.requires_merge); + if (!(resp.success & 1)) + return -ENOENT; + if (!(resp.requires_merge & 1)) { + if (dcp->peer_tiling.vftg || peer->peer_tiling.vftg) { + ret = dcp_peer_unpair(dcp, peer); + if (ret) + return ret; + } + return -EOPNOTSUPP; + } + + /* An HDMI sink plugged later connects the link again (HPD irq). */ + if (peer->dptxport[0].connected && dcp_peer_hdmi_sinkless(peer)) { + scoped_guard(mutex, &peer->hpd_mutex) + if (dptx_service_available(&peer->dptxport[0])) + dptxport_set_hpd(peer->dptxport[0].service, false); + ret = dcp_dptx_disconnect(peer, 0); + dev_dbg(dcp->dev, "peer HDMI link released: %d\n", ret); + if (ret) + return ret; + } + + ret = dcp_peer_quiesce(dcp, peer); + if (ret) + return ret; + + insert.video = resp.video; + dcp_peer_split_timing(insert.video.timing); + insert.timing_id = cpu_to_le32(timing + 10000); + insert.color_id = cpu_to_le32(color + 10000); + + ret = dcp_peer_prepare_vftg(dcp, peer, &insert); + if (ret) + return ret; + + /* A366: setup_peer_dcp on the peer */ + ret = dcp_peer_call(peer, "A366", &enabled, sizeof(enabled), NULL, 0); + if (ret) + return ret; + + /* A365: insertMergeModes on primary */ + ret = dcp_peer_call(dcp, "A365", &insert, sizeof(insert), + &inserted, sizeof(inserted)); + if (!ret && !(inserted.success & 1)) + ret = -EIO; + if (ret) + return ret; + + /* A365: insertMergeModes on peer with primary's output */ + insert.video = inserted.video; + ret = dcp_peer_call(peer, "A365", &insert, sizeof(insert), + &inserted, sizeof(inserted)); + if (!ret && !(inserted.success & 1)) + ret = -EIO; + if (ret) + return ret; + + dev_dbg(dcp->dev, "peer derived mode timing=%u color=%u width=%u height=%u\n", + timing + 10000, color + 10000, + get_unaligned_le32(insert.video.timing + 8), + get_unaligned_le32(insert.video.timing + 0x28)); + + /* Paired modeset: A473+A360/A361+A413 on both */ + return dcp_peer_modeset(dcp, peer, &insert, resp.requires_merge & 1, + min_vrr); +} + +/* ---- swaps on the peer pipe ---- */ + +static_assert(sizeof(struct dcp_swap_submit_req_v26_6_desktop) == DCP_V26_SWAP_SIZE); + +static const struct dcp_method_entry dcp_peer_swap_start_method = { + .name = "peer_swap_start", .tag = { 'A', '4', '0', '6' }, +}; + +static const struct dcp_method_entry dcp_peer_swap_submit_method = { + .name = "peer_swap_submit", .tag = { 'A', '4', '0', '7' }, +}; + +static const struct dcp_method_entry dcp_peer_set_master_method = { + .name = "peer_set_master_swap_id", .tag = { 'A', '3', '6', '1' }, +}; + +/* + * One swap on a pipe: swap_start, then swap_submit of @wire. The desktop + * 26.6 firmware does not use the IOMFB transaction queue (nothing advances + * it there: m4.134 timed every peer swap out after 10 s), so the two calls + * are chained directly. The firmware's swap ID must equal @swap_id unless + * that is U32_MAX; on a mismatch the peer's master swap ID is set to it + * once and the swap is started again. @owner is the primary of a paired + * DRM frame. + */ +struct dcp_peer_swap { + struct apple_dcp *owner; + u32 swap_id; + __le32 master; + bool retried; + struct dcp_swap_submit_req_v26_6_desktop wire; +}; + +static void dcp_peer_frame_failed(struct apple_dcp *owner) +{ + guard(mutex)(&dcp_peer_lock); + owner->peer_failed = true; +} + +static void dcp_peer_swap_finish(struct apple_dcp *dcp, struct dcp_peer_swap *ps, + int result, u32 swap_id) +{ + if (result) { + dev_err_ratelimited(dcp->dev, "peer swap %u failed: %d\n", swap_id, result); + if (ps->owner) + dcp_peer_frame_failed(ps->owner); + } + kfree(ps); +} + +static void dcp_peer_swap_submitted(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_peer_swap *ps = cookie; + struct dcp_swap_submit_resp_v26_6_desktop *resp = out; + + dcp_peer_swap_finish(dcp, ps, resp && !resp->ret ? 0 : -EIO, + ps->wire.swap.swap_id); +} + +static void dcp_peer_swap_begin(struct apple_dcp *dcp, struct dcp_peer_swap *ps); + +static void dcp_peer_swap_resynced(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_peer_swap *ps = cookie; + + dcp_peer_swap_begin(dcp, ps); +} + +static void dcp_peer_swap_started(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_peer_swap *ps = cookie; + struct dcp_swap_start_resp_v26_6_desktop *resp = out; + + if (!resp || resp->ret) { + dcp_peer_swap_finish(dcp, ps, -EIO, resp ? resp->swap_id : 0); + return; + } + if (ps->swap_id != U32_MAX && resp->swap_id != ps->swap_id) { + if (!ps->retried && !READ_ONCE(dcp->crashed)) { + dev_info_ratelimited(dcp->dev, + "peer swap ID %u, primary %u: setting the master ID\n", + resp->swap_id, ps->swap_id); + ps->retried = true; + ps->master = cpu_to_le32(ps->swap_id); + dcp_push(dcp, false, &dcp_peer_set_master_method, + sizeof(ps->master), 0, &ps->master, + dcp_peer_swap_resynced, ps); + return; + } + dev_err(dcp->dev, "peer swap ID %u, primary %u\n", + resp->swap_id, ps->swap_id); + dcp_peer_swap_finish(dcp, ps, -EPROTO, resp->swap_id); + return; + } + ps->wire.swap.swap_id = resp->swap_id; + dcp_push(dcp, false, &dcp_peer_swap_submit_method, sizeof(ps->wire), + sizeof(struct dcp_swap_submit_resp_v26_6_desktop), &ps->wire, + dcp_peer_swap_submitted, ps); +} + +static void dcp_peer_swap_begin(struct apple_dcp *dcp, struct dcp_peer_swap *ps) +{ + struct dcp_swap_start_req_v26_6_desktop req = { + .client = dcp->desktop->client, + .padding = { 0xaa, 0xaa, 0xaa }, + }; + + dcp_push(dcp, false, &dcp_peer_swap_start_method, sizeof(req), + sizeof(struct dcp_swap_start_resp_v26_6_desktop), &req, + dcp_peer_swap_started, ps); +} + +/* Takes over @ps unless it returns an error. */ +static int dcp_peer_queue_swap(struct apple_dcp *dcp, struct dcp_peer_swap *ps) +{ + if (READ_ONCE(dcp->crashed) || !dcp->active || !dcp->desktop) { + kfree(ps); + return -EIO; + } + dcp_peer_swap_begin(dcp, ps); + return 0; +} + +/* + * The part of a layer inside the tile [@tile_x, @tile_x + @tile_w), in the + * tile's coordinates. The shared source boundary is rounded once, so the + * halves neither lose nor repeat a source column. + */ +static bool dcp_peer_clip(const struct dcp_rect *src, const struct dcp_rect *dst, + u32 tile_x, u32 tile_w, + struct dcp_rect *tile_src, struct dcp_rect *tile_dst) +{ + u64 left = max_t(u64, dst->x, tile_x); + u64 right = min_t(u64, (u64)dst->x + dst->w, (u64)tile_x + tile_w); + u64 first, last; + + if (!src->w || !src->h || !dst->w || !dst->h || left >= right) + return false; + first = div_u64((left - dst->x) * src->w + dst->w / 2, dst->w); + last = div_u64((right - dst->x) * src->w + dst->w / 2, dst->w); + if (first >= last || (u64)src->x + last > U32_MAX) + return false; + *tile_src = *src; + tile_src->x += first; + tile_src->w = last - first; + *tile_dst = *dst; + tile_dst->x = left - tile_x; + tile_dst->w = right - left; + return true; +} + +/* Both pipes scan the same buffers: the display DARTs share one domain. */ +static void dcp_peer_split_swap(struct dcp_swap_submit_req_v26_6_desktop *left, + struct dcp_swap_submit_req_v26_6_desktop *right, + u32 tile_w) +{ + *right = *left; + for (unsigned int i = 0; i < SWAP_SURFACES; i++) { + struct dcp_swap_submit_req_v26_6_desktop *halves[] = { left, right }; + struct dcp_rect src = left->swap.src_rect[i]; + struct dcp_rect dst = left->swap.dst_rect[i]; + bool present = !left->surf_null[i]; + + for (unsigned int side = 0; side < 2; side++) { + struct dcp_swap_submit_req_v26_6_desktop *req = halves[side]; + struct dcp_rect tile_src, tile_dst; + + if (present && dcp_peer_clip(&src, &dst, side * tile_w, tile_w, + &tile_src, &tile_dst)) { + req->swap.src_rect[i] = tile_src; + req->swap.dst_rect[i] = tile_dst; + } else { + req->surf_null[i] = true; + req->surf_iova[i] = 0; + memset(&req->swap.src_rect[i], 0, sizeof(struct dcp_rect)); + memset(&req->swap.dst_rect[i], 0, sizeof(struct dcp_rect)); + } + } + } +} + +/* + * Paired DRM frame: the primary's swap_start returned @swap_id for @req. + * @req keeps the left tile; the right tile goes to the peer as its own swap. + */ +void dcp_peer_swap_submit_26_6(struct apple_dcp *dcp, + struct dcp_swap_submit_req_v26_6_desktop *req, + u32 swap_id) +{ + struct dcp_peer_swap *ps; + struct apple_dcp *peer; + u32 tile_w; + + if (!READ_ONCE(dcp->peer_active)) + return; + scoped_guard(mutex, &dcp_peer_lock) { + peer = dcp->peer_reserved; + if (!dcp->peer_active || dcp->peer_failed || !peer || + peer->peer_owner != dcp || peer->crashed || peer->peer_unbinding || + !dcp->peer_tiling.modeset_complete || + !peer->peer_tiling.modeset_complete || !dcp->peer_tiling.width) + return; + tile_w = dcp->peer_tiling.width; + } + ps = kzalloc(sizeof(*ps), GFP_KERNEL); + if (!ps) { + dcp_peer_frame_failed(dcp); + return; + } + ps->owner = dcp; + ps->swap_id = swap_id; + dcp_peer_split_swap(req, &ps->wire, tile_w); + if (dcp_peer_queue_swap(peer, ps)) { + dev_err_ratelimited(dcp->dev, "peer swap %u not queued\n", swap_id); + dcp_peer_frame_failed(dcp); + } +} + +/* + * A swap completed on a reserved peer. Its own CRTC is idle; the frame is + * the primary's, which reports it from its own completion. + */ +bool dcp_peer_swap_complete_26_6(struct apple_dcp *dcp, u32 swap_id) +{ + /* Only a peer without a display of its own: its swaps are the pair's. */ + if (!READ_ONCE(dcp->peer_owner) || READ_ONCE(dcp->active_typec_route)) + return false; + WRITE_ONCE(dcp->last_swap_id, swap_id); + WRITE_ONCE(dcp->have_swap_complete, true); + if (!dcp->peer_complete_logged) { + dcp->peer_complete_logged = true; + dev_info(dcp->dev, "peer pipe completed swap %u\n", swap_id); + } + return true; +} + +/* + * DRM mode set on a primary with a reserved peer. A mode wider than one + * pipe is set on both pipes through the merge path; -EOPNOTSUPP leaves the + * mode to the single-pipe path, also after a failed paired mode set. + */ +int dcp_peer_drm_modeset_26_6(struct apple_dcp *dcp, u32 hdisplay, + u32 timing, u32 color, u32 min_vrr) +{ + struct apple_dcp *peer; + bool restart = false; + int ret; + + scoped_guard(mutex, &dcp_peer_lock) { + peer = dcp->peer_reserved; + if (!peer || peer->peer_owner != dcp) + return -EOPNOTSUPP; + dcp->peer_active = false; + } + if (hdisplay <= DCP_DESKTOP_PIPE_MAX_WIDTH || timing > U32_MAX - 10000 || + color > U32_MAX - 10000) { + if (dcp->peer_tiling.vftg || peer->peer_tiling.vftg) + dcp_peer_unpair(dcp, peer); + return -EOPNOTSUPP; + } + /* + * DRM sets the same mode again right after the pair came up. The + * merged pipe no longer resolves the original timing in A364, so a + * second install would fail and tear the pair down: keep it. The pair + * runs with the mode's adaptive-sync rate, so VRR switched on or off + * keeps it too; only a changed rate for the same timing (a new mode + * list) stops the pair and installs it again. + */ + scoped_guard(mutex, &dcp_peer_lock) { + if (dcp->peer_tiling.modeset_complete && + peer->peer_tiling.modeset_complete && !dcp->peer_failed && + dcp->peer_mode_timing == timing && dcp->peer_mode_color == color) { + if (dcp->peer_min_vrr == min_vrr) { + dcp->peer_active = true; + dev_info(dcp->dev, + "paired DRM modeset %u px timing %u color %u: kept\n", + hdisplay, timing, color); + return 0; + } + restart = true; + } + } + if (restart) { + ret = dcp_peer_quiesce(dcp, peer); + dev_info(dcp->dev, "paired DRM modeset: adaptive sync %#x -> %#x, pair stopped: %d\n", + dcp->peer_min_vrr, min_vrr, ret); + if (ret) + return -EOPNOTSUPP; + } + ret = dcp_peer_install_video(dcp, peer, timing, color, min_vrr); + dev_info(dcp->dev, "paired DRM modeset %u px timing %u color %u adaptive sync %#x: %d\n", + hdisplay, timing, color, min_vrr, ret); + if (ret) { + if (dcp->peer_tiling.vftg || peer->peer_tiling.vftg) + dcp_peer_unpair(dcp, peer); + return -EOPNOTSUPP; + } + scoped_guard(mutex, &dcp_peer_lock) { + dcp->peer_active = true; + dcp->peer_failed = false; + dcp->peer_mode_timing = timing; + dcp->peer_mode_color = color; + dcp->peer_min_vrr = min_vrr; + peer->peer_complete_logged = false; + } + return 0; +} diff --git a/drivers/gpu/drm/apple/dcp-refresh.h b/drivers/gpu/drm/apple/dcp-refresh.h new file mode 100644 index 0000000000000..06a8d1c703294 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-refresh.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* Copyright 2026 Ryan Murray */ +#ifndef __APPLE_DCP_REFRESH_H__ +#define __APPLE_DCP_REFRESH_H__ + +#include + +/* Accept nominal 120 Hz and its 1000/1001 fractional timing. */ +static inline bool dcp_refresh_is_promotion(u32 rate) +{ + return rate >= (120U << 16) - (120U << 16) / 500 && + rate <= (120U << 16) + (120U << 16) / 500; +} + +/* All rates are unsigned 16.16 Hz, including fractional fixed timings. */ +static inline bool dcp_refresh_range(u32 rate, u32 minimum, u32 maximum, + bool internal, u32 *low, u32 *high) +{ + if (!minimum && !maximum && internal && dcp_refresh_is_promotion(rate)) { + minimum = 24U << 16; + maximum = rate; + } + /* A partial, inverted or out-of-mode range leaves a fixed timing. */ + if (minimum < (1U << 16) || minimum >= maximum || + maximum > rate || maximum > (65535U << 16)) + return false; + *low = minimum; + *high = maximum; + return true; +} + +/* + * Desktop 26.6 uses its own presentation clock. A deadline long past asks + * for the next permitted refresh. ARM counter values can stall the swap. + */ +static inline u64 dcp_desktop_present_deadline(bool have_surface, bool required) +{ + return have_surface && required ? 120 : 0; +} + +#endif diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8a2e2c5bcbf10..a2a785d577ef2 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -51,6 +51,10 @@ #include "afk.h" #include "av.h" #include "dcp.h" +#include "dcp-panel-adt.h" +#include +#include +#include #include "dcp-fabric-effects.h" #include "dcp-fabric.h" #include "dcp-hdmi.h" @@ -60,6 +64,7 @@ #include "dcp-external-bw.h" #include "dptx-service.h" #include "iomfb.h" +#include "iomfb_internal.h" #include "iomfb_m4.h" #include "ibootep.h" #include "parser.h" @@ -198,15 +203,6 @@ static bool show_notch; module_param(show_notch, bool, 0644); MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch"); -static bool dcpext_probe; -module_param(dcpext_probe, bool, 0444); -MODULE_PARM_DESC(dcpext_probe, "Opt into experimental T6030 external DCP probe (may hang)"); - -static bool t6030_show_notch; -module_param(t6030_show_notch, bool, 0444); -MODULE_PARM_DESC(t6030_show_notch, - "M3 internal display (14.x and 26.6 IOMFB): use the full height and show the notch"); - bool hdmi_audio; module_param(hdmi_audio, bool, 0644); MODULE_PARM_DESC(hdmi_audio, "Enable unstable HDMI audio support"); @@ -230,6 +226,9 @@ bool dcp_t6041_ext_enabled(void) return t6041_dcpext; } +#include "iomfb_v26_6_swap.h" +#include "dcp-peer-v26_6.inc" + /* * The backlight maximum of a desktop 26.x panel whose node has no * apple,max-brightness: the J616s panel's user-accessible SDR maximum (ADT @@ -548,9 +547,39 @@ void dcp_drm_crtc_page_flip(struct apple_dcp *dcp, ktime_t now) spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); } -static bool dcp_firmware_metadata_enabled(struct apple_dcp *dcp) +/* + * A desktop 26.x external processor has no dcpav-service-epic to read the + * sink's EDID through, but exports the TimingElements and DisplayAttributes + * of every sink it drives, on HDMI and Type-C alike. + */ +static bool dcp_sink_metadata(struct apple_dcp *dcp) { - return dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; + return dcp->hw.sink_metadata_edid; +} + +bool dcp_firmware_metadata_enabled(struct apple_dcp *dcp) +{ + return dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL || + dcp_sink_metadata(dcp); +} + +/* + * Such metadata belongs to the sink the firmware reports, as these + * processors have no cable generation of their own: each sink change starts + * a new connection, which the metadata of an earlier sink can never match. + */ +static void dcp_desktop_metadata_new_sink(struct apple_dcp *dcp) +{ + unsigned long flags; + + spin_lock_irqsave(&dcp->metadata.lock, flags); + dcp->metadata.attributes_valid = false; + dcp->metadata.timing_valid = false; + dcp->metadata.epoch = (struct dcp_metadata_epoch) { + .generation = dcp->metadata.epoch.generation + 1, + .connected = true, + }; + spin_unlock_irqrestore(&dcp->metadata.lock, flags); } void dcp_firmware_metadata_connection(struct apple_dcp *dcp, u64 generation, bool connected) @@ -559,6 +588,10 @@ void dcp_firmware_metadata_connection(struct apple_dcp *dcp, u64 generation, boo if (!dcp_firmware_metadata_enabled(dcp)) return; + if (dcp_sink_metadata(dcp)) { + dcp_desktop_metadata_new_sink(dcp); + return; + } spin_lock_irqsave(&dcp->metadata.lock, flags); if (generation != dcp->metadata.epoch.generation || !connected) { dcp->rate_proof.connection_sequence++; @@ -631,6 +664,12 @@ void dcp_firmware_metadata_hotplug(struct apple_dcp *dcp, bool connected) if (!dcp_firmware_metadata_enabled(dcp)) return; + /* + * A desktop 26.x link can bounce without the firmware describing its + * sink again: the cable and HDMI HPD paths report a sink change. + */ + if (dcp_sink_metadata(dcp)) + return; spin_lock_irqsave(&dcp->metadata.lock, flags); if (connected) { dcp_firmware_metadata_present_locked(dcp); @@ -721,6 +760,8 @@ void dcp_firmware_metadata_timing(struct apple_dcp *dcp) !dcp->modes[i].metadata_timing_valid) continue; dcp->metadata.preferred = dcp->modes[i].mode; + /* Desktop 26.x publishes its modes without their polarity. */ + dcp->metadata.preferred.flags |= dcp->modes[i].metadata_sync_flags; dcp->metadata.timing_ticket = dcp->chunks.metadata_ticket; dcp->metadata.timing_valid = true; break; @@ -776,6 +817,214 @@ const struct drm_edid *dcp_firmware_edid(struct apple_dcp *dcp, u64 *generation) return edid; } +/* + * m1n1 reserves the original ADT as a phram node labelled "adt". Read it + * without an MTD dependency. Only the desktop panel calls this; other DCP + * profiles retain their established identity and brightness paths. + */ +void dcp_panel_read_dimensions(struct apple_dcp *dcp, struct device_node *panel_np) +{ + of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm); + /* Read the full height first: adj-height-mm is optional. */ + of_property_read_u32(panel_np, "height-mm", &dcp->panel.height_mm); + if (dcp->notch_height) + of_property_read_u32(panel_np, "adj-height-mm", &dcp->panel.height_mm); +} + +static void dcp_panel_adt_identity(struct apple_dcp *dcp) +{ + struct dcp_panel_adt panel = { + .identity = { .manufacturer = "APP", .product = 0x0616 }, + .width_mm = dcp->panel.width_mm, + .height_mm = dcp->panel.height_mm, + }; + struct device_node *np; + const char *label; + struct resource res; + void *data; + int ret = -ENODATA; + + /* Explicit generic label when no serial or product is provided. */ + strscpy(panel.identity.name, "Apple Panel", sizeof(panel.identity.name)); + for_each_compatible_node(np, NULL, "phram") { + if (of_property_read_string(np, "label", &label) || strcmp(label, "adt")) + continue; + if (of_address_to_resource(np, 0, &res) || + !resource_size(&res) || resource_size(&res) > SZ_16M) + break; + data = memremap(res.start, resource_size(&res), MEMREMAP_WB); + if (!data) + break; + ret = dcp_panel_adt_parse(data, resource_size(&res), &panel); + memunmap(data); + break; + } + of_node_put(np); + /* The board's OF panel dimensions remain valid if ADT has none. */ + dcp->panel.width_mm = panel.width_mm; + dcp->panel.height_mm = panel.height_mm; + dcp->desktop->panel_adt_identity = panel.identity; + dev_info(dcp->dev, "panel identity fallback: ADT %s, panel node %s, dimensions %ux%u mm\n", + ret ? "unavailable" : "parsed", panel.found ? "present" : "absent", + panel.width_mm, panel.height_mm); +} + +/* Internal panel identity has no detachable Type-C service or EDID transport. */ +void dcp_panel_metadata_replace(struct apple_dcp *dcp, + const struct dcp_display_metadata *metadata) +{ + struct dcp_display_metadata merged; + unsigned long flags; + bool changed = false; + + if (!metadata || dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + !dcp_has_panel(dcp)) + return; + spin_lock_irqsave(&dcp->metadata.lock, flags); + merged = *metadata; + if (dcp->desktop->panel_metadata_valid && + merged.product == dcp->desktop->panel_metadata.product && + !memcmp(merged.manufacturer, dcp->desktop->panel_metadata.manufacturer, 4)) { + const struct dcp_display_metadata *old = &dcp->desktop->panel_metadata; + bool same_serial = !merged.serial || merged.serial == old->serial; + + /* Zero/empty optional fields mean unspecified, not a new panel. */ + if (!merged.name[0]) + memcpy(merged.name, old->name, sizeof(merged.name)); + if (!merged.serial) + merged.serial = old->serial; + if (same_serial && !merged.serial_text[0]) + memcpy(merged.serial_text, old->serial_text, sizeof(merged.serial_text)); + if (!merged.year) + merged.year = old->year; + if (!merged.week) + merged.week = old->week; + } + if (!dcp->desktop->panel_metadata_valid || + memcmp(&dcp->desktop->panel_metadata, &merged, sizeof(merged))) { + dcp->desktop->panel_metadata = merged; + dcp->desktop->panel_metadata_valid = true; + if (!dcp->desktop->panel_raw_edid_size) { + dcp->desktop->panel_metadata_generation++; + changed = true; + } + } + spin_unlock_irqrestore(&dcp->metadata.lock, flags); + if (changed && dcp->connector) + dcp_queue_hotplug(dcp->connector); +} + +/* A checksum alone does not guarantee usable userspace identity strings. */ +static bool dcp_panel_edid_has_identity(const u8 *raw) +{ + u16 vendor = (u16)raw[8] << 8 | raw[9]; + bool model = raw[10] || raw[11]; + bool serial = raw[12] || raw[13] || raw[14] || raw[15]; + + if (vendor & 0x8000) + return false; + for (u32 shift = 0; shift <= 10; shift += 5) + if (!((vendor >> shift) & 31) || ((vendor >> shift) & 31) > 26) + return false; + for (u32 offset = 54; offset <= 108; offset += 18) { + const u8 *desc = raw + offset; + bool text = false; + + if (desc[0] || desc[1] || desc[2] || desc[4]) + continue; + for (u32 i = 5; i < 18 && desc[i] != '\n'; i++) { + if (desc[i] < 0x20 || desc[i] > 0x7e) { + text = false; + break; + } + text |= desc[i] != ' '; + } + if (desc[3] == EDID_DETAIL_MONITOR_NAME) + model |= text; + if (desc[3] == EDID_DETAIL_MONITOR_SERIAL) + serial |= text; + } + return model && serial; +} + +/* Preserve a validated real EDID ahead of any metadata-derived fallback. */ +void dcp_panel_edid_receive(struct apple_dcp *dcp, const void *blob, size_t len) +{ + const struct drm_edid *edid; + const u8 *raw; + size_t size; + unsigned long flags; + bool changed; + + if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp) || + parse_panel_edid_data(blob, len, &raw, &size)) + return; + edid = drm_edid_alloc(raw, size); + if (!edid) + return; + if (!drm_edid_valid(edid) || !dcp_panel_edid_has_identity(raw)) { + drm_edid_free(edid); + return; + } + spin_lock_irqsave(&dcp->metadata.lock, flags); + changed = size != dcp->desktop->panel_raw_edid_size || + memcmp(raw, dcp->desktop->panel_raw_edid, size); + if (changed) { + memcpy(dcp->desktop->panel_raw_edid, raw, size); + dcp->desktop->panel_raw_edid_size = size; + dcp->desktop->panel_metadata_generation++; + } + spin_unlock_irqrestore(&dcp->metadata.lock, flags); + drm_edid_free(edid); + if (changed && dcp->connector) + dcp_queue_hotplug(dcp->connector); +} + +bool dcp_panel_metadata_current(struct apple_dcp *dcp, u64 generation) +{ + unsigned long flags; + bool valid; + + spin_lock_irqsave(&dcp->metadata.lock, flags); + valid = generation == dcp->desktop->panel_metadata_generation; + spin_unlock_irqrestore(&dcp->metadata.lock, flags); + return valid; +} + +const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *dcp, + const struct dcp_display_mode *modes, + unsigned int count, u64 *generation) +{ + struct dcp_display_metadata metadata; + const struct drm_edid *edid; + u8 *raw; + size_t size; + unsigned long flags; + + raw = kmalloc_objs(u8, sizeof(dcp->desktop->panel_raw_edid), GFP_KERNEL); + if (!raw) + return ERR_PTR(-ENOMEM); + spin_lock_irqsave(&dcp->metadata.lock, flags); + size = dcp->desktop->panel_raw_edid_size; + memcpy(raw, dcp->desktop->panel_raw_edid, size); + metadata = dcp->desktop->panel_metadata_valid ? + dcp->desktop->panel_metadata : dcp->desktop->panel_adt_identity; + *generation = dcp->desktop->panel_metadata_generation; + spin_unlock_irqrestore(&dcp->metadata.lock, flags); + /* The caller supplies the same retained catalog it is publishing. */ + edid = size ? drm_edid_alloc(raw, size) : + dcp_panel_edid(&metadata, modes, count, + dcp->panel.width_mm, dcp->panel.height_mm); + kfree(raw); + if (!edid) + return ERR_PTR(-ENOMEM); + if (!IS_ERR(edid) && !dcp_panel_metadata_current(dcp, *generation)) { + drm_edid_free(edid); + return ERR_PTR(-ESTALE); + } + return edid; +} + void dcp_set_dimensions(struct apple_dcp *dcp, u64 generation) { struct apple_connector *apple_connector = READ_ONCE(dcp->connector); @@ -891,6 +1140,13 @@ static struct apple_dcp_afkep *dcp_afkep(struct apple_dcp *dcp, u8 endpoint) } } +/* RX and the watchdog may race to finish one present, never both. */ +bool dcp_desktop_present_complete(struct apple_dcp *dcp, u32 id) +{ + return id == READ_ONCE(dcp->desktop->present_id) && + xchg(&dcp->desktop->present_pending, false); +} + #define DCP_SWAP_WATCHDOG_MS 1000 #define DCP_SWAP_WATCHDOG_RETRAINS 5 @@ -909,9 +1165,25 @@ VISIBLE_IF_KUNIT void dcp_swap_watchdog(struct work_struct *work) container_of(to_delayed_work(work), struct apple_dcp, swap_watchdog_wq); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + if (time_before(jiffies, dcp->swap_watchdog_deadline) || + !xchg(&dcp->desktop->present_pending, false)) + return; + /* Retain displaced buffers until a later present really completes. */ + dcp_unarm_retained_framebuffers(dcp, READ_ONCE(dcp->desktop->present_id)); + dcp_backlight_desktop_finish(dcp, READ_ONCE(dcp->desktop->present_id), + true, false); + } + if (!iomfb_retire_dropped_present_h17p(dcp)) return; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->vrr_enabled)) { + WRITE_ONCE(dcp->desktop->vrr_failed, true); + WRITE_ONCE(dcp->vrr_enabled, false); + dev_warn(dcp->dev, "adaptive swap timed out, restoring fixed refresh\n"); + } dev_warn(dcp->dev, "swap not completed, retraining the display\n"); dcp_mode_invalidate(&dcp->mode_state); dcp_drm_crtc_vblank(dcp->crtc); @@ -922,7 +1194,9 @@ VISIBLE_IF_KUNIT void dcp_swap_watchdog(struct work_struct *work) void dcp_swap_watchdog_arm(struct apple_dcp *dcp) { - if (dcp_is_typec_output(dcp)) { + if (dcp_is_typec_output(dcp) || + (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->vrr_enabled))) { dcp->swap_watchdog_deadline = jiffies + msecs_to_jiffies(DCP_SWAP_WATCHDOG_MS); mod_delayed_work(system_dfl_wq, &dcp->swap_watchdog_wq, @@ -1203,6 +1477,15 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *commi crtc_state->vrr_enabled) return -EINVAL; + /* The desktop 26.6 firmware applies adaptive sync with a mode set. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && crtc_state->enable) { + struct drm_crtc_state *old = + drm_atomic_get_old_crtc_state(commit, crtc); + + if (old && old->vrr_enabled != crtc_state->vrr_enabled) + crtc_state->mode_changed = true; + } + /* DRM core only caps the blob size; the gamma update copies a full table. */ if (crtc_state->color_mgmt_changed && crtc_state->gamma_lut && drm_color_lut_size(crtc_state->gamma_lut) != dcp_gamma_lut_size(pdev)) @@ -1342,6 +1625,7 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable { /* T8122 dcpext: HPD only after the link wait, see dcp_ext_dptx_607() */ bool late_hpd = dcp_ext_dptx_607(dcp); + bool compact; unsigned long timeout; u64 generation; u8 dfp_port; @@ -1367,6 +1651,8 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable dcp->connector_type, dcp->dptxport[port].connected); mutex_lock(&dcp->hpd_mutex); + compact = dptx_service_available(&dcp->dptxport[port]) && + READ_ONCE(dcp->dptxport[port].service->ep->compact); if (!dcp_connect_session_valid_locked(dcp, session, cable, recovery)) { ret = -ESTALE; goto out_unlock; @@ -1395,9 +1681,14 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable reinit_completion(&dcp->dptxport[port].linkcfg_completion); WRITE_ONCE(dcp->dptxport[port].tile_hint, false); dcp->dptxport[port].atcphy = dcp->phy; + /* A desktop 26.x route powers its PHY when the display connects. */ + ret = dcp_typec_phy_on(dcp); + if (ret) + goto out_unlock; /* a tiled display's second half comes in on the port's dpin1 */ dfp_port = port && READ_ONCE(dcp->split.active) ? 2 : dcp->dptx_dfp_port; - if (!dcp_ext_dptx_607(dcp)) { + /* Compact DPTX (desktop 26.x) has no connection validation call. */ + if (!dcp_ext_dptx_607(dcp) && !compact) { ret = dptxport_validate_connection(dcp->dptxport[port].service, dfp_port, dcp->dptx_phy, dcp->dptx_die); @@ -1472,7 +1763,12 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable /* A revoked wait must not disconnect or publish readiness for a new session. */ if (!dcp_connect_session_valid(dcp, session, cable, recovery)) return -ESTALE; - if (!ret && late_hpd) { + if (!ret && compact) { + /* Without a sink the link is configured once one arrives. */ + dev_dbg(dcp->dev, + "dcp_dptx_connect: no link configuration on port %u yet\n", port); + ret = 0; + } else if (!ret && late_hpd) { /* Without HPD this firmware does not train: assert it now. */ dev_dbg(dcp->dev, "dcp_dptx_connect: no link configuration on port %u yet, asserting HPD\n", @@ -1893,119 +2189,30 @@ static int dcp_dptx_connect_tile(struct apple_dcp *dcp) /* * Desktop 26.x external processors (T6041 dcpext0 HDMI, dcpext1 USB-C). - * They keep the classic external model of the driver this was ported from: - * no Type-C display fabric, the DPTX link set up on HPD (HDMI) or on the - * DP alt mode hotplug of the port (USB-C), and the Type-C port chosen - * dynamically from the PHY that entered DP alt mode. + * The DPTX link is set up on HPD (HDMI) or when the Type-C display fabric + * routes a port's DP alt mode to the processor (USB-C). */ -/* The caller holds hpd_mutex, which also orders dcp->phy against a connect. */ -static int dcp_desktop_dyn_route_select_locked(struct apple_dcp *dcp) -{ - struct dcp_desktop_state *d = dcp->desktop; - typeof(&apple_t6040_phy_dp_alt) dp_alt; - unsigned int i; - int ret; - - lockdep_assert_held(&dcp->hpd_mutex); - if (!d->ndyn || d->dyn_active >= 0) - return 0; - /* Looked up at runtime so appledrm does not require the T6040 PHY driver. */ -#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) - dp_alt = symbol_get(apple_t6040_phy_dp_alt); -#else - dp_alt = NULL; -#endif - if (!dp_alt) { - dev_dbg(dcp->dev, "T6040 PHY driver not loaded\n"); - return -ENODEV; - } - for (i = 0; i < d->ndyn; i++) - if (dp_alt(d->dyn_phy[i])) - break; -#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) - symbol_put(apple_t6040_phy_dp_alt); -#endif - if (i == d->ndyn) { - dev_dbg(dcp->dev, "no Type-C port in DP alt mode\n"); - return -ENODEV; - } - ret = mux_control_select(d->dyn_xbar[i], d->dyn_mux_index); - if (ret) { - dev_warn(dcp->dev, "Type-C port %u: crossbar select: %d\n", i, ret); - return ret; - } - ret = phy_init(d->dyn_phy[i]); - if (!ret) { - ret = phy_power_on(d->dyn_phy[i]); - if (ret) - phy_exit(d->dyn_phy[i]); - } - if (ret) { - dev_warn(dcp->dev, "Type-C port %u: DP PHY init/power: %d\n", i, ret); - mux_control_deselect(d->dyn_xbar[i]); - return ret; - } - msleep(25); - dcp->phy = d->dyn_phy[i]; - dcp->xbar = d->dyn_xbar[i]; - dcp->dptx_phy = d->dyn_dptx_phy[i]; - d->dyn_active = i; - dev_dbg(dcp->dev, "Type-C port %u selected (DPTX target PHY %u, crossbar state %u)\n", - i, dcp->dptx_phy, d->dyn_mux_index); - return 0; -} - -static void dcp_desktop_dyn_route_release_locked(struct apple_dcp *dcp) -{ - struct dcp_desktop_state *d = dcp->desktop; - int i = d->dyn_active; - - lockdep_assert_held(&dcp->hpd_mutex); - if (!d->ndyn || i < 0) - return; - d->dyn_active = -1; - dcp->phy = NULL; - dcp->xbar = NULL; - phy_power_off(d->dyn_phy[i]); - phy_exit(d->dyn_phy[i]); - mux_control_deselect(d->dyn_xbar[i]); - dev_dbg(dcp->dev, "Type-C port %u released\n", i); -} - -/* Unbind: a selected route holds its crossbar and its powered PHY. */ -static void dcp_desktop_dyn_route_release(struct apple_dcp *dcp) -{ - if (!dcp->desktop) - return; - guard(mutex)(&dcp->hpd_mutex); - dcp_desktop_dyn_route_release_locked(dcp); -} - static int dcp_desktop_dptx_connect(struct apple_dcp *dcp, u32 port) { struct dptx_port *dptx = &dcp->dptxport[port]; unsigned long left; - bool selected; int ret = 0; mutex_lock(&dcp->hpd_mutex); - /* A route selected here is released again if the connect fails. */ - selected = !dcp->phy && dcp->desktop->ndyn; - if (selected) { - ret = dcp_desktop_dyn_route_select_locked(dcp); - if (ret) - goto out_unlock; - } if (!dcp->phy) { dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); ret = -ENODEV; goto out_unlock; } + /* A fabric route powers its PHY here (a no-op for the other routes). */ + ret = dcp_typec_phy_on(dcp); + if (ret) + goto out_unlock; if (!dptx_service_available(dptx)) { dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %u not enabled\n", port); ret = -ENODEV; - goto out_release; + goto out_unlock; } if (dptx->connected) goto out_unlock; @@ -2015,13 +2222,13 @@ static int dcp_desktop_dptx_connect(struct apple_dcp *dcp, u32 port) ret = dptxport_connect(dptx->service, 0, dcp->dptx_phy, dcp->dptx_die, true); if (ret) { dev_warn(dcp->dev, "DPTX port %u: connect failed: %d\n", port, ret); - goto out_release; + goto out_unlock; } ret = dptxport_request_display(dptx->service); if (ret) { dev_warn(dcp->dev, "DPTX port %u: display request failed: %d\n", port, ret); - goto out_release; + goto out_unlock; } dptx->connected = true; @@ -2050,9 +2257,6 @@ static int dcp_desktop_dptx_connect(struct apple_dcp *dcp, u32 port) av_service_connect(dcp); return 0; -out_release: - if (selected) - dcp_desktop_dyn_route_release_locked(dcp); out_unlock: mutex_unlock(&dcp->hpd_mutex); return ret; @@ -2070,32 +2274,64 @@ static int dcp_desktop_dptx_disconnect_oob(struct apple_dcp *dcp, u32 port) dptxport_set_hpd(dcp->dptxport[port].service, false); } ret = dcp_dptx_disconnect(dcp, port); - scoped_guard(mutex, &dcp->hpd_mutex) - dcp_desktop_dyn_route_release_locked(dcp); if (!dcp->main_display) dcp->desktop->ext_pipe_off = true; return ret; } /* - * HDMI HPD of a desktop 26.x external processor. A disconnect is left to - * the firmware, which detects it itself. A cold plug after boot finds the - * port connected without a sink: the firmware never saw this HPD, so pulse - * the AP-asserted HPD to make it export the modes now. + * HDMI HPD of a desktop 26.x external processor. A cold plug after boot + * finds the port connected without a sink: the firmware never saw this HPD, + * so pulse the AP-asserted HPD to make it export the modes now. An unplug + * is reported by nothing else: the DP-to-HDMI converter keeps its DP link + * up without a sink and the firmware sees no change. */ +/* The HDMI display gives its pipeline up: report it gone, release its link. */ +void dcp_hdmi_yield_link(struct apple_dcp *dcp) +{ + int tries = 5, ret; + + disconnected_hpd_event(dcp->connector); + /* A link the firmware keeps retrying is released between attempts. */ + while ((ret = dcp_dptx_disconnect(dcp, 0)) && --tries) + msleep(600); + if (ret) + dev_warn(dcp->dev, "HDMI link not released: %d\n", ret); +} + irqreturn_t dcp_desktop_dp2hdmi_hpd(struct apple_dcp *dcp) { struct dptx_port *dptx = &dcp->dptxport[0]; - bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + bool connected; int ret; - if (connected) { - msleep(500); - connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); - } + msleep(500); + connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_dbg(dcp->dev, "DP2HDMI HPD irq, connected:%d after 500 ms\n", connected); - if (!connected) + if (!connected) { + /* + * A yield outlasts HDMI blinks (a monitor reconfiguring its + * inputs drops HPD on all of them): it ends with the Type-C + * display, not with HPD here. + */ + if (!READ_ONCE(dcp->active_typec_route) && dcp->connector && + READ_ONCE(dcp->connector->connected)) { + dev_info(dcp->dev, "HDMI display unplugged\n"); + disconnected_hpd_event(dcp->connector); + dcp_dptx_disconnect(dcp, 0); + dcp_fabric_hdmi_unplugged(dcp); + } return IRQ_HANDLED; + } + /* + * The pipeline drives a Type-C display, or is about to: the HDMI + * display waits for it, and gets it back once that one fits one pipe. + */ + if (READ_ONCE(dcp->active_typec_route) || READ_ONCE(dcp->hdmi_yielded)) { + dev_info(dcp->dev, "HDMI display waits: its pipeline drives a Type-C display\n"); + dcp_fabric_hybrid_kick(dcp, 1000); + return IRQ_HANDLED; + } mutex_lock(&dcp->hpd_mutex); if (dptx_service_available(dptx) && dptx->connected && @@ -2119,7 +2355,8 @@ int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) struct dcp_fabric_session session = {}; int ret; - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + /* The fixed HDMI output of a desktop 26.x processor has no Type-C session. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); if (!dcp_is_external_h17p(dcp)) { @@ -2395,6 +2632,43 @@ static void dcp_placeholder_edid_work(struct work_struct *work) mutex_unlock(&dcp->hpd_mutex); } +#define DCP_LINK_RECOVERY_TRIES 4 +#define DCP_LINK_RECOVERY_DELAY_MS 2000 + +/* + * A retrain after a hotplug found no mode for the active CRTC: the sink's + * EDID was incomplete (it had just woken from sleep and listed only its base + * modes). Nothing re-reads it until the next HPD event, which can be a + * minute away, so a Type-C display stays at a fallback mode. Connect the + * link again, as a replug does, unless a mode set completed in between. + */ +void dcp_link_recovery_schedule(struct apple_dcp *dcp) +{ + if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || dcp->main_display || + !dcp_is_typec_output(dcp) || !READ_ONCE(dcp->typec_cable_connected) || + dcp->link_recovery_tries >= DCP_LINK_RECOVERY_TRIES) + return; + dcp->link_recovery_snap = atomic_read(&dcp->modesets_done); + mod_delayed_work(system_freezable_wq, &dcp->link_recovery_wq, + msecs_to_jiffies(DCP_LINK_RECOVERY_DELAY_MS)); +} + +static void dcp_link_recovery_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(to_delayed_work(work), + struct apple_dcp, link_recovery_wq); + struct platform_device *pdev = to_platform_device(dcp->dev); + + if (!READ_ONCE(dcp->typec_cable_connected) || + atomic_read(&dcp->modesets_done) != dcp->link_recovery_snap) + return; + dev_info(dcp->dev, "link recovery %u: connecting again after a retrain that found no mode\n", + ++dcp->link_recovery_tries); + dcp_dptx_disconnect_oob(pdev, 0); + msleep(500); + dcp_dptx_connect_oob(pdev, 0); +} + static void dcp_typec_reconnect_work(struct work_struct *work) { struct apple_dcp *dcp = @@ -2649,6 +2923,9 @@ static void disconnected_hpd_event(struct apple_connector *con) struct apple_dcp *dcp = platform_get_drvdata(pdev); WRITE_ONCE(dcp->ext_backlight, false); + /* Nothing else reports an HDMI unplug to the firmware metadata. */ + if (dcp_sink_metadata(dcp)) + dcp_desktop_metadata_new_sink(dcp); } /* May run with DRM modeset locks held; defer property/cache cleanup. */ @@ -2698,6 +2975,8 @@ int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) if (!ret) WRITE_ONCE(dcp->dptxport[port].tile_hint, false); mutex_unlock(&dcp->hpd_mutex); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !port) + dcp_peer_release_26_6(dcp); return ret; } @@ -2706,7 +2985,7 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); if (dcp_is_external_h17p(dcp)) { @@ -2749,8 +3028,9 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_disconnect_oob(dcp, port); if (dcp_is_external_h17p(dcp)) @@ -2778,13 +3058,16 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) av_service_disconnect(dcp); if (dptx_service_available(&dcp->dptxport[port])) { - int ret = dptxport_set_hpd(dcp->dptxport[port].service, false); - + ret = dptxport_set_hpd(dcp->dptxport[port].service, false); if (ret) return ret; } - return dcp_dptx_disconnect(dcp, port); + ret = dcp_dptx_disconnect(dcp, port); + /* The firmware shuts a desktop 26.x external pipe down with its display. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp->main_display) + dcp->desktop->ext_pipe_off = true; + return ret; } /* @@ -3142,7 +3425,8 @@ static int dcp_start_desktop_external(struct apple_dcp *dcp, u32 map) if (ret) return ret; - if (!dcp->phy && !d->ndyn) { + /* An idle Type-C pipeline has no PHY until a route is claimed. */ + if (!dcp->phy && !dcp->nr_typec_routes) { dev_warn(dcp->dev, "no DP PHY and no Type-C route: no display link\n"); return 0; } @@ -3170,7 +3454,12 @@ static int dcp_start_desktop_external(struct apple_dcp *dcp, u32 map) if (ret) return ret; - if (!dptx_ret && dptx_service_available(&dcp->dptxport[0])) { + /* + * A Type-C route powers the PHY and selects the crossbar when it is + * claimed; until then there is no link to bring up. + */ + if (!dptx_ret && dptx_service_available(&dcp->dptxport[0]) && + (dcp->fixed_phy || dcp->active_typec_route)) { ret = dcp_dptx_connect(dcp, 0); if (ret) dev_info(dcp->dev, "no DPTX link at startup: %d\n", ret); @@ -3479,9 +3768,9 @@ static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); - if (connected) + if (connected && !READ_ONCE(dcp->hdmi_yielded)) dcp_dptx_connect(dcp, 0); - else + else if (!connected) _dcp_poweroff(dcp); } @@ -3686,7 +3975,8 @@ void dcp_poweron(struct platform_device *pdev) bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); - if (connected) + /* A yielded hybrid is about to take a Type-C display. */ + if (connected && !READ_ONCE(dcp->hdmi_yielded)) dcp_dptx_connect(dcp, 0); } @@ -4435,7 +4725,7 @@ static void dcp_release_fixed_route(void *data) struct apple_dcp *dcp = data; if (dcp->fixed_route_selected) { - mux_control_deselect(dcp->xbar); + mux_control_deselect(dcp->fixed_xbar); dcp->fixed_route_selected = false; } typec_mux_put(dcp->typec_mux); @@ -4466,6 +4756,8 @@ VISIBLE_IF_KUNIT void dcp_disable_typec_work(struct apple_dcp *dcp, bool release cancel_delayed_work_sync(&dcp->external_stop_retry_wq); disable_delayed_work_sync(&dcp->hdmi_settle_wq); disable_delayed_work_sync(&dcp->hdmi_recheck_wq); + disable_delayed_work_sync(&dcp->link_recovery_wq); + disable_delayed_work_sync(&dcp->hybrid_wq); dcp_hdmi_disable(dcp); } @@ -4479,6 +4771,8 @@ VISIBLE_IF_KUNIT void dcp_enable_typec_work(struct apple_dcp *dcp) enable_delayed_work(&dcp->announce_wq); enable_delayed_work(&dcp->hdmi_settle_wq); enable_delayed_work(&dcp->hdmi_recheck_wq); + enable_delayed_work(&dcp->link_recovery_wq); + enable_delayed_work(&dcp->hybrid_wq); dcp_hdmi_enable(dcp); scoped_guard(mutex, &dcp->hpd_mutex) dcp->typec_work_enabled = true; @@ -4510,20 +4804,6 @@ static bool dcp_external_is_idle(struct apple_dcp *dcp) (status & APPLE_DCP_COPROC_CPU_STATUS_IDLE); } -/* - * The M3 internal displays (14.x and 26.6 IOMFB) also accept - * appledrm.t6030_show_notch=1, the switch existing J613/T6030 installs pass, - * so the panel comes up at its full height (2560x1664 on the J613) instead - * of the module refusing an unknown parameter. - */ -static bool dcp_show_notch(struct apple_dcp *dcp) -{ - if (dcp->fw_compat != DCP_FIRMWARE_V_14_7 && - dcp->fw_compat != DCP_FIRMWARE_V_26_6) - return show_notch; - return show_notch || t6030_show_notch; -} - /* * T8122 dcpext (DCP_FIRMWARE_V_14_7_TEMPLATE): if the PMP runs, the display * gate added its DISPEXT request and pointed the dcpext at it @@ -4670,7 +4950,7 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat dev_info(dev, "DCP index:%u dptx target phy: %u dptx die: %u\n", dcp->index, dcp->dptx_phy, dcp->dptx_die); - if (!dcp_show_notch(dcp)) + if (!show_notch) ret = of_property_read_u32(dev->of_node, "apple,notch-height", &dcp->notch_height); @@ -4688,8 +4968,6 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); if (panel_np) { - const char height_prop[2][16] = { "adj-height-mm", "height-mm" }; - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { /* * Desktop 26.x swaps carry nits, and the panel node @@ -4707,11 +4985,10 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat dev_err(dev, "Missing property 'apple,max-brightness'\n"); } - of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm); - /* use adjusted height as long as the notch is hidden */ - of_property_read_u32(panel_np, height_prop[!dcp->notch_height], - &dcp->panel.height_mm); + dcp_panel_read_dimensions(dcp, panel_np); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp_panel_adt_identity(dcp); of_node_put(panel_np); dcp->fixed_connector_type = DRM_MODE_CONNECTOR_eDP; dcp->connector_type = DRM_MODE_CONNECTOR_eDP; @@ -4886,6 +5163,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (READ_ONCE(dcp->ext_dead)) return 0; + dcp_peer_bound_26_6(dcp); ret = dcp_comp_bind_pipe(dev, main, data); if (ret && dcp->ext_failsafe) { dcp_ext_give_up(dcp, ret, "bind"); @@ -4930,6 +5208,7 @@ VISIBLE_IF_KUNIT void dcp_comp_unbind(struct device *dev, struct device *main, v } disable_work_sync(&dcp->dimensions_wq); + dcp_peer_forget(dcp); if (dcp->fw_compat == DCP_FIRMWARE_V_26_6) { iomfb_v26_6_unbind(dcp); return; @@ -5045,8 +5324,6 @@ VISIBLE_IF_KUNIT void dcp_comp_unbind(struct device *dev, struct device *main, v dcp->systemep = NULL; } - dcp_desktop_dyn_route_release(dcp); - /* * The auxiliary endpoints a desktop 26.x start brought up, still * routed while their shutdown is acknowledged. @@ -5108,8 +5385,9 @@ static int dcp_fixed_output_error(struct apple_dcp *dcp, int err, const char *wh dev_err(dcp->dev, "HDMI output unusable (%s: %d); USB-C displays are not affected\n", what, err); if (dcp->fixed_route_selected) - mux_control_deselect(dcp->xbar); + mux_control_deselect(dcp->fixed_xbar); dcp->fixed_route_selected = false; + dcp->fixed_xbar = NULL; dcp->xbar = NULL; if (!IS_ERR_OR_NULL(dcp->typec_mux)) typec_mux_put(dcp->typec_mux); @@ -5157,62 +5435,31 @@ static void dcp_desktop_phy_off(void *data) } /* - * The outputs of a desktop 26.x external processor (T6041 dcpext): the - * fixed HDMI route's DP PHY is powered here, as the boot loader's working - * sequence does; a dynamic Type-C route lists the DP PHY and crossbar of - * every port (dp-phyN, dp-xbarN, apple,dptx-phys, apple,dptx-mux-index), - * and the one in DP alt mode is claimed at connect. + * The fixed HDMI output of a desktop 26.x external processor (T6041 dcpext0): + * its DP PHY is powered here, as the boot loader's working sequence does. A + * Type-C output (dcpext1) has no fixed PHY; the display fabric powers the + * PHY of the route a port claims (see dcp_typec_phy_on()). */ static int dcp_desktop_probe_routes(struct apple_dcp *dcp) { - struct dcp_desktop_state *d = dcp->desktop; struct device *dev = dcp->dev; - unsigned int i; - int nd, ret; + int ret; - d->dyn_active = -1; - if (!dcp_desktop_external(dcp)) + if (!dcp_desktop_external(dcp) || !dcp->phy) return 0; - if (dcp->phy) { - ret = phy_init(dcp->phy); - if (ret) - return dev_err_probe(dev, ret, "Failed to initialize dp-phy\n"); - ret = phy_power_on(dcp->phy); - if (ret) { - phy_exit(dcp->phy); - return dev_err_probe(dev, ret, "Failed to power on dp-phy\n"); - } - ret = devm_add_action_or_reset(dev, dcp_desktop_phy_off, dcp->phy); - if (ret) - return ret; - msleep(25); + ret = phy_init(dcp->phy); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize dp-phy\n"); + ret = phy_power_on(dcp->phy); + if (ret) { + phy_exit(dcp->phy); + return dev_err_probe(dev, ret, "Failed to power on dp-phy\n"); } - - nd = of_property_count_u32_elems(dev->of_node, "apple,dptx-phys"); - if (nd <= 0) - return 0; - if (nd > ARRAY_SIZE(d->dyn_phy)) - return dev_err_probe(dev, -EINVAL, "too many Type-C routes: %d\n", nd); - - for (i = 0; i < nd; i++) { - char name[16]; - - snprintf(name, sizeof(name), "dp-phy%u", i); - d->dyn_phy[i] = devm_phy_get(dev, name); - if (IS_ERR(d->dyn_phy[i])) - return dev_err_probe(dev, PTR_ERR(d->dyn_phy[i]), "%s\n", name); - snprintf(name, sizeof(name), "dp-xbar%u", i); - d->dyn_xbar[i] = devm_mux_control_get(dev, name); - if (IS_ERR(d->dyn_xbar[i])) - return dev_err_probe(dev, PTR_ERR(d->dyn_xbar[i]), "%s\n", name); - of_property_read_u32_index(dev->of_node, "apple,dptx-phys", i, - &d->dyn_dptx_phy[i]); - } - of_property_read_u32(dev->of_node, "apple,dptx-mux-index", &d->dyn_mux_index); - d->ndyn = nd; - dev_info(dev, "%u Type-C DP routes, crossbar state %u\n", d->ndyn, - d->dyn_mux_index); + ret = devm_add_action_or_reset(dev, dcp_desktop_phy_off, dcp->phy); + if (ret) + return ret; + msleep(25); return 0; } @@ -5311,6 +5558,9 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) mutex_init(&dcp->dcpavserv.lock); INIT_WORK(&dcp->dcpavserv.close_wq, dcpavserv_close_work); dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); + /* The display such a processor finds at boot is its first sink. */ + if (dcp_sink_metadata(dcp)) + dcp_desktop_metadata_new_sink(dcp); if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) { if (!dcp->hw.firmware_clock || @@ -5332,12 +5582,16 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) dcp->fixed_connector_type = dcp_connector_type_from_dt(dev->of_node); dcp->connector_type = dcp->fixed_connector_type; of_property_read_u32(dev->of_node, "apple,dcp-index", &dcp->index); + of_property_read_u32(dev->of_node, "apple,dcp-endpoint", &dcp->peer_endpoint); + if (of_property_read_bool(dev->of_node, "apple,typec-hybrid-first")) + dcp_typec_hybrid_first_dt(); of_property_read_u32(dev->of_node, "apple,dptx-phy", &dcp->dptx_phy); of_property_read_u32(dev->of_node, "apple,dptx-die", &dcp->dptx_die); dcp->fixed_dptx_phy = dcp->dptx_phy; init_completion(&dcp->typec_iomfb_hpd_ready); INIT_DELAYED_WORK(&dcp->typec_reconnect_wq, dcp_typec_reconnect_work); + INIT_DELAYED_WORK(&dcp->link_recovery_wq, dcp_link_recovery_work); INIT_DELAYED_WORK(&dcp->placeholder_edid_wq, dcp_placeholder_edid_work); INIT_DELAYED_WORK(&dcp->typec_relink_wq, dcp_typec_relink_work); @@ -5355,6 +5609,7 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) disable_delayed_work(&dcp->placeholder_edid_wq); disable_delayed_work(&dcp->typec_relink_wq); disable_delayed_work(&dcp->announce_wq); + disable_delayed_work(&dcp->link_recovery_wq); dcp->announce.stopped = true; /* * A native external pipe's bind enables these; firmware callbacks may @@ -5547,7 +5802,8 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) ret = devm_add_action_or_reset(dev, dcp_release_fixed_route, dcp); if (ret) return ret; - ret = mux_control_select(dcp->xbar, mux_index); + dcp->fixed_xbar = dcp->xbar; + ret = mux_control_select(dcp->fixed_xbar, mux_index); if (ret) dev_warn(dev, "mux_control_select failed: %d\n", ret); else @@ -6084,6 +6340,7 @@ static int dcp_platform_resume(struct device *dev) if (READ_ONCE(dcp->ext_dead)) return 0; + dcp->link_recovery_tries = 0; dcp_release_after_sleep(dcp); dcp_enable_typec_work(dcp); /* The HDMI output's PHY and crossbar may have lost their setup in sleep. */ @@ -6203,6 +6460,7 @@ static const struct apple_dcp_hw_data apple_dcp_hw_t6041 = { static const struct apple_dcp_hw_data apple_dcp_hw_t6041_dcpext = { .num_dptx_ports = 2, + .sink_metadata_edid = true, }; static const struct of_device_id of_match[] = { diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 30b728820e2c7..bcdbf49318a6a 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -101,6 +101,7 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); void dcp_set_dimensions(struct apple_dcp *dcp, u64 generation); +void dcp_panel_read_dimensions(struct apple_dcp *dcp, struct device_node *panel_np); void dcp_update_connector_dimensions(struct apple_dcp *dcp, struct drm_connector *connector); void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message); @@ -143,4 +144,29 @@ unsigned int apple_j613_25g83_clock_hz(void); /* A J613, or a J615 whose boot loader carries the experimental opt-in. */ bool apple_t8122_25g83_board(void); +void dcp_typec_hybrid_first_dt(void); +void dcp_fabric_hdmi_unplugged(struct apple_dcp *dcp); +void dcp_fabric_hybrid_kick(struct apple_dcp *dcp, unsigned int delay_ms); +void dcp_hdmi_yield_link(struct apple_dcp *dcp); +void dcp_link_recovery_schedule(struct apple_dcp *dcp); + +/* Desktop 26.6 dual-pipe peer (dcp-peer-v26_6.inc, included from dcp.c) */ +struct dptx_port; +int dcp_peer_hints_26_6(struct apple_dcp *dcp, struct dptx_port *port, + const void *data, size_t size, + void *reply, size_t reply_size); +bool dcp_peer_tiling_state_26_6(struct apple_dcp *dcp, bool write, + u32 event, u32 param, u32 *value); +void dcp_peer_display_ids(struct apple_dcp *dcp, u32 own, u32 related); +struct dcp_swap_submit_req_v26_6_desktop; +int dcp_peer_drm_modeset_26_6(struct apple_dcp *dcp, u32 hdisplay, + u32 timing, u32 color, u32 min_vrr); +void dcp_peer_swap_submit_26_6(struct apple_dcp *dcp, + struct dcp_swap_submit_req_v26_6_desktop *req, + u32 swap_id); +bool dcp_peer_swap_complete_26_6(struct apple_dcp *dcp, u32 swap_id); +int dcp_peer_stop_26_6(struct apple_dcp *dcp); +void dcp_peer_release_26_6(struct apple_dcp *dcp); +void dcp_peer_bound_26_6(struct apple_dcp *dcp); + #endif diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index e9bc471654cde..2a9318beb4d32 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -22,9 +22,6 @@ #define MIN_BRIGHTNESS_PART2 103U #define MAX_BRIGHTNESS_PART2 510U -/* Initial level of a desktop 26.x panel, which reports none. */ -#define DCP_DESKTOP_DEFAULT_NITS 250U - /* * lookup for display brightness 2 to 99 nits * */ @@ -104,19 +101,119 @@ bool dcp_backlight_active(struct apple_dcp *dcp) READ_ONCE(dcp->backlight.state.ready); } +static bool dcp_backlight_desktop(struct apple_dcp *dcp) +{ + return dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->backlight.state.ready); +} + +static void dcp_backlight_desktop_kick(struct apple_dcp *dcp) +{ + if (!READ_ONCE(dcp->quiescing) && !READ_ONCE(dcp->crashed)) + schedule_work(&dcp->bl_update_wq); +} + +/* + * Match the backlight core's change event without force_update(), which + * would replace a user's target with an intermediate fade level. + */ +static void dcp_backlight_notify_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(work, struct apple_dcp, backlight.notify_work); + struct backlight_device *bd = READ_ONCE(dcp->brightness.bl_dev); + char *envp[2]; + + if (!bd || READ_ONCE(dcp->quiescing)) + return; + envp[0] = "SOURCE=unknown"; + envp[1] = NULL; + kobject_uevent_env(&bd->dev.kobj, KOBJ_CHANGE, envp); + sysfs_notify(&bd->dev.kobj, NULL, "brightness"); + sysfs_notify(&bd->dev.kobj, NULL, "actual_brightness"); +} + +/* Run before the later-registered backlight devres unregisters the device. */ +static void dcp_backlight_notify_release(void *data) +{ + struct apple_dcp *dcp = data; + + WRITE_ONCE(dcp->brightness.bl_dev, NULL); + cancel_work_sync(&dcp->backlight.notify_work); +} + +static void dcp_backlight_notify(struct apple_dcp *dcp) +{ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + !READ_ONCE(dcp->quiescing) && READ_ONCE(dcp->brightness.bl_dev)) + schedule_work(&dcp->backlight.notify_work); +} + +/* A loader hint describes the inherited level, not a new brightness request. */ +static void dcp_backlight_sync_hint(struct apple_dcp *dcp) +{ + struct backlight_device *bd = READ_ONCE(dcp->brightness.bl_dev); + unsigned long flags; + + if (!bd) + return; + /* Match update_status's lock order and never replace a user's target. */ + mutex_lock(&bd->ops_lock); + spin_lock_irqsave(&dcp->backlight.lock, flags); + if (!dcp->backlight.state.controlled && dcp->backlight.state.level_known) + bd->props.brightness = dcp->backlight.state.target; + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + mutex_unlock(&bd->ops_lock); + dcp_backlight_notify(dcp); +} + int dcp_backlight_takeover(struct apple_dcp *dcp, u32 millinits) { u32 maximum, nits; int ret; - if (dcp_backlight_active(dcp)) - return 0; /* H17P takes nits up to the panel ceiling, not the older DAC table's. */ maximum = dcp->brightness.maximum; ret = dcp_bl_takeover_nits(maximum, millinits, &nits); if (ret) return ret; + if (dcp_backlight_active(dcp) || + (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->backlight.state.ready))) { + unsigned long flags; + bool seeded; + + spin_lock_irqsave(&dcp->backlight.lock, flags); + seeded = dcp_bl_report(&dcp->backlight.state, nits); + if (seeded && dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + dcp->desktop->inherited_brightness = nits; + dcp->desktop->inherited_brightness_valid = true; + } + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + if (seeded) + dcp_backlight_sync_hint(dcp); + return 0; + } + + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + unsigned long flags; + bool seeded = false; + + /* Accept the first hint until a user request takes ownership. */ + spin_lock_irqsave(&dcp->backlight.lock, flags); + if (!dcp->desktop->inherited_brightness_valid && + !dcp->backlight.state.controlled) { + dcp->desktop->inherited_brightness = nits; + dcp->desktop->inherited_brightness_valid = true; + if (dcp->backlight.state.ready) + seeded = dcp_bl_report(&dcp->backlight.state, nits); + } + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + if (seeded) + dcp_backlight_sync_hint(dcp); + return 0; + } + /* Keep the first bounded hint. Later reports are not user requests. */ return iomfb_configure_backlight_h17p(dcp, maximum, true, nits, false, 0); } @@ -125,14 +222,17 @@ static int dcp_get_brightness(struct backlight_device *bd) { struct apple_dcp *dcp = bl_get_data(bd); unsigned long flags; - u32 actual; + int actual; if (READ_ONCE(dcp->quiescing)) return -ENODEV; - if (dcp_backlight_active(dcp)) { + if (dcp_backlight_active(dcp) || dcp_backlight_desktop(dcp)) { spin_lock_irqsave(&dcp->backlight.lock, flags); - actual = dcp->backlight.state.actual; + if (dcp_backlight_desktop(dcp)) + actual = dcp_bl_actual(&dcp->backlight.state); + else + actual = dcp->backlight.state.actual; spin_unlock_irqrestore(&dcp->backlight.lock, flags); return actual; } @@ -227,9 +327,21 @@ static void dcp_backlight_fade_work(struct work_struct *work) msecs_to_jiffies(DCP_BL_FADE_STEP_MS)); } +static void dcp_backlight_retry_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(to_delayed_work(work), + struct apple_dcp, backlight.retry_work); + + if (!READ_ONCE(dcp->quiescing) && !READ_ONCE(dcp->crashed)) + dcp_backlight_kick(dcp); +} + void dcp_backlight_stop(struct apple_dcp *dcp) { cancel_delayed_work_sync(&dcp->backlight.fade_work); + cancel_delayed_work_sync(&dcp->backlight.retry_work); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + cancel_work_sync(&dcp->backlight.notify_work); } static void dcp_backlight_release(void *data) @@ -246,6 +358,8 @@ int dcp_backlight_init(struct apple_dcp *dcp, struct device *dev) { spin_lock_init(&dcp->backlight.lock); INIT_DELAYED_WORK(&dcp->backlight.fade_work, dcp_backlight_fade_work); + INIT_WORK(&dcp->backlight.notify_work, dcp_backlight_notify_work); + INIT_DELAYED_WORK(&dcp->backlight.retry_work, dcp_backlight_retry_work); return devm_add_action_or_reset(dev, dcp_backlight_release, dcp); } @@ -350,6 +464,69 @@ unsigned int dcp_backlight_retry_delay(struct apple_dcp *dcp) return delay; } +/* Power changes jump; a target chosen while off is restored on enable. */ +void dcp_backlight_desktop_power(struct apple_dcp *dcp, bool on) +{ + unsigned long flags; + + if (!dcp_backlight_desktop(dcp)) + return; + if (!on) + dcp_backlight_stop(dcp); + spin_lock_irqsave(&dcp->backlight.lock, flags); + dcp->backlight.state.dpms_off = !on; + if (dcp->backlight.state.controlled) + dcp_bl_jump(&dcp->backlight.state); + spin_unlock_irqrestore(&dcp->backlight.lock, flags); +} + +/* Reserve only after swap_start succeeds, using its actual firmware ID. */ +bool dcp_backlight_desktop_prepare(struct apple_dcp *dcp, u32 id, + bool have_surface, u32 *nits) +{ + unsigned long flags; + int ret; + + if (!dcp_backlight_desktop(dcp)) + return false; + spin_lock_irqsave(&dcp->backlight.lock, flags); + ret = dcp_bl_swap_prepare(&dcp->backlight.state, + &dcp->desktop->backlight, id, have_surface); + if (!ret) + *nits = dcp->desktop->backlight.present.nits; + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + return !ret; +} + +void dcp_backlight_desktop_finish(struct apple_dcp *dcp, u32 id, + bool reply, bool accepted) +{ + unsigned long flags; + unsigned int delay; + bool finished, pending, changed; + int previous; + + if (!dcp_backlight_desktop(dcp)) + return; + spin_lock_irqsave(&dcp->backlight.lock, flags); + previous = dcp_bl_actual(&dcp->backlight.state); + finished = dcp_bl_swap_finish(&dcp->backlight.state, + &dcp->desktop->backlight, id, reply, accepted); + changed = previous != dcp_bl_actual(&dcp->backlight.state); + delay = dcp_bl_retry_delay(&dcp->backlight.state); + pending = dcp->backlight.state.dirty; + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + if (changed) + dcp_backlight_notify(dcp); + if (!finished || READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->crashed)) + return; + if (delay) + mod_delayed_work(system_dfl_wq, &dcp->backlight.retry_work, + msecs_to_jiffies(delay)); + else if (pending && (!reply || accepted)) + dcp_backlight_kick(dcp); +} + #define SCALE_FACTOR (1 << 10) static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size) @@ -416,7 +593,8 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) if (ret) goto out; - if (!dcp->brightness.update) + if (dcp_backlight_desktop(dcp) ? !dcp_backlight_pending(dcp) : + !dcp->brightness.update) goto out; crtc_state = drm_atomic_get_crtc_state(commit, crtc); @@ -425,12 +603,19 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) goto out; } + /* A request made while DPMS is off is restored on the enable swap. */ + if (dcp_backlight_desktop(dcp) && !crtc_state->active) + goto out; + /* Re-present the current scanout when brightness is the only change. */ ret = drm_atomic_add_affected_planes(commit, crtc); if (ret) goto out; - crtc_state->color_mgmt_changed |= true; + if (dcp_backlight_desktop(dcp)) + crtc_state->planes_changed = true; + else + crtc_state->color_mgmt_changed = true; ret = drm_atomic_commit(commit); out: @@ -471,7 +656,8 @@ int dcp_backlight_update(struct apple_dcp *dcp) /* Wait 1 vblank cycle in the hope an atomic swap has already updated * the brightness */ - msleep((1001 + 23) / 24); // 42ms for 23.976 fps + if (!dcp_backlight_desktop(dcp)) + msleep((1001 + 23) / 24); // 42ms for 23.976 fps return drm_crtc_set_brightness(dcp); } @@ -486,7 +672,7 @@ static int dcp_set_brightness(struct backlight_device *bd) if (READ_ONCE(dcp->quiescing)) return -ENODEV; - if (dcp_backlight_active(dcp)) { + if (dcp_backlight_active(dcp) || dcp_backlight_desktop(dcp)) { struct dcp_backlight_state *state = &dcp->backlight.state; bool fade; u32 old; @@ -510,24 +696,11 @@ static int dcp_set_brightness(struct backlight_device *bd) return ret; } - /* - * Desktop 26.x swaps carry nits. A level of 0 must not turn the - * panel off (DPMS does that); only a blanked panel still gets 0. - */ - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !brightness && - !bd->props.brightness) - brightness = min(bd->props.max_brightness, 2); - ret = drm_modeset_lock_single_interruptible(&dcp->crtc->base.mutex); if (ret) return ret; - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { - dcp->brightness.nits = brightness; - dcp->brightness.requested = true; - } else { - dcp->brightness.dac = calculate_dac(dcp, brightness); - } + dcp->brightness.dac = calculate_dac(dcp, brightness); dcp->brightness.update = true; drm_modeset_unlock(&dcp->crtc->base.mutex); @@ -540,6 +713,12 @@ static bool dcp_backlight_controls_device(struct backlight_device *bd, { struct apple_dcp *dcp = bl_get_data(bd); + /* A framebuffer's first unblank must not apply the reporting default. */ + if (dcp_backlight_desktop(dcp) && + !READ_ONCE(dcp->backlight.state.level_known) && + !READ_ONCE(dcp->backlight.state.controlled)) + return false; + return !display_dev || display_dev == dcp->dev || (dcp->crtc && display_dev == dcp->crtc->base.dev->dev); } @@ -555,6 +734,7 @@ int dcp_backlight_register(struct apple_dcp *dcp) { struct device *dev = dcp->dev; struct backlight_device *bl_dev; + int ret; unsigned long flags; struct backlight_properties props = { .type = BACKLIGHT_PLATFORM, @@ -588,10 +768,25 @@ int dcp_backlight_register(struct apple_dcp *dcp) if (!dcp->brightness.maximum || dcp->brightness.maximum > INT_MAX) return -ERANGE; props.max_brightness = dcp->brightness.maximum; - if (!props.brightness) - props.brightness = min_t(u32, DCP_DESKTOP_DEFAULT_NITS, - props.max_brightness); - dcp->brightness.nits = props.brightness; + /* Match the Neo's reporting default without presenting a guess. */ + props.brightness = props.max_brightness / 2; + spin_lock_irqsave(&dcp->backlight.lock, flags); + if (!dcp->backlight.state.ready) { + int ret = dcp_bl_init(&dcp->backlight.state, props.max_brightness, + dcp->desktop->inherited_brightness_valid, + dcp->desktop->inherited_brightness, + true, props.brightness); + + if (ret) { + spin_unlock_irqrestore(&dcp->backlight.lock, flags); + return ret; + } + dcp->backlight.state.fade = true; + dcp->backlight.state.preserve_inherited = true; + dcp->backlight.kick = dcp_backlight_desktop_kick; + } + props.brightness = dcp->backlight.state.target; + spin_unlock_irqrestore(&dcp->backlight.lock, flags); } bl_dev = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, @@ -599,7 +794,16 @@ int dcp_backlight_register(struct apple_dcp *dcp) if (IS_ERR(bl_dev)) return PTR_ERR(bl_dev); - dcp->brightness.bl_dev = bl_dev; + /* LIFO devres order drains event work before unregistering the backlight. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + ret = devm_add_action_or_reset(dev, dcp_backlight_notify_release, dcp); + if (ret) + return ret; + } + WRITE_ONCE(dcp->brightness.bl_dev, bl_dev); + /* A hint may have arrived between the property snapshot and registration. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp_backlight_sync_hint(dcp); if (!dcp_backlight_active(dcp) && dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits); diff --git a/drivers/gpu/drm/apple/dcp_backlight.h b/drivers/gpu/drm/apple/dcp_backlight.h index 92ac07b2e9d9d..ad8ca6d5623fc 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.h +++ b/drivers/gpu/drm/apple/dcp_backlight.h @@ -22,10 +22,14 @@ struct dcp_backlight_state { u32 fade_from; u32 actual; u32 sent_nits; + /* Firmware observations confirm output; they never become user requests. */ + u32 reported; u64 sequence; u8 retries; bool ready; bool controlled; + /* An unchanged registration default cannot take over an unknown level. */ + bool preserve_inherited; bool dpms_off; bool core_blank; bool suspended; @@ -41,6 +45,7 @@ struct dcp_backlight_state { * without a loader report is not known until a present carries a level. */ bool level_known; + bool report_valid; }; struct dcp_backlight_present { @@ -86,8 +91,10 @@ static inline int dcp_bl_init(struct dcp_backlight_state *state, u32 maximum, .target = nits, .level = nits, .actual = nits, + .reported = nits, .ready = true, .level_known = inherited_valid, + .report_valid = inherited_valid, }; return 0; } @@ -107,6 +114,30 @@ static inline bool dcp_bl_seed(struct dcp_backlight_state *state, u32 nits) return true; } +/* + * A report can establish takeover once. After Linux has a level or target, + * keep the observation separate from target, fade progress and completion. + * In particular, delayed reports cannot restart a fade or request a resend. + */ +static inline bool dcp_bl_report(struct dcp_backlight_state *state, u32 nits) +{ + if (!state->ready || nits > state->maximum) + return false; + state->reported = nits; + state->report_valid = true; + if (state->controlled || state->level_known) + return false; + return dcp_bl_seed(state, nits); +} + +/* Report a usable target until a hint or completed present gives the level. */ +static inline int dcp_bl_actual(const struct dcp_backlight_state *state) +{ + if (!state->ready) + return -ENODATA; + return state->level_known ? state->actual : state->target; +} + static inline bool dcp_bl_lit(const struct dcp_backlight_state *state) { return !state->dpms_off && !state->core_blank && !state->suspended; @@ -139,6 +170,20 @@ static inline int dcp_bl_request(struct dcp_backlight_state *state, u32 nits, return -ENODATA; if (nits > state->maximum) return -ERANGE; + /* Save/restore clients may echo the reporting default without a change. */ + if (state->preserve_inherited && !state->controlled && !state->level_known && + state->target == nits) { + state->core_blank = core_blank; + state->suspended = suspended; + return 0; + } + /* Core blank/suspend notifications do not establish a panel level. */ + if (!state->controlled && !state->level_known && state->target == nits && + (state->core_blank != core_blank || state->suspended != suspended)) { + state->core_blank = core_blank; + state->suspended = suspended; + return 0; + } state->controlled = true; state->retries = 0; /* @@ -150,7 +195,8 @@ static inline int dcp_bl_request(struct dcp_backlight_state *state, u32 nits, state->target = nits; state->core_blank = core_blank; state->suspended = suspended; - if (state->fade && state->level_known && was_lit && + /* With no hint, the reporting target is the first fade's origin. */ + if (state->fade && (state->level_known || state->preserve_inherited) && was_lit && dcp_bl_lit(state) && state->level != nits) { state->fade_from = state->level; state->fading = true; @@ -272,8 +318,57 @@ static inline unsigned int dcp_bl_retry_delay(const struct dcp_backlight_state * return 100U << (state->retries - 1); } +/* A desktop present can complete before its submit reply arrives. */ +struct dcp_backlight_swap { + struct dcp_backlight_present present; + u32 id; + bool pending; + bool accepted; + bool completed; +}; + +static inline int dcp_bl_swap_prepare(struct dcp_backlight_state *state, + struct dcp_backlight_swap *swap, + u32 id, bool have_surface) +{ + struct dcp_backlight_present present; + int ret = dcp_bl_prepare(state, have_surface, &present); + + if (ret) + return ret; + *swap = (struct dcp_backlight_swap) { + .present = present, + .id = id, + .pending = true, + }; + return 0; +} + +static inline bool dcp_bl_swap_finish(struct dcp_backlight_state *state, + struct dcp_backlight_swap *swap, + u32 id, bool reply, bool accepted) +{ + if (!swap->pending || swap->id != id) + return false; + if (reply) + swap->accepted = accepted; + else + swap->completed = true; + if ((!reply || accepted) && !(swap->accepted && swap->completed)) + return false; + swap->pending = false; + return dcp_bl_complete(state, swap->present.sequence, + swap->accepted && swap->completed); +} + struct apple_dcp; +void dcp_backlight_desktop_power(struct apple_dcp *dcp, bool on); +bool dcp_backlight_desktop_prepare(struct apple_dcp *dcp, u32 id, + bool have_surface, u32 *nits); +void dcp_backlight_desktop_finish(struct apple_dcp *dcp, u32 id, + bool reply, bool accepted); + /* * kick only queues work; it must not submit a top-level RPC from the caller. * The central IOMFB queue owns surface references, retries, and wire encoding. diff --git a/drivers/gpu/drm/apple/dcp_backlight_test.c b/drivers/gpu/drm/apple/dcp_backlight_test.c index 70379571a5dd3..c8a4d31bde2af 100644 --- a/drivers/gpu/drm/apple/dcp_backlight_test.c +++ b/drivers/gpu/drm/apple/dcp_backlight_test.c @@ -292,6 +292,70 @@ static void backlight_fallback_test(struct kunit *test) KUNIT_EXPECT_EQ(test, present.nits, 262U); } +static void backlight_unknown_takeover_test(struct kunit *test) +{ + struct dcp_backlight_state state; + struct dcp_backlight_present present; + + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + state.preserve_inherited = true; + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 300); + /* Core suspend/blank and resume do not turn the guess into a write. */ + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, true, true), 0); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); + KUNIT_EXPECT_FALSE(test, state.controlled); + KUNIT_EXPECT_FALSE(test, state.dirty); + KUNIT_EXPECT_EQ(test, dcp_bl_prepare(&state, true, &present), -EALREADY); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 300); + + /* A late loader hint supplies the inherited level without a present. */ + KUNIT_EXPECT_TRUE(test, dcp_bl_seed(&state, 250)); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 250); + KUNIT_EXPECT_FALSE(test, state.dirty); + + /* Echoing the unknown registration value leaves the panel alone. */ + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + state.preserve_inherited = true; + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); + KUNIT_EXPECT_FALSE(test, state.controlled); + KUNIT_EXPECT_EQ(test, dcp_bl_prepare(&state, true, &present), -EALREADY); + /* A new target is reported even before its present completes. */ + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 250, false, false), 0); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, false)); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 250); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 250); +} + +static void backlight_first_change_fades_test(struct kunit *test) +{ + struct dcp_backlight_state state; + struct dcp_backlight_present present; + + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + state.preserve_inherited = true; + state.fade = true; + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 300); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 300, false, false), 0); + KUNIT_EXPECT_FALSE(test, state.dirty); + KUNIT_EXPECT_FALSE(test, state.controlled); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 100, false, false), 0); + KUNIT_EXPECT_TRUE(test, state.fading); + KUNIT_EXPECT_FALSE(test, state.level_known); + KUNIT_EXPECT_FALSE(test, state.dirty); + KUNIT_EXPECT_EQ(test, state.fade_from, 300U); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 100); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_EQ(test, state.level, 200U); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); + KUNIT_EXPECT_EQ(test, dcp_bl_actual(&state), 200); + KUNIT_EXPECT_FALSE(test, dcp_bl_fade_step(&state, 250, 250)); + KUNIT_EXPECT_EQ(test, state.level, 100U); +} + static void backlight_fallback_same_level_test(struct kunit *test) { struct dcp_backlight_state state; @@ -501,7 +565,76 @@ static void backlight_fade_blank_test(struct kunit *test) KUNIT_EXPECT_EQ(test, dcp_bl_effective(&state), 400U); } +static void backlight_desktop_swap_test(struct kunit *test) +{ + struct dcp_backlight_state state; + struct dcp_backlight_swap swap = {}; + + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, true, 300, false, 0), 0); + state.fade = true; + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 600, false, false), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_EQ(test, state.level, 450U); + KUNIT_ASSERT_EQ(test, dcp_bl_swap_prepare(&state, &swap, 42, true), 0); + /* A completion alone must not publish an unaccepted panel level. */ + KUNIT_EXPECT_FALSE(test, dcp_bl_swap_finish(&state, &swap, 42, false, true)); + KUNIT_EXPECT_EQ(test, state.actual, 300U); + KUNIT_EXPECT_FALSE(test, dcp_bl_swap_finish(&state, &swap, 41, true, true)); + KUNIT_EXPECT_TRUE(test, dcp_bl_swap_finish(&state, &swap, 42, true, true)); + KUNIT_EXPECT_EQ(test, state.actual, 450U); + KUNIT_EXPECT_FALSE(test, dcp_bl_swap_finish(&state, &swap, 42, true, false)); + KUNIT_EXPECT_FALSE(test, dcp_bl_fade_step(&state, 250, 250)); + KUNIT_ASSERT_EQ(test, dcp_bl_swap_prepare(&state, &swap, 43, true), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_swap_finish(&state, &swap, 43, true, false)); + KUNIT_EXPECT_EQ(test, state.actual, 450U); + KUNIT_EXPECT_EQ(test, dcp_bl_retry_delay(&state), 100U); + KUNIT_ASSERT_EQ(test, dcp_bl_swap_prepare(&state, &swap, 44, true), 0); + KUNIT_EXPECT_FALSE(test, dcp_bl_swap_finish(&state, &swap, 44, true, true)); + KUNIT_EXPECT_TRUE(test, dcp_bl_swap_finish(&state, &swap, 44, false, true)); + KUNIT_EXPECT_EQ(test, state.actual, 600U); + /* A lit zero is a valid request, with the same fade as the Neo. */ + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 0, false, false), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_EQ(test, state.level, 300U); + KUNIT_EXPECT_FALSE(test, dcp_bl_fade_step(&state, 250, 250)); + KUNIT_EXPECT_EQ(test, state.level, 0U); +} + +static void backlight_reports_keep_target_test(struct kunit *test) +{ + struct dcp_backlight_state state; + struct dcp_backlight_present present; + + KUNIT_ASSERT_EQ(test, dcp_bl_init(&state, 600, false, 0, true, 300), 0); + state.fade = true; + KUNIT_EXPECT_TRUE(test, dcp_bl_report(&state, 100)); + KUNIT_EXPECT_EQ(test, state.actual, 100U); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 600, false, false), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_FALSE(test, dcp_bl_report(&state, 50)); + KUNIT_EXPECT_EQ(test, state.target, 600U); + KUNIT_EXPECT_EQ(test, state.level, 350U); + KUNIT_EXPECT_EQ(test, state.actual, 100U); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_FALSE(test, dcp_bl_report(&state, 600)); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); + KUNIT_EXPECT_EQ(test, state.actual, 350U); + KUNIT_ASSERT_EQ(test, dcp_bl_request(&state, 500, false, false), 0); + KUNIT_EXPECT_FALSE(test, dcp_bl_report(&state, 100)); + KUNIT_EXPECT_TRUE(test, dcp_bl_fade_step(&state, 125, 250)); + KUNIT_EXPECT_EQ(test, state.level, 425U); + KUNIT_EXPECT_FALSE(test, dcp_bl_fade_step(&state, 250, 250)); + KUNIT_ASSERT_EQ(test, dcp_bl_prepare(&state, true, &present), 0); + KUNIT_EXPECT_TRUE(test, dcp_bl_complete(&state, present.sequence, true)); + KUNIT_EXPECT_FALSE(test, dcp_bl_report(&state, 200)); + KUNIT_EXPECT_EQ(test, state.reported, 200U); + KUNIT_EXPECT_EQ(test, state.target, 500U); + KUNIT_EXPECT_EQ(test, state.actual, 500U); + KUNIT_EXPECT_FALSE(test, state.dirty); +} + static struct kunit_case backlight_cases[] = { + KUNIT_CASE(backlight_reports_keep_target_test), KUNIT_CASE(backlight_millinits_takeover_test), KUNIT_CASE(backlight_takeover_test), KUNIT_CASE(backlight_range_test), @@ -513,8 +646,11 @@ static struct kunit_case backlight_cases[] = { KUNIT_CASE(backlight_inflight_dpms_test), KUNIT_CASE(backlight_resend_test), KUNIT_CASE(backlight_fallback_test), + KUNIT_CASE(backlight_unknown_takeover_test), + KUNIT_CASE(backlight_first_change_fades_test), KUNIT_CASE(backlight_fallback_same_level_test), KUNIT_CASE(backlight_seed_known_test), + KUNIT_CASE(backlight_desktop_swap_test), KUNIT_CASE(backlight_fade_test), KUNIT_CASE(backlight_fade_restart_test), KUNIT_CASE(backlight_fade_blank_test), diff --git a/drivers/gpu/drm/apple/dcp_dropped_swap_test.c b/drivers/gpu/drm/apple/dcp_dropped_swap_test.c index b45010d38fac1..cd11dbc374aa8 100644 --- a/drivers/gpu/drm/apple/dcp_dropped_swap_test.c +++ b/drivers/gpu/drm/apple/dcp_dropped_swap_test.c @@ -497,7 +497,29 @@ static void retired_completion_in_flight_test(struct kunit *test) KUNIT_EXPECT_TRUE(test, READ_ONCE(dcp->crashed)); } +static void desktop_present_completion_test(struct kunit *test) +{ + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, dcp); + dcp->desktop = kunit_kzalloc(test, sizeof(*dcp->desktop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dcp->desktop); + dcp->desktop->present_id = 42; + dcp->desktop->present_pending = true; + KUNIT_EXPECT_FALSE(test, dcp_desktop_present_complete(dcp, 41)); + KUNIT_EXPECT_TRUE(test, dcp->desktop->present_pending); + KUNIT_EXPECT_TRUE(test, dcp_desktop_present_complete(dcp, 42)); + KUNIT_EXPECT_FALSE(test, dcp_desktop_present_complete(dcp, 42)); + /* A late completion cannot claim the replacement's event. */ + dcp->desktop->present_id = 43; + dcp->desktop->present_pending = true; + KUNIT_EXPECT_FALSE(test, dcp_desktop_present_complete(dcp, 42)); + KUNIT_EXPECT_TRUE(test, dcp->desktop->present_pending); + KUNIT_EXPECT_TRUE(test, dcp_desktop_present_complete(dcp, 43)); +} + static struct kunit_case dropped_swap_cases[] = { + KUNIT_CASE(desktop_present_completion_test), KUNIT_CASE(dropped_present_retired_test), KUNIT_CASE(dropped_present_buffers_test), KUNIT_CASE(completed_present_kept_test), diff --git a/drivers/gpu/drm/apple/dcp_external_announce_test.c b/drivers/gpu/drm/apple/dcp_external_announce_test.c index bd0af843698f9..a1bd2dcd4b2ab 100644 --- a/drivers/gpu/drm/apple/dcp_external_announce_test.c +++ b/drivers/gpu/drm/apple/dcp_external_announce_test.c @@ -111,8 +111,11 @@ static struct announce_test *announce_test_init(struct kunit *test) INIT_DELAYED_WORK(&dcp->typec_relink_wq, announce_test_noop); INIT_DELAYED_WORK(&dcp->hdmi_settle_wq, announce_test_noop); INIT_DELAYED_WORK(&dcp->hdmi_recheck_wq, announce_test_noop); + INIT_DELAYED_WORK(&dcp->link_recovery_wq, announce_test_noop); + INIT_DELAYED_WORK(&dcp->hybrid_wq, announce_test_noop); INIT_DELAYED_WORK(&dcp->bl_fallback_wq, announce_test_noop); INIT_DELAYED_WORK(&dcp->backlight.fade_work, announce_test_noop); + INIT_DELAYED_WORK(&dcp->backlight.retry_work, announce_test_noop); INIT_WORK(&dcp->external_stop_work, announce_test_noop); INIT_DELAYED_WORK(&dcp->external_stop_retry_wq, announce_test_noop); INIT_WORK(&dcp->vblank_wq, announce_test_noop); diff --git a/drivers/gpu/drm/apple/dcp_external_relink_test.c b/drivers/gpu/drm/apple/dcp_external_relink_test.c index b9d7767be80d8..cf3dfa6d911bf 100644 --- a/drivers/gpu/drm/apple/dcp_external_relink_test.c +++ b/drivers/gpu/drm/apple/dcp_external_relink_test.c @@ -222,6 +222,7 @@ static void relink_test_exit(void *data) wait_for_completion_timeout(&t->userspace_done, 10 * HZ); disable_delayed_work_sync(&t->dcp.announce_wq); disable_work_sync(&t->con.hotplug_wq); + apple_connector_backlight_early_unregister(&t->con); drm_atomic_helper_shutdown(&t->drm); drm_framebuffer_put(&t->fb); } @@ -408,6 +409,7 @@ static struct relink_test *relink_test_init(struct kunit *test) spin_lock_init(&dcp->mode_state.lock); INIT_DELAYED_WORK(&dcp->announce_wq, dcp_announce_work); INIT_WORK(&dcp->vblank_wq, relink_test_noop); + apple_connector_backlight_init(&t->con); INIT_WORK(&t->con.hotplug_wq, dcp_hotplug); dcp->test_point = relink_test_point; dcp->typec_cable_connected = true; diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index a7620457f1f46..66196c8c5cc61 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -490,11 +490,12 @@ static int dptxport_call_get_link_rate(struct apple_epic_service *service, static int dptxport_compact_xbar_link(struct apple_epic_service *service, bool up) { struct apple_dcp *dcp = service->ep->dcp; + struct mux_control *xbar = READ_ONCE(dcp->xbar); int ret; - if (!READ_ONCE(service->ep->compact) || !dcp->xbar) + if (!READ_ONCE(service->ep->compact) || !xbar) return 0; - ret = dcp_dpxbar_link(dcp->xbar, up); + ret = dcp_dpxbar_link(xbar, up); dev_dbg(dcp->dev, "DPTXPort: crossbar link %s: %d\n", up ? "up" : "down", ret); return ret; } @@ -693,6 +694,27 @@ static int dptxport_call_set_tiled_display_hint(struct apple_epic_service *servi return 0; } +/* + * Desktop 26.6 display allocation: the 160-byte SET_TILED_DISPLAY_HINTS + * record carries the sink's size and pixel rates and takes the main and + * peer endpoint IDs back (dcp_peer_hints_26_6()). A sink wider than one + * pipe gets the idle pipe above this one as its peer. + */ +static bool dptxport_dual_pipe_alloc(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply, size_t reply_size) +{ + struct apple_dcp *dcp = service->ep->dcp; + struct dptx_port *dptx = service->cookie; + + if (data_size != 160 || reply_size < 160 || + dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) + return false; + + return dcp_peer_hints_26_6(dcp, dptx, data, data_size, + reply, reply_size) == 0; +} + static int dptxport_call_activate(struct apple_epic_service *service, const void *data, size_t data_size, @@ -878,6 +900,9 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_SET_TILED_DISPLAY_HINTS: { int ret; + if (dptxport_dual_pipe_alloc(service, data, data_size, reply, + reply_size)) + return 0; memcpy(reply, data, min(reply_size, data_size)); ret = dptxport_call_set_tiled_display_hint(service, data, data_size, reply, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index c0bef660c25ec..0d266130531ed 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "dcp.h" @@ -40,6 +41,117 @@ #include "parser.h" #include "trace.h" +struct dcp_identity_trace { + struct apple_dcp *dcp; + atomic_t *count; +}; + +static void dcp_identity_key_log(void *data, const char *key, u32 key_len, + u32 type, u32 size, u32 depth) +{ + struct dcp_identity_trace *trace = data; + struct apple_dcp *dcp = trace->dcp; + char safe[65]; + u32 i; + + if (!atomic_add_unless(trace->count, 1, 128)) + return; + for (i = 0; i < min_t(u32, key_len, sizeof(safe) - 1); i++) + safe[i] = key[i] >= 0x20 && key[i] <= 0x7e ? key[i] : '?'; + safe[i] = 0; + dev_info(dcp->dev, "panel identity depth=%u key=%s type=%u size=%u\n", + depth, safe, type, size); +} + +void dcp_trace_panel_property(struct apple_dcp *dcp, int tag, const char *key, + size_t key_len, const void *blob, size_t len) +{ + struct dcp_identity_trace trace; + u32 slot = tag == 552 ? 0 : tag == 561 ? 1 : tag == 114 ? 2 : 3; + int ret; + + if (!drm_debug_enabled(DRM_UT_KMS) || + dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp)) + return; + trace.dcp = dcp; + trace.count = &dcp->desktop->identity_log_count[slot]; + /* Periodic statistics cannot consume the panel setters' budget. */ + if (!atomic_add_unless(trace.count, 1, 128)) + return; + dev_info(dcp->dev, "panel identity callback=D%03d bytes=%zu null=%d\n", + tag, len, !blob); + dcp_identity_key_log(&trace, key, key_len, 0, len, 0); + if (!blob) + return; + ret = parse_identity_keys(blob, len, dcp_identity_key_log, &trace); + if (ret) + dev_info(dcp->dev, "panel identity inspection stopped: %d\n", ret); +} + +/* Other property setters use fixed scalar records, not serialization. */ +static void dcp_trace_panel_setter(struct apple_dcp *dcp, int tag, + const void *data, size_t len) +{ + struct dcp_identity_trace trace = { .dcp = dcp }; + const char *in = data; + u32 offset = 0, type, size, expected; + bool is_null = false; + + if (!drm_debug_enabled(DRM_UT_KMS) || + dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !dcp_has_panel(dcp)) + return; + switch (tag) { + case 414: + offset = 4; + fallthrough; + case 563: + type = 4; + size = 8; + expected = offset + 76; + break; + case 415: + offset = 4; + fallthrough; + case 565: + type = 11; + size = 4; + expected = offset + 72; + break; + case 567: + type = 9; + size = 64; + expected = 128; + break; + case 102: + type = 4; + size = 4; + expected = 68; + break; + case 104: + type = 11; + size = 1; + expected = 68; + break; + default: + return; + } + /* Inspect only the public wire shape, without changing its handler. */ + if (len != expected) + return; + if (tag == 414 || tag == 415 || tag == 563 || tag == 565) + is_null = in[offset + 64 + size] & 1; + if (tag == 567) + size = strnlen(in + 64, 64); + /* Scalar/service setters share the bounded miscellaneous-property budget. */ + trace.count = &dcp->desktop->identity_log_count[3]; + if (!atomic_add_unless(trace.count, 1, 128)) + return; + dev_info(dcp->dev, "panel identity callback=D%03d bytes=%zu null=%d\n", + tag, len, is_null); + dcp_identity_key_log(&trace, in + offset, strnlen(in + offset, 64), + type, is_null ? 0 : size, 0); +} + static int dcp_tx_offset(enum dcp_context_id id) { switch (id) { @@ -90,17 +202,6 @@ static inline u64 dcpep_ack(enum dcp_context_id id) return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK; } -/* - * Adaptive sync through IOMFBParameter 14 black-screens the J616s panel on - * the desktop 26.6 firmware: the firmware accepts the VRR mode, then no swap - * completes. Do not offer it there. - */ -static bool dcp_mode_vrr_supported(struct apple_dcp *dcp, - const struct dcp_display_mode *mode) -{ - return dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !mode->vrr; -} - /* * A channel is busy if we have sent a message that has yet to be * acked. The driver must not sent a message to a busy channel. @@ -1232,10 +1333,12 @@ void dcp_hotplug(struct work_struct *work) * callback has completed. */ ret = dcp_retrain_active_crtc(connector); - if (ret) + if (ret) { dev_warn(dcp->dev, "failed to retrain active CRTC after hotplug: %d\n", ret); + dcp_link_recovery_schedule(dcp); + } } dcp_test_point(dcp, DCP_TEST_HOTPLUG_EVENT); @@ -1276,7 +1379,8 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, u8 depth; bool handled, zero_reply; - if (dcp->fw_compat == DCP_FIRMWARE_H17P && + if ((dcp->fw_compat == DCP_FIRMWARE_H17P || + dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) && (length < sizeof(*hdr) || !ch || ch->depth >= DCP_MAX_CALL_DEPTH || offset >= 0x8000 || length > 0x8000 - offset)) { dev_err(dev, "invalid IOMFB callback envelope\n"); @@ -1284,11 +1388,35 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, return; } + /* The header is read below: check the record lies in the shared buffer first. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (length < sizeof(*hdr) || offset >= 0x8000 || length > 0x8000 - offset)) { + dev_err(dev, "invalid IOMFB callback envelope\n"); + dcp->crashed = true; + return; + } + tag = dcp_parse_tag(hdr->tag); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (!iomfb_check_callback_v26_6_desktop(tag, hdr->in_len, hdr->out_len) || + (u64)sizeof(*hdr) + hdr->in_len + hdr->out_len != length)) { + dev_err(dev, "invalid desktop IOMFB callback %d (%u, %u)\n", + tag, hdr->in_len, hdr->out_len); + dcp->crashed = true; + return; + } handled = tag >= 0 && tag < IOMFB_MAX_CB && dcp->cb_handlers && dcp->cb_handlers[tag]; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && handled && + ((u64)sizeof(*hdr) + hdr->in_len + hdr->out_len > length || + !iomfb_check_callback_v26_6_desktop(tag, hdr->in_len, hdr->out_len))) { + dev_err(dev, "short IOMFB callback %d (%u, %u, length %u)\n", + tag, hdr->in_len, hdr->out_len, length); + dcp->crashed = true; + return; + } if (dcp->fw_compat == DCP_FIRMWARE_H17P && (tag < 0 || tag >= IOMFB_MAX_CB || (u64)sizeof(*hdr) + hdr->in_len + hdr->out_len != length || @@ -1304,6 +1432,8 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, in = data + sizeof(*hdr); out = in + hdr->in_len; + dcp_trace_panel_setter(dcp, tag, in, hdr->in_len); + if (!handled) { zero_reply = dcp_external_zero_reply(dcp, tag, context, hdr->in_len, hdr->out_len); @@ -1321,11 +1451,15 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, return; } /* - * Leaving a callback unanswered wedges the coprocessor: it - * waits for the ack forever and the outer call never returns. - * On H17P answer it with a zeroed output instead. + * Drain an unsupported desktop callback too. The outer modeset + * must see an error even if firmware accepts the zero reply; + * never pop its command before its real ACK arrives. */ - if (dcp->fw_compat == DCP_FIRMWARE_H17P) { + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + READ_ONCE(dcp->desktop->modeset_active)) + WRITE_ONCE(dcp->desktop->modeset_callback_error, true); + if (dcp->fw_compat == DCP_FIRMWARE_H17P || + dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { if (hdr->out_len) memset(out, 0, hdr->out_len); depth = dcp_push_depth(&ch->depth); @@ -1606,7 +1740,9 @@ int dcp_modes_replace(struct apple_dcp *dcp, struct dcp_parse_ctx *ctx, ret = parse_array_count(ctx, &entries); if (ret) return ret; + ctx->metadata_timing = dcp_firmware_metadata_enabled(dcp); } + ctx->zero_vrr_range_fallback = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP; modes = enumerate_modes(ctx, &nr_modes, dcp->width_mm, dcp->height_mm, dcp->notch_height, dcp->fixed_connector_type == DRM_MODE_CONNECTOR_eDP, @@ -1907,8 +2043,11 @@ int dcp_get_modes(struct drm_connector *connector) } } else { u64 generation = 0; + bool panel = dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + dcp_has_panel(dcp); const struct drm_edid *derived = nr_modes ? - dcp_firmware_edid(dcp, &generation) : ERR_PTR(-ENODATA); + (panel ? dcp_panel_edid_snapshot(dcp, modes, nr_modes, &generation) : + dcp_firmware_edid(dcp, &generation)) : ERR_PTR(-ENODATA); if (!IS_ERR(derived)) { bool log = !apple_connector->drm_edid_firmware_derived || @@ -1919,16 +2058,21 @@ int dcp_get_modes(struct drm_connector *connector) apple_connector->drm_edid_generation = generation; update = drm_edid_connector_update(connector, derived); drm_edid_free(derived); - if (update || !dcp_firmware_metadata_current(dcp, generation)) { + if (update || !(panel ? dcp_panel_metadata_current(dcp, generation) : + dcp_firmware_metadata_current(dcp, generation))) { drm_edid_connector_update(connector, NULL); apple_connector->drm_edid_firmware_derived = false; } else if (log) { dev_info(dcp->dev, "using firmware-derived EDID metadata for connection %llu\n", generation); } - } else if (apple_connector->drm_edid_firmware_derived) { - drm_edid_connector_update(connector, NULL); - apple_connector->drm_edid_firmware_derived = false; + } else { + if (panel && nr_modes) + dev_dbg(dcp->dev, "panel EDID generation failed: %pe\n", derived); + if (apple_connector->drm_edid_firmware_derived) { + drm_edid_connector_update(connector, NULL); + apple_connector->drm_edid_firmware_derived = false; + } } } @@ -1947,10 +2091,8 @@ int dcp_get_modes(struct drm_connector *connector) continue; published_180hz |= modes[i].automatic_rgb8_quirk; published++; - if (!dcp_mode_vrr_supported(dcp, &modes[i])) - continue; if (modes[i].vrr) { - u16 lo = modes[i].min_vrr >> 16; + u16 lo = DIV_ROUND_UP(modes[i].min_vrr, 1U << 16); u16 hi = modes[i].max_vrr >> 16; if (!min_vfreq || lo < min_vfreq) @@ -1982,6 +2124,10 @@ int dcp_get_modes(struct drm_connector *connector) connector->display_info.monitor_range.max_vfreq = max_vfreq; } + /* EDID base dimensions are rounded to cm; the panel's OF/ADT size is exact. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && dcp_has_panel(dcp)) + dcp_update_connector_dimensions(dcp, connector); + return published; } @@ -1998,7 +2144,8 @@ static int dcp_desktop_modeset(struct apple_dcp *dcp, if (d->trial_modeset_ok) { d->trial_modeset_ok = false; - if (d->mode_live && lookup_mode(dcp, &crtc_state->mode, &matched) && + if (!crtc_state->vrr_enabled && d->mode_live && + lookup_mode(dcp, &crtc_state->mode, &matched) && matched.timing_mode_id == d->trial_timing_mode_id && matched.color_mode_id == d->trial_color_mode_id) { dev_dbg(dcp->dev, "keeping the takeover mode " DRM_MODE_FMT "\n", diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 7e6e16f1dddf7..7c60a81207ad7 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -212,6 +212,8 @@ bool iomfb_validate_callback_h17p(int tag, u32 in_len, u32 out_len); struct apple_dcp; bool iomfb_check_callback_h17p(struct apple_dcp *dcp, int tag, u32 in_len, u32 out_len); +/* Desktop 26.6: does the record hold what the tag's handler reads and writes? */ +bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len); typedef bool (*iomfb_cb_handler)(struct apple_dcp *, int, void *, void *); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 246498a8e843c..9d248cccf72da 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -32,6 +32,7 @@ #include "iomfb.h" #include "iomfb_internal.h" #include "parser.h" +#include "dcp-refresh.h" #include "dcp-property.h" #include "trace.h" #include "version_utils.h" @@ -425,6 +426,10 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, ktime_t now = ktime_get(); bool retire = true; +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (dcp_peer_swap_complete_26_6(dcp, resp->swap_id)) + return; +#endif #if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G && !dcp_present_complete_h17p(&dcp->present_state_h17p, resp->swap_id)) { @@ -458,9 +463,16 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, } if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) retire = iomfb_present_complete_h17p(dcp); +#endif +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* Late or duplicate completions cannot retire the replacement's flip. */ + if (!dcp_desktop_present_complete(dcp, resp->swap_id)) + return; + dcp_backlight_desktop_finish(dcp, resp->swap_id, false, true); #endif trace_iomfb_swap_complete(dcp, resp->swap_id); dcp->last_swap_id = resp->swap_id; + dcp->have_swap_complete = true; dcp_swap_watchdog_complete(dcp); if (retire) { dcp_release_retained_framebuffers(dcp, resp->swap_id); @@ -1134,7 +1146,11 @@ dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, u8 *enabled) { -#if DCP_FW_H17(DCP_FW_VER) +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (dcp_has_panel(dcp) && dcp_backlight_resend(dcp)) + schedule_work(&dcp->bl_update_wq); + return; +#elif DCP_FW_H17(DCP_FW_VER) /* H17P carries the level only in presents; ask for one more. */ if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { if (dcp_has_panel(dcp) && dcp_backlight_active(dcp) && @@ -1248,11 +1264,20 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, } /* Validate serialized extent outside the metadata spinlock. */ - if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) + if (dcp_firmware_metadata_enabled(dcp)) dcp->chunks.payload_complete = dcp->chunks.contiguous && dcp_property_complete(dcp->chunks.data, dcp->chunks.received, dcp->chunks.length); + dcp_trace_panel_property(dcp, 0, req->key, + strnlen(req->key, sizeof(req->key)), + dcp->chunks.data, dcp->chunks.length); + + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp_panel_edid_receive(dcp, dcp->chunks.data, dcp->chunks.length); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (!strcmp(req->key, "EDID") || !strcmp(req->key, "edid"))) + return true; ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); if (ret) { @@ -1304,11 +1329,20 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, return false; } - if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) { + if (dcp_firmware_metadata_enabled(dcp)) { struct dcp_display_metadata metadata; ret = parse_display_metadata(&metadata_ctx, &metadata); + if (ret) + dev_dbg(dcp->dev, "DisplayAttributes without sink identity: %d\n", + ret); dcp_firmware_metadata_attributes(dcp, ret ? NULL : &metadata); + } else if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + dcp_has_panel(dcp)) { + struct dcp_display_metadata metadata; + + ret = parse_panel_metadata(&metadata_ctx, &metadata); + dcp_panel_metadata_replace(dcp, ret ? NULL : &metadata); } } @@ -1734,6 +1768,9 @@ static bool dcp_present_begin(struct apple_dcp *dcp, u32 swap_id) dcp->crashed = true; return false; } +#elif DCP_FW_DESKTOP_26(DCP_FW_VER) + WRITE_ONCE(dcp->desktop->present_id, swap_id); + WRITE_ONCE(dcp->desktop->present_pending, true); #endif return true; } @@ -1820,6 +1857,12 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) if (ret) status = ~0U; +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (status && swap_id == READ_ONCE(dcp->desktop->present_id)) + WRITE_ONCE(dcp->desktop->present_pending, false); + dcp_backlight_desktop_finish(dcp, swap_id, true, !status); +#endif + if (status) { /* An aborted present has already been reported. */ if (ret != -ECANCELED) @@ -2033,6 +2076,8 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(10000)); #if DCP_FW_DESKTOP_26(DCP_FW_VER) dcp->desktop->poweron_result = ret > 0 ? 0 : ret == 0 ? 1 : 2; + if (ret > 0) + dcp_backlight_desktop_power(dcp, true); #endif if (ret == 0) { @@ -2141,6 +2186,15 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) return; #endif +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* A merged pair stops as a pair; its clear swap would not complete. */ + ret = dcp_peer_stop_26_6(dcp); + if (ret < 0) + dev_err(dcp->dev, "paired power-off failed: %d\n", ret); + if (ret > 0) + goto peer_stopped; +#endif + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) return; @@ -2154,6 +2208,9 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G) dcp_prepare_clear_swap(dcp, swap); #else +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + dcp_backlight_desktop_power(dcp, false); +#endif dcp_prepare_clear_swap(dcp, swap); #endif @@ -2177,6 +2234,9 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) dev_dbg(dcp->dev, "%s: clear swap submitted: %u\n", __func__, swap_id); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +peer_stopped: +#endif poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); if (!poff_cookie) return; @@ -2269,6 +2329,12 @@ void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) return; #endif +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + ret = dcp_peer_stop_26_6(dcp); + if (ret < 0) + dev_err(dcp->dev, "paired sleep failed: %d\n", ret); +#endif + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) return; @@ -2385,6 +2451,24 @@ dcpep_cb_hotplug_v26_6(struct apple_dcp *dcp, struct dcp_hotplug_req_v26_6 *req) { struct dcp_hotplug_resp_v26_6 resp = { 0 }; u64 connected = req->connected; + const u8 *tiled = req->opaque; + bool tiled_valid = !(req->opaque[sizeof(resp.opaque)] & 1); + bool paired = READ_ONCE(dcp->peer_reserved) || READ_ONCE(dcp->peer_owner); + + /* + * With a peer reserved, the record names this pipe's and the related + * pipe's display at 0x3b and 0x47, as on firmware 27; it is echoed + * back as that firmware's client does. + */ + if (paired) { + dev_dbg(dcp->dev, "peer HPD connected=%llu null=%u rec=%*ph\n", + connected, req->opaque[sizeof(resp.opaque)], + (int)sizeof(resp.opaque), tiled); + if (tiled_valid && connected && + !(get_unaligned_le32(tiled + 0xc) & BIT(3))) + dcp_peer_display_ids(dcp, get_unaligned_le32(tiled + 0x3b), + get_unaligned_le32(tiled + 0x47)); + } /* A display that went away is bootstrapped again on its next mode. */ if (!dcp->main_display && @@ -2397,7 +2481,9 @@ dcpep_cb_hotplug_v26_6(struct apple_dcp *dcp, struct dcp_hotplug_req_v26_6 *req) dcpep_cb_hotplug(dcp, &connected); - if (connected) + if (paired && tiled_valid) + memcpy(resp.opaque, tiled, sizeof(resp.opaque)); + else if (connected) memset(&resp.opaque[0x47], 0xff, sizeof(u32)); return resp; @@ -2455,12 +2541,42 @@ static struct dcpep_get_tiling_state_resp dcpep_cb_get_tiling_state(struct apple_dcp *dcp, struct dcpep_get_tiling_state_req *req) { +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* D115 reads the tiling state of a paired pipe; D116 writes it. */ + u32 value = req->value; + bool handled = dcp_peer_tiling_state_26_6(dcp, false, req->event, + req->param, &value); + + dev_dbg(dcp->dev, "peer tiling get event %u param %u value %u handled %d\n", + req->event, req->param, value, handled); + if (handled) + return (struct dcpep_get_tiling_state_resp){ + .value = value, + .ret = 1, + }; +#endif return (struct dcpep_get_tiling_state_resp){ .value = 0, .ret = 1, }; } +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +/* D116 set_tiling_state: the D115 record in, a boolean (padded to 4) out. */ +static u32 dcpep_cb_set_tiling_state_v26_6(struct apple_dcp *dcp, + struct dcpep_get_tiling_state_req *req) +{ + u32 value = req->value; + bool handled = !req->value_null && + dcp_peer_tiling_state_26_6(dcp, true, req->event, + req->param, &value); + + dev_dbg(dcp->dev, "peer tiling set event %u param %u value %u null %u handled %d\n", + req->event, req->param, req->value, req->value_null, handled); + return handled; +} +#endif + static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp) { #if DCP_FW_H17(DCP_FW_VER) @@ -2548,6 +2664,10 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +TRAMPOLINE_INOUT(trampoline_set_tiling_state_v26_6, dcpep_cb_set_tiling_state_v26_6, + struct dcpep_get_tiling_state_req, u32); +#endif TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8); /* @@ -2566,6 +2686,12 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) if (ret) status = ~0U; +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (status && swap_id == READ_ONCE(dcp->desktop->present_id)) + WRITE_ONCE(dcp->desktop->present_pending, false); + dcp_backlight_desktop_finish(dcp, swap_id, true, !status); +#endif + if (status) { /* An aborted present has already been reported. */ if (ret != -ECANCELED) @@ -2601,8 +2727,23 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) if (dcp_present_retires(dcp)) dcp_arm_retained_framebuffers(dcp, resp->swap_id); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (dcp_has_panel(dcp)) { + u32 nits; + bool have_surface = false; + + for (unsigned int i = 0; i < SWAP_SURFACES; i++) + have_surface |= !DCP_FW_SWAP(dcp).surf_null[i]; + if (dcp_backlight_desktop_prepare(dcp, resp->swap_id, + have_surface, &nits)) + dcp_swap_set_brightness(&DCP_FW_SWAP(dcp).swap, nits); + } +#endif trace_iomfb_swap_submit(dcp, resp->swap_id); dcp_h17p_prepare_swap(dcp); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + dcp_peer_swap_submit_26_6(dcp, &DCP_FW_SWAP(dcp), resp->swap_id); +#endif dcp_swap_submit(dcp, false, &DCP_FW_SWAP(dcp), dcp_swapped, NULL); } @@ -2808,8 +2949,19 @@ struct dcp_modeset_cookie { struct dcp_set_digital_out_mode_req actual; struct dcp_set_digital_out_mode_req bootstrap; bool use_bootstrap; + /* Adaptive sync is programmed on (a nonzero minimum rate). */ + bool vrr; }; +/* Finish only after the RPC which contained the unknown callback has replied. */ +static bool dcp_modeset_callback_failed(struct apple_dcp *dcp, void *cookie) +{ + if (!READ_ONCE(dcp->desktop->modeset_callback_error)) + return false; + complete_set_digital_out_mode(dcp, NULL, cookie); + return true; +} + /* * get_mode_info (A364) fills the firmware's mode record for a timing and * color pair before set_digital_out_mode (A413) selects it. The request @@ -2834,17 +2986,27 @@ static void dcp_get_mode_info(struct apple_dcp *dcp, sizeof(struct dcp_get_mode_info_resp_v26_6), &req, cb, cookie); } +static void dcp_modeset_actual_done(struct apple_dcp *dcp, void *data, + void *cookie) +{ + if (dcp_modeset_callback_failed(dcp, cookie)) + return; + complete_set_digital_out_mode(dcp, data, cookie); +} + static void dcp_modeset_after_actual_info(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_modeset_cookie *modeset = cookie; + if (dcp_modeset_callback_failed(dcp, cookie)) + return; if (!data) { complete_set_digital_out_mode(dcp, NULL, cookie); return; } dcp_set_digital_out_mode(dcp, false, &modeset->actual, - complete_set_digital_out_mode, cookie); + dcp_modeset_actual_done, cookie); } static void dcp_modeset_after_bootstrap(struct apple_dcp *dcp, void *data, @@ -2852,8 +3014,10 @@ static void dcp_modeset_after_bootstrap(struct apple_dcp *dcp, void *data, { struct dcp_modeset_cookie *modeset = cookie; + if (dcp_modeset_callback_failed(dcp, cookie)) + return; if (!data || get_unaligned_le32(data)) { - complete_set_digital_out_mode(dcp, data, cookie); + dcp_modeset_actual_done(dcp, data, cookie); return; } dcp_get_mode_info(dcp, &modeset->actual, dcp_modeset_after_actual_info, @@ -2865,6 +3029,8 @@ static void dcp_modeset_after_bootstrap_info(struct apple_dcp *dcp, void *data, { struct dcp_modeset_cookie *modeset = cookie; + if (dcp_modeset_callback_failed(dcp, cookie)) + return; if (!data) { complete_set_digital_out_mode(dcp, NULL, cookie); return; @@ -2897,6 +3063,9 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, if (dcp->crashed) return -ENODEV; + /* A timed-out command still owns its channel until firmware replies. */ + if (READ_ONCE(dcp->ch_cmd.depth)) + return -EBUSY; if (!lookup_mode(dcp, &crtc_state->mode, mode)) { dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n", @@ -2927,11 +3096,43 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, .timing_mode_id = mode->timing_mode_id }; + /* VRR modes need swap timestamps, see iomfb_flush(); ProMotion fixed 120 too. */ + dcp->use_timestamps = mode->needs_timestamps; + + /* A display within one pipe may let the other input have a pipe again. */ + { + bool narrow = crtc_state->mode.hdisplay <= DCP_DESKTOP_PIPE_MAX_WIDTH; + + WRITE_ONCE(dcp->typec_narrow, narrow); + if (narrow) + dcp_fabric_hybrid_kick(dcp, 1000); + } + /* - * Suspected VRR modes need swap timestamps, see iomfb_flush(). - * Adaptive sync itself stays off, see dcp_mode_vrr_supported(). + * A mode wider than one pipe goes to both pipes of a reserved pair, + * with adaptive sync set the same on both. The pair takes the mode's + * VRR range from its first install, whatever the CRTC asks: switching + * VRR on later would stop and install the pair again, and an install + * that fails (the sink dropped its link meanwhile) leaves the display + * dark. Swaps keep their timestamps, so the compositor still paces it. */ - dcp->use_timestamps = mode->vrr; + if (!dcp->main_display) { + dev_dbg(dcp->dev, "modeset %ux%u vrr %u min %#x enabled %u\n", + crtc_state->mode.hdisplay, crtc_state->mode.vdisplay, + mode->vrr, mode->min_vrr, crtc_state->vrr_enabled); + ret = dcp_peer_drm_modeset_26_6(dcp, crtc_state->mode.hdisplay, + mode->timing_mode_id, + mode->color_mode_id, + mode->vrr ? mode->min_vrr : 0); + if (ret != -EOPNOTSUPP) { + dcp->desktop->mode_live = !ret; + /* The pair runs with the mode's adaptive-sync rate. */ + dcp->vrr_enabled = !ret && mode->vrr; + if (!ret) + atomic_inc(&dcp->modesets_done); + return ret; + } + } /* * The firmware changes a fixed panel's mode through the swap geometry, @@ -2949,6 +3150,9 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, return -ENOMEM; cookie = &modeset->wait; modeset->actual = dcp->mode; + /* After an adaptive swap timed out (dcp_swap_watchdog), stay fixed. */ + modeset->vrr = mode->vrr && crtc_state->vrr_enabled && + !READ_ONCE(dcp->desktop->vrr_failed); cookie->status = ~0U; init_completion(&cookie->done); @@ -2971,7 +3175,26 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, modeset->bootstrap.timing_mode_id); } - dcp_modeset_select(dcp, NULL, cookie); + WRITE_ONCE(dcp->desktop->modeset_callback_error, false); + WRITE_ONCE(dcp->desktop->modeset_active, true); + /* + * Adaptive sync (IOMFBParameter 14, minimum rate in 16.16 Hz, 0 = off) + * goes before the mode is selected; the firmware then reports the mode + * set with VRR on. A fixed mode after adaptive sync was on turns it + * off explicitly. + */ + if (mode->vrr || dcp->vrr_enabled) { + struct dcp_set_parameter_dcp param = { + .param = IOMFBPARAM_ADAPTIVE_SYNC, + .value = { modeset->vrr ? mode->min_vrr : 0 }, + .count = 3, + }; + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_modeset_select, + cookie); + } else { + dcp_modeset_select(dcp, NULL, cookie); + } /* * The DCP firmware has an internal timeout of ~8 seconds for @@ -2988,6 +3211,9 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, dev_err(dcp->dev, "waiting on set_digital_out_mode failed:%d\n", ret); ret = -EIO; + } else if (READ_ONCE(dcp->desktop->modeset_callback_error)) { + dev_err(dcp->dev, "unsupported callback during mode set\n"); + ret = -EOPNOTSUPP; } else if (!READ_ONCE(cookie->replied)) { dev_err(dcp->dev, "set_digital_out_mode got no reply\n"); ret = -EIO; @@ -3001,9 +3227,13 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, jiffies_to_msecs(ret)); ret = 0; } + WRITE_ONCE(dcp->desktop->modeset_active, false); + dcp->vrr_enabled = !ret && modeset->vrr; kref_put(&cookie->refcount, release_wait_cookie); dcp->desktop->mode_live = !ret; + if (!ret) + atomic_inc(&dcp->modesets_done); return ret; } #else @@ -3161,7 +3391,10 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_SWAP(dcp); int plane_idx, l; int has_surface = 0; + +#if !DCP_FW_DESKTOP_26(DCP_FW_VER) bool update_brightness; +#endif crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); @@ -3262,19 +3495,13 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru } #if DCP_FW_DESKTOP_26(DCP_FW_VER) - if (has_surface && dcp->use_timestamps) { - /* - * Timestamp types 1, 2 and 7 hold the time the swap should be - * shown, in the firmware's clock. A target long past shows it - * at the next refresh: 120 Hz in the fixed mode, as soon as - * the panel allows with adaptive sync. The architectural - * counter must not go here: after a VRR mode set the firmware - * then never shows the swap, and it holds a swap whose target - * it reads as more than about a frame ahead. - */ - req->swap.timestamp[0] = 120; - req->swap.timestamp[1] = 120; - req->swap.timestamp[6] = 120; + { + u64 deadline = dcp_desktop_present_deadline(has_surface, dcp->use_timestamps); + + /* Types 1, 2 and 7 carry the same desktop presentation deadline. */ + req->swap.timestamp[0] = deadline; + req->swap.timestamp[1] = deadline; + req->swap.timestamp[6] = deadline; } #else if (has_surface && (dcp->use_timestamps || dcp->vrr_enabled)) { @@ -3303,28 +3530,16 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* The shared ramp is encoded after swap_start, at submission time. */ + dcp->brightness.update = false; +#else /* update brightness if changed */ update_brightness = dcp_has_panel(dcp) && dcp->brightness.update; #if DCP_FW_H17(DCP_FW_VER) update_brightness = update_brightness && dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G; #endif -#if DCP_FW_DESKTOP_26(DCP_FW_VER) - /* - * The firmware publishes no initial level, so nits stays at the - * registration default until userspace writes the backlight. Sending - * that from the forced post-poweron update can turn the panel off: - * keep the level the boot loader set until one is requested. - */ - if (update_brightness && !dcp->brightness.requested) { - dcp->brightness.update = false; - update_brightness = false; - } - if (update_brightness) { - dcp_swap_set_brightness(&req->swap, dcp->brightness.nits); - dcp->brightness.update = false; - } -#else if (update_brightness) { #if DCP_FW_LAYOUT_14_7(DCP_FW_VER) req->swap.bl_update = 1; diff --git a/drivers/gpu/drm/apple/iomfb_v26_6.c b/drivers/gpu/drm/apple/iomfb_v26_6.c index 3a33ae65d2018..bf2f0c4f610c7 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6.c @@ -28,7 +28,7 @@ * Scanout is one plane, XRGB8888 or ARGB8888, linear, exactly the mode size. * Each flush is one swap, completed when the firmware reports that swap id, * and then the page-flip event is sent. The notch rows (apple,notch-height, - * unless appledrm.t6030_show_notch=1) are outside the mode and stay black. + * unless appledrm.show_notch=1) are outside the mode and stay black. */ #include diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index 98a3504a9a5f0..064eff4beff53 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -81,9 +81,14 @@ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, struct dcp_dict_passthrough_resp_v26_6 *resp = out; trace_iomfb_callback(dcp, tag, "dcpep_cb_passthrough_dict"); + dcp_trace_panel_property(dcp, tag, (const char *)req->key, + strnlen((const char *)req->key, sizeof(req->key)), + req->value_null ? NULL : req->value, sizeof(req->value)); - if (!req->value_null) + if (!req->value_null) { + dcp_panel_edid_receive(dcp, req->value, sizeof(req->value)); memcpy(resp->value, req->value, sizeof(resp->value)); + } resp->ret = false; memset(resp->padding, 0, sizeof(resp->padding)); @@ -91,6 +96,107 @@ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, return true; } +/* The firmware can publish panel identity through dictionary setters too. */ +static bool trampoline_panel_dictionary_v26_6(struct apple_dcp *dcp, int tag, + void *out, void *in) +{ + /* Service dictionary setters prefix the same record with a handle. */ + const struct dcp_dict_passthrough_req_v26_6 *req = tag == 413 ? in + 4 : in; + const char *key = (const char *)req->key; + struct dcp_display_metadata metadata; + struct dcp_parse_ctx ctx; + int ret; + + trace_iomfb_callback(dcp, tag, __func__); + dcp_trace_panel_property(dcp, tag, key, strnlen(key, sizeof(req->key)), + req->value_null ? NULL : req->value, sizeof(req->value)); + if (!dcp_has_panel(dcp) || req->value_null || + !memchr(key, 0, sizeof(req->key))) + goto done; + /* A real EDID may be a blob or nested in a property dictionary. */ + dcp_panel_edid_receive(dcp, req->value, sizeof(req->value)); + if (strcmp(key, "DisplayAttributes") && strcmp(key, "ProductAttributes")) + goto done; + ret = parse(req->value, sizeof(req->value), &ctx); + if (ret) + goto done; + if (!strcmp(key, "ProductAttributes")) + ret = parse_panel_product_metadata(&ctx, &metadata); + else + ret = parse_panel_metadata(&ctx, &metadata); + if (!ret) + dcp_panel_metadata_replace(dcp, &metadata); +done: + /* Preserve the existing setter acknowledgment for unrelated properties. */ + *(u8 *)out = true; + return true; +} + +static bool iomfb_check_callback_exact_v26_6_desktop(int tag, u32 in_len, u32 out_len) +{ + switch (tag) { + case 201: + return in_len == 12 && out_len == 16; + case 202: + return in_len == 24 && !out_len; + case 451: + return in_len == 20 && out_len == 28; + case 452: + return in_len == 24 && out_len == 20; + case 454: + return in_len == 4 && out_len == 4; + case 413: + return in_len == 4168 && (out_len == 1 || out_len == 4); + case 552: + case 561: + return in_len == 4164 && (out_len == 1 || out_len == 4); + default: + return true; + } +} + +/* + * unmap_piodma (D202): the buffer id, the id again, the DVA (unaligned) and a + * trailing word. The older firmware sends the DVA after a virtual address. + */ +struct dcp_unmap_buf_req_v26_6 { + __le64 buffer; + __le32 id; + __le64 dva; + __le32 unk; +} __packed; +static_assert(sizeof(struct dcp_unmap_buf_req_v26_6) == 24); + +static bool trampoline_unmap_piodma_v26_6(struct apple_dcp *dcp, int tag, + void *out, void *in) +{ + const struct dcp_unmap_buf_req_v26_6 *wire = in; + struct dcp_unmap_buf_resp request = { + .buffer = get_unaligned_le64(&wire->buffer), + .dva = get_unaligned_le64(&wire->dva), + }; + + trace_iomfb_callback(dcp, tag, "dcpep_cb_unmap_piodma"); + dcpep_unmap_piodma(dcp, &request); + + return true; +} + +/* + * release_mem_desc (D454): the firmware sends the 32-bit descriptor id and + * wants a 32-bit result, where the older firmware took a byte. + */ +static bool trampoline_release_mem_desc_v26_6(struct apple_dcp *dcp, int tag, + void *out, void *in) +{ + u32 id = get_unaligned_le32(in); + + trace_iomfb_callback(dcp, tag, "dcpep_cb_release_mem_desc"); + put_unaligned_le32(dcpep_cb_release_mem_desc(dcp, &id), out); + + return true; +} + static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [0] = trampoline_true, /* did_boot_signal */ [1] = trampoline_true, /* did_power_on_signal */ @@ -111,6 +217,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [114] = trampoline_passthrough_dict_v26_6, /* set_tiling_state: the value followed by success */ [115] = trampoline_get_tiling_state, + [116] = trampoline_set_tiling_state_v26_6, [121] = dcpep_cb_boot_1, /* start_hardware_boot */ [122] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate */ [123] = trampoline_false, @@ -120,6 +227,8 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [129] = trampoline_prop_end, [134] = trampoline_zero, [201] = trampoline_map_piodma, + /* a mode change retires its piodma buffers, then their descriptors */ + [202] = trampoline_unmap_piodma_v26_6, [206] = iomfbep_cb_match_pmu_service_2, [207] = iomfbep_cb_match_backlight_service, [208] = trampoline_nop, /* update_backlight_factor_prop */ @@ -130,12 +239,14 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [406] = trampoline_nop, /* set_fx_prop: a property notification */ [408] = trampoline_get_frequency, [411] = trampoline_map_reg, - [413] = trampoline_true, /* sr_set_property_dict */ + [413] = trampoline_panel_dictionary_v26_6, /* sr_set_property_dict */ [414] = trampoline_sr_set_property_int, [415] = trampoline_true, /* sr_set_property_bool */ [451] = trampoline_allocate_buffer, - [552] = trampoline_true, /* set_property_dict_0 */ - [561] = trampoline_true, /* set_property_dict */ + [452] = trampoline_map_physical, + [454] = trampoline_release_mem_desc_v26_6, + [552] = trampoline_panel_dictionary_v26_6, + [561] = trampoline_panel_dictionary_v26_6, [563] = trampoline_true, /* set_property_int */ [565] = trampoline_true, /* set_property_bool */ [567] = trampoline_true, /* set_property_str */ @@ -145,6 +256,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [576] = trampoline_nop, [581] = trampoline_nop, [582] = trampoline_true, /* create_default_fb_surface */ + [588] = trampoline_nop, /* resize_default_fb_surface_gated */ [590] = trampoline_swap_complete, [592] = trampoline_swap_complete_intent_gated, [593] = trampoline_abort_swap_ap_gated, @@ -155,6 +267,104 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [599] = trampoline_nop, }; +/* + * What each handler above reads from the request and writes to the reply. + * The firmware may send longer records; a shorter one is refused. + */ +struct dcp_cb_min_v26_6_desktop { + u32 in_len; + u32 out_len; + bool valid; +}; + +#define CB_MIN(in, out) { .in_len = (in), .out_len = (out), .valid = true } + +static const struct dcp_cb_min_v26_6_desktop cb_min[IOMFB_MAX_CB] = { + [0] = CB_MIN(0, sizeof(u8)), + [1] = CB_MIN(0, sizeof(u8)), + [2] = CB_MIN(0, 0), + [3] = CB_MIN(0, sizeof(struct dcp_rt_bandwidth)), + [6] = CB_MIN(sizeof(struct dcp_set_frame_sync_props_req_v26_6_desktop), + sizeof(struct dcp_set_frame_sync_props_resp_v26_6_desktop)), + [100] = CB_MIN(0, 0), + [101] = CB_MIN(0, sizeof(u32)), + [102] = CB_MIN(0, 0), + [104] = CB_MIN(0, 0), + [107] = CB_MIN(0, 0), + [108] = CB_MIN(0, sizeof(u8)), + [109] = CB_MIN(0, sizeof(u8)), + [110] = CB_MIN(0, sizeof(u8)), + [111] = CB_MIN(0, sizeof(u8)), + [112] = CB_MIN(0, sizeof(u8)), + [113] = CB_MIN(0, sizeof(u8)), + [114] = CB_MIN(sizeof(struct dcp_dict_passthrough_req_v26_6), + sizeof(struct dcp_dict_passthrough_resp_v26_6)), + [115] = CB_MIN(sizeof(struct dcpep_get_tiling_state_req), + sizeof(struct dcpep_get_tiling_state_resp)), + [116] = CB_MIN(sizeof(struct dcpep_get_tiling_state_req), sizeof(u32)), + [121] = CB_MIN(0, 0), + [122] = CB_MIN(0, sizeof(u8)), + [123] = CB_MIN(0, sizeof(u8)), + [125] = CB_MIN(sizeof(struct dcp_read_edt_data_req), + sizeof(dcp_read_edt_data_resp_t)), + [127] = CB_MIN(sizeof(u32), sizeof(u8)), + [128] = CB_MIN(sizeof(struct dcp_set_dcpav_prop_chunk_req), sizeof(u8)), + [129] = CB_MIN(sizeof(struct dcp_set_dcpav_prop_end_req), sizeof(u8)), + [134] = CB_MIN(0, sizeof(u32)), + [201] = CB_MIN(sizeof(struct dcp_map_buf_req), + sizeof(struct DCP_FW_NAME(dcp_map_buf_resp))), + [202] = CB_MIN(sizeof(struct dcp_unmap_buf_req_v26_6), 0), + [206] = CB_MIN(0, sizeof(u8)), + [207] = CB_MIN(0, sizeof(u8)), + [208] = CB_MIN(0, 0), + [209] = CB_MIN(0, sizeof(u64)), + [300] = CB_MIN(sizeof(struct iomfb_property), 0), + [400] = CB_MIN(0, 0), + [401] = CB_MIN(sizeof(struct dcp_get_uint_prop_req), + sizeof(struct dcp_get_uint_prop_resp)), + [406] = CB_MIN(0, 0), + [408] = CB_MIN(0, sizeof(u64)), + [411] = CB_MIN(sizeof(struct DCP_FW_NAME(dcp_map_reg_req)), + sizeof(struct DCP_FW_NAME(dcp_map_reg_resp))), + [413] = CB_MIN(0, sizeof(u8)), + [414] = CB_MIN(sizeof(struct iomfb_sr_set_property_int_req), sizeof(u8)), + [415] = CB_MIN(0, sizeof(u8)), + [451] = CB_MIN(sizeof(struct dcp_allocate_buffer_req), + sizeof(struct dcp_allocate_buffer_resp)), + [452] = CB_MIN(sizeof(struct dcp_map_physical_req), + sizeof(struct dcp_map_physical_resp)), + [454] = CB_MIN(sizeof(u32), sizeof(u32)), + [552] = CB_MIN(0, sizeof(u8)), + [561] = CB_MIN(0, sizeof(u8)), + [563] = CB_MIN(0, sizeof(u8)), + [565] = CB_MIN(0, sizeof(u8)), + [567] = CB_MIN(0, sizeof(u8)), + [572] = CB_MIN(0, sizeof(u32)), + [575] = CB_MIN(sizeof(struct dcp_hotplug_req_v26_6), + sizeof(struct dcp_hotplug_resp_v26_6)), + [576] = CB_MIN(0, 0), + [581] = CB_MIN(0, 0), + [582] = CB_MIN(0, sizeof(u8)), + [588] = CB_MIN(0, 0), + [590] = CB_MIN(sizeof(struct DCP_FW_NAME(dc_swap_complete_resp)), 0), + [592] = CB_MIN(sizeof(dcp_swap_intent_t), 0), + [593] = CB_MIN(sizeof(u32), 0), + [594] = CB_MIN(sizeof(u8), 0), + [595] = CB_MIN(0, 0), + [598] = CB_MIN(0, 0), + [599] = CB_MIN(0, 0), +}; + +#undef CB_MIN + +bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len) +{ + if (tag < 0 || tag >= IOMFB_MAX_CB || !cb_min[tag].valid) + return false; + return in_len >= cb_min[tag].in_len && out_len >= cb_min[tag].out_len && + iomfb_check_callback_exact_v26_6_desktop(tag, in_len, out_len); +} + void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp) { dcp->cb_handlers = cb_handlers; diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h index 697edc47fde3b..6f61493858e8b 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h @@ -49,4 +49,6 @@ struct dcp_set_frame_sync_props_resp_v26_6_desktop { u8 props[0x50]; } __packed; +bool iomfb_check_callback_v26_6_desktop(int tag, u32 in_len, u32 out_len); + #endif /* __APPLE_IOMFB_V26_6_DESKTOP_H__ */ diff --git a/drivers/gpu/drm/apple/parser-test.c b/drivers/gpu/drm/apple/parser-test.c index 9cf57af1b3c94..dc807a2013a43 100644 --- a/drivers/gpu/drm/apple/parser-test.c +++ b/drivers/gpu/drm/apple/parser-test.c @@ -3,9 +3,12 @@ #include #include #include +#include +#include #include #include "dcp.h" +#include #include #include "connector.h" @@ -13,6 +16,8 @@ #include "dcp-property.h" #include "iomfb_internal.h" #include "parser.h" +#include "dcp-panel-adt.h" +#include "dcp-panel-test.h" static struct device parser_device = { .init_name = "apple-display-parser-test", @@ -22,6 +27,13 @@ static struct apple_dcp parser_dcp = { .dev = &parser_device, }; +/* The probe owns the metadata lock beside the mode catalogue. */ +static void parser_modes_init(struct apple_dcp *dcp) +{ + dcp_modes_init(dcp); + spin_lock_init(&dcp->metadata.lock); +} + struct parser_blob { u8 data[8192]; u32 size; @@ -34,6 +46,7 @@ struct parser_record { s64 first_format; bool bad_score_type; bool vrr; + bool vrr_zero; }; enum parser_missing { @@ -143,8 +156,8 @@ static void blob_record(struct parser_blob *blob, const struct parser_record *re record->second_color_missing, DCP_COLOR_FORMAT_RGB); } if (record->vrr) { - blob_int(blob, "MinimumVariableRefreshRate", 48 << 16); - blob_int(blob, "MaximumVariableRefreshRate", 120 << 16); + blob_int(blob, "MinimumVariableRefreshRate", record->vrr_zero ? 0 : 48 << 16); + blob_int(blob, "MaximumVariableRefreshRate", record->vrr_zero ? 0 : 120 << 16); } } @@ -435,6 +448,58 @@ static void parser_notch_vrr(struct kunit *test) kfree(modes); } +static void parser_vrr_empty_range(struct kunit *test) +{ + struct parser_record record = valid_record(7, 10); + struct dcp_display_mode *modes; + struct parser_blob *blob; + unsigned int count, desktop; + struct dcp_parse_ctx ctx; + + /* Desktop firmware can publish both fields as zero placeholders. */ + record.rate = 120 << 16; + record.vrr = true; + record.vrr_zero = true; + blob = make_blob(test, &record, 1); + KUNIT_ASSERT_NOT_NULL(test, blob); + modes = parse_blob(blob, &count, 0, true); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); + KUNIT_ASSERT_EQ(test, count, 1U); + KUNIT_EXPECT_FALSE(test, modes[0].vrr); + KUNIT_EXPECT_EQ(test, modes[0].min_vrr, 0U); + KUNIT_EXPECT_EQ(test, modes[0].max_vrr, 0U); + kfree(modes); + + /* A display without the internal ProMotion policy stays fixed. */ + modes = parse_blob(blob, &count, 0, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); + KUNIT_ASSERT_EQ(test, count, 1U); + KUNIT_EXPECT_FALSE(test, modes[0].vrr); + KUNIT_EXPECT_EQ(test, modes[0].min_vrr, 0U); + KUNIT_EXPECT_EQ(test, modes[0].max_vrr, 0U); + kfree(modes); + /* The catalog owner enables this convention only for desktop firmware. */ + for (desktop = 0; desktop < 2; desktop++) { + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + u64 generation; + + KUNIT_ASSERT_NOT_NULL(test, dcp); + parser_modes_init(dcp); + dcp->fw_compat = desktop ? DCP_FIRMWARE_V_26_6_DESKTOP : + DCP_FIRMWARE_V_13_5; + dcp->fixed_connector_type = DRM_MODE_CONNECTOR_eDP; + KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); + ctx.dcp = &parser_dcp; + generation = dcp_modes_transfer_begin(dcp); + KUNIT_ASSERT_EQ(test, dcp_modes_replace(dcp, &ctx, generation), 0); + KUNIT_ASSERT_EQ(test, dcp->nr_modes, 1U); + KUNIT_EXPECT_EQ(test, dcp->modes[0].vrr, !!desktop); + KUNIT_EXPECT_EQ(test, dcp->modes[0].min_vrr, desktop ? 24U << 16 : 0U); + KUNIT_EXPECT_EQ(test, dcp->modes[0].max_vrr, desktop ? 120U << 16 : 0U); + kfree(dcp->modes); + } +} + static void parser_replacement(struct kunit *test) { struct parser_record record = valid_record(7, 10); @@ -445,7 +510,7 @@ static void parser_replacement(struct kunit *test) struct drm_display_mode requested; KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); blob = make_blob(test, &record, 1); KUNIT_ASSERT_NOT_NULL(test, blob); KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); @@ -493,8 +558,7 @@ static void parser_held_keeps_edid(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, connector); KUNIT_ASSERT_NOT_NULL(test, pipeline); - dcp_modes_init(dcp); - spin_lock_init(&dcp->metadata.lock); + parser_modes_init(dcp); apple_connector_edid_init(connector); apple_connector_set_pipeline(connector, pipeline); dcp->connector = connector; @@ -528,7 +592,7 @@ static void parser_attachment_admission(struct kunit *test) struct drm_display_mode requested; KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); spin_lock_init(&dcp->mode_state.lock); blob = make_blob(test, &record, 1); KUNIT_ASSERT_NOT_NULL(test, blob); @@ -575,7 +639,7 @@ static void parser_stale_transfer(struct kunit *test) u64 old_generation, current_generation; KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); blob = make_blob(test, &record, 1); KUNIT_ASSERT_NOT_NULL(test, blob); KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); @@ -649,7 +713,7 @@ static void parser_route_catalog_handoff(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, connector); KUNIT_ASSERT_NOT_NULL(test, other); KUNIT_ASSERT_NOT_NULL(test, pipeline); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->dev = &pipeline->dev; connector->dcp = pipeline; observer->dcp = dcp; @@ -770,7 +834,7 @@ static void parser_route_programming_guard(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, connector->base.state); connector->base.state->crtc = &crtc->base; crtc_state.connector_mask = drm_connector_mask(&connector->base); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->modes_admitted = true; dcp->fw_compat = DCP_FIRMWARE_V_13_5; dcp->nr_typec_routes = 1; @@ -856,7 +920,7 @@ static void parser_route_invalid_own_mode(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, crtc); KUNIT_ASSERT_NOT_NULL(test, pdev); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->fw_compat = DCP_FIRMWARE_V_13_5; dcp->nr_typec_routes = 1; crtc->dcp = pdev; @@ -875,7 +939,7 @@ static void parser_connector_ownership(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, fixed); KUNIT_ASSERT_NOT_NULL(test, borrowed); - dcp_modes_init(dcp); + parser_modes_init(dcp); mutex_lock(&dcp->modes_lock); dcp->connector = borrowed; dcp->modes_admitted = true; @@ -903,7 +967,7 @@ static void parser_route_retirement(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, fixed); KUNIT_ASSERT_NOT_NULL(test, route); KUNIT_ASSERT_NOT_NULL(test, other); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->connector = fixed; dcp->active_typec_route = route; dcp->modes_admitted = true; @@ -927,7 +991,7 @@ static void parser_rejected_replacement(struct kunit *test) struct parser_blob *blob; KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); blob = make_blob(test, &record, 1); KUNIT_ASSERT_NOT_NULL(test, blob); KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); @@ -969,7 +1033,7 @@ static void parser_attribute_epoch(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, blob); - dcp_modes_init(dcp); + parser_modes_init(dcp); put_unaligned_le32(0xd3, blob->data); blob->size = 4; blob_tag(blob, 1, 3); @@ -1003,7 +1067,7 @@ static void parser_attributes_malformed(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, blob); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->width_mm = 520; dcp->height_mm = 290; dcp->ext_backlight = true; @@ -1040,7 +1104,7 @@ static void parser_optional_flag_extent(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, blob); - dcp_modes_init(dcp); + parser_modes_init(dcp); put_unaligned_le32(0xd3, blob->data); blob->size = 4; blob_tag(blob, 1, 3); @@ -1089,11 +1153,12 @@ static void parser_attribute_deferred(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, connector); KUNIT_ASSERT_NOT_NULL(test, dev); KUNIT_ASSERT_NOT_NULL(test, blob); - dcp_modes_init(dcp); + parser_modes_init(dcp); observer->dcp = dcp; INIT_WORK(&connector->hotplug_wq, parser_catalog_ready); mutex_init(&dev->mode_config.mutex); connector->base.dev = dev; + apple_connector_backlight_init(connector); dcp->connector = connector; dcp->panel.width_mm = 600; dcp->modes = kmalloc_obj(*dcp->modes); @@ -1118,6 +1183,9 @@ static void parser_attribute_deferred(struct kunit *test) KUNIT_EXPECT_EQ(test, connector->base.display_info.width_mm, 520U); KUNIT_EXPECT_EQ(test, connector->base.display_info.height_mm, 290U); dcp_modes_release(dcp); + cancel_work_sync(&connector->hotplug_wq); + cancel_work_sync(&connector->bl_sync_wq); + apple_connector_backlight_early_unregister(connector); } static void parser_dimensions_lifetime(struct kunit *test) @@ -1129,7 +1197,7 @@ static void parser_dimensions_lifetime(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, dcp); KUNIT_ASSERT_NOT_NULL(test, connector); KUNIT_ASSERT_NOT_NULL(test, dev); - dcp_modes_init(dcp); + parser_modes_init(dcp); mutex_init(&dev->mode_config.mutex); connector->base.dev = dev; dcp->connector = connector; @@ -1158,7 +1226,7 @@ static void parser_catalog_release(struct kunit *test) struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, dcp); - dcp_modes_init(dcp); + parser_modes_init(dcp); dcp->modes = kmalloc_obj(*dcp->modes); KUNIT_ASSERT_NOT_NULL(test, dcp->modes); dcp->nr_modes = 1; @@ -1238,12 +1306,341 @@ static void parser_route_prepare_scope(struct kunit *test) dcp->typec_follow_start = true; dcp->typec_follow_gen = dcp->typec_generation; mutex_init(&dcp->hpd_mutex); + mutex_init(&dcp->tb_lock); KUNIT_EXPECT_EQ(test, dcp_typec_prepare_route(dcp), -ENODEV); + mutex_destroy(&dcp->tb_lock); mutex_destroy(&dcp->hpd_mutex); mutex_destroy(&dcp->modes_lock); } +static void parser_panel_identity(struct kunit *test) +{ + struct parser_blob *blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + struct dcp_display_metadata metadata; + struct dcp_parse_ctx ctx; + const struct drm_edid *edid; + const struct edid *raw; + struct dcp_display_mode modes[2] = {}; + + modes[0].mode = (struct drm_display_mode) { + .clock = 540005, .hdisplay = 3456, .hsync_start = 3500, + .hsync_end = 3550, .htotal = 4000, .vdisplay = 2160, + .vsync_start = 2165, .vsync_end = 2170, .vtotal = 2250, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, + }; + modes[1] = modes[0]; + modes[1].mode.clock = 1080005; + modes[1].vrr = true; + modes[1].min_vrr = 24U << 16; + modes[1].max_vrr = 120U << 16; + + KUNIT_ASSERT_NOT_NULL(test, blob); + put_unaligned_le32(0xd3, blob->data); + blob->size = 4; + blob_tag(blob, 1, 1); + blob_key(blob, "ProductAttributes"); + blob_tag(blob, 1, 4); + blob_key(blob, "ManufacturerID"); + blob_key(blob, "TST"); + blob_key(blob, "ProductName"); + blob_key(blob, "Synthetic Internal Panel"); + blob_int(blob, "ProductID", 42); + blob_int(blob, "SerialNumber", 0x12345678); + KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); + KUNIT_ASSERT_EQ(test, parse_panel_metadata(&ctx, &metadata), 0); + KUNIT_EXPECT_STREQ(test, metadata.name, "Synthetic Int"); + edid = dcp_panel_edid(&metadata, modes, ARRAY_SIZE(modes), 345, 223); + KUNIT_ASSERT_FALSE(test, IS_ERR(edid)); + KUNIT_EXPECT_TRUE(test, drm_edid_valid(edid)); + raw = drm_edid_raw(edid); + KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->product_id.product_code), 42); + KUNIT_EXPECT_EQ(test, le32_to_cpu(raw->product_id.serial_number), 0x12345678U); + KUNIT_EXPECT_EQ(test, raw->gamma, 0xff); + KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->detailed_timings[0].pixel_clock), 54001); + KUNIT_EXPECT_EQ(test, raw->detailed_timings[3].data.other_data.data.range.min_vfreq, 24); + KUNIT_EXPECT_EQ(test, raw->detailed_timings[3].data.other_data.data.range.max_vfreq, 120); + KUNIT_EXPECT_EQ(test, modes[0].mode.clock, 540005); + drm_edid_free(edid); +} + +static void parser_panel_identity_stable(struct kunit *test) +{ + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + struct dcp_desktop_state *desktop = kunit_kzalloc(test, sizeof(*desktop), GFP_KERNEL); + struct dcp_display_metadata metadata = { + .manufacturer = "TST", .name = "Synthetic", .product = 42, .serial = 1, + }; + u64 generation; + + KUNIT_ASSERT_NOT_NULL(test, dcp); + KUNIT_ASSERT_NOT_NULL(test, desktop); + dcp->desktop = desktop; + dcp->fw_compat = DCP_FIRMWARE_V_26_6_DESKTOP; + dcp->panel.width_mm = 345; + spin_lock_init(&dcp->metadata.lock); + dcp_panel_metadata_replace(dcp, &metadata); + generation = desktop->panel_metadata_generation; + KUNIT_EXPECT_TRUE(test, dcp_panel_metadata_current(dcp, generation)); + dcp_panel_metadata_replace(dcp, NULL); + dcp_panel_metadata_replace(dcp, &metadata); + { + struct dcp_display_metadata partial = { .manufacturer = "TST", .product = 42 }; + + dcp_panel_metadata_replace(dcp, &partial); + } + KUNIT_EXPECT_TRUE(test, dcp_panel_metadata_current(dcp, generation)); + KUNIT_EXPECT_EQ(test, desktop->panel_metadata.serial, 1U); + metadata.serial++; + dcp_panel_metadata_replace(dcp, &metadata); + KUNIT_EXPECT_FALSE(test, dcp_panel_metadata_current(dcp, generation)); + KUNIT_EXPECT_EQ(test, desktop->panel_metadata.serial, 2U); +} + +static void parser_identity_emit(void *data, const char *key, u32 len, + u32 type, u32 size, u32 depth) +{ + u32 *seen = data; + + (*seen)++; +} + +static void parser_identity_bounds(struct kunit *test) +{ + u32 seen = 0; + __le32 huge[] = { cpu_to_le32(0xd3), cpu_to_le32(0x01ffffff) }; + int ret; + + ret = parse_identity_keys(huge, sizeof(huge), parser_identity_emit, &seen); + KUNIT_EXPECT_EQ(test, ret, -E2BIG); + KUNIT_EXPECT_EQ(test, seen, 1U); + for (u32 len = 0; len < sizeof(huge); len++) { + ret = parse_identity_keys(huge, len, parser_identity_emit, &seen); + KUNIT_EXPECT_LT(test, ret, 0); + } +} + +static void parser_adt_bounds(struct kunit *test) +{ + struct dcp_panel_adt panel = { .identity = { .manufacturer = "APP" } }; + struct dcp_panel_adt before = panel; + __le32 huge[] = { cpu_to_le32(4097), 0 }; + + KUNIT_EXPECT_EQ(test, dcp_panel_adt_parse(huge, sizeof(huge), &panel), -E2BIG); + KUNIT_EXPECT_MEMEQ(test, &panel, &before, sizeof(panel)); + KUNIT_EXPECT_EQ(test, dcp_panel_adt_parse(NULL, 0, &panel), -EINVAL); +} + +static void parser_panel_of_dimensions(struct kunit *test) +{ + __be32 width = cpu_to_be32(345), height = cpu_to_be32(223); + __be32 adjusted = cpu_to_be32(216); + struct property adj = { .name = "adj-height-mm", .length = 4, .value = &adjusted }; + struct property h = { .name = "height-mm", .length = 4, .value = &height }; + struct property w = { .name = "width-mm", .length = 4, .value = &width, .next = &h }; + struct device_node node = { .properties = &w }; + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, dcp); + dcp->notch_height = 74; + /* A missing optional adj-height-mm must not erase the full height. */ + dcp_panel_read_dimensions(dcp, &node); + KUNIT_EXPECT_EQ(test, dcp->panel.width_mm, 345U); + KUNIT_EXPECT_EQ(test, dcp->panel.height_mm, 223U); + h.next = &adj; + dcp_panel_read_dimensions(dcp, &node); + KUNIT_EXPECT_EQ(test, dcp->panel.height_mm, 216U); + dcp->notch_height = 0; + dcp_panel_read_dimensions(dcp, &node); + KUNIT_EXPECT_EQ(test, dcp->panel.height_mm, 223U); +} + +static void parser_panel_j616s_publication(struct kunit *test) +{ + struct apple_dcp *dcp = kunit_kzalloc(test, sizeof(*dcp), GFP_KERNEL); + struct dcp_desktop_state *desktop = kunit_kzalloc(test, sizeof(*desktop), GFP_KERNEL); + struct platform_device *pipeline = kunit_kzalloc(test, sizeof(*pipeline), GFP_KERNEL); + struct apple_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + struct dcp_panel_adt panel = { + .identity = { .manufacturer = "APP", .product = 0x616, .name = "Apple Panel" }, + .width_mm = 345, .height_mm = 216, + }; + struct dcp_panel_adt before = panel; + struct device *dev = drm_kunit_helper_alloc_device(test); + struct drm_device *drm; + struct drm_display_mode *published; + struct dcp_display_mode mode = panel_test_mode; + const struct edid *raw; + int count, ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm), 0, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + KUNIT_ASSERT_NOT_NULL(test, dcp); + KUNIT_ASSERT_NOT_NULL(test, desktop); + KUNIT_ASSERT_NOT_NULL(test, pipeline); + KUNIT_ASSERT_NOT_NULL(test, connector); + ret = drmm_connector_init(drm, &connector->base, &parser_route_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + ret = dcp_panel_adt_parse(panel_test_adt, sizeof(panel_test_adt), &panel); + KUNIT_ASSERT_EQ(test, ret, 0); + /* /product's machine ID is not a panel product; neither panel node exists. */ + KUNIT_EXPECT_FALSE(test, panel.found); + KUNIT_EXPECT_MEMEQ(test, &panel, &before, sizeof(panel)); + parser_modes_init(dcp); + mutex_init(&dcp->dcpavserv.lock); + mutex_init(&dcp->hpd_mutex); + dcp->dev = dev; + dcp->desktop = desktop; + dcp->fw_compat = DCP_FIRMWARE_V_26_6_DESKTOP; + dcp->panel.width_mm = panel.width_mm; + dcp->panel.height_mm = panel.height_mm; + desktop->panel_adt_identity = panel.identity; + dcp->connector = connector; + dcp->modes = &mode; + dcp->nr_modes = 1; + dcp->modes_admitted = true; + connector->dcp = pipeline; + connector->edid_live = true; + platform_set_drvdata(pipeline, dcp); + + /* Probe through the real iomfb publication path and DRM blob allocator. */ + mutex_lock(&drm->mode_config.mutex); + count = dcp_get_modes(&connector->base); + mutex_unlock(&drm->mode_config.mutex); + KUNIT_EXPECT_EQ(test, count, 1); + KUNIT_ASSERT_NOT_NULL(test, connector->base.edid_blob_ptr); + KUNIT_EXPECT_EQ(test, connector->base.edid_blob_ptr->length, (size_t)128); + KUNIT_EXPECT_TRUE(test, connector->drm_edid_firmware_derived); + raw = connector->base.edid_blob_ptr->data; + KUNIT_EXPECT_MEMEQ(test, raw->detailed_timings[1].data.other_data.data.str.str, + "Apple Panel", 11); + KUNIT_EXPECT_EQ(test, connector->base.display_info.width_mm, 345U); + KUNIT_EXPECT_EQ(test, connector->base.display_info.height_mm, 216U); + KUNIT_EXPECT_MEMEQ(test, &mode, &panel_test_mode, sizeof(mode)); + KUNIT_ASSERT_FALSE(test, list_empty(&connector->base.probed_modes)); + published = list_first_entry(&connector->base.probed_modes, struct drm_display_mode, head); + KUNIT_EXPECT_EQ(test, published->clock, 521065); + KUNIT_EXPECT_EQ(test, published->vsync_start, 2278); + KUNIT_EXPECT_EQ(test, published->vsync_end, 2360); + list_del(&published->head); + drm_mode_destroy(drm, published); + /* The stack catalogue is not owned by dcp_modes_release(). */ + dcp->modes = NULL; + dcp->nr_modes = 0; + dcp_modes_release(dcp); +} + +static void blob_timing_dimension(struct parser_blob *blob, const char *key, + s64 active, s64 total, s64 porch, s64 sync, s64 rate) +{ + blob_key(blob, key); + blob_tag(blob, 1, 7); + blob_int(blob, "Active", active); + blob_int(blob, "Total", total); + blob_int(blob, "FrontPorch", porch); + blob_int(blob, "SyncWidth", sync); + blob_int(blob, "PreciseSyncRate", rate); + blob_int(blob, "SyncPolarity", 1); + blob_int(blob, "PixelRepetition", 0); +} + +/* A desktop 26.x sink timing: the keys the firmware-derived EDID needs. */ +static struct parser_blob *make_timing_blob(struct kunit *test) +{ + struct parser_blob *blob = kunit_kzalloc(test, sizeof(*blob), GFP_KERNEL); + + if (!blob) + return NULL; + put_unaligned_le32(0xd3, blob->data); + blob->size = 4; + blob_tag(blob, 2, 1); + blob_tag(blob, 1, 8); + blob_bool(blob, "IsVirtual", false); + blob_int(blob, "ID", 7); + blob_int(blob, "Score", 10); + blob_timing_dimension(blob, "HorizontalAttributes", 1920, 2200, 88, 44, + (675U << 16) / 10); + blob_timing_dimension(blob, "VerticalAttributes", 1080, 1125, 4, 5, 60 << 16); + blob_key(blob, "ColorModes"); + blob_tag(blob, 2, 1); + blob_color(blob, 100, 10, 0, DCP_COLOR_FORMAT_RGB); + blob_bool(blob, "IsPreferred", true); + blob_bool(blob, "IsInterlaced", false); + return blob; +} + +static void parser_desktop_metadata_timing(struct kunit *test) +{ + const struct dcp_display_metadata metadata = { + .width_cm = 60, .height_cm = 33, + .chromaticity = { { 44608, 20288 }, { 16960, 40448 }, { 9344, 5184 } }, + .white = { 20544, 21568 }, .gamma = 144179, + .product = 2895, .serial = 305419896, .week = 7, .year = 2013, + .manufacturer = "ACR", .name = "QG270 S3", .serial_text = "ANON000000001", + }; + struct parser_blob *blob = make_timing_blob(test); + struct drm_display_mode preferred; + struct dcp_display_mode *modes; + const struct drm_edid *edid; + const struct edid *raw; + struct dcp_parse_ctx ctx; + unsigned int count; + + KUNIT_ASSERT_NOT_NULL(test, blob); + + /* Other DCPs keep their modes and give no metadata timing. */ + KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); + ctx.dcp = &parser_dcp; + modes = enumerate_modes(&ctx, &count, 600, 330, 0, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); + KUNIT_ASSERT_EQ(test, count, 1U); + KUNIT_EXPECT_TRUE(test, modes[0].preferred); + KUNIT_EXPECT_FALSE(test, modes[0].metadata_timing_valid); + KUNIT_EXPECT_EQ(test, modes[0].metadata_sync_flags, 0U); + KUNIT_EXPECT_EQ(test, modes[0].mode.flags, 0U); + kfree(modes); + + KUNIT_ASSERT_EQ(test, parse(blob->data, blob->size, &ctx), 0); + ctx.dcp = &parser_dcp; + ctx.metadata_timing = true; + modes = enumerate_modes(&ctx, &count, 600, 330, 0, false, false); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, modes); + KUNIT_ASSERT_EQ(test, count, 1U); + KUNIT_EXPECT_TRUE(test, modes[0].preferred); + KUNIT_EXPECT_TRUE(test, modes[0].mode.type & DRM_MODE_TYPE_PREFERRED); + KUNIT_EXPECT_TRUE(test, modes[0].metadata_timing_valid); + KUNIT_EXPECT_EQ(test, modes[0].metadata_sync_flags, + (u32)(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)); + /* The published mode is unchanged: only the EDID timing takes the polarity. */ + KUNIT_EXPECT_EQ(test, modes[0].mode.flags, 0U); + KUNIT_EXPECT_EQ(test, modes[0].mode.clock, 148500); + + preferred = modes[0].mode; + preferred.flags |= modes[0].metadata_sync_flags; + kfree(modes); + edid = dcp_metadata_edid(&metadata, &preferred); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, edid); + raw = drm_edid_raw(edid); + KUNIT_EXPECT_EQ(test, be16_to_cpu(raw->product_id.manufacturer_name), + (u16)(('A' - '@') << 10 | ('C' - '@') << 5 | ('R' - '@'))); + KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->product_id.product_code), 2895); + KUNIT_EXPECT_EQ(test, raw->detailed_timings[1].data.other_data.type, + EDID_DETAIL_MONITOR_NAME); + KUNIT_EXPECT_MEMEQ(test, raw->detailed_timings[1].data.other_data.data.str.str, + "QG270 S3\n", 9); + KUNIT_EXPECT_EQ(test, le16_to_cpu(raw->detailed_timings[0].pixel_clock), 14850); + drm_edid_free(edid); +} + static struct kunit_case parser_cases[] = { + KUNIT_CASE(parser_panel_of_dimensions), + KUNIT_CASE(parser_panel_j616s_publication), + KUNIT_CASE(parser_adt_bounds), + KUNIT_CASE(parser_identity_bounds), + KUNIT_CASE(parser_panel_identity_stable), + KUNIT_CASE(parser_panel_identity), KUNIT_CASE(parser_score_presence), KUNIT_CASE(parser_legacy_ties), KUNIT_CASE(parser_color_presence), @@ -1252,6 +1649,7 @@ static struct kunit_case parser_cases[] = { KUNIT_CASE(parser_horizontal_rate), KUNIT_CASE(parser_bad_score), KUNIT_CASE(parser_notch_vrr), + KUNIT_CASE(parser_vrr_empty_range), KUNIT_CASE(parser_replacement), KUNIT_CASE(parser_held_keeps_edid), KUNIT_CASE(parser_attachment_admission), @@ -1271,6 +1669,7 @@ static struct kunit_case parser_cases[] = { KUNIT_CASE(parser_dimensions_lifetime), KUNIT_CASE(parser_catalog_release), KUNIT_CASE(parser_property_bounds), + KUNIT_CASE(parser_desktop_metadata_timing), {} }; diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index cecad5a1fb866..e5a8c8e6cc700 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -14,6 +14,7 @@ #endif #include "parser.h" +#include "dcp-refresh.h" #include "trace.h" #define DCP_PARSE_HEADER 0xd3 @@ -723,7 +724,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, struct timing_color_restrictions limits = {}; struct dcp_color_mode unsafe_rgb8 = { .score = -1 }; bool have_horiz = false, have_vert = false, have_virtual = false; - bool duplicate_scope = false; + bool duplicate_scope = false, vrr_ambiguous = false, vrr_fallback; + unsigned int vrr_seen = 0; s64 min_vrr = 0, max_vrr = 0; u64 clock; s64 id = -1; @@ -732,6 +734,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, bool preferred = false, interlaced = false; bool have_preferred = false, have_interlaced = false; bool is_preferred = false; + bool timing_keys = external_sdr || handle->metadata_timing; struct drm_display_mode *mode = &out->mode; *out = (struct dcp_display_mode) { @@ -773,16 +776,20 @@ static int parse_mode(struct dcp_parse_ctx *handle, else if (!strcmp(key, "HorizontalAttributes")) { duplicate_scope |= have_horiz; have_horiz = true; - ret = parse_dimension(it.handle, &horiz, external_sdr); + ret = parse_dimension(it.handle, &horiz, timing_keys || internal); } else if (!strcmp(key, "VerticalAttributes")) { duplicate_scope |= have_vert; have_vert = true; - ret = parse_dimension(it.handle, &vert, external_sdr); - } else if (!strcmp(key, "MinimumVariableRefreshRate")) + ret = parse_dimension(it.handle, &vert, timing_keys || internal); + } else if (!strcmp(key, "MinimumVariableRefreshRate")) { + vrr_ambiguous |= !!(vrr_seen & 1); + vrr_seen |= 1; ret = parse_int_bound(it.handle, &min_vrr, 0, U32_MAX); - else if (!strcmp(key, "MaximumVariableRefreshRate")) + } else if (!strcmp(key, "MaximumVariableRefreshRate")) { + vrr_ambiguous |= !!(vrr_seen & 2); + vrr_seen |= 2; ret = parse_int_bound(it.handle, &max_vrr, 0, U32_MAX); - else if (!strcmp(key, "ColorModes")) + } else if (!strcmp(key, "ColorModes")) ret = parse_color_modes(it.handle, out, external_sdr, &limits, handle->automatic_1080p180_rgb8 ? &unsafe_rgb8 : NULL); @@ -792,13 +799,16 @@ static int parse_mode(struct dcp_parse_ctx *handle, duplicate_scope |= have_virtual; have_virtual = true; ret = parse_bool(it.handle, &is_virtual); - } else if (external_sdr && !strcmp(key, "IsPreferred")) { + } else if (timing_keys && !strcmp(key, "IsPreferred")) { if (have_preferred) ret = -EINVAL; else ret = parse_bool(it.handle, &preferred); have_preferred = true; - } else if (external_sdr && !strcmp(key, "IsInterlaced")) { + /* Outside the external SDR path it also ranks the timing. */ + if (!external_sdr) + is_preferred = preferred; + } else if (timing_keys && !strcmp(key, "IsInterlaced")) { if (have_interlaced) ret = -EINVAL; else @@ -870,26 +880,16 @@ static int parse_mode(struct dcp_parse_ctx *handle, return -EINVAL; /* - * An internal ProMotion panel carries no EDID or DisplayID, so DCP - * reports no adaptive-sync range for it. Assume the ProMotion floor of - * 24 Hz up to whatever rate the mode itself advertises. + * External H17P stays fixed. Desktop zero placeholders are also an + * absent range; a partial or duplicate pair must not enable fallback. */ - if (internal && vert.precise_sync_rate >> 16 == 120) { - out->min_vrr = 24 << 16; - out->max_vrr = vert.precise_sync_rate; - out->vrr = true; - } - - /* - * Refresh rates are reported by DCP as 16.16 fixed-point Hz. - * External H17P skips adaptive-sync programming: keep the fixed timing - * available, but do not advertise its sink VRR range as driver support. - */ - if (!external_sdr && min_vrr && max_vrr > min_vrr) { - out->min_vrr = min_vrr; - out->max_vrr = max_vrr; - out->vrr = true; - } + vrr_fallback = !vrr_seen || (vrr_seen == 3 && handle->zero_vrr_range_fallback); + if (!external_sdr && !vrr_ambiguous) + out->vrr = dcp_refresh_range(vert.precise_sync_rate, min_vrr, + max_vrr, internal && vrr_fallback, + &out->min_vrr, &out->max_vrr); + out->needs_timestamps = out->vrr || + (internal && dcp_refresh_is_promotion(vert.precise_sync_rate)); clock = calculate_clock(&horiz, &vert); if (!clock || clock > INT_MAX) @@ -918,10 +918,16 @@ static int parse_mode(struct dcp_parse_ctx *handle, .height_mm = height_mm, }; - if (external_sdr && horiz.sync_polarity >= 0 && vert.sync_polarity >= 0) - mode->flags = (horiz.sync_polarity ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC) | - (vert.sync_polarity ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC); - out->metadata_timing_valid = external_sdr && !out->automatic_rgb8_quirk && + /* The internal panel also publishes its polarity with its modes (J616s series). */ + if ((timing_keys || internal) && horiz.sync_polarity >= 0 && + vert.sync_polarity >= 0) + out->metadata_sync_flags = + (horiz.sync_polarity ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC) | + (vert.sync_polarity ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC); + /* The external SDR path and the internal panel publish it with their modes. */ + if (external_sdr || internal) + mode->flags = out->metadata_sync_flags; + out->metadata_timing_valid = timing_keys && !out->automatic_rgb8_quirk && have_preferred && preferred && have_interlaced && !interlaced && horiz.pixel_repetition == 0 && vert.pixel_repetition == 0 && horiz.sync_polarity >= 0 && vert.sync_polarity >= 0; @@ -1322,7 +1328,38 @@ static int parse_metadata_chromaticity(struct dcp_parse_ctx *handle, return seen == 7 ? 0 : -EINVAL; } -static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_display_metadata *out) +/* Internal panel labels can be longer than an EDID descriptor. */ +static int parse_panel_label(struct dcp_parse_ctx *handle, char out[14]) +{ + const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING); + const u8 *value; + unsigned int length; + + if (IS_ERR(tag)) + return PTR_ERR(tag); + if (tag->size > 255) + return -EINVAL; + value = parse_bytes(handle, tag->size); + if (IS_ERR(value)) + return PTR_ERR(value); + for (unsigned int i = 0; i < tag->size; i++) + if (value[i] < 0x20 || value[i] > 0x7e) + return -EINVAL; + length = tag->size; + if (length > 13) + length = 13; + memcpy(out, value, length); + out[length] = 0; + /* A blank descriptor is absent to libdisplay-info; use the fallback. */ + for (unsigned int i = 0; i < length; i++) + if (out[i] != ' ') + return 0; + out[0] = 0; + return 0; +} + +static int parse_metadata_product(struct dcp_parse_ctx *handle, + struct dcp_display_metadata *out, bool panel) { struct iterator it; unsigned int seen = 0; @@ -1347,7 +1384,8 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_displ if (seen & 2) return -EINVAL; seen |= 2; - ret = parse_metadata_string(handle, out->name, sizeof(out->name)); + ret = panel ? parse_panel_label(handle, out->name) : + parse_metadata_string(handle, out->name, sizeof(out->name)); } else if (!strcmp(key, "ProductID")) { if (seen & 4) return -EINVAL; @@ -1357,12 +1395,14 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_displ if (seen & 8) return -EINVAL; seen |= 8; - ret = parse_metadata_number(handle, &out->week, 0, 53); + ret = panel ? skip(handle) : + parse_metadata_number(handle, &out->week, 0, 53); } else if (!strcmp(key, "YearOfManufacture")) { if (seen & 16) return -EINVAL; seen |= 16; - ret = parse_metadata_number(handle, &out->year, 1990, 2245); + ret = panel ? skip(handle) : + parse_metadata_number(handle, &out->year, 1990, 2245); } else if (!strcmp(key, "SerialNumber")) { if (seen & 64) return -EINVAL; @@ -1372,8 +1412,9 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_displ if (seen & 128) return -EINVAL; seen |= 128; - ret = parse_metadata_string(handle, out->serial_text, - sizeof(out->serial_text)); + ret = panel ? parse_panel_label(handle, out->serial_text) : + parse_metadata_string(handle, out->serial_text, + sizeof(out->serial_text)); } else if (!strcmp(key, "LegacyManufacturerID")) { if (seen & 32) return -EINVAL; @@ -1385,7 +1426,13 @@ static int parse_metadata_product(struct dcp_parse_ctx *handle, struct dcp_displ if (ret) return ret; } - if ((seen & 31) != 31 || strlen(out->manufacturer) != 3) + if (panel && !(seen & 1) && (seen & 32)) { + for (unsigned int i = 0; i < 3; i++) + out->manufacturer[i] = '@' + ((legacy >> (10 - 5 * i)) & 31); + seen |= 1; + } + if ((seen & (panel ? 5 : 31)) != (panel ? 5 : 31) || + strlen(out->manufacturer) != 3) return -EINVAL; for (unsigned int i = 0; i < 3; i++) if (out->manufacturer[i] < 'A' || out->manufacturer[i] > 'Z') @@ -1436,7 +1483,7 @@ int parse_display_metadata(struct dcp_parse_ctx *handle, struct dcp_display_meta if (seen & 16) return -EINVAL; seen |= 16; - ret = parse_metadata_product(handle, &parsed); + ret = parse_metadata_product(handle, &parsed, false); } else { ret = skip(handle); } @@ -1449,6 +1496,50 @@ int parse_display_metadata(struct dcp_parse_ctx *handle, struct dcp_display_meta return 0; } +/* Identity does not depend on optional colorimetry or EDID-compatible timing. */ +int parse_panel_product_metadata(struct dcp_parse_ctx *handle, + struct dcp_display_metadata *out) +{ + struct dcp_display_metadata parsed = {}; + int ret = parse_metadata_product(handle, &parsed, true); + + if (!ret) + *out = parsed; + return ret; +} + +int parse_panel_metadata(struct dcp_parse_ctx *handle, struct dcp_display_metadata *out) +{ + struct dcp_display_metadata parsed = {}; + struct iterator it; + bool product = false; + int ret = iterator_begin(handle, &it, true); + + if (ret) + return ret; + for (; it.idx < it.len; it.idx++) { + char key[64]; + + ret = parse_metadata_string(handle, key, sizeof(key)); + if (ret) + return ret; + if (!strcmp(key, "ProductAttributes")) { + if (product) + return -EINVAL; + product = true; + ret = parse_metadata_product(handle, &parsed, true); + } else { + ret = skip(handle); + } + if (ret) + return ret; + } + if (!product) + return -ENODATA; + *out = parsed; + return 0; +} + int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, const char **class, s64 *unit, bool h17p_keys) { @@ -1926,3 +2017,145 @@ int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mn return 0; } + +/* Inspect structure only. Never emit a firmware value or allocate from its size. */ +static int parse_identity_tree(struct dcp_parse_ctx *ctx, dcp_identity_key_fn emit, + void *data, u32 depth, u32 *budget) +{ + const struct dcp_parse_tag *tag; + u32 count; + + if (!*budget || depth > 4) + return -E2BIG; + --*budget; + tag = parse_tag(ctx); + if (IS_ERR(tag)) + return PTR_ERR(tag); + if (tag->type != DCP_TYPE_DICTIONARY && tag->type != DCP_TYPE_ARRAY) { + /* Reuse the bounded scalar decoder from the original position. */ + ctx->pos -= sizeof(*tag); + return skip(ctx); + } + count = tag->size; + if (count > *budget) + return -E2BIG; + for (u32 i = 0; i < count; i++) { + struct dcp_parse_ctx value; + const struct dcp_parse_tag *key_tag, *value_tag; + const char *key = "[]"; + u32 length = 2; + int ret; + + if (tag->type == DCP_TYPE_DICTIONARY) { + key_tag = parse_tag_of_type(ctx, DCP_TYPE_STRING); + if (IS_ERR(key_tag)) + return PTR_ERR(key_tag); + length = key_tag->size; + key = parse_bytes(ctx, length); + if (IS_ERR(key)) + return PTR_ERR(key); + } + value = *ctx; + value_tag = parse_tag(&value); + if (IS_ERR(value_tag)) + return PTR_ERR(value_tag); + emit(data, key, length, value_tag->type, value_tag->size, depth); + ret = parse_identity_tree(ctx, emit, data, depth + 1, budget); + if (ret) + return ret; + } + return 0; +} + +int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, void *data) +{ + struct dcp_parse_ctx ctx, root; + const struct dcp_parse_tag *tag; + u32 budget = 64; + int ret = parse(blob, len, &ctx); + + if (ret) + return ret; + root = ctx; + tag = parse_tag(&root); + if (IS_ERR(tag)) + return PTR_ERR(tag); + emit(data, "", 6, tag->type, tag->size, 0); + return parse_identity_tree(&ctx, emit, data, 0, &budget); +} + +/* Look for named EDID blobs inside bounded property dictionaries. */ +static int parse_panel_edid_value(struct dcp_parse_ctx *ctx, u32 depth, u32 *budget, + bool edid_key, const u8 **edid, size_t *size) +{ + const struct dcp_parse_tag *tag; + struct dcp_parse_ctx start = *ctx; + + if (depth > 4 || !*budget) + return -E2BIG; + --*budget; + tag = parse_tag(ctx); + if (IS_ERR(tag)) + return PTR_ERR(tag); + if (tag->type == DCP_TYPE_BLOB && edid_key) { + const u8 *data; + + if (tag->size < 128 || tag->size > 4096 || tag->size % 128) + return -EINVAL; + data = parse_bytes(ctx, tag->size); + if (IS_ERR(data)) + return PTR_ERR(data); + if (memcmp(data, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) || + (1U + data[126]) * 128 != tag->size) + return -EINVAL; + *edid = data; + *size = tag->size; + return 0; + } + if (tag->type == DCP_TYPE_DICTIONARY) { + if (tag->size > *budget) + return -E2BIG; + for (u32 i = 0; i < tag->size; i++) { + const struct dcp_parse_tag *key_tag; + const char *key; + bool match; + int ret; + + key_tag = parse_tag_of_type(ctx, DCP_TYPE_STRING); + if (IS_ERR(key_tag)) + return PTR_ERR(key_tag); + key = parse_bytes(ctx, key_tag->size); + if (IS_ERR(key)) + return PTR_ERR(key); + match = (key_tag->size == 4 && (!memcmp(key, "EDID", 4) || + !memcmp(key, "edid", 4))) || + (key_tag->size == 13 && !memcmp(key, "IODisplayEDID", 13)); + ret = parse_panel_edid_value(ctx, depth + 1, budget, match, edid, size); + if (ret != -ENOENT) + return ret; + } + return -ENOENT; + } + *ctx = start; + return skip(ctx) ?: -ENOENT; +} + +/* A setter may deliver raw bytes, a blob, or a dictionary containing a blob. */ +int parse_panel_edid_data(const void *blob, size_t len, const u8 **edid, size_t *size) +{ + struct dcp_parse_ctx ctx; + const u8 *data = blob; + u32 bytes, budget = 64; + int ret; + + if (len >= 128 && !memcmp(data, "\x00\xff\xff\xff\xff\xff\xff\x00", 8)) { + bytes = (1U + data[126]) * 128; + if (bytes > len || bytes > 4096) + return -EINVAL; + *edid = data; + *size = bytes; + return 0; + } + ret = parse(blob, len, &ctx); + return ret ?: parse_panel_edid_value(&ctx, 0, &budget, true, edid, size); +} diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 97a75a8b046a0..5e573371e0072 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -11,6 +11,10 @@ #include "dcp-metadata-epoch.h" struct apple_dcp; +int parse_panel_edid_data(const void *blob, size_t len, const u8 **edid, size_t *size); +typedef void (*dcp_identity_key_fn)(void *data, const char *key, u32 key_len, + u32 type, u32 size, u32 depth); +int parse_identity_keys(const void *blob, size_t len, dcp_identity_key_fn emit, void *data); struct apple_connector; struct apple_dcp_typec_route; @@ -21,6 +25,14 @@ struct dcp_parse_ctx { /* External H17P candidates require separate publication/lookup proof. */ bool automatic_1080p180_rgb8; bool ambiguous_180hz; + /* Desktop 26.6 uses zero placeholders for an unreported VRR range. */ + bool zero_vrr_range_fallback; + /* + * A desktop 26.x external processor: read the sink timing keys that + * describe the preferred timing for the firmware-derived EDID, without + * the H17P external color policy. + */ + bool metadata_timing; #if IS_ENABLED(CONFIG_DRM_APPLE_DCP_T6030_AUDIO) /* * T6030 external processor audio elements (DCP firmware 14.7, external @@ -120,10 +132,14 @@ struct dcp_display_mode { struct dcp_color_mode sdr; struct dcp_color_mode best; bool vrr; + /* ProMotion fixed timings still need presentation deadlines. */ + bool needs_timestamps; /* IsPreferred, outside the external SDR path */ bool preferred; bool metadata_timing_valid; bool automatic_rgb8_quirk; + /* The sync polarity flags of the timing, if it reports them */ + u32 metadata_sync_flags; /* * The best virtual color element of the timing, which the desktop * 26.x firmware selects first to bootstrap its generic pipe (valid if @@ -192,6 +208,9 @@ int replace_modes(struct dcp_parse_ctx *handle, struct dcp_display_mode **modes, unsigned int *count, int width_mm, int height_mm, unsigned int notch_height, bool internal); +int parse_panel_metadata(struct dcp_parse_ctx *handle, struct dcp_display_metadata *out); +int parse_panel_product_metadata(struct dcp_parse_ctx *handle, + struct dcp_display_metadata *out); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm, bool *backlight_control); int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, diff --git a/drivers/gpu/drm/apple/parser_mode_test.c b/drivers/gpu/drm/apple/parser_mode_test.c index 40d0c8d9cd196..877d012740df1 100644 --- a/drivers/gpu/drm/apple/parser_mode_test.c +++ b/drivers/gpu/drm/apple/parser_mode_test.c @@ -7,6 +7,7 @@ #include #include "parser.h" +#include "dcp-refresh.h" struct dcp_mode_rank_case { const char *desc; @@ -40,7 +41,34 @@ static void dcp_mode_rank_test(struct kunit *test) c->above); } +static void dcp_refresh_range_test(struct kunit *test) +{ + u32 low = 0, high = 0; + + KUNIT_EXPECT_TRUE(test, dcp_refresh_range(120U << 16, 0, 0, + true, &low, &high)); + KUNIT_EXPECT_EQ(test, low, 24U << 16); + KUNIT_EXPECT_EQ(test, high, 120U << 16); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(60U << 16, 0, 0, + true, &low, &high)); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 0, 0, + false, &low, &high)); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 0, 120U << 16, + true, &low, &high)); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 24U << 16, + 144U << 16, true, &low, &high)); + KUNIT_EXPECT_FALSE(test, dcp_refresh_range(120U << 16, 120U << 16, + 24U << 16, true, &low, &high)); + KUNIT_EXPECT_TRUE(test, dcp_refresh_range(120U << 16, 48U << 16, + 120U << 16, true, &low, &high)); + KUNIT_EXPECT_EQ(test, low, 48U << 16); + KUNIT_EXPECT_EQ(test, dcp_desktop_present_deadline(true, true), 120ULL); + KUNIT_EXPECT_EQ(test, dcp_desktop_present_deadline(false, true), 0ULL); + KUNIT_EXPECT_EQ(test, dcp_desktop_present_deadline(true, false), 0ULL); +} + static struct kunit_case dcp_mode_rank_test_cases[] = { + KUNIT_CASE(dcp_refresh_range_test), KUNIT_CASE_PARAM(dcp_mode_rank_test, dcp_mode_rank_gen_params), {} }; diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index a32f0a0bcf6a0..44e239c6016b0 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -75,7 +75,7 @@ static int powerlog_report(struct apple_epic_service *service, enum epic_subtype struct apple_dcp *dcp = service->ep->dcp; int ret; - /* Later reports must not overwrite a latched hint or Linux's target. */ + /* Keep the existing filtering on active non-desktop backends. */ if (dcp_backlight_active(dcp)) return 0; @@ -100,8 +100,9 @@ static int powerlog_report(struct apple_epic_service *service, enum epic_subtype dev_dbg(dcp->dev, "systemep: mNits event: Nits: %u.%03u, iDAC: %u\n", mnits.millinits / 1000, mnits.millinits % 1000, mnits.idac); - if (dcp->fw_compat == DCP_FIRMWARE_H17P && - dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { + if ((dcp->fw_compat == DCP_FIRMWARE_H17P && + dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) || + dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { if (!dcp_has_panel(dcp)) return 0; ret = dcp_backlight_takeover(dcp, mnits.millinits); diff --git a/drivers/gpu/drm/asahi/Makefile b/drivers/gpu/drm/asahi/Makefile index 0523740332290..fd8421f27590d 100644 --- a/drivers/gpu/drm/asahi/Makefile +++ b/drivers/gpu/drm/asahi/Makefile @@ -1,7 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_ASAHI) += asahi.o -asahi-y := asahi_core.o fwid.o sysfs.o +asahi-y := asahi_core.o fwid.o sysfs.o g17/sync_input.o g17/sync_output.o g17/sync_scope.o g17/trace.o g17/clpc.o +CFLAGS_g17/trace.o := -I$(src)/g17 asahi-$(CONFIG_DRM_ASAHI_G15) += g15/glue.o diff --git a/drivers/gpu/drm/asahi/driver.rs b/drivers/gpu/drm/asahi/driver.rs index addabd04fe701..ab68573881659 100644 --- a/drivers/gpu/drm/asahi/driver.rs +++ b/drivers/gpu/drm/asahi/driver.rs @@ -346,6 +346,8 @@ impl AsahiPlatformDriver { _device: reg.device().into(), _reg: reg, g17: None, + #[cfg(CONFIG_DRM_ASAHI_G16)] + g16: None, raw_dev: core::ptr::null_mut(), raw_stats: core::ptr::null_mut(), stats: None, diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs index a2b17082494cb..01598b691ded3 100644 --- a/drivers/gpu/drm/asahi/file.rs +++ b/drivers/gpu/drm/asahi/file.rs @@ -127,10 +127,18 @@ pub(crate) struct SyncItem { pub(crate) fence: Option, pub(crate) chain_fence: Option, pub(crate) timeline_value: u64, + pub(crate) cumulative: Option, + pub(crate) timeline: bool, } impl SyncItem { - fn parse_one(file: &DrmFile, data: uapi::drm_asahi_sync, out: bool) -> Result { + fn parse_one(file: &DrmFile, mut data: uapi::drm_asahi_sync, out: bool, + bundled: bool) -> Result + { + if data.sync_type == crate::g17::sync_output::SYNC_CUMULATIVE { + if !out || !bundled || data.timeline_value == 0 { return Err(EINVAL); } + data.sync_type = uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ; + } match data.sync_type { uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_SYNCOBJ => { if data.timeline_value != 0 { @@ -151,6 +159,8 @@ impl SyncItem { syncobj, chain_fence: None, timeline_value: data.timeline_value, + cumulative: None, + timeline: false, }) } uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ => { @@ -173,12 +183,10 @@ impl SyncItem { Ok(SyncItem { fence, syncobj, - chain_fence: if out { - Some(dma_fence::FenceChain::new()?) - } else { - None - }, + chain_fence: None, timeline_value: data.timeline_value, + cumulative: None, + timeline: true, }) } _ => { @@ -193,12 +201,14 @@ impl SyncItem { ptr: u64, in_count: u32, out_count: u32, + allow_cumulative: bool, ) -> Result> { let count = in_count.checked_add(out_count).ok_or(EOVERFLOW)?; const STRIDE: usize = core::mem::size_of::(); let size = STRIDE.checked_mul(count as usize).ok_or(EOVERFLOW)?; let mut vec = KVec::with_capacity(count as usize, GFP_KERNEL)?; + let mut anchor = None; // SAFETY: We only read this once, so there are no TOCTOU issues. let mut reader = UserSlice::new(UserPtr::from_addr(ptr as _), size).reader(); @@ -214,7 +224,25 @@ impl SyncItem { // SAFETY: All bit patterns in the struct are valid let sync = unsafe { sync.assume_init() }; - vec.push(SyncItem::parse_one(file, sync, i >= in_count)?, GFP_KERNEL)?; + if sync.sync_type == crate::g17::sync_output::SYNC_CUMULATIVE { + if !allow_cumulative || i < in_count || anchor.is_some() { + return Err(EINVAL); + } + anchor = Some(i as usize); + } + vec.push(Self::parse_one(file, sync, i >= in_count, + allow_cumulative)?, GFP_KERNEL)?; + } + if let Some(anchor) = anchor { + let bundle = crate::g17::sync_output::SignalBundle::new( + &vec[in_count as usize..], anchor - in_count as usize)?; + vec[anchor].cumulative = Some(bundle); + } else { + for sync in &mut vec[in_count as usize..] { + if sync.timeline { + sync.chain_fence = Some(dma_fence::FenceChain::new()?); + } + } } Ok(vec) @@ -1398,7 +1426,8 @@ impl File { copy_commands(&mut vec)?; } let syncs = - SyncItem::parse_array(file, data.syncs, data.in_sync_count, data.out_sync_count)?; + SyncItem::parse_array(file, data.syncs, data.in_sync_count, data.out_sync_count, + gpu.as_any().is::())?; mod_dev_dbg!( device, diff --git a/drivers/gpu/drm/asahi/float.rs b/drivers/gpu/drm/asahi/float.rs index 46c05699d7722..224e92c6d97e2 100644 --- a/drivers/gpu/drm/asahi/float.rs +++ b/drivers/gpu/drm/asahi/float.rs @@ -44,6 +44,11 @@ struct F32U { } impl F32 { + /// Return the IEEE-754 binary32 representation. + pub(crate) const fn to_bits(self) -> u32 { + self.0 + } + /// Convert a raw 32-bit representation into an F32 pub(crate) const fn from_bits(u: u32) -> F32 { F32(u) diff --git a/drivers/gpu/drm/asahi/fw/workqueue.rs b/drivers/gpu/drm/asahi/fw/workqueue.rs index b86bd3ff3757a..78ac6c676a6eb 100644 --- a/drivers/gpu/drm/asahi/fw/workqueue.rs +++ b/drivers/gpu/drm/asahi/fw/workqueue.rs @@ -152,7 +152,7 @@ pub(crate) mod raw { pub(crate) unk_94: u32, pub(crate) pending: AtomicU32, pub(crate) unk_9c: u32, - pub(crate) gpu_context: GpuPointer<'a, super::GpuContextData>, + pub(crate) gpu_context: GpuWeakPointer, pub(crate) unk_a8: U64, #[ver(V >= V13_2 && G < G14X)] pub(crate) unk_b0: u32, @@ -171,6 +171,10 @@ impl GpuStruct for GpuContextData { type Raw<'a> = raw::GpuContextData; } +/// The firmware structures of a work queue. +/// +/// The raw structure points to the queue's GPU context weakly. The `GpuContext` keeps these +/// structures until it has been invalidated, and only then frees its own data. #[versions(AGX)] #[derive(Debug)] pub(crate) struct QueueInfo { @@ -178,7 +182,6 @@ pub(crate) struct QueueInfo { pub(crate) ring: GpuArray, pub(crate) gpu_buf: GpuArray, pub(crate) notifier_list: Arc>, - pub(crate) gpu_context: Arc, } #[versions(AGX)] diff --git a/drivers/gpu/drm/asahi/fwid.rs b/drivers/gpu/drm/asahi/fwid.rs index 36a6fa45581be..be81eac5dfb74 100644 --- a/drivers/gpu/drm/asahi/fwid.rs +++ b/drivers/gpu/drm/asahi/fwid.rs @@ -103,6 +103,20 @@ fn find_entropy(image: &[u8]) -> Option<[u32; 2]> { Some(found) } +/// Finds an exact identity, measuring each candidate at its own entropy offsets. +fn lookup( + builds: &'static [FirmwareBuild], + text_size: u64, + data_size: u64, + mut digest: impl FnMut([u32; 2]) -> Option<[u8; 32]>, +) -> Option<&'static FirmwareBuild> { + builds.iter().find(|build| { + build.text_size == text_size + && build.data_size == data_size + && digest(build.entropy) == Some(build.text_sha256) + }) +} + /// Identifies the firmware build loaded at `text` (DATA `data_size` bytes) among `builds`, /// before any firmware-visible write. pub(crate) fn identify( @@ -118,19 +132,20 @@ pub(crate) fn identify( dev_err!(dev, "GPU firmware TEXT changed while it was measured\n"); return Err(EAGAIN); } - for build in builds { - if build.text_size == size as u64 - && build.data_size == data_size - && digest_at(&mut copy, build.entropy) == Some(build.text_sha256) - { - dev_info!(dev, "GPU firmware build: {}\n", build.name); - return Ok(build); - } + if let Some(build) = lookup(builds, size as u64, data_size, |at| { + digest_at(&mut copy, at) + }) { + dev_info!(dev, "GPU firmware build: {}\n", build.name); + return Ok(build); } let Some((entropy, digest)) = find_entropy(©).and_then(|at| Some((at, digest_at(&mut copy, at)?))) else { - dev_err!(dev, "unsupported GPU firmware build: TEXT {:#x} has no entropy records\n", size); + dev_err!( + dev, + "unsupported GPU firmware build: TEXT {:#x} has no entropy records\n", + size + ); return Err(EINVAL); }; dev_err!( @@ -185,4 +200,74 @@ mod tests { assert_eq!(image, original); assert_eq!(find_entropy(&image), None); } + + fn measured( + builds: &'static [FirmwareBuild], + data_size: u64, + entropy: [u32; 2], + digest: [u8; 32], + ) -> Option<&'static FirmwareBuild> { + lookup(builds, 0x60000, data_size, |at| { + (at == entropy).then_some(digest) + }) + } + + #[test] + fn t6040_accepts_all_loaded_images() { + let crate::g17::fw::variant::Firmware::Identified(builds) = + crate::hw::t6040::CONFIG.firmware + else { + panic!("T6040 must identify its loaded firmware"); + }; + let build = builds.iter().find(|b| b.os_version == [26, 6, 2]).unwrap(); + let digests = [ + [ + 0x03, 0xdf, 0x75, 0x48, 0x4a, 0x7f, 0xdd, 0x6e, 0x4b, 0x47, 0x7f, 0x95, 0x6c, 0xaa, + 0x5b, 0x44, 0x06, 0xdf, 0xff, 0x4d, 0x19, 0x17, 0x28, 0x62, 0x7f, 0x53, 0x10, 0xe0, + 0x63, 0x87, 0x6d, 0xcd, + ], + [ + 0x19, 0x0c, 0xef, 0x19, 0x46, 0xa2, 0x3d, 0xfb, 0xde, 0x18, 0x05, 0x92, 0x62, 0x96, + 0x31, 0xc3, 0xb2, 0x8d, 0x05, 0x09, 0xa1, 0x7e, 0xb7, 0x61, 0x08, 0xb7, 0xc9, 0xa5, + 0x2a, 0xcc, 0xd9, 0xac, + ], + [ + 0xd7, 0x25, 0x26, 0x4d, 0x48, 0x31, 0xb9, 0x5a, 0x5b, 0x8a, 0x37, 0x5a, 0xbf, 0x2f, + 0xb5, 0xcd, 0xfb, 0xd6, 0xd7, 0xcb, 0xc7, 0xf1, 0xba, 0xa2, 0xcc, 0x8b, 0x37, 0xee, + 0x5d, 0x6e, 0x5e, 0x98, + ], + ]; + for (i, digest) in digests.into_iter().enumerate() { + let found = measured(build.images, 0x138000, [0x59839, 0x5991c], digest).unwrap(); + assert!(core::ptr::eq(found, &build.images[i])); + assert!(measured(build.images, 0x13c000, [0x59839, 0x5991c], digest).is_none()); + assert!(measured(build.images, 0x138000, [0x5c8df, 0x5c9c2], digest).is_none()); + assert!(lookup(build.images, 0x60001, 0x138000, |_| Some(digest)).is_none()); + } + assert!(measured(build.images, 0x138000, [0x59839, 0x5991c], [0; 32]).is_none()); + } + + #[cfg(CONFIG_DRM_ASAHI_G16)] + #[test] + fn j713_accepts_both_loaded_images() { + let builds = crate::g16::hw::t8132::CONFIG.firmware; + let digests = [ + [ + 0x5f, 0x65, 0xf6, 0x14, 0x58, 0x72, 0x54, 0x70, 0xb4, 0xb0, 0xc0, 0x20, 0x28, 0x47, + 0x7b, 0xf2, 0xea, 0x57, 0x3a, 0xfb, 0x4e, 0x0c, 0x49, 0x6e, 0xf9, 0x95, 0xe9, 0x6a, + 0xff, 0xb0, 0xd8, 0x6c, + ], + [ + 0x3b, 0x36, 0x8e, 0xe4, 0x2d, 0xcc, 0xc6, 0x26, 0xe8, 0x49, 0x67, 0xff, 0x40, 0x87, + 0x64, 0x71, 0x0a, 0x64, 0xbf, 0xf1, 0x59, 0x4f, 0x3c, 0xfd, 0x2c, 0x6e, 0x4d, 0x34, + 0xd0, 0x65, 0x24, 0x8b, + ], + ]; + for (i, digest) in digests.into_iter().enumerate() { + let found = measured(builds, 0x13c000, [0x5c8df, 0x5c9c2], digest).unwrap(); + assert!(core::ptr::eq(found, &builds[i])); + assert!(measured(builds, 0x138000, [0x5c8df, 0x5c9c2], digest).is_none()); + } + assert!(measured(builds, 0x13c000, [0x5c8df, 0x5c9c2], [0; 32]).is_none()); + } } diff --git a/drivers/gpu/drm/asahi/g15/boot.rs b/drivers/gpu/drm/asahi/g15/boot.rs index ddcf5efef4c3d..7a06aa64ffbc3 100644 --- a/drivers/gpu/drm/asahi/g15/boot.rs +++ b/drivers/gpu/drm/asahi/g15/boot.rs @@ -306,10 +306,12 @@ unsafe extern "C" fn rtkit_shmem_destroy( /// The GPU's RTKit callbacks. static RTKIT_OPS: bindings::apple_rtkit_ops = bindings::apple_rtkit_ops { flags: 0, + protocol_version: 0, crashed: Some(rtkit_crashed), recv_message: Some(rtkit_recv), recv_message_early: Some(rtkit_recv_early), shmem_setup: Some(rtkit_shmem_setup), + shmem_setup_ep: None, shmem_destroy: Some(rtkit_shmem_destroy), syslog_is_debug: None, }; diff --git a/drivers/gpu/drm/asahi/g15/command.rs b/drivers/gpu/drm/asahi/g15/command.rs index 45b65d1ddc026..6fc1e03fb637c 100644 --- a/drivers/gpu/drm/asahi/g15/command.rs +++ b/drivers/gpu/drm/asahi/g15/command.rs @@ -361,6 +361,7 @@ impl Timestamps<'_> { .ok_or(ENOENT)? .clone(); let file::Object::TimestampBuffer(mapping) = object; + let mapping = mapping.firmware_mapping()?; let offset = timestamp.offset as usize; if offset % 8 != 0 || offset >= mapping.size() || mapping.size() - offset < 8 { return Err(EINVAL); diff --git a/drivers/gpu/drm/asahi/g15/uat.rs b/drivers/gpu/drm/asahi/g15/uat.rs index 854ff34e729a3..d6971489ee80f 100644 --- a/drivers/gpu/drm/asahi/g15/uat.rs +++ b/drivers/gpu/drm/asahi/g15/uat.rs @@ -96,7 +96,7 @@ fn with_table( // SAFETY: Every table walked here is a page the shared page-table code allocated in RAM // (`Page::alloc_page`) and never frees while the firmware runs (tables are not freed on // unmap, and these VMs are retained until reboot). - unsafe { pgtable::with_root_entries(pa, count, f) } + unsafe { pgtable::with_root_entries(None, pa, count, f) } } /// Cleans `len` bytes of the RAM table page at `pa`, from entry `first`, to the point of diff --git a/drivers/gpu/drm/asahi/g17/availability.rs b/drivers/gpu/drm/asahi/g17/availability.rs new file mode 100644 index 0000000000000..8014266f0b0ab --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/availability.rs @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Resource-specific scheduler waits. A caller samples a domain before a failed try, then +//! registers against that sample after rolling back unpublished claims; the release that frees +//! the resource bumps the generation and signals exactly the waiters of its own domain. +//! Successful acquisition never allocates a fence, and a release without waiters costs one +//! lock round trip. + +use super::fence::{self, CompletionFence}; +use kernel::{ + c_str, + dma_fence::{Fence, FenceContexts, RawDmaFence, UserFence}, + new_mutex, + prelude::*, + sync::{Arc, Mutex}, +}; + +struct Entry { + fence: UserFence, + next: Option>, +} + +struct State { + generation: u64, + waiters: Option>, +} + +#[pin_data] +pub(crate) struct Availability { + #[pin] + state: Mutex, + fences: FenceContexts, +} + +impl Availability { + pub(crate) fn new() -> Result> { + Arc::pin_init( + try_pin_init!(Self { + state <- new_mutex!( + State { generation: 0, waiters: None }, + "g17::Availability" + ), + fences: FenceContexts::new(1, c_str!("asahi_resource"), kernel::static_lock_class!())?, + }), + GFP_KERNEL, + ) + } + + /// Sample before checking capacity under the resource's own synchronization. + pub(crate) fn observe(&self) -> u64 { + self.state.lock().generation + } + + pub(crate) fn has_waiters(&self) -> bool { + self.state.lock().waiters.is_some() + } + + /// A fence the next notification signals. None means capacity changed after the sample: + /// retry instead of waiting. Called with no resource lock held. The recheck after allocation + /// closes both the failed-check/registration and the fence-allocation/release races. A + /// waiter that goes away leaves its entry for the next notification to signal. + pub(crate) fn wait(&self, observed: u64) -> Result> { + if self.observe() != observed { + return Ok(None); + } + let fence = fence::independent(&self.fences)?; + let mut entry = KBox::new( + Entry { fence: fence.clone(), next: None }, + GFP_KERNEL, + )?; + let mut state = self.state.lock(); + if state.generation != observed { + return Ok(None); + } + entry.next = state.waiters.take(); + state.waiters = Some(entry); + Ok(Some(Fence::from_fence(&fence))) + } + + /// Blocks until a notification after `observed`, for at most `timeout_ms`. For callers + /// outside the scheduler that own no other wait. + pub(crate) fn block(&self, observed: u64, timeout_ms: u32, interruptible: bool) -> Result { + let Some(fence) = self.wait(observed)? else { return Ok(()); }; + // SAFETY: The owned fence keeps the raw DMA fence alive throughout the wait. + let waited = unsafe { + kernel::bindings::dma_fence_wait_timeout( + fence.raw(), + interruptible, + kernel::time::msecs_to_jiffies(timeout_ms) as isize, + ) + }; + if waited < 0 { + return Err(Error::from_errno(waited as i32)); + } + Ok(()) + } + + /// Wakes every waiter of this domain. Call after dropping the resource's own lock; fence + /// callbacks run outside the domain lock. + pub(crate) fn notify(&self) { + let mut waiters = { + let mut state = self.state.lock(); + state.generation = state.generation.wrapping_add(1); + state.waiters.take() + }; + while let Some(mut entry) = waiters { + waiters = entry.next.take(); + entry.fence.signal(); + } + } +} diff --git a/drivers/gpu/drm/asahi/g17/buffer.rs b/drivers/gpu/drm/asahi/g17/buffer.rs index 8f3a03fbb8dba..fd6460db2d1fb 100644 --- a/drivers/gpu/drm/asahi/g17/buffer.rs +++ b/drivers/gpu/drm/asahi/g17/buffer.rs @@ -677,6 +677,22 @@ impl Manager { pub(crate) fn blocks(&self) -> usize { self.tvb.blocks() } + /// Read-only diagnostic while firmware is halted. In particular this + /// does not reset the high-water counter as automatic_target() does. + pub(crate) fn report_recovery(&self) { + let read = |offset| -> Result { + Ok(self.graph.word(offset)?.load(Ordering::Relaxed)) + }; + pr_warn!("G17: recovery tvb id={} blocks={} cursors={:?} pages={:?} capacity={:?} producer={:?} consumer={:?} max_pages={:?} reset={:?} initialized={}\n", + self.buffer_id, self.blocks(), self.cursors(), + read(core::mem::offset_of!(fw::State, pages)), + read(core::mem::offset_of!(fw::State, block_capacity)), + read(core::mem::offset_of!(fw::State, producer)), + read(core::mem::offset_of!(fw::State, consumer)), + read(fw::CONTROL + core::mem::offset_of!(fw::Control, max_pages)), + read(fw::CONTROL + core::mem::offset_of!(fw::Control, reset)), + self.initialized); + } pub(crate) fn state_va(&self) -> u64 { self.graph.gpu_va() } diff --git a/drivers/gpu/drm/asahi/g17/buffer/target.rs b/drivers/gpu/drm/asahi/g17/buffer/target.rs index 64c88da8eca14..42a2c04b40112 100644 --- a/drivers/gpu/drm/asahi/g17/buffer/target.rs +++ b/drivers/gpu/drm/asahi/g17/buffer/target.rs @@ -139,6 +139,16 @@ impl Layout { }) } pub(crate) fn tpc_capacity(&self) -> Result { + // TPC_MAX bounds the tail-pointer cache itself. On 25G83 this object + // also contains GTP sub-buffers, notably 64 times the tile map. Applying + // the cache limit to their combined extent rejects valid large targets. + // Keep the original cache and tile-record limits; they also bound the + // derived auxiliary extent before any backing allocation or replacement. + if aligned(self.tpc_size, mmu::UAT_PGSZ)?.max(cfg::TPC_MIN) > cfg::TPC_MAX + || self.record_stride()? > cfg::TARGET_RECORD_MAX + { + return Err(ERANGE); + } Ok(aligned(self.tpc_end, mmu::UAT_PGSZ)?.max(cfg::TPC_MIN)) } pub(crate) fn record_stride(&self) -> Result { @@ -188,7 +198,7 @@ impl Target { pub(crate) fn new(dev: &AsahiDrmDevice, vm: &mmu::Vm, layout: &Layout) -> Result> { let tpc = layout.tpc_capacity()?; let stride = layout.record_stride()?; - if tpc > cfg::TPC_MAX || stride > cfg::TARGET_RECORD_MAX { + if stride > cfg::TARGET_RECORD_MAX { return Err(ERANGE); } let records = Object::new(dev, vm, stride * DEPTH)?; @@ -272,9 +282,6 @@ impl Target { let stride = layout.record_stride()?; let mut grew = false; if tpc > self.tpc.backing.size() { - if tpc > cfg::TPC_MAX { - return Err(ERANGE); - } Self::replace(&mut self.retired, &mut self.tpc, dev, vm, tpc, owners)?; grew = true; } @@ -350,3 +357,59 @@ impl Target { } } } + + +#[kernel::macros::kunit_tests(asahi_g17_target_capacity)] +mod tests { + use super::*; + + fn pass(size: u32, utile: u8) -> RenderPass { + // SAFETY: RenderPass and its nested surfaces/programs contain only + // integers and bools, for which the all-zero representation is valid. + let mut pass: RenderPass = unsafe { core::mem::zeroed() }; + pass.width = size; + pass.height = size; + pass.layers = 1; + pass.utile_width = utile; + pass.utile_height = utile; + pass + } + + #[test] + fn t6040_auxiliary_tiler_storage_is_not_tail_pointer_cache() { + // CTS point_size_10000 renders to 10240 squared. Its GTP sub-buffer + // is required by the existing 25G83 program, not a larger cache. + for utile in [16, 32] { + let layout = Layout::new(&pass(10240, utile), 2, RegisterPrograms::V25G83).unwrap(); + assert!(layout.tpc_size < cfg::TPC_MAX); + assert!(layout.record_stride().unwrap() < cfg::TARGET_RECORD_MAX); + assert!(layout.tpc_end > cfg::TPC_MAX); + assert!(layout.tpc_capacity().unwrap() >= layout.tpc_end); + assert_eq!(layout.gtp[0], Some(0)); + assert!(layout.gtp[1].unwrap() >= layout.tpc_size); + } + } + + #[test] + fn core_and_tile_record_limits_still_reject_oversized_targets() { + let mut layout = Layout::new(&pass(10240, 32), 2, RegisterPrograms::V25G83).unwrap(); + layout.tpc_size = cfg::TPC_MAX + 1; + assert_eq!(layout.tpc_capacity(), Err(ERANGE)); + let mut layered = pass(16384, 16); + layered.layers = 2; + let layout = Layout::new(&layered, 2, RegisterPrograms::V25G83).unwrap(); + assert!(layout.record_stride().unwrap() > cfg::TARGET_RECORD_MAX); + assert_eq!(layout.tpc_capacity(), Err(ERANGE)); + } + + #[test] + fn neo_keeps_the_same_cache_extent_and_limit() { + let mut layout = Layout::new(&pass(10240, 32), 1, RegisterPrograms::G17P).unwrap(); + assert!(layout.gtp.iter().all(Option::is_none)); + assert_eq!(layout.tpc_capacity().unwrap(), + aligned(layout.tpc_size, mmu::UAT_PGSZ).unwrap().max(cfg::TPC_MIN)); + layout.tpc_size = cfg::TPC_MAX + 1; + layout.tpc_end = layout.tpc_size; + assert_eq!(layout.tpc_capacity(), Err(ERANGE)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/calibration.rs b/drivers/gpu/drm/asahi/g17/calibration.rs new file mode 100644 index 0000000000000..704a91f49b0c1 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/calibration.rs @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Per-chip T6040 power data supplied by the boot loader. + +use super::fw::initdata::{Opp, PerfConfig, PERF_STATES}; +use crate::driver::AsahiDrmDevice; +use kernel::{c_str, of, prelude::*, str::CStr}; + +/// Validated calibration of the running chip. There is no built-in fallback. +pub(super) struct Calibration { + pub(super) perf: PerfConfig, + pub(super) leakage: [u32; 56], + pub(super) max_power_mw: u32, + core_mask: u32, + base: usize, +} + +fn words(node: &of::Node, name: &CStr) -> Result<[u32; N]> { + let values: KVec = node.get_property(name)?; + values.as_slice().try_into().map_err(|_| EINVAL) +} + +// Raw ADT OPP slots and SGX register slots number 16. The supported firmware consumes +// the 11 populated selectors; the remaining handoff entries must all be zero. +const RAW_STATES: usize = 16; + +fn active(values: [u32; RAW_STATES]) -> Result<[u32; PERF_STATES]> { + if values[PERF_STATES..].iter().any(|&v| v != 0) { + return Err(EINVAL); + } + values[..PERF_STATES].try_into().map_err(|_| EINVAL) +} + +fn map(values: [u32; RAW_STATES]) -> Result<[u8; PERF_STATES]> { + let values = active(values)?; + if values[0] != 0 + || values[PERF_STATES - 1] != RAW_STATES as u32 - 1 + || values.iter().any(|&v| v >= RAW_STATES as u32) + || values.windows(2).any(|p| p[0] >= p[1]) + { + return Err(EINVAL); + } + Ok(values.map(|v| v as u8)) +} + +fn relative(values: &[u32; PERF_STATES]) -> bool { + values[0] == 0 + && values[PERF_STATES - 1] == 100 + && values.windows(2).all(|p| p[0] <= p[1] && p[1] <= 100) +} + +impl Calibration { + pub(super) fn read(dev: &AsahiDrmDevice) -> Result { + let node = dev.as_ref().as_ref().of_node().ok_or(EIO)?; + if node.get_property::(c_str!("apple,t6040-handoff-version"))? != 2 { + return Err(EINVAL); + } + let core_mask = node.get_property(c_str!("apple,core-mask"))?; + let max_power_mw = node.get_property(c_str!("apple,max-power-mw"))?; + let base = node.get_property::(c_str!("apple,perf-base-pstate"))? as usize; + if core_mask == 0 + || core_mask & !0xfffff != 0 + || !(1..=200_000).contains(&max_power_mw) + || base != 1 + { + return Err(EINVAL); + } + let perf = PerfConfig { + states_a: map(words(&node, c_str!("apple,perf-map-a"))?)?, + states_b: map(words(&node, c_str!("apple,perf-map-b"))?)?, + relative_a: active(words(&node, c_str!("apple,perf-relative-a"))?)?, + relative_b: active(words(&node, c_str!("apple,perf-relative-b"))?)?, + sram_min_mv: 810, + }; + if !relative(&perf.relative_a) + || !relative(&perf.relative_b) + || perf.relative_b[..=base].iter().any(|&v| v != 0) + { + return Err(EINVAL); + } + let core = words::<2>(&node, c_str!("apple,core-leak-coef"))?; + let sram = words::<2>(&node, c_str!("apple,sram-leak-coef"))?; + let cs = words::<1>(&node, c_str!("apple,cs-leak-coef"))?; + let afr = words::<1>(&node, c_str!("apple,afr-leak-coef"))?; + // Positive finite binary32 values. Integer comparisons preserve their ordering. + if Iterator::chain( + Iterator::chain(Iterator::chain(core.iter(), &sram), &cs), + &afr, + ) + .any(|&v| !(0x3f80_0000..0x4780_0000).contains(&v)) + { + return Err(EINVAL); + } + let mut leakage = [0; 56]; + leakage[..2].copy_from_slice(&core); + leakage[32..34].copy_from_slice(&sram); + leakage[52] = cs[0]; + leakage[54] = afr[0]; + Ok(Self { + perf, + leakage, + max_power_mw, + core_mask, + base, + }) + } + + /// Check the loader's mask against the GPU before starting its firmware. + pub(super) fn check_cores(&self, masks: &[u32]) -> Result { + if masks != [self.core_mask & 0x3ff, self.core_mask >> 10] { + return Err(EINVAL); + } + Ok(()) + } + + /// The performance maps index OPPs in ADT order, rather than sorted frequency order. + pub(super) fn check_opps(&self, opps: &[Opp]) -> Result { + if opps.len() != 16 || opps[0].freq_hz != 0 || opps.iter().any(|opp| opp.cells != 2) { + return Err(EINVAL); + } + for map in [&self.perf.states_a, &self.perf.states_b] { + let mut previous = 0; + for &index in &map[1..] { + let opp = &opps[usize::from(index)]; + let mhz = opp.freq_hz / 1_000_000; + if mhz <= previous + || opp.volt_uv[..2] + .iter() + .any(|v| !(400_000..=1_300_000).contains(v)) + { + return Err(EINVAL); + } + previous = mhz; + } + } + let base = opps[usize::from(self.perf.states_a[self.base])].freq_hz / 1_000_000; + let top = opps[usize::from(self.perf.states_a[PERF_STATES - 1])].freq_hz / 1_000_000; + for (i, &index) in self.perf.states_a.iter().enumerate() { + let freq = opps[usize::from(index)].freq_hz / 1_000_000; + let expected = if i < self.base { + 0 + } else { + 100 * (freq - base) / (top - base) + }; + if u64::from(self.perf.relative_b[i]) != expected { + return Err(EINVAL); + } + } + Ok(()) + } +} + +#[kunit_tests(asahi_t6040_calibration)] +mod tests { + use super::*; + + fn chip() -> Calibration { + Calibration { + perf: PerfConfig { + states_a: map([0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15, 0, 0, 0, 0, 0]).unwrap(), + states_b: map([0, 1, 2, 3, 4, 6, 7, 9, 11, 13, 15, 0, 0, 0, 0, 0]).unwrap(), + relative_a: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], + relative_b: [0, 0, 7, 14, 21, 35, 50, 64, 78, 92, 100], + sram_min_mv: 810, + }, + leakage: [0; 56], + max_power_mw: 70_000, + core_mask: 0xfffff, + base: 1, + } + } + + #[test] + fn operating_points_and_mask_must_match_the_handoff() { + let chip = chip(); + let mut opps = [Opp { + freq_hz: 0, + volt_uv: [800_000; super::super::fw::initdata::OPP_CELLS], + cells: 2, + }; 16]; + for (i, opp) in opps.iter_mut().enumerate() { + opp.freq_hz = i as u64 * 100_000_000; + } + assert!(chip.check_opps(&opps).is_ok()); + opps[0].freq_hz = 100_000_000; + assert!(chip.check_opps(&opps).is_err()); + opps[0].freq_hz = 0; + assert!(chip.check_cores(&[0x3ff, 0x3ff]).is_ok()); + assert!(chip.check_cores(&[0x3ff, 0x1ff]).is_err()); + assert!(chip.check_cores(&[0xfffff]).is_err()); + // Both eleven-state maps must reach raw OPP 15, including its second voltage lane. + assert_eq!(chip.perf.states_a[PERF_STATES - 1], 15); + assert_eq!(chip.perf.states_b[PERF_STATES - 1], 15); + opps[15].volt_uv[1] = 0; + assert!(chip.check_opps(&opps).is_err()); + opps[15].volt_uv[1] = 800_000; + assert!(chip.check_opps(&opps).is_ok()); + opps[6].volt_uv[1] = 0; + assert!(chip.check_opps(&opps).is_err()); + opps[6].volt_uv[1] = 800_000; + opps[6].freq_hz = opps[4].freq_hz; + assert!(chip.check_opps(&opps).is_err()); + opps[6].freq_hz = 601_000_000; + assert!(chip.check_opps(&opps).is_ok()); + opps[6].freq_hz = 611_000_000; + assert!(chip.check_opps(&opps).is_err()); + } + + #[test] + fn maps_refuse_off_repeats_and_out_of_range() { + let valid = [0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15, 0, 0, 0, 0, 0]; + assert!(map(valid).is_ok()); + for (index, value) in [ + (0, 1), + (1, 0), + (5, 4), + (10, 16), + (10, u32::MAX), + (10, 14), + (11, 1), + (15, 15), + ] { + let mut bad = valid; + bad[index] = value; + assert!(map(bad).is_err()); + } + } + + #[test] + fn relative_tables_are_bounded_and_monotonic() { + let mut values = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; + assert!(relative(&values)); + values[5] = 101; + assert!(!relative(&values)); + values[5] = 39; + assert!(!relative(&values)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/capacity.rs b/drivers/gpu/drm/asahi/g17/capacity.rs index bcbbb2e7e875d..57406e5487a37 100644 --- a/drivers/gpu/drm/asahi/g17/capacity.rs +++ b/drivers/gpu/drm/asahi/g17/capacity.rs @@ -55,6 +55,11 @@ impl kernel::debugfs::Writer for View { let state = self.0.state.lock(); let Some(firmware) = state.as_deref() else { return writeln!(f, "online=0"); }; writeln!(f, "online=1")?; + let (converted, published) = super::sync_input::counters(); + writeln!(f, "fw_inputs converted={} published={} native={} pending={}", + converted, published, super::sync_input::native_count(), + super::sync_input::pending_count())?; + writeln!(f, "compute_admission_demand={}", self.0.compute_demand.load(Ordering::Acquire))?; firmware.queues.write_capacity(f)?; firmware.pool.write_capacity(f)?; writeln!(f, "render_pool_ids used={} total=127", firmware.render_ids.used())?; diff --git a/drivers/gpu/drm/asahi/g17/channel.rs b/drivers/gpu/drm/asahi/g17/channel.rs index 8e4740b76a166..568647ebb444a 100644 --- a/drivers/gpu/drm/asahi/g17/channel.rs +++ b/drivers/gpu/drm/asahi/g17/channel.rs @@ -43,16 +43,21 @@ impl ReportRing { /// Host-side receive-ring state. A quarantined ring stays stopped until the /// device is destroyed; firmware restart does not clear it. -#[derive(Debug)] pub(crate) struct Rings { quarantined: [[bool; 2]; 2], + reports: super::reports::Reports, } impl Rings { - pub(crate) const fn new() -> Self { - Self { + pub(crate) fn new() -> Result { + Ok(Self { quarantined: [[false; 2]; 2], - } + reports: super::reports::Reports::new()?, + }) + } + + pub(super) fn write_reports(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + self.reports.write(f) } pub(crate) fn drain_trace(&mut self, dev: &Device, init: &InitData, role: Role) { @@ -68,7 +73,7 @@ impl Rings { if *quarantined { return; } - if let Err(error) = Self::consume_report(init, role, ring) { + if let Err(error) = Self::consume_report(init, role, ring, &mut self.reports) { *quarantined = true; dev_err!( dev.as_ref(), @@ -80,7 +85,7 @@ impl Rings { } } - fn consume_report(init: &InitData, role: Role, ring: ReportRing) -> Result { + fn consume_report(init: &InitData, role: Role, ring: ReportRing, reports: &mut super::reports::Reports) -> Result { let (state, entries, capacity, allowed_types) = ring.geometry(); let consumer_word = init.status_word(role, state)?; let producer_word = init.status_word(role, state + RX_PRODUCER_OFFSET)?; @@ -94,14 +99,17 @@ impl Rings { } while consumer != producer { let offset = entries + consumer as usize * EVENT_RECORD_SIZE; - let tag = match ring { - // Copy the entire trace entry before returning its slot. - ReportRing::Trace => init.status_read_words::(role, offset)?[0], - ReportRing::Statistics => init.status_read_words::<1>(role, offset)?[0], - }; + // Copy all words before returning the slot. Interpretation is a + // separate firmware qualification step, never a scheduling input. + let words = init.status_read_words::(role, offset)?; + super::trace::report(role as u8, ring as u8, &words); + let tag = words[0]; if tag >= u32::BITS || allowed_types & (1 << tag) == 0 { return Err(EIO); } + if matches!(ring, ReportRing::Statistics) { + reports.record(role, words, super::now_ns()); + } consumer = (consumer + 1) & (capacity - 1); fence(Ordering::SeqCst); consumer_word.store(consumer, Ordering::Relaxed); @@ -138,6 +146,7 @@ impl Rings { record.opcode().asserts_gpu_power(), next, )?; + super::trace::message(super::trace::CONTROL, record.opcode() as u64); Ok(next) } } @@ -146,6 +155,7 @@ impl Rings { /// the caller can service the copied event using the complete device owner. pub(crate) struct Events { role: Role, + graph: super::fw::variant::InitGraph, remaining: u32, unknown_reported: bool, } @@ -166,9 +176,10 @@ impl Events { Ok(consumer != producer) } - pub(crate) fn new(role: Role) -> Self { + pub(crate) fn new(role: Role, graph: super::fw::variant::InitGraph) -> Self { Self { role, + graph, remaining: EVENT_SLOTS, unknown_reported: false, } @@ -194,8 +205,10 @@ impl Events { // Observe the producer before reading the record it publishes. fence(Ordering::Acquire); let offset = status::EVENT_RING as usize + consumer as usize * EVENT_RECORD_SIZE; - let record = EventRecord::from_words(init.status_read_words(role, offset)?); - let event = record.decode(); + let words = init.status_read_words(role, offset)?; + super::trace::report(role as u8, 2, &words); + let record = EventRecord::from_words(words); + let event = record.decode_for(self.graph); fence(Ordering::SeqCst); consumer_word.store((consumer + 1) & (EVENT_SLOTS - 1), Ordering::Relaxed); fence(Ordering::SeqCst); diff --git a/drivers/gpu/drm/asahi/g17/clpc.c b/drivers/gpu/drm/asahi/g17/clpc.c new file mode 100644 index 0000000000000..58adaba19910f --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/clpc.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#include +#include "clpc.h" + +int asahi_clpc_gpu_get(void *dev, struct apple_clpc_gpu **out) +{ + return apple_clpc_gpu_get(dev, out); +} +void asahi_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active) +{ + apple_clpc_gpu_set_active(gpu, active); +} +void asahi_clpc_gpu_put(struct apple_clpc_gpu *gpu) +{ + apple_clpc_gpu_put(gpu); +} diff --git a/drivers/gpu/drm/asahi/g17/clpc.h b/drivers/gpu/drm/asahi/g17/clpc.h new file mode 100644 index 0000000000000..372820ade7eff --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/clpc.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef _ASAHI_G17_CLPC_H +#define _ASAHI_G17_CLPC_H +struct device; +struct apple_clpc_gpu; +int asahi_clpc_gpu_get(void *dev, struct apple_clpc_gpu **out); +void asahi_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active); +void asahi_clpc_gpu_put(struct apple_clpc_gpu *gpu); +#endif diff --git a/drivers/gpu/drm/asahi/g17/completion.rs b/drivers/gpu/drm/asahi/g17/completion.rs index 4f5b87bc853dc..02e7bbc5c33dc 100644 --- a/drivers/gpu/drm/asahi/g17/completion.rs +++ b/drivers/gpu/drm/asahi/g17/completion.rs @@ -27,9 +27,9 @@ mod publication; pub(crate) use publication::{Prepared as PreparedPublication, Publication}; struct Destination { - // Keep the existing GPU alias alive along with its prepared CPU view. - _mapping: Arc, - cpu: mmu::TimestampMapping, + // The buffer keeps its GPU alias and its CPU mapping alive. + buffer: Arc, + offset: usize, } impl Destination { @@ -45,13 +45,13 @@ impl Destination { .get(timestamp.handle.try_into()?) .ok_or(ENOENT)? .clone(); - let file::Object::TimestampBuffer(mapping) = object; + let file::Object::TimestampBuffer(buffer) = object; let offset: usize = timestamp.offset.try_into()?; - if offset.checked_add(size_of::()).ok_or(EINVAL)? > mapping.size() { + if offset.checked_add(size_of::()).ok_or(EINVAL)? > buffer.size() { return Err(ERANGE); } - let cpu = mapping.timestamp_mapping(offset)?; - Ok(Some(Self { _mapping: mapping, cpu })) + let offset = buffer.timestamp_offset(offset)?; + Ok(Some(Self { buffer, offset })) } fn write(&self, ticks: u64) { @@ -61,13 +61,14 @@ impl Destination { .wrapping_add( ticks % cfg::COMMAND_TIMESTAMP_HZ * NSEC_PER_SEC / cfg::COMMAND_TIMESTAMP_HZ, ); - self.cpu.write(ns); + self.buffer.write_timestamp(self.offset, ns); barrier(Ordering::Release); } } -/// Timestamp mappings resolved and retained before any command is enqueued. -pub(crate) struct Destinations(Option; 4]>>); +/// Timestamp destinations resolved and retained before any command is enqueued. Dropping +/// one releases only a buffer reference; the buffer defers its own mapping release. +pub(crate) struct Destinations([Option; 4]); impl Destinations { pub(crate) fn resolve( @@ -82,20 +83,13 @@ impl Destinations { destinations[2 * index] = Destination::resolve(objects, timestamp.start)?; destinations[2 * index + 1] = Destination::resolve(objects, timestamp.end)?; } - // Empty submissions need no cleanup allocation. Timestamped commands - // prepare the unique work item before any output fence is published. - let retained = if destinations.iter().any(Option::is_some) { - Some(crate::cleanup::Deferred::new(destinations)?) - } else { - None - }; - Ok(Self(retained)) + Ok(Self(destinations)) } + pub(crate) fn has_outputs(&self) -> bool { self.0.iter().any(Option::is_some) } + fn iter(&self) -> core::slice::Iter<'_, Option> { - let destinations: &[Option] = - self.0.as_deref().map_or(&[], |destinations| destinations.as_slice()); - destinations.iter() + self.0.iter() } } @@ -254,6 +248,7 @@ pub(crate) struct Completion { terminal: Option>, compute: Option, feed: Option>, + sources: Option>, #[pin] vm_job: Mutex>, #[pin] @@ -267,6 +262,9 @@ pub(crate) struct Completion { /// A recovery blamed this command. Unlike `class`, which a later /// classification can change, this never clears. blamed: AtomicBool, + /// A firmware restart listed this command as in flight. Until a replay + /// republishes it from the start, it never reports success. + interrupted: AtomicBool, firmware_consumer: AtomicBool, class: AtomicU8, } @@ -286,6 +284,7 @@ impl Completion { compute: bool, host_predecessors: bool, feed: Option>, + sources: Option>, ) -> Result> { let fence = fence::independent(contexts)?; let ordered = !compute @@ -313,6 +312,7 @@ impl Completion { terminal, compute, feed, + sources, vm_job <- new_mutex!(Some(vm_job), "G17 command VM pin"), render <- new_mutex!(None, "G17 command render lease"), signalled: AtomicBool::new(false), @@ -322,6 +322,7 @@ impl Completion { replays: AtomicU8::new(0), replay_timeout: AtomicBool::new(false), blamed: AtomicBool::new(false), + interrupted: AtomicBool::new(false), firmware_consumer: AtomicBool::new(false), class: AtomicU8::new(0), }), @@ -357,6 +358,38 @@ impl Completion { pub(crate) fn status(&self) -> &Arc { &self.status } + pub(super) fn sources(&self) -> Option<&Arc> { + self.sources.as_ref() + } + pub(super) fn is_input_source(&self, source: &Arc) -> bool { + self.output.output().member.is_source(source) + } + pub(super) fn set_source_ready(&self, ready: Fence) { + self.output.output().member.set_source_ready(ready); + } + pub(super) fn needs_source_ready(&self) -> bool { + self.output.output().member.needs_source_ready() + } + pub(super) fn note_input_error(&self, error: Error) -> bool { + self.output.output().member.note_error(error) + } + /// Called under the device mutex as a successful retirement is queued. + pub(super) fn note_source_retired(&self) { + if self.interrupted() { + // complete() fails it; consumers must not take its output as valid. + self.note_input_error(ECANCELED); + return; + } + match self.check_source_inputs() { + Ok(()) => self.output.output().member.note_retired(), + Err(error) => { self.note_input_error(error); } + } + } + pub(super) fn check_source_inputs(&self) -> Result { + if let Some(sources) = &self.sources { sources.check()?; } + Ok(()) + } + pub(crate) fn work_state(&self) -> Result> { self.output.output().member.work_state() } @@ -397,6 +430,9 @@ impl Completion { .is_ok(); if accepted { self.replay_timeout.store(true, Ordering::Release); + // Callers replay only work proven not to have started; the new + // publication runs from the beginning and reports its own result. + self.interrupted.store(false, Ordering::Release); } accepted } @@ -485,6 +521,35 @@ impl Completion { self.blamed.load(Ordering::Acquire) } + /// A firmware restart listed this command in its recovery table: it was in + /// flight, so it may have run partly. Its stamps and completion record may + /// still look like success. Unless a replay republishes it from the start, + /// it fails and the failure is always reported to the client. + pub(crate) fn note_interrupted(&self) { + self.interrupted.store(true, Ordering::Release); + } + + pub(crate) fn interrupted(&self) -> bool { + self.interrupted.load(Ordering::Acquire) + } + + /// Whether this failure makes the VM unusable. Spared work, and interrupted + /// work no recovery found guilty, fail without poisoning it. + pub(crate) fn poisons(&self, error: Error) -> bool { + let interrupted = self.interrupted(); + (error != ECANCELED || interrupted) && !self.spared() && !(interrupted && !self.guilty()) + } + + /// ECANCELED stays silent only for work that was never interrupted (closed + /// or cancelled by its owner); every other failure reaches the client. + fn report(&self, error: Error) { + if self.poisons(error) { + self.status.record(error); + } else if error != ECANCELED || self.interrupted() { + self.status.report_failure(error); + } + } + /// Installed before the first firmware-visible prefix. Cancellation takes /// the same lock after claiming the signal, so it cannot miss a late lease. pub(crate) fn attach_render_lease( @@ -515,6 +580,13 @@ impl Completion { .map(super::queue::admission::RenderLease::slot) } + /// Called under the device mutex once the render's kicks are committed. + pub(crate) fn note_render_publication(&self, fragment: super::fw::kick::KickDependency) { + if let Some(lease) = self.render.lock().as_ref() { + lease.note_publication(fragment); + } + } + /// Called under the device mutex before deferring the error signal. pub(crate) fn quarantine_render(&self) { if let Some(lease) = self.render.lock().as_ref() { @@ -525,6 +597,7 @@ impl Completion { /// An exact whole-pair drain or processor stop allows rehabilitation. The /// VM pin goes first; the still-held lease excludes reuse until both settle. pub(crate) fn release_after_retirement(&self) { + if let Some(sources) = &self.sources { sources.release(); } drop(self.vm_job.lock().take()); let lease = self.render.lock().take(); if let Some(lease) = lease.as_ref() { @@ -534,6 +607,7 @@ impl Completion { } fn release_healthy(&self) { + if let Some(sources) = &self.sources { sources.release(); } drop(self.render.lock().take()); drop(self.vm_job.lock().take()); } @@ -541,22 +615,28 @@ impl Completion { /// Exact retirement permits the physical lease and VM pin to be released /// before waking userspace. A failure fence alone never establishes this. #[track_caller] - pub(crate) fn complete(&self, result: Result<[u64; 4]>) { + pub(crate) fn complete(&self, mut result: Result<[u64; 4]>) { + if result.is_ok() { + if let Err(error) = self.check_source_inputs() { result = Err(error); } + } if self.signalled.swap(true, Ordering::AcqRel) { return; } + if result.is_ok() && self.interrupted() { + result = Err(ECANCELED); + } if let (Some(feed), Ok(ticks)) = (&self.feed, &result) { - feed.note_render_pass(ticks[2], ticks[3]); + // Compute reports its dispatch envelope in the first pair, a + // render its fragment pass in the second. + if self.compute.is_some() { + feed.note_pass(ticks[0], ticks[1]); + } else { + feed.note_pass(ticks[2], ticks[3]); + } } if let Err(error) = result { // Spared work cancelled after another VM's recovery is not this client's device loss. - if self.spared() { - if error != ECANCELED { - self.status.report_failure(error); - } - } else if error != ECANCELED { - self.status.record(error); - } + self.report(error); } self.release_healthy(); self.signal_terminal(result); @@ -590,8 +670,8 @@ impl Completion { } if self.spared() { self.status.report_failure(error); - } else if error != ECANCELED { - self.status.record(error); + } else { + self.report(error); } self.signal_terminal(Err(error)); self.output.finish(Err(error)); diff --git a/drivers/gpu/drm/asahi/g17/context.rs b/drivers/gpu/drm/asahi/g17/context.rs index c93ab25d55ce3..0b3bd97fb42fe 100644 --- a/drivers/gpu/drm/asahi/g17/context.rs +++ b/drivers/gpu/drm/asahi/g17/context.rs @@ -114,9 +114,19 @@ pub(crate) struct Context { scheduler: Arc, scheduler_generation: u32, published: AtomicBool, + /// A kill of this context was published, through a compute queue or on its render pairs' + /// behalf. A context is killed at most once. + kill_published: AtomicBool, + /// Advisory only: publication still checks the physical binding under the + /// device mutex. False skips an inline attempt known to need admission. + compute_binding_hint: AtomicBool, + /// Mixed-engine contexts keep the original inline publication path. + render_seen: AtomicBool, qos: QosLease, owner_pid: u32, states: WorkStates, + /// Signalled when a work state returns; publishers that found none wait on it. + state_returns: Arc, work: Arc>, work_base: u64, render: Arc>>>, @@ -135,6 +145,7 @@ impl Context { execution: Arc, owner_pid: u32, policy: Policy, + state_returns: Arc, ) -> Result { let status = execution.vm().status().ok_or(EINVAL)?.clone(); let qos = qos.acquire()?; @@ -189,9 +200,13 @@ impl Context { scheduler, scheduler_generation, published: AtomicBool::new(false), + kill_published: AtomicBool::new(false), + compute_binding_hint: AtomicBool::new(false), + render_seen: AtomicBool::new(false), qos, owner_pid, states: WorkStates::new(), + state_returns, work, work_base, render, @@ -203,6 +218,22 @@ impl Context { pub(crate) fn vm(&self) -> &Vm { self.execution.vm() } + pub(crate) fn compute_binding_hint(&self) -> bool { + self.render_seen.load(Ordering::Acquire) + || self.compute_binding_hint.load(Ordering::Relaxed) + } + /// This logical queue has submitted render work, so its compute may have a render prefix. + pub(crate) fn render_seen(&self) -> bool { + self.render_seen.load(Ordering::Acquire) + } + pub(crate) fn note_render(&self) { + if !self.render_seen.load(Ordering::Relaxed) { + self.render_seen.store(true, Ordering::Release); + } + } + pub(crate) fn note_compute_binding(&self, owned: bool) { + self.compute_binding_hint.store(owned, Ordering::Relaxed); + } pub(crate) fn compute_timestamps(&self) -> &Arc { &self.compute_timestamps } @@ -253,6 +284,28 @@ impl Context { _work: self.work.clone(), } } + /// Called once the firmware consumed this context's scheduler release. If its work list + /// still links a node, the firmware keeps referencing that node: on Neo a render command + /// that ran behind a failed compute command is never unlinked, and a later context given + /// the same work storage (the object cache hands it out again) made the firmware recover + /// the GPU. Keep the storage mapped and out of reuse until firmware stop; returns whether + /// it was linked. + pub(crate) fn retain_linked_work(&self, pool: &Arc) -> bool { + // An unreadable head counts as linked: retaining is the safe side. + let linked = self.work_head().map_or(true, |head| head != [0, self.work_head_va()]); + if linked { + self.work.lock().object.retain_until_stop(pool); + } + linked + } + + /// The firmware may still walk this context's work list into memory it names: a node is + /// linked and the scheduler page shows no kill. Unreadable state counts as linked. + pub(crate) fn firmware_may_link(&self) -> bool { + let killed = super::queue::compute::exit::State::read(self).map_or(0, |state| state.killed); + must_retain(self.work_head().ok(), self.work_head_va(), killed) + } + pub(crate) fn work_head_va(&self) -> u64 { self.work_base + (UAT_PGSZ + 0x400) as u64 } @@ -267,6 +320,16 @@ impl Context { Ok(head) } + /// Record that a kill of this context was published (see `kill_published`). + pub(crate) fn note_kill_published(&self) { + self.kill_published.store(true, Ordering::Release); + } + + /// A kill of this context was published. + pub(crate) fn kill_published(&self) -> bool { + self.kill_published.load(Ordering::Acquire) + } + pub(crate) fn is_published(&self) -> bool { self.published.load(Ordering::Acquire) } @@ -335,6 +398,7 @@ pub(crate) struct WorkStateLease { word: AtomicU32, } impl WorkStateLease { + pub(super) fn status(&self) -> &Arc { self.context.status() } pub(crate) fn deferred(context: Arc) -> Self { Self { context, @@ -354,8 +418,9 @@ impl WorkStateLease { Err(EBUSY) }; }; - if let Err(error) = self.context.work.lock().initialize(candidate) { - self.context.states.release(candidate); + let initialized = self.context.work.lock().initialize(candidate); + if let Err(error) = initialized { + self.context.release_state(candidate); return Err(error); } match self @@ -364,11 +429,15 @@ impl WorkStateLease { { Ok(_) => Ok(candidate), Err(installed) => { - self.context.states.release(candidate); + self.context.release_state(candidate); Ok(installed) } } } + /// The domain a publisher samples before acquire() and waits on after EBUSY. + pub(crate) fn returns(&self) -> &Arc { + &self.context.state_returns + } pub(crate) fn word(&self) -> u32 { self.word.load(Ordering::Acquire) } @@ -409,7 +478,17 @@ impl Drop for WorkStateLease { fn drop(&mut self) { let word = self.word(); if word != FREE { - self.context.states.release(word); + self.context.release_state(word); + } + } +} + +impl Context { + /// Every return bumps the domain: a publisher that sampled it before its + /// failed acquire() then never sleeps through this node. + fn release_state(&self, word: u32) { + if self.states.release(word) { + self.state_returns.notify(); } } } @@ -420,10 +499,26 @@ impl QosIds { } } +/// Memory a context's work list may lead the firmware to stays allocated: a linked node (or +/// an unreadable head) and no acknowledged kill. +fn must_retain(head: Option<[u64; 2]>, head_va: u64, killed: u32) -> bool { + killed == 0 && head.is_none_or(|head| head != [0, head_va]) +} + #[kunit_tests(asahi_g17_qos_capacity)] mod capacity_tests { use super::*; + #[test] + fn linked_unkilled_work_is_retained() { + let va = 0xfffffc20_0000_4400; + assert!(!must_retain(Some([0, va]), va, 0)); + assert!(must_retain(Some([0xfffffc20_0000_0000, 0xfffffc20_0000_0098]), va, 0)); + assert!(must_retain(None, va, 0)); + assert!(!must_retain(Some([0xfffffc20_0000_0000, 0xfffffc20_0000_0098]), va, 1)); + assert!(!must_retain(None, va, 1)); + } + #[test] fn failed_client_construction_returns_every_qos_id() { let ids = QosIds::new().unwrap(); diff --git a/drivers/gpu/drm/asahi/g17/event.rs b/drivers/gpu/drm/asahi/g17/event.rs index 9508a827be4b1..a175e58392379 100644 --- a/drivers/gpu/drm/asahi/g17/event.rs +++ b/drivers/gpu/drm/asahi/g17/event.rs @@ -36,6 +36,8 @@ pub(super) trait Queues { fn retain_render_completions(&mut self, masks: [u64; 2]); fn complete_compute(&mut self, masks: [u64; 2]) -> Result; fn context_killed(&mut self, cookie: u64); + /// The firmware failed a compute command (see [`Event::ComputeFault`]). + fn compute_fault(&mut self, qid: u8, stamp: u32); } /// Work requested after releasing the device mutex. @@ -72,6 +74,12 @@ fn batch(host: &mut H, role: Role, masks: &mut [u64; 2]) -> Result { } Event::FreeListGrow(completion) => host.complete_freelist_grow(completion)?, Event::ContextKilled(cookie) => host.context_killed(cookie), + Event::ComputeFault { qid, stamp } => { + crate::g17::trace::teardown(1, u64::from(qid), u64::from(stamp), 0); + host.compute_fault(qid, stamp); + // Its queue retires or settles its quarantine on this pass. + masks[usize::from(qid) / 64] |= 1 << (qid % 64); + } Event::Unknown(_) => {} } Ok(true) diff --git a/drivers/gpu/drm/asahi/g17/feed.rs b/drivers/gpu/drm/asahi/g17/feed.rs index 0045dff413922..760f352046192 100644 --- a/drivers/gpu/drm/asahi/g17/feed.rs +++ b/drivers/gpu/drm/asahi/g17/feed.rs @@ -410,7 +410,7 @@ impl Feed { Monotonic::ktime_get() as u64 } - pub(crate) fn note_render_pass(&self, start: u64, end: u64) { + pub(crate) fn note_pass(&self, start: u64, end: u64) { if cfg!(CONFIG_CPU_FREQ) { self.detector.note(start, end, Self::now()); } diff --git a/drivers/gpu/drm/asahi/g17/fence.rs b/drivers/gpu/drm/asahi/g17/fence.rs index 3414e36db9d36..8245e8bca71e6 100644 --- a/drivers/gpu/drm/asahi/g17/fence.rs +++ b/drivers/gpu/drm/asahi/g17/fence.rs @@ -7,10 +7,10 @@ use super::context::{Context, WorkStateLease}; use crate::file::SyncItem; -use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicUsize, Ordering}; use kernel::{ bindings, c_str, - dma_fence::{Fence, FenceContexts, FenceObject, FenceOps, RawDmaFence, UserFence}, + dma_fence::{Fence, FenceContexts, FenceObject, FenceOps, RawDmaFence, UniqueFence, UserFence}, new_mutex, prelude::*, str::CStr, @@ -19,6 +19,16 @@ use kernel::{ pub(crate) struct CompletionFence; +unsafe extern "C" { + fn asahi_g17_signal_fence(fence: *mut bindings::dma_fence); +} + +fn signal_exported(fence: &dyn RawDmaFence) { + // SAFETY: The borrowed fence remains owned throughout synchronous signaling. + // Ready completion scopes are drained iteratively after its lock is released. + unsafe { asahi_g17_signal_fence(fence.raw()) }; +} + #[vtable] impl FenceOps for CompletionFence { fn get_driver_name<'a>(self: &'a FenceObject) -> &'a CStr { @@ -48,106 +58,274 @@ pub(crate) fn allocation_failure(contexts: &FenceContexts) -> Result { Ok(Fence::from_fence(&fence)) } -/// All command members of an ioctl share its work-state node and aggregate fence. +/// The existing aggregate-fence allocation owns the existing submission state. +/// Submission never owns this fence, so exported references cannot form a cycle. +pub(crate) struct ExportFence { + source: Option>, + scoped: bool, +} +static EXPORT_OPS: AtomicUsize = AtomicUsize::new(0); + +#[vtable] +impl FenceOps for ExportFence { + fn get_driver_name<'a>(self: &'a FenceObject) -> &'a CStr { c_str!("asahi") } + fn get_timeline_name<'a>(self: &'a FenceObject) -> &'a CStr { + c_str!("g17-aggregate") + } +} + +#[inline(never)] +fn export_fallback(contexts: &FenceContexts) -> Result> { + let fence = contexts.new_fence(0, ExportFence { source: None, scoped: false })?; + // SAFETY: Unique, initialized and still private. Only this constructor + // publishes ExportFence objects; its payload is immutable after sharing. + let ops = unsafe { + (*fence.raw()).context = bindings::dma_fence_context_alloc(1); + (*fence.raw()).ops as usize + }; + let previous = EXPORT_OPS.load(Ordering::Acquire); + if previous == 0 { + match EXPORT_OPS.compare_exchange(0, ops, Ordering::AcqRel, Ordering::Acquire) { + Ok(_) => {}, + Err(previous) if previous == ops => {}, + Err(_) => return Err(EIO), + } + } else if previous != ops { + return Err(EIO); + } + Ok(fence) +} + +/// Caller retains the leaf fence throughout lookup. Exact registered operations +/// identify the repr(C) FenceObject payload, not a driver or timeline name. +pub(super) unsafe fn exported_source_raw(raw: *mut bindings::dma_fence) + -> Option> +{ + let expected = EXPORT_OPS.load(Ordering::Acquire); + if expected == 0 { return None; } + // SAFETY: The caller owns a live fence reference. ExportFence has a unique + // timeline callback and nontrivial payload drop. No cast precedes the exact + // registered vtable check. FenceObject begins with its dma_fence. + let object = unsafe { + if (*raw).ops as usize != expected { return None; } + &*(raw as *const FenceObject) + }; + object.source.as_ref().filter(|source| source.firmware_source).cloned() +} + +struct State { + work: Option>, + ready: Option, +} + +// One Member per parsed command, plus the enqueue guard. Exported references +// retain the allocation but do not contribute to either of these counters. +kernel::static_assert!(crate::file::MAX_COMMANDS_PER_SUBMISSION < u8::MAX as u32); + /// The initial member keeps the aggregate unsignalled until enqueue finishes. #[pin_data] pub(crate) struct Submission { - fence: UserFence, - /// Successful retirement drops only this bookkeeping owner; runtime owners - /// and failed submissions keep their work-state allocation. #[pin] - work_state: Mutex>>, - remaining: AtomicU32, + state: Mutex, + remaining: AtomicU8, error: AtomicI32, + /// Live Member objects, plus the enqueue guard, independently of signalling. + owners: AtomicU8, + firmware_source: bool, + /// Set under the device mutex when the only member's successful retirement + /// is queued, before its fence is signalled off-lock: a consumer published + /// in between needs no wait on it. + retired: AtomicBool, } impl Submission { - fn new(fence: UserFence, context: Arc) -> Result> { - let work_state = Arc::new(WorkStateLease::deferred(context), GFP_KERNEL)?; - Arc::pin_init( - pin_init!(Self { - fence, - work_state <- new_mutex!(Some(work_state), "G17 submission work state"), - remaining: AtomicU32::new(1), - error: AtomicI32::new(0), - }), - GFP_KERNEL, - ) - } - - pub(crate) fn fence(&self) -> Fence { - Fence::from_fence(&self.fence) - } - - /// Add one command before enqueue ends. The parser bounds the count to 64. - pub(crate) fn member(self: &Arc) -> Member { - self.remaining.fetch_add(1, Ordering::Relaxed); - Member { - submission: self.clone(), - complete: AtomicBool::new(false), - } + fn new(context: Arc, firmware_source: bool) -> Result> { + let work = Arc::new(WorkStateLease::deferred(context), GFP_KERNEL)?; + Self::with_work(Some(work), firmware_source) } - - fn member_done(&self, result: Result) { - if let Err(error) = result { - let _ = self.error.compare_exchange( - 0, - error.to_errno(), - Ordering::AcqRel, - Ordering::Acquire, - ); - } + fn with_work(work: Option>, firmware_source: bool) -> Result> { + Arc::pin_init(pin_init!(Self { + state <- new_mutex!(State { work, ready: None }, "G17 submission work state"), + remaining: AtomicU8::new(1), + error: AtomicI32::new(0), + owners: AtomicU8::new(1), + firmware_source, + retired: AtomicBool::new(false), + }), GFP_KERNEL) + } + pub(super) fn source_status(&self) -> Result { + let complete = self.remaining.load(Ordering::Acquire) == 0 + || self.retired.load(Ordering::Acquire); + let error = self.error.load(Ordering::Acquire); + if error != 0 { return Err(super::dependency::inherited_error(Error::from_errno(error))); } + Ok(complete) + } + pub(super) fn same_vm(&self, status: &Arc) -> bool { + self.state.lock().work.as_ref().is_some_and(|work| Arc::ptr_eq(work.status(), status)) + } + pub(super) fn ready(&self) -> Option { self.state.lock().ready.clone() } + fn set_ready(&self, ready: Fence) { + if !self.firmware_source { return; } + let mut state = self.state.lock(); + if state.work.is_some() && state.ready.is_none() { state.ready = Some(ready); } + } + fn note_error(&self, error: Error) -> bool { + self.error.compare_exchange(0, error.to_errno(), Ordering::AcqRel, Ordering::Acquire).is_ok() + } + fn release_state(&self) { + let old = { + let mut state = self.state.lock(); + (state.work.take(), state.ready.take()) + }; + drop(old); + } + fn owner_done(&self) { + // Every hardware owner retains its Completion/Member through exact + // retirement or stop. Exported fence FDs must not retain failed pins. + if self.owners.fetch_sub(1, Ordering::AcqRel) == 1 { self.release_state(); } + } + fn member_done(&self, fence: &UserFence, result: Result) { + if let Err(error) = result { self.note_error(error); } if self.remaining.fetch_sub(1, Ordering::AcqRel) == 1 { let error = self.error.load(Ordering::Acquire); - if error != 0 { - self.fence.set_error(Error::from_errno(error)); - } else { - // Scheduler cleanup can wait behind submission on the same worker. - // Once every member and enqueue have succeeded, retained packets - // must not prevent new submissions from acquiring work-state nodes. - let retired = self.work_state.lock().take(); - drop(retired); - } - self.fence.signal(); + if error != 0 { fence.set_error(Error::from_errno(error)); } + else { self.release_state(); } + if fence.scoped { signal_exported(fence); } else { fence.signal(); } } } +} +#[derive(Clone)] +pub(crate) struct Aggregate(UserFence); +impl Aggregate { + fn source(&self) -> &Arc { + // Only Outputs::publish constructs Aggregate, after setting the payload. + self.0.source.as_ref().expect("published aggregate has submission state") + } + fn fence(&self) -> Fence { Fence::from_fence(&self.0) } + pub(crate) fn member(&self) -> Member { + self.source().remaining.fetch_add(1, Ordering::Relaxed); + self.source().owners.fetch_add(1, Ordering::Relaxed); + Member { aggregate: self.clone(), complete: AtomicBool::new(false) } + } fn finish_enqueue(&self, result: Result) { - // A dropped, never-enqueued member reports cancellation. The ioctl's actual - // failure takes precedence, while published members still retain their work. - if let Err(error) = result { - self.error.store(error.to_errno(), Ordering::Release); - } - self.member_done(result); + if let Err(error) = result { self.source().error.store(error.to_errno(), Ordering::Release); } + self.source().member_done(&self.0, result); + self.source().owner_done(); } } -/// One command's contribution to its submission, including construction failure. pub(crate) struct Member { - submission: Arc, + aggregate: Aggregate, complete: AtomicBool, } - impl Member { + pub(super) fn needs_source_ready(&self) -> bool { self.aggregate.source().firmware_source } pub(crate) fn work_state(&self) -> Result> { - self.submission - .work_state - .lock() - .as_ref() - .cloned() - .ok_or(ECANCELED) + self.aggregate.source().state.lock().work.clone().ok_or(ECANCELED) + } + pub(super) fn is_source(&self, source: &Arc) -> bool { + self.aggregate.source().firmware_source && Arc::ptr_eq(self.aggregate.source(), source) + } + pub(super) fn set_source_ready(&self, ready: Fence) { self.aggregate.source().set_ready(ready); } + pub(super) fn note_error(&self, error: Error) -> bool { self.aggregate.source().note_error(error) } + pub(super) fn note_retired(&self) { + let source = self.aggregate.source(); + if source.firmware_source { source.retired.store(true, Ordering::Release); } } - pub(crate) fn complete(&self, result: Result) { if !self.complete.swap(true, Ordering::AcqRel) { - self.submission.member_done(result); + self.aggregate.source().member_done(&self.aggregate.0, result); } } } +#[kunit_tests(asahi_g17_aggregate_promise)] +mod tests { + use super::*; + use kernel::sync::LockClassKey; + static KEY: Pin<&LockClassKey> = kernel::static_lock_class!(); + + fn contexts() -> FenceContexts { + FenceContexts::new(1, c_str!("asahi_promise_test"), KEY).unwrap() + } + fn aggregate(contexts: &FenceContexts) -> Aggregate { + let mut fence = export_fallback(contexts).unwrap(); + fence.source = Some(Submission::with_work(None, true).unwrap()); + Aggregate(fence.into()) + } + + #[test] + fn exact_export_identity_recognizes_unpublished_source() { + let contexts = contexts(); + let aggregate = aggregate(&contexts); + let plain = independent(&contexts).unwrap(); + // SAFETY: Both references remain owned throughout the checked lookup. + assert!(unsafe { exported_source_raw(plain.raw()) }.is_none()); + let source = unsafe { exported_source_raw(aggregate.0.raw()) }.unwrap(); + assert!(Arc::ptr_eq(&source, aggregate.source())); + assert_eq!(source.source_status().unwrap(), false); + aggregate.finish_enqueue(Ok(())); + assert_eq!(source.source_status().unwrap(), true); + } + + #[test] + fn failed_export_releases_state_after_last_runtime_owner() { + let contexts = contexts(); + let aggregate = aggregate(&contexts); + let source = aggregate.source().clone(); + source.state.lock().ready = Some(Fence::from_fence(&independent(&contexts).unwrap())); + let member = aggregate.member(); + aggregate.finish_enqueue(Ok(())); + member.complete(Err(EIO)); + assert_eq!(super::super::job::fence_status(&aggregate.fence()), EIO.to_errno()); + assert!(source.state.lock().ready.is_some()); + drop(member); + // Keep both the exported fence and source state alive, as userspace can. + assert!(source.state.lock().ready.is_none()); + assert_eq!(source.owners.load(Ordering::Acquire), 0); + } + + #[test] + fn actual_enqueue_error_wins_over_dropped_member_cancellation() { + let aggregate = aggregate(&contexts()); + drop(aggregate.member()); + aggregate.finish_enqueue(Err(ENOMEM)); + assert_eq!(super::super::job::fence_status(&aggregate.fence()), ENOMEM.to_errno()); + } + + #[test] + fn rejected_private_fence_never_exports_source_state() { + let fence = export_fallback(&contexts()).unwrap(); + fence.set_error(ENOMEM); + fence.signal(); + // SAFETY: The unique fence owns its initialized allocation. + assert!(unsafe { exported_source_raw(fence.raw()) }.is_none()); + } + + #[test] + fn maximum_submission_keeps_all_members_and_enqueue_guard() { + let aggregate = aggregate(&contexts()); + let source = aggregate.source().clone(); + let mut members: [Option; crate::file::MAX_COMMANDS_PER_SUBMISSION as usize] = + core::array::from_fn(|_| Some(aggregate.member())); + assert_eq!(source.remaining.load(Ordering::Acquire), 65); + assert_eq!(source.owners.load(Ordering::Acquire), 65); + aggregate.finish_enqueue(Ok(())); + for member in &members { + assert_eq!(super::super::job::fence_status(&aggregate.fence()), 0); + member.as_ref().unwrap().complete(Ok(())); + } + assert!(super::super::job::fence_status(&aggregate.fence()) > 0); + assert_eq!(source.remaining.load(Ordering::Acquire), 0); + for member in &mut members { drop(member.take()); } + assert_eq!(source.owners.load(Ordering::Acquire), 0); + } +} impl Drop for Member { fn drop(&mut self) { self.complete(Err(ECANCELED)); + self.aggregate.source().owner_done(); } } @@ -155,83 +333,73 @@ fn install(outputs: impl Iterator, fence: &Fence) { for mut sync in outputs { if let Some(chain) = sync.chain_fence.take() { sync.syncobj.add_point(chain, fence, sync.timeline_value); - } else { - sync.syncobj.replace_fence(Some(fence)); - } + } else { sync.syncobj.replace_fence(Some(fence)); } } } -/// Keeps resolved output syncobjs reachable on every fallible submission path. -/// Input syncobjs stay in the same vector, avoiding a separate output allocation. pub(crate) struct Outputs { - fallback: UserFence, + fallback: Option>, syncs: KVec, input_count: usize, - submission: Option>, + submission: Option, result: Result, + cumulative: Option, } - impl Outputs { - pub(crate) fn new( - contexts: &FenceContexts, - mut syncs: KVec, - input_count: usize, - allocation_failure: &Fence, - ) -> Result { - if input_count > syncs.len() { - return Err(EINVAL); - } - let fallback = match independent(contexts) { + pub(crate) fn new(contexts: &FenceContexts, mut syncs: KVec, + input_count: usize, allocation_failure: &Fence) -> Result + { + if input_count > syncs.len() { return Err(EINVAL); } + let mut cumulative = syncs.iter_mut().find_map(|sync| sync.cumulative.take()); + let fallback = match export_fallback(contexts) { Ok(fence) => fence, Err(error) => { - // Syncobjs and timeline-chain storage are already resolved. - // No work has been accepted; publish the pre-signaled ENOMEM - // fence without another allocation before returning the error. - install(syncs.drain(input_count..), allocation_failure); + if let Some(bundle) = &mut cumulative { + bundle.fail(allocation_failure); + } else { + install(syncs.drain(input_count..), allocation_failure); + } return Err(error); } }; - Ok(Self { - fallback, - syncs, - input_count, - submission: None, - result: Err(ECANCELED), - }) - } - - pub(crate) fn inputs(&self) -> &[SyncItem] { - &self.syncs[..self.input_count] + Ok(Self { fallback: Some(fallback), syncs, input_count, + submission: None, result: Err(ECANCELED), cumulative }) } - - /// Publish the actual aggregate before enqueueing any command. - pub(crate) fn publish(&mut self, context: Arc) -> Result> { - let submission = Submission::new(self.fallback.clone(), context)?; - install(self.syncs.drain(self.input_count..), &submission.fence()); + pub(crate) fn inputs(&self) -> &[SyncItem] { &self.syncs[..self.input_count] } + pub(crate) fn publish(&mut self, context: Arc, firmware_source: bool) -> Result { + let source = Submission::new(context, firmware_source)?; + let mut fence = self.fallback.take().ok_or(EINVAL)?; + fence.source = Some(source); + fence.scoped = self.cumulative.is_some(); + let submission = Aggregate(fence.into()); self.submission = Some(submission.clone()); + if let Some(bundle) = &mut self.cumulative { + bundle.publish(&submission.fence())?; + self.syncs.truncate(self.input_count); + } else { + install(self.syncs.drain(self.input_count..), &submission.fence()); + } Ok(submission) } - - /// The caller first records terminal VM errors and settles prior failed work. - pub(crate) fn finish(mut self, result: Result) -> Result { - self.result = result; - result - } + pub(crate) fn finish(mut self, result: Result) -> Result { self.result = result; result } } - impl Drop for Outputs { fn drop(&mut self) { if let Some(submission) = self.submission.take() { + if let Some(bundle) = &mut self.cumulative { + bundle.fail(&submission.fence()); + } submission.finish_enqueue(self.result); return; } - if let Err(error) = self.result { - self.fallback.set_error(error); + if let Some(fallback) = self.fallback.take() { + if let Err(error) = self.result { fallback.set_error(error); } + if let Some(bundle) = &mut self.cumulative { + bundle.fail(&Fence::from_fence(&fallback)); + } else { + install(self.syncs.drain(self.input_count..), &Fence::from_fence(&fallback)); + } + fallback.signal(); } - install( - self.syncs.drain(self.input_count..), - &Fence::from_fence(&self.fallback), - ); - self.fallback.signal(); } } diff --git a/drivers/gpu/drm/asahi/g17/freelist.rs b/drivers/gpu/drm/asahi/g17/freelist.rs index 3b31c3cead064..9015dc82e4076 100644 --- a/drivers/gpu/drm/asahi/g17/freelist.rs +++ b/drivers/gpu/drm/asahi/g17/freelist.rs @@ -670,10 +670,18 @@ pub(crate) struct Owner { enum LeasePhase { Preparing, Ready, + /// No reference since the given time. The registration stays live so that + /// the owner's next render reuses it without a control-ring release; the + /// release becomes pending when the linger ends. + Lingering(u64), ReleaseSent(u32), Released, } +/// How long an idle registration of an open owner may stay live: the +/// firmware's effort idle hold. +pub(crate) const LINGER_NS: u64 = 16_000_000; + /// References to one prepopulated owner: preparation and two engine commands /// per render. Published releases do not gate reacquisition; their consumption /// witnesses are carried into the successor. @@ -707,10 +715,14 @@ impl Lease { } let generation = previous.generation.checked_add(1).ok_or(EOVERFLOW)?; let same_owner = previous.owner == owner; + // A lingering registration was never released: it continues like an + // overlap of the owner's previous render. let overlapping = same_owner - && previous.phase == LeasePhase::Ready - && previous.references >= 2 - && previous.commands >= 2; + && match previous.phase { + LeasePhase::Ready => previous.references >= 2 && previous.commands >= 2, + LeasePhase::Lingering(_) => true, + _ => false, + }; let reacquire = previous.reacquirable(); if !overlapping && !reacquire { return Err(EBUSY); @@ -772,16 +784,35 @@ impl Lease { Err(EIO) } /// Both engines have retired this render's exact retained submission. + /// With `linger`, a registration left without references stays live + /// instead of making its final release pending. pub(crate) fn complete_render( &mut self, + linger: bool, ) -> Result> { if self.phase != LeasePhase::Ready || self.references < 2 || self.commands < 2 { return Err(EINVAL); } self.references -= 2; self.commands -= 2; + if self.references == 0 && linger { + self.phase = LeasePhase::Lingering(super::now_ns().max(1)); + } Ok(self.pending_release()) } + /// The time since which an idle registration lingers, if it does. + pub(crate) fn lingering(&self) -> Option { + match self.phase { + LeasePhase::Lingering(since) => Some(since), + _ => None, + } + } + /// Ends a linger; the final release becomes pending. + pub(crate) fn end_linger(&mut self) { + if matches!(self.phase, LeasePhase::Lingering(_)) { + self.phase = LeasePhase::Ready; + } + } pub(crate) fn pending_release(&self) -> Option { (self.phase == LeasePhase::Ready && self.references == 0).then(|| { super::fw::channels::FreeListRelease::new(self.generation, self.owner.buffer_slot) diff --git a/drivers/gpu/drm/asahi/g17/fw/channels.rs b/drivers/gpu/drm/asahi/g17/fw/channels.rs index 524132c4ae7be..26c6adcf34c83 100644 --- a/drivers/gpu/drm/asahi/g17/fw/channels.rs +++ b/drivers/gpu/drm/asahi/g17/fw/channels.rs @@ -136,6 +136,9 @@ pub(crate) enum Event { TvbGrow(TvbGrowRequest), FreeListGrow(FreeListCompletion), ContextKilled(u64), + /// The firmware failed a compute command of queue `qid` and stores no completion stamp for + /// it: `stamp` is the value that command would have stored. + ComputeFault { qid: u8, stamp: u32 }, Unknown(u32), } @@ -145,6 +148,8 @@ impl EventRecord { const TVB_GROW: u32 = 6; const FREE_LIST_GROW: u32 = 13; const CONTEXT_KILLED: u32 = 12; + const COMPUTE_FAULT: u32 = 7; + const COMPUTE_QIDS: u32 = 128; const DIAGNOSTIC_PAIRS_MAX: u16 = 5; const TVB_SLOTS: u32 = 127; const SUBPIPES: u32 = 64; @@ -160,6 +165,16 @@ impl EventRecord { unsafe { self.words[0] } } + /// Type 7 is a retirement witness only on the firmware whose layout and + /// visibility semantics are qualified. 25G83 must keep consuming it as an + /// unknown event until its own fault contract is established. + pub(crate) fn decode_for(&self, graph: super::variant::InitGraph) -> Result { + if graph == super::variant::InitGraph::V25G83 && self.tag() == Self::COMPUTE_FAULT { + return Ok(Event::Unknown(Self::COMPUTE_FAULT)); + } + self.decode() + } + /// Validates the record's fields without interpreting resource ownership. /// A malformed known event is an error, unlike an unknown tag. pub(crate) fn decode(&self) -> Result { @@ -198,6 +213,19 @@ impl EventRecord { halt_count: record.halt_count, })) } + Self::COMPUTE_FAULT => { + // SAFETY: Every constructor initializes all integer words. + let words = unsafe { self.words }; + // Only compute faults are known to use this type: any other layout stays an + // unknown event, consumed and ignored as before. + if words[3] >= Self::COMPUTE_QIDS || words[4] & 0xff != 0 { + return Ok(Event::Unknown(Self::COMPUTE_FAULT)); + } + Ok(Event::ComputeFault { + qid: words[3] as u8, + stamp: words[4], + }) + } Self::CONTEXT_KILLED => { // SAFETY: Every constructor initializes all integer words. let words = unsafe { self.words }; @@ -525,3 +553,37 @@ impl ContextKill { } } } + +#[cfg_attr(not(test), kernel::macros::kunit_tests(asahi_g17_event_records))] +mod tests { + use super::*; + + fn record(words: &[u32]) -> EventRecord { + let mut all = [0u32; EVENT_WORDS]; + all[..words.len()].copy_from_slice(words); + EventRecord::from_words(all) + } + + #[test] + fn a_compute_fault_names_its_queue_and_stamp() { + let event = record(&[7, 1, 2, 0x39, 0x300, 0xffff_fc00, 1]).decode(); + assert_eq!(event, Ok(Event::ComputeFault { qid: 0x39, stamp: 0x300 })); + } + + #[test] + fn v25g83_does_not_accept_an_unqualified_fault_witness() { + use super::super::variant::InitGraph; + let fault = record(&[7, 1, 2, 0x39, 0x300]); + assert_eq!(fault.decode_for(InitGraph::G17P), + Ok(Event::ComputeFault { qid: 0x39, stamp: 0x300 })); + assert_eq!(fault.decode_for(InitGraph::V25G83), Ok(Event::Unknown(7))); + assert_eq!(record(&[7, 1, 2, 128, 0x301]).decode_for(InitGraph::V25G83), + Ok(Event::Unknown(7))); + } + + #[test] + fn an_unrecognized_type_7_layout_stays_unknown() { + assert_eq!(record(&[7, 1, 2, 128, 0x300]).decode(), Ok(Event::Unknown(7))); + assert_eq!(record(&[7, 1, 2, 9, 0x301]).decode(), Ok(Event::Unknown(7))); + } +} diff --git a/drivers/gpu/drm/asahi/g17/fw/compute.rs b/drivers/gpu/drm/asahi/g17/fw/compute.rs index 0cb7304e30677..b1afdad816b6b 100644 --- a/drivers/gpu/drm/asahi/g17/fw/compute.rs +++ b/drivers/gpu/drm/asahi/g17/fw/compute.rs @@ -12,7 +12,7 @@ use kernel::prelude::*; use super::kick::{KickTimestamp, RegisterArray, RegisterArrayBinding, RegisterWrite}; use super::queue::CommandTag; use super::{ - clear, offset_va, sampler_max, stamp_value, timestamp_end_va, work_key, ObjectIds, UNK_0A599, + offset_va, sampler_max, stamp_value, timestamp_end_va, work_key, ObjectIds, UNK_0A599, UNK_0D411, UNK_101D9, UNK_10791, UNK_1A0E9, UNK_1C8F8, }; @@ -507,8 +507,9 @@ impl ComputeDescriptor { const UNK_FBA: u32 = 0xe0a0_0001; const UNK_FC5: u8 = 0x9f; - /// Writes the complete descriptor. - pub(crate) fn write(&mut self, args: &ComputeArgs) -> Result { + /// Fills a descriptor cleared by `Object::initialize`. The unused firmware + /// fields and padding retain that zero initialization. + pub(crate) fn write_zeroed(&mut self, args: &ComputeArgs) -> Result { if args.cdm_end_va < args.cdm_va || (args.sampler_heap_va == 0) != (args.sampler_count == 0) || args.sampler_heap_va & 7 != 0 @@ -522,7 +523,6 @@ impl ComputeDescriptor { let timestamp_end_va = timestamp_end_va(args.timestamp_va)?; let sampler_max = sampler_max(args.sampler_count)?; - clear(self); self.tag = CommandTag::Compute as u32; self.context_id = args.context_id.into(); self.work_state_va = args.work_state_va; diff --git a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs index c237896a49fb1..f4c3ee5db22e2 100644 --- a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: GPL-2.0-only //! Initialization data of the 25G83 firmware build (macOS 26.6, `mBoot-18000.161.10`). //! @@ -17,9 +17,9 @@ //! firmware on the M4 Pro, by evaluating them for this SoC; see the commit that added them. use super::{ - fill_words, perf_states, CompletionRingDescriptor, HwDataArgs, Opp, PerfGroup, RegisterMapEntry, - Words, COMPLETION_RING_ENTRIES, COMPLETION_RING_ENTRY_SIZE, COMPLETION_RING_INDEX, PERF_STATES, - REGISTER_MAP_SLOTS, + fill_words, perf_states, CompletionRingDescriptor, HwDataArgs, Opp, PerfGroup, + RegisterMapEntry, Words, COMPLETION_RING_ENTRIES, COMPLETION_RING_ENTRY_SIZE, + COMPLETION_RING_INDEX, PERF_STATES, REGISTER_MAP_SLOTS, }; use kernel::prelude::*; @@ -381,26 +381,10 @@ impl HwData25G83 { /// Power configuration control period, in 24 MHz clocks (8 ms). const PERIOD_CLOCKS: u32 = 200_000; -/// Highest GPU power, in mW. -pub(crate) const MAX_POWER_MW: u32 = 69_116; -/// [`MAX_POWER_MW`] as an IEEE-754 single. -const MAX_POWER_MW_F32: u32 = 0x4786_fe00; /// Temperature sensors read by the die and MTR temperature limiters. const TEMP_SENSOR_MASK: u32 = 0x2a22_2aa2; -/// Leakage coefficients, IEEE-754 singles: three arrays of sixteen clusters, then four arrays -/// of two dies. Not found in the ADT; constant for the SoC. -const LEAKAGE: [u32; 56] = { - let mut words = [0; 56]; - words[0] = 0x44f0_3000; // 1921.5 - words[1] = 0x44f3_0000; // 1944.0 - words[32] = 0x4373_0000; // 243.0 - words[33] = 0x4373_0000; // 243.0 - words[52] = 0x437b_0000; // 251.0 - words[54] = 0x43bd_c000; // 379.5 - words -}; -/// Offset of [`LEAKAGE`] in the power configuration. +/// Offset of the per-chip leakage coefficients in the power configuration. const LEAKAGE_OFFSET: usize = 0x642c; /// Offset in the power configuration of the GPU address of the configuration's status (the /// configuration named by the root, `+0x80`). @@ -433,12 +417,10 @@ const POWER_CONFIG_WORDS: Words = &[ (0x9c4, 0x3f7f2e9f, 1), (0x9cc, 0x3b516154, 1), (0x9d4, 0x3ca59586, 1), - (0x9e0, MAX_POWER_MW_F32, 1), (0x9e4, 0x40a90fdb, 1), (0x9ec, 0xbc6d0cfe, 1), (0x9f0, 0x64, 1), (0x9f4, 0x3e8, 2), - (0xa04, MAX_POWER_MW, 1), (0xa38, 0x3e8, 1), (0xa70, 0x8, 1), (0xa7c, 0x3f000000, 1), @@ -448,7 +430,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0xa9c, 0x3ecf2e49, 1), (0xaa8, 0x64, 1), (0xaac, 0x3e8, 1), - (0xabc, MAX_POWER_MW, 1), (0xac8, 0x10, 1), (0xad0, 0x5dc00, 1), (0xb28, 0x50, 1), @@ -508,7 +489,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0x27b8, 0xc80000, 1), (0x2870, 0x3e8, 1), (0x28c0, 0x1, 1), - (0x28c8, MAX_POWER_MW, 3), (0x28e0, 0x3f800000, 1), (0x28e4, 0x4, 1), (0x28e8, 0x8, 1), @@ -521,8 +501,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0x29e8, 0x3f4f2e49, 1), (0x29f4, 0x28, 1), (0x29f8, 0x3e8, 2), - (0x2a04, MAX_POWER_MW_F32, 1), - (0x2a08, MAX_POWER_MW, 1), (0x2a14, 0xf8, 1), (0x2a1c, 0x5ad200, 1), (0x2a3c, 0x3e8, 1), @@ -591,27 +569,34 @@ unsafe impl Zeroable for PowerConfig25G83 {} static_assert!(LEAKAGE_OFFSET + size_of::<[u32; 56]>() <= size_of::()); impl PowerConfig25G83 { - /// Pointer and random words, which [`Self::hash`] leaves out. - const RUNTIME: [(usize, usize); 3] = - [(CONFIG_STATUS_POINTER, 8), (AON_POINTER, 8), (ENTROPY, 4)]; - /// Initializes the power configuration. - pub(crate) fn init(&mut self, config_va: u64, aon_va: u64, entropy: u32) { + pub(crate) fn init( + &mut self, + config_va: u64, + aon_va: u64, + entropy: u32, + max_power_mw: u32, + leakage: &[u32; 56], + ) { + self.0.fill(0); fill_words(&mut self.0, POWER_CONFIG_WORDS); - for (index, word) in LEAKAGE.iter().enumerate() { + for (index, word) in leakage.iter().enumerate() { let at = LEAKAGE_OFFSET + 4 * index; self.0[at..at + 4].copy_from_slice(&word.to_le_bytes()); } + // All copies of the power ceiling must describe the same chip. + for offset in [0xa04, 0xabc, 0x28c8, 0x28cc, 0x28d0, 0x2a08] { + self.0[offset..offset + 4].copy_from_slice(&max_power_mw.to_le_bytes()); + } + let power = crate::float::F32::from(max_power_mw).to_bits(); + for offset in [0x9e0, 0x2a04] { + self.0[offset..offset + 4].copy_from_slice(&power.to_le_bytes()); + } self.0[CONFIG_STATUS_POINTER..CONFIG_STATUS_POINTER + 8] .copy_from_slice(&(config_va + 0x80).to_le_bytes()); self.0[AON_POINTER..AON_POINTER + 8].copy_from_slice(&aon_va.to_le_bytes()); self.0[ENTROPY..ENTROPY + 4].copy_from_slice(&entropy.to_le_bytes()); } - - /// Hash of the contents without the pointers and the random word. - pub(crate) fn hash(&self) -> u64 { - hash_without(&self.0, &Self::RUNTIME) - } } /// The AON temperature-source rows the power configuration names: the GPU cluster 0, GPU @@ -632,50 +617,3 @@ pub(crate) const AON_TABLE: [u32; 9] = [ /// (`+0xc8`, 101.00 °C) and release (`+0xcc`, 96.00 °C) temperatures in 0.01 °C; left at zero /// the limit is 0 °C and the GPU stays at its lowest state whenever the PMP runs. pub(crate) const POWER_STATE_WORDS: Words = &[(0xc8, 10_100, 1), (0xcc, 9_600, 1)]; - -impl HwData25G83 { - /// What [`Self::hash`] leaves out: the runtime words (completion descriptors, QoS pointer) - /// and what comes from the device: the performance tables built from its operating points, - /// which are binned per device, and its enabled core count. - const UNHASHED: [(usize, usize); 10] = [ - (COMPLETION_DESCRIPTORS, 0x80), - (QOS_POINTER, 8), - (core::mem::offset_of!(Self, unk_f88), size_of::()), - (core::mem::offset_of!(Self, unk_fc0), size_of::()), - (core::mem::offset_of!(Self, perf_a), size_of::()), - (core::mem::offset_of!(Self, perf_b_freq_mhz), size_of::<[u32; PERF_STATES]>()), - (core::mem::offset_of!(Self, cs_perf), size_of::()), - (core::mem::offset_of!(Self, afr_perf), size_of::()), - (core::mem::offset_of!(Self, perf_b), size_of::()), - (core::mem::offset_of!(Self, unk_2570), size_of::()), - ]; - - /// Hash of the constant contents (everything but [`Self::UNHASHED`]). - pub(crate) fn hash(&self) -> u64 { - // SAFETY: `HwData25G83` consists of integers only and has no padding. - let bytes = unsafe { - core::slice::from_raw_parts((self as *const Self).cast::(), size_of::()) - }; - hash_without(bytes, &Self::UNHASHED) - } -} - -/// FNV-1a 64 of `bytes` with the `(offset, length)` ranges in `skip` left out. -pub(crate) fn hash_without(bytes: &[u8], skip: &[(usize, usize)]) -> u64 { - let mut hash = 0xcbf2_9ce4_8422_2325u64; - for (offset, byte) in bytes.iter().enumerate() { - if skip.iter().any(|&(at, len)| (at..at + len).contains(&offset)) { - continue; - } - hash = (hash ^ u64::from(*byte)).wrapping_mul(0x100_0000_01b3); - } - hash -} - -/// Hashes ([`hash_without`]) of the hardware data, power configuration and region C that the -/// Gravity Linux G16 encoders produce for the J616s, which this build's objects must equal. -pub(crate) mod expected { - pub(crate) const HW_DATA: u64 = 0xfd3b_a26f_570f_57cd; - pub(crate) const POWER_CONFIG: u64 = 0xb669_5b84_2ad2_36fe; - pub(crate) const REGION_C: u64 = 0xd2db_fa6b_8976_47b4; -} diff --git a/drivers/gpu/drm/asahi/g17/fw/kick.rs b/drivers/gpu/drm/asahi/g17/fw/kick.rs index 4f3a70671eb87..ca2b6604a302b 100644 --- a/drivers/gpu/drm/asahi/g17/fw/kick.rs +++ b/drivers/gpu/drm/asahi/g17/fw/kick.rs @@ -278,6 +278,20 @@ impl RegisterArray { self.size = (size_of_val(writes) as u32) << 16 | count as u32; Ok(()) } + + /// Stores `writes` without the entry at `skip`, exactly as [`Self::set`] stores the + /// remaining writes, without first copying them elsewhere. + pub(crate) fn set_without(&mut self, self_va: u64, writes: &[RegisterWrite], skip: usize) -> Result { + let count = writes.len().checked_sub(1).ok_or(EINVAL)?; + if skip > count || count > REGISTER_ARRAY_ENTRIES { + return Err(EINVAL); + } + self.entries[..skip].copy_from_slice(&writes[..skip]); + self.entries[skip..count].copy_from_slice(&writes[skip + 1..]); + self.self_va = self_va; + self.size = ((count * size_of::()) as u32) << 16 | count as u32; + Ok(()) + } } /// Binding of one [`RegisterArray`] to a kick entry. diff --git a/drivers/gpu/drm/asahi/g17/fw/queue.rs b/drivers/gpu/drm/asahi/g17/fw/queue.rs index 5708a2251a062..66e7d07db48a5 100644 --- a/drivers/gpu/drm/asahi/g17/fw/queue.rs +++ b/drivers/gpu/drm/asahi/g17/fw/queue.rs @@ -96,8 +96,6 @@ pub(crate) enum CommandTag { QueueConfig = 0x0f, /// [`KickPredecessor`]. KickPredecessor = 0x10, - /// [`FragmentBarrier`] (25G83 firmware). - FragmentBarrier = 4, /// [`InitBufferManager`] (25G83 firmware). InitBufferManager = 5, } @@ -569,63 +567,6 @@ impl QueueConfig { } } -/// The 25G83 firmware's barrier before every fragment descriptor (tag 4): the fragment work -/// waits for the paired tiling stamp, in addition to the kick-entry dependency. -#[repr(C, packed)] -#[derive(Copy, Clone, Debug)] -pub(crate) struct FragmentBarrier { - /// [`CommandTag::FragmentBarrier`]. - pub(crate) tag: u32, - /// GPU address of the tiling queue's completion stamp word. - pub(crate) stamp_va: u64, - /// Timestamp of the tiling kick. - pub(crate) tiling_kick: u64, - /// QID of the tiling queue. - pub(crate) tiling_qid: u32, - /// Tiling stamp value waited for. - pub(crate) wait: u32, - pub(crate) unk_1c: [u8; 8], - /// Event of the wait: the tiling QID. - pub(crate) event: u32, - /// Fragment stamp value. - pub(crate) stamp_self: u32, - /// Current object ID of the fragment stage. - pub(crate) fragment_id: u64, - pub(crate) unk_34: u32, - pub(crate) unk_38: [u8; 0x10], -} - -static_assert!(size_of::() == 0x48); -static_assert!(core::mem::offset_of!(FragmentBarrier, wait) == 0x18); -static_assert!(core::mem::offset_of!(FragmentBarrier, event) == 0x24); -static_assert!(core::mem::offset_of!(FragmentBarrier, fragment_id) == 0x2c); - -impl FragmentBarrier { - /// The barrier of a pass whose tiling stage, kicked at `tiling_kick` on `tiling_qid`, - /// signals `stamp_value` at `stamp_va`. - pub(crate) fn new( - stamp_va: u64, - tiling_kick: KickTimestamp, - tiling_qid: u32, - stamp_value: u32, - fragment_id: u64, - ) -> Self { - Self { - tag: CommandTag::FragmentBarrier as u32, - stamp_va, - tiling_kick: tiling_kick.get(), - tiling_qid, - wait: stamp_value, - unk_1c: [0; 8], - event: tiling_qid, - stamp_self: stamp_value, - fragment_id, - unk_34: 1, - unk_38: [0; 0x10], - } - } -} - /// The 25G83 firmware's buffer-manager initialization (tag 5), before the first tiling /// descriptor that uses a parameter buffer. The block count is read from the manager. #[repr(C, packed)] diff --git a/drivers/gpu/drm/asahi/g17/fw/render.rs b/drivers/gpu/drm/asahi/g17/fw/render.rs index 0e270d40392a8..a6bc1704d1418 100644 --- a/drivers/gpu/drm/asahi/g17/fw/render.rs +++ b/drivers/gpu/drm/asahi/g17/fw/render.rs @@ -1023,7 +1023,11 @@ pub(crate) struct TaDescriptor { /// QID of the tiling queue. pub(crate) qid: u32, pub(crate) unk_8be: u32, - pub(crate) unk_8c2: u32, + /// Non-zero on the first kick of a queue pair, like the fragment descriptor's word at the + /// same position. Set on every kick, the firmware also notifies the host of each pass's + /// tiling completion, which no retirement needs. The 25G83 firmware keeps it set on every + /// kick until this is measured there. + pub(crate) first_kick: u32, /// Completion value of the tiling kick (see /// [`KickTimestamp::completion_value`]). pub(crate) completion: u64, @@ -1073,7 +1077,6 @@ impl TaDescriptor { const ARRAY_OFFSET: u64 = core::mem::offset_of!(Self, registers) as u64; const UNK_876: u32 = u32::MAX; const UNK_892: u32 = 1; - const UNK_8C2: u32 = 1; const UNK_932: u8 = 0x44; const UNK_93C: u8 = 1; const UNK_93E_FIRST_KICK: [u8; 2] = [0xd0, 0x91]; @@ -1110,9 +1113,7 @@ impl TaDescriptor { } else if scs_register { self.registers.set(array_va, ®isters)?; } else { - let mut trimmed = [registers[0]; TA_REGISTERS]; - trimmed[TA_SCS_ENABLE_INDEX..].copy_from_slice(®isters[TA_SCS_ENABLE_INDEX + 1..]); - self.registers.set(array_va, &trimmed)?; + self.registers.set_without(array_va, ®isters, TA_SCS_ENABLE_INDEX)?; } self.tpc_va = resources.tpc_va; self.tpc_size = resources.tpc_size; @@ -1129,7 +1130,10 @@ impl TaDescriptor { self.aux_stamp_va = args.ta.aux_stamp_va; self.stamp_value_2 = stamp_value; self.qid = args.ta.qid.into(); - self.unk_8c2 = Self::UNK_8C2; + self.first_kick = match args.programs == RegisterPrograms::V25G83 { + true => 1, + false => u32::from(args.ordinal == 0), + }; self.current_id = resources.ta_ids.current.into(); self.ordinal = args.ordinal as u32; self.timestamp_start_va = resources.timestamp_va; @@ -1456,10 +1460,7 @@ impl FragmentSlot { } else if scs_register { desc.arrays[0].set(arrays[0], ®isters)?; } else { - let mut trimmed = [registers[0]; FRAGMENT_REGISTERS]; - trimmed[FRAGMENT_SCS_ENABLE_INDEX..] - .copy_from_slice(®isters[FRAGMENT_SCS_ENABLE_INDEX + 1..]); - desc.arrays[0].set(arrays[0], &trimmed)?; + desc.arrays[0].set_without(arrays[0], ®isters, FRAGMENT_SCS_ENABLE_INDEX)?; } desc.arrays[1].set(arrays[1], &partial_store_registers(pass, &words))?; desc.arrays[2].set(arrays[2], &partial_resume_registers(pass, &words))?; diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 59f1bb4714fc0..39c8cfcd77a2a 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -57,22 +57,24 @@ pub(crate) struct Variant { pub(crate) few_primitives: bool, /// The firmware runs ray-traversal work (ray queries and intersection programs). pub(crate) ray_traversal: bool, + /// Tiling descriptors report whether the fragment queue still had earlier kicks + /// outstanding (`fragment_busy`). + pub(crate) fragment_busy: bool, /// Render item rings carry lead commands: InitBufferManager before the first tiling pass - /// of a buffer manager, and FragmentBarrier before every fragment descriptor. + /// of a buffer manager. pub(crate) lead_commands: bool, /// Register programs of the render descriptors. pub(crate) programs: super::render::RegisterPrograms, } -/// A firmware build a SoC supports, selected by the macOS release it ships with and proven by -/// its loaded TEXT. +/// A firmware build a SoC supports, identified by its loaded TEXT. pub(crate) struct Build { - /// macOS version (major, minor, patch). + /// macOS version (major, minor, patch), used to order image candidates. pub(crate) os_version: [u32; 3], /// Name, for the log. pub(crate) name: &'static str, - /// The loaded image, measured from the `fw-text` region. - pub(crate) image: crate::fwid::FirmwareBuild, + /// The admitted loaded images, measured from the `fw-text` region. + pub(crate) images: &'static [crate::fwid::FirmwareBuild], /// Layouts and rules of the build. pub(crate) variant: &'static Variant, } @@ -81,9 +83,8 @@ pub(crate) struct Build { pub(crate) enum Firmware { /// The SoC runs a single build, which is not identified (A18 Pro). Fixed(&'static Variant), - /// One of these builds, selected by the macOS version that the boot loader reports in - /// `apple,firmware-version` (the release whose boot firmware loaded the GPU firmware) and - /// then proven by the loaded TEXT. Any other version or image is refused. + /// One of these exact loaded images. The loader's `apple,firmware-version` is only + /// a candidate-ordering hint because multiple OS releases can share an iBoot string. Identified(&'static [Build]), } @@ -101,6 +102,7 @@ pub(crate) const V26A428: Variant = Variant { mcache: true, few_primitives: true, ray_traversal: true, + fragment_busy: true, lead_commands: false, programs: super::render::RegisterPrograms::G17P, }; @@ -126,6 +128,9 @@ pub(crate) const V25G83: Variant = Variant { few_primitives: false, // A stalled ray traversal wedges the GPU without a firmware recovery. ray_traversal: false, + // When a tiling pass runs out of memory with this set, the firmware resumes it without + // adding memory and the tiler stops silently; macOS always writes 0. + fragment_busy: false, lead_commands: true, programs: super::render::RegisterPrograms::V25G83, }; diff --git a/drivers/gpu/drm/asahi/g17/hazard.rs b/drivers/gpu/drm/asahi/g17/hazard.rs index 20daaab85b832..4d9e99d9a9cc0 100644 --- a/drivers/gpu/drm/asahi/g17/hazard.rs +++ b/drivers/gpu/drm/asahi/g17/hazard.rs @@ -3,11 +3,21 @@ //! Attachment overlap ordering across physical render pairs and shared images. //! Tile stores can overlap even when the application's pixel regions do not. //! Keep independent attachments concurrent; order only conflicting live spans. +//! +//! Within one VM, a render of another logical owner is ordered at publication: +//! the device mutex totally orders publications, and the later render's +//! fragment kick waits in firmware for the newest overlapping fragment kick of +//! every other pair. Admission never waits for such an overlap. Only a +//! cross-VM overlap through a shared object waits on the host for retirement, +//! so that one VM's fault can never hold another VM's kick in firmware. use super::job::Order; const MAX_SPANS: usize = 24; pub(crate) const WAIT_MS: u32 = 1000; +/// Firmware barriers one render publication may add for same-VM overlaps, +/// one per other pair. +pub(crate) const PUBLICATION_BARRIERS: usize = 7; #[derive(Clone, Copy)] struct Span { @@ -56,7 +66,12 @@ impl Footprint { self.shared |= self.count != before; } - fn overlaps(&self, other: &Self) -> bool { + /// Whether a publication orders an overlap with `other` in firmware. + pub(crate) fn same_vm(&self, other: &Self) -> bool { + self.vm == other.vm + } + + pub(crate) fn overlaps(&self, other: &Self) -> bool { if self.vm != other.vm && !(self.shared && other.shared) { return false; } @@ -86,6 +101,13 @@ pub(crate) struct Live<'a> { /// A queue prefix already orders covered predecessors. Pending arrivals order /// different owners fairly without imposing order on unrelated attachments. +/// +/// A same-VM overlap with another owner's admitted render needs no wait; its +/// publication carries one barrier per other pair. That count stays within +/// [`PUBLICATION_BARRIERS`] because such a render is admitted only while fewer +/// than `PUBLICATION_BARRIERS + 1` other pairs of its VM hold admitted renders: +/// of any set of renders overlapping one publication, the last admitted saw all +/// the others live. pub(crate) fn plan<'a>( owner: u64, prefix: Option, @@ -94,10 +116,29 @@ pub(crate) fn plan<'a>( live: impl IntoIterator>, ) -> Plan { let mut selected = None; + let mut ordered = false; + let mut pairs = [0u8; PUBLICATION_BARRIERS]; + let mut count = 0; + let mut crowded = false; for entry in live { + let foreign = entry.owner != owner && footprint.same_vm(entry.footprint); + if let Some(slot) = entry.slot.filter(|_| foreign) { + if !pairs[..count].contains(&slot) { + if count == PUBLICATION_BARRIERS { + crowded = true; + } else { + pairs[count] = slot; + count += 1; + } + } + } if entry.quarantined || !footprint.overlaps(entry.footprint) { continue; } + if foreign { + ordered |= entry.slot.is_some(); + continue; + } let Some(slot) = entry.slot else { if entry.owner != owner && ticket.is_none_or(|mine| entry.ticket < mine) { return Plan::Wait; @@ -116,5 +157,8 @@ pub(crate) fn plan<'a>( Some(_) => return Plan::Wait, } } + if ordered && crowded { + return Plan::Wait; + } selected.map_or(Plan::Any, Plan::Pair) } diff --git a/drivers/gpu/drm/asahi/g17/initdata.rs b/drivers/gpu/drm/asahi/g17/initdata.rs index 10af261aeb3ac..c8763f40871d1 100644 --- a/drivers/gpu/drm/asahi/g17/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/initdata.rs @@ -38,6 +38,26 @@ pub(crate) struct ComputeCompletion<'a> { offset: usize, } +/// Records in the compute completion ring that the host scans. +pub(super) const COMPUTE_COMPLETION_SLOTS: usize = { + let slots = abi::COMPLETION_RING_SIZE / 0x40; + if slots < 0x800 { slots } else { 0x800 } +}; + +/// Timestamp bits of a completion record's start and end words. +const COMPLETION_TIME_MASK: u64 = 0x003f_ffff_ffff_ffff; + +/// What one completion-ring slot gave a scan. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum CompletionFold { + /// No new observation of a pending request; the scan continues. + Passed, + /// Every request now has an observation. + Done, + /// A pending request's record whose payload the firmware is still writing. + Incomplete, +} + impl ComputeCompletion<'_> { pub(crate) fn read(&self, word: usize) -> Result { if word >= 8 { @@ -129,14 +149,6 @@ impl WorkPublication<'_> { ); } } - - fn commit(self) -> u32 { - self.copy_record(); - fence(Ordering::SeqCst); - self.producer.store(self.next, Ordering::Relaxed); - fence(Ordering::SeqCst); - self.next - } } /// Session-long storage and mappings. Aliases are released before their backing objects. @@ -394,6 +406,12 @@ impl InitData { Ok(()) } + pub(super) fn compute_vm_cached(&self, vm: &mmu::Vm) -> bool { + self.compute_shared + .as_ref() + .is_some_and(|shared| vm.compute_pool_mappings_match(shared.id)) + } + pub(super) fn cache_compute_vm(&self, vm: &mmu::Vm) -> Result { let shared = self.compute_shared.as_ref().ok_or(EIO)?; if vm.compute_pool_mappings_match(shared.id) { @@ -510,7 +528,7 @@ impl InitData { let engine = match record.data_master { 0 => DataMaster::Tiling, 1 => DataMaster::Fragment, - 2 => DataMaster::Compute, + // Compute publications reserve their slots through the port. _ => return Err(EINVAL), }; let channel = channels::work_ring_index(priority, engine).ok_or(EINVAL)?; @@ -563,16 +581,6 @@ impl InitData { Ok(values) } - /// Inner queue records must be visible before this outer publication. The returned - /// producer is the retained consumer witness for this slot. - pub(super) fn publish_work( - &self, - priority: u8, - record: &super::fw::channels::WorkSlot, - ) -> Result { - Ok(self.work_publication(priority, record)?.commit()) - } - /// Validate both destinations before exposing fragment, then tiling. The accounting token /// is committed after copying fragment; late tiling state is published before its slot. /// Deferred pairs retain both slot bodies without exposing either producer. @@ -671,21 +679,118 @@ impl InitData { Ok(()) } + /// Raw views of the memory publishers share without the device mutex: every work ring, + /// the completion stamps, the QoS table and the recovery state word. They stay valid for + /// this session. + pub(super) fn port_views( + &self, + mailbox: *const super::Mailbox, + ) -> Result { + use super::fw::{channels, queue::{DataMaster, Policy}}; + let ring_of = |priority: u8, engine: DataMaster| -> Result { + let channel = channels::work_ring_index(priority, engine).ok_or(EINVAL)?; + let base = self.state_offset(Role::Primary) + abi::work_state_offset(channel); + let state = self.object(self.cluster)?; + let word = |offset: usize| -> Result<*const AtomicU32> { + Ok(state.word(base + offset)?) + }; + Ok(super::port::RingView { + slots: self.object(self.bundle)?.pointer( + abi::work_ring_offset(channel), + channels::WORK_RING_SLOTS as usize + * core::mem::size_of::(), + )?, + consumers: [ + word(abi::ChannelState::CONSUMER)?, + word(abi::ChannelState::UNK_10)?, + ], + producer: word(abi::ChannelState::PRODUCER)?, + }) + }; + let rings = |engine: DataMaster| -> Result<[super::port::RingView; Policy::PRIORITIES as usize]> { + let mut views = [ring_of(0, engine)?; Policy::PRIORITIES as usize]; + for (priority, view) in views.iter_mut().enumerate().skip(1) { + *view = ring_of(priority as u8, engine)?; + } + Ok(views) + }; + Ok(super::port::Views { + compute: rings(DataMaster::Compute)?, + tiling: rings(DataMaster::Tiling)?, + fragment: rings(DataMaster::Fragment)?, + stamps: self.object(self.stamps[0])?.word(0)?, + qos: self + .qos()? + .pointer(0, core::mem::size_of::())?, + recovery: self + .object(self.cluster)? + .word(abi::private::STATUS_B + abi::PrimaryStatusB::RECOVERY_STATE)?, + mailbox, + }) + } + /// Snapshot the shared compute completion ring once per service transaction. The queue /// scanner latches exact identities; firmware retains sole ownership of these slots. - pub(super) fn scan_compute_completions( + /// Folds the compute completion records the firmware appended since `start`, in ring + /// order. `seen` holds every slot's control word as of the previous pass over it: the + /// first slot still holding it is where the firmware writes next, and the pass ends there + /// instead of reading the rest of the ring. A pass also ends once `record` has found every + /// request, or at a requested record whose payload is still being written, which the next + /// pass reads again. Returns the slot the next pass starts at. + pub(super) fn fold_compute_completions( &self, + start: usize, + seen: &mut [u64], + mut record: impl FnMut(ComputeCompletion<'_>) -> Result, + ) -> Result { + let object = self.object(self.completion_rings[0])?; + if seen.len() != COMPUTE_COMPLETION_SLOTS { + return Err(EINVAL); + } + fence(Ordering::Acquire); + let completion = |slot: usize| ComputeCompletion { + object, + offset: slot * 0x40, + }; + super::queue::compute::retirement::walk_completions( + start, + seen, + |slot| completion(slot).read(0), + |slot| record(completion(slot)), + ) + } + + /// Folds every slot of the compute completion ring and records its control word in + /// `seen`. For a pass that stopped before a record the firmware must already have + /// written. Returns the slot after the newest record (latest end timestamp), where the + /// firmware writes next. + pub(super) fn resync_compute_completions( + &self, + seen: &mut [u64], mut record: impl FnMut(ComputeCompletion<'_>) -> Result, - ) -> Result { + ) -> Result { let object = self.object(self.completion_rings[0])?; + if seen.len() != COMPUTE_COMPLETION_SLOTS { + return Err(EINVAL); + } fence(Ordering::Acquire); - for slot in 0..(abi::COMPLETION_RING_SIZE / 0x40).min(0x800) { - record(ComputeCompletion { + let mut newest = None; + for slot in 0..COMPUTE_COMPLETION_SLOTS { + let completion = ComputeCompletion { object, offset: slot * 0x40, - })?; + }; + let control = completion.read(0)?; + seen[slot] = control; + if control != 0 { + let end = completion.read(5)? & COMPLETION_TIME_MASK; + if newest.is_none_or(|(_, latest)| end > latest) { + newest = Some((slot, end)); + } + } + record(completion)?; } - Ok(()) + Ok(newest.map_or(0, |(slot, _)| (slot + 1) % COMPUTE_COMPLETION_SLOTS)) } fn object(&self, index: usize) -> Result<&KernelObject> { @@ -1223,7 +1328,11 @@ impl InitData { /// The GPU's operating points: `count` of them, each with one voltage cell for the whole /// GPU or one per cluster. - fn opps(dev: &AsahiDrmDevice, count: usize, clusters: u32) -> Result> { + pub(super) fn opps( + dev: &AsahiDrmDevice, + count: usize, + clusters: u32, + ) -> Result> { let opps = Self::opp_table(dev, c_str!("operating-points-v2"))?; if opps.len() != count { return Err(EINVAL); @@ -1454,7 +1563,10 @@ impl super::recovery::Memory for InitData { .dword(self.status_b + abi::PrimaryStatusB::RECOVERY_SLOT_MASK)? .load(Ordering::Relaxed); let mut sources = super::recovery::Sources::sample(mask as u32, |slot| { - let key = super::recovery::slot_key(slot, self.recovery_slot_keys)?; + // Progress is independently available even when this firmware's key + // address is unknown. Zero keeps ownership unknown and cannot override + // firmware blame, but preserves the progress evidence for diagnostics. + let key = super::recovery::slot_key(slot, self.recovery_slot_keys).unwrap_or(0); let monitor = self.recovery_view_words::<6>(abi::recovery::PROGRESS_VIEW, abi::recovery::PROGRESS_START + slot * abi::recovery::PROGRESS_STRIDE) .ok().map(|words| words[0])?; @@ -1517,17 +1629,22 @@ impl super::recovery::Memory for InitData { Ok(()) } - fn entries(&self) -> Result { + fn entry(&self, index: usize) -> Result<(u32, u32)> { + if index >= super::recovery::RECOVERY_RECORDS { + return Err(EINVAL); + } let cluster = self.object(self.cluster)?; - let base = self.status_b + abi::PrimaryStatusB::RECOVERY_INFO; + let offset = self.status_b + + abi::PrimaryStatusB::RECOVERY_INFO + + index * core::mem::size_of::(); fence(Ordering::Acquire); - let mut entries = super::recovery::Entries::default(); - for index in 0..256 { - let offset = base + index * core::mem::size_of::(); - let flags = cluster.word(offset)?.load(Ordering::Relaxed); - entries.include(flags); - } - Ok(entries) + let flags = cluster + .word(offset + core::mem::offset_of!(abi::RecoveryInfoEntry, flags))? + .load(Ordering::Relaxed); + let stamp = cluster + .word(offset + core::mem::offset_of!(abi::RecoveryInfoEntry, stamp))? + .load(Ordering::Relaxed); + Ok((flags, stamp)) } fn wait_tick(&self) { diff --git a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs index 85aa6468fa311..670f4b96dcb76 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs @@ -8,7 +8,7 @@ //! ([`abi::v25g83`]); free lists, pools, descriptor tables, rings and regions are the A18 Pro's. use super::{abi, low, BuildEnv, BundleObjects, CpuMap, DescriptorTables, InitData, KernelObject}; -use crate::g17::{Config, Role}; +use crate::g17::{calibration::Calibration, Config, Role}; use crate::mmu; use abi::v25g83 as v; use core::sync::atomic::{fence, Ordering}; @@ -74,7 +74,7 @@ impl InitData { }) } - /// Writes the 25G83 firmware objects, checks them against the expected contents, then + /// Writes the 25G83 firmware objects using the chip calibration, then /// stages the opening control-ring state. fn initialize_25g83( &mut self, @@ -101,12 +101,14 @@ impl InitData { let opps = Self::opps(env.dev, cfg.opps, env.clusters)?; let cs_opps = Self::opp_table(env.dev, c_str!("apple,cs-opp"))?; let afr_opps = Self::opp_table(env.dev, c_str!("apple,afr-opp"))?; + let calibration = Calibration::read(env.dev)?; + calibration.check_opps(&opps)?; let hw_args = abi::HwDataArgs { chip_id: cfg.chip_id, windows: cfg.windows, reserved_slots: cfg.reserved_slots, opps: &opps, - perf: cfg.perf, + perf: &calibration.perf, completion_rings: bundle.completion_rings, qos_va: self.object(bundle.qos)?.gpu_va(), }; @@ -117,41 +119,28 @@ impl InitData { }; // SAFETY: Plain C entropy source. let entropy = unsafe { kernel::bindings::get_random_u32() }; - let mut hashes = [0; 3]; self.object_mut(self.bundle)? .initialize::(0, |hw| { hw.init(&hw_args, &hw_args_25g83)?; - hashes[0] = hw.hash(); Ok(()) })?; self.object_mut(self.bundle)? .initialize::(v::POWER_CONFIG, |power| { - power.init(config_va, aon_va, entropy); - hashes[1] = power.hash(); + power.init( + config_va, + aon_va, + entropy, + calibration.max_power_mw, + &calibration.leakage, + ); Ok(()) })?; self.object_mut(self.region_c)? .initialize::(0, |region| { region.init(cfg.region_c); + region.max_power_mw = calibration.max_power_mw; Ok(()) })?; - hashes[2] = v::hash_without(self.object_mut(self.region_c)?.bytes_mut(), &[]); - // The constant contents must equal what the Gravity Linux encoders produce on the proven - // setup; the operating points and the core count come from the device and are not hashed. - let expected = [ - v::expected::HW_DATA, - v::expected::POWER_CONFIG, - v::expected::REGION_C, - ]; - if hashes != expected { - dev_err!( - env.dev.as_ref(), - "25G83 initialization data differs: {:#x?}, expected {:#x?}\n", - hashes, - expected - ); - return Err(EINVAL); - } for (index, word) in v::AON_TABLE.iter().enumerate() { self.object_mut(objects.aon)?.write(index * 4, *word)?; } @@ -167,7 +156,9 @@ impl InitData { v::private::STATUS_B + core::mem::offset_of!(abi::PrimaryStatusB, fwctl_state_va); let cluster = self.object(self.cluster)?; cluster.dword(fwctl)?.store(fwctl_va, Ordering::Relaxed); - cluster.dword(fwctl + 8)?.store(fwctl_va + abi::FWCTL_RING, Ordering::Relaxed); + cluster + .dword(fwctl + 8)? + .store(fwctl_va + abi::FWCTL_RING, Ordering::Relaxed); // One main configuration carrying both roles. let main_offset = abi::bundle::MAIN_CONFIG[Role::Primary as usize]; @@ -218,7 +209,8 @@ impl InitData { )?; for &(offset, value, repeat) in v::POWER_STATE_WORDS { for index in 0..repeat { - self.object_mut(objects.power_state)?.write(offset + 4 * index, value)?; + self.object_mut(objects.power_state)? + .write(offset + 4 * index, value)?; } } diff --git a/drivers/gpu/drm/asahi/g17/job.rs b/drivers/gpu/drm/asahi/g17/job.rs index 3b47d361c2ef9..ad9adcecf0f81 100644 --- a/drivers/gpu/drm/asahi/g17/job.rs +++ b/drivers/gpu/drm/asahi/g17/job.rs @@ -14,7 +14,10 @@ use kernel::{ }; pub(super) const TIMEOUT_MS: kernel::time::Msecs = super::timeout::POLL_MS; -const RENDER_CREDITS: u32 = 32; +/// One logical render queue owns one physical pair, whose active ring holds +/// `RENDER_DEPTH` commands. A deeper scheduler would only park run_job in +/// admission until a retirement; the scheduler waits for the credit instead. +const RENDER_CREDITS: u32 = crate::hw::t8140::queues::RENDER_DEPTH as u32; const COMPUTE_CREDITS: u32 = 16; #[derive(Clone, Copy, PartialEq, Eq)] @@ -150,6 +153,7 @@ impl Packet { super::dependency::replay_status(fence_status(fence))?; } for fence in &self.firmware { check_status(fence)?; } + self.completion.check_source_inputs()?; Ok(()) } @@ -183,6 +187,7 @@ impl Packet { for fence in &self.firmware { check_status(fence)?; } + self.completion.check_source_inputs()?; Ok(()) } @@ -262,6 +267,9 @@ fn check_status(fence: &Fence) -> Result { /// It performs all completion signalling after releasing the device mutex. pub(crate) trait Backend: Send + Sync { /// Device detector retained by render completions after queue teardown. + fn input_sources(&self, _inputs: &[crate::file::SyncItem]) + -> Option> { None } + fn feed(&self) -> Option> { None } @@ -277,12 +285,16 @@ pub(crate) trait Backend: Send + Sync { fn fail_vm(&self, error: Error); /// Called after both scheduler entities have cancelled their queued jobs. fn release_owner(&self); - fn prepare(&self, packet: &Packet) -> Option; + /// A fence to wait for before `publish`, signalled by the release of the capacity the + /// packet lacks. Called again once it signals. + fn prepare(&self, packet: &Arc) -> Option; fn publish(&self, packet: Arc) -> Result; fn timed_out(&self, packet: Arc) -> sched::Status; fn cancel(&self, packet: Arc); /// Transfer a timeout to an outstanding off-lock graph transaction, if any. fn defer_timeout(&self, packet: &Arc, renewals: u8) -> bool; + /// A long job of another queue is running beside `packet`. + fn long_job_beside(&self, _packet: &Arc) -> bool { false } /// Return true only after taking publication ownership and recording its result. fn try_early(&self, packet: &Arc) -> bool; fn count_backlog(&self, packet: &Packet); @@ -297,6 +309,7 @@ struct Timeout { backend: Arc, victim_renewals: u8, execution_renewals: u8, + shared_renewals: u8, packet: Arc, } @@ -321,6 +334,7 @@ impl sched::JobImpl for Job { backend: self.backend.clone(), victim_renewals: 0, execution_renewals: 0, + shared_renewals: 0, packet: self.packet.clone(), } } @@ -334,6 +348,13 @@ impl sched::JobImpl for Job { ) { return true; } + // Other work retiring means the GPU is shared, not hung. So does another long job + // running beside a long job, though neither retires until it ends. + let shared = super::timeout::retiring(super::recovery::since_retired(super::now_ns())) + || (job.packet.command.long_job() && job.backend.long_job_beside(&job.packet)); + if super::timeout::renew_shared(job.packet.is_published(), shared, &mut job.shared_renewals) { + return true; + } let deferred = job.backend.defer_timeout(&job.packet, job.victim_renewals); if deferred { job.victim_renewals = job.victim_renewals.saturating_add(1); @@ -342,10 +363,17 @@ impl sched::JobImpl for Job { } fn prepare(job: &mut Self) -> Option { - job.backend.prepare(&job.packet) + super::trace::schedule(&job.packet, 5, 0); + let wait = job.backend.prepare(&job.packet); + super::trace::schedule(&job.packet, 6, i32::from(wait.is_some())); + wait } fn run(job: &mut Self) -> Result> { - job.backend.publish(job.packet.clone()).map(Some) + super::trace::schedule(&job.packet, 3, 0); + let result = job.backend.publish(job.packet.clone()).map(Some); + super::trace::schedule(&job.packet, 4, + result.as_ref().err().map_or(0, |error| error.to_errno())); + result } fn timed_out(job: &mut Timeout, finished: bool) -> sched::Status { // The scheduler retains the detached job throughout this callback. Keep @@ -375,6 +403,18 @@ pub(crate) struct Fences { pub(crate) publication: Option>, } +impl Fences { + /// Scheduler::enqueue always supplies our explicit completion fence here. + /// It has no polling callback, so pending frontier checks need no lock. + pub(crate) fn status(&self) -> i32 { + unsafe extern "C" { + fn asahi_g17_completion_fence_status(fence: *mut bindings::dma_fence) -> i32; + } + // SAFETY: completed owns the live private fence throughout this read. + unsafe { asahi_g17_completion_fence_status(self.completed.raw()) } + } +} + impl Scheduler { pub(crate) fn new(dev: &AsahiDrmDevice, backend: Arc, engine: Engine) -> Result { let (credits, name) = match engine { @@ -397,6 +437,7 @@ impl Scheduler { early: bool, publication: Option, ) -> Result { + super::trace::schedule(&packet, 0, dependencies.len() as i32); let completed = packet.completion.fence(); let host_timestamps = packet.completion.has_timestamps(); // Only commands which actually write timestamps become host-prefix @@ -429,7 +470,12 @@ impl Scheduler { }; // All fallible scheduler setup precedes early hardware publication. The // scheduler still owns watchdog, credits and cancellation after push. - if packet.engine() == Engine::Render && !(early && self.backend.try_early(&packet)) { + let inline = early && self.backend.try_early(&packet); + super::trace::schedule(&packet, if inline { 1 } else { 2 }, 0); + if !inline { + if packet.completion.needs_source_ready() { + packet.completion.set_source_ready(ready.clone()); + } self.backend.count_backlog(&packet); } job.push(); diff --git a/drivers/gpu/drm/asahi/g17/kick.rs b/drivers/gpu/drm/asahi/g17/kick.rs index c337a8b298f19..6499cae106309 100644 --- a/drivers/gpu/drm/asahi/g17/kick.rs +++ b/drivers/gpu/drm/asahi/g17/kick.rs @@ -24,22 +24,38 @@ use crate::{hw::t8140, mem, mmu}; pub(super) const QID_COUNT: usize = QID_MAX as usize + 1; -/// Minimum render-channel capacity protected from compute-only allocation. -// Protect one tiling/fragment pair for each logical execution context, plus -// the two unpublished bootstrap reservations. The remaining ten compute -// channels are shared after exact retirement, rather than pinned per client. -const COMPUTE_RENDER_RESERVE: usize = 2 * crate::mmu::MAX_EXECUTION_CONTEXTS as usize + 2; -pub(super) const COMPUTE_CHANNELS: usize = QID_COUNT - COMPUTE_RENDER_RESERVE; -/// Logical queues that can each hold a render pair: what the render reserve leaves beside the -/// two bootstrap reservations once the QIDs below `floor`, which the firmware keeps, are gone -/// (every execution context with no floor). +/// Render identities that must stay reachable for `queues` logical queues: +/// one tiling/fragment pair each, plus the two bootstrap reservations. +/// +/// Installed identities never change engine before processor stop, so a +/// compute channel permanently narrows what render can still install. Compute +/// may take a free QID only while every open logical queue, and +/// [`RENDER_HEADROOM`] queues opened later, can still install a dedicated +/// pair; logical-queue admission refuses a queue whose pair no longer fits. +pub(super) const fn render_reserve(queues: usize) -> usize { + let queues = queues + RENDER_HEADROOM; + let max = crate::mmu::MAX_EXECUTION_CONTEXTS as usize; + 2 * if queues < max { queues } else { max } + 2 +} +/// Logical queues that may still open after compute growth without losing a +/// dedicated render pair. +const RENDER_HEADROOM: usize = 24; +/// The render reserve with every execution context open. +const FULL_RENDER_RESERVE: usize = render_reserve(crate::mmu::MAX_EXECUTION_CONTEXTS as usize); +/// Compute channels that stay reachable with every execution context open. +#[cfg(CONFIG_KUNIT = "y")] +pub(super) const COMPUTE_FLOOR: usize = QID_COUNT - FULL_RENDER_RESERVE; +/// Upper bound on distinct physical compute channels named by one kick. +pub(super) const COMPUTE_CHANNELS: usize = QID_COUNT; +/// Maximum logical queues after firmware-owned QIDs are excluded. pub(super) const fn queue_limit(floor: usize) -> u32 { - ((COMPUTE_RENDER_RESERVE - 2 - floor) / 2) as u32 + ((FULL_RENDER_RESERVE - 2 - floor) / 2) as u32 } const _: () = { assert!(queue_limit(0) == crate::mmu::MAX_EXECUTION_CONTEXTS); - assert!(COMPUTE_CHANNELS >= 4); + // Compute keeps this many channels even with every execution context open. + assert!(QID_COUNT - FULL_RENDER_RESERVE >= 4); assert!(crate::hw::t8140::queues::RENDER_PAIRS_PER_OWNER == 1); }; @@ -71,7 +87,6 @@ struct Identity { pub(crate) struct QueueIds { entries: [Option; QID_COUNT], initial_render: [Option; 2], - publication: u64, leased: usize, /// First QID the host may use ([`crate::g17::fw::Variant::qid_floor`]). floor: usize, @@ -82,10 +97,9 @@ impl QueueIds { kernel::try_init!(Self { entries <- pin_init::init_array_from_fn(|_| None), initial_render: [None; 2], - publication: 0, leased: 0, // The reserve must keep render capacity above the floor. - floor: if floor < COMPUTE_RENDER_RESERVE { floor } else { return Err(EINVAL) }, + floor: if floor < FULL_RENDER_RESERVE { floor } else { return Err(EINVAL) }, }) } @@ -113,6 +127,58 @@ impl QueueIds { owner: u64, engine: DataMaster, preferred: Option, + ) -> Result { + self.reserve_for(owner, engine, preferred, crate::mmu::MAX_EXECUTION_CONTEXTS as usize) + } + + fn render_ids(&self) -> usize { + self.entries + .iter() + .flatten() + .filter(|entry| matches!(entry.id.engine, DataMaster::Tiling | DataMaster::Fragment)) + .count() + } + + /// Whether `queues` open logical queues can each still install a dedicated + /// render pair, beside the bootstrap reservations and installed compute. + pub(crate) fn render_fits(&self, queues: usize) -> bool { + self.render_ids() + (QID_COUNT - self.floor - self.leased) >= 2 * queues + 2 + } + + /// Whether a render pair not yet built can still reserve both of its identities. + pub(crate) fn render_pair_fits(&self) -> bool { + QID_COUNT - self.floor - self.leased >= 2 + } + + /// Whether a fresh compute identity fits beside the render reserve of + /// `queues` open logical queues, without reserving it. + pub(crate) fn compute_fits(&self, queues: usize) -> bool { + let initial = self.leased == QID_COUNT - self.floor + && self.initial_render.iter().flatten().any(|id| self.can_cancel_unpublished(*id)); + self.compute_room(queues, usize::from(initial)) + } + + fn compute_room(&self, queues: usize, replacement: usize) -> bool { + // Existing render identities already satisfy this reservation. Counting + // them twice can refuse compute even while physical QIDs remain free. + // Replacing an initial reservation consumes one render identity. + // Include that transition when protecting render's minimum, and + // count the replacement as available compute capacity. + let render = self.render_ids(); + QID_COUNT - self.floor - self.leased + replacement + > render_reserve(queues) + .min(FULL_RENDER_RESERVE - self.floor) + .saturating_sub(render - replacement) + } + + /// As [`Self::reserve`], with the compute reserve sized for `queues` open + /// logical queues instead of every possible execution context. + pub(crate) fn reserve_for( + &mut self, + owner: u64, + engine: DataMaster, + preferred: Option, + queues: usize, ) -> Result { if owner == 0 { return Err(EINVAL); @@ -129,10 +195,8 @@ impl QueueIds { Err(EINVAL) }; } - // The QIDs below the floor are never the host's; the reserve shrinks - // with them, so the compute channel count stays the same. + // The QIDs below the floor are never available to the host. let total = QID_COUNT - self.floor; - let reserve = COMPUTE_RENDER_RESERVE - self.floor; let initial = if engine == DataMaster::Compute && self.leased == total { self.initial_render .iter() @@ -145,27 +209,9 @@ impl QueueIds { } else { None }; - // Existing render identities already satisfy this reservation. Counting - // them twice can refuse compute even while physical QIDs remain free. // Published identities always retain their original lifetime rules. - if engine == DataMaster::Compute { - let render = self - .entries - .iter() - .flatten() - .filter(|entry| { - matches!(entry.id.engine, DataMaster::Tiling | DataMaster::Fragment) - }) - .count(); - // Replacing an initial reservation consumes one render identity. - // Include that transition when protecting render's minimum, and - // count the replacement as available compute capacity. - let replacement = usize::from(initial.is_some()); - if total - self.leased + replacement - <= reserve.saturating_sub(render - replacement) - { - return Err(crate::g17::capacity::refused(crate::g17::capacity::Site::ComputeReserve)); - } + if engine == DataMaster::Compute && !self.compute_room(queues, usize::from(initial.is_some())) { + return Err(crate::g17::capacity::refused(crate::g17::capacity::Site::ComputeReserve)); } let index = preferred .map(|index| usize::from(index) + self.floor) @@ -196,13 +242,6 @@ impl QueueIds { Ok(id) } - /// Ordered under the device mutex; no atomic operation on the submission path. Compute and - /// render publications share it, so a watchdog can order work across both engines. - pub(crate) fn next_publication(&mut self) -> Result { - self.publication = self.publication.checked_add(1).ok_or(EOVERFLOW)?; - Ok(self.publication) - } - /// Call after preflighting the complete installation, before any address /// becomes firmware-visible. A failed or partial installation retains it. pub(crate) fn publish(&mut self, id: Id) -> Result { @@ -249,10 +288,10 @@ mod accounting_tests { #[test] fn render_identities_satisfy_the_compute_reserve() { let mut ids = table(); - for owner in 1..=COMPUTE_RENDER_RESERVE as u64 { + for owner in 1..=FULL_RENDER_RESERVE as u64 { ids.reserve(owner, DataMaster::Tiling, None).unwrap(); } - for owner in COMPUTE_RENDER_RESERVE as u64 + 1..=QID_COUNT as u64 { + for owner in FULL_RENDER_RESERVE as u64 + 1..=QID_COUNT as u64 { ids.reserve(owner, DataMaster::Compute, None).unwrap(); } assert_eq!(ids.leased, QID_COUNT); @@ -263,14 +302,49 @@ mod accounting_tests { #[test] fn compute_cannot_consume_the_render_minimum() { let mut ids = table(); - for owner in 1..=(QID_COUNT - COMPUTE_RENDER_RESERVE) as u64 { + for owner in 1..=(QID_COUNT - FULL_RENDER_RESERVE) as u64 { ids.reserve(owner, DataMaster::Compute, None).unwrap(); } assert_eq!(ids.reserve(1000, DataMaster::Compute, None), Err(ENOSPC)); let tiling = ids.reserve(1001, DataMaster::Tiling, None).unwrap(); assert_eq!(ids.reserve(1000, DataMaster::Compute, None), Err(ENOSPC)); ids.cancel_unpublished(tiling).unwrap(); - assert_eq!(ids.leased, QID_COUNT - COMPUTE_RENDER_RESERVE); + assert_eq!(ids.leased, QID_COUNT - FULL_RENDER_RESERVE); + } + + #[test] + fn dynamic_compute_growth_preserves_render_headroom_above_firmware_floor() { + for floor in [0, 4, 32] { + let mut ids = KBox::init(QueueIds::new(floor), GFP_KERNEL).unwrap(); + let queues = 2; + let mut owner = 1; + while ids.compute_fits(queues) { + let id = ids.reserve_for(owner, DataMaster::Compute, None, queues).unwrap(); + assert!(usize::from(id.qid()) >= floor); + ids.publish(id).unwrap(); + owner += 1; + } + assert_eq!(ids.leased, QID_COUNT - floor - render_reserve(queues)); + assert_eq!(ids.reserve_for(owner, DataMaster::Compute, None, queues), Err(ENOSPC)); + assert!(ids.render_fits(queues + RENDER_HEADROOM)); + assert!(!ids.render_fits(queues + RENDER_HEADROOM + 1)); + } + } + + #[test] + fn full_logical_queue_budget_keeps_compute_floor_and_excludes_firmware_ids() { + for floor in [0, 4, 32] { + let mut ids = KBox::init(QueueIds::new(floor), GFP_KERNEL).unwrap(); + let queues = queue_limit(floor) as usize; + for owner in 1..=COMPUTE_FLOOR as u64 { + let id = ids.reserve_for(owner, DataMaster::Compute, None, queues).unwrap(); + assert!(usize::from(id.qid()) >= floor); + ids.publish(id).unwrap(); + } + assert!(!ids.compute_fits(queues)); + assert!(ids.render_fits(queues)); + assert!(!ids.render_fits(queues + 1)); + } } #[test] diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 897a982b3728a..1bb7a30e8e033 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -12,12 +12,18 @@ //! //! # Locking //! -//! `Shared::state` is the device lock. It serializes all firmware-visible publication and all -//! firmware-shared state, including the bounded waits for the firmware that publication needs. -//! Completions are signalled after dropping it. Lock order: `Shared::state` -> UAT shared state -//! -> VM exec lock -> GEM reservation. `RoleState::ack` nests inside nothing. - +//! `Shared::state` is the device lock. It serializes binding, recovery, the event worker and all +//! firmware-shared state that has no narrower owner. Steady-state compute publication into a +//! bound queue takes only that queue's cell lock and the port's locks ([`port`]), reached under +//! a preparation lease. Completions are signalled after dropping the device lock. Lock order: +//! `Shared::state` -> compute queue cell (one at a time) -> port locks -> availability domains; +//! `Shared::state` -> UAT shared state -> VM exec lock -> GEM reservation. `RoleState::ack` +//! nests inside nothing. Capacity waits block on an availability domain signalled by the +//! release that provides the capacity, never on a sleep. + +mod availability; mod buffer; +mod calibration; mod channel; mod command; pub(crate) use command::Attachments; @@ -25,6 +31,9 @@ mod completion; pub(crate) mod context; mod data_segment; mod dependency; +mod sync_input; +pub(crate) mod sync_output; +mod trace; #[cfg(CONFIG_DRM_ASAHI_G15)] pub(crate) use dependency::inherited_error; mod event; @@ -39,7 +48,9 @@ mod job; mod capacity; mod kick; mod object; +mod port; pub(crate) mod power; +mod reports; mod preparation; mod qos; mod queue; @@ -55,6 +66,8 @@ use core::any::Any; use core::ops::Range; use core::sync::atomic::{ AtomicBool, + AtomicU32, + AtomicU64, Ordering, // }; @@ -359,7 +372,7 @@ impl rtkit::Operations for RoleOps { if !is_events_message(ep, msg) { return false; } - data.shared.queue_events(); + data.shared.queue_event_records(); true } @@ -394,13 +407,48 @@ impl rtkit::Operations for RoleOps { } } +/// The RTKit of one coprocessor. The mailbox driver serializes its own register writes; this +/// lock only serializes the exclusive borrows the RTKit API takes, so that publishers holding +/// different locks can ring the same doorbell. +#[pin_data] +struct Mailbox { + #[pin] + lock: Mutex<()>, + rtkit: core::cell::UnsafeCell>, +} + +// SAFETY: Every access to the RTKit goes through `with`, which holds the lock. +unsafe impl Sync for Mailbox {} + +impl Mailbox { + fn new(rtkit: rtkit::RtKit) -> Result>> { + KBox::pin_init( + pin_init!(Self { + lock <- new_mutex!((), "g17::Mailbox::lock"), + rtkit: core::cell::UnsafeCell::new(rtkit), + }), + GFP_KERNEL, + ) + } + + fn with(&self, f: impl FnOnce(Pin<&mut rtkit::RtKit>) -> R) -> R { + let _guard = self.lock.lock(); + // SAFETY: The lock excludes every other borrow of the RTKit. + f(Pin::new(unsafe { &mut *self.rtkit.get() })) + } + + fn send(&self, endpoint: u8, message: u64) -> Result { + self.with(|rtkit| rtkit.send_message(endpoint, message)) + } +} + /// A running firmware coprocessor. /// /// Dropping it stops the CPU before RTKit and the crash log buffer go away. struct Coprocessor { cpu: regs::CpuControl, running: bool, - rtkit: rtkit::RtKit, + rtkit: Pin>, state: Arc, _crash_buffer: Option, } @@ -455,7 +503,7 @@ impl Coprocessor { Ok(Coprocessor { cpu: regs::CpuControl::new(pdev, index)?, running: false, - rtkit: Self::receiver(dev, index, &state)?, + rtkit: Mailbox::new(Self::receiver(dev, index, &state)?)?, state, _crash_buffer: crash_buffer, }) @@ -484,19 +532,20 @@ impl Coprocessor { fn boot(&mut self) -> Result { self.cpu.start()?; self.running = true; - Pin::new(&mut self.rtkit).boot() + self.rtkit.with(|rtkit| rtkit.boot()) } /// Starts the firmware endpoints and hands the firmware its initialization root. An /// acknowledgment message is awaited here; one in status A is the caller's to poll. fn init(&mut self, root_va: u64, ack: fw::variant::InitAck) -> Result { - let mut rtkit = Pin::new(&mut self.rtkit); - rtkit.as_mut().start_endpoint(EP_FIRMWARE)?; - rtkit.as_mut().start_endpoint(EP_DOORBELL)?; - rtkit.send_message( - EP_FIRMWARE, - MSG_INITDATA | (root_va & MSG_INITDATA_ADDR_MASK), - )?; + self.rtkit.with(|mut rtkit| { + rtkit.as_mut().start_endpoint(EP_FIRMWARE)?; + rtkit.as_mut().start_endpoint(EP_DOORBELL)?; + rtkit.send_message( + EP_FIRMWARE, + MSG_INITDATA | (root_va & MSG_INITDATA_ADDR_MASK), + ) + })?; match ack { fw::variant::InitAck::Message => self.state.wait_for_ack(), fw::variant::InitAck::Status => Ok(()), @@ -504,8 +553,9 @@ impl Coprocessor { } /// Sends a host notification to the firmware. - fn notify(&mut self, msg: u64) -> Result { - Pin::new(&mut self.rtkit).send_message(EP_DOORBELL, msg) + fn notify(&self, msg: u64) -> Result { + trace::message(trace::MAILBOX, msg); + self.rtkit.send(EP_DOORBELL, msg) } /// Stops the CPU. The firmware keeps its state, but no longer runs. @@ -572,6 +622,10 @@ struct Firmware { secondary: Option, primary: Coprocessor, bridge: Bridge, + /// Publication state shared with publishers that do not hold the device mutex. + port: Arc, + /// Fast-path submissions already accounted by the idle check. + seen_submissions: u64, rings: channel::Rings, recovery: recovery::State, effort: power::Effort, @@ -595,6 +649,7 @@ impl Drop for Firmware { secondary.stop(); } self.primary.stop(); + self.port.set_effort_full(false); self.pool.stop_after_firmware(); // RTKit destruction releases the mailbox power references. Clear the bridge while // those references still keep the register block powered. @@ -641,39 +696,56 @@ impl Firmware { /// Called at actual submission publication, not while a queued job is merely being built. fn note_submission(&mut self) -> Result { + self.port.note_gpu_activity(); self.effort.submit(); self.apply_effort(power::FULL_EFFORT) } fn apply_effort(&mut self, effort: u32) -> Result { - Self::apply_shared_effort(&self.init, &mut self.effort, effort) + Self::apply_shared_effort(&self.init, &mut self.effort, &self.port, effort) } fn apply_shared_effort( init: &initdata::InitData, state: &mut power::Effort, + port: &port::Port, effort: u32, ) -> Result { if state.applied() == effort && init.effort()? == effort { return Ok(()); } init.publish_effort(effort)?; + trace::message(trace::EFFORT, u64::from(effort)); state.did_apply(effort); + port.set_effort_full(effort == power::FULL_EFFORT); Ok(()) } /// Returns the delay in ns for a follow-up idle check. Queue owners report only submitted, - /// unretired work; quarantined work retaining mappings is excluded. + /// unretired work; quarantined work retaining mappings is excluded. Submissions published + /// without the device mutex count as activity, and a clear that races one is abandoned. fn idle_check(&mut self, now_ns: u64, in_flight: bool) -> Result> { - let effort = match self.effort.idle(now_ns, in_flight) { + let submissions = self.port.submissions(); + let submitted = submissions != self.seen_submissions; + self.seen_submissions = submissions; + if submitted { + self.effort.submit(); + } + let effort = match self.effort.idle(now_ns, in_flight || submitted) { power::IdleAction::None => None, power::IdleAction::Clear => { + self.port.set_effort_full(false); + if self.port.submissions() != submissions { + self.port.set_effort_full(true); + self.effort.submit(); + return Ok(Some(power::IDLE_HOLD_NS)); + } self.apply_effort(0)?; None } power::IdleAction::Recheck(delay) => Some(delay), }; - Ok(match (effort, self.idle_hold(now_ns, in_flight)) { + Ok(match (effort, self.idle_hold(now_ns, in_flight || submitted)) { (Some(a), Some(b)) => Some(a.min(b)), (a, b) => a.or(b), }) @@ -773,8 +845,28 @@ struct Shared { #[pin] feed_work: Work, preparations: Arc, + /// Compute admission waiting for a physical channel: signalled when a channel becomes + /// reusable or shareable, a QID or slot is returned, or a pool vacancy is witnessed. + channels: Arc, + /// Compute admission waiting for retirement-bound capacity (work states, ring and + /// in-flight room, drain witnesses, pool releases) or for publication to reopen. + progress: Arc, + /// Logical-queue creation waiting for a returned queue slot or execution context. + queues: Arc, + /// Render publication waiting for render capacity, a pair on loan, a work + /// state or firmware room: signalled when a pair, lease or work state + /// returns, an owner closes, a teardown parks, and on every event pass + /// while a render publisher waits (firmware consumption has no event). + renders: Arc, + /// Unbound compute publishers which can reuse a retired channel. + compute_demand: AtomicU32, #[pin] work: Work, + /// The next event pass runs every housekeeping step: set by every [`Self::queue_events`] + /// request, not by the firmware's event notification ([`Self::queue_event_records`]). + housekeeping: AtomicBool, + /// When an event pass last ran every housekeeping step, in ns. + housekeeping_ns: AtomicU64, #[pin] poll: DelayedWork, #[pin] @@ -795,6 +887,8 @@ struct Shared { state: Mutex>>, #[pin] changed: CondVar, + /// The port of the session in `state`; null before boot and once detachment begins. + port: core::sync::atomic::AtomicPtr, } impl_has_work! { @@ -884,8 +978,38 @@ impl Shared { drop(retired); } - /// Queues the event worker. Callable from any context. + /// Wakes the waiters for a returned pair, binding or capacity change. + fn note_changed(&self) { + self.changed.notify_all(); + self.renders.notify(); + } + + + /// The session's port, valid while `lease` is held: shutdown and device loss join every + /// lease before they detach the session. + fn port<'a>(&self, lease: &'a preparation::Lease) -> Option<&'a port::Port> { + let _ = lease; + // SAFETY: The pointer is published before the session becomes reachable and cleared + // only after every lease has been joined, so it names a live port for 'a. + unsafe { self.port.load(Ordering::Acquire).as_ref() } + } + + /// Called after joining every preparation lease, before the session is detached. + fn forget_port(&self) { + self.port.store(core::ptr::null_mut(), Ordering::Release); + } + + /// Queues the event worker for a pass that runs every housekeeping step. Callable from any + /// context. fn queue_events(self: &Arc) { + self.housekeeping.store(true, Ordering::Release); + self.queue_event_records(); + } + + /// Queues the event worker for firmware records alone: completions, or records that arrived + /// during a pass. The pass skips the housekeeping that nothing needs (see + /// `runtime_event_turn`). Callable from any context. + fn queue_event_records(self: &Arc) { // Already queued if this fails, which is just as good. let _ = workqueue::system_highpri().enqueue::, EVENT_WORK>(self.clone()); } @@ -912,6 +1036,7 @@ impl Shared { // Context release returns its execution root off-lock. The next // device pass can now detach idle compute aliases and their QoS // leases, without depending on a new client's submission/event. + self.queues.notify(); self.queue_events(); } } @@ -992,6 +1117,8 @@ pub(crate) struct Gpu { ids: gpu::SequenceIDs, shared: Arc, _capacity: Pin>>, + _reports: Pin>>, + _power: Pin>>, } impl Gpu { @@ -1002,8 +1129,19 @@ impl Gpu { cfg: &'static Config, regs: regs::G17Resources, ) -> Result> { + let calibration = if cfg.chip_id == crate::hw::t6040::CHIP_ID { + Some(calibration::Calibration::read(dev).inspect_err(|_| { + dev_err!(dev.as_ref(), "Missing or invalid T6040 GPU calibration handoff\n"); + })?) + } else { + None + }; let variant = Self::firmware_variant(dev, &cfg.firmware)?; let id = regs.get_g17_id()?; + if let Some(calibration) = calibration { + calibration.check_cores(&id.core_masks)?; + calibration.check_opps(&initdata::InitData::opps(dev, cfg.opps, id.num_clusters)?)?; + } if id.family != cfg.identity.family || id.variant != cfg.identity.variant || id.num_dies != cfg.identity.num_dies @@ -1023,7 +1161,17 @@ impl Gpu { oas: cfg.uat_oas, }; let max_frequency_khz = Self::max_frequency_khz(dev)?; - let preparations = preparation::Gate::new()?; + let node = dev.as_ref().as_ref().of_node().ok_or(EIO)?; + let idle_delay = power::idle_delay( + node.get_opt_property(c_str!("apple,idle-off-delay-ms"))?, + )?; + power::IDLE_HOLD_MS.store(idle_delay, Ordering::Relaxed); + dev_info!(dev.as_ref(), "GPU idle power-off delay: {} ms\n", idle_delay); + let channels = availability::Availability::new()?; + let progress = availability::Availability::new()?; + let queues = availability::Availability::new()?; + let preparations = preparation::Gate::with_waiters(Some(progress.clone()))?; + let renders = availability::Availability::new()?; let reclaim = runtime::teardown::ReclaimBatch::new()?; let feed = feed::Feed::new()?; let active_cores = id.core_masks.iter().map(|mask| mask.count_ones()).sum(); @@ -1054,7 +1202,14 @@ impl Gpu { feed, feed_work <- new_work!("g17::Shared::feed_work"), preparations, + channels, + progress, + queues, + renders, + compute_demand: AtomicU32::new(0), work <- new_work!("g17::Shared::work"), + housekeeping: AtomicBool::new(true), + housekeeping_ns: AtomicU64::new(0), poll <- new_delayed_work!("g17::Shared::poll"), idle <- new_delayed_work!("g17::Shared::idle"), grow <- new_work!("g17::Shared::grow"), @@ -1063,6 +1218,7 @@ impl Gpu { stop <- new_mutex!((), "g17::Shared::stop"), state <- new_mutex!(None, "g17::Shared::state"), changed <- new_condvar!("g17::Shared::changed"), + port: core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()), }), GFP_KERNEL, )?; @@ -1072,6 +1228,14 @@ impl Gpu { directory.read_only_file(c"capacity", capacity::View(shared.clone())), GFP_KERNEL, )?; + let reports = KBox::pin_init( + directory.read_only_file(c"firmware_reports", reports::View(shared.clone())), + GFP_KERNEL, + )?; + let power = KBox::pin_init( + directory.read_only_file(c"power", reports::PowerView(shared.clone())), + GFP_KERNEL, + )?; let gpu = Arc::new( Gpu { cfg, @@ -1081,10 +1245,15 @@ impl Gpu { ids: Default::default(), shared, _capacity: capacity, + _reports: reports, + _power: power, }, GFP_KERNEL, )?; let firmware = Self::boot(pdev, dev, cfg, regs, geometry, &gpu.shared)?; + gpu.shared + .port + .store(Arc::as_ptr(&firmware.port).cast_mut(), Ordering::Release); *gpu.shared.state.lock() = Some(firmware); gpu.shared.queue_events(); Ok(gpu) @@ -1101,24 +1270,26 @@ impl Gpu { fw::variant::Firmware::Identified(builds) => builds, }; let version = Self::os_firmware_version(dev); - if let Some(build) = builds.iter().find(|build| Some(build.os_version) == version) { - dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); - Self::identify_image(dev, build).map_err(|_| ENODEV)?; - return Ok(build.variant); + // iBoot strings can be shared by OS releases. Prefer the reported release's + // candidate, but admit only an exact loaded image, regardless of that hint. + let preferred = builds.iter().filter(|build| Some(build.os_version) == version); + let others = builds.iter().filter(|build| Some(build.os_version) != version); + for build in Iterator::chain(preferred, others) { + if Self::identify_image(dev, build).is_ok() { + dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); + return Ok(build.variant); + } } dev_err!( dev.as_ref(), - "Unsupported GPU firmware: macOS version {:?} (apple,firmware-version)\n", + "Unsupported GPU firmware image (reported macOS version {:?})\n", version ); - for build in builds.iter() { - dev_err!(dev.as_ref(), "Supported GPU firmware: {}\n", build.name); - } Err(ENODEV) } - /// Checks that the loaded TEXT (the `fw-text` region) is `build`'s image, with the DATA size - /// of the `fw-data` region. The version property only says which build to expect. + /// Checks that the loaded TEXT (the `fw-text` region) is one of `build`'s images, with the DATA size + /// of the `fw-data` region. The version property is only a selection hint. fn identify_image(dev: &AsahiDrmDevice, build: &'static fw::variant::Build) -> Result { let node = dev.as_ref().as_ref().of_node().ok_or(EINVAL)?; let text = node.reserved_mem_region_to_resource_byname(c_str!("fw-text"))?; @@ -1129,7 +1300,7 @@ impl Gpu { dev.as_ref().as_ref(), start..end, data.size(), - core::slice::from_ref(&build.image), + build.images, ) .inspect_err(|e| dev_err!(dev.as_ref(), "GPU firmware image not admitted: {:?}\n", e))?; Ok(()) @@ -1215,17 +1386,21 @@ impl Gpu { secondary, primary, bridge, - rings: channel::Rings::new(), + port: port::Port::new(power::Demand::new(pdev.as_ref().as_raw())?)?, + seen_submissions: 0, + rings: channel::Rings::new()?, recovery: recovery::State::new(), effort: power::Effort::new(), hold: power::Hold::new(), registration_power_release_pending: false, variant: shared.variant, - queues <- runtime::Registry::new( + queues <- runtime::Registry::with_waiters( shared.clusters, shared.deflake, shared.descriptor_flags, shared.variant, + Some(shared.queues.clone()), + Some(shared.renders.clone()), ), pool: object::Pool::new()?, render_rings: kick::RenderRings::new()?, @@ -1261,6 +1436,8 @@ impl Gpu { if let Some(root_entries) = root_entries { session.uat.confirm_firmware_root_entries(root_entries)?; } + let mailbox: *const Mailbox = &*session.primary.rtkit; + session.port.attach(session.init.port_views(mailbox)?, session.bridge.regs.clone())?; session.uat.mirror_secondary_root()?; if cfg.fabric_bridge { session.bridge.regs.enable_g17_gating()?; @@ -1396,7 +1573,8 @@ impl gpu::Gpu for Gpu { u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_PER_COMMAND_INPUT_SYNCS) | u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_EXACT_PRIOR_BARRIERS) | u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_FRAGMENT_BARRIERS) - | u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_BOUNDED_AS_BUILD); + | u64::from(uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_BOUNDED_AS_BUILD) + | sync_output::FEATURE_CUMULATIVE_SIGNALS; let variant = self.shared.variant; if variant.few_primitives { params.features |= @@ -1494,16 +1672,21 @@ impl gpu::Gpu for Gpu { let policy = scheduling_policy(priority)?; let admission_started = now_ns(); let (backend, slot) = loop { + let observed = self.shared.queues.observe(); match runtime::Backend::new(&self.shared, &vm, owner, policy) { Err(error) if error == EAGAIN => continue, Err(error) if error == ENOSPC => { // Closed logical queues can still own execution/QoS IDs // until the scheduler release is consumed and reclaimed. // Backend construction has rolled back all temporary leases. - if now_ns().saturating_sub(admission_started) - >= job::TIMEOUT_MS as u64 * 1_000_000 { return Err(EBUSY); } + let waited = now_ns().saturating_sub(admission_started); + let limit = job::TIMEOUT_MS as u64 * 1_000_000; + if waited >= limit { return Err(EBUSY); } self.shared.queue_events(); - kernel::time::delay::fsleep(kernel::time::Delta::from_millis(1)); + // A returned queue slot or reclaimed context wakes this. + self.shared + .queues + .block(observed, (limit - waited).div_ceil(1_000_000) as u32, true)?; } result => break result?, } @@ -1529,18 +1712,22 @@ impl gpu::Gpu for Gpu { mut bo: gem::ObjectRef, range: Range, ) -> Result { - let state = self.shared.state.lock(); - let firmware = (*state).as_ref().ok_or(ENODEV)?; - let mut addresses = firmware.uat.geometry().kernel_range(); - addresses.start += hw::t8140::dynamic::KERNEL_OFFSET; - crate::timestamp::Buffer::firmware(bo.map_range_into_range( - firmware.uat.kernel_vm(), - range, - addresses, - mmu::UAT_PGSZ as u64, - mmu::PROT_FW_SHARED_RW, - false, - )?) + let mapping = { + let state = self.shared.state.lock(); + let firmware = (*state).as_ref().ok_or(ENODEV)?; + let mut addresses = firmware.uat.geometry().kernel_range(); + addresses.start += hw::t8140::dynamic::KERNEL_OFFSET; + bo.map_range_into_range( + firmware.uat.kernel_vm(), + range, + addresses, + mmu::UAT_PGSZ as u64, + mmu::PROT_FW_SHARED_RW, + false, + )? + }; + // The buffer's CPU mapping takes the reservation lock: not under the device mutex. + crate::timestamp::Buffer::firmware(mapping) } } diff --git a/drivers/gpu/drm/asahi/g17/object.rs b/drivers/gpu/drm/asahi/g17/object.rs index f1b22ad5d7331..87fdb0e8a62ad 100644 --- a/drivers/gpu/drm/asahi/g17/object.rs +++ b/drivers/gpu/drm/asahi/g17/object.rs @@ -466,8 +466,17 @@ struct PoolInner { reserved: [usize; POOL_FAMILIES], bytes: usize, stopped: bool, + /// Objects the firmware may still reference after their owner dropped them, kept mapped + /// and out of reuse until firmware stop (see [`PooledObject::retain_until_stop`]). + kept: KVec, + kept_bytes: usize, + /// Kept objects still owned by their users, whose list entries are reserved. + kept_reserved: usize, } +/// Retained objects past this many are still retained, with one warning. +const KEPT_WARN: usize = 256; + /// Per-device cache-safe object recycling. Every converted mapping reserves /// one return slot before its page-table attributes change. Active leases and /// poisoned mappings count against the same capacity as reusable objects. @@ -490,6 +499,9 @@ struct CacheLease { pub(crate) struct PooledObject { object: core::mem::ManuallyDrop, lease: Option, + /// The pool keeping this object until firmware stop, and whether `reserve_kept` took its + /// list entry. + keep: Option<(kernel::sync::Arc, bool)>, } impl core::ops::Deref for PooledObject { @@ -503,11 +515,28 @@ impl core::ops::DerefMut for PooledObject { &mut self.object } } +impl PooledObject { + /// The firmware may still reference this object after its owner drops it: keep it mapped + /// and never hand it out again before firmware stop. It leaves the reuse cache, so it + /// does not take cache capacity. Call from sleepable context: this reserves the list + /// entry, so the drop, wherever it happens, does not allocate. + pub(crate) fn retain_until_stop(&mut self, pool: &kernel::sync::Arc) { + if self.keep.is_none() { + let reserved = pool.reserve_kept(); + self.keep = Some((pool.clone(), reserved)); + } + } +} + impl Drop for PooledObject { fn drop(&mut self) { // SAFETY: This destructor is the only operation that moves `object` // out; ManuallyDrop prevents a second implicit drop of the field. let object = unsafe { core::mem::ManuallyDrop::take(&mut self.object) }; + if let Some((pool, reserved)) = self.keep.take() { + pool.keep_object(object, self.lease.take(), reserved); + return; + } match self.lease.take() { Some(lease) => lease.pool.return_object(object, &lease), None => drop(object), @@ -523,7 +552,10 @@ impl Pool { } kernel::sync::Arc::pin_init( pin_init!(Self { - inner <- kernel::new_mutex!(PoolInner { returned, reserved: [0; POOL_FAMILIES], bytes: 0, stopped: false }, "g17::object::Pool"), + inner <- kernel::new_mutex!(PoolInner { + returned, reserved: [0; POOL_FAMILIES], bytes: 0, stopped: false, kept: KVec::new(), + kept_bytes: 0, kept_reserved: 0, + }, "g17::object::Pool"), }), GFP_KERNEL, ) @@ -572,6 +604,7 @@ impl Pool { return Ok(PooledObject { object: core::mem::ManuallyDrop::new(entry.object), lease: Some(lease()), + keep: None, }); } let object = alloc.kernel(size, mmu::UAT_PGSZ as u64, prot, cpu)?; @@ -596,6 +629,7 @@ impl Pool { let mut result = PooledObject { object: core::mem::ManuallyDrop::new(object), lease: reserved.then(lease), + keep: None, }; if !reserved { return Ok(result); @@ -655,10 +689,67 @@ impl Pool { core::mem::forget(object); } + /// Room for one more kept object, taken by the matching `keep_object`. Returns whether + /// it was reserved; after a failed allocation that drop leaks the object instead. + fn reserve_kept(&self) -> bool { + let mut inner = self.inner.lock(); + let pending = inner.kept_reserved + 1; + if inner.kept.capacity() - inner.kept.len() < pending + && inner.kept.reserve(pending, GFP_KERNEL).is_err() + { + return false; + } + inner.kept_reserved = pending; + true + } + + fn keep_object(&self, object: KernelObject, lease: Option, reserved: bool) { + let mut inner = self.inner.lock(); + if reserved { + inner.kept_reserved = inner.kept_reserved.saturating_sub(1); + } + if let Some(lease) = &lease { + // The object leaves the reuse cache and its capacity. + inner.reserved[lease.family as usize] -= 1; + inner.bytes -= object.size(); + } + if inner.stopped { + drop(inner); + drop(object); + return; + } + let size = object.size(); + if inner.kept.len() == KEPT_WARN { + pr_warn!("G17 keeps {} firmware-linked objects until the GPU firmware restarts\n", KEPT_WARN); + } + if !reserved { + // Another kept object's reservation must not be used: leak this one instead. + pr_err!("G17 could not record a firmware-linked object; leaking it\n"); + core::mem::forget(object); + return; + } + // retain_until_stop reserved the entry; this never allocates. + match inner.kept.push_within_capacity(object) { + Ok(()) => inner.kept_bytes += size, + Err(error) => { + // Never unmap an object the firmware may still reference. + pr_err!("G17 could not record a firmware-linked object; leaking it\n"); + core::mem::forget(error.0); + } + } + } + /// Releases retained mappings after both firmware processors have stopped. /// Active objects returned later observe the same stop witness. pub(crate) fn stop_after_firmware(&self) { - self.inner.lock().stopped = true; + let kept = { + let mut inner = self.inner.lock(); + inner.stopped = true; + inner.kept_bytes = 0; + core::mem::take(&mut inner.kept) + }; + // Mapping teardown must not run while the pool mutex is held. + drop(kept); for index in 0..POOL_FAMILIES * POOL_SLOTS { let entry = { let mut inner = self.inner.lock(); @@ -684,6 +775,7 @@ impl Pool { inner.reserved[family], POOL_SLOTS, entries.iter().flatten().count(), entries.iter().flatten().filter(|entry| entry.poisoned).count())?; } - writeln!(f, "cached_object_bytes used={} total={}", inner.bytes, POOL_BYTES) + writeln!(f, "cached_object_bytes used={} total={}", inner.bytes, POOL_BYTES)?; + writeln!(f, "firmware_linked_objects kept={} bytes={}", inner.kept.len(), inner.kept_bytes) } } diff --git a/drivers/gpu/drm/asahi/g17/port.rs b/drivers/gpu/drm/asahi/g17/port.rs new file mode 100644 index 0000000000000..243fbfce07f9e --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/port.rs @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Device-wide publication state that physical queues share without the device mutex. +//! +//! A firmware session owns its port, and the views it holds name that session's +//! firmware-shared memory. Code that does not hold the device mutex reaches the port only +//! through a preparation lease ([`super::Shared::port`]): shutdown and device loss join every +//! lease before they detach the session. Each piece below is atomic or has its own lock. +//! +//! Lock order: device mutex -> compute queue cell -> the locks of this port. The render +//! publication lock comes first among the port's locks: device mutex -> render publication +//! lock -> admission pool, QoS, mailbox. Its holder never takes the device mutex. + +use super::{ + fw::{ + channels::{WorkSlot, WORK_RING_SLOTS}, + queue::{DataMaster, Policy}, + }, + qos, +}; +use core::sync::atomic::{fence, AtomicBool, AtomicU32, AtomicU64, Ordering}; +use kernel::{ + new_mutex, new_spinlock, + prelude::*, + sync::{Arc, Mutex, MutexGuard, SetOnce, SpinLock}, +}; + +const PRIORITIES: usize = Policy::PRIORITIES as usize; + +/// One priority class's compute work ring: its slot array and the cursors of its two firmware +/// consumers and of the host producer. +#[derive(Clone, Copy)] +pub(super) struct RingView { + pub(super) slots: *mut u8, + pub(super) consumers: [*const AtomicU32; 2], + pub(super) producer: *const AtomicU32, +} + +impl RingView { + fn cursors(&self) -> Result<[u32; 3]> { + // SAFETY: The views name 32-bit words of the session's retained firmware objects, which + // the port's owner keeps alive. Other agents only use single 32-bit accesses. + let values = unsafe { + [ + (*self.consumers[0]).load(Ordering::Relaxed), + (*self.consumers[1]).load(Ordering::Relaxed), + (*self.producer).load(Ordering::Relaxed), + ] + }; + if values.iter().any(|&value| value >= WORK_RING_SLOTS) { + return Err(EINVAL); + } + Ok(values) + } + + /// Slots the host may still fill before reaching either consumer. + fn free(&self) -> Result { + let [first, second, producer] = self.cursors()?; + let room = |consumer: u32| (consumer + WORK_RING_SLOTS - producer - 1) % WORK_RING_SLOTS; + Ok(room(first).min(room(second))) + } +} + +/// Raw views of the session's firmware-shared memory, fixed once the firmware is built. +pub(super) struct Views { + pub(super) compute: [RingView; PRIORITIES], + pub(super) tiling: [RingView; PRIORITIES], + pub(super) fragment: [RingView; PRIORITIES], + /// Completion stamp page A: one word per QID, written by the firmware. + pub(super) stamps: *const AtomicU32, + pub(super) qos: *mut u8, + pub(super) recovery: *const AtomicU32, + pub(super) mailbox: *const super::Mailbox, +} + +// SAFETY: The views only name memory and a mailbox owned by the firmware session, which outlives +// every user of the port. Accesses are atomic words or serialized by the port's locks. +unsafe impl Send for Views {} +// SAFETY: As above. +unsafe impl Sync for Views {} + +#[pin_data] +pub(super) struct Port { + views: SetOnce, + /// Register access for tracing the core power state at the doorbell. + regs: SetOnce, + /// Compute work-ring slots reserved by publishers that have not exposed them yet. + #[pin] + rings: SpinLock<[u32; PRIORITIES]>, + #[pin] + qos: SpinLock, + /// Serializes every render publication, with or without the device mutex: the tiling + /// and fragment work rings have one host producer at a time, and same-VM attachment + /// overlaps are ordered by publication order (see hazard.rs). + #[pin] + render: Mutex<()>, + /// The accepted recovery generation, mirrored for publishers without the device mutex. + generation: AtomicU64, + /// Device publication order shared by compute and render commands. + publications: AtomicU64, + /// Compute work slots written per priority class. + compute_written: [AtomicU64; PRIORITIES], + /// `compute_written` as read before the latest compute doorbell of each class: every slot + /// counted there was in its ring when the firmware handled that doorbell. + compute_announced: [AtomicU64; PRIORITIES], + /// Full firmware effort is applied; cleared before an idle clear rechecks `submissions`. + effort_full: AtomicBool, + demand: super::power::Demand, + last_submission_ns: AtomicU64, + submissions: AtomicU64, +} + +/// Room in both render work rings of one priority, observed under the render publication +/// lock. While that lock is held no other host producer moves, and the firmware consumers only +/// advance, so the observed producers stay current and the room stays available. +#[derive(Clone, Copy)] +pub(super) struct RenderRoom { + priority: u8, + /// Fragment, then tiling producer. + producers: [u32; 2], +} + +impl RenderRoom { + pub(super) fn priority(&self) -> u8 { + self.priority + } +} + +/// A compute work-ring slot reserved before any command becomes visible. Dropping an unused +/// claim returns the slot. +pub(super) struct OuterClaim<'a> { + port: &'a Port, + priority: usize, + armed: bool, +} + +impl Drop for OuterClaim<'_> { + fn drop(&mut self) { + if self.armed { + let mut reserved = self.port.rings.lock(); + reserved[self.priority] = reserved[self.priority].saturating_sub(1); + } + } +} + +impl Port { + pub(super) fn new(demand: super::power::Demand) -> Result> { + Arc::pin_init( + pin_init!(Self { + views: SetOnce::new(), + regs: SetOnce::new(), + rings <- new_spinlock!([0; PRIORITIES], "g17::Port::rings"), + qos <- new_spinlock!(qos::Accounting::new(), "g17::Port::qos"), + render <- new_mutex!((), "g17::Port::render"), + generation: AtomicU64::new(0), + publications: AtomicU64::new(0), + compute_written: [const { AtomicU64::new(0) }; PRIORITIES], + compute_announced: [const { AtomicU64::new(0) }; PRIORITIES], + effort_full: AtomicBool::new(false), + demand, + last_submission_ns: AtomicU64::new(0), + submissions: AtomicU64::new(0), + }), + GFP_KERNEL, + ) + } + + /// Called once while the session is built, before any publisher can see the port. + pub(super) fn attach(&self, views: Views, regs: crate::regs::G17Resources) -> Result { + if self.views.populate(views) && self.regs.populate(regs) { + Ok(()) + } else { + Err(EBUSY) + } + } + + fn views(&self) -> Result<&Views> { + self.views.as_ref().ok_or(ENODEV) + } + + fn ring(&self, priority: u8) -> Result<(usize, RingView)> { + let index = usize::from(priority); + Ok((index, *self.views()?.compute.get(index).ok_or(EINVAL)?)) + } + + /// Reserves one slot of the compute work ring of `priority`. EAGAIN while firmware has not + /// consumed enough slots; only consumption, never a waiting publisher, frees one. + pub(super) fn reserve_compute(&self, priority: u8) -> Result> { + let (index, ring) = self.ring(priority)?; + let mut reserved = self.rings.lock(); + if ring.free()? <= reserved[index] { + return Err(EAGAIN); + } + reserved[index] += 1; + Ok(OuterClaim { + port: self, + priority: index, + armed: true, + }) + } + + /// Whether a compute publication at `priority` can reserve a slot now. + pub(super) fn compute_ready(&self, priority: u8) -> Result { + let (index, ring) = self.ring(priority)?; + let reserved = self.rings.lock(); + Ok(ring.free()? > reserved[index]) + } + + /// Exposes `record` in its reserved slot. Inner queue records must be visible first. Returns + /// the new producer, the retained consumer witness for this slot. + pub(super) fn publish_compute(&self, mut claim: OuterClaim<'_>, record: &WorkSlot) -> Result { + if record.data_master != DataMaster::Compute as u32 || !core::ptr::eq(claim.port, self) { + return Err(EINVAL); + } + let ring = self.views()?.compute[claim.priority]; + let mut reserved = self.rings.lock(); + let [first, second, producer] = ring.cursors()?; + let next = (producer + 1) % WORK_RING_SLOTS; + if next == first || next == second { + return Err(EIO); + } + // SAFETY: The ring view spans WORK_RING_SLOTS records, checked when the session built + // it; `producer` is in range. The reservation proves firmware does not own this slot and + // the ring lock excludes every other host writer. The record is a padding-free ABI value. + unsafe { + core::ptr::copy_nonoverlapping( + core::ptr::from_ref(record).cast::(), + ring.slots.add(producer as usize * core::mem::size_of::()), + core::mem::size_of::(), + ); + } + fence(Ordering::SeqCst); + // SAFETY: As for `RingView::cursors`. + unsafe { (*ring.producer).store(next, Ordering::Relaxed) }; + fence(Ordering::SeqCst); + reserved[claim.priority] = reserved[claim.priority].saturating_sub(1); + self.compute_written[claim.priority].fetch_add(1, Ordering::AcqRel); + claim.armed = false; + Ok(next) + } + + /// Compute work slots written at `priority` so far. + pub(super) fn compute_written(&self, priority: u8) -> u64 { + self.compute_written + .get(usize::from(priority)) + .map_or(u64::MAX, |written| written.load(Ordering::Acquire)) + } + + /// Compute work slots at `priority` that a doorbell has announced. + pub(super) fn compute_announced(&self, priority: u8) -> u64 { + self.compute_announced + .get(usize::from(priority)) + .map_or(0, |announced| announced.load(Ordering::Acquire)) + } + + /// Rings the compute doorbell `message` of `priority`, after noting which slots it + /// announces. + pub(super) fn notify_compute(&self, priority: u8, message: u64) -> Result { + let index = usize::from(priority); + let written = self.compute_written.get(index).ok_or(EINVAL)?.load(Ordering::Acquire); + self.notify(message)?; + self.compute_announced[index].fetch_max(written, Ordering::AcqRel); + Ok(()) + } + + /// Taken around every render publication; see `render`. + pub(super) fn render_lock(&self) -> MutexGuard<'_, ()> { + self.render.lock() + } + + fn render_rings(&self, priority: u8) -> Result<(RingView, RingView)> { + let views = self.views()?; + let index = usize::from(priority); + Ok(( + *views.fragment.get(index).ok_or(EINVAL)?, + *views.tiling.get(index).ok_or(EINVAL)?, + )) + } + + /// Reads both render work rings of `priority` once. Returns their producers if both have a + /// free slot; the room holds for as long as `guard`. Only firmware consumption, never a + /// publisher without the render lock, changes it. + pub(super) fn observe_render_room( + &self, + _guard: &MutexGuard<'_, ()>, + priority: u8, + ) -> Result> { + let (fragment, tiling) = self.render_rings(priority)?; + let cursors = [fragment.cursors()?, tiling.cursors()?]; + let room = |[first, second, producer]: [u32; 3]| { + let room = |consumer: u32| (consumer + WORK_RING_SLOTS - producer - 1) % WORK_RING_SLOTS; + room(first).min(room(second)) + }; + Ok((room(cursors[0]) > 0 && room(cursors[1]) > 0).then_some(RenderRoom { + priority, + producers: [cursors[0][2], cursors[1][2]], + })) + } + + /// Copies a render pair's fragment, then tiling record into the slots after the producers + /// that `room` observed, without exposing them, as `InitData::publish_work_pair` does for a + /// deferred pair. Returns the producers that expose them. + pub(super) fn write_render_pair( + &self, + _guard: &MutexGuard<'_, ()>, + room: &RenderRoom, + fragment: &WorkSlot, + tiling: &WorkSlot, + commit_accounting: impl FnOnce() -> Result, + publish_late_tiling: impl FnOnce(), + ) -> Result<[u32; 2]> { + if fragment.data_master != DataMaster::Fragment as u32 + || tiling.data_master != DataMaster::Tiling as u32 + { + return Err(EINVAL); + } + let (fragment_ring, tiling_ring) = self.render_rings(room.priority)?; + let slot = |ring: &RingView, producer: u32| -> Result<(*mut u8, u32)> { + if producer >= WORK_RING_SLOTS { + return Err(EINVAL); + } + // SAFETY: The ring view spans WORK_RING_SLOTS records; `producer` is in range. + let slot = unsafe { ring.slots.add(producer as usize * core::mem::size_of::()) }; + Ok((slot, (producer + 1) % WORK_RING_SLOTS)) + }; + let (fragment_slot, fragment_next) = slot(&fragment_ring, room.producers[0])?; + let (tiling_slot, tiling_next) = slot(&tiling_ring, room.producers[1])?; + let copy = |record: &WorkSlot, destination: *mut u8| { + // SAFETY: The room proves that the slot after the producer is host-owned until the + // producer moves past it, and the render lock excludes every other host writer. + unsafe { + core::ptr::copy_nonoverlapping( + core::ptr::from_ref(record).cast::(), + destination, + core::mem::size_of::(), + ) + } + }; + copy(fragment, fragment_slot); + commit_accounting()?; + fence(Ordering::SeqCst); + publish_late_tiling(); + copy(tiling, tiling_slot); + fence(Ordering::SeqCst); + Ok([fragment_next, tiling_next]) + } + + /// Exposes a pair written by `write_render_pair` with `room`, fragment first, as + /// `InitData::publish_retained_work_pair` does: `next` must follow the observed producers. + pub(super) fn expose_render_pair( + &self, + _guard: &MutexGuard<'_, ()>, + room: &RenderRoom, + next: [u32; 2], + ) -> Result { + let (fragment_ring, tiling_ring) = self.render_rings(room.priority)?; + for (producer, next) in room.producers.into_iter().zip(next) { + if next != (producer + 1) % WORK_RING_SLOTS { + return Err(EBUSY); + } + } + fence(Ordering::SeqCst); + // SAFETY: As for `RingView::cursors`. + unsafe { (*fragment_ring.producer).store(next[0], Ordering::Relaxed) }; + fence(Ordering::SeqCst); + // SAFETY: As above. + unsafe { (*tiling_ring.producer).store(next[1], Ordering::Relaxed) }; + fence(Ordering::SeqCst); + Ok(()) + } + + /// The completion stamp of `qid` in page A. + pub(super) fn stamp(&self, qid: u8) -> Result { + if qid > super::fw::kick::QID_MAX { + return Err(EINVAL); + } + let base = self.views()?.stamps; + // SAFETY: Page A holds one word per QID up to QID_MAX; the session retains it. + let value = unsafe { (*base.add(usize::from(qid))).load(Ordering::Relaxed) }; + fence(Ordering::Acquire); + Ok(value) + } + + fn qos_view(&self) -> Result> { + // SAFETY: The session retains the QoS table for at least as long as this port. + unsafe { qos::View::from_raw(self.views()?.qos) } + } + + pub(super) fn qos_publish( + &self, + owner: qos::Owner, + scheduler: u64, + policy: Policy, + ) -> Result { + let view = self.qos_view()?; + self.qos.lock().publish(&view, owner, scheduler, policy, qos::clock) + } + + /// Publishes the two owners of a render command, fragment then tiling. + pub(super) fn qos_publish_pair( + &self, + owners: [qos::Owner; 2], + scheduler: u64, + policy: Policy, + ) -> Result<[qos::Publication; 2]> { + let view = self.qos_view()?; + self.qos.lock().publish_owners(&view, owners, scheduler, policy, qos::clock) + } + + pub(super) fn qos_cancel(&self, publication: qos::Publication) -> Result { + let view = self.qos_view()?; + self.qos.lock().cancel(&view, publication) + } + + pub(super) fn qos_complete(&self, owners: &[qos::Owner]) -> Result { + let view = self.qos_view()?; + self.qos.lock().complete(&view, owners) + } + + /// Rings the primary coprocessor's doorbell. + pub(super) fn notify(&self, message: u64) -> Result { + // SAFETY: The session's primary coprocessor owns the mailbox and outlives the port. + let mailbox = unsafe { &*self.views()?.mailbox }; + super::trace::doorbell(super::trace::COMPUTE, || { + self.regs.as_ref().map_or(0, |regs| regs.g17_core_power()) + }); + super::trace::message(super::trace::MAILBOX, message); + mailbox.send(super::EP_DOORBELL, message) + } + + pub(super) fn recovery_state(&self) -> Result { + let word = self.views()?.recovery; + fence(Ordering::Acquire); + // SAFETY: As for `RingView::cursors`. + Ok(unsafe { (*word).load(Ordering::Relaxed) }) + } + + pub(super) fn generation(&self) -> u64 { + self.generation.load(Ordering::Acquire) + } + + /// Called with the device mutex held after the accepted generation changed, before + /// publication reopens. + pub(super) fn set_generation(&self, generation: u64) { + self.generation.store(generation, Ordering::Release); + } + + /// Unique reservation order shared by both engines. Concurrent publishers may expose + /// their work in a different order; queue serialization and visibility use other locks. + /// The watchdog uses this number only to choose bounded cross-VM timeout grace. + pub(super) fn next_publication(&self) -> u64 { + self.publications.fetch_add(1, Ordering::Relaxed) + 1 + } + + /// A publisher without the device mutex counts itself before it checks that full effort is + /// applied. The idle clear withdraws the flag before it rechecks the count, so either the + /// clear sees this submission or this submission sees the flag withdrawn. + pub(super) fn note_submission(&self) -> bool { + self.submissions.fetch_add(1, Ordering::SeqCst); + self.note_gpu_activity(); + self.effort_full.load(Ordering::SeqCst) + } + + pub(super) fn submissions(&self) -> u64 { + self.submissions.load(Ordering::SeqCst) + } + + pub(super) fn set_effort_full(&self, full: bool) { + self.effort_full.store(full, Ordering::SeqCst); + self.demand.set_active(full); + } + + pub(super) fn note_gpu_activity(&self) { + self.last_submission_ns.store(super::now_ns(), Ordering::Relaxed); + self.demand.set_active(true); + } + + pub(super) fn write_power(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + writeln!(f, "effort_full={} direct_submissions={} last_submission_ns={}", + self.effort_full.load(Ordering::SeqCst), self.submissions(), + self.last_submission_ns.load(Ordering::Relaxed)) + } +} diff --git a/drivers/gpu/drm/asahi/g17/power.rs b/drivers/gpu/drm/asahi/g17/power.rs index 7fef15084c50a..a2e293df91510 100644 --- a/drivers/gpu/drm/asahi/g17/power.rs +++ b/drivers/gpu/drm/asahi/g17/power.rs @@ -6,16 +6,28 @@ use core::sync::atomic::AtomicU32; pub(super) const FULL_EFFORT: u32 = 0x10000; -const IDLE_HOLD_NS: u64 = 16_000_000; +pub(super) const IDLE_HOLD_NS: u64 = 16_000_000; /// The firmware's idle power-off delay as measured on T6040/25G83: the cores power off 2 ms after /// the last job. Not measured on other firmware; at or below it the hold stays off. pub(crate) const FIRMWARE_IDLE_OFF_MS: u32 = 2; -/// The `idle_off_delay_ms` sysfs knob. Above [`FIRMWARE_IDLE_OFF_MS`] the driver keeps the GPU -/// cores powered until the GPU has been idle this long ([`Hold`]). +/// Idle policy initialized from the existing DT `apple,idle-off-delay-ms` property. +/// The existing `idle_off_delay_ms` sysfs attribute may override it. Above +/// [`FIRMWARE_IDLE_OFF_MS`] the driver keeps the GPU cores powered until it has been idle +/// this long ([`Hold`]). pub(crate) static IDLE_HOLD_MS: AtomicU32 = AtomicU32::new(FIRMWARE_IDLE_OFF_MS); +/// Use the same bounds as the existing idle-delay attribute. Missing DT policy +/// preserves the native delay; malformed or out-of-range values fail probe. +pub(super) fn idle_delay(requested: Option) -> kernel::error::Result { + let delay = requested.unwrap_or(FIRMWARE_IDLE_OFF_MS); + if !(FIRMWARE_IDLE_OFF_MS..=1000).contains(&delay) { + return Err(kernel::error::code::EINVAL); + } + Ok(delay) +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(super) enum IdleAction { None, @@ -237,6 +249,34 @@ pub(super) fn release(control: &mut C) -> kernel::error::Result { mod tests { use super::*; + #[test] + fn dt_idle_delay_is_bounded_and_absence_preserves_native_policy() { + assert_eq!(idle_delay(None), Ok(FIRMWARE_IDLE_OFF_MS)); + for delay in [2, 16, 1000] { + assert_eq!(idle_delay(Some(delay)), Ok(delay)); + } + for delay in [0, 1, 1001, u32::MAX] { + assert!(idle_delay(Some(delay)).is_err()); + } + } + + #[test] + fn frame_hold_survives_depth_one_gaps_but_releases_at_idle() { + let mut hold = Hold::new(); + assert_eq!(hold.step(0, true, 1, 16), HoldAction::Assert); + hold.asserted(); + for submission in 1..=8 { + // A completed depth-one dispatch followed by another short gap. + let now = submission * 3_000_000; + assert_eq!(hold.step(now, false, submission, 16), HoldAction::Recheck(16_000_000)); + assert!(hold.active()); + } + assert_eq!(hold.step(39_999_999, false, 8, 16), HoldAction::Recheck(1)); + assert_eq!(hold.step(40_000_000, false, 8, 16), HoldAction::Release); + hold.released(); + assert!(!hold.active()); + } + #[test] fn holds_until_idle_for_the_delay_then_releases() { let mut hold = Hold::new(); @@ -260,3 +300,43 @@ mod tests { assert_eq!(hold.step(1, false, 2, FIRMWARE_IDLE_OFF_MS), HoldAction::Release); } } + +unsafe extern "C" { + fn asahi_clpc_gpu_get(dev: *mut core::ffi::c_void, out: *mut *mut core::ffi::c_void) -> core::ffi::c_int; + fn asahi_clpc_gpu_set_active(gpu: *mut core::ffi::c_void, active: bool); + fn asahi_clpc_gpu_put(gpu: *mut core::ffi::c_void); +} + +/// DT-selected memory-demand provider. Its independent allocation outlives a +/// detached provider; a device link orders supplier removal and suspend. +pub(super) struct Demand(*mut core::ffi::c_void); + +// SAFETY: the provider's leaf spinlock serializes all access; no GPU callback. +unsafe impl Send for Demand {} +// SAFETY: shared access only changes that locked request, never its lifetime. +unsafe impl Sync for Demand {} + +impl Demand { + pub(super) fn new(dev: *mut kernel::bindings::device) -> kernel::error::Result { + let mut out = core::ptr::null_mut(); + // SAFETY: live platform device and writable result; C returns an owned reference. + let ret = unsafe { asahi_clpc_gpu_get(dev.cast(), &mut out) }; + if ret < 0 { + Err(kernel::error::Error::from_errno(ret)) + } else { + Ok(Self(out)) + } + } + + pub(super) fn set_active(&self, active: bool) { + // SAFETY: the retained reference is live; NULL denotes no DT provider. + unsafe { asahi_clpc_gpu_set_active(self.0, active) }; + } +} + +impl Drop for Demand { + fn drop(&mut self) { + // SAFETY: releases exactly the reference returned by get, clearing demand first. + unsafe { asahi_clpc_gpu_put(self.0) }; + } +} diff --git a/drivers/gpu/drm/asahi/g17/preparation.rs b/drivers/gpu/drm/asahi/g17/preparation.rs index 0297c619b4f93..2aa3213115076 100644 --- a/drivers/gpu/drm/asahi/g17/preparation.rs +++ b/drivers/gpu/drm/asahi/g17/preparation.rs @@ -55,20 +55,37 @@ pub(super) struct Gate { wait: Mutex<()>, #[pin] changed: CondVar, + /// Scheduler admission waiting for publication to reopen. + available: Option>, } impl Gate { + #[cfg(CONFIG_KUNIT = "y")] pub(super) fn new() -> Result> { + Self::with_waiters(None) + } + + /// A gate whose reopening, failure and removal also wake `available`. + pub(super) fn with_waiters( + available: Option>, + ) -> Result> { Arc::pin_init( pin_init!(Self { state: AtomicU64::new(EPOCH), wait <- new_mutex!((), "g17::Gate::wait"), changed <- new_condvar!("g17::Gate::changed"), + available, }), GFP_KERNEL, ) } + fn wake_waiters(&self) { + if let Some(available) = &self.available { + available.notify(); + } + } + pub(super) fn try_enter(self: &Arc) -> Result { let mut previous = self.state.load(Ordering::Acquire); loop { @@ -162,20 +179,55 @@ impl Gate { } } + /// Also wakes publishers in wait_closure(); they then block in enter(). pub(super) fn close(&self) { self.invalidate(0); + let _wait = self.wait.lock(); + self.changed.notify_all(); + } + + /// Opaque snapshot for wait_closure(), taken where recovery was observed. + pub(super) fn snapshot(&self) -> u64 { + self.state.load(Ordering::Acquire) + } + + /// A publisher that observed recovery (host pending, or the firmware's state + /// ahead of the event worker, which always follows with a restart and the + /// closure) sleeps until this gate has closed since `seen`. enter() then + /// blocks until recovery reopens it. The deadline is only a failure bound. + pub(super) fn wait_closure(&self, seen: u64, deadline: u64) -> Result { + let mut wait = self.wait.lock(); + loop { + let state = self.state.load(Ordering::Acquire); + if state & (REMOVED | FAILED) != 0 { + return Err(ENODEV); + } + if state & CLOSED != 0 || state & !COUNT_MASK != seen & !COUNT_MASK { + return Ok(()); + } + let remaining = deadline.saturating_sub(super::now_ns()); + if remaining == 0 { + return Err(ETIMEDOUT); + } + let ms = u32::try_from(remaining.div_ceil(1_000_000)).unwrap_or(u32::MAX); + let _ = self.changed.wait_timeout(&mut wait, kernel::time::msecs_to_jiffies(ms)); + } } pub(super) fn remove(&self) { self.invalidate(REMOVED); - let _wait = self.wait.lock(); + let wait = self.wait.lock(); self.changed.notify_all(); + drop(wait); + self.wake_waiters(); } pub(super) fn fail(&self) { self.invalidate(FAILED); - let _wait = self.wait.lock(); + let wait = self.wait.lock(); self.changed.notify_all(); + drop(wait); + self.wake_waiters(); } /// Runtime failure/recovery must not join an indefinitely stalled builder. @@ -199,7 +251,7 @@ impl Gate { /// Reopen only an empty, temporary closure. Failed or removed devices remain closed. pub(super) fn reopen(&self) -> bool { - let _wait = self.wait.lock(); + let wait = self.wait.lock(); let mut previous = self.state.load(Ordering::Acquire); loop { if previous & (REMOVED | FAILED | COUNT_MASK) != 0 { @@ -213,6 +265,8 @@ impl Gate { ) { Ok(_) => { self.changed.notify_all(); + drop(wait); + self.wake_waiters(); return true; } Err(current) => previous = current, @@ -307,9 +361,9 @@ mod compute_deadline_tests { #[test] fn recovery_and_binding_retry_churn_cannot_renew_budget() { let gate = Gate::new().unwrap(); - let start = super::super::now_ns(); + let deadline = super::super::now_ns() + 20_000_000; let check = || Ok(()); - let admission = Admission::new(start + 20_000_000, &check); + let admission = Admission::new(deadline, &check); loop { // Re-entry models a channel taken between admission/publication. // Recovery joins each dropped lease, then wakes the same budget. @@ -317,12 +371,17 @@ mod compute_deadline_tests { gate.changed.notify_all(); gate.wait_drained(); assert!(gate.reopen()); + // However often the gate reopened, an attempt that starts at the deadline + // is refused. Timing the whole loop would also count time descheduled. + let attempted = super::super::now_ns(); match gate.enter_until(&admission) { - Ok(lease) => drop(lease), + Ok(lease) => { + assert!(attempted < deadline); + drop(lease); + } Err(error) => { assert_eq!(error, EBUSY); break; } } } - assert!(super::super::now_ns() - start < 500_000_000); assert_eq!(gate.state.load(Ordering::Acquire) & COUNT_MASK, 0); } diff --git a/drivers/gpu/drm/asahi/g17/qos.rs b/drivers/gpu/drm/asahi/g17/qos.rs index 9a100db4cffdf..b36656b0174a7 100644 --- a/drivers/gpu/drm/asahi/g17/qos.rs +++ b/drivers/gpu/drm/asahi/g17/qos.rs @@ -2,17 +2,14 @@ //! Shared QoS publication and exact-completion accounting for every engine. //! -//! The device publication lock serializes this host state. Publishing identity +//! The port's QoS lock serializes this host state. Publishing identity //! precedes submitted counters; retiring a final owner clears only its two row //! bytes. Cancellation balances host references without rewinding shared counts. -use super::{ - fw::{qos as fw, queue::Policy}, - object::KernelObject, -}; +use super::fw::{qos as fw, queue::Policy}; use crate::hw::t8140::qos as cfg; use core::{ - mem::{offset_of, size_of}, + mem::offset_of, sync::atomic::{fence, AtomicU32, AtomicU64, AtomicU8, Ordering}, }; use kernel::prelude::*; @@ -30,15 +27,17 @@ pub(crate) struct View<'a> { times_and_counts: &'a [AtomicU64; 7], } impl<'a> View<'a> { - pub(crate) fn new(object: &'a KernelObject) -> Result { - let ptr = object.pointer(0, size_of::())?; + /// # Safety + /// + /// `ptr` must span a complete QoS table that stays mapped for `'a`. + pub(crate) unsafe fn from_raw(ptr: *mut u8) -> Result { if ptr as usize & 7 != 0 { return Err(EINVAL); } // SAFETY: pointer spans the complete layout, whose asserted offsets // and size bound every array below. Integer atomics have their integer - // representation and alignment. The retained object borrow keeps every - // shared reference alive; accesses use each field's fixed width. + // representation and alignment. The caller keeps every shared + // reference alive; accesses use each field's fixed width. Ok(unsafe { Self { rows: &*ptr.add(offset_of!(fw::Qos, rows)).cast(), @@ -54,6 +53,14 @@ impl<'a> View<'a> { fn row(&self, qid: usize) -> [u8; 8] { core::array::from_fn(|index| self.rows[qid][index].load(Ordering::Relaxed)) } + /// The complete row in one access. The table is uncached: a publication reads + /// each row once, before it stores anything. + fn row_word(&self, qid: usize) -> [u8; 8] { + let row = core::ptr::from_ref(&self.rows[qid]).cast::(); + // SAFETY: Rows are eight bytes at eight-byte offsets of the eight-byte + // aligned table (checked in from_raw), so the row is one aligned word. + unsafe { (*row).load(Ordering::Relaxed) }.to_le_bytes() + } } #[derive(Clone, Copy, Debug)] @@ -109,66 +116,111 @@ impl Accounting { policy: Policy, clock: impl FnOnce() -> (u64, u64), ) -> Result { - let (qid, qos, dm) = owner.indices()?; + let [publication] = self.publish_owners(view, [owner], scheduler, policy, clock)?; + Ok(publication) + } + + /// Publishes the owners of one command's queues, in order, with the same + /// firmware-visible values as one `publish` per owner. The owners use + /// distinct queues and data masters. Every check precedes the first store, + /// so a refusal changes nothing; every table word is read before the first + /// store, so no uncached read waits for an earlier store to drain. + pub(crate) fn publish_owners( + &mut self, + view: &View<'_>, + owners: [Owner; N], + scheduler: u64, + policy: Policy, + clock: impl FnOnce() -> (u64, u64), + ) -> Result<[Publication; N]> { if scheduler == 0 { return Err(EINVAL); } - let next_qid = self.in_flight[qid].checked_add(1).ok_or(EOVERFLOW)?; - let next_dm = self.dm_in_flight[dm].checked_add(1).ok_or(EOVERFLOW)?; - let starts_busy = self.dm_in_flight[dm] == 0; - let time = if starts_busy { + let mut indices = [(0, 0, 0); N]; + for (index, owner) in owners.iter().enumerate() { + indices[index] = owner.indices()?; + let (qid, _, dm) = indices[index]; + if indices[..index] + .iter() + .any(|&(other_qid, _, other_dm)| other_qid == qid || other_dm == dm) + { + return Err(EINVAL); + } + self.in_flight[qid].checked_add(1).ok_or(EOVERFLOW)?; + self.dm_in_flight[dm].checked_add(1).ok_or(EOVERFLOW)?; + } + let rows = indices.map(|(qid, _, _)| view.row_word(qid)); + let queue_submitted = indices.map(|(qid, _, _)| view.queue_submitted[qid].load(Ordering::Relaxed)); + let owner_submitted = indices.map(|(_, qos, _)| view.owner_submitted[qos].load(Ordering::Relaxed)); + let dm_submitted = indices.map(|(_, _, dm)| view.times_and_counts[3 + dm].load(Ordering::Relaxed)); + let starts_busy = indices.map(|(_, _, dm)| self.dm_in_flight[dm] == 0); + let busy = starts_busy.iter().any(|&starts| starts); + let intervals = if busy { view.intervals.load(Ordering::Relaxed) } else { 0 }; + for (index, owner) in owners.iter().enumerate() { + let (qid, _, _) = indices[index]; + if self.in_flight[qid] != 0 + && (rows[index][0] != owner.qos || self.dm_by_qid[qid] != owner.data_master) + { + return Err(EBUSY); + } + } + let time = if busy { let (counter, frequency) = clock(); time_from_host(counter, frequency)? } else { 0 }; - if self.in_flight[qid] != 0 - && (view.rows[qid][0].load(Ordering::Relaxed) != owner.qos - || self.dm_by_qid[qid] != owner.data_master) - { - return Err(EBUSY); + let class = policy.qos_class(); + let mut first = false; + for (index, owner) in owners.iter().enumerate() { + let (qid, qos, _) = indices[index]; + view.rows[qid][0].store(owner.qos, Ordering::Relaxed); + view.rows[qid][1].store(class, Ordering::Relaxed); + view.scheduler[qos].store(scheduler, Ordering::Relaxed); + view.share[qos].store(policy.qos_share(), Ordering::Relaxed); + first |= self.in_flight[qid] == 0; } - view.rows[qid][0].store(owner.qos, Ordering::Relaxed); - view.rows[qid][1].store(policy.qos_class(), Ordering::Relaxed); - let record = view.row(qid); - view.scheduler[qos].store(scheduler, Ordering::Relaxed); - view.share[qos].store(policy.qos_share(), Ordering::Relaxed); - if self.in_flight[qid] == 0 { + if first { fence(Ordering::SeqCst); } - view.queue_submitted[qid].store( - view.queue_submitted[qid] - .load(Ordering::Relaxed) - .wrapping_add(1), - Ordering::Relaxed, - ); - view.owner_submitted[qos].store( - view.owner_submitted[qos] - .load(Ordering::Relaxed) - .wrapping_add(1), - Ordering::Relaxed, - ); - if starts_busy { - view.times_and_counts[dm].store(time, Ordering::Relaxed); - } - let submitted = &view.times_and_counts[3 + dm]; - let dm_submitted = submitted.load(Ordering::Relaxed).wrapping_add(1); - submitted.store(dm_submitted, Ordering::Relaxed); - if starts_busy { - view.intervals.store( - view.intervals.load(Ordering::Relaxed).wrapping_add(1), + let mut publications = [Publication { + record: [0; 8], + owner: owners[0], + dm_submitted: 0, + }; N]; + let mut intervals = intervals; + for (index, owner) in owners.iter().enumerate() { + let (qid, qos, dm) = indices[index]; + view.queue_submitted[qid].store(queue_submitted[index].wrapping_add(1), Ordering::Relaxed); + // Owners of one logical queue share their submission count. + let earlier = indices[..index].iter().filter(|&&(_, other, _)| other == qos).count(); + view.owner_submitted[qos].store( + owner_submitted[index].wrapping_add(earlier as u32 + 1), Ordering::Relaxed, ); + if starts_busy[index] { + view.times_and_counts[dm].store(time, Ordering::Relaxed); + } + let submitted = dm_submitted[index].wrapping_add(1); + view.times_and_counts[3 + dm].store(submitted, Ordering::Relaxed); + if starts_busy[index] { + intervals = intervals.wrapping_add(1); + view.intervals.store(intervals, Ordering::Relaxed); + } + let mut record = rows[index]; + record[0] = owner.qos; + record[1] = class; + self.in_flight[qid] += 1; + self.dm_in_flight[dm] += 1; + self.dm_by_qid[qid] = owner.data_master; + publications[index] = Publication { + record, + owner: *owner, + dm_submitted: submitted, + }; } - self.in_flight[qid] = next_qid; - self.dm_in_flight[dm] = next_dm; - self.dm_by_qid[qid] = owner.data_master; fence(Ordering::SeqCst); - Ok(Publication { - record, - owner, - dm_submitted, - }) + Ok(publications) } /// A possibly exposed producer must retain its reference until completion. diff --git a/drivers/gpu/drm/asahi/g17/queue/admission.rs b/drivers/gpu/drm/asahi/g17/queue/admission.rs index ebdc79d2b2dd3..ed73323e34e6f 100644 --- a/drivers/gpu/drm/asahi/g17/queue/admission.rs +++ b/drivers/gpu/drm/asahi/g17/queue/admission.rs @@ -9,6 +9,7 @@ //! a graph or waiting for GPU work. Capacity waiters sleep on the release event. use crate::g17::{ + fw::kick::KickDependency, hazard::{self, Footprint, Plan}, job::Packet, }; @@ -43,6 +44,9 @@ struct InFlight { slot: Option, ticket: u64, footprint: Arc, + /// Fragment kick of the published render; a later same-VM overlap of + /// another pair waits for it in firmware. + fragment: Option, } const QUARANTINE_WAIT_MS: u32 = 1000; @@ -67,11 +71,19 @@ struct State { /// their own fence with EBUSY instead of a device-loss error, so the VM stays usable. spared: u64, kill_hold: u64, + /// Slots whose kill hold ended at their killed context's consumed scheduler release, + /// and when. A new owner takes such a pair last, the one released longest ago first. + killed: u64, + killed_at: [u64; cfg::RENDER_SLOTS], + /// The physical queue table still has room for a pair built in an unowned slot. The + /// runtime updates this whenever the table changes. + build_room: bool, released: u64, epoch: u64, footprints: KVec, next_ticket: u64, } + impl State { const MASK: u64 = (u64::MAX >> (64 - cfg::RENDER_SLOTS)) & !1; const fn new() -> Self { @@ -83,6 +95,9 @@ impl State { quarantined: 0, spared: 0, kill_hold: 0, + killed: 0, + killed_at: [0; cfg::RENDER_SLOTS], + build_room: true, released: 0, epoch: 0, footprints: KVec::new(), @@ -116,6 +131,15 @@ impl State { self.epochs[slot] = self.epoch; } fn take_idle(&mut self, slot: usize, owner: u64) -> u8 { + if self.owners[slot] != owner { + crate::g17::trace::teardown( + 6, + slot as u64, + self.owners[slot], + owner | u64::from(self.killed & (1u64 << slot) != 0) << 63, + ); + self.killed &= !(1u64 << slot); + } self.refs[slot] += 1; self.active |= 1u64 << slot; self.released &= !(1u64 << slot); @@ -161,13 +185,23 @@ impl State { } let free = !self.active & Self::MASK & !self.quarantined & !self.kill_hold; let eligible = |slot: usize| free & (1u64 << slot) != 0; - // Reuse a closed, exactly retired owner before consuming a new graph. + let released = |slot: usize| self.released & (1u64 << slot) != 0; + let unowned = || (0..cfg::RENDER_SLOTS).find(|slot| eligible(*slot) && self.owners[*slot] == 0); + // Reuse a closed, exactly retired owner before consuming a new graph. A killed + // context's pair comes after a new graph while one can still be built: on Neo, new + // owners of such pairs had renders that executed but never stored their stamps. + // Once the queue table is full it is reused, the one released longest ago first, + // so killed pairs never strand physical queues. let slot = (0..cfg::RENDER_SLOTS) - .filter(|slot| eligible(*slot) && self.released & (1u64 << slot) != 0) + .filter(|slot| eligible(*slot) && released(*slot) && self.killed & (1u64 << slot) == 0) .min_by_key(|slot| self.epochs[*slot]) + .or_else(|| if self.build_room { unowned() } else { None }) .or_else(|| { - (0..cfg::RENDER_SLOTS).find(|slot| eligible(*slot) && self.owners[*slot] == 0) - })?; + (0..cfg::RENDER_SLOTS) + .filter(|slot| eligible(*slot) && released(*slot)) + .min_by_key(|slot| self.killed_at[*slot]) + }) + .or_else(unowned)?; let first = self.take_idle(slot, owner); self.claim_sibling(owner); Some(first) @@ -176,7 +210,8 @@ impl State { if self.owners.iter().filter(|id| **id == owner).count() >= cfg::RENDER_PAIRS_PER_OWNER { return; } - let free = !self.active & Self::MASK & !self.quarantined & !self.kill_hold; + // A speculative sibling never takes a killed context's pair. + let free = !self.active & Self::MASK & !self.quarantined & !self.kill_hold & !self.killed; let eligible = |slot: usize| free & (1u64 << slot) != 0; let sibling = (0..cfg::RENDER_SLOTS) .filter(|slot| { @@ -186,6 +221,10 @@ impl State { }) .min_by_key(|slot| self.epochs[*slot]) .or_else(|| { + // Nor an unowned slot once no pair can be built there. + if !self.build_room { + return None; + } (0..cfg::RENDER_SLOTS).find(|slot| eligible(*slot) && self.owners[*slot] == 0) }); if let Some(slot) = sibling { @@ -219,6 +258,7 @@ impl State { let bit = 1u64 << slot; let old = self.owners[slot]; if old == 0 + || self.killed & bit != 0 || owner == Some(old) || expected.is_some_and(|expected| expected != old) || self.refs[slot] != 0 @@ -294,6 +334,28 @@ impl State { ) } + /// Whether `pick` would return a slot, without taking it. + fn can_pick(&self, owner: u64, plan: Plan) -> bool { + let usable = |slot: usize| { + self.owners[slot] == owner + && (self.quarantined | self.kill_hold) & (1u64 << slot) == 0 + && self.refs[slot] < cfg::RENDER_DEPTH + }; + match plan { + Plan::Wait => false, + Plan::Pair(slot) => usize::from(slot) < cfg::RENDER_SLOTS && usable(usize::from(slot)), + Plan::Any if owner == 0 => false, + Plan::Any if self.owners.contains(&owner) => (0..cfg::RENDER_SLOTS).any(usable), + Plan::Any => { + let free = !self.active & Self::MASK & !self.quarantined & !self.kill_hold; + (0..cfg::RENDER_SLOTS).any(|slot| { + free & (1u64 << slot) != 0 + && (self.released & (1u64 << slot) != 0 || self.owners[slot] == 0) + }) + } + } + } + fn pick(&mut self, owner: u64, plan: Plan) -> Option { match plan { Plan::Any => self.acquire(owner), @@ -332,6 +394,7 @@ impl State { slot, ticket, footprint: request.footprint.clone(), + fragment: None, }, GFP_KERNEL, ) @@ -339,6 +402,12 @@ impl State { Some((owner, request.sequence)) } + fn position(&self, slot: u8, (owner, sequence): (u64, u64)) -> Option { + self.footprints.iter().position(|entry| { + entry.slot == Some(slot) && entry.owner == owner && entry.sequence == sequence + }) + } + fn forget_waiter(&mut self, ticket: Option) { if let Some(ticket) = ticket { self.footprints @@ -366,9 +435,22 @@ pub(crate) struct Pool { queues: AtomicU32, /// Logical queues admitted at most: each must be able to hold a render pair. max_queues: u32, + /// Logical-queue creation waiting for a returned slot. + waiters: Option>, + /// Render publication waiting for render capacity: woken with every + /// release event above. + renders: Option>, } impl Pool { + #[cfg(CONFIG_KUNIT = "y")] pub(crate) fn new(max_queues: u32) -> Result> { + Self::with_waiters(max_queues, None, None) + } + pub(crate) fn with_waiters( + max_queues: u32, + waiters: Option>, + renders: Option>, + ) -> Result> { if max_queues > MAX_EXECUTION_CONTEXTS { return Err(EINVAL); } @@ -378,10 +460,19 @@ impl Pool { released <- new_condvar!("G17 render pair release"), queues: AtomicU32::new(0), max_queues, + waiters, + renders, }), GFP_KERNEL, ) } + /// Every pair or lease return, owner close and quarantine change. + fn returned(&self) { + self.released.notify_all(); + if let Some(renders) = &self.renders { + renders.notify(); + } + } /// Logical queues currently counted, including one being created. pub(crate) fn queues(&self) -> u32 { self.queues.load(Ordering::Acquire) @@ -522,6 +613,24 @@ impl Pool { record, })) } + /// Whether `acquire_render` would admit this request now, without + /// admitting it: the scheduler's prepare step waits for this. + pub(crate) fn admits_render(&self, owner: u64, request: Option>) -> bool { + let state = self.state.lock(); + if owner == 0 || state.refusal(owner).is_err() { + return false; + } + if state + .owners + .iter() + .position(|id| *id == owner) + .is_some_and(|slot| state.quarantined & (1u64 << slot) != 0) + { + return false; + } + let plan = state.plan(owner, request, None); + state.can_pick(owner, plan) + } pub(crate) fn warmable_sibling( self: &Arc, owner: u64, @@ -558,7 +667,7 @@ impl Pool { } } drop(state); - self.released.notify_all(); + self.returned(); } /// Active leases survive this transition. Their later drop is still the /// event which releases capacity, so a stopped owner is never double-freed. @@ -575,9 +684,27 @@ impl Pool { state.quarantined &= !released; state.spared &= !released; state.kill_hold = 0; + state.killed = 0; drop(state); - self.released.notify_all(); + self.returned(); + } + + /// Whether the physical queue table can hold another pair (see `State::build_room`). + pub(crate) fn note_build_room(&self, room: bool) { + let mut state = self.state.lock(); + if state.build_room != room { + state.build_room = room; + drop(state); + self.returned(); + } } + + /// A killed context's pair is free for a new owner. + pub(crate) fn killed_pair_free(&self) -> bool { + let state = self.state.lock(); + state.killed & state.released & !state.active & !state.quarantined & !state.kill_hold & State::MASK != 0 + } + } #[kunit_tests(asahi_g17_admission_accounting)] @@ -653,9 +780,18 @@ mod accounting_tests { pub(crate) struct QueueSlot { pool: Arc, } +impl QueueSlot { + /// The device's admission pool, which lives as long as the firmware session. + pub(crate) fn pool(&self) -> &Arc { + &self.pool + } +} impl Drop for QueueSlot { fn drop(&mut self) { self.pool.queues.fetch_sub(1, Ordering::AcqRel); + if let Some(waiters) = &self.pool.waiters { + waiters.notify(); + } } } @@ -691,7 +827,7 @@ impl RenderLease { } state.quarantined |= bit; drop(state); - self.pool.released.notify_all(); + self.pool.returned(); } /// The VM pin must be retired first. Keep this lease until after clearing /// quarantine; its drop is what permits waiting publications to proceed. @@ -705,24 +841,83 @@ impl RenderLease { self.rehabilitate_after_processor_stop(); } } +impl RenderLease { + /// Called under the device mutex once the render's kicks are committed, + /// including a replay, which supersedes the earlier kick. + pub(crate) fn note_publication(&self, fragment: KickDependency) { + let Some(record) = self.record else { + return; + }; + let mut state = self.pool.state.lock(); + if let Some(index) = state.position(self.slot, record) { + state.footprints[index].fragment = Some(fragment); + } + } +} + +impl Pool { + /// Under the device mutex, which orders every publication: the newest + /// published fragment kick of each other pair whose live same-VM render + /// overlaps `footprint`. Quarantined pairs are skipped as in admission. + pub(crate) fn publication_barriers( + &self, + owner: u64, + slot: u8, + footprint: &Footprint, + barriers: &mut [KickDependency; hazard::PUBLICATION_BARRIERS], + ) -> Result { + let state = self.state.lock(); + let mut slots = [0u8; hazard::PUBLICATION_BARRIERS]; + let mut count = 0; + for entry in state.footprints.iter() { + let (Some(other), Some(fragment)) = (entry.slot, entry.fragment) else { + continue; + }; + if other == slot + || entry.owner == owner + || state.quarantined & (1u64 << other) != 0 + || !footprint.same_vm(&entry.footprint) + || !footprint.overlaps(&entry.footprint) + { + continue; + } + match slots[..count].iter().position(|known| *known == other) { + // One fragment ring completes in kick order; same QID, so the + // word orders by timestamp. + Some(index) => { + if fragment.word() > barriers[index].word() { + barriers[index] = fragment; + } + } + // Admission bounds the pairs; see hazard::plan. + None if count == hazard::PUBLICATION_BARRIERS => return Err(EBUSY), + None => { + slots[count] = other; + barriers[count] = fragment; + count += 1; + } + } + } + Ok(count) + } +} + impl Drop for RenderLease { fn drop(&mut self) { let mut state = self.pool.state.lock(); - if let Some((owner, sequence)) = self.record { - if let Some(index) = state.footprints.iter().position(|entry| { - entry.slot == Some(self.slot) && entry.owner == owner && entry.sequence == sequence - }) { + if let Some(record) = self.record { + if let Some(index) = state.position(self.slot, record) { state.footprints.swap_remove(index); } } state.release(self.slot); drop(state); - self.pool.released.notify_all(); + self.pool.returned(); } } impl State { - fn hold_for_exit_kill(&mut self, owner: u64, slots: u64) -> bool { + fn hold_for_exit_kill(&mut self, owner: u64, slots: u64, executed: u64) -> bool { if owner == 0 || slots & !Self::MASK != 0 { return false; } @@ -731,10 +926,10 @@ impl State { if slots & bit == 0 { continue; } + // A pair whose commands all ran is held with them (see `Pair::executed_for_kill`). if self.owners[slot] != owner - || self.refs[slot] != 0 - || self.active & bit != 0 - || self.released & bit == 0 + || (executed & bit == 0 + && (self.refs[slot] != 0 || self.active & bit != 0 || self.released & bit == 0)) { return false; } @@ -745,18 +940,39 @@ impl State { } impl Pool { /// Hold the departed owner's idle pairs before publishing its exit record. - /// Successful publication retains these bits until both processors stop. - pub(crate) fn hold_render_slots_for_exit_kill(&self, owner: u64, slots: u64) -> bool { - self.state.lock().hold_for_exit_kill(owner, slots) + /// Successful publication retains these bits until the owner's scheduler release is + /// consumed (`release_killed_slots` at detach), or until both processors stop. + pub(crate) fn hold_render_slots_for_exit_kill(&self, owner: u64, slots: u64, executed: u64) -> bool { + self.state.lock().hold_for_exit_kill(owner, slots, executed) } - /// Undo only a hold whose control record was not published. + /// Undo a hold whose control record was not published. pub(crate) fn release_exit_kill_hold(&self, slots: u64) { if slots == 0 { return; } self.state.lock().kill_hold &= !slots; - self.released.notify_all(); + self.returned(); + } + + /// End the kill hold of `slots` once their killed context's scheduler release was + /// consumed (the firmware no longer names these pairs). A new owner takes them last. + pub(crate) fn release_killed_slots(&self, slots: u64) { + let mut state = self.state.lock(); + let held = state.kill_hold & slots & State::MASK; + if held == 0 { + return; + } + let now = crate::g17::now_ns(); + state.kill_hold &= !held; + state.killed |= held; + for slot in 0..cfg::RENDER_SLOTS { + if held & (1u64 << slot) != 0 { + state.killed_at[slot] = now; + } + } + drop(state); + self.returned(); } } @@ -831,13 +1047,86 @@ mod fault_capacity_tests { let bit = 1u64 << lease.slot(); pool.release_owner(1); drop(lease); - assert!(pool.hold_render_slots_for_exit_kill(1, bit)); + assert!(pool.hold_render_slots_for_exit_kill(1, bit, 0)); assert!(!pool.reassign_unused_spare(bit.trailing_zeros() as u8, Some(1), Some(2))); // A failed (unpublished) control may roll back its hold. Published kills // keep the existing processor-stop rule; this test must not weaken it. pool.release_retained_owners_after_processor_stop(); assert_eq!(pool.state.lock().kill_hold, 0); } + + #[test] + fn consumed_scheduler_release_ends_the_kill_hold() { + let pool = Pool::new(MAX_EXECUTION_CONTEXTS).unwrap(); + let lease = pool.acquire_render(1, None, u64::MAX, || Ok(())).unwrap(); + let slot = lease.slot(); + let bit = 1u64 << slot; + pool.release_owner(1); + drop(lease); + assert!(pool.hold_render_slots_for_exit_kill(1, bit, 0)); + assert!(!pool.reassign_unused_spare(slot, Some(1), Some(2))); + pool.release_killed_slots(bit); + assert_eq!(pool.state.lock().kill_hold, 0); + assert_eq!(pool.state.lock().killed, bit); + assert!(pool.killed_pair_free()); + // The killed context's pair is never reassigned as a spare, and a new owner takes + // a new graph before it. + assert!(!pool.reassign_unused_spare(slot, Some(1), Some(2))); + let next = pool.acquire_render(2, None, u64::MAX, || Ok(())).unwrap(); + assert_ne!(next.slot(), slot); + drop(next); + pool.release_retained_owners_after_processor_stop(); + assert_eq!(pool.state.lock().killed, 0); + } + + #[test] + fn killed_pairs_are_taken_last_oldest_first() { + let pool = Pool::new(MAX_EXECUTION_CONTEXTS).unwrap(); + { + let mut state = pool.state.lock(); + for slot in 1..cfg::RENDER_SLOTS { + state.owners[slot] = 100 + slot as u64; + state.released |= 1u64 << slot; + state.killed |= 1u64 << slot; + state.killed_at[slot] = 1000 - slot as u64; + } + // One pair released without a kill. + state.killed &= !(1u64 << 7); + } + let first = pool.acquire_render(1, None, u64::MAX, || Ok(())).unwrap(); + assert_eq!(first.slot(), 7); + let second = pool.acquire_render(2, None, u64::MAX, || Ok(())).unwrap(); + assert_eq!(usize::from(second.slot()), cfg::RENDER_SLOTS - 1); + assert_eq!(pool.state.lock().killed & (1u64 << second.slot()), 0); + } + + #[test] + fn a_full_queue_table_reuses_killed_pairs_before_unowned_slots() { + let pool = Pool::new(MAX_EXECUTION_CONTEXTS).unwrap(); + { + let mut state = pool.state.lock(); + for slot in 1..8 { + state.owners[slot] = 100 + slot as u64; + state.released |= 1u64 << slot; + state.killed |= 1u64 << slot; + state.killed_at[slot] = 1000 - slot as u64; + } + } + // With room in the queue table a new owner builds a pair in an unowned slot. + let built = pool.acquire_render(1, None, u64::MAX, || Ok(())).unwrap(); + assert!(usize::from(built.slot()) >= 8); + // Without room it takes the killed pair released longest ago. + pool.note_build_room(false); + let reused = pool.acquire_render(2, None, u64::MAX, || Ok(())).unwrap(); + assert_eq!(reused.slot(), 7); + assert_eq!(pool.state.lock().killed & (1u64 << 7), 0); + // It claims no sibling: neither another killed pair nor an unowned slot. + let state = pool.state.lock(); + for slot in 1..7 { + assert_eq!(state.owners[slot], 100 + slot as u64); + } + assert_eq!(state.owners.iter().filter(|owner| **owner == 2).count(), 1); + } } impl Pool { diff --git a/drivers/gpu/drm/asahi/g17/queue/compute.rs b/drivers/gpu/drm/asahi/g17/queue/compute.rs index 713809907dd4b..e1bda516ee102 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute.rs @@ -15,7 +15,7 @@ use crate::g17::{ queue::*, variant::PointerLayout, }, - object::{Allocator, CpuMap, KernelObject}, + object::{Allocator, CpuMap, Family, KernelObject, Pool, PooledObject}, }; use crate::{hw::t8140::dynamic, mmu}; use core::sync::atomic::{fence, Ordering}; @@ -30,10 +30,20 @@ const DESCRIPTORS: usize = 32; const GRAPH_SIZE: usize = 0x8000; const CONTEXT_SIZE: usize = 0x20000; const RECORD_STRIDE: usize = 0x1000; +const NATIVE_WAIT_OFFSET: usize = 0x900; +const NATIVE_WAIT_STRIDE: usize = 0x60; +// A submission can name every physical compute channel. Keep the complete +// wait list inside its own cached slot, including the last retained slot. +const CACHED_RECORD_STRIDE: usize = (NATIVE_WAIT_OFFSET + + crate::g17::kick::COMPUTE_CHANNELS * NATIVE_WAIT_STRIDE).next_power_of_two(); const CONFIG: usize = 0; const ANNOUNCE: usize = 0x800; const PREDECESSOR: usize = 0x400; const POINTERS: usize = 0x100; +/// Producer/consumer words live on their own uncached page after the graph, +/// which the firmware reads through a cached mapping (queue record, job-list +/// head and item ring), as native does. +const CURSORS: usize = GRAPH_SIZE; const ITEMS: usize = 0x4000; const STAMP: usize = 0x300; @@ -83,24 +93,32 @@ pub(crate) struct Binding { /// Descriptor ring of one compute QID with its kernel-VM alias. Built without /// the device mutex; installed into a vacant graph before an owner binds. pub(crate) struct DescriptorRing { - object: KernelObject, - high: mmu::KernelMapping, + // Drop the GPU alias before the retained storage returns to its pool. + low: mmu::KernelMapping, + object: PooledObject, } impl DescriptorRing { - pub(crate) fn new(alloc: &Allocator<'_>) -> Result { - let page = mmu::UAT_PGSZ as u64; - let prot = mmu::PROT_GPU_FW_SHARED_RW; - let object = alloc.lower( - DESCRIPTORS * COMPUTE_DESCRIPTOR_SIZE, - page, - prot, + /// Firmware reads every descriptor through a cached kernel mapping, as it + /// does for render. The pool keeps that mapping until processor stop; the + /// hardware reads the same pages through an uncached canonical low alias. + pub(crate) fn new(alloc: &Allocator<'_>, objects: &Arc) -> Result { + let size = DESCRIPTORS * COMPUTE_DESCRIPTOR_SIZE; + let object = objects.allocate( + alloc, + Family::Descriptor, + size, + size, + mmu::PROT_GPU_FW_SHARED_RW, CpuMap::WriteCombined, )?; - let mut high = alloc.uat.geometry().kernel_range(); - high.start += dynamic::KERNEL_OFFSET; - let high = object.alias_in(alloc.uat.kernel_vm(), high, page, prot)?; - Ok(Self { object, high }) + let low = object.alias_in( + alloc.uat.kernel_lower_vm(), + dynamic::LOWER, + mmu::UAT_PGSZ as u64, + mmu::PROT_GPU_SHARED_RW, + )?; + Ok(Self { low, object }) } } @@ -112,10 +130,14 @@ struct Graph { usage: KernelObject, operand: KernelObject, support: KernelObject, - graph: KernelObject, + graph: PooledObject, context_low: KernelObject, context_high: KernelObject, + /// Announcements, which stay uncached for the firmware. records: KernelObject, + /// Configuration, predecessor and completion-wait records at the same + /// offsets, which the firmware reads through a cached mapping. + cached_records: PooledObject, record_ends: [Option; RECORD_SLOTS], reclaimed: u32, /// Host words of the pointer block in this firmware build. @@ -127,6 +149,7 @@ struct Graph { impl Graph { fn new( alloc: &Allocator<'_>, + objects: &Arc, context: &Context, pointers: PointerLayout, operand_state: &'static OperandState, @@ -134,7 +157,7 @@ impl Graph { let page = mmu::UAT_PGSZ as u64; let prot = mmu::PROT_GPU_FW_SHARED_RW; let cpu = CpuMap::WriteCombined; - let ring = DescriptorRing::new(alloc)?; + let ring = DescriptorRing::new(alloc, objects)?; let preempt = alloc.kernel(0x4000, page, prot, cpu)?; let usage = alloc.kernel(mmu::UAT_PGSZ, page, prot, cpu)?; let operand = alloc.lower(operand_state.size, page, prot, cpu)?; @@ -144,11 +167,18 @@ impl Graph { support.write(0x100, support_va + page + 4)?; support.write(0x110, 0x50u32)?; support.write(mmu::UAT_PGSZ + 4, 1u32)?; - let mut graph = alloc.kernel(GRAPH_SIZE, page, prot, cpu)?; + let mut graph = objects.allocate( + alloc, + Family::QueueState, + GRAPH_SIZE + mmu::UAT_PGSZ, + GRAPH_SIZE, + prot, + cpu, + )?; let graph_va = graph.gpu_va(); graph.initialize::(0, |view| { view.queue = QueueRecord::new(&QueueRecordArgs { - pointers_va: graph_va + POINTERS as u64, + pointers_va: graph_va + CURSORS as u64, items_va: graph_va + ITEMS as u64, job_list_va: context.work_head_va(), scheduler_va: context.scheduler_va(), @@ -159,9 +189,21 @@ impl Graph { view.jobs = JobListHead::new(graph_va + 0x200); Ok(()) })?; + graph.initialize::(CURSORS, |view| { + *view = PointerBlock::new(&pointers)?; + Ok(()) + })?; let context_low = alloc.lower(CONTEXT_SIZE, page, prot, cpu)?; let context_high = alloc.kernel(CONTEXT_SIZE, page, prot, cpu)?; let records = alloc.kernel(RECORD_SLOTS * RECORD_STRIDE, page, prot, cpu)?; + let cached_records = objects.allocate( + alloc, + Family::QueueConfig, + RECORD_SLOTS * CACHED_RECORD_STRIDE, + RECORD_SLOTS * CACHED_RECORD_STRIDE, + prot, + cpu, + )?; Ok(Self { ring: Some(ring), preempt, @@ -172,6 +214,7 @@ impl Graph { context_low, context_high, records, + cached_records, record_ends: [None; RECORD_SLOTS], reclaimed: 0, pointers, @@ -237,9 +280,9 @@ impl Graph { fn cursors(&self) -> Result { Ok(Cursors { - producer: self.graph.word(POINTERS + self.pointers.producer)?.load(Ordering::Relaxed), - consumer: self.graph.word(POINTERS)?.load(Ordering::Relaxed), - count: self.graph.word(POINTERS + 0x60)?.load(Ordering::Relaxed), + producer: self.graph.word(CURSORS + self.pointers.producer)?.load(Ordering::Relaxed), + consumer: self.graph.word(CURSORS)?.load(Ordering::Relaxed), + count: self.graph.word(CURSORS + 0x60)?.load(Ordering::Relaxed), }) } fn stamp(&self) -> Result { @@ -250,12 +293,12 @@ impl Graph { fn descriptor_vas(&self, slot: u8) -> Result<(u64, u64)> { let ring = self.ring.as_ref().ok_or(EAGAIN)?; let offset = (usize::from(slot) % DESCRIPTORS * COMPUTE_DESCRIPTOR_SIZE) as u64; - Ok((ring.high.iova() + offset, ring.object.gpu_va() + offset)) + Ok((ring.object.gpu_va() + offset, ring.low.iova() + offset)) } fn descriptor_alias(&self, vm: &mmu::Vm) -> Result { let ring = self.ring.as_ref().ok_or(EAGAIN)?; ring.object - .map_alias(vm, ring.object.gpu_va(), mmu::PROT_GPU_SHARED_RO) + .map_alias(vm, ring.low.iova(), mmu::PROT_GPU_SHARED_RO) } /// Detaches the descriptor ring of an idle vacant queue; the caller frees it /// off-lock. The job list and the context item only name consumed @@ -293,7 +336,7 @@ impl Graph { } fence(Ordering::SeqCst); self.graph - .word(POINTERS + self.pointers.producer)? + .word(CURSORS + self.pointers.producer)? .store(cursors.consumer, Ordering::Relaxed); self.reclaimed = cursors.consumer; fence(Ordering::SeqCst); @@ -326,6 +369,10 @@ use crate::g17::{ }; use retirement::{Batch, Observation, Recordless, Ticket, IN_FLIGHT}; +/// Position in its queue's active ring from which a publication withholds its doorbell: +/// with two unretired commands ahead, it cannot be the next to start. +const WITHHOLD_DEPTH: usize = 3; + struct Fifo { entries: [Option; IN_FLIGHT], head: usize, @@ -519,12 +566,24 @@ pub(crate) struct Queue { replay_error: Option, replay_prepared: bool, exit: Option, + /// Since when the kill of this failed queue's context waits for other contexts' compute, + /// and whether that wait was reported. + kill_wait: Option<(u64, bool)>, installed: bool, /// Bit `p` set once a work slot of priority class `p` named this queue. outer_started: u8, published_config: Option, + /// Last configuration record the firmware consumed, and its recovery + /// epoch. A command repeats the record only when it differs. + config_cache: Option<(QueueConfig, u64)>, ordinal: u32, submitted: u32, + /// The stamp of the newest command the firmware reported failed ([`Self::note_fault`]), + /// while it is newer than the stored stamp. + fault_stamp: Option, + /// Prepared packets of the bound owner about to publish (see [`Self::claim`]). + claims: u32, + last_published: u64, last_end: u64, released: bool, retirement_ready: bool, @@ -538,13 +597,72 @@ pub(crate) struct Queue { quarantine_error: Option, failure_status_pending: bool, retirement_proved: bool, + source_readers: Arc, + source_hint: crate::g17::sync_input::ReaderHint, + /// The doorbell withheld from publications that had [`WITHHOLD_DEPTH`] - 1 or more + /// unretired commands of this queue ahead of them, the publication of the first of them + /// that no doorbell has announced, and the compute work slots written at its priority once + /// the last one's slot was. + withheld_doorbell: Option<(u64, u64, u64)>, } impl Queue { + /// Returns the withheld doorbell once at most one command of this queue that precedes the + /// first publication it covers has no completion record yet. A doorbell of the same class + /// rung after the last withheld work slot was written announced every one of them: nothing + /// is left to ring. `announced` gives the slots announced per class. + /// + /// Retirement waiting for a stamp does not hold the doorbell back, and any departure from + /// ordinary retirement (timeout, kill, quarantine, replay, recovery or exit) rings it at + /// once: the firmware then sees every published command, as without withholding. + pub(crate) fn due_doorbell(&mut self, announced: impl Fn(u8) -> u64) -> Option { + let (doorbell, first, written) = self.withheld_doorbell?; + if announced(((doorbell >> 2) & 3) as u8) >= written { + self.withheld_doorbell = None; + return None; + } + let settling = self.settling(); + let incomplete = self + .active + .iter() + .filter(|active| active.published < first && active.observed.is_none()) + .count(); + if !settling && incomplete > 1 { + return None; + } + self.withheld_doorbell = None; + Some(doorbell) + } + + /// The queue has left ordinary retirement: a timeout, kill, quarantine, replay, recovery + /// witness or exit is in progress, or retirement stopped at a command the firmware + /// finished without a completion record. A front whose record has arrived and that only + /// waits for its stamp is ordinary retirement: [`Self::due_doorbell`] counts it as + /// finished, and ringing at once there rang most withheld doorbells early. + fn settling(&self) -> bool { + self.quarantined + || self.released + || self.exit.is_some() + || self.replays.len != 0 + || self.replay_retirement + || self.awaiting_witness + || (self.retire_pending + && self.active.front().is_none_or(|active| active.observed.is_none())) + || self.retired_by_teardown + || self.spared_quarantine + || self.failure_status_pending + || self.terminal.is_some() + } + + /// Takes the withheld doorbell regardless of its predecessors. + pub(crate) fn take_withheld_doorbell(&mut self) -> Option { + self.withheld_doorbell.take().map(|(doorbell, _, _)| doorbell) + } /// The caller reserves a QID first, cancels it on construction failure, and retains every /// successfully installed queue until processor stop. Shared VM apertures precede this call. pub(crate) fn new( alloc: &Allocator<'_>, + objects: &Arc, backing: Backing, owner: u64, context: Arc, @@ -552,11 +670,13 @@ impl Queue { operand_state: &'static OperandState, ) -> Result { let Backing { mut pool, mut kick } = backing; - let mut graph = Graph::new(alloc, &context, pointers, operand_state)?; + let mut graph = Graph::new(alloc, objects, &context, pointers, operand_state)?; let kick_alias = kick.map_client(context.vm())?; let pool_alias = pool.map_client(context.vm())?; let binding = graph.bind(context.clone(), kick_alias, pool_alias)?; context.mark_published(); + context.note_compute_binding(true); + crate::g17::trace::bind(crate::g17::trace::COMPUTE, kick.id().qid(), context.id(), 0); Ok(Self { owner: Some(owner), retained_owner: None, @@ -579,11 +699,16 @@ impl Queue { replay_error: None, replay_prepared: false, exit: None, + kill_wait: None, installed: false, outer_started: 0, published_config: None, + config_cache: None, ordinal: 0, submitted: 0, + fault_stamp: None, + claims: 0, + last_published: 0, last_end: 0, released: false, retirement_ready: false, @@ -597,8 +722,42 @@ impl Queue { quarantine_error: None, failure_status_pending: false, retirement_proved: false, + source_readers: Arc::new(core::sync::atomic::AtomicU32::new(0), GFP_KERNEL)?, + source_hint: crate::g17::sync_input::ReaderHint::new(), + withheld_doorbell: None, }) } + /// The firmware failed the command whose completion stamp is `stamp`. It writes a + /// zero-duration completion record for it, which quarantines the queue, but stores no + /// stamp: without this the quarantine would wait for its retirement witness forever and + /// keep the queue's backing. Commands published after the failed one still need their own + /// stamp or record. + pub(crate) fn note_fault(&mut self, stamp: u32) { + if self + .fault_stamp + .is_none_or(|old| retirement::stamp_newer(stamp, old)) + { + self.fault_stamp = Some(stamp); + } + } + + /// The firmware failed a command of this quarantined queue, and the queue still waits for + /// its retirement witness: commands published after the failed one, which the firmware + /// never runs, or the failed command itself, whose ring entry it does not consume (a ray + /// traversal's, on Neo). Only a kill of the queue's context gives them back. + pub(crate) fn fault_pending(&self) -> bool { + self.fault_stamp.is_some() && self.quarantined && self.awaiting_witness && self.exit.is_none() + } + + /// The stamp that proves retirement: the stored one, or a newer reported fault's. + fn witness_stamp(&self) -> Result { + let stored = self.graph.stamp()?; + Ok(match self.fault_stamp { + Some(fault) if retirement::stamp_newer(fault, stored) => fault, + _ => stored, + }) + } + pub(crate) fn qid(&self) -> u8 { self.kick.id().qid() } @@ -632,6 +791,16 @@ impl Queue { pub(crate) fn in_flight(&self) -> bool { self.active.len != 0 && !self.quarantined } + /// The oldest command is a published long job (ray traversal or AS build) whose + /// completion record the driver has not seen yet. + pub(crate) fn long_job_running(&self) -> bool { + !self.quarantined + && self.active.front().is_some_and(|active| { + active.observed.is_none() + && active.packet.is_published() + && active.packet.command.long_job() + }) + } /// Quarantines without a pending witness no longer describe live pipe order. pub(crate) fn timeout_head(&self) -> Option { if self.quarantined && !self.awaiting_witness { @@ -659,6 +828,27 @@ impl Queue { visit(active.packet.completion.status()); } } + /// A recovery record names this queue and the completion stamp of the + /// command it interrupted, `kick << 8` like the queue's stamp. Without a + /// stamp match the oldest published command is marked. + pub(crate) fn note_interrupted(&self, stamp: u32) -> recovery::Interrupted { + let sequence = stamp >> 8; + if let Some(active) = self.active.iter().find(|active| { + active + .ticket + .is_some_and(|ticket| (ticket.kick as u32) & 0xff_ffff == sequence) + }) { + active.packet.completion.note_interrupted(); + return recovery::Interrupted::Exact; + } + match self.active.iter().find(|active| active.ticket.is_some()) { + Some(active) => { + active.packet.completion.note_interrupted(); + recovery::Interrupted::Oldest + } + None => recovery::Interrupted::None, + } + } pub(crate) fn classify(&self, class: &dyn Fn(&Arc) -> recovery::Class) { for active in self.active.iter() { active @@ -706,33 +896,32 @@ impl Queue { || self.retire_pending || masks.is_none_or(|m| m[self.qid() as usize / 64] & (1 << (self.qid() % 64)) != 0) } - /// Stage every unlatched exact identity before the device's single shared-ring scan. - pub(crate) fn request_observations( - &self, - batch: &mut Batch, - masks: Option<[u64; 2]>, - ) -> Result { - if self.quarantined || !self.selected(masks) { + /// Stage every unlatched exact identity before the device's single shared-ring scan, + /// whichever queues the scan's batch retires. + pub(crate) fn request_observations(&self, batch: &mut Batch) -> Result { + if self.quarantined { return Ok(false); } + batch.stage_frontier(self.qid(), self.kick.timestamp().get())?; let mut requested = false; + let mut stamp = None; for active in self.active.iter() { let Some(ticket) = active.ticket else { break; }; if active.observed.is_none() { - batch.request(&ticket)?; + let stamp = match stamp { + Some(stamp) => stamp, + None => *stamp.insert(self.graph.stamp()?), + }; + batch.request(&ticket, retirement::stamp_covers(stamp, ticket.kick))?; requested = true; } } Ok(requested) } - pub(crate) fn latch_observations( - &mut self, - batch: &mut Batch, - masks: Option<[u64; 2]>, - ) -> Result { - if self.quarantined || !self.selected(masks) { + pub(crate) fn latch_observations(&mut self, batch: &mut Batch) -> Result { + if self.quarantined { return Ok(()); } for active in self.active.iter_mut() { @@ -887,6 +1076,11 @@ impl Queue { self.active.iter_mut().next().ok_or(EIO)?.qos_pending = false; } let front = self.active.front().ok_or(EIO)?; + if let Some(ticket) = front.ticket { + crate::g17::recovery::note_retired(); + crate::g17::trace::complete(crate::g17::trace::COMPUTE, ticket.qid, + ticket.ordinal, &front.packet, [timestamps[0], timestamps[1]]); + } self.retire_pending = true; defer(Deferred::Retired( front.packet.completion.clone(), @@ -956,7 +1150,7 @@ impl Queue { if !retirement::quarantined_retired( &newest, self.graph.cursors()?, - self.graph.stamp()?, + self.witness_stamp()?, self.active.len as u32, self.submitted, )? { @@ -1000,6 +1194,7 @@ impl Queue { drop(self.active.pop()); } self.awaiting_witness = false; + self.fault_stamp = None; self.retirement_proved = false; self.replay_retirement = false; self.spared_deferred = false; @@ -1139,6 +1334,32 @@ fn release_pending(released: bool, quarantined: bool, active: usize, retiring: b mod admission_tests { use super::*; + #[test] + fn complete_native_wait_list_fits_every_cached_slot() { + let waits = crate::g17::kick::COMPUTE_CHANNELS; + for slot in 0..RECORD_SLOTS { + for index in 0..waits { + let start = record_start(slot, NATIVE_WAIT_OFFSET + index * NATIVE_WAIT_STRIDE, + NATIVE_WAIT_STRIDE, CACHED_RECORD_STRIDE).unwrap(); + assert!(start >= slot * CACHED_RECORD_STRIDE); + assert!(start + NATIVE_WAIT_STRIDE <= (slot + 1) * CACHED_RECORD_STRIDE); + } + } + } + + #[test] + fn record_window_cannot_overwrite_the_next_slot() { + // The nineteenth wait used to cross the old slot boundary silently. + let offset = NATIVE_WAIT_OFFSET + 18 * NATIVE_WAIT_STRIDE; + assert_eq!(record_start(0, offset, NATIVE_WAIT_STRIDE, RECORD_STRIDE), Err(EINVAL)); + assert!(record_start(0, offset, NATIVE_WAIT_STRIDE, CACHED_RECORD_STRIDE).is_ok()); + assert_eq!(record_start(RECORD_SLOTS, 0, 1, CACHED_RECORD_STRIDE), Err(EINVAL)); + assert_eq!(record_start(0, usize::MAX, 1, CACHED_RECORD_STRIDE), Err(EINVAL)); + assert_eq!(record_start(0, CACHED_RECORD_STRIDE, 1, CACHED_RECORD_STRIDE), Err(EINVAL)); + assert_eq!(record_start(0, CACHED_RECORD_STRIDE - 1, 1, CACHED_RECORD_STRIDE), + Ok(CACHED_RECORD_STRIDE - 1)); + } + #[test] fn retained_ring_pressure_waits_without_discarding_work() { let mut active = Fifo::new(); @@ -1230,11 +1451,15 @@ impl Queue { && self.active.len == 0 && self.replays.len == 0 && self.previous.is_none() + // Borrow acquisition is serialized by the device mutex. Avoid + // touching the separate loan counter for an otherwise busy queue. + && !self.source_hint.has_readers(&self.source_readers) } pub(crate) fn bind_owner(&mut self, owner: u64, context: Arc) -> Result { if !self.reusable() { return Err(EBUSY); } + self.source_hint.reset_for_owner(&self.source_readers)?; if !context.is_current() { return Err(EFAULT); } @@ -1274,6 +1499,9 @@ impl Queue { self.retained_owner = None; } context.mark_published(); + context.note_compute_binding(true); + crate::g17::trace::bind(crate::g17::trace::COMPUTE, self.qid(), context.id(), + if same_vm { 2 } else { 1 }); self.owner = Some(owner); self.unpublished_binding = true; self.released = false; @@ -1284,6 +1512,9 @@ impl Queue { /// True allows the registry to cancel the never-published QID and drop this graph. An /// installed graph remains retained even after the logical owner exits without work. pub(crate) fn release_owner(&mut self) -> Result { + if let Some(binding) = &self.binding { + binding.context.note_compute_binding(false); + } if !self.installed && self.active.len == 0 && !self.quarantined && self.previous.is_none() { return Ok(true); } @@ -1338,7 +1569,9 @@ impl Queue { } /// An idle retained queue whose USC backing may be given back. pub(crate) fn vacate_candidate(&self) -> bool { - (self.reusable() || self.recycled_pending()) && self.pool_phase == PoolPhase::Populated && self.pool.populated() + self.pool_phase == PoolPhase::Populated && self.pool.populated() + && (self.reusable() || self.recycled_pending()) + && !self.source_hint.has_readers(&self.source_readers) } /// A recycled killed queue still holding the backing its killed work used. pub(crate) fn recycled_pending(&self) -> bool { @@ -1396,6 +1629,7 @@ impl Queue { } fn recycle_settled(&mut self) -> Result { self.graph.reset_items()?; + self.config_cache = None; self.exit = None; self.quarantined = false; self.quarantine_error = None; @@ -1405,6 +1639,7 @@ impl Queue { self.spared_deferred = false; self.retire_pending = false; self.awaiting_witness = false; + self.fault_stamp = None; self.retirement_proved = false; self.replay_retirement = false; self.replay_error = None; @@ -1476,6 +1711,7 @@ impl Queue { ring: DescriptorRing, ) -> Result<(u16, u64)> { if !self.can_rearm() { return Err(EAGAIN); } + self.withheld_doorbell = None; self.graph.rearm_ring(ring)?; self.pool.rearm(pages)?; self.pool_phase = PoolPhase::Populated; @@ -1634,11 +1870,11 @@ impl Queue { let producer = self .graph .graph - .word(POINTERS + self.graph.pointers.producer)? + .word(CURSORS + self.graph.pointers.producer)? .load(Ordering::Relaxed); self.graph .graph - .word(POINTERS + target)? + .word(CURSORS + target)? .store(producer, Ordering::Relaxed); Ok(()) } @@ -1690,6 +1926,15 @@ impl Queue { } } +/// Validate the individual slot before deriving an offset into its allocation. +/// Allocation-wide checks cannot detect a write into the following live slot. +fn record_start(slot: usize, offset: usize, window: usize, stride: usize) -> Result { + if slot >= RECORD_SLOTS || offset > stride || window > stride - offset { + return Err(EINVAL); + } + slot.checked_mul(stride).and_then(|start| start.checked_add(offset)).ok_or(EINVAL) +} + impl Graph { fn record( &mut self, @@ -1699,12 +1944,15 @@ impl Graph { value: T, ) -> Result { let (object, start) = if let Some(slot) = slot { - if slot >= RECORD_SLOTS { - return Err(EINVAL); - } - (&mut self.records, slot * RECORD_STRIDE + offset) + let (object, stride): (&mut KernelObject, usize) = if offset == ANNOUNCE { + (&mut self.records, RECORD_STRIDE) + } else { + (&mut self.cached_records, CACHED_RECORD_STRIDE) + }; + (object, record_start(slot, offset, window, stride)?) } else { - (&mut self.graph, 0x6800 + offset) + let start = record_start(0, offset, window, GRAPH_SIZE - 0x6800)?; + (&mut *self.graph, 0x6800 + start) }; if core::mem::size_of::() > window { return Err(EINVAL); @@ -1730,7 +1978,7 @@ impl Graph { return Err(EBUSY); } let slot = self.graph.dword(ITEMS + expected as usize * 8)?; - let producer = self.graph.word(POINTERS + self.pointers.producer)?; + let producer = self.graph.word(CURSORS + self.pointers.producer)?; if slot.load(Ordering::Relaxed) != 0 { return Err(EBUSY); } @@ -1758,7 +2006,10 @@ pub(crate) trait Host { fn publish_qid(&mut self, id: kick::Id) -> Result; fn publish_outer(&mut self, priority: u8, slot: &crate::g17::fw::channels::WorkSlot) -> Result; - fn notify(&mut self, message: u64) -> Result; + fn notify_compute(&mut self, priority: u8, message: u64) -> Result; + fn compute_written(&self, priority: u8) -> u64; + /// Compute work slots at `priority` that a doorbell has announced. + fn compute_announced(&self, priority: u8) -> u64; fn note_submission(&mut self) -> Result; } @@ -1775,6 +2026,17 @@ impl Queue { dependencies: &[crate::g17::fw::kick::KickDependency], host: &mut impl Host, defer: &mut impl FnMut(Deferred) -> Result, + ) -> Result { + self.publish_with_native_waits(packet, dependencies, &[], host, defer) + } + + pub(in crate::g17) fn publish_with_native_waits( + &mut self, + packet: Arc, + dependencies: &[crate::g17::fw::kick::KickDependency], + native: &[crate::g17::sync_input::NativeWait], + host: &mut impl Host, + defer: &mut impl FnMut(Deferred) -> Result, ) -> Result { use crate::g17::fw::{ channels::WorkSlot, @@ -1809,11 +2071,22 @@ impl Queue { let (descriptor_high, descriptor_low) = self.graph.descriptor_vas(kick_timestamp.slot() as u8)?; let first = !self.installed && self.ordinal == 0; + // The first prototype only waits on an installed consumer channel. + // A retry without a published source falls back to completion on CPU. + if first && !native.is_empty() { return Err(EAGAIN); } + if native.len() > crate::g17::kick::COMPUTE_CHANNELS { return Err(EINVAL); } let entry_signal = first || dependencies .iter() .any(|dependency| dependency.qid() != qid); item_capacity(self.graph.cursors()?, entry_signal)?; + if !native.is_empty() { + let cursors = self.graph.cursors()?; + let free = (cursors.consumer + cursors.count - cursors.producer - 1) % cursors.count; + if free < (if entry_signal { 4 } else { 3 }) + native.len() as u32 { + return Err(EAGAIN); + } + } let record_slot = (!first).then_some(self.ordinal as usize % RECORD_SLOTS); if let Some(slot) = record_slot { self.graph.ensure_record_slot(slot)?; @@ -1858,6 +2131,8 @@ impl Queue { let policy = packet.context.policy(); let ring = 1u8 << policy.priority(); let config = self.queue_config(&identity, first, args.context_id, policy, &packet)?; + let cached = self.config_cache.and_then(|(config, at)| (at == epoch).then_some(config)); + let send_config = first || super::render::publication::required(cached, config); let announce = KickAnnounce::new(qid, DataMaster::Compute, kick_timestamp, policy.priority())?; let predecessor = KickPredecessor::new(qid, kick_parent)?; @@ -1879,6 +2154,7 @@ impl Queue { priority: policy.priority(), }; let published = host.next_compute_publication()?; + self.last_published = published; self.active .push(Active { packet: packet.clone(), @@ -1904,7 +2180,7 @@ impl Queue { .object .initialize::( kick_timestamp.slot() % DESCRIPTORS * COMPUTE_DESCRIPTOR_SIZE, - |descriptor| descriptor.write(&args), + |descriptor| descriptor.write_zeroed(&args), )?; if first { self.graph.context_high.write( @@ -1921,8 +2197,20 @@ impl Queue { let cursors = self.graph.cursors()?; let _ = self.graph.recycle(cursors); } - let config_va = self.graph.record(record_slot, CONFIG, 0xc0, config)?; + let config_va = if send_config { + Some(self.graph.record(record_slot, CONFIG, 0xc0, config)?) + } else { + None + }; let cursors = self.graph.cursors()?; + let mut wait_vas = KVec::with_capacity(native.len(), GFP_KERNEL)?; + for (index, wait) in native.iter().enumerate() { + let record = wait.bytes(crate::g17::fw::stamp_value(kick_timestamp.get() as u32), + packet.order.sequence); + let address = self.graph.record(record_slot, NATIVE_WAIT_OFFSET + index * NATIVE_WAIT_STRIDE, + NATIVE_WAIT_STRIDE, record)?; + wait_vas.push(address, GFP_KERNEL)?; + } let prepared_kick = self.kick.prepare_entry(&kick_args)?; let mut producer; if first { @@ -1948,7 +2236,7 @@ impl Queue { })?; producer = self .graph - .append(producer, config_va, || {}) + .append(producer, config_va.ok_or(EINVAL)?, || {}) .or_else(|error| { self.pool.cancel_unpublished(target)?; host.qos_cancel(qos)?; @@ -1957,15 +2245,22 @@ impl Queue { })?; } else { let target = self.pool.reserve_submission()?; - producer = self - .graph - .append(cursors.producer, descriptor_high, || { - packet.mark_published() + producer = (|| -> Result { + let mut before_descriptor = cursors.producer; + for &wait in &wait_vas { + before_descriptor = self.graph.append(before_descriptor, wait, || { + packet.mark_published(); + visible = true; + })?; + } + self.graph.append(before_descriptor, descriptor_high, || { + packet.mark_published(); + visible = true; }) - .or_else(|error| { - self.pool.cancel_unpublished(target)?; - Err(error) - })?; + })().or_else(|error| { + if !visible { self.pool.cancel_unpublished(target)?; } + Err(error) + })?; visible = true; self.unpublished_binding = false; let qos = host.qos_publish( @@ -1978,14 +2273,16 @@ impl Queue { policy, )?; self.active.back_mut().ok_or(EIO)?.qos_pending = true; - producer = self - .graph - .append(producer, config_va, || {}) - .or_else(|error| { - host.qos_cancel(qos)?; - self.active.back_mut().ok_or(EIO)?.qos_pending = false; - Err(error) - })?; + if let Some(config_va) = config_va { + producer = self + .graph + .append(producer, config_va, || {}) + .or_else(|error| { + host.qos_cancel(qos)?; + self.active.back_mut().ok_or(EIO)?.qos_pending = false; + Err(error) + })?; + } } if first { prepared_kick.prepare_compute_install()?; @@ -2015,10 +2312,12 @@ impl Queue { self.outer_started & ring == 0, )?, )?; - let notify_activation = self.outer_started & ring == 0; self.outer_started |= ring; self.installed = true; self.published_config = Some(identity); + if send_config { + self.config_cache = Some((config, epoch)); + } self.ordinal = ordinal; let (accepted, state) = host.epoch()?; if state != 0 || accepted != epoch { @@ -2043,13 +2342,43 @@ impl Queue { previous.publication = Some((packet.order.sequence, epoch)); } } - if notify_activation { - fence(Ordering::SeqCst); - // Pipe 2 (compute) in bits 1:0, priority class in bits 3:2. - host.notify((0x83 << 48) | 0x02 | u64::from(policy.priority()) << 2)?; - } + // A publication rings its own pipe and priority class, as render + // publication does: pipe 2 (compute) in bits 1:0, priority class in + // bits 3:2. The generic work doorbell left the firmware to find the + // ring itself and delayed dispatch. + // + // A doorbell makes the firmware read the whole work ring of its class, + // and each one it handles while finishing a command delays that + // command's completion and the release of its waiters. A command with + // two or more unretired commands of its queue ahead cannot start soon: + // its doorbell is withheld until at most one precedes it (see + // `due_doorbell`), and any doorbell rung meanwhile on the same ring + // announces its work slot earlier. fence(Ordering::SeqCst); - host.notify((0x83 << 48) | 0x10)?; + let doorbell = (0x83 << 48) | 0x02 | u64::from(policy.priority()) << 2; + if self.active.len >= WITHHOLD_DEPTH + && !self.settling() + && self.withheld_doorbell.is_none_or(|(withheld, _, _)| withheld == doorbell) + { + let written = host.compute_written(policy.priority()); + // Once another doorbell announced every slot the withheld one covered, it + // starts over from this publication: it then waits for this command's own + // predecessors rather than for those of slots already announced. + if self + .withheld_doorbell + .is_some_and(|(_, _, last)| host.compute_announced(policy.priority()) >= last) + { + self.withheld_doorbell = None; + } + let entry = self.withheld_doorbell.get_or_insert((doorbell, published, written)); + entry.2 = written; + } else { + host.notify_compute(policy.priority(), doorbell)?; + if self.withheld_doorbell.is_some_and(|(withheld, _, _)| withheld == doorbell) { + self.withheld_doorbell = None; + } + } + crate::g17::trace::publish(crate::g17::trace::COMPUTE, qid, ordinal, &packet); host.note_submission() })(); if let Err(error) = result { @@ -2155,7 +2484,7 @@ impl Graph { let first = self.graph.dword(ITEMS)?; let second = self.graph.dword(ITEMS + 8)?; let third = self.graph.dword(ITEMS + 16)?; - let producer = self.graph.word(POINTERS + self.pointers.producer)?; + let producer = self.graph.word(CURSORS + self.pointers.producer)?; first.store(address, Ordering::Relaxed); second.store(0, Ordering::Relaxed); third.store(0, Ordering::Relaxed); @@ -2262,21 +2591,57 @@ impl Queue { /// Logical queue/context ownership is unchanged. The existing binding is /// retained through the successor's exact replacement witness. pub(crate) fn share_idle(&mut self) -> Result { - if !self.installed || self.owner.is_none() || !self.retirement_ready - || self.growing || self.quarantined || self.awaiting_witness - || self.exit.is_some() || self.active.len != 0 || self.replays.len != 0 - || self.previous.is_some() || self.pool_phase != PoolPhase::Populated - { + if !self.shareable() { return Ok(false); } self.release_owner()?; Ok(self.reusable()) } + + /// Another owner could take this channel now: idle and released, shareable, or vacant. + pub(crate) fn channel_free(&self) -> bool { + self.reusable() || self.shareable() || self.can_rearm() + } + + /// A pool replacement is in flight for this queue. + pub(crate) fn growing(&self) -> bool { + self.growing + } + + /// The queue's first command has been published: its identity and configuration exist. + pub(crate) fn installed(&self) -> bool { + self.installed + } + + /// A packet of the bound owner passed prepare_job and will publish here. + pub(crate) fn claim(&mut self) { + self.claims = self.claims.saturating_add(1); + } + + pub(crate) fn release_claim(&mut self) { + self.claims = self.claims.saturating_sub(1); + } + + /// An open client's idle channel that [`Self::share_idle`] can hand over. + pub(crate) fn shareable(&self) -> bool { + self.claims == 0 && self.installed && self.owner.is_some() && self.retirement_ready + && !self.growing && !self.quarantined && !self.awaiting_witness + && self.exit.is_none() && self.active.len == 0 && self.replays.len == 0 + && self.previous.is_none() && self.pool_phase == PoolPhase::Populated + && !self.source_hint.has_readers(&self.source_readers) + } + + /// Device publication order of this channel's newest command: lower is + /// idle longer. + pub(crate) fn last_published(&self) -> u64 { + self.last_published + } } impl Queue { pub(crate) fn can_rearm(&self) -> bool { self.pool_phase == PoolPhase::Vacant && (self.reusable() || self.recycled_pending()) + && !self.source_hint.has_readers(&self.source_readers) } } @@ -2318,3 +2683,50 @@ impl Queue { Ok((false, retired)) } } + + +impl Queue { + /// Same immutable singleton identity and current physical epoch. + pub(crate) fn has_input_source(&self, source: &Arc, + status: &Arc, epoch: u64) -> bool + { + !self.quarantined && self.replays.len == 0 && self.active.iter().any(|active| + active.ticket.is_some_and(|ticket| ticket.epoch == epoch) + && Arc::ptr_eq(active.packet.completion.status(), status) + && active.packet.completion.is_input_source(source)) + } + + pub(crate) fn source_dependency(&mut self, source: &crate::g17::sync_input::Source, + status: &Arc, epoch: u64) -> Result> + { + for active in self.active.iter() { + if !active.packet.completion.is_input_source(&source.submission) { continue; } + if !Arc::ptr_eq(active.packet.completion.status(), status) { return Err(EINVAL); } + if self.quarantined || self.replays.len != 0 { return Err(EAGAIN); } + let ticket = active.ticket.ok_or(EAGAIN)?; + if ticket.epoch != epoch { return Err(EAGAIN); } + self.valid_ticket(active, &ticket)?; + if active.packet.cancel_requested() { return Err(ECANCELED); } + source.submission.source_status()?; + let dependency = crate::g17::fw::kick::KickDependency::new(self.qid(), + crate::g17::fw::kick::KickTimestamp::new(ticket.kick).ok_or(EIO)?).ok_or(EIO)?; + let hold = self.source_hint.hold(&self.source_readers)?; + return Ok(Some((active.published, dependency, + self.graph.support.gpu_va() + STAMP as u64, + u32::from(self.qid()), hold))); + } + Ok(None) + } + + /// Preparing a possible wait does not create a firmware consumer. Mark it + /// only once the consumer's first item is visible, under the device mutex. + pub(crate) fn note_input_consumer(&self, source: &crate::g17::sync_input::Source) { + for active in self.active.iter() { + if active.packet.completion.is_input_source(&source.submission) { + active.packet.completion.note_firmware_consumer(); + break; + } + } + } +} diff --git a/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs b/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs index d7386e335e167..719a74787ca70 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute/exit.rs @@ -83,11 +83,12 @@ impl Transaction { } impl Queue { - /// A command with ray traversal scratch is never killed. On Neo, killing a + /// A running command with ray traversal scratch is never killed. On Neo, killing a /// traversal that stopped making progress either crashed the GPU firmware /// when the queue's pool was then released, or stalled every later render /// when it was retained; left alone, the rest of the GPU kept working. Such - /// a queue stays quarantined and retained until its commands retire. + /// a queue stays quarantined and retained until its commands retire. A + /// traversal the firmware reported failed no longer runs (see `fault_pending`). fn active_traversal(&self) -> bool { self.active.iter().any(|active| { matches!( @@ -101,7 +102,7 @@ impl Queue { && self.released && self.active.len != 0 && self.exit.is_none() - && !self.active_traversal() + && (!self.active_traversal() || self.fault_pending()) && (self.quarantined || self.retired_by_teardown) && age >= if self.quarantined { @@ -110,6 +111,10 @@ impl Queue { CANCELLED_DELAY_NS } } + /// A kill of `context` was published through this queue. + pub(crate) fn exit_published(&self, context: &Arc) -> bool { + self.exit.is_some() && self.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) + } pub(crate) fn exit_owner(&self, context: &Arc) -> Option { if !self .active @@ -124,6 +129,23 @@ impl Queue { .and_then(|active| active.ticket.map(|ticket| ticket.owner)) }) } + /// The kill of this queue's failed context waits; returns how long it has waited, and + /// whether this is the first time it crossed `slow`. + pub(crate) fn note_kill_wait(&mut self, now: u64, slow: u64) -> (u64, bool) { + let (since, warned) = self.kill_wait.get_or_insert((now, false)); + let waited = now.saturating_sub(*since); + let crossed = waited >= slow && !*warned; + *warned |= crossed; + (waited, crossed) + } + /// How long the kill of this queue's failed context has been waiting, if it is. + pub(crate) fn kill_waiting(&self, now: u64) -> Option { + self.kill_wait.map(|(since, _)| now.saturating_sub(since)) + } + /// The kill was published: how long it waited, if it did. + pub(crate) fn take_kill_wait(&mut self, now: u64) -> u64 { + self.kill_wait.take().map_or(0, |(since, _)| now.saturating_sub(since)) + } pub(crate) fn record_exit(&mut self, cookie: u64, now: u64) { self.exit = Some(Transaction { cookie, @@ -162,6 +184,7 @@ impl Queue { return false; } exit.phase = Phase::Acknowledged; + crate::g17::trace::teardown(4, cookie, u64::from(self.qid()), 0); true } pub(crate) fn settle_exit( diff --git a/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs b/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs index e938ec566dbac..6b942dcd86a3e 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute/retirement.rs @@ -3,7 +3,7 @@ //! Exact identities and visibility witnesses for a physical compute queue. //! A shared work-state counter can include other engines and cannot prove this queue retired. -use crate::g17::initdata::ComputeCompletion; +use crate::g17::initdata::{CompletionFold, ComputeCompletion}; use kernel::prelude::*; pub(crate) const IN_FLIGHT: usize = 16; @@ -52,6 +52,12 @@ impl Cursors { } } +/// Whether stamp `a` was stored after stamp `b` (both kick stamps, modulo wrap). +pub(crate) const fn stamp_newer(a: u32, b: u32) -> bool { + let delta = a.wrapping_sub(b); + delta != 0 && delta < 0x8000_0000 +} + /// Outstanding kicks span fewer than half the 24-bit stamp sequence. Zero is a valid wrap. pub(crate) const fn stamp_covers(word: u32, kick: u64) -> bool { let delta = word.wrapping_sub((kick as u32).wrapping_shl(8)); @@ -88,13 +94,17 @@ pub(crate) fn record_aged(observed_at: Option, now: u64) -> bool { static STAMPLESS: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); +pub(crate) fn stampless_total() -> u64 { + STAMPLESS.load(core::sync::atomic::Ordering::Relaxed) +} + /// Counts a command that retired on its record because its completion stamp never arrived; /// logs the first and every 64th. pub(crate) fn note_stampless(qid: u8, kick: u64) { let count = STAMPLESS.fetch_add(1, core::sync::atomic::Ordering::Relaxed) + 1; if count == 1 || count % 64 == 0 { pr_info!( - "G17: {} compute commands retired on their completion record after {} ms without a completion stamp (latest: queue {} kick {:#x})\n", + "G17: lifetime total {} compute commands retired on their completion record after {} ms without a completion stamp (latest: queue {} kick {:#x})\n", count, STAMP_FALLBACK_NS / 1_000_000, qid, @@ -106,7 +116,9 @@ pub(crate) fn note_stampless(qid: u8, kick: u64) { /// A zero-duration record fails before visibility polling. It proves execution did not occur; /// ownership still requires a separate retirement witness before mappings can be released. /// The command is complete when its stamp covers it, or when its exact completion record has -/// been present for [`STAMP_FALLBACK_NS`] without one (`record_aged`); in both cases its items +/// been present for [`STAMP_FALLBACK_NS`] without one (`record_aged`) in the same recovery +/// generation. Recovery may consume items without completing their writes, so an old record +/// and consumed items cannot replace the stamp across a restart. In both cases its items /// must be consumed. #[expect( clippy::too_many_arguments, @@ -125,7 +137,9 @@ pub(crate) fn poll( if observed.is_some_and(|record| record.timestamps[0] == record.timestamps[1]) { return Err(ENODATA); } - let completed = stamp_covers(stamp, ticket.kick) || (observed.is_some() && record_aged); + let same_generation = !matches!(recordless, Recordless::CrossedRecovery); + let completed = stamp_covers(stamp, ticket.kick) + || (same_generation && observed.is_some() && record_aged); if !completed || !cursors.consumed(ticket.item_producer, ticket.item_count)? { return Ok(None); } @@ -161,35 +175,122 @@ pub(crate) fn quarantined_retired( && submitted == pending) } +/// Walks a completion ring of `seen.len()` slots from `start` over the records written since +/// the previous walk: `seen` holds each slot's control word as of the last walk that passed it, +/// so the first slot still holding it is where the firmware writes next. `record` folds one +/// slot; the walk also ends once it reports every request found, or at a requested record +/// still being written, which the next walk reads again. Returns where the next walk starts. +pub(crate) fn walk_completions( + start: usize, + seen: &mut [u64], + mut control: impl FnMut(usize) -> Result, + mut record: impl FnMut(usize) -> Result, +) -> Result { + let slots = seen.len(); + if slots == 0 { + return Err(EINVAL); + } + let start = start % slots; + for step in 0..slots { + let slot = (start + step) % slots; + let word = control(slot)?; + if word == seen[slot] { + return Ok(slot); + } + match record(slot)? { + CompletionFold::Incomplete => return Ok(slot), + CompletionFold::Passed => seen[slot] = word, + CompletionFold::Done => { + seen[slot] = word; + return Ok((slot + 1) % slots); + } + } + } + Ok(start) +} + #[derive(Clone, Copy)] struct Request { descriptor: u64, kick: u64, observed: Option, + covered: bool, + next: u16, +} + +const NO_REQUEST: u16 = u16::MAX; +const _: () = assert!(QIDS * IN_FLIGHT < NO_REQUEST as usize); + +fn request_bucket(qid: usize, kick: u64) -> usize { + qid * IN_FLIGHT + kick as usize % IN_FLIGHT } /// Reusable heap scratch for one shared-ring scan. QID requests are contiguous and bounded; /// staging and folding never allocate under the device mutex. pub(crate) struct Batch { requests: KVVec, + /// Per-QID kick buckets; collisions retain the complete identity check. + heads: KVVec, slots: [(usize, usize); QIDS], open: Option, + /// Requests with no observation yet; the scan stops when this reaches zero. + unobserved: usize, + /// Unobserved requests whose queue's completion stamp already covered them when staged. + /// The firmware stores the stamp after the record, so each has a record in the ring. + covered: usize, + /// Per QID, the next kick its queue had not published when the batch was staged + /// ([`NO_FRONTIER`] for a QID without a staged queue). A record at or past it belongs + /// to a command published during the scan, which a later batch stages. + frontiers: [u64; QIDS], } +const NO_FRONTIER: u64 = u64::MAX; +const KICK_MASK: u64 = (1 << 40) - 1; + impl Batch { pub(crate) fn new() -> Result { + let mut heads = KVVec::with_capacity(QIDS * IN_FLIGHT, GFP_KERNEL)?; + for _ in 0..QIDS * IN_FLIGHT { + heads.push_within_capacity(NO_REQUEST).map_err(|_| ENOSPC)?; + } Ok(Self { requests: KVVec::with_capacity(QIDS * IN_FLIGHT, GFP_KERNEL)?, + heads, slots: [(0, 0); QIDS], open: None, + unobserved: 0, + covered: 0, + frontiers: [NO_FRONTIER; QIDS], }) } pub(crate) fn clear(&mut self) { self.requests.clear(); self.slots.fill((0, 0)); self.open = None; + self.unobserved = 0; + self.covered = 0; + self.frontiers.fill(NO_FRONTIER); + } + /// Records the first kick `qid`'s queue had not yet published when this batch was staged. + pub(crate) fn stage_frontier(&mut self, qid: u8, next: u64) -> Result { + *self.frontiers.get_mut(usize::from(qid)).ok_or(EINVAL)? = next & KICK_MASK; + Ok(()) + } + /// A record of `qid` with `kick` names a command published after this batch was staged. + fn published_after_staging(&self, qid: usize, kick: u64) -> bool { + let frontier = self.frontiers[qid]; + frontier != NO_FRONTIER && kick.wrapping_sub(frontier) & KICK_MASK < 1 << 39 } - pub(crate) fn request(&mut self, ticket: &Ticket) -> Result { + /// True once every requested identity has an observation. + pub(crate) fn complete(&self) -> bool { + self.unobserved == 0 + } + /// Some request whose record must already be in the ring is still unobserved. + pub(crate) fn covered_pending(&self) -> bool { + self.covered != 0 + } + /// Stages `ticket`; `covered` when its queue's completion stamp already covers its kick. + pub(crate) fn request(&mut self, ticket: &Ticket, covered: bool) -> Result { if ticket.descriptor == 0 || ticket.kick == 0 { return Err(EINVAL); } @@ -201,65 +302,119 @@ impl Batch { return Err(ENOSPC); } let start = if slot.1 == 0 { + let begin = ticket.qid as usize * IN_FLIGHT; + self.heads.get_mut(begin..begin + IN_FLIGHT).ok_or(EIO)?.fill(NO_REQUEST); self.requests.len() } else { slot.0 }; + let bucket = request_bucket(ticket.qid as usize, ticket.kick); + let next = *self.heads.get(bucket).ok_or(EIO)?; + let index = u16::try_from(self.requests.len()).map_err(|_| ENOSPC)?; self.requests .push_within_capacity(Request { descriptor: ticket.descriptor, kick: ticket.kick, observed: None, + covered, + next, }) .map_err(|_| ENOSPC)?; + *self.heads.get_mut(bucket).ok_or(EIO)? = index; *slot = (start, slot.1 + 1); self.open = Some(ticket.qid); + self.unobserved += 1; + self.covered += usize::from(covered); Ok(()) } - /// Capture the control word once, then read only a requested identity's payload. - /// Records can be overwritten during a scan: incomplete or older identities are skipped. - pub(crate) fn fold(&mut self, record: ComputeCompletion<'_>) -> Result { - let control = record.read(0)?; + /// Capture the control word once. Most slots belong to an older kick of a + /// selected QID; reject those before reading any additional shared words. + pub(crate) fn fold(&mut self, record: ComputeCompletion<'_>) -> Result { + if !self.fold_reader(|word| record.read(word))? { + return Ok(CompletionFold::Incomplete); + } + Ok(self.folded()) + } + + /// Like [`Self::fold`], for a scan that reads every slot regardless of the outcome. + pub(crate) fn fold_any(&mut self, record: ComputeCompletion<'_>) -> Result { + self.fold_reader(|word| record.read(word)).map(|_| ()) + } + + fn folded(&self) -> CompletionFold { + if self.complete() { CompletionFold::Done } else { CompletionFold::Passed } + } + + /// Ok(false) when a pending request's record is still being written. + fn fold_reader(&mut self, mut read: impl FnMut(usize) -> Result) -> Result { + let control = read(0)?; let qid = ((control >> 9) & 0x7f) as usize; - let (start, count) = self.slots[qid]; + let (_, count) = self.slots[qid]; + let kick = + (((control >> 8) & 0x00ff_ffff_ff00) | ((control >> 48) & 0xff)) & 0xff_ffff_ffff; if count == 0 { - return Ok(()); + return Ok(!self.published_after_staging(qid, kick)); } - let descriptor = record.read(6)? & !1; - if descriptor == 0 || record.read(7)? == 0 { - return Ok(()); + let mut index = *self.heads.get(request_bucket(qid, kick)).ok_or(EIO)?; + let mut descriptor = None; + let mut found = None; + for _ in 0..count { + if index == NO_REQUEST { + break; + } + let request = self.requests.get(usize::from(index)).ok_or(EIO)?; + if request.kick == kick { + let value = if let Some(value) = descriptor { + value + } else { + let value = read(6)? & !1; + if value == 0 || read(7)? == 0 { + return Ok(request.observed.is_some()); + } + descriptor = Some(value); + value + }; + if request.descriptor == value { + // Chains run newest first. Retain the original first-match + // rule even if a caller stages the same identity twice. + found = Some(usize::from(index)); + } + } + index = request.next; } - let kick = - (((control >> 8) & 0x00ff_ffff_ff00) | ((control >> 48) & 0xff)) & 0xff_ffff_ffff; - let requests = self.requests.get_mut(start..start + count).ok_or(EIO)?; - let Some(request) = requests - .iter_mut() - .find(|r| r.descriptor == descriptor && r.kick == kick) - else { - return Ok(()); + let Some(index) = found else { + return Ok(!self.published_after_staging(qid, kick)); }; - let timestamps = [record.read(2)? & TIME_MASK, record.read(5)? & TIME_MASK]; - if timestamps[0] == 0 - || timestamps[1] == 0 - || request - .observed - .is_some_and(|old| old.timestamps[1] >= timestamps[1]) + let request = self.requests.get_mut(index).ok_or(EIO)?; + let timestamps = [read(2)? & TIME_MASK, read(5)? & TIME_MASK]; + if timestamps[0] == 0 || timestamps[1] == 0 { + return Ok(request.observed.is_some()); + } + if request + .observed + .is_some_and(|old| old.timestamps[1] >= timestamps[1]) { - return Ok(()); + return Ok(true); + } + if request.observed.is_none() { + self.unobserved -= 1; + self.covered -= usize::from(request.covered); } request.observed = Some(Observation { timestamps }); - Ok(()) + Ok(true) } + + /// A command published after this batch staged its queue's requests was not part of the + /// ring snapshot: it stays unobserved until a later batch. pub(crate) fn take(&mut self, ticket: &Ticket) -> Result> { let &(start, count) = self.slots.get(ticket.qid as usize).ok_or(EINVAL)?; - let request = self + Ok(self .requests .get_mut(start..start + count) .ok_or(EIO)? .iter_mut() .find(|r| r.descriptor == ticket.descriptor && r.kick == ticket.kick) - .ok_or(EIO)?; - Ok(request.observed.take()) + .and_then(|request| request.observed.take())) } } @@ -306,6 +461,15 @@ mod tests { assert!(stamp_covers(0, 0xff_ffff)); } + #[test] + fn a_reported_fault_stamp_is_newer_only_until_stored() { + assert!(stamp_newer(own(KICK), own(KICK - 1))); + assert!(!stamp_newer(own(KICK), own(KICK))); + assert!(!stamp_newer(own(KICK - 1), own(KICK))); + // Across the wrap of the 24-bit kick in the stamp word. + assert!(stamp_newer(own(0), own(0xff_ffff))); + } + #[test] fn a_late_stamp_is_waited_for() { // Under games on J700 the stamp lands up to about 4 ms after the record while the @@ -386,6 +550,30 @@ mod tests { assert_eq!(result, Ok(None)); } + #[test] + fn recovery_cannot_turn_an_aged_record_into_a_release_witness() { + let record = Some(Observation { timestamps: [10, 20] }); + // Recovery can advance the consumer without completing cache maintenance. + // Even a record observed before the restart and aged past the fallback + // must retain its command's mappings until a real stamp covers it. + for stamp in [0, own(KICK - 1)] { + assert_eq!(poll(&ticket(), record, consumed(), stamp, + Recordless::CrossedRecovery, 0, None, true), Ok(None)); + } + // A real stamp restores the release witness; completion's interrupted + // classification independently prevents false successful fences. + for stamp in [own(KICK), own(KICK + 1)] { + assert_eq!(poll(&ticket(), record, consumed(), stamp, + Recordless::CrossedRecovery, 0, None, true), Ok(Some([10, 20]))); + } + let busy = Cursors { producer: 4, consumer: 2, count: 64 }; + assert_eq!(poll(&ticket(), record, busy, own(KICK), + Recordless::CrossedRecovery, 0, None, true), Ok(None)); + // The existing same-generation fallback remains available. + assert_eq!(poll(&ticket(), record, consumed(), own(KICK - 1), + Recordless::Allowed, 0, None, true), Ok(Some([10, 20]))); + } + #[test] fn a_zero_duration_record_fails() { let record = Some(Observation { timestamps: [5, 5] }); @@ -402,3 +590,156 @@ mod tests { assert_eq!(result, Err(ENODATA)); } } + + +#[kunit_tests(asahi_g17_completion_index)] +mod index_tests { + use super::*; + + fn ticket(qid: u8, kick: u64, descriptor: u64) -> Ticket { + Ticket { owner: 1, submission: 1, context_id: 1, qid, descriptor, + work_node: 0x2000, kick, ordinal: 1, item_producer: 4, item_count: 64, epoch: 1 } + } + fn record(ticket: &Ticket, timestamps: [u64; 2]) -> [u64; 8] { + let mut words = [0; 8]; + words[0] = ((ticket.kick & !0xff) << 8) | ((ticket.kick & 0xff) << 48) + | (u64::from(ticket.qid) << 9); + words[2] = timestamps[0]; words[5] = timestamps[1]; + words[6] = ticket.descriptor | 1; words[7] = 1; + words + } + #[test] + fn stale_kick_reads_only_the_control_even_on_a_bucket_collision() { + let mut batch = Batch::new().unwrap(); + batch.request(&ticket(7, 33, 0x1000), false).unwrap(); + let words = record(&ticket(7, 17, 0x1000), [10, 20]); + batch.fold_reader(|word| { + assert_eq!(word, 0); + Ok(words[word]) + }).unwrap(); + assert!(!batch.complete()); + } + #[test] + fn collisions_and_reused_descriptors_keep_full_kick_identity() { + let mut batch = Batch::new().unwrap(); + let first = ticket(7, 33, 0x1000); + let second = ticket(7, 49, 0x1000); + batch.request(&first, false).unwrap(); batch.request(&second, false).unwrap(); + let words = record(&first, [10, 20]); + batch.fold_reader(|word| Ok(words[word])).unwrap(); + assert!(!batch.complete()); + let words = record(&second, [30, 40]); + batch.fold_reader(|word| Ok(words[word])).unwrap(); + assert!(batch.complete()); + assert_eq!(batch.take(&first).unwrap().unwrap().timestamps, [10, 20]); + assert_eq!(batch.take(&second).unwrap().unwrap().timestamps, [30, 40]); + } + #[test] + fn same_kick_still_checks_descriptor_and_zero_duration_is_retained() { + let mut batch = Batch::new().unwrap(); + let first = ticket(7, 33, 0x1000); + let second = ticket(7, 33, 0x2000); + batch.request(&first, false).unwrap(); batch.request(&second, false).unwrap(); + let words = record(&first, [20, 20]); + batch.fold_reader(|word| Ok(words[word])).unwrap(); + assert_eq!(batch.take(&first).unwrap().unwrap().timestamps, [20, 20]); + assert!(batch.take(&second).unwrap().is_none()); + } + #[test] + fn duplicate_identity_keeps_the_first_staged_request() { + let mut batch = Batch::new().unwrap(); + let current = ticket(7, 33, 0x1000); + batch.request(¤t, false).unwrap(); batch.request(¤t, false).unwrap(); + let words = record(¤t, [10, 20]); + batch.fold_reader(|word| Ok(words[word])).unwrap(); + assert_eq!(batch.take(¤t).unwrap().unwrap().timestamps, [10, 20]); + assert_eq!(batch.unobserved, 1); + } + #[test] + fn clear_restarts_buckets_and_later_publication_stays_unobserved() { + let mut batch = Batch::new().unwrap(); + batch.request(&ticket(7, 33, 0x1000), false).unwrap(); batch.clear(); + let current = ticket(7, 49, 0x1000); + batch.request(¤t, false).unwrap(); + let stale = record(&ticket(7, 33, 0x1000), [10, 20]); + batch.fold_reader(|word| Ok(stale[word])).unwrap(); + assert!(!batch.complete()); + assert!(batch.take(&ticket(7, 50, 0x2000)).unwrap().is_none()); + let words = record(¤t, [30, 40]); + batch.fold_reader(|word| Ok(words[word])).unwrap(); + assert!(batch.complete()); + } + #[test] + fn a_requested_record_still_being_written_is_read_again() { + let mut batch = Batch::new().unwrap(); + let current = ticket(7, 33, 0x1000); + batch.request(¤t, true).unwrap(); + let mut words = record(¤t, [10, 20]); + words[7] = 0; + assert!(!batch.fold_reader(|word| Ok(words[word])).unwrap()); + words[7] = 1; + words[5] = 0; + assert!(!batch.fold_reader(|word| Ok(words[word])).unwrap()); + assert!(batch.covered_pending()); + let words = record(¤t, [10, 20]); + assert!(batch.fold_reader(|word| Ok(words[word])).unwrap()); + assert!(batch.complete() && !batch.covered_pending()); + } + #[test] + fn a_command_published_during_the_scan_is_left_for_the_next_batch() { + let mut batch = Batch::new().unwrap(); + batch.stage_frontier(7, 50).unwrap(); + batch.request(&ticket(7, 49, 0x1000), false).unwrap(); + batch.stage_frontier(9, 3).unwrap(); + let late = record(&ticket(7, 50, 0x3000), [30, 40]); + assert!(!batch.fold_reader(|word| Ok(late[word])).unwrap()); + let idle = record(&ticket(9, 3, 0x3000), [30, 40]); + assert!(!batch.fold_reader(|word| Ok(idle[word])).unwrap()); + let earlier = record(&ticket(9, 2, 0x3000), [30, 40]); + assert!(batch.fold_reader(|word| Ok(earlier[word])).unwrap()); + let wrapped = record(&ticket(7, 0xff_ffff_fff0, 0x3000), [30, 40]); + assert!(batch.fold_reader(|word| Ok(wrapped[word])).unwrap()); + } + fn walk(start: usize, seen: &mut [u64], ring: &[u64], outcome: impl Fn(usize) -> CompletionFold) + -> usize + { + walk_completions(start, seen, |slot| Ok(ring[slot]), |slot| Ok(outcome(slot))).unwrap() + } + #[test] + fn a_walk_reads_only_the_records_written_since_the_previous_one() { + let mut seen = [0u64; 8]; + let mut ring = [0u64; 8]; + assert_eq!(walk(0, &mut seen, &ring, |_| CompletionFold::Passed), 0); + ring[0] = 10; ring[1] = 11; ring[2] = 12; + let read = core::cell::Cell::new(0); + assert_eq!(walk(0, &mut seen, &ring, |_| { read.set(read.get() + 1); CompletionFold::Passed }), 3); + assert_eq!(read.get(), 3); + ring[3] = 13; + assert_eq!(walk(3, &mut seen, &ring, |_| CompletionFold::Passed), 4); + // A full lap later the firmware overwrote slots 4..8 and 0..2 with newer records. + for (slot, word) in [(4, 14), (5, 15), (6, 16), (7, 17), (0, 18), (1, 19)] { ring[slot] = word; } + assert_eq!(walk(4, &mut seen, &ring, |_| CompletionFold::Passed), 2); + } + #[test] + fn a_walk_stops_at_a_partial_record_and_after_the_last_request() { + let mut seen = [0u64; 8]; + let ring = [10, 11, 12, 13, 0, 0, 0, 0]; + assert_eq!(walk(0, &mut seen, &ring, |slot| if slot == 2 { CompletionFold::Incomplete } + else { CompletionFold::Passed }), 2); + assert_eq!(seen[..3], [10, 11, 0]); + assert_eq!(walk(2, &mut seen, &ring, |slot| if slot == 2 { CompletionFold::Done } + else { CompletionFold::Passed }), 3); + assert_eq!(walk(3, &mut seen, &ring, |_| CompletionFold::Passed), 4); + } + #[test] + fn an_unrequested_or_stale_record_is_passed() { + let mut batch = Batch::new().unwrap(); + batch.request(&ticket(7, 49, 0x1000), false).unwrap(); + let mut stale = record(&ticket(7, 33, 0x1000), [10, 20]); + stale[7] = 0; + assert!(batch.fold_reader(|word| Ok(stale[word])).unwrap()); + let other = record(&ticket(9, 33, 0x1000), [10, 20]); + assert!(batch.fold_reader(|word| Ok(other[word])).unwrap()); + assert!(!batch.complete() && !batch.covered_pending()); + } +} diff --git a/drivers/gpu/drm/asahi/g17/queue/mod.rs b/drivers/gpu/drm/asahi/g17/queue/mod.rs index 6a9b15789339f..d22c0f617f374 100644 --- a/drivers/gpu/drm/asahi/g17/queue/mod.rs +++ b/drivers/gpu/drm/asahi/g17/queue/mod.rs @@ -10,7 +10,7 @@ use super::{ command::{Parser, UscWindow, Validated}, completion::{Completion, Destinations, PreparedPublication}, context::Context, - fence::{Outputs, Submission}, + fence::Outputs, job::{self, Backend, Engine, Fences, Order, Packet, Scheduler}, validation::Proof, }; @@ -27,7 +27,6 @@ use kernel::{ xarray, }; -const EARLY_DEPTH: usize = 2; static FENCE_KEY: Pin<&LockClassKey> = kernel::static_lock_class!(); pub(crate) mod admission; @@ -96,6 +95,9 @@ pub(crate) struct Queue { proof: Proof, sequence: [u64; 2], frontiers: [KVec<(u64, Fences)>; 2], + /// Finished frontier entries pruned during this ioctl. Their last references + /// can tear a whole completion down; that waits until the ioctl has published. + finished: KVec, failures: [Option<(u64, Fence)>; 2], // Closing the userspace queue releases admission even if firmware still // retains one of its execution contexts or installed physical graphs. @@ -134,6 +136,7 @@ impl Queue { proof: Proof::new(), sequence: [0; 2], frontiers: core::array::from_fn(|_| KVec::new()), + finished: KVec::new(), failures: [None, None], _slot: slot, }) @@ -155,11 +158,20 @@ impl Queue { fn prune_frontier(&mut self, index: usize) { let failure = &mut self.failures[index]; + // Without room the entries are released here, as they always could be. + let mut finished = if self.finished.reserve(self.frontiers[index].len(), GFP_KERNEL).is_ok() { + Some(&mut self.finished) + } else { + None + }; self.frontiers[index].retain(|(sequence, fences)| { - let status = job::fence_status(&fences.completed); + let status = fences.status(); if status == 0 { return true; } + if let Some(finished) = finished.as_mut() { + let _ = finished.push_within_capacity(fences.clone()); + } // One earliest failed ordinal covers every later prefix without // retaining its packet, mappings, or every failed fence. if status < 0 && failure.as_ref().is_none_or(|(first, _)| *sequence < *first) { @@ -179,7 +191,7 @@ impl Queue { command: Validated, order: Order, guard: mmu::VmJobGuard, - aggregate: &Arc, + aggregate: &super::fence::Aggregate, inputs: &[file::SyncItem], destinations: Destinations, ) -> Result { @@ -198,6 +210,28 @@ impl Queue { let Validated::Compute { scratch, .. } = &command else { return Err(EINVAL); }; self.ensure_compute(scratch.pages())?; } + // Only convert inputs when no independent host-prefix wait could keep + // the retained source bookkeeping queued behind an external signal. + if engine == Engine::Compute { + super::trace::inputs(self.context.id(), order.sequence, 0, inputs.len(), None); + } + let sources = if engine == Engine::Compute && !inputs.is_empty() + && !destinations.has_outputs() + && self.frontiers[0].iter().all(|(sequence, fences)| + !Order::contains(order.wait_through[0], *sequence) + || fences.status() > 0) + && self.frontiers[1].iter().all(|(sequence, fences)| + !Order::contains(order.wait_through[1], *sequence) + || fences.ready.raw() != fences.completed.raw() + || fences.status() > 0) + && self.failures.iter().enumerate().all(|(index, failure)| + failure.as_ref().is_none_or(|(sequence, _)| + !Order::contains(order.wait_through[index], *sequence))) + { self.backend.input_sources(inputs) } else { None }; + if engine == Engine::Compute { + super::trace::inputs(self.context.id(), order.sequence, 1, inputs.len(), sources.as_deref()); + } + // Only earlier render ordinals covered by the explicit logical prefix // can delay host publication. These links never become GPU dependencies. let host_predecessors = self.frontiers[index] @@ -216,7 +250,8 @@ impl Queue { destinations, engine == Engine::Compute, host_predecessors.clone().next().is_some(), - if engine == Engine::Render { self.backend.feed() } else { None }, + self.backend.feed(), + sources.clone(), )?; let publication = completion.publication() .map(|target| PreparedPublication::new(target, host_predecessors)) @@ -277,7 +312,7 @@ impl Queue { // Successful completion includes host timestamp publication. Its // ready fence adds no ordering and would prevent shallow inline // publication. Failed and pending producers still need checking. - if job::fence_status(&fences.completed) > 0 { + if fences.status() > 0 { continue; } dependencies.push(fences.ready.clone(), GFP_KERNEL)?; @@ -294,13 +329,23 @@ impl Queue { if job::fence_status(fence) > 0 { continue; } + if sources.as_ref().is_some_and(|sources| sources.contains_input(fence)) { + continue; + } dependencies.push(fence.clone(), GFP_KERNEL)?; checked_inputs.push(fence.clone(), GFP_KERNEL)?; } } - let early = engine == Engine::Render - && dependencies.is_empty() - && self.frontiers[index].len() < EARLY_DEPTH; + if let Some(sources) = &sources { + for ready in sources.publication_waits() { + dependencies.push(ready.clone(), GFP_KERNEL)?; + checked_inputs.push(ready.clone(), GFP_KERNEL)?; + } + } + // Publish from the ioctl whenever nothing has to wait, at any depth. + // The backend falls back to the scheduler behind any backlog or on any + // capacity miss, so submission order is preserved. + let early = dependencies.is_empty(); let packet = Packet::new( id, self.context.clone(), @@ -410,11 +455,17 @@ impl crate::queue::Queue for Queue { return Err(EIO); } let (mut pending, guards) = self.prepare_submission(bytes, objects)?; + if pending.iter().any(|(command, _, _)| matches!(command, Validated::Render { .. })) { + self.context.note_render(); + } if super::timeout::select_submission(pending.iter().map(|(cmd, _, _)| cmd.long_job())) { for (command, _, _) in pending.iter_mut() { command.allow_long_job(); } } let mut guards = guards.into_iter(); - let aggregate = outputs.publish(self.context.clone())?; + let firmware_source = pending.len() == 1 + && matches!(&pending[0].0, Validated::Compute { .. }) + && !pending[0].2.has_outputs(); + let aggregate = outputs.publish(self.context.clone(), firmware_source)?; for (command, order, destinations) in pending { let guard = guards.next().ok_or(EIO)?; self.enqueue( @@ -429,6 +480,8 @@ impl crate::queue::Queue for Queue { } Ok(()) })(); + // Everything this ioctl publishes is visible; release what it pruned. + self.finished.clear(); if let Err(error) = result { self.context.status().record_if_device_loss(error); if [EIO, ETIMEDOUT, ENODEV].contains(&error) || self.context.status().get() != 0 { diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index fb6c77fbdf04f..6fb4e233de8d6 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -7,7 +7,7 @@ pub(crate) use observation::Observed; mod dependencies; mod memory; pub(crate) mod parked; -mod publication; +pub(super) mod publication; pub(crate) mod retirement; mod warm; @@ -46,6 +46,9 @@ pub(crate) struct RenderDependencies { pub(crate) tiling: [KickDependency; 2], pub(crate) tiling_count: usize, pub(crate) compute: Option, + /// Fragment kicks of other pairs' overlapping same-VM renders. + pub(crate) hazards: [KickDependency; crate::g17::hazard::PUBLICATION_BARRIERS], + pub(crate) hazard_count: usize, } impl RenderDependencies { @@ -55,6 +58,8 @@ impl RenderDependencies { tiling: [KickDependency::ZERO; 2], tiling_count: 0, compute: None, + hazards: [KickDependency::ZERO; crate::g17::hazard::PUBLICATION_BARRIERS], + hazard_count: 0, }; pub(crate) fn render(&self) -> &[KickDependency] { &self.render[..self.render_count] @@ -78,12 +83,13 @@ pub(crate) struct PublicationLease { pub(crate) trait Host { fn epoch(&self) -> Result<(u64, u32)>; fn stamps(&self, qids: [u8; 2]) -> Result<[u32; 2]>; - fn qos_publish( + /// Publishes `owners` in order with one pass over the QoS table. + fn qos_publish_pair( &mut self, - owner: qos::Owner, + owners: [qos::Owner; 2], scheduler: u64, policy: crate::g17::fw::queue::Policy, - ) -> Result; + ) -> Result<[qos::Publication; 2]>; fn qos_cancel(&mut self, publication: qos::Publication) -> Result; fn install_pair(&mut self, registration: &RenderRegistration) -> Result; fn publish_qid(&mut self, id: kick::Id) -> Result; @@ -186,6 +192,9 @@ pub(crate) struct Pair { /// An owner change in flight: the previous scheduler page and the ordinal of /// the new owner's first command, until that command leaves the pair. switch: Option<(u64, u64)>, + /// Completion values of the two kicks of the last publication, kept with the configuration + /// cache: what each queue holds once that publication completed. + published_completions: Option<[u64; 2]>, } impl Pair { #[inline(never)] @@ -284,6 +293,7 @@ impl Pair { context_update: true, executed: 0, switch: None, + published_completions: None, })) } pub(crate) fn qids(&self) -> [u8; 2] { @@ -368,6 +378,7 @@ impl Pair { } let ordinal = self.ordinal; ordinal.checked_add(1).ok_or(EOVERFLOW)?; + self.memory.prefetch_descriptors(ordinal); let work = packet.completion.work_state()?; let node = work.node_va()?; let word = work.word(); @@ -484,7 +495,7 @@ impl Pair { struct RenderBarriers { tiling: [KickDependency; 3], tiling_count: usize, - fragment: [KickDependency; 3], + fragment: [KickDependency; 3 + crate::g17::hazard::PUBLICATION_BARRIERS], fragment_count: usize, } @@ -495,7 +506,7 @@ fn render_barriers( ) -> RenderBarriers { let mut tiling_barriers = [KickDependency::ZERO; 3]; let mut tiling_count = 0; - let mut fragment_barriers = [own_tiling; 3]; + let mut fragment_barriers = [own_tiling; 3 + crate::g17::hazard::PUBLICATION_BARRIERS]; let mut fragment_count = 1; for dependency in dependencies.render() { if fragment_stage { @@ -521,6 +532,11 @@ fn render_barriers( tiling_barriers[tiling_count] = compute; tiling_count += 1; } + // Only tile stores overlap: the tiling kick stays concurrent. + for hazard in &dependencies.hazards[..dependencies.hazard_count] { + fragment_barriers[fragment_count] = *hazard; + fragment_count += 1; + } RenderBarriers { tiling: tiling_barriers, tiling_count, @@ -571,12 +587,16 @@ impl Pair { let stamps = host.stamps(qids)?; fence(Ordering::Acquire); let covered = stamps.map(|stamp| retirement::stamp_covers(stamp, ordinal)); - self.memory - .completion_links(ordinal, kicks, payload, !covered[1])?; + self.memory.completion_links( + ordinal, + kicks, + payload, + self.variant.fragment_busy && !covered[1], + )?; let leads = match self.variant.lead_commands { true => { let init_manager = !self.memory.manager.initialized(); - self.memory.write_leads(ordinal, kicks[0], init_manager)? + self.memory.write_leads(ordinal, init_manager)? } false => [false; 2], }; @@ -637,10 +657,26 @@ impl Pair { } else { [None; 2] }; - let present = [ - publication::required(cache[0], configs[0]), - publication::required(cache[1], configs[1]), - ]; + // A covered queue holds the completion value of its newest kick. When that kick is the + // previous publication of this pair in this recovery epoch, the seed would reload the + // value already held, so the configuration record is needed only for its identity. + // Each record costs the firmware time before it starts the kick. + let held = self + .published_completions + .take() + .filter(|_| self.recovery_generation == epoch); + let present = [0, 1].map(|stage| { + let mut config = configs[stage]; + let reload = kicks[stage].prev().completion_value(payload.wrapping_sub(1)); + if config.completion_seed_valid != 0 + && self.installed[stage] + && held.is_some_and(|held| held[stage] == reload) + { + config.completion_seed_valid = 0; + config.completion_seed = 0; + } + publication::required(cache[stage], config) + }); let cursors = self.memory.graph.cursors()?; if cursors[0].write != self.cursors[0] || cursors[1].write != self.cursors[1] { return Err(EBUSY); @@ -790,10 +826,12 @@ impl Pair { tiling_kick.commit(); let mut qos_publications = [None; 2]; let result = (|| { - qos_publications[1] = - Some(host.qos_publish(qos_owners[1], context.scheduler_va(), policy)?); - qos_publications[0] = - Some(host.qos_publish(qos_owners[0], context.scheduler_va(), policy)?); + let [fragment, tiling] = host.qos_publish_pair( + [qos_owners[1], qos_owners[0]], + context.scheduler_va(), + policy, + )?; + qos_publications = [Some(tiling), Some(fragment)]; // A class change happens only at a rebind, after the pair drained. if self.registered != Some(policy.priority()) { host.install_pair(®istration)?; @@ -840,6 +878,9 @@ impl Pair { } }; self.ordinal = next_ordinal; + if let Some(fragment) = KickDependency::new(qids[1], kicks[1]) { + packet.completion.note_render_publication(fragment); + } self.cursors = windows.map(|window| window.target()); for stage in 0..2 { if present[stage] { @@ -848,6 +889,7 @@ impl Pair { } self.recovery_generation = epoch; self.installed = [true; 2]; + self.published_completions = Some(kicks.map(|kick| kick.completion_value(payload))); self.outer_started |= ring; if let Some(previous) = self.previous.as_mut().filter(|p| p.publication.is_none()) { previous.publication = Some(ordinal); @@ -941,6 +983,8 @@ impl Pair { host.publish_retained_pair(priority, fragment_slot, tiling_slot, outer_next)?; self.active.back_mut().ok_or(EIO)?.outer_published = true; host.notify_render(priority)?; + crate::g17::trace::publish(crate::g17::trace::RENDER, self.qids()[1], self.ordinal as u32, + packet); host.note_submission() })(); if let Err(error) = publish { @@ -993,12 +1037,27 @@ impl Pair { }, ] } + /// Bounded first-fault evidence while the recovery owner has firmware + /// halted, before it acknowledges state 1. Never changes retirement. + pub(crate) fn report_recovery(&self, stamps: Result<[u32; 2]>) { + pr_warn!("G17: recovery render slot={} qids={:?} active={} prepared={} quarantined={} stamps={:?} freelist={:?}\n", + self.slot, self.qids(), self.active.len(), self.prepared.is_some(), + self.quarantined, stamps, self.free_list.counters()); + self.memory.manager.report_recovery(); + for active in self.active.iter().take(4) { + if let Validated::Render { pass, .. } = &active.packet.command { + pr_warn!("G17: recovery render geometry ordinal={} width={} height={} layers={} layout={:?}\n", + active.ticket.ordinal, pass.width, pass.height, pass.layers, + target::Layout::new(pass, self.clusters, self.variant.programs)); + } + pr_warn!("G17: recovery render ordinal={} published={} ctx={} seq={} snapshot={:?}\n", + active.ticket.ordinal, active.outer_published, active.packet.context.id(), + active.packet.order.sequence, observation::snapshot(&self.memory, active.ticket.ordinal)); + } + } + fn selected(&self, masks: Option<[u64; 2]>) -> bool { - masks.is_none_or(|masks| { - self.qids() - .into_iter() - .all(|qid| masks[usize::from(qid) / 64] & (1u64 << (qid % 64)) != 0) - }) + masks.is_none_or(|masks| completion_noted(self.qids(), masks)) } /// The oldest published group still running, for cross-VM timeout attribution /// (crate::g17::timeout). A quarantined pair's work holds the GPU until the firmware drains @@ -1073,6 +1132,28 @@ impl Pair { .front() .map(|active| active.packet.completion.spared()) } + /// A recovery record names one of this pair's queues and the completion + /// stamp of the group it interrupted, `(ordinal + 1) << 8` like the pair's + /// stamps. Without a stamp match the oldest published group is marked. + pub(crate) fn note_interrupted(&self, stamp: u32) -> recovery::Interrupted { + let sequence = stamp >> 8; + if let Some(active) = self + .active + .iter() + .filter(|active| active.outer_published) + .find(|active| (active.ticket.ordinal.wrapping_add(1) as u32) & 0xff_ffff == sequence) + { + active.packet.completion.note_interrupted(); + return recovery::Interrupted::Exact; + } + match self.active.iter().find(|active| active.outer_published) { + Some(active) => { + active.packet.completion.note_interrupted(); + recovery::Interrupted::Oldest + } + None => recovery::Interrupted::None, + } + } pub(crate) fn visit_owners(&self, visit: &mut dyn FnMut(&Arc)) { for active in self.active.iter() { visit(active.packet.completion.status()); @@ -1101,6 +1182,7 @@ impl Pair { pub(crate) fn resync(&mut self) { self.configs = [None; 2]; + self.published_completions = None; for kick in &mut self.kicks { kick.clear_parent_after_recovery(); } @@ -1223,6 +1305,9 @@ impl Pair { front.qos_completed = true; front.flist_completed = true; } + crate::g17::recovery::note_retired(); + crate::g17::trace::complete(crate::g17::trace::RENDER, qids[1], ordinal as u32, + &front.packet, [timestamps[0], timestamps[3]]); defer(Deferred::Retired( front.packet.completion.clone(), Ok(timestamps), @@ -1254,6 +1339,9 @@ impl Pair { if self.quarantined || self.active.len() == 0 || self.replay.len() != 0 { return Ok(false); } + if let Some(progressed) = self.settle_interrupted_front(host, defer, grace)? { + return Ok(progressed); + } use crate::g17::replay::{self, Decision, Group}; let qids = self.qids(); let memory = &self.memory; @@ -1264,10 +1352,9 @@ impl Pair { let blamed = active.packet.completion.blamed(); let exhausted = active.packet.completion.replay_count() >= crate::g17::completion::MAX_REPLAYS; - // decide() stops at the first group that is not abandoned, an error included. - let abandoned = innocent - && !blamed - && (|| -> Result { + // decide() stops at the first group that is still live, an error included. + let (abandoned, ran, lost) = if innocent && !blamed { + (|| -> Result<(bool, bool, bool)> { let mut observer = Observer { memory, qids, @@ -1278,20 +1365,33 @@ impl Pair { let stamps = observer.stamps()?; let (_, state) = observer.host.epoch()?; // A torn sample is no proof; a later poll or the timeout looks again. - Ok(matches!( - retirement::abandoned(&active.ticket, snapshot, stamps, state, false), - Ok(true) - )) + let witness = |executed| { + matches!( + retirement::abandoned(&active.ticket, snapshot, stamps, state, executed), + Ok(true) + ) + }; + let unexecuted = witness(false); + // Interrupted work is run again only if it never started, and work that + // another queue waited on never: the wait ended when firmware retired it. + let lost = (active.packet.completion.interrupted() && !snapshot.untouched()) + || active.packet.completion.has_firmware_consumer(); + Ok((unexecuted && !lost, !unexecuted && witness(true), unexecuted && lost)) })() .unwrap_or_else(|error: Error| { failure = Some(error); - false - }); + (false, false, false) + }) + } else { + (false, false, false) + }; Group { innocent, blamed, abandoned, exhausted, + ran, + lost, } }); let decision = replay::decide(groups); @@ -1306,7 +1406,7 @@ impl Pair { front.abandoned_since = None; return Ok(false); } - Decision::Replay => {} + Decision::Replay | Decision::Fail => {} } if grace { let Some(since) = front.abandoned_since else { @@ -1317,6 +1417,9 @@ impl Pair { return Ok(false); } } + if decision == Decision::Fail { + return self.fail_dropped(host, defer); + } // decide() lets only the front be exhausted. An exhausted front fails, and its // successors, which consume its output, fail with it rather than run without it. let mut failing = self.active.front().is_some_and(|front| { @@ -1369,11 +1472,126 @@ impl Pair { self.memory.retire(ordinal)?; self.active.pop().ok_or(EIO)?; packet.reset_publication_for_replay(); + self.published_completions = None; self.replay.push(packet).map_err(|_| EIO)?; } Ok(true) } + /// Firmware retired every group of the pair after a restart, but some were dropped + /// unexecuted and cannot run again in order (see `replay::decide`). Every group fails now, + /// the ones that ran too: they consumed output the dropped ones never wrote. The work is + /// innocent, so it fails without poisoning its VM, with ENODATA, which its owner sees, + /// instead of waiting for the render timeout to fail it with ETIMEDOUT. + fn fail_dropped( + &mut self, + host: &mut impl Host, + defer: &mut impl FnMut(Deferred) -> Result, + ) -> Result { + pr_warn!( + "G17: render slot {}: a GPU recovery dropped groups that cannot run again in order; failing {} groups\n", + self.slot, + self.active.len() + ); + let qos = self.qos_owners(); + let buffer = self.buffer_id(); + let pool = self.free_list.id(); + while let Some(front) = self.active.front_mut() { + if !front.flist_completed { + host.complete_render( + (!front.qos_completed).then_some(qos), + pool, + front.generation, + buffer, + )?; + front.flist_completed = true; + front.qos_completed = true; + } + let ordinal = front.ticket.ordinal; + let completion = front.packet.completion.clone(); + completion.classify(recovery::Class::Spared); + defer(Deferred::Retired(completion, Err(ENODATA)))?; + self.memory.retire(ordinal)?; + self.active.pop().ok_or(EIO)?; + } + Ok(true) + } + + /// A front group a restart interrupted after its tiler started, which firmware + /// then retired without writing its fragment end, may have run partly. It is + /// not replayed (blending or storage writes could apply twice) and fails now, + /// reported to its owner, instead of waiting out the render timeout. Returns + /// None when the front is not such a group. + fn settle_interrupted_front( + &mut self, + host: &mut impl Host, + defer: &mut impl FnMut(Deferred) -> Result, + grace: bool, + ) -> Result> { + use retirement::Memory; + let qids = self.qids(); + let Some(front) = self.active.front() else { + return Ok(None); + }; + if !front.outer_published || front.failed || !front.packet.completion.interrupted() { + return Ok(None); + } + let ordinal = front.ticket.ordinal; + let ticket = front.ticket; + let mut observer = Observer { + memory: &self.memory, + qids, + host: &mut *host, + }; + core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); + let snapshot = observer.snapshot(ordinal)?; + let stamps = observer.stamps()?; + let (_, state) = observer.host.epoch()?; + if snapshot.untouched() + || !matches!( + retirement::abandoned(&ticket, snapshot, stamps, state, false), + Ok(true) + ) + { + return Ok(None); + } + let now = crate::g17::now_ns(); + let front = self.active.front_mut().ok_or(EIO)?; + if grace { + let Some(since) = front.abandoned_since else { + front.abandoned_since = Some(now); + return Ok(Some(false)); + }; + if now.saturating_sub(since) < ABANDON_GRACE_NS { + return Ok(Some(false)); + } + } + if !front.flist_completed { + let qos = self.qos_owners(); + let buffer = self.buffer_id(); + let pool = self.free_list.id(); + let front = self.active.front_mut().ok_or(EIO)?; + host.complete_render( + (!front.qos_completed).then_some(qos), + pool, + front.generation, + buffer, + )?; + front.flist_completed = true; + front.qos_completed = true; + } + pr_warn!( + "G17: render slot {}: group {} was interrupted by a GPU recovery after it started; failing it\n", + self.slot, + ordinal + ); + let packet = self.active.front().ok_or(EIO)?.packet.clone(); + defer(Deferred::Retired(packet.completion.clone(), Err(ECANCELED)))?; + self.memory.retire(ordinal)?; + self.active.pop().ok_or(EIO)?; + Ok(Some(true)) + } + /// The pending replay cannot proceed (closed owner, cancellation or a /// publication refusal). Its successors cannot run without it and fail with /// it. ECANCELED leaves the VM usable; any other error is the ordinary @@ -1415,7 +1633,43 @@ impl Pair { let counters = self.free_list.counters()?; let stamps = host.stamps(self.qids())?; let (_, state) = host.epoch()?; - Ok(retirement::drained(self.ordinal, stamps, counters, state)) + Ok(retirement::drained(self.ordinal, stamps, counters, state) + || (state == 0 && self.executed_unstamped() && self.context_killed())) + } + + /// The firmware acknowledged a kill of this pair's context (scheduler page): it no longer + /// walks that context's work list, so the commands it never unlinked can be released. + fn context_killed(&self) -> bool { + crate::g17::queue::compute::exit::State::read(&self.memory.context).is_ok_and(|state| state.killed != 0) + } + + /// The pair's context may be killed although its commands were not retired: they all ran + /// (`executed_unstamped`), so the kill leaves nothing queued for the firmware to run. + /// Its memory stays until the kill is acknowledged. + pub(crate) fn executed_for_kill(&self, context: &Arc) -> bool { + Arc::ptr_eq(context, &self.memory.context) && self.executed_unstamped() + } + + /// Every command of this quarantined pair ran, its ring entries were consumed and its + /// free-list blocks returned, yet the firmware never stores the stamp of the last one. + /// On Neo this is the render work of a context whose compute command failed: it runs + /// about a second after the failure, but its stamp never arrives and it stays linked in + /// the firmware's job list, so neither the stamp proof nor the abandoned-group proof + /// ever holds and the pair would stay quarantined, keeping its context, until the + /// processors stop. + fn executed_unstamped(&self) -> bool { + self.quarantined + // Only for a closed owner: a live owner would go on publishing into a context + // whose work list still links the unstamped node. + && self.owner.is_none() + && self.prepared.is_none() + && self.active.len() != 0 + && self.free_list.counters().is_ok_and(|counters| counters[0] == counters[1]) + && self.active.iter().all(|active| { + active.outer_published + && observation::snapshot(&self.memory, active.ticket.ordinal) + .is_ok_and(retirement::Snapshot::executed_and_consumed) + }) } /// Account one witnessed tracker, preserving all completion ownership. @@ -1429,6 +1683,10 @@ impl Pair { let qids = self.qids(); let qos = self.qos_owners(); let buffer = self.buffer_id(); + // Commands the firmware never unlinked are released only after it acknowledged the + // kill of their context, on every path (see `firmware_drained`). + let unstamped = self.executed_unstamped(); + let killed = unstamped && self.context_killed(); let Some(active) = self.active.iter_mut().find(|a| !a.flist_completed) else { return Ok(false); }; @@ -1439,6 +1697,12 @@ impl Pair { }; let result = match retirement::poll(&mut active.ticket, &mut observer)? { retirement::Poll::Complete(ticks) => Ok(ticks), + // Its commands ran without a stamp (see `executed_unstamped`): they already failed + // with the quarantine, and the client sees them cancelled. + retirement::Poll::Pending | retirement::Poll::Retry if killed => { + crate::g17::trace::teardown(2, u64::from(self.slot), active.ticket.ordinal, 0); + Err(ECANCELED) + } retirement::Poll::Pending | retirement::Poll::Retry => { core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); let snapshot = observer.snapshot(active.ticket.ordinal)?; @@ -1770,6 +2034,55 @@ impl Pair { } /// The scheduler page these queues last ran a command for, when the next /// publication for `context` changes the queues' owner. + /// The steady state in which a command may publish without the device + /// mutex: the graph is installed and registered at `priority`, it last ran + /// `context`'s scheduler, and nothing is replayed, settled, aliased or + /// switched on it. + pub(crate) fn direct_ready(&self, context: &Context, priority: u8) -> bool { + self.graph_started + && self.installed == [true; 2] + && self.registered == Some(priority) + && priority < 8 + && self.outer_started & (1u8 << priority) != 0 + && self.executed == context.scheduler_va() + && self.switch.is_none() + && self.owner.is_some() + && !self.quarantined + && self.terminal.is_none() + && self.pending_aliases.is_none() + && self.kick_aliases.is_none() + && self.prepared.is_none() + && self.replay.len() == 0 + && self.displaced.is_none() + && self.active.room() + } + /// The render prefix of `packet` on this pair, as the registry collects it + /// for an owner whose only pair this is. + pub(crate) fn own_dependencies(&self, owner: u64, packet: &Arc) -> Result { + let mut dependencies = RenderDependencies::EMPTY; + let mut frontier = crate::g17::dependency::Frontier::new(); + self.collect_dependencies(owner, packet.completion.status(), packet.order.wait_through[0], &mut frontier, false)?; + for dependency in frontier.as_slice() { + *dependencies.render.get_mut(dependencies.render_count).ok_or(EOVERFLOW)? = *dependency; + dependencies.render_count += 1; + } + if dependencies.render_count != 0 + && matches!(&packet.command, Validated::Render { pass, .. } if pass.fragment_barriers) + { + let mut tiling = crate::g17::dependency::Frontier::new(); + self.collect_tiling_dependencies( + owner, + packet.completion.status(), + packet.order.wait_through[0], + &mut tiling, + )?; + for dependency in tiling.as_slice() { + *dependencies.tiling.get_mut(dependencies.tiling_count).ok_or(EOVERFLOW)? = *dependency; + dependencies.tiling_count += 1; + } + } + Ok(dependencies) + } pub(crate) fn owner_switch_from(&self, context: &Context) -> Option { (self.executed != 0 && self.executed != context.scheduler_va()).then_some(self.executed) } @@ -1952,3 +2265,46 @@ impl Pair { Ok(()) } } + +/// Whether `masks`, the stamp notifications collected for a pair whose tiling and fragment +/// queues are `qids`, can mean a finished pass: the fragment queue's bit. A fragment kick starts +/// only after its fragment barrier saw the tiling stamp, and retirement still checks both stamps +/// in memory, so the tiling notification is not needed to retire. +pub(crate) fn completion_noted(qids: [u8; 2], masks: [u64; 2]) -> bool { + let fragment = usize::from(qids[1]); + masks[fragment / 64] & (1u64 << (fragment % 64)) != 0 +} + +#[kunit_tests(asahi_g17_render_completion_noted)] +mod completion_noted_tests { + use super::completion_noted; + + fn bit(qid: u8) -> [u64; 2] { + let mut masks = [0; 2]; + masks[usize::from(qid) / 64] |= 1u64 << (qid % 64); + masks + } + + #[test] + fn fragment_notification_alone_selects() { + assert!(completion_noted([3, 4], bit(4))); + assert!(completion_noted([70, 100], bit(100))); + } + + #[test] + fn tiling_notification_alone_does_not_select() { + assert!(!completion_noted([3, 4], bit(3))); + assert!(!completion_noted([70, 100], bit(70))); + assert!(!completion_noted([3, 4], [0; 2])); + } + + #[test] + fn other_queues_do_not_select() { + assert!(!completion_noted([3, 4], bit(5))); + assert!(!completion_noted([3, 68], bit(4))); + let mut both = bit(3); + both[1] |= bit(68)[1]; + assert!(completion_noted([3, 68], both)); + } +} + diff --git a/drivers/gpu/drm/asahi/g17/queue/render/dependencies.rs b/drivers/gpu/drm/asahi/g17/queue/render/dependencies.rs index 53a0fa6e449ed..ab96b48de3e35 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render/dependencies.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render/dependencies.rs @@ -31,6 +31,7 @@ fn include( prefix: Option, packet: &Arc, word: u64, + compute: bool, ) -> Result { if !Arc::ptr_eq(packet.completion.status(), status) { return Ok(()); @@ -38,12 +39,15 @@ fn include( if !job::Order::contains(prefix, packet.order.sequence) { return Ok(()); } - frontier.include( - prefix, - packet.order.sequence, - job::fence_status(&packet.completion.scheduler_fence()), - word, - ) + let status = job::fence_status(&packet.completion.scheduler_fence()); + frontier.include(prefix, packet.order.sequence, status, word)?; + // A compute consumer's firmware wait ends once firmware retires this group, even unexecuted + // after a restart, so it may then read output the group never wrote. Render consumers of the + // same owner follow the group on its pair, and a replay keeps that order. + if compute && status == 0 { + packet.completion.note_firmware_consumer(); + } + Ok(()) } impl Dependencies { @@ -60,12 +64,13 @@ impl Dependencies { status: &Arc, prefix: Option, frontier: &mut Frontier, + compute: bool, ) -> Result { if self.owner != Some(owner) { return Ok(()); } for entry in self.entries.iter().flatten() { - include(frontier, status, prefix, &entry.packet, entry.word)?; + include(frontier, status, prefix, &entry.packet, entry.word, compute)?; } Ok(()) } @@ -80,7 +85,7 @@ impl Dependencies { return Ok(()); } for entry in self.entries.iter().flatten() { - include(frontier, status, prefix, &entry.packet, entry.ta_word)?; + include(frontier, status, prefix, &entry.packet, entry.ta_word, false)?; } Ok(()) } @@ -112,6 +117,7 @@ impl Pair { status: &Arc, prefix: Option, frontier: &mut Frontier, + compute: bool, ) -> Result { if self.owner != Some(owner) { return Ok(()); @@ -120,7 +126,7 @@ impl Pair { for entry in self.active.iter() { let word = KickDependency::new(qid, entry.kicks[1]).map_or(u64::MAX, KickDependency::word); - include(frontier, status, prefix, &entry.packet, word)?; + include(frontier, status, prefix, &entry.packet, word, compute)?; } Ok(()) } @@ -138,7 +144,7 @@ impl Pair { for entry in self.active.iter() { let word = KickDependency::new(qid, entry.kicks[0]).map_or(u64::MAX, KickDependency::word); - include(frontier, status, prefix, &entry.packet, word)?; + include(frontier, status, prefix, &entry.packet, word, false)?; } Ok(()) } diff --git a/drivers/gpu/drm/asahi/g17/queue/render/memory.rs b/drivers/gpu/drm/asahi/g17/queue/render/memory.rs index 80ee0b871cc82..90a11ca977684 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render/memory.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render/memory.rs @@ -31,8 +31,8 @@ pub(super) const ANNOUNCE: [usize; 2] = [0x17000, 0x1b000]; pub(super) const JOB_LIST: usize = 0xa000; pub(super) const CONFIG: [usize; 2] = [0, 0x4000]; pub(super) const CONFIG_STRIDE: usize = 0x180; -/// The lead command of a stage (InitBM before tiling, the barrier before fragment), after the -/// stage's configuration record in its slot ([`crate::g17::fw::Variant::lead_commands`]). +/// The lead command of a stage (InitBM before tiling), after the stage's configuration record +/// in its slot ([`crate::g17::fw::Variant::lead_commands`]). pub(super) const LEAD: usize = 0xc0; const DEPTH: usize = queues::RENDER_DEPTH as usize; /// Offsets of the priority pair and the policy word in a queue record. @@ -528,15 +528,44 @@ impl Memory { } const _: () = { + // Every unretired render of a pair holds its own command record. + assert!(DEPTH <= RECORDS); assert!(abi::TA_DESCRIPTOR_SIZE * RECORDS <= TA_BYTES); assert!(abi::FRAGMENT_SLOT_SIZE * RECORDS <= FRAGMENT_BYTES); assert!(RECORDS * CONFIG_STRIDE <= 0x4000); assert!(size_of::() <= LEAD); - assert!(LEAD + size_of::() <= CONFIG_STRIDE); assert!(LEAD + size_of::() <= CONFIG_STRIDE); }; impl Memory { + /// Requests the tiling descriptor slot and both queue configuration records of + /// `ordinal` for writing. The firmware accessed them for the command that used them + /// last, so the host no longer holds their lines; requesting them before the rest of + /// the preparation overlaps those transfers with it instead of stalling the writes one + /// line at a time. + pub(super) fn prefetch_descriptors(&self, ordinal: u64) { + let record = ordinal as usize % RECORDS; + let config = size_of::(); + let ranges = [ + (&self.graph.descriptors, Graph::offset(0, ordinal), size_of::()), + (&self.graph.config, CONFIG[0] + record * CONFIG_STRIDE, config), + (&self.graph.config, CONFIG[1] + record * CONFIG_STRIDE, config), + ]; + for (object, offset, size) in ranges { + let Ok(base) = object.pointer(offset, size) else { + continue; + }; + for line in (0..size).step_by(64) { + // SAFETY: A prefetch hint inside the retained mapping; it has no + // architectural effect on memory. + unsafe { + core::arch::asm!("prfm pstl1keep, [{0}]", in(reg) base.add(line), + options(nostack, preserves_flags, readonly)); + } + } + } + } + pub(super) fn write_descriptors( &mut self, args: &abi::RenderArgs<'_>, @@ -571,21 +600,10 @@ impl Memory { (&mut *tiling).write(args)?; let fragment = &mut *fragment; fragment.write(args)?; - self.registers.bytes_mut().fill(0); - for (index, array) in fragment.descriptor.arrays.iter().enumerate() { - for (entry, value) in array - .entries - .get(..(array.size & 0xffff) as usize) - .ok_or(EINVAL)? - .iter() - .enumerate() - { - self.registers.write( - index * size_of::() - + entry * size_of::(), - *value, - )?; - } + // Kick entries bind the arrays embedded in this descriptor + // (register_bindings); the client register page is never read. + for array in &fragment.descriptor.arrays { + array.entries.get(..(array.size & 0xffff) as usize).ok_or(EINVAL)?; } fragment.set_mcache( args.fragment.descriptor_va, @@ -629,14 +647,8 @@ impl Memory { } /// Writes the lead commands of `ordinal` from its linked descriptors: InitBM before the - /// tiling descriptor when `init_manager`, and the fragment barrier. Returns which stages - /// have one. - pub(super) fn write_leads( - &mut self, - ordinal: u64, - tiling_kick: fw::kick::KickTimestamp, - init_manager: bool, - ) -> Result<[bool; 2]> { + /// tiling descriptor when `init_manager`. Returns which stages have one. + pub(super) fn write_leads(&mut self, ordinal: u64, init_manager: bool) -> Result<[bool; 2]> { if !self.owns(ordinal) { return Err(EIO); } @@ -645,23 +657,10 @@ impl Memory { .descriptors .pointer(Graph::offset(0, ordinal), size_of::())? .cast::(); - let fragment = self - .graph - .descriptors - .pointer(Graph::offset(1, ordinal), size_of::())? - .cast::(); - // SAFETY: Both views were checked during preparation and the ordinal still owns them; - // only integer fields are read. - let (ta, fragment_id) = unsafe { (&*ta, (*fragment).descriptor.current_id) }; + // SAFETY: The view was checked during preparation and the ordinal still owns it; only + // integer fields are read. + let ta = unsafe { &*ta }; let slot = ordinal as usize % RECORDS * CONFIG_STRIDE + LEAD; - let barrier = queue::FragmentBarrier::new( - ta.stamp_va, - tiling_kick, - ta.qid, - ta.stamp_value, - fragment_id, - ); - self.graph.config.write(CONFIG[1] + slot, barrier)?; if init_manager { let init = queue::InitBufferManager::new( ta.context_id, @@ -671,7 +670,7 @@ impl Memory { ); self.graph.config.write(CONFIG[0] + slot, init)?; } - Ok([init_manager, true]) + Ok([init_manager, false]) } } diff --git a/drivers/gpu/drm/asahi/g17/queue/render/parked.rs b/drivers/gpu/drm/asahi/g17/queue/render/parked.rs index 209ce0b8176ec..81d4306eda28c 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render/parked.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render/parked.rs @@ -252,6 +252,7 @@ impl ParkedPair { context_update: true, executed: self.executed, switch: None, + published_completions: None, })) } } diff --git a/drivers/gpu/drm/asahi/g17/queue/render/publication.rs b/drivers/gpu/drm/asahi/g17/queue/render/publication.rs index 9f4cb05b6bede..c1985c72f6ec1 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render/publication.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render/publication.rs @@ -19,7 +19,7 @@ fn identity(mut config: QueueConfig) -> QueueConfig { config.qos_update = 0; config } -pub(super) fn required(previous: Option, config: QueueConfig) -> bool { +pub(in crate::g17::queue) fn required(previous: Option, config: QueueConfig) -> bool { config.install != 0 || config.context_update != 0 || config.completion_seed_valid != 0 diff --git a/drivers/gpu/drm/asahi/g17/queue/render/retirement.rs b/drivers/gpu/drm/asahi/g17/queue/render/retirement.rs index 24aeed6bcd0ec..7996970ee369f 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render/retirement.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render/retirement.rs @@ -122,6 +122,17 @@ impl Snapshot { fn executed(self) -> bool { self.pass[0] != 0 && self.pass[1] != 0 } + + /// Neither the tiler start nor the fragment end was written: nothing ran. + pub(crate) fn untouched(self) -> bool { + self.pass == [0, 0] + } + + /// The command ran (tiler start and fragment end written) and the firmware consumed both + /// queues' ring entries. + pub(crate) fn executed_and_consumed(self) -> bool { + self.executed() && self.cursors.iter().all(|cursors| cursors.done == cursors.write) + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -350,3 +361,25 @@ impl Tracker { .filter_map(Option::as_mut) } } + +#[kunit_tests(asahi_g17_render_retirement)] +mod executed_tests { + use super::*; + + fn snapshot(done: u32, write: u32, pass: [u64; 2]) -> Snapshot { + Snapshot { + cursors: [Cursors { done, read: done, write }; 2], + job_list_empty: false, + pass, + } + } + + #[test] + fn executed_and_consumed_needs_both_passes_and_consumed_rings() { + assert!(snapshot(3, 3, [1, 2]).executed_and_consumed()); + assert!(!snapshot(3, 3, [0, 0]).executed_and_consumed()); + assert!(!snapshot(3, 3, [1, 0]).executed_and_consumed()); + assert!(!snapshot(3, 3, [0, 2]).executed_and_consumed()); + assert!(!snapshot(2, 3, [1, 2]).executed_and_consumed()); + } +} diff --git a/drivers/gpu/drm/asahi/g17/recovery.rs b/drivers/gpu/drm/asahi/g17/recovery.rs index 89f8d33bdb80c..dcd8d59e4c412 100644 --- a/drivers/gpu/drm/asahi/g17/recovery.rs +++ b/drivers/gpu/drm/asahi/g17/recovery.rs @@ -10,6 +10,7 @@ mod attribution; mod slot; mod safety; +pub(crate) use safety::{note_retired, since_retired}; pub(crate) use attribution::Sources; use attribution::Attribution; @@ -24,20 +25,34 @@ const QIDS: u8 = 128; const GUILTY: u8 = 1 << 1; const CANCELLED: u8 = 1 << 2; const MAX_GUILTY_VMS: usize = 8; -const RECOVERY_POLLS: usize = 500; +/// One-millisecond ticks per handshake phase. The firmware finishes a restart only after the +/// work it interrupted has drained; with several clients running long ray-traversal jobs that +/// takes seconds, and giving up loses every client's device. Ten seconds matches the longest +/// execution allowance (`timeout::POLL_MS` plus renewals). +const RECOVERY_POLLS: usize = 10_000; -/// Per-QID classifications from the complete firmware recovery table. +/// Records in the firmware recovery table. +pub(super) const RECOVERY_RECORDS: usize = 256; + +/// Per-QID classifications from the firmware recovery table. /// Multiple records for a queue contribute all of their classification bits. #[derive(Default)] pub(super) struct Entries { guilty: u128, cancelled: u128, + /// Valid records, each naming one command that was in flight at the restart. + pub(super) listed: u32, + /// Listed commands found by their exact stamp, and by the oldest-work fallback. + pub(super) exact: u32, + pub(super) fallback: u32, } impl Entries { - pub(super) fn include(&mut self, flags: u32) { + /// Returns false at the first invalid record: the table is a packed list and + /// records after it are left over from an earlier restart. + pub(super) fn include(&mut self, flags: u32) -> bool { if flags & 1 == 0 { - return; + return false; } let qid = (flags >> 4) & 0x7f; let bit = 1u128 << qid; @@ -47,9 +62,22 @@ impl Entries { if flags & u32::from(CANCELLED) != 0 { self.cancelled |= bit; } + self.listed += 1; + true } } +/// How a listed recovery record was matched to retained work. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(super) enum Interrupted { + /// No retained command of the queue. + None, + /// The command whose completion stamp equals the record's. + Exact, + /// No stamp matched; the queue's oldest unretired command was marked instead. + Oldest, +} + /// How an error completion affects its VM's sticky status and other jobs. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(super) enum Class { @@ -71,10 +99,19 @@ pub(super) trait Queues { fn visit_owners(&self, qid: u8, visit: &mut dyn FnMut(&Arc)); /// Classify every in-flight completion; return QIDs of render slots with spared work. fn classify_work(&mut self, class: &dyn Fn(&Arc) -> Class) -> u128; + /// Mark the command a recovery record lists as interrupted. It may have + /// partly executed, so it must never report success; see + /// `Completion::note_interrupted`. + fn note_interrupted(&mut self, qid: u8, stamp: u32) -> Interrupted; + /// Optional diagnostics only; absence cannot alter attribution or release. + fn report_blamed_render(&self, _qid: u8) {} fn settle_blamed_render(&mut self, qid: u8, error: Error); fn settle_cancelled_renders(&mut self, qids: u128); fn rescan_compute(&mut self) -> Result; fn release_retired_compute(&mut self); + /// Ring every compute doorbell still withheld: a restart is a departure from ordinary + /// retirement, after which the firmware must see every published command. + fn announce_withheld(&mut self); } /// Live shared-memory operations. The handshake word uses aligned 32-bit accesses; transitions @@ -85,7 +122,9 @@ pub(super) trait Memory { fn fault_sources(&self) -> Result; fn transition(&self, expected: u32, next: u32) -> Result; fn clear_timestamps(&self) -> Result; - fn entries(&self) -> Result; + /// One recovery record's flags and completion stamp, read while firmware + /// is halted in state 3. + fn entry(&self, index: usize) -> Result<(u32, u32)>; /// One millisecond wait; this is polled because the firmware sends no state-3 notification. fn wait_tick(&self); } @@ -119,7 +158,9 @@ impl State { restarts: safety::Storm::new(), } } - pub(super) fn timeout_limit(&mut self, now: u64) -> bool { self.timeouts.note(now) } + pub(super) fn timeout_limit(&mut self, now: u64) -> bool { + self.timeouts.note_unless_progress(now, safety::retired()) + } pub(super) fn restart_limit(&mut self, now: u64) -> bool { self.restarts.note(now) } pub(super) fn generation(&self) -> u64 { self.accepted @@ -192,6 +233,8 @@ pub(super) fn service( } } if !ready { + pr_warn!("G17: recovery gen {} did not reach state 1 (state {:?})\n", + request.generation, host.recovery_state()); return Err(ETIMEDOUT); } // Recheck ownership after the off-CPU waits, before any handshake mutation. @@ -203,6 +246,15 @@ pub(super) fn service( host.clear_timestamps()?; } let mut sources = host.fault_sources()?; + sources.report(request.generation, super::queue::compute::retirement::stampless_total()); + if let Some(qid) = request.blamed { + host.report_blamed_render(qid); + } + // The keys, monitor and pass-start evidence must describe the same halted + // instant. Firmware may update pass timestamps as soon as state 3 is + // acknowledged; sampling them during classification can blame a bystander. + let attribution = sources.attribute(request.blamed, + |qid, stamp| host.render_pass_started(qid, stamp)); let target_error = host.publish_recovery_targets(); host.transition(1, 2)?; let mut recovered = false; @@ -217,17 +269,31 @@ pub(super) fn service( host.wait_tick(); } if !recovered { + pr_warn!("G17: recovery gen {} did not reach state 3 (state {:?})\n", + request.generation, host.recovery_state()); return Err(ETIMEDOUT); } // A failed table read leaves only the explicit blamed QID as evidence of guilt. - let entries = host.entries().unwrap_or_default(); + // Every listed command was in flight when firmware stopped. Firmware may + // still retire it with stamps and records that look like success, so it is + // marked before firmware resumes and never reports success. + let entries = read_entries(host); host.transition(3, 0)?; if host.recovery_state()? != 0 { return Err(EIO); } host.recovery_mut().pending = None; - let (cancelled, blame) = classify(host, entries, request.blamed, &mut sources); + let (listed, exact, fallback) = (entries.listed, entries.exact, entries.fallback); + let (cancelled, blame) = classify(host, entries, request.blamed, attribution); + if listed != 0 { + pr_warn!( + "G17: GPU recovery: {} commands were in flight ({} matched by stamp, {} by queue); they fail instead of completing\n", + listed, + exact, + fallback + ); + } if let Some(qid) = request.blamed { pr_warn!( "G17: GPU recovery (reason {:?}): firmware blamed queue {}, {}\n", @@ -244,19 +310,39 @@ pub(super) fn service( } let scan_error = host.rescan_compute().err(); host.release_retired_compute(); + host.announce_withheld(); Ok(ServiceResult { scan_error, target_error }) } +/// Walks the packed recovery table up to its first invalid record and marks +/// every listed command interrupted. A failed read keeps what was read so far. +fn read_entries(host: &mut H) -> Entries { + let mut entries = Entries::default(); + for index in 0..RECOVERY_RECORDS { + let Ok((flags, stamp)) = host.entry(index) else { + break; + }; + if !entries.include(flags) { + break; + } + match host.note_interrupted(((flags >> 4) & 0x7f) as u8, stamp) { + Interrupted::Exact => entries.exact += 1, + Interrupted::Oldest => entries.fallback += 1, + Interrupted::None => {} + } + } + entries +} + /// Resolves guilty QIDs before changing any completion's classification. Unknown ownership or /// too many distinct guilty VMs leaves all prior classifications intact. The oldest spared work /// of a QID makes later blame collateral to its previous recovery, rather than new VM guilt. /// Also returns whether the firmware's blamed QID is still guilty after attribution: a blamed /// render that had not started while others had (or with no culprit at all) is innocent, and its /// work is spared like that of every other innocent VM instead of failing with EIO. -fn classify(queues: &mut Q, entries: Entries, blamed: Option, sources: &mut Sources) -> (u128, bool) { +fn classify(queues: &mut Q, entries: Entries, blamed: Option, attribution: Attribution) -> (u128, bool) { let mut guilty_qids = entries.guilty | blamed.map_or(0, |qid| 1u128 << qid); let cancelled = entries.cancelled; - let attribution = sources.attribute(blamed, |qid, stamp| queues.render_pass_started(qid, stamp)); match attribution { Attribution::Firmware => {} Attribution::Queues(qids) => guilty_qids = qids, @@ -316,3 +402,102 @@ fn classify(queues: &mut Q, entries: Entries, blamed: Option, sou }); (cancelled & !(guilty_qids & !collateral) & spared, blame) } + + +#[kernel::macros::kunit_tests(asahi_g17_recovery_snapshot)] +mod tests { + use super::*; + use core::cell::Cell; + + struct Halted { + recovery: State, + state: Cell, + reads: Cell, + known_keys: bool, + blamed: bool, + } + + impl Halted { + fn new(known_keys: bool) -> Self { + let mut recovery = State::new(); + assert!(recovery.request(0, 38).unwrap()); + Self { recovery, state: Cell::new(1), reads: Cell::new(0), + known_keys, blamed: false } + } + } + + impl Memory for Halted { + fn recovery_state(&self) -> Result { Ok(self.state.get()) } + fn host_recovery(&self) -> Result { Ok(0) } + fn fault_sources(&self) -> Result { + assert_eq!(self.state.get(), 1); + let mut sources = Sources::sample(3, |slot| { + Some((if self.known_keys { ((38 + slot as u64) << 40) | 1 } else { 0 }, 0)) + }); + sources.reason = Some(2); + Ok(sources) + } + fn transition(&self, expected: u32, next: u32) -> Result { + assert_eq!(self.state.get(), expected); + self.state.set(next); + Ok(()) + } + fn clear_timestamps(&self) -> Result { panic!("active render scheduler") } + fn entry(&self, _index: usize) -> Result<(u32, u32)> { + assert_eq!(self.state.get(), 3); + Ok((0, 0)) + } + fn wait_tick(&self) { + assert_eq!(self.state.get(), 2); + self.state.set(3); + } + } + + impl Queues for Halted { + fn render_scheduler_active(&self) -> bool { true } + fn render_pass_started(&self, qid: u8, _stamp: u64) -> Option { + // This is the regression oracle: state 0/2/3 is too late. A + // resumed peer may have started after the fault we are attributing. + assert_eq!(self.state.get(), 1); + self.reads.set(self.reads.get() + 1); + Some(qid == 39) + } + fn publish_recovery_targets(&self) -> Option { None } + fn oldest_spared(&self, _qid: u8) -> Option { None } + fn visit_owners(&self, _qid: u8, _visit: &mut dyn FnMut(&Arc)) {} + fn classify_work(&mut self, _class: &dyn Fn(&Arc) -> Class) -> u128 { 0 } + fn note_interrupted(&mut self, _qid: u8, _stamp: u32) -> Interrupted { Interrupted::None } + fn settle_blamed_render(&mut self, qid: u8, _error: Error) { + assert_eq!(qid, 38); + self.blamed = true; + } + fn settle_cancelled_renders(&mut self, _qids: u128) {} + fn rescan_compute(&mut self) -> Result { Ok(()) } + fn release_retired_compute(&mut self) {} + fn announce_withheld(&mut self) {} + } + + impl Host for Halted { + fn recovery(&self) -> &State { &self.recovery } + fn recovery_mut(&mut self) -> &mut State { &mut self.recovery } + } + + #[test] + fn pass_evidence_is_frozen_before_the_first_acknowledgement() { + let mut host = Halted::new(true); + service(&mut host, || false).unwrap(); + assert_eq!(host.reads.get(), 2); + assert_eq!(host.state.get(), 0); + assert_eq!(host.recovery.generation(), 1); + assert!(!host.blamed); + } + + #[test] + fn unknown_25g83_keys_still_preserve_firmware_blame() { + let mut host = Halted::new(false); + service(&mut host, || false).unwrap(); + assert_eq!(host.reads.get(), 0); + assert!(host.blamed); + assert_eq!(host.state.get(), 0); + } +} diff --git a/drivers/gpu/drm/asahi/g17/recovery/attribution.rs b/drivers/gpu/drm/asahi/g17/recovery/attribution.rs index 1755484219187..38ad8612a0b99 100644 --- a/drivers/gpu/drm/asahi/g17/recovery/attribution.rs +++ b/drivers/gpu/drm/asahi/g17/recovery/attribution.rs @@ -30,6 +30,23 @@ pub(super) enum Attribution { } impl Sources { + /// Bounded evidence from the existing halted-firmware snapshot. No guessed + /// MMIO/key addresses or additional firmware reads are used for diagnostics. + pub(super) fn report(&self, generation: u64, stampless_total: u64) { + kernel::pr_warn!( + "G17: recovery snapshot gen {} reason {:?} slot_mask {:#x} sampled {} known_keys {}; stampless lifetime total {} before restart\n", + generation, self.reason, self.mask, self.count, + self.records[..self.count].iter().filter(|source| source.qid.is_some()).count(), + stampless_total + ); + for source in &self.records[..self.count] { + kernel::pr_warn!( + "G17: recovery slot {} queue {:?} kick {:#x} monitor {:#x}\n", + source.slot, source.qid, source.stamp, source.monitor + ); + } + } + /// Firmware is halted for every selected key and progress-record read. /// Failed reads stay unknown; incomplete evidence cannot override firmware blame. pub(crate) fn sample(mask: u32, mut read: impl FnMut(usize) -> Option<(u64, u32)>) -> Self { @@ -64,7 +81,10 @@ impl Sources { blamed: Option, mut started: impl FnMut(u8, u64) -> Option, ) -> Attribution { - if self.reason.is_none() || self.count as u32 != self.mask.count_ones() { + if self.reason.is_none() + || self.count as u32 != self.mask.count_ones() + || self.records[..self.count].iter().any(|source| source.qid.is_none()) + { return Attribution::Firmware; } for source in &mut self.records[..self.count] { @@ -103,3 +123,54 @@ impl Sources { } } } + +#[kernel::macros::kunit_tests(asahi_g17_recovery_sources)] +mod tests { + use super::*; + + #[test] + fn progress_without_keys_preserves_firmware_blame() { + let mut reads = 0; + let mut sources = Sources::sample(0b101, |slot| { + reads += 1; + Some((0, (slot + 1) as u32)) + }); + sources.reason = Some(2); + assert_eq!(reads, 2); + assert_eq!(sources.count, 2); + assert_eq!(sources.records[1].slot, 2); + assert_eq!(sources.records[1].monitor, 3); + assert!(sources.records[..sources.count].iter().all(|source| source.qid.is_none())); + assert_eq!(sources.attribute(Some(38), |_, _| panic!("unknown key")), + Attribution::Firmware); + } + + #[test] + fn incomplete_progress_cannot_clear_firmware_blame() { + let mut sources = Sources::sample(0b11, |slot| { + (slot == 0).then_some(((38 << 40) | 1, 0)) + }); + sources.reason = Some(2); + assert_eq!(sources.attribute(Some(38), |_, _| Some(false)), Attribution::Firmware); + } + + #[test] + fn a_missing_peer_key_cannot_clear_a_known_queues_blame() { + let mut sources = Sources::sample(0b11, |slot| { + Some((if slot == 0 { (38 << 40) | 1 } else { 0 }, 0)) + }); + sources.reason = Some(2); + assert_eq!(sources.count, 2); + assert_eq!(sources.attribute(Some(38), |_, _| Some(false)), Attribution::Firmware); + } + + #[test] + fn page_fault_keeps_firmware_blame_with_complete_keys() { + let mut sources = Sources::sample(0b11, |slot| { + Some(((((38 + slot) as u64) << 40) | 1, 0)) + }); + sources.reason = Some(PAGE_FAULT); + assert_eq!(sources.attribute(Some(38), |qid, _| Some(qid == 39)), + Attribution::Firmware); + } +} diff --git a/drivers/gpu/drm/asahi/g17/recovery/safety.rs b/drivers/gpu/drm/asahi/g17/recovery/safety.rs index c30e45e22579a..082416f0cb9a6 100644 --- a/drivers/gpu/drm/asahi/g17/recovery/safety.rs +++ b/drivers/gpu/drm/asahi/g17/recovery/safety.rs @@ -2,17 +2,52 @@ //! Fail closed on repeated watchdog/restart traffic, even across client loss. +use core::sync::atomic::{AtomicU64, Ordering}; + const WINDOW_NS: u64 = 30_000_000_000; const LIMIT: u8 = 8; +/// Commands retired successfully. A watchdog storm means the GPU stopped making progress; +/// timeouts of overloaded clients while other work keeps retiring are not one. +static RETIRED: AtomicU64 = AtomicU64::new(0); +/// [`crate::g17::now_ns`] of the latest successful retirement, zero before the first. +static LAST_RETIRED_NS: AtomicU64 = AtomicU64::new(0); + +/// Called for every command that retires without error. +pub(crate) fn note_retired() { + RETIRED.fetch_add(1, Ordering::Relaxed); + LAST_RETIRED_NS.store(crate::g17::now_ns(), Ordering::Relaxed); +} + +pub(super) fn retired() -> u64 { + RETIRED.load(Ordering::Relaxed) +} + +/// Nanoseconds since any command last retired successfully, or `None` before the first. +pub(crate) fn since_retired(now: u64) -> Option { + match LAST_RETIRED_NS.load(Ordering::Relaxed) { + 0 => None, + last => Some(now.saturating_sub(last)), + } +} + pub(super) struct Storm { start: Option, count: u8, stopped: bool, + progress: u64, } impl Storm { pub(super) const fn new() -> Self { - Self { start: None, count: 0, stopped: false } + Self { start: None, count: 0, stopped: false, progress: 0 } + } + /// Like [`Self::note`], but work retired since the previous event starts a new window. + pub(super) fn note_unless_progress(&mut self, now: u64, progress: u64) -> bool { + if !self.stopped && progress != self.progress { + self.progress = progress; + self.start = None; + } + self.note(now) } pub(super) fn note(&mut self, now: u64) -> bool { if self.stopped { return true; } @@ -74,6 +109,17 @@ mod tests { for _ in 0..1024 { assert!(storm.note(u64::MAX)); } } #[test] + fn recovery_table_ends_at_its_first_invalid_record() { + use crate::g17::recovery::Entries; + let mut entries = Entries::default(); + assert!(entries.include(1 | (12 << 4))); + assert!(entries.include(1 | 2 | (17 << 4))); + assert!(!entries.include(0)); + assert_eq!(entries.listed, 2); + assert_eq!(entries.guilty, 1u128 << 17); + assert_eq!(entries.cancelled, 0); + } + #[test] fn replay_dependency_checks_never_wait_for_event_service() { use crate::g17::dependency::replay_status; assert_eq!(replay_status(0), Err(EBUSY)); diff --git a/drivers/gpu/drm/asahi/g17/replay.rs b/drivers/gpu/drm/asahi/g17/replay.rs index 94daa5ec0dfeb..7668871376bad 100644 --- a/drivers/gpu/drm/asahi/g17/replay.rs +++ b/drivers/gpu/drm/asahi/g17/replay.rs @@ -16,6 +16,12 @@ pub(crate) struct Group { pub(crate) abandoned: bool, /// Already republished the maximum number of times. pub(crate) exhausted: bool, + /// Retired by firmware after executing in full. + pub(crate) ran: bool, + /// Retired by firmware unexecuted, but never to run again: a recovery interrupted it after + /// its tiler started, so it may have run partly, or a command of another queue waited on it, + /// and that wait ended when firmware retired the group, before its output existed. + pub(crate) lost: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -25,26 +31,54 @@ pub(crate) enum Decision { Wait, /// Never replay this set; the render timeout settles it. Refuse, + /// Firmware has retired every group, but a dropped group cannot run again in order: a later + /// group already ran, or the dropped group had started or had a consumer. Fail the set now. + Fail, } /// Groups are read in order and the first one that decides stops the walk, so a caller can /// observe the abandonment lazily. An exhausted front is replayed: the replay fails it, and its /// successors, which consume its output, with it. An exhausted successor would break the order. +/// +/// After a restart the firmware retires the groups it had started on a pair without running +/// them, while groups it had not yet started run as usual. Once such a later group has run, a +/// replay of the dropped ones would run them after it; a group that had started cannot run +/// twice; and a group another queue waited on may already have been read before it ran. None of +/// these is replayed: the whole set fails at once instead of waiting for the render timeout. A +/// group that ran ahead of every dropped one is left to in-order retirement. pub(crate) fn decide(groups: impl Iterator) -> Decision { let mut front_exhausted = None; let mut successor_exhausted = false; + let mut dropped = false; + let mut unordered = false; for group in groups { if !group.innocent || group.blamed { return Decision::Refuse; } + if group.ran { + if !dropped { + return Decision::Wait; + } + unordered = true; + continue; + } + if group.lost { + dropped = true; + unordered = true; + continue; + } if !group.abandoned { return Decision::Wait; } + dropped = true; match front_exhausted { None => front_exhausted = Some(group.exhausted), Some(_) => successor_exhausted |= group.exhausted, } } + if unordered { + return Decision::Fail; + } match front_exhausted { Some(true) => Decision::Replay, Some(false) if !successor_exhausted => Decision::Replay, @@ -56,10 +90,13 @@ pub(crate) fn decide(groups: impl Iterator) -> Decision { mod tests { use super::*; fn group(innocent: bool, abandoned: bool, exhausted: bool) -> Group { - Group { innocent, blamed: false, abandoned, exhausted } + Group { innocent, blamed: false, abandoned, exhausted, ran: false, lost: false } } - const OK: Group = Group { innocent: true, blamed: false, abandoned: true, exhausted: false }; - const BLAMED: Group = Group { innocent: true, blamed: true, abandoned: true, exhausted: false }; + const OK: Group = + Group { innocent: true, blamed: false, abandoned: true, exhausted: false, ran: false, lost: false }; + const BLAMED: Group = Group { blamed: true, ..OK }; + const RAN: Group = Group { abandoned: false, ran: true, ..OK }; + const LOST: Group = Group { abandoned: false, lost: true, ..OK }; #[test] fn every_group_abandoned_and_innocent_is_replayed() { assert_eq!(decide([OK].into_iter()), Decision::Replay); @@ -106,4 +143,28 @@ mod tests { Decision::Replay ); } + #[test] + fn a_group_that_ran_after_dropped_ones_fails_the_set() { + // Seen after restarts: the started groups were dropped, the queued ones ran. + assert_eq!(decide([OK, OK, RAN, RAN].into_iter()), Decision::Fail); + assert_eq!(decide([OK, RAN, OK].into_iter()), Decision::Fail); + // Also with an exhausted front, which a replay would only fail later. + assert_eq!(decide([group(true, true, true), RAN].into_iter()), Decision::Fail); + } + #[test] + fn a_group_that_ran_first_is_left_to_retirement() { + assert_eq!(decide([RAN].into_iter()), Decision::Wait); + assert_eq!(decide([RAN, OK].into_iter()), Decision::Wait); + } + #[test] + fn a_started_dropped_group_fails_the_set() { + assert_eq!(decide([OK, LOST].into_iter()), Decision::Fail); + assert_eq!(decide([LOST, OK].into_iter()), Decision::Fail); + } + #[test] + fn failing_still_waits_for_live_groups_and_refuses_guilty_ones() { + assert_eq!(decide([OK, RAN, group(true, false, false)].into_iter()), Decision::Wait); + assert_eq!(decide([OK, RAN, group(false, true, false)].into_iter()), Decision::Refuse); + assert_eq!(decide([LOST, BLAMED].into_iter()), Decision::Refuse); + } } diff --git a/drivers/gpu/drm/asahi/g17/reports.rs b/drivers/gpu/drm/asahi/g17/reports.rs new file mode 100644 index 0000000000000..b67512f7eec6b --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/reports.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Preserve the firmware's statistics without assigning unqualified units or +//! treating another firmware's pstate report as an achieved GPU frequency. +//! Writers and readers hold the device mutex; reads never power the GPU up. + +use super::{fw::channels::{EVENT_WORDS, STATISTICS_TYPES}, Role}; +use kernel::prelude::*; + +#[derive(Clone, Copy, Default)] +struct Sample { + count: u64, + host_ns: u64, + words: [u32; EVENT_WORDS], +} + +pub(super) struct Reports { + samples: KVec, +} + +impl Reports { + pub(super) fn new() -> Result { + // Allocate before firmware starts; receive and debugfs paths allocate nothing. + let mut samples = KVec::with_capacity(64, GFP_KERNEL)?; + for _ in 0..64 { + samples.push(Sample::default(), GFP_KERNEL)?; + } + Ok(Self { samples }) + } + + pub(super) fn record(&mut self, role: Role, words: [u32; EVENT_WORDS], host_ns: u64) { + let tag = words[0]; + if tag >= u32::BITS || STATISTICS_TYPES & (1 << tag) == 0 { + return; + } + let sample = &mut self.samples[role as usize * 32 + tag as usize]; + sample.count = sample.count.saturating_add(1); + sample.host_ns = host_ns; + sample.words = words; + } + + pub(super) fn write(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + writeln!(f, "format=1 units=raw host_time=monotonic_ns achieved_frequency=unqualified")?; + for (index, sample) in self.samples.iter().enumerate().filter(|(_, s)| s.count != 0) { + writeln!(f, "role={} tag={:#x} count={} host_ns={} words={:08x?}", + index / 32, index % 32, sample.count, sample.host_ns, sample.words)?; + } + Ok(()) + } +} + +/// The view owns the session, not a pointer into a detachable Firmware. +pub(super) struct View(pub(super) kernel::sync::Arc); + +impl kernel::debugfs::Writer for View { + fn write(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + let state = self.0.state.lock(); + let Some(firmware) = state.as_deref() else { return writeln!(f, "online=0"); }; + writeln!(f, "online=1 graph={:?} sampled_ns={}", self.0.variant.graph, super::now_ns())?; + write_power(firmware, f)?; + firmware.rings.write_reports(f) + } +} + +fn write_power(firmware: &super::Firmware, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + let core_power = firmware.bridge.regs.g17_core_power(); + writeln!(f, "power_management=firmware sampled_ns={} core_power={:#x} cores_powered={} host_hold={} idle_hold_ms={}", + super::now_ns(), core_power, core_power & 0xf != 0, firmware.hold.active(), + super::power::IDLE_HOLD_MS.load(core::sync::atomic::Ordering::Relaxed))?; + firmware.port.write_power(f) +} + +/// Actual firmware-managed core gating, separate from generic Linux runtime PM. +pub(super) struct PowerView(pub(super) kernel::sync::Arc); + +impl kernel::debugfs::Writer for PowerView { + fn write(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { + let state = self.0.state.lock(); + let Some(firmware) = state.as_deref() else { return writeln!(f, "online=0"); }; + write_power(firmware, f) + } +} + +#[kernel::macros::kunit_tests(asahi_g17_reports)] +mod tests { + use super::*; + + #[test] + fn retains_all_words_and_separates_role_and_tag() { + let mut reports = Reports::new().unwrap(); + let mut words = [0; EVENT_WORDS]; + words[0] = 16; + words[17] = 0xdeadbeef; + reports.record(Role::Primary, words, 100); + reports.record(Role::Primary, words, 101); + reports.record(Role::Secondary, words, 102); + assert_eq!(reports.samples[16].count, 2); + assert_eq!(reports.samples[16].host_ns, 101); + assert_eq!(reports.samples[16].words, words); + assert_eq!(reports.samples[48].count, 1); + words[0] = 17; + reports.record(Role::Primary, words, 103); + assert_eq!(reports.samples[17].count, 1); + assert_eq!(reports.samples[16].count, 2); + } + + #[test] + fn rejects_invalid_tags_before_indexing() { + let mut reports = Reports::new().unwrap(); + for tag in [0, 15, 29, 31, 32, u32::MAX] { + let mut words = [0; EVENT_WORDS]; + words[0] = tag; + reports.record(Role::Primary, words, 1); + } + assert!(reports.samples.iter().all(|s| s.count == 0)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/runtime.rs b/drivers/gpu/drm/asahi/g17/runtime.rs index 6f4a5355d6eb3..108c9fe19a462 100644 --- a/drivers/gpu/drm/asahi/g17/runtime.rs +++ b/drivers/gpu/drm/asahi/g17/runtime.rs @@ -31,9 +31,32 @@ use kernel::{prelude::*, sync::Arc}; const QIDS: usize = 128; const FIRST_COMPUTE_QID: u8 = 4; -const RECOVERY_SETTLE_POLLS: usize = 200; const WORK_RING_WAIT_NS: u64 = super::job::TIMEOUT_MS as u64 * 1_000_000; +/// Pending admission keeps exactly retired compute backing available for reuse. +/// The guard spans retries and disappears on publication, cancellation or error. +/// It borrows Shared, so the accounting cannot retain the device after teardown. +struct ComputeDemand<'a>(&'a Arc); + +impl<'a> ComputeDemand<'a> { + fn new(shared: &'a Arc) -> Self { + // Each admitted logical queue has at most one scheduler publisher. + // The execution-context bound is far below the counter's range. + shared.compute_demand.fetch_add(1, Ordering::AcqRel); + Self(shared) + } +} + +impl Drop for ComputeDemand<'_> { + fn drop(&mut self) { + if self.0.compute_demand.fetch_sub(1, Ordering::AcqRel) == 1 { + // Reinstate normal idle retention even if the last publisher was + // cancelled before a kick could produce another event. + self.0.queue_events(); + } + } +} + /// A reservation remains identifiable while its backing is built without the /// device mutex. Failed construction returns the slot, never its host identity. #[derive(Clone, Copy)] @@ -45,13 +68,103 @@ pub(super) struct ComputeReservation { struct ComputeEntry { reservation: ComputeReservation, - queue: Option>, + queue: ComputeSlot, +} + +/// A physical compute queue behind its own lock. Publication into a bound queue takes only +/// this lock; device-mutex holders take it to observe or change the queue (device mutex -> +/// cell). A logical queue may keep the cell after the registry gave the queue up: the cell is +/// then empty, so the cached reference never extends any firmware-visible lifetime. +#[pin_data] +pub(super) struct ComputeCell { + #[pin] + queue: kernel::sync::Mutex>>, +} + +impl ComputeCell { + fn new() -> Result> { + Arc::pin_init( + pin_init!(Self { + queue <- kernel::new_mutex!(None, "g17::ComputeCell::queue"), + }), + GFP_KERNEL, + ) + } + + /// The installed queue, locked; None once the registry gave it up. + pub(super) fn lock(&self) -> Option> { + QueueGuard::new(self.queue.lock()) + } +} + +/// A locked installed queue. +pub(super) struct QueueGuard<'a> { + queue: core::ptr::NonNull, + _guard: kernel::sync::MutexGuard<'a, Option>>, +} + +impl<'a> QueueGuard<'a> { + fn new(mut guard: kernel::sync::MutexGuard<'a, Option>>) -> Option { + let queue = core::ptr::NonNull::from((*guard).as_deref_mut()?); + Some(Self { queue, _guard: guard }) + } +} + +impl core::ops::Deref for QueueGuard<'_> { + type Target = compute::Queue; + fn deref(&self) -> &compute::Queue { + // SAFETY: The boxed queue cannot be taken or dropped while the guard holds its lock. + unsafe { self.queue.as_ref() } + } +} + +impl core::ops::DerefMut for QueueGuard<'_> { + fn deref_mut(&mut self) -> &mut compute::Queue { + // SAFETY: As for `deref`; the lock makes this the only reference. + unsafe { self.queue.as_mut() } + } +} + +/// The registry's handle on one compute cell. Dropping it drops the queue in the cell, so a +/// cached cell never keeps a removed queue (or its QID's aliases) alive. +struct ComputeSlot(Arc); + +impl ComputeSlot { + fn new() -> Result { + Ok(Self(ComputeCell::new()?)) + } + fn as_deref(&self) -> Option> { + self.0.lock() + } + fn as_deref_mut(&mut self) -> Option> { + self.0.lock() + } + fn is_some(&self) -> bool { + self.0.queue.lock().is_some() + } + fn is_none(&self) -> bool { + !self.is_some() + } + /// Gives the queue up to the caller, which drops it outside the cell lock. + fn take(&mut self) -> Option> { + self.0.queue.lock().take() + } + fn put(&mut self, queue: Option>) { + let old = core::mem::replace(&mut *self.0.queue.lock(), queue); + drop(old); + } +} + +impl Drop for ComputeSlot { + fn drop(&mut self) { + let queue = self.0.queue.lock().take(); + drop(queue); + } } pub(super) struct Registry { pub(super) qids: kick::QueueIds, pub(super) buffers: BufferIds, - pub(super) accounting: qos::Accounting, pub(super) metrics: Arc, pub(super) qos_ids: Arc, pub(super) admission: Arc, @@ -62,6 +175,10 @@ pub(super) struct Registry { compute_scheduler_building: bool, replay_outer_wait: Option, pub(super) observations: compute::retirement::Batch, + /// Completion-ring slot where the next compute scan starts. + completion_cursor: usize, + /// Control word of each completion-ring slot as of the last scan that read it. + completion_seen: KVVec, worker_deferred: Option, teardown: super::teardown::Pending, } @@ -69,21 +186,34 @@ pub(super) struct Registry { impl Registry { /// Initialize inside the firmware allocation instead of staging the whole /// registry on the probe stack. No additional allocation is required. + #[cfg(CONFIG_KUNIT = "y")] pub(super) fn new( clusters: u32, deflake: crate::g17::fw::render::Deflake, descriptor_flags: [u32; 2], variant: &'static crate::g17::fw::Variant, + ) -> impl Init { + Self::with_waiters(clusters, deflake, descriptor_flags, variant, None, None) + } + + /// `queues` wakes logical-queue creation when a logical queue slot is returned; + /// `renders` wakes render publication when a render pair or lease returns. + pub(super) fn with_waiters( + clusters: u32, + deflake: crate::g17::fw::render::Deflake, + descriptor_flags: [u32; 2], + variant: &'static crate::g17::fw::Variant, + queues: Option>, + renders: Option>, ) -> impl Init { kernel::try_init!(Self { qids <- kick::QueueIds::new(variant.qid_floor).chain(|qids| { qids.reserve_initial_render() }), buffers <- BufferIds::new(), - accounting: qos::Accounting::new(), metrics: Arc::new(MetricsIds::new(), GFP_KERNEL)?, qos_ids: QosIds::new()?, - admission: admission::Pool::new(kick::queue_limit(variant.qid_floor))?, + admission: admission::Pool::with_waiters(kick::queue_limit(variant.qid_floor), queues, renders)?, render: render::RenderState::new(clusters, deflake, descriptor_flags)?, identities: queue::Identities::new(), compute: { @@ -97,12 +227,21 @@ impl Registry { compute_scheduler_building: false, replay_outer_wait: None, observations: compute::retirement::Batch::new()?, + completion_cursor: 0, + completion_seen: { + let slots = super::initdata::COMPUTE_COMPLETION_SLOTS; + let mut seen = KVVec::with_capacity(slots, GFP_KERNEL)?; + for _ in 0..slots { + seen.push_within_capacity(0).map_err(|_| ENOMEM)?; + } + seen + }, worker_deferred: Some(DeferredBatch::worker()?), teardown: super::teardown::Pending::new()?, }) } - pub(super) fn compute(&self, owner: u64) -> Option<&compute::Queue> { + pub(super) fn compute(&self, owner: u64) -> Option> { self.compute .iter() .flatten() @@ -110,12 +249,16 @@ impl Registry { .find(|queue| queue.owner() == Some(owner)) } - pub(super) fn compute_mut(&mut self, owner: u64) -> Option<&mut compute::Queue> { - self.compute - .iter_mut() - .flatten() - .filter_map(|entry| entry.queue.as_deref_mut()) - .find(|queue| queue.owner() == Some(owner)) + pub(super) fn compute_mut(&mut self, owner: u64) -> Option> { + self.compute(owner) + } + + /// The cell of `owner`'s bound queue, which its publisher may lock without the device mutex. + pub(super) fn compute_cell(&self, owner: u64) -> Option> { + self.compute.iter().flatten().find_map(|entry| { + entry.queue.as_deref()?.owner().filter(|bound| *bound == owner)?; + Some(entry.queue.0.clone()) + }) } /// An installed idle queue keeps its original QID and backing. Mapping a @@ -131,15 +274,80 @@ impl Registry { Rebind::Fresh }); } - // Physical channels are a device budget, not a permanent entitlement - // of each open logical queue. Only exact idle retirement can yield one. - for queue in self.compute.iter_mut().flatten().filter_map(|entry| entry.queue.as_deref_mut()) { - admission.check()?; - queue.share_idle()?; + // A binding persists for its logical queue's lifetime while physical + // QIDs allow: take a channel of a closed owner, then a fresh QID, and + // only then the least recently idle channel of one open owner. + if let Some(rebind) = self.bind_reusable(owner, context, required, admission)? { + return Ok(rebind); + } + if self.qids.compute_fits(self.admission.queues() as usize) { + return Ok(Rebind::Fresh); + } + let teardown = &self.teardown; + let idle = self + .compute + .iter() + .flatten() + .filter_map(|entry| entry.queue.as_deref()) + .filter(|queue| queue.shareable()) + .filter(|queue| !queue.context().is_some_and(|bound| teardown.releasing(bound))) + .map(|queue| ((!queue.pool_fits(required), queue.last_published()), queue.qid())) + .min_by_key(|(key, _)| *key) + .map(|(_, qid)| qid); + let Some(qid) = idle else { return Err(ENOSPC); }; + admission.check()?; + let shared = self + .compute + .iter_mut() + .flatten() + .filter_map(|entry| entry.queue.as_deref_mut()) + .find(|queue| queue.qid() == qid) + .ok_or(EIO)? + .share_idle()?; + if !shared { + return Err(ENOSPC); + } + Ok(self.bind_reusable(owner, context, required, admission)?.unwrap_or(Rebind::Fresh)) + } + + /// Whether `owner` can bind a channel now, without changing any binding. Owner 0 asks + /// only whether some channel is free for a new owner. + fn compute_readiness(&self, owner: u64, context: Option<&Arc>, required: usize) -> Readiness { + if let (Some(context), Some(queue)) = (context, self.compute(owner).filter(|_| owner != 0)) { + return if queue.spared_pending() { + Readiness::Progress + } else if !matches!(queue.matches(owner, queue.qid(), context), Ok(true)) { + // Publication reports the mismatch. + Readiness::Ready + } else if queue.pool_fits(required) || !queue.growing() { + Readiness::Ready + } else { + Readiness::Progress + }; + } + let teardown = &self.teardown; + let available = self + .compute + .iter() + .flatten() + .filter_map(|entry| entry.queue.as_deref()) + .any(|queue| { + (queue.reusable() || queue.shareable() || queue.can_rearm()) + && !queue.context().is_some_and(|bound| teardown.releasing(bound)) + }); + if available || self.qids.compute_fits(self.admission.queues() as usize) { + Readiness::Ready + } else { + Readiness::Channel } + } + + /// Binds `owner` to a reusable channel whose last owner released it, or + /// names a vacant one to re-arm. None if no channel is reusable. + fn bind_reusable(&mut self, owner: u64, context: &Arc, required: usize, admission: &Admission<'_>) -> Result> { let mut vacant = None; let teardown = &self.teardown; - for queue in self + for mut queue in self .compute .iter_mut() .flatten() @@ -159,9 +367,9 @@ impl Registry { } if !queue.pool_fits(required) { continue; } queue.bind_owner(owner, context.clone())?; - return Ok(Rebind::Done); + return Ok(Some(Rebind::Done)); } - Ok(vacant.map_or(Rebind::Fresh, Rebind::Rearm)) + Ok(vacant.map(Rebind::Rearm)) } /// Installs fresh USC backing into the vacant queue `qid`; the caller writes @@ -172,7 +380,7 @@ impl Registry { pages: crate::g17::freelist::Pages, ring: compute::DescriptorRing, ) -> Result<(u16, u64)> { - let queue = self + let mut queue = self .compute .iter_mut() .flatten() @@ -184,7 +392,7 @@ impl Registry { pub(super) fn bind_rearmed(&mut self, qid: u8, owner: u64, context: &Arc) -> Result { let teardown = &self.teardown; - let queue = self + let mut queue = self .compute .iter_mut() .flatten() @@ -252,7 +460,7 @@ impl Registry { let excess = candidates.len().saturating_sub(cap).max(forced); let mut published = 0; for (_, qid) in candidates.iter().take(excess.min(4)) { - let queue = self + let mut queue = self .compute .iter_mut() .flatten() @@ -282,7 +490,7 @@ impl Registry { let consumer = init.control_consumer()?.load(Ordering::Relaxed); let producer = init.control_producer()?.load(Ordering::Relaxed); let mut freed = 0; - for queue in self + for mut queue in self .compute .iter_mut() .flatten() @@ -313,7 +521,7 @@ impl Registry { || self.compute.iter().flatten().any(|entry| { entry .queue - .as_ref() + .as_deref() .map_or(entry.reservation.owner == owner, |queue| { queue.owner() == Some(owner) }) @@ -328,14 +536,16 @@ impl Registry { .ok_or_else(|| self.compute_admission_error( crate::g17::capacity::refused(crate::g17::capacity::Site::ComputeSlots)))?; let identity = self.identities.reserve(1)?.start; + let queues = self.admission.queues() as usize; let id = self .qids - .reserve(identity, DataMaster::Compute, Some(FIRST_COMPUTE_QID)) + .reserve_for(identity, DataMaster::Compute, Some(FIRST_COMPUTE_QID), queues) .map_err(|error| self.compute_admission_error(error))?; + self.note_build_room(); let reservation = ComputeReservation { slot, id, owner }; self.compute[slot] = Some(ComputeEntry { reservation, - queue: None, + queue: ComputeSlot::new()?, }); Ok(reservation) } @@ -367,7 +577,7 @@ impl Registry { return Err(EIO); } let entry = self.reserved(reservation)?; - entry.queue = queue.take(); + entry.queue.put(queue.take()); Ok(()) } @@ -375,6 +585,7 @@ impl Registry { self.reserved(reservation)?; self.qids.cancel_unpublished(reservation.id)?; self.compute[reservation.slot] = None; + self.note_build_room(); Ok(()) } @@ -382,20 +593,32 @@ impl Registry { let Some(slot) = self.compute.iter().position(|entry| { entry .as_ref() - .and_then(|entry| entry.queue.as_ref()) + .and_then(|entry| entry.queue.as_deref()) .is_some_and(|queue| queue.owner() == Some(owner)) }) else { return Ok(()); }; let entry = self.compute[slot].as_mut().ok_or(EIO)?; - if entry.queue.as_mut().ok_or(EIO)?.release_owner()? { + let unpublished = entry.queue.as_deref_mut().ok_or(EIO)?.release_owner()?; + if unpublished { self.qids.cancel_unpublished(entry.reservation.id)?; self.compute[slot] = None; + self.note_build_room(); } Ok(()) } } +/// What a compute packet still lacks before publication. +#[derive(Copy, Clone, PartialEq, Eq)] +pub(super) enum Readiness { + Ready, + /// A physical channel: every one is busy and no QID fits. + Channel, + /// Retirement-bound capacity or reopened publication. + Progress, +} + /// Outcome of offering an owner a retained compute queue. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(super) enum Rebind { @@ -436,12 +659,28 @@ mod uninstalled_cleanup_tests { /// An immutable logical owner shared by its two scheduler entities. Installed /// physical graphs retain the context independently of this frontend lifetime. +#[pin_data] pub(super) struct Backend { shared: Arc, pub(super) context: Arc, owner: u64, backlog: core::sync::atomic::AtomicU32, + compute_backlog: core::sync::atomic::AtomicU32, closed: core::sync::atomic::AtomicBool, + /// The cell of the compute queue this owner last published to under the device mutex, + /// with the shared compute objects mapped into its VM. Direct publication revalidates the + /// binding under the cell lock; a stale cell only sends the packet back to that path. + #[pin] + direct: kernel::sync::Mutex>>, + /// The channel a prepared packet is about to publish into. Its claim keeps the idle + /// channel from being shared away between prepare_job and run_job. + #[pin] + claimed: kernel::sync::Mutex>>, + /// Render pair admission, shared by every logical queue of the session. + admission: Arc, + /// The render pair this owner last published to without the device mutex + /// (zero for none): a hint, revalidated by every lend. + direct_slot: core::sync::atomic::AtomicU32, } impl Backend { @@ -459,6 +698,7 @@ impl Backend { const RETAINED_CONTEXT_WAIT_NS: u64 = 4 * WORK_RING_WAIT_NS; let started = now_ns(); let (lease, uat, pool, qos, execution, slot) = loop { + let observed = shared.queues.observe(); let lease = shared.preparations.enter(true)?; let mut state = shared.state.lock(); let firmware = (*state).as_deref_mut().ok_or(ENODEV)?; @@ -466,6 +706,11 @@ impl Backend { return Err(EIO); } let slot = firmware.queues.admission.reserve_queue()?; + // Installed compute channels narrow render's QIDs: every open + // logical queue keeps a dedicated render pair within reach. + if !firmware.queues.qids.render_fits(firmware.queues.admission.queues() as usize) { + return Err(super::capacity::refused(super::capacity::Site::LogicalQueues)); + } match firmware.uat.new_execution_context(vm) { Ok(execution) => { break ( @@ -493,7 +738,10 @@ impl Backend { return Err(ERESTARTSYS); } shared.queue_events(); - kernel::time::delay::fsleep(kernel::time::Delta::from_millis(1)); + // A closing queue returns its context when its release + // is reclaimed; that reclaim wakes this creator. + let left = RETAINED_CONTEXT_WAIT_NS.saturating_sub(now_ns().saturating_sub(started)); + shared.queues.block(observed, left.div_ceil(1_000_000) as u32, true)?; } Err(error) => return Err(error), } @@ -506,7 +754,7 @@ impl Backend { let owner_pid = u32::try_from(kernel::current!().group_leader().pid()).map_err(|_| EOVERFLOW)?; let context = Arc::new( - Context::new(&alloc, &pool, &qos, execution, owner_pid, policy)?, + Context::new(&alloc, &pool, &qos, execution, owner_pid, policy, shared.renders.clone())?, GFP_KERNEL, )?; let state = shared.state.lock(); @@ -514,15 +762,21 @@ impl Backend { return Err(EAGAIN); } drop(state); + let admission = slot.pool().clone(); Ok(( - Arc::new( - Self { + Arc::pin_init( + pin_init!(Self { shared: shared.clone(), context, owner, backlog: core::sync::atomic::AtomicU32::new(0), + compute_backlog: core::sync::atomic::AtomicU32::new(0), closed: core::sync::atomic::AtomicBool::new(false), - }, + direct <- kernel::new_mutex!(None, "g17::Backend::direct"), + claimed <- kernel::new_mutex!(None, "g17::Backend::claimed"), + admission, + direct_slot: core::sync::atomic::AtomicU32::new(0), + }), GFP_KERNEL, )?, slot, @@ -592,7 +846,7 @@ impl Backend { .queues .compute_building = false; } - self.shared.changed.notify_all(); + self.shared.note_changed(); drop(lease); if result != Err(EAGAIN) { return result; @@ -652,7 +906,7 @@ impl Backend { .queues .compute_scheduler_building = false; } - self.shared.changed.notify_all(); + self.shared.note_changed(); result } @@ -667,11 +921,14 @@ impl Backend { drop(queue); let mut state = self.shared.state.lock(); let firmware = state.as_deref_mut().ok_or(ENODEV)?; - firmware.queues.cancel_compute(reservation) + firmware.queues.cancel_compute(reservation)?; + drop(state); + // The slot and its never-published QID are free again. + self.shared.channels.notify(); + Ok(()) } fn grow_compute(&self, required: usize, admission: &Admission<'_>) -> Result { - use kernel::time::{delay::fsleep, Delta}; admission.check()?; { let state = self.shared.state.lock(); @@ -689,6 +946,9 @@ impl Backend { let mut deferred = DeferredBatch::publication()?; let mut growth = None; let result = (|| loop { + // Growth waits for this queue's retirement and for firmware to consume the pool + // release; both are progress the event worker reports. + let observed = self.shared.progress.observe(); let result = (|| { let mut state = self.shared.state.lock(); admission.check()?; @@ -696,7 +956,7 @@ impl Backend { if !lease.is_current() { return Err(EAGAIN); } if self.shared.crashed.load(Ordering::Acquire) { return Err(EIO); } firmware.scan_compute(None, &mut deferred)?; - let queue = firmware.queues.compute_mut(self.owner).ok_or(EAGAIN)?; + let mut queue = firmware.queues.compute_mut(self.owner).ok_or(EAGAIN)?; if queue.pool_fits(required) { return Ok(None); } if growth.is_none() { let notify = queue.begin_growth(&firmware.init, required)?; @@ -714,9 +974,9 @@ impl Backend { match result { Ok(old) => { drop(old); return Ok(()); } Err(error) if error == EAGAIN && lease.is_current() => { - self.shared.queue_events(); - fsleep(Delta::from_millis(1)); - } + self.shared.queue_events(); + self.wait_release(&self.shared.progress, observed, admission)?; + } Err(error) => return Err(error), } })(); @@ -727,8 +987,9 @@ impl Backend { if let Some(firmware) = state.as_deref_mut() { for index in 0..firmware.queues.compute.len() { let Some(entry) = firmware.queues.compute[index].as_mut() else { continue; }; - let Some(queue) = entry.queue.as_deref_mut() else { continue; }; - if queue.abort_growth(token)? == Some(true) { + let Some(aborted) = entry.queue.as_deref_mut() + .map(|mut queue| queue.abort_growth(token)).transpose()? else { continue; }; + if aborted == Some(true) { // Keep the empty entry and QID reserved until all // graph aliases have been destroyed outside the lock. retired = entry.queue.take().map(|queue| (entry.reservation, queue)); @@ -762,9 +1023,11 @@ impl Backend { } loop { admission.check()?; + // Sampled before any capacity check of this attempt. + let observed = self.shared.channels.observe(); let lease = self.shared.preparations.enter_until(admission)?; let dev = self.shared.drm()?; - let (uat, reservation) = { + let (uat, objects, reservation) = { let mut state = self.shared.state.lock(); admission.check()?; let firmware = (*state).as_deref_mut().ok_or(ENODEV)?; @@ -780,7 +1043,7 @@ impl Backend { Err(error) if error == ENOSPC => { drop(state); drop(lease); - self.wait_compute_capacity(admission)?; + self.wait_channels(observed, admission, true)?; continue; } result => result?, @@ -788,16 +1051,17 @@ impl Backend { match rebound { Rebind::Done => return Ok(()), Rebind::Grow => { drop(state); drop(lease); return self.grow_compute(required, admission); } - Rebind::Rearm(qid) => (firmware.uat.clone(), Err(qid)), + Rebind::Rearm(qid) => (firmware.uat.clone(), firmware.pool.clone(), Err(qid)), Rebind::Fresh => { let reservation = match firmware.queues.reserve_compute(self.owner) { Ok(reservation) => reservation, Err(error) if error == EAGAIN => { - // The event worker owns release observation and - // drops vacated aliases/backing off this mutex. + // All physical channels are occupied. Retain their + // USC backing for exact-retirement handoff; releasing + // it cannot create a QID and forces fresh allocation. drop(state); drop(lease); - self.wait_compute_capacity(admission)?; + self.wait_channels(observed, admission, false)?; continue; } Err(error) if error == ENOSPC => { @@ -811,7 +1075,7 @@ impl Backend { } Err(error) => return Err(error), }; - (firmware.uat.clone(), Ok(reservation)) + (firmware.uat.clone(), firmware.pool.clone(), Ok(reservation)) } } }; @@ -826,12 +1090,12 @@ impl Backend { // queue may have been taken or re-armed by another owner since. let built = (|| -> Result<_> { let pages = crate::g17::freelist::Pages::compute_sized(&alloc, required)?; - Ok((pages, compute::DescriptorRing::new(&alloc)?)) + Ok((pages, compute::DescriptorRing::new(&alloc, &objects)?)) })(); let (pages, ring) = match built { Err(error) if error == ENOSPC => { drop(lease); - self.wait_compute_capacity(admission)?; + self.wait_channels(observed, admission, true)?; continue; } result => result?, @@ -881,6 +1145,7 @@ impl Backend { self.compute_scheduler(&alloc, &lease, admission)?; let queue = compute::Queue::new( &alloc, + &objects, backing, self.owner, self.context.clone(), @@ -920,7 +1185,7 @@ impl Backend { } drop(lease); if result == Err(ENOSPC) { - self.wait_compute_capacity(admission)?; + self.wait_channels(observed, admission, true)?; continue; } if result != Err(EAGAIN) { return result; } @@ -932,6 +1197,7 @@ impl Backend { /// The physical namespace bounds the batch independently of open logical queues. /// Collection failure leaves the engine's Active owner intact for a stopped retry. pub(super) struct DeferredBatch { + has_source_completions: bool, values: KVVec, /// Exited owners detached from idle retained compute queues and the backing /// of vacated pools: unmapping and freeing happen in `finish`, off-lock. @@ -967,6 +1233,7 @@ impl DeferredBatch { 0 }; Ok(Self { + has_source_completions: false, values: KVVec::with_capacity(capacity, GFP_KERNEL)?, bindings: KVVec::with_capacity(detached, GFP_KERNEL)?, vacated: KVVec::with_capacity(detached, GFP_KERNEL)?, @@ -1020,10 +1287,18 @@ impl DeferredBatch { // it. Expose terminal VM status now, before off-lock fence callbacks. // Recovery-spared and cancelled commands never poison their VM. if let Deferred::FailedOwned(completion, error) = &value { - if *error != ECANCELED && !completion.spared() { + if completion.poisons(*error) { completion.status().record(*error); } } + match &value { + Deferred::Retired(completion, Err(error)) | Deferred::Drained(completion, Err(error)) + | Deferred::FailedOwned(completion, error) | Deferred::Unpublished(completion, error) => + { completion.note_input_error(*error); }, + Deferred::Retired(completion, Ok(_)) | Deferred::Drained(completion, Ok(_)) => + completion.note_source_retired(), + _ => {}, + } self.values.push_within_capacity(value).map_err(|_| EIO)?; // A later publisher can borrow this pair before finish signals its old // fence. Publish the exact completion's witness only after collection @@ -1035,11 +1310,33 @@ impl DeferredBatch { ) = self.values.last() { completion.note_retirement_queued(); + self.has_source_completions |= completion.sources().is_some(); } Ok(()) } + /// Completions collected so far. + pub(super) fn pending(&self) -> usize { + self.values.len() + } /// Called only after releasing the device mutex. pub(super) fn finish(&mut self) { + use super::completion::Deferred; + if self.has_source_completions { + for _ in 0..self.values.len() { + let mut changed = false; + for value in &self.values { + if let Deferred::Retired(completion, Ok(_)) + | Deferred::Drained(completion, Ok(_)) = value + { + if let Err(error) = completion.check_source_inputs() { + changed |= completion.note_input_error(error); + } + } + } + if !changed { break; } + } + } + self.has_source_completions = false; for value in self.values.drain_all() { value.finish(); } @@ -1060,15 +1357,15 @@ impl DeferredBatch { struct ComputeHost<'a> { init: &'a super::initdata::InitData, - primary: &'a mut super::Coprocessor, + port: &'a super::port::Port, + outer: Option>, effort: &'a mut super::power::Effort, qids: &'a mut kick::QueueIds, - accounting: &'a mut qos::Accounting, generation: u64, } impl compute::Host for ComputeHost<'_> { fn next_compute_publication(&mut self) -> Result { - self.qids.next_publication() + Ok(self.port.next_publication()) } fn epoch(&self) -> Result<(u64, u32)> { Ok(( @@ -1085,54 +1382,163 @@ impl compute::Host for ComputeHost<'_> { scheduler: u64, policy: super::fw::queue::Policy, ) -> Result { - self.accounting.publish( - &qos::View::new(self.init.qos()?)?, - owner, - scheduler, - policy, - qos::clock, - ) + self.port.qos_publish(owner, scheduler, policy) } fn qos_cancel(&mut self, publication: qos::Publication) -> Result { - self.accounting - .cancel(&qos::View::new(self.init.qos()?)?, publication) + self.port.qos_cancel(publication) } fn publish_qid(&mut self, id: kick::Id) -> Result { self.qids.publish(id) } - fn publish_outer(&mut self, priority: u8, slot: &super::fw::channels::WorkSlot) -> Result { - self.init.publish_work(priority, slot).map(|_| ()) + fn publish_outer(&mut self, _priority: u8, slot: &super::fw::channels::WorkSlot) -> Result { + self.port.publish_compute(self.outer.take().ok_or(EIO)?, slot).map(|_| ()) + } + fn notify_compute(&mut self, priority: u8, message: u64) -> Result { + self.port.notify_compute(priority, message) } - fn notify(&mut self, message: u64) -> Result { - self.primary.notify(message) + fn compute_written(&self, priority: u8) -> u64 { + self.port.compute_written(priority) + } + fn compute_announced(&self, priority: u8) -> u64 { + self.port.compute_announced(priority) } fn note_submission(&mut self) -> Result { self.effort.submit(); - super::Firmware::apply_shared_effort(self.init, self.effort, super::power::FULL_EFFORT) + super::Firmware::apply_shared_effort(self.init, self.effort, self.port, super::power::FULL_EFFORT) + } +} + +/// Publication into a bound, installed queue through the port alone. The caller checked the +/// recovery state, full effort and the queue's binding; first installation never comes here. +struct DirectHost<'a> { + port: &'a super::port::Port, + outer: Option>, + generation: u64, +} +impl compute::Host for DirectHost<'_> { + fn next_compute_publication(&mut self) -> Result { + Ok(self.port.next_publication()) + } + fn epoch(&self) -> Result<(u64, u32)> { + Ok((self.generation, self.port.recovery_state()?)) + } + fn prepare_compute_shared(&mut self) -> Result { + Err(EAGAIN) + } + fn qos_publish( + &mut self, + owner: qos::Owner, + scheduler: u64, + policy: super::fw::queue::Policy, + ) -> Result { + self.port.qos_publish(owner, scheduler, policy) + } + fn qos_cancel(&mut self, publication: qos::Publication) -> Result { + self.port.qos_cancel(publication) + } + fn publish_qid(&mut self, _id: kick::Id) -> Result { + // An installed queue published its identity with its first command. + Ok(()) + } + fn publish_outer(&mut self, _priority: u8, slot: &super::fw::channels::WorkSlot) -> Result { + self.port.publish_compute(self.outer.take().ok_or(EIO)?, slot).map(|_| ()) + } + fn notify_compute(&mut self, priority: u8, message: u64) -> Result { + self.port.notify_compute(priority, message) + } + fn compute_written(&self, priority: u8) -> u64 { + self.port.compute_written(priority) + } + fn compute_announced(&self, priority: u8) -> u64 { + self.port.compute_announced(priority) + } + fn note_submission(&mut self) -> Result { + Ok(()) } } impl super::Firmware { - fn publish_compute( + #[inline] + fn publish_compute(&mut self, owner: u64, packet: Arc, + dependencies: &[super::fw::kick::KickDependency], deferred: &mut DeferredBatch) -> Result + { + if packet.completion.sources().is_some() { + self.publish_compute_inputs(owner, packet, dependencies, deferred) + } else { + self.publish_compute_raw(owner, packet, dependencies, &[], deferred) + } + } + + #[inline(never)] + fn publish_compute_inputs(&mut self, owner: u64, packet: Arc, + dependencies: &[super::fw::kick::KickDependency], deferred: &mut DeferredBatch) -> Result + { + if self.recovery.pending() || super::recovery::Memory::recovery_state(&self.init)? != 0 { + return Err(EAGAIN); + } + let sources = packet.completion.sources().ok_or(EINVAL)?.clone(); + sources.check()?; + let mut merged = super::dependency::Frontier::new(); + for dependency in dependencies { merged.insert(0, *dependency)?; } + let mut holds = KVec::with_capacity(sources.sources.len(), GFP_KERNEL)?; + let mut native: KVec = + KVec::with_capacity(sources.sources.len(), GFP_KERNEL)?; + for source in &sources.sources { + if source.submission.source_status()? { continue; } + let found = self.queues.compute.iter_mut().flatten() + .filter_map(|entry| entry.queue.as_deref_mut()) + .find_map(|mut queue| match queue.source_dependency(source, packet.completion.status(), + self.recovery.generation()) { + Ok(None) => None, + result => Some(result), + }).ok_or(EAGAIN)??; + let Some((sequence, dependency, stamp_va, event_slot, hold)) = found else { + return Err(EAGAIN); + }; + // The completion-wait record orders the consumer behind its + // source; naming the source in the kick entry as well only adds + // a second, slower wait and a predecessor record. + let wait = super::sync_input::NativeWait { sequence, dependency, stamp_va, event_slot }; + if let Some(old) = native.iter_mut().find(|old| + old.dependency.qid() == dependency.qid()) { + if sequence > old.sequence { *old = wait; } + } else { + native.push(wait, GFP_KERNEL)?; + } + holds.push(hold, GFP_KERNEL)?; + } + let result = self.publish_compute_raw(owner, packet.clone(), merged.as_slice(), + native.as_slice(), deferred); + if packet.is_published() { + for queue in self.queues.compute.iter().flatten() + .filter_map(|entry| entry.queue.as_deref()) + .filter(|queue| native.iter().any(|wait| wait.dependency.qid() == queue.qid())) + { + for source in &sources.sources { queue.note_input_consumer(source); } + } + sources.set_holds(holds); + super::sync_input::note_published(); + super::sync_input::note_native(native.len()); + } + result + } + + fn publish_compute_raw( &mut self, owner: u64, packet: Arc, dependencies: &[super::fw::kick::KickDependency], + native: &[super::sync_input::NativeWait], deferred: &mut DeferredBatch, ) -> Result { if self.recovery.pending() { return Err(EAGAIN); } // No descriptor, accounting or inner producer changes precede this - // capacity check. The same device lock covers queue.publish below. - if !self - .init - .work_ready(packet.context.policy().priority(), DataMaster::Compute)? - { - return Err(EAGAIN); - } + // reservation; dropping it unused returns the work-ring slot. + let outer = self.port.reserve_compute(packet.context.policy().priority())?; let queues = &mut self.queues; - let queue = queues + let mut queue = queues .compute .iter_mut() .flatten() @@ -1150,26 +1556,42 @@ impl super::Firmware { } let mut host = ComputeHost { init: &self.init, - primary: &mut self.primary, + port: &self.port, + outer: Some(outer), effort: &mut self.effort, qids: &mut queues.qids, - accounting: &mut queues.accounting, generation: self.recovery.generation(), }; - queue.publish(packet, dependencies, &mut host, &mut |event| { - deferred.push(event) - }) + if native.is_empty() { + queue.publish(packet, dependencies, &mut host, &mut |event| deferred.push(event)) + } else { + queue.publish_with_native_waits(packet, dependencies, native, + &mut host, &mut |event| deferred.push(event)) + } } } impl Registry { + /// Everything ensure_compute establishes for `owner`: the shared compute + /// objects mapped into its VM, and its queue bound to `context` with a + /// pool that fits and no spared witness to release. + fn compute_bound(&self, owner: u64, context: &Arc, required: usize) -> Result { + let Some(queue) = self.compute(owner) else { + return Ok(false); + }; + Ok(!queue.spared_pending() + && queue.pool_fits(required) + && queue.matches(owner, queue.qid(), context)?) + } + fn release_compute_witness( &mut self, init: &super::initdata::InitData, + port: &super::port::Port, owner: u64, deferred: &mut DeferredBatch, ) -> Result { - let Some(queue) = self + let Some(mut queue) = self .compute .iter_mut() .flatten() @@ -1181,10 +1603,9 @@ impl Registry { if !queue.spared_pending() { return Ok(false); } - let view = qos::View::new(init.qos()?)?; queue.release_witnessed( super::recovery::Memory::recovery_state(init)?, - |owner| self.accounting.complete(&view, &[owner]), + |owner| port.qos_complete(&[owner]), &mut |event| deferred.push(event), )?; Ok(queue.spared_pending()) @@ -1196,7 +1617,7 @@ impl Registry { error: Error, deferred: &mut DeferredBatch, ) -> Result { - for queue in self + for mut queue in self .compute .iter_mut() .flatten() @@ -1223,33 +1644,63 @@ impl super::Firmware { let registry = &mut self.queues; registry.observations.clear(); let mut requested = false; + // Every queue's pending identities are staged, not only the ones this batch + // retires: the scan passes each new record once, and a queue retired by a later + // batch finds its observation already latched. for queue in registry .compute .iter() .flatten() .filter_map(|entry| entry.queue.as_deref()) { - requested |= queue.request_observations(&mut registry.observations, masks)?; + requested |= queue.request_observations(&mut registry.observations)?; } + // Completion records are appended in order: fold only the records written since + // the previous scan. A request whose stamp already covers it has its record in the + // ring; if the scan ended before finding one, read the whole ring once. if requested { - self.init - .scan_compute_completions(|record| registry.observations.fold(record))?; + let observations = &mut registry.observations; + let seen = &mut registry.completion_seen; + registry.completion_cursor = self.init.fold_compute_completions( + registry.completion_cursor, + seen, + |record| observations.fold(record), + )?; + if observations.covered_pending() { + registry.completion_cursor = self + .init + .resync_compute_completions(seen, |record| observations.fold_any(record))?; + } } - let view = qos::View::new(self.init.qos()?)?; - for queue in registry + let port = &self.port; + let retired = deferred.pending(); + let mut freed = false; + let mut doorbells = [0u64; 4]; + for mut queue in registry .compute .iter_mut() .flatten() .filter_map(|entry| entry.queue.as_deref_mut()) { - queue.latch_observations(&mut registry.observations, masks)?; + let busy = !queue.channel_free(); + queue.latch_observations(&mut registry.observations)?; queue.retire( masks, generation, state, - |owner| registry.accounting.complete(&view, &[owner]), + |owner| port.qos_complete(&[owner]), &mut |event| deferred.push(event), )?; + freed |= busy && queue.channel_free(); + // Withheld doorbells are rung once their command is next but one; one per + // priority class announces every queue's work slots on that ring. + if let Some(doorbell) = queue.due_doorbell(|class| port.compute_announced(class)) { + let class = ((doorbell >> 2) & 3) as usize; + doorbells[class] = doorbell; + } + } + for doorbell in doorbells.into_iter().filter(|doorbell| *doorbell != 0) { + self.port.notify_compute(((doorbell >> 2) & 3) as u8, doorbell)?; } // All compute observations in this batch are latched before a VM // failure can settle its render peers. Exact successes win first. @@ -1257,26 +1708,37 @@ impl super::Firmware { let terminal = self.queues.compute[index] .as_mut() .and_then(|entry| entry.queue.as_deref_mut()) - .and_then(compute::Queue::take_terminal); + .and_then(|mut queue| queue.take_terminal()); if let Some((status, error)) = terminal { status.record(error); self.settle_render_vm(&status, error, deferred)?; self.queues.fail_compute_vm(&status, error, deferred)?; } } - let view = qos::View::new(self.init.qos()?)?; + let port = &self.port; let registry = &mut self.queues; - for queue in registry + for mut queue in registry .compute .iter_mut() .flatten() .filter_map(|entry| entry.queue.as_deref_mut()) { + let busy = !queue.channel_free(); queue.release_witnessed( state, - |owner| registry.accounting.complete(&view, &[owner]), + |owner| port.qos_complete(&[owner]), &mut |event| deferred.push(event), )?; + freed |= busy && queue.channel_free(); + } + // Wake only the admission waits this batch can end. The fences gate + // scheduler entities alone; their callbacks never take the device mutex. + let shared = &self.primary.state.shared; + if freed { + shared.channels.notify(); + } + if deferred.pending() != retired { + shared.progress.notify(); } Ok(()) } @@ -1286,18 +1748,17 @@ impl Backend { fn ensure_compute(&self, scs_pages: u16, admission: &Admission<'_>) -> Result { admission.check()?; let required = super::fw::compute_pool::required_blocks(self.shared.active_cores, scs_pages)?; - use kernel::time::{delay::fsleep, Delta}; - const SPARED_WAIT_MS: usize = 200; let mut deferred = DeferredBatch::publication()?; - for _ in 0..SPARED_WAIT_MS { + loop { admission.check()?; + let observed = self.shared.progress.observe(); let pending = (|| { let mut state = self.shared.state.lock(); let firmware = (*state).as_deref_mut().ok_or(ENODEV)?; firmware.replay_compute(&mut deferred)?; firmware .queues - .release_compute_witness(&firmware.init, self.owner, &mut deferred) + .release_compute_witness(&firmware.init, &firmware.port, self.owner, &mut deferred) })(); deferred.finish(); let pending = pending?; @@ -1307,14 +1768,306 @@ impl Backend { result => return result, } } - // Other owners continue submitting while this owner gives its - // queue-local drain witness time to arrive. - fsleep(Delta::from_millis(1)); + // Other owners continue submitting while this owner waits for its + // queue-local drain witness. No new command has been accepted: the + // spared queue and its retained work stay intact past the deadline. + self.shared.queue_events(); + self.wait_release(&self.shared.progress, observed, admission)?; } - // No new command has been accepted. Keep the spared queue and its - // retained work intact until a later admission observes its witness. - admission.check()?; - Err(EBUSY) + } + + /// Steady-state publication into this owner's bound channel without the device mutex: only + /// the channel's own lock and the port's small locks are taken, and nothing device-wide is + /// scanned. None leaves the packet untouched for the device-mutex path, which owns binding, + /// first installation, input sources, render prefixes, replay and recovery. + fn compute_direct( + &self, + packet: &Arc, + required: usize, + deferred: &mut DeferredBatch, + ) -> Option { + if packet.completion.sources().is_some() + || self.context.render_seen() + || packet.cancel_requested() + || packet.completion.status().get() != 0 + || self.admission_open().is_err() + { + return None; + } + let cell = self.direct.lock().clone()?; + let lease = self.shared.preparations.try_enter().ok()?; + let port = self.shared.port(&lease)?; + if self.shared.crashed.load(Ordering::Acquire) || port.recovery_state().ok()? != 0 { + return None; + } + // Full effort is applied by the device-mutex path when it is not already. + if !port.note_submission() { + return None; + } + match packet.completion.work_state().ok()?.acquire() { + Ok(_) => {} + Err(error) if error == EBUSY => return None, + Err(error) => return Some(Err(error)), + } + let mut queue = cell.lock()?; + if !queue.installed() + || queue.spared_pending() + || !queue.pool_fits(required) + || queue.pending_switch_from().is_some() + || !matches!(queue.matches(self.owner, queue.qid(), &self.context), Ok(true)) + || !lease.is_current() + { + return None; + } + let outer = port.reserve_compute(packet.context.policy().priority()).ok()?; + let mut host = DirectHost { + port, + outer: Some(outer), + generation: port.generation(), + }; + match queue.publish(packet.clone(), &[], &mut host, &mut |event| deferred.push(event)) { + Err(error) if (error == EAGAIN || error == EBUSY) && !packet.is_published() => None, + result => Some(result), + } + } + + /// Scheduler admission of a compute packet. Shared capacity the packet lacks (a channel, a + /// work state, retirement-bound room, reopened publication) becomes a fence that the release + /// providing it signals, so publication finds it ready instead of sleeping for it. Errors + /// and anything prepare cannot judge are left to publication. + pub(super) fn prepare_compute(&self, packet: &Arc) -> Option { + if packet.early_result().is_some() + || packet.cancel_requested() + || packet.completion.status().get() != 0 + || self.admission_open().is_err() + { + return None; + } + let super::command::Validated::Compute { scratch, .. } = &packet.command else { return None; }; + let required = super::fw::compute_pool::required_blocks(self.shared.active_cores, scratch.pages()).ok()?; + loop { + let channels = self.shared.channels.observe(); + let progress = self.shared.progress.observe(); + let (domain, observed) = match self.compute_readiness(packet, required) { + Readiness::Ready => return None, + Readiness::Channel => (&self.shared.channels, channels), + Readiness::Progress => (&self.shared.progress, progress), + }; + self.shared.queue_events(); + match domain.wait(observed) { + Ok(Some(fence)) => return Some(fence), + Ok(None) => continue, + Err(_) => return None, + } + } + } + + fn compute_readiness(&self, packet: &Arc, required: usize) -> Readiness { + match packet.completion.work_state().and_then(|work| work.acquire()) { + Err(error) if error == EBUSY => return Readiness::Progress, + Err(_) => return Readiness::Ready, + Ok(_) => {} + } + // The steady state: this owner's channel is bound and installed. + if packet.completion.sources().is_none() { + if let Some(cell) = self.direct.lock().clone() { + let bound = cell.lock().is_some_and(|mut queue| { + let bound = queue.installed() + && !queue.spared_pending() + && queue.pool_fits(required) + && matches!(queue.matches(self.owner, queue.qid(), &self.context), Ok(true)); + if bound { + queue.claim(); + } + bound + }); + if bound { + self.hold_claim(Some(cell)); + return Readiness::Ready; + } + } + } + match self.sources_published(packet) { + Ok(false) => return Readiness::Progress, + Err(_) => return Readiness::Ready, + Ok(true) => {} + } + let lease = match self.shared.preparations.try_enter() { + Ok(lease) => lease, + Err(error) if error == EAGAIN => return Readiness::Progress, + Err(_) => return Readiness::Ready, + }; + let state = self.shared.state.lock(); + let Some(firmware) = state.as_deref() else { return Readiness::Ready; }; + if !lease.is_current() + || firmware.recovery.pending() + || super::recovery::Memory::recovery_state(&firmware.init).unwrap_or(0) != 0 + { + return Readiness::Progress; + } + firmware.queues.compute_readiness(self.owner, Some(&self.context), required) + } + + /// Replaces the prepared packet's channel claim; None releases it after publication, + /// cancellation or close. + pub(super) fn hold_claim(&self, cell: Option>) { + let old = core::mem::replace(&mut *self.claimed.lock(), cell); + let freed = old.as_ref().and_then(|old| old.lock()).is_some_and(|mut queue| { + let busy = !queue.channel_free(); + queue.release_claim(); + busy && queue.channel_free() + }); + if freed { + // A channel nobody prepares into any more is shareable now. + self.shared.channels.notify(); + } + } + + /// Called with the device mutex held after a successful publication: remembers the bound + /// channel for direct publication. + fn remember_direct(&self, registry: &Registry) { + let cell = registry.compute_cell(self.owner); + let mut direct = self.direct.lock(); + let current = match (&*direct, &cell) { + (Some(old), Some(new)) => Arc::ptr_eq(old, new), + (None, None) => true, + _ => false, + }; + if !current { + let old = core::mem::replace(&mut *direct, cell); + drop(direct); + drop(old); + } + } + + /// Publishes a dependency-free compute command from its submit ioctl when + /// its queue is already bound and nothing has to wait, as renders do. Any + /// wait leaves the command, unpublished, to its scheduler. + pub(super) fn try_compute_early(&self, packet: &Arc) -> bool { + if self.compute_backlog.load(Ordering::Acquire) != 0 || packet.has_dependencies() { + return false; + } + if !packet.begin_publication() { + packet.completion.fail_unpublished(ECANCELED); + packet.set_early_result(Err(ECANCELED)); + return true; + } + let mut deferred = match DeferredBatch::publication() { + Ok(deferred) => deferred, + Err(error) => { + if packet.completion.status().get() != 0 { + ::fail_vm(self, error); + } + packet.completion.fail_unpublished(error); + packet.set_early_result(Err(error)); + return true; + } + }; + let result = match self.compute_direct_packet(packet, &mut deferred) { + Some(result) => result, + None => self.compute_inline(packet, &mut deferred), + }; + if matches!(result, Err(error) if error == render::FALLBACK) && !packet.is_published() { + deferred.finish(); + packet.abandon_publication(); + return false; + } + let result = self + .finish_publication(packet, result, &mut deferred) + .map(|_| ()); + packet.set_early_result(result); + true + } + + fn compute_direct_packet( + &self, + packet: &Arc, + deferred: &mut DeferredBatch, + ) -> Option { + let super::command::Validated::Compute { scratch, .. } = &packet.command else { + return Some(Err(EINVAL)); + }; + let required = match super::fw::compute_pool::required_blocks(self.shared.active_cores, scratch.pages()) { + Ok(required) => required, + Err(error) => return Some(Err(error)), + }; + self.compute_direct(packet, required, deferred) + } + + /// compute_packet's publication section without its waits: every condition + /// it would sleep or retry for returns FALLBACK instead. + fn compute_inline( + &self, + packet: &Arc, + deferred: &mut DeferredBatch, + ) -> Result { + let super::command::Validated::Compute { scratch, .. } = &packet.command else { return Err(EINVAL); }; + let required = super::fw::compute_pool::required_blocks(self.shared.active_cores, scratch.pages())?; + if packet.cancel_requested() || self.admission_open().is_err() || packet.completion.status().get() != 0 { + return Err(render::FALLBACK); + } + let lease = self.enter_preparation(false)?; + match packet.completion.work_state()?.acquire() { + Err(error) if error == EBUSY => return Err(render::FALLBACK), + result => { + result?; + } + } + // A lone admission keeps the original latency-sensitive probe. + // Competing publishers otherwise spin on a binding they do not own. + if !self.context.compute_binding_hint() + && self.shared.compute_demand.load(Ordering::Relaxed) > 1 + { + return Err(render::FALLBACK); + } + let mut state = self.shared.state.lock(); + let firmware = (*state).as_deref_mut().ok_or(ENODEV)?; + if !lease.is_current() + || self.shared.crashed.load(Ordering::Acquire) + || firmware.recovery.pending() + || super::recovery::Memory::recovery_state(&firmware.init)? != 0 + { + return Err(render::FALLBACK); + } + firmware.replay_compute(deferred)?; + if !firmware.init.compute_vm_cached(self.context.vm()) + || !firmware.queues.compute_bound(self.owner, &self.context, required)? + { + return Err(render::FALLBACK); + } + let dependencies = firmware.queues.render_prefix(self.owner, packet)?; + match firmware.publish_compute(self.owner, packet.clone(), dependencies.as_slice(), deferred) { + Err(error) if (error == EBUSY || error == EAGAIN) && !packet.is_published() => { + Err(render::FALLBACK) + } + result => { + if result.is_ok() { + self.remember_direct(&firmware.queues); + } + result + } + } + } + + /// A scheduled dependency may refer to a previous recovery epoch. Do not + /// acquire consumer capacity until every live source is published now. + fn sources_published(&self, packet: &super::job::Packet) -> Result { + let Some(sources) = packet.completion.sources() else { return Ok(true); }; + sources.check()?; + let state = self.shared.state.lock(); + let firmware = state.as_deref().ok_or(ENODEV)?; + if firmware.recovery.pending() + || super::recovery::Memory::recovery_state(&firmware.init)? != 0 + { return Ok(false); } + for source in &sources.sources { + if source.submission.source_status()? { continue; } + if !firmware.queues.compute.iter().flatten() + .filter_map(|entry| entry.queue.as_deref()) + .any(|queue| queue.has_input_source(&source.submission, + packet.completion.status(), firmware.recovery.generation())) + { return Ok(false); } + } + Ok(true) } /// Called after taking this packet's publication claim. All waits and work @@ -1326,8 +2079,17 @@ impl Backend { ) -> Result { packet.check_dependencies()?; let super::command::Validated::Compute { scratch, .. } = &packet.command else { return Err(EINVAL); }; - let mut recovery_polls = 0; - let mut state_delay = 20i64; + let required = super::fw::compute_pool::required_blocks(self.shared.active_cores, scratch.pages())?; + if let Some(result) = self.compute_direct(packet, required, deferred) { + return result; + } + // A bound queue needs none of ensure_compute's admission passes. Try + // without them; the publication section below checks the same state. + // Every retry blocks on the release that ends its wait: retirement + // progress (work states, ring room, replays) and the reopening of + // publication after recovery both signal `progress`. + let mut bound = true; + let mut demand = None; let deadline = now_ns().saturating_add(WORK_RING_WAIT_NS); let check = || { if packet.cancel_requested() { return Err(ECANCELED); } @@ -1336,24 +2098,30 @@ impl Backend { Ok(()) }; let admission = Admission::new(deadline, &check); + let progress = &self.shared.progress; loop { admission.check()?; + let observed = progress.observe(); + if !self.sources_published(packet)? { + self.shared.queue_events(); + self.wait_release(progress, observed, &admission)?; + continue; + } // All DRM dependencies were satisfied before this scheduler callback. // Recheck on every retry: pressure may yield an idle binding between // admission and publication without changing this logical queue. - Backend::ensure_compute(self, scratch.pages(), &admission)?; - admission.check()?; + if !bound { + demand.get_or_insert_with(|| ComputeDemand::new(&self.shared)); + Backend::ensure_compute(self, scratch.pages(), &admission)?; + admission.check()?; + } let lease = self.shared.preparations.enter_until(&admission)?; // Work states free as this context's earlier commands retire. - // Wait for one under the packet's admission deadline, as render - // publication does, instead of failing the packet. match packet.completion.work_state()?.acquire() { Err(error) if error == EBUSY => { drop(lease); - admission.check()?; self.shared.queue_events(); - kernel::time::delay::fsleep(kernel::time::Delta::from_micros(state_delay)); - state_delay = (state_delay * 2).min(1000); + self.wait_release(progress, observed, &admission)?; continue; } result => { @@ -1376,18 +2144,17 @@ impl Backend { } let recovery_state = super::recovery::Memory::recovery_state(&firmware.init)?; if firmware.recovery.pending() || recovery_state != 0 { - if recovery_polls == RECOVERY_SETTLE_POLLS { - return Err(EBUSY); - } - recovery_polls += 1; drop(state); drop(lease); self.shared.queue_events(); - kernel::time::delay::fsleep(kernel::time::Delta::from_millis(1)); + self.wait_release(progress, observed, &admission)?; continue; } firmware.replay_compute(deferred)?; - if firmware.queues.compute(self.owner).is_none() { + if !firmware.init.compute_vm_cached(self.context.vm()) + || !firmware.queues.compute_bound(self.owner, &self.context, required)? + { + bound = false; drop(state); drop(lease); continue; @@ -1400,29 +2167,22 @@ impl Backend { dependencies.as_slice(), deferred, ); - if matches!(result, Err(EBUSY) | Err(EAGAIN)) - && !packet.is_published() - && (firmware.recovery.pending() - || super::recovery::Memory::recovery_state(&firmware.init)? != 0) + if !packet.is_published() + && (matches!(result, Err(EAGAIN)) + || (matches!(result, Err(EBUSY)) + && (firmware.recovery.pending() + || super::recovery::Memory::recovery_state(&firmware.init)? != 0))) { - if recovery_polls == RECOVERY_SETTLE_POLLS { - return Err(EBUSY); - } - recovery_polls += 1; + // Recovery or backpressure: nothing is firmware-visible. Other + // queues keep submitting and the event worker retires meanwhile. drop(state); drop(lease); self.shared.queue_events(); - kernel::time::delay::fsleep(kernel::time::Delta::from_millis(1)); + self.wait_release(progress, observed, &admission)?; continue; } - if matches!(result, Err(EAGAIN)) && !packet.is_published() { - // Backpressure owns no firmware-visible attempt. Other queues - // can submit and the event worker can retire while we sleep. - drop(state); - drop(lease); - self.shared.queue_events(); - kernel::time::delay::fsleep(kernel::time::Delta::from_millis(1)); - continue; + if result.is_ok() { + self.remember_direct(&firmware.queues); } return result; } @@ -1435,7 +2195,7 @@ impl super::Firmware { /// or an allocation-dependent collector. fn finish_stopped_queues(&mut self) { self.stop_render(); - for queue in self + for mut queue in self .queues .compute .iter_mut() @@ -1491,6 +2251,7 @@ impl super::Shared { dev_warn!(self.dev.as_ref(), "Stopped GPU retains outstanding preparation owners\n"); return; } + self.forget_port(); let firmware = self.state.lock().take(); if let Some(mut firmware) = firmware { firmware.finish_stopped_queues(); } } @@ -1504,6 +2265,7 @@ impl super::Shared { pub(super) fn stop_queues(&self) { self.preparations.wait_drained(); let _stopping = self.stop.lock(); + self.forget_port(); let firmware = { let mut state = self.state.lock(); if let Some(firmware) = (*state).as_deref_mut() { @@ -1543,20 +2305,41 @@ impl super::Firmware { deferred: &mut DeferredBatch, now: u64, ) -> Result> { - for queue in self + let mut freed = false; + let mut releasing = false; + for mut queue in self .queues .compute .iter_mut() .flatten() .filter_map(|entry| entry.queue.as_deref_mut()) { + let busy = !queue.channel_free(); queue.recycle_killed()?; queue.recycle_quarantined_idle()?; if let Some(binding) = queue.detach_exited_owner() { deferred.defer_binding(binding); } + freed |= busy && queue.channel_free(); + releasing |= queue.pool_release_pending(); + } + freed |= self.queues.observe_pool_releases(&self.init, deferred)? != 0; + let shared = &self.primary.state.shared; + // Edge notifications cover the transitions this driver makes; every + // event pass also rechecks the level, so capacity that appeared through + // another engine (render QIDs, closed logical queues, render-held work + // states) still ends a scheduler wait that has no timeout of its own. + if freed + || (shared.channels.has_waiters() + && self.queues.compute_readiness(0, None, 0) == Readiness::Ready) + { + shared.channels.notify(); + } + // A pool release has no completion event: each observation pass is + // the progress a growing owner waits for. + if releasing || shared.progress.has_waiters() { + shared.progress.notify(); } - self.queues.observe_pool_releases(&self.init, deferred)?; if self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()) .any(|queue| queue.pool_release_stalled(now)) { return Err(ETIMEDOUT); @@ -1568,6 +2351,11 @@ impl super::Firmware { // retained compute pool stays warm: render and new compute need it more. let cap = if super::freelist::lower_pressure() { 0 + } else if self.primary.state.shared.compute_demand.load(Ordering::Acquire) != 0 { + // A retired channel being requested by another runnable publisher + // is not an unused warm pool. Keep its existing backing through + // handoff; real allocation pressure above still forces reclamation. + super::kick::COMPUTE_CHANNELS } else { COMPUTE_WARM_CAP }; @@ -1594,12 +2382,19 @@ impl Registry { writeln!(f, "qos_ids used={} total=126", self.qos_ids.used())?; writeln!(f, "metrics_ids used={} total={}", self.metrics.used(), super::fw::buffer::METRICS_SLOTS)?; self.render.write_capacity(f)?; + let now = now_ns(); + let waits = || self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()) + .filter_map(|queue| queue.kill_waiting(now)); + compute_exit::write_kill_waits(f, waits().count(), waits().max().unwrap_or(0))?; + compute_exit::write_render_kills(f, self.teardown.kill_waits(now).count(), + self.teardown.kill_waits(now).max().unwrap_or(0))?; writeln!(f, "compute_slots used={} total={} reusable={} building={}", self.compute.iter().flatten().count(), self.compute.len(), self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).filter(|queue| queue.reusable()).count(), self.compute.iter().flatten().filter(|entry| entry.queue.is_none()).count())?; - writeln!(f, "compute_channels used={} total={}", - self.compute.iter().flatten().count(), super::kick::COMPUTE_CHANNELS)?; + writeln!(f, "compute_channels used={} fresh_fits={}", + self.compute.iter().flatten().count(), + u8::from(self.qids.compute_fits(self.admission.queues() as usize)))?; writeln!(f, "scheduler_teardown pending={}", self.teardown.len())?; for queue in self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()) { queue.write_capacity(f)?; @@ -1609,13 +2404,42 @@ impl Registry { } impl Backend { - pub(super) fn wait_capacity(&self, started: u64) -> Result { - let check = || self.admission_open(); - let admission = Admission::new(started.saturating_add(WORK_RING_WAIT_NS), &check); - self.wait_compute_capacity(&admission) + /// Compute admission without a channel: give back what pressure allows, then block until a + /// channel is released after `observed` (sampled before the failed check). + fn wait_channels(&self, observed: u64, admission: &Admission<'_>, reclaim_backing: bool) -> Result { + self.reclaim_capacity(admission, reclaim_backing)?; + self.wait_release(&self.shared.channels, observed, admission) + } + + /// Blocks until `domain` is signalled after `observed`, this packet's admission ends, or one + /// admission slice passes: cancellation and logical close have no release to wait for. + fn wait_release( + &self, + domain: &super::availability::Availability, + observed: u64, + admission: &Admission<'_>, + ) -> Result { + use kernel::dma_fence::RawDmaFence; + let slice = admission.slice_ms()?; + if let Some(fence) = domain.wait(observed)? { + // SAFETY: The owned fence keeps the raw DMA fence alive throughout the wait. + let waited = unsafe { + kernel::bindings::dma_fence_wait_timeout( + fence.raw(), + false, + kernel::time::msecs_to_jiffies(slice) as isize, + ) + }; + if waited < 0 { + return Err(Error::from_errno(waited as i32)); + } + } + admission.check() } - fn wait_compute_capacity(&self, admission: &Admission<'_>) -> Result { + /// Reclaim steps of a capacity wait that never sleep: observe consumed pool releases and, + /// under backing pressure, give idle pools back. + fn reclaim_capacity(&self, admission: &Admission<'_>, reclaim_backing: bool) -> Result { admission.check()?; let mut deferred = DeferredBatch::publication()?; let result: Result = (|| { @@ -1630,14 +2454,15 @@ impl Backend { if firmware.recovery.pending() || super::recovery::Memory::recovery_state(&firmware.init)? != 0 { return Ok(()); } - for queue in firmware.queues.compute.iter_mut().flatten().filter_map(|entry| entry.queue.as_deref_mut()) { - admission.check()?; - queue.share_idle()?; - } // Consumption, not publication or an elapsed timeout, frees backing. firmware.queues.observe_pool_releases(&firmware.init, &mut deferred)?; - firmware.queues.reclaim_idle_render_pools(&firmware.init, &mut deferred)?; - if !firmware.render_control_backpressured() { + // Pool eviction addresses backing allocation pressure, not channel + // occupancy. Normal idle expiry and the retained-pool cap still run + // in the event worker for either kind of admission wait. + if reclaim_backing { + firmware.queues.reclaim_idle_render_pools(&firmware.init, &mut deferred)?; + } + if reclaim_backing && !firmware.render_control_backpressured() { let (published, _) = firmware.queues.vacate_idle_pools(&firmware.init, 0, now_ns(), 0)?; if published != 0 { firmware.primary.notify(super::MSG_CONTROL_NOTIFY)?; } } @@ -1649,8 +2474,6 @@ impl Backend { Err(error) if error == EAGAIN => {} result => result?, } - admission.check()?; - kernel::time::delay::fsleep(kernel::time::Delta::from_millis(1)); admission.check() } } @@ -1669,12 +2492,17 @@ mod shared_capacity_tests { ) .unwrap(); let mut leases = KVec::with_capacity(crate::mmu::MAX_EXECUTION_CONTEXTS as usize, GFP_KERNEL).unwrap(); - // Physical compute channels are device-owned and bounded independently - // of how many logical clients have queued unsignalled work. - for owner in 1..=super::super::kick::COMPUTE_CHANNELS as u64 { + let mut slots = KVec::with_capacity(crate::mmu::MAX_EXECUTION_CONTEXTS as usize, GFP_KERNEL).unwrap(); + for _ in 0..crate::mmu::MAX_EXECUTION_CONTEXTS { + slots.push(registry.admission.reserve_queue().unwrap(), GFP_KERNEL).unwrap(); + } + // With every execution context open, compute keeps only the channels + // that leave each logical queue a dedicated render pair. + for owner in 1..=super::super::kick::COMPUTE_FLOOR as u64 { registry.reserve_compute(owner).unwrap(); } assert!(matches!(registry.reserve_compute(1000), Err(EAGAIN))); + assert!(registry.qids.render_fits(slots.len())); for owner in 1..=u64::from(crate::mmu::MAX_EXECUTION_CONTEXTS) { let lease = registry.admission.acquire_render(owner, None, u64::MAX, || Ok(())).unwrap(); registry.ensure_render_ids(lease.slot(), owner).unwrap(); @@ -1695,9 +2523,6 @@ mod shared_capacity_tests { GFP_KERNEL, ) .unwrap(); - for owner in 1..=super::super::kick::COMPUTE_CHANNELS as u64 { - registry.reserve_compute(owner).unwrap(); - } let mut slots = KVec::with_capacity(limit as usize, GFP_KERNEL).unwrap(); let mut leases = KVec::with_capacity(limit as usize, GFP_KERNEL).unwrap(); for owner in 1..=u64::from(limit) { @@ -1726,11 +2551,31 @@ mod shared_capacity_tests { leases.push(lease, GFP_KERNEL).unwrap(); } } - for owner in 1..=super::super::kick::COMPUTE_CHANNELS as u64 { + for owner in 1..=super::super::kick::COMPUTE_FLOOR as u64 { registry.reserve_compute(owner).unwrap(); } assert_eq!(leases.len(), 36); } + + #[test] + fn compute_grows_with_free_qids_and_narrows_queue_admission() { + let mut registry = KBox::init(Registry::new(1, Deflake::ONE_CLUSTER, [0; 2], &V26A428), GFP_KERNEL).unwrap(); + let mut slots = KVec::with_capacity(20, GFP_KERNEL).unwrap(); + for _ in 0..20 { + slots.push(registry.admission.reserve_queue().unwrap(), GFP_KERNEL).unwrap(); + } + // Sixteen open compute clients each keep their own channel. + for owner in 1..=16u64 { + registry.reserve_compute(owner).unwrap(); + } + // Growth stops where open queues plus headroom would lose their pairs. + let mut owner = 17; + while registry.reserve_compute(owner).is_ok() { + owner += 1; + } + assert!(registry.qids.render_fits(slots.len() + 24)); + assert!(!registry.qids.render_fits(crate::mmu::MAX_EXECUTION_CONTEXTS as usize)); + } } impl Backend { diff --git a/drivers/gpu/drm/asahi/g17/runtime/backend.rs b/drivers/gpu/drm/asahi/g17/runtime/backend.rs index 7d690470cd7b5..bf0f989f042a5 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/backend.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/backend.rs @@ -13,7 +13,14 @@ use kernel::{dma_fence::Fence, drm::sched, prelude::*, sync::Arc}; impl Backend { pub(super) fn release_backlog(&self, packet: &Packet) { if packet.take_backlog() { - self.backlog.fetch_sub(1, Ordering::AcqRel); + self.backlog_of(packet).fetch_sub(1, Ordering::AcqRel); + } + } + + pub(super) fn backlog_of(&self, packet: &Packet) -> &core::sync::atomic::AtomicU32 { + match packet.engine() { + Engine::Render => &self.backlog, + Engine::Compute => &self.compute_backlog, } } @@ -48,6 +55,7 @@ impl Backend { drop(state); drop(binding); self.finish_uninstalled_compute(retired)?; + self.shared.channels.notify(); self.shared.queue_events(); return Ok(()); } @@ -85,6 +93,7 @@ impl Backend { // A builder may own only an off-lock reservation when close arrives. // Record intent before scanning queues; every later install/rearm checks it. self.closed.store(true, Ordering::Release); + self.hold_claim(None); let mut state = self.shared.state.lock(); let Some(firmware) = (*state).as_deref_mut() else { return Ok(()); @@ -99,12 +108,63 @@ impl Backend { firmware.queues.release_render(self.owner)?; firmware.queues.admission.release_owner(self.owner); drop(state); + // Its idle channel, or its slot and never-published QID, is free. + self.shared.channels.notify(); self.shared.queue_events(); Ok(()) } } impl job::Backend for Backend { + fn input_sources(&self, inputs: &[crate::file::SyncItem]) + -> Option> + { + use crate::g17::{fence, sync_input}; + let mut heads = KVec::new(); + let mut sources: KVec = KVec::new(); + let mut publication_waits = KVec::new(); + for input in inputs.iter().filter_map(|input| input.fence.as_ref()) { + let status = job::fence_status(input); + if status > 0 { continue; } + if status < 0 { return None; } + let leaves = sync_input::collect(input)?; + for leaf in &leaves { + if leaf.status > 0 { continue; } + if leaf.status < 0 { return None; } + // SAFETY: Collection owns each leaf until this lookup finishes. + let submission = unsafe { fence::exported_source_raw(leaf.fence) }?; + if submission.source_status().ok()? { continue; } + if !submission.same_vm(self.context.status()) { return None; } + if sources.iter().any(|known| Arc::ptr_eq(&known.submission, &submission)) { + continue; + } + if sources.len() == crate::g17::fw::kick::KICK_BARRIERS_MAX { return None; } + let published = { + let state = self.shared.state.lock(); + let firmware = state.as_deref()?; + if self.shared.crashed.load(Ordering::Acquire) || firmware.recovery.pending() + || crate::g17::recovery::Memory::recovery_state(&firmware.init).ok()? != 0 + { return None; } + firmware.queues.compute.iter().flatten() + .filter_map(|entry| entry.queue.as_deref()) + .any(|queue| queue.has_input_source(&submission, self.context.status(), + firmware.recovery.generation())) + }; + if !published { + if submission.source_status().ok()? { continue; } + // The existing scheduler fence becomes ready after actual + // publication, not GPU completion. No binding loan or + // consumer hardware capacity is acquired while waiting. + publication_waits.push(submission.ready()?, GFP_KERNEL).ok()?; + } + sources.push(sync_input::Source { submission }, GFP_KERNEL).ok()?; + } + heads.push(input.clone(), GFP_KERNEL).ok()?; + } + if sources.is_empty() { return None; } + sync_input::Sources::new(heads, sources, publication_waits).ok() + } + fn feed(&self) -> Option> { Some(self.shared.feed.clone()) } @@ -156,8 +216,13 @@ impl job::Backend for Backend { /// Firmware kick parents order the same engine. Explicit cross-engine and /// input dependencies were already attached to the DRM scheduled job. - fn prepare(&self, _packet: &Packet) -> Option { - None + /// Compute and render admission wait for their shared capacity here, as a + /// fence. + fn prepare(&self, packet: &Arc) -> Option { + match packet.engine() { + Engine::Compute => self.prepare_compute(packet), + Engine::Render => self.admit_render(packet), + } } fn publish(&self, packet: Arc) -> Result { @@ -185,6 +250,9 @@ impl job::Backend for Backend { Engine::Render => self.render_packet(&packet, &mut deferred), } }; + if packet.engine() == Engine::Compute { + self.hold_claim(None); + } self.release_backlog(&packet); self.finish_publication(&packet, result, &mut deferred) } @@ -213,6 +281,9 @@ impl job::Backend for Backend { fn cancel(&self, packet: Arc) { self.release_backlog(&packet); + if packet.engine() == Engine::Compute { + self.hold_claim(None); + } if packet.request_cancel() { Deferred::Unpublished(packet.completion.clone(), ECANCELED).finish(); return; @@ -226,6 +297,16 @@ impl job::Backend for Backend { } } + fn long_job_beside(&self, _packet: &Arc) -> bool { + let state = self.shared.state.lock(); + let Some(firmware) = (*state).as_deref() else { + return false; + }; + firmware.queues.compute.iter().flatten() + .filter_map(|entry| entry.queue.as_deref()) + .any(|other| other.owner() != Some(self.owner) && other.long_job_running()) + } + fn defer_timeout(&self, packet: &Arc, renewals: u8) -> bool { if packet.engine() == Engine::Compute { return self.defer_compute_timeout(packet, renewals); @@ -274,11 +355,14 @@ impl job::Backend for Backend { } fn try_early(&self, packet: &Arc) -> bool { - self.try_render_early(packet) + match packet.engine() { + Engine::Render => self.try_render_early(packet), + Engine::Compute => self.try_compute_early(packet), + } } fn count_backlog(&self, packet: &Packet) { packet.count_backlog(); - self.backlog.fetch_add(1, Ordering::AcqRel); + self.backlog_of(packet).fetch_add(1, Ordering::AcqRel); } } diff --git a/drivers/gpu/drm/asahi/g17/runtime/compute_control.rs b/drivers/gpu/drm/asahi/g17/runtime/compute_control.rs index c407d9576e9c4..42282aea62023 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/compute_control.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/compute_control.rs @@ -20,11 +20,10 @@ impl Backend { return Ok(Decision::Own); }; firmware.scan_compute(None, &mut deferred)?; - let Some(queue) = firmware.queues.compute(self.owner) - .filter(|queue| queue.can_classify_timeout(packet)) else { - return Ok(Decision::Own); - }; - let Some(own) = queue.timeout_head() else { + // Each queue is locked on its own: release this one before visiting the others. + let Some(own) = firmware.queues.compute(self.owner) + .filter(|queue| queue.can_classify_timeout(packet)) + .and_then(|queue| queue.timeout_head()) else { return Ok(Decision::Own); }; let computes = firmware.queues.compute.iter().flatten() @@ -64,13 +63,20 @@ impl Backend { return Ok(None); }; firmware.scan_compute(None, &mut deferred)?; - let Some(queue) = firmware.queues.compute_mut(self.owner) else { + let Some(mut queue) = firmware.queues.compute_mut(self.owner) else { return Ok(None); }; if !queue.owns_packet(packet) { return Ok(None); } - queue.timeout(&mut |event| deferred.push(event)) + let status = queue.timeout(&mut |event| deferred.push(event)); + // A timed-out queue leaves ordinary retirement: announce what it withheld. + let withheld = queue.take_withheld_doorbell(); + drop(queue); + if let Some(doorbell) = withheld { + firmware.port.notify_compute(((doorbell >> 2) & 3) as u8, doorbell)?; + } + status })(); if result.as_ref().is_ok_and(|status| status.is_some()) { // VM status and peer settlement precede signalling this timeout. The backend @@ -91,8 +97,13 @@ impl Backend { return Ok(()); }; firmware.scan_compute(None, &mut deferred)?; - if let Some(queue) = firmware.queues.compute_mut(self.owner) { + if let Some(mut queue) = firmware.queues.compute_mut(self.owner) { queue.cancel(packet, &mut |event| deferred.push(event))?; + let withheld = queue.take_withheld_doorbell(); + drop(queue); + if let Some(doorbell) = withheld { + firmware.port.notify_compute(((doorbell >> 2) & 3) as u8, doorbell)?; + } } Ok(()) })(); diff --git a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs index 984b6c1f643eb..41960369b04a3 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/compute_exit.rs @@ -9,34 +9,147 @@ use crate::g17::{ fw::channels::{ContextKill, ControlRecord}, queue::compute::exit, recovery::Memory as _, + timeout::LIVENESS_NS, Firmware, MSG_CONTROL_NOTIFY, }; -use core::sync::atomic::{fence, Ordering}; +use core::sync::atomic::{fence, AtomicU32, AtomicU64, Ordering}; use kernel::{prelude::*, sync::Arc}; +/// Waits of failed contexts' kills for a moment without other contexts' compute in flight, +/// by bucket (under 10 ms, 100 ms, 1 s, 5 s, and longer) and the longest. +static KILL_WAITS: [AtomicU32; 5] = [const { AtomicU32::new(0) }; 5]; +static KILL_WAIT_MAX_NS: AtomicU64 = AtomicU64::new(0); +const KILL_WAIT_BOUNDS_NS: [u64; 4] = [10_000_000, 100_000_000, 1_000_000_000, 5_000_000_000]; +/// A failed context's kill waiting this long is reported, once per kill and at most once per +/// this interval across kills. +const SLOW_KILL_WAIT_NS: u64 = 5_000_000_000; +static SLOW_KILL_WARNED_NS: AtomicU64 = AtomicU64::new(0); +/// Failed contexts' kills published beside other contexts' compute under GPU memory pressure, +/// and when the last one was: at most one per [`PRESSED_KILL_INTERVAL_NS`], so the memory of +/// one is freed before another is considered. +static PRESSED_KILLS: AtomicU32 = AtomicU32::new(0); +static PRESSED_KILL_NS: AtomicU64 = AtomicU64::new(0); +const PRESSED_KILL_INTERVAL_NS: u64 = 250_000_000; + +/// Render-only kills (see `Firmware::publish_render_kill`): published, acknowledged, and +/// contexts refused (each counted once, by its first reason) because the scheduler page names +/// a queue outside the context, because the held pairs have no single owner, or because +/// admission could not hold them. +static RENDER_KILLS: [AtomicU32; 5] = [const { AtomicU32::new(0) }; 5]; +const RENDER_KILL_PUBLISHED: usize = 0; +const RENDER_KILL_ACKNOWLEDGED: usize = 1; +const RENDER_KILL_SCOPE: usize = 2; +const RENDER_KILL_OWNER: usize = 3; +const RENDER_KILL_HOLD: usize = 4; +/// A render-only candidate is left this long after close, as a quarantined compute queue is, +/// so that ordinary retirement goes first. +const RENDER_KILL_DELAY_NS: u64 = 250_000_000; + +/// The render queue a render-only kill names: one of the context's own render queues that +/// its scheduler page lists, never the reserved queues 0 and 1. +fn render_kill_target(listed: u128, own: u128) -> Option { + let candidates = listed & own & !0b11; + (candidates != 0).then(|| candidates.trailing_zeros() as u8) +} + +/// A closed context may be killed on its render pairs' behalf once it has been closed for +/// [`RENDER_KILL_DELAY_NS`], it was never killed, a pair of it holds executed but unstamped +/// commands and no compute queue still runs it, switches away from it or killed it. A second kill +/// of a killed context made the firmware fault on the killed pair's own tiling queue within a +/// millisecond of acknowledging it: with the pair's release held back, a context killed through +/// its compute queue still looked like a render-only candidate. +fn render_kill_eligible(age: u64, killed: bool, executed: bool, compute_clear: bool) -> bool { + age >= RENDER_KILL_DELAY_NS && !killed && executed && compute_clear +} + +/// The one admission owner of every pair a kill holds, if there is one. +fn hold_owner(owners: impl Iterator>) -> Option { + let mut owner = None; + for slot_owner in owners { + match (owner, slot_owner) { + (_, None) | (_, Some(0)) => return None, + (None, Some(next)) => owner = Some(next), + (Some(current), Some(next)) if current != next => return None, + _ => {} + } + } + owner +} + +fn refuse_render_kill(teardown: &mut crate::g17::teardown::Pending, index: usize, reason: usize) { + if teardown.refuse_render_kill(index) { + RENDER_KILLS[reason].fetch_add(1, Ordering::Relaxed); + } +} + +/// GPU memory is within an eighth of the device limit. A failed context holds its memory +/// until its kill is acknowledged, so kills waiting for other contexts' compute to pause can +/// push the device to its limit, and then every client's allocations fail: on Neo, +/// gnome-shell crashed on such an ENOMEM. A kill beside foreign compute risks one GPU +/// recovery instead. +fn memory_pressed() -> bool { + let ledger = &crate::memacct::LEDGER; + let limit = ledger.limit.load(Ordering::Relaxed); + limit != 0 + && ledger + .user_committed + .load(Ordering::Relaxed) + .saturating_add(ledger.kernel_resident.load(Ordering::Relaxed)) + > limit - limit / 8 +} + +fn record_kill_wait(waited: u64) { + let bucket = KILL_WAIT_BOUNDS_NS.iter().position(|bound| waited < *bound).unwrap_or(4); + KILL_WAITS[bucket].fetch_add(1, Ordering::Relaxed); + KILL_WAIT_MAX_NS.fetch_max(waited, Ordering::Relaxed); +} + +pub(in crate::g17) fn write_kill_waits(f: &mut kernel::fmt::Formatter<'_>, waiting: usize, longest: u64) + -> kernel::fmt::Result +{ + let count = |bucket: usize| KILL_WAITS[bucket].load(Ordering::Relaxed); + writeln!(f, "failed_kill_waits under_10ms={} under_100ms={} under_1s={} under_5s={} longer={} max_ms={} memory_pressed={}", + count(0), count(1), count(2), count(3), count(4), + KILL_WAIT_MAX_NS.load(Ordering::Relaxed) / 1_000_000, PRESSED_KILLS.load(Ordering::Relaxed))?; + writeln!(f, "failed_kills waiting={} longest_wait_ms={}", waiting, longest / 1_000_000) +} + +pub(in crate::g17) fn write_render_kills(f: &mut kernel::fmt::Formatter<'_>, waiting: usize, longest: u64) + -> kernel::fmt::Result +{ + let count = |index: usize| RENDER_KILLS[index].load(Ordering::Relaxed); + writeln!(f, "render_kills published={} acknowledged={} refused_scope={} refused_owner={} refused_hold={} waiting={} longest_wait_ms={}", + count(RENDER_KILL_PUBLISHED), count(RENDER_KILL_ACKNOWLEDGED), count(RENDER_KILL_SCOPE), + count(RENDER_KILL_OWNER), count(RENDER_KILL_HOLD), waiting, longest / 1_000_000) +} + impl super::Registry { /// ACK storage belongs to the already-retained transaction, so a full event /// batch cannot overflow a separate cache. No allocation or wait is needed. pub(super) fn acknowledge_compute_exit(&mut self, cookie: u64) { for entry in self.compute.iter_mut().flatten() { - if let Some(queue) = entry.queue.as_deref_mut() { + if let Some(mut queue) = entry.queue.as_deref_mut() { if queue.acknowledge_exit(cookie) { return; } } } + if self.teardown.acknowledge_render_kill(cookie) { + RENDER_KILLS[RENDER_KILL_ACKNOWLEDGED].fetch_add(1, Ordering::Relaxed); + crate::g17::trace::teardown(4, cookie, 0x100, 0); + } } } impl Firmware { pub(in crate::g17) fn service_compute_exit(&mut self, deferred: &mut DeferredBatch) -> Result { let recovery_closed = self.init.recovery_state()? == 0; - let view = crate::g17::qos::View::new(self.init.qos()?)?; + let port = &self.port; for entry in self.queues.compute.iter_mut().flatten() { - if let Some(queue) = entry.queue.as_deref_mut() { + if let Some(mut queue) = entry.queue.as_deref_mut() { queue.settle_exit( recovery_closed, - &mut |owner| self.queues.accounting.complete(&view, &[owner]), + &mut |owner| port.qos_complete(&[owner]), &mut |event| deferred.push(event), )?; } @@ -48,20 +161,38 @@ impl Firmware { // Publication has already retained each kill. Retry only the doorbell; // failed attempts do not extend its bounded polling window. for entry in self.queues.compute.iter_mut().flatten() { - if let Some(queue) = entry.queue.as_deref_mut() { + if let Some(mut queue) = entry.queue.as_deref_mut() { let _ = queue.announce_exit(now, || self.primary.notify(MSG_CONTROL_NOTIFY)); } } + let primary = &mut self.primary; + self.queues.teardown.announce_render_kills(|| primary.notify(MSG_CONTROL_NOTIFY)); + // Under memory pressure one kill skips the wait, at most one per interval. + let last = PRESSED_KILL_NS.load(Ordering::Relaxed); + let mut pressed = + (last == 0 || now.saturating_sub(last) >= PRESSED_KILL_INTERVAL_NS) && memory_pressed(); for index in 0..self.queues.teardown.len() { let Some((context, age)) = self.queues.teardown.closed(index, now) else { continue; }; - if !self.queues.render.teardown_idle(&context)? - || !self.queues.render.teardown_pools_released(&context) - { + if !self.queues.render.teardown_idle(&context)? { + self.queues.teardown.clear_kill_wait(index); continue; } - if let Err(error) = self.publish_compute_exit(&context, age, now) { + // A failed context's executed pairs keep their pool leases until the kill is + // acknowledged (`Pair::firmware_drained`), so its kill cannot wait for them. + let result = if self.compute_failed(&context) || self.queues.render.teardown_pools_released(&context) { + // Only a render-only kill's wait lives on the entry; a compute kill keeps its + // own on the queue. + self.queues.teardown.clear_kill_wait(index); + self.publish_compute_exit(&context, age, now, &mut pressed) + } else if self.render_kill_candidate(index, &context, age) { + self.publish_render_kill(index, &context, now, &mut pressed) + } else { + self.queues.teardown.clear_kill_wait(index); + Ok(()) + }; + if let Err(error) = result { dev_err!( self.primary.state.shared.dev.as_ref(), "Scheduler {} exit request failed: {:?}\n", @@ -73,7 +204,7 @@ impl Firmware { Ok(()) } - fn publish_compute_exit(&mut self, context: &Arc, age: u64, now: u64) -> Result { + fn publish_compute_exit(&mut self, context: &Arc, age: u64, now: u64, pressed: &mut bool) -> Result { let Some(index) = self.queues.compute.iter().position(|entry| { entry .as_ref() @@ -82,17 +213,48 @@ impl Firmware { }) else { return Ok(()); }; + let failed = self.compute_failed(context); if self.render_control_backpressured() { return Ok(()); } - let queue = self.queues.compute[index] - .as_ref() - .and_then(|entry| entry.queue.as_deref()) - .ok_or(EIO)?; - let Some(owner) = queue.exit_owner(context) else { + let beside = failed && self.foreign_compute_running(context, now); + if failed { + if beside && !*pressed { + let queue = self.queues.compute[index] + .as_mut() + .and_then(|entry| entry.queue.as_deref_mut()); + if let Some(mut queue) = queue { + let (waited, crossed) = queue.note_kill_wait(now, SLOW_KILL_WAIT_NS); + let last = SLOW_KILL_WARNED_NS.load(Ordering::Relaxed); + if crossed + && (last == 0 || now.saturating_sub(last) >= SLOW_KILL_WAIT_NS) + && SLOW_KILL_WARNED_NS + .compare_exchange(last, now.max(1), Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + dev_warn!( + self.primary.state.shared.dev.as_ref(), + "Scheduler {} kill has waited {} ms for other contexts' compute to pause\n", + context.id(), + waited / 1_000_000 + ); + } + } + return Ok(()); + } + } else if self.kill_unsafe(context, now) { return Ok(()); + } + let (owner, target) = { + let queue = self.queues.compute[index] + .as_ref() + .and_then(|entry| entry.queue.as_deref()) + .ok_or(EIO)?; + let Some(owner) = queue.exit_owner(context) else { + return Ok(()); + }; + (owner, queue.qid()) }; - let target = queue.qid(); let (mut own, mut foreign, slots) = self.queues.render.exit_scope(context); for entry in self.queues.compute.iter().flatten() { if let Some(queue) = entry.queue.as_deref() { @@ -119,7 +281,7 @@ impl Firmware { && !self .queues .admission - .hold_render_slots_for_exit_kill(owner, hold) + .hold_render_slots_for_exit_kill(owner, hold, self.queues.render.executed_slots(context) & hold) { return Ok(()); } @@ -128,13 +290,24 @@ impl Firmware { self.queues.admission.release_exit_kill_hold(hold); return if error == EAGAIN { Ok(()) } else { Err(error) }; } - // Publication is irrevocable even if its doorbell fails. Keep both - // the killed graph and the held render identities until processor stop. - let queue = self.queues.compute[index] + // Publication is irrevocable even if its doorbell fails. Keep the killed + // graph until processor stop, and the held render identities until this + // context's scheduler release is consumed or processors stop. + let mut queue = self.queues.compute[index] .as_mut() .and_then(|entry| entry.queue.as_deref_mut()) .ok_or(EIO)?; + if failed { + record_kill_wait(queue.take_kill_wait(now)); + } + if beside { + *pressed = false; + PRESSED_KILLS.fetch_add(1, Ordering::Relaxed); + PRESSED_KILL_NS.store(now.max(1), Ordering::Relaxed); + } queue.record_exit(cookie, now); + context.note_kill_published(); + crate::g17::trace::teardown(3, cookie, u64::from(context.id()), hold | u64::from(beside)); fence(Ordering::SeqCst); if let Err(error) = queue.announce_exit(now, || self.primary.notify(MSG_CONTROL_NOTIFY)) { dev_warn!( @@ -147,6 +320,157 @@ impl Firmware { Ok(()) } + /// A closed context whose compute side is clean, with render pairs whose commands all ran + /// but were never stamped (`Pair::executed_for_kill`). Its failed compute command was + /// retired on the firmware's fault record, so no compute queue is left to carry a kill, + /// yet those pairs are released only after one: without this the context, its pairs and + /// its memory stay until the processors stop. + fn render_kill_candidate(&self, index: usize, context: &Arc, age: u64) -> bool { + render_kill_eligible( + age, + self.queues.teardown.render_kill_published(index) || context.kill_published(), + self.queues.render.executed_slots(context) != 0, + self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).all(|queue| { + queue.idle_for_context(context) && !queue.switching_from(context) && !queue.exit_published(context) + }), + ) + } + + /// Kill a render-only candidate (`render_kill_candidate`). The rules of a failed + /// context's kill apply: the pairs are held for the kill and their memory is released only + /// once the scheduler page reports it, the kill waits for a moment without other contexts' + /// compute unless GPU memory is pressed, and it names only queues of this context. + fn publish_render_kill(&mut self, index: usize, context: &Arc, now: u64, pressed: &mut bool) -> Result { + if self.render_control_backpressured() { + return Ok(()); + } + let beside = self.foreign_compute_running(context, now); + if beside && !*pressed { + let (waited, crossed) = self.queues.teardown.note_kill_wait(index, now, SLOW_KILL_WAIT_NS); + let last = SLOW_KILL_WARNED_NS.load(Ordering::Relaxed); + if crossed + && (last == 0 || now.saturating_sub(last) >= SLOW_KILL_WAIT_NS) + && SLOW_KILL_WARNED_NS + .compare_exchange(last, now.max(1), Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + dev_warn!( + self.primary.state.shared.dev.as_ref(), + "Scheduler {} kill has waited {} ms for other contexts' compute to pause\n", + context.id(), + waited / 1_000_000 + ); + } + return Ok(()); + } + let (mut own, mut foreign, slots) = self.queues.render.exit_scope(context); + for entry in self.queues.compute.iter().flatten() { + if let Some(queue) = entry.queue.as_deref() { + let bit = 1u128 << queue.qid(); + if queue.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) { + own |= bit; + } else { + foreign |= bit; + } + } + } + let state = exit::State::read(context)?; + let Some(qids) = render_kill_target(state.qids, own).and_then(|target| state.scope(target, own, foreign)) else { + refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_SCOPE); + return Ok(()); + }; + let held = |slot: u8| slots & (1u64 << slot) != 0 && self.queues.render.qids_for_slot(slot) & qids != 0; + let range = 0..crate::hw::t8140::queues::RENDER_SLOTS as u8; + let hold = range.clone().filter(|slot| held(*slot)).fold(0u64, |hold, slot| hold | 1u64 << slot); + let owner = hold_owner(range.filter(|slot| held(*slot)).map(|slot| self.queues.render.slot_owner(slot))); + let Some(owner) = owner.filter(|_| hold != 0) else { + refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_OWNER); + return Ok(()); + }; + // As for a failed context's kill: the cookie first, so that running out of cookies + // leaves no admission hold behind. + let cookie = exit::next_cookie()?; + if !self + .queues + .admission + .hold_render_slots_for_exit_kill(owner, hold, self.queues.render.executed_slots(context) & hold) + { + refuse_render_kill(&mut self.queues.teardown, index, RENDER_KILL_HOLD); + return Ok(()); + } + let record = ControlRecord::ContextKill(ContextKill::new(context.scheduler_va(), cookie)); + if let Err(error) = Rings::publish_control(&self.init, &record) { + self.queues.admission.release_exit_kill_hold(hold); + return if error == EAGAIN { Ok(()) } else { Err(error) }; + } + // Publication is irrevocable. The held pairs stay held until this context's + // scheduler release is consumed (`release_killed_slots` at detach). + self.queues.teardown.record_render_kill(index, cookie, now)?; + context.note_kill_published(); + record_kill_wait(self.queues.teardown.take_kill_wait(index, now)); + if beside { + *pressed = false; + PRESSED_KILLS.fetch_add(1, Ordering::Relaxed); + PRESSED_KILL_NS.store(now.max(1), Ordering::Relaxed); + } + RENDER_KILLS[RENDER_KILL_PUBLISHED].fetch_add(1, Ordering::Relaxed); + crate::g17::trace::teardown(7, cookie, u64::from(context.id()), hold | u64::from(beside)); + fence(Ordering::SeqCst); + let primary = &mut self.primary; + self.queues.teardown.announce_render_kills(|| primary.notify(MSG_CONTROL_NOTIFY)); + Ok(()) + } + + /// Another context's compute command is in flight. On Neo, kills of failed contexts beside + /// other clients' compute occasionally made the firmware recover the GPU (twice in ten + /// minutes of a five-lane load with 67 faults), even with nothing freed before the kill's + /// acknowledgement; such a kill takes the next moment without foreign compute instead. + /// Render work is not waited for. A GPU that retired nothing for [`LIVENESS_NS`] has + /// stopped. + fn foreign_compute_running(&self, context: &Arc, now: u64) -> bool { + let stalled = crate::g17::recovery::since_retired(now).is_none_or(|since| since >= LIVENESS_NS); + !stalled + && self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).any(|queue| { + queue.in_flight() && !queue.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) + }) + } + + /// A compute command of `context` failed and the firmware holds commands behind it that it + /// never runs. `publish_compute_exit` is only reached once `teardown_idle` found all of + /// the context's render pairs idle; the pairs behind such a failure become idle when + /// their commands have executed (`Pair::firmware_drained`), so by then nothing of the + /// context runs any more. + fn compute_failed(&self, context: &Arc) -> bool { + self.queues.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref()).any(|queue| { + queue.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) && queue.fault_pending() + }) + } + + /// A kill while other contexts' commands run makes the firmware restart the + /// GPU, and repeated ones crash it; on an otherwise idle GPU it is safe. A + /// closed context's commands left alone retire like any others once the GPU + /// reaches them, so its queues stay quarantined and retained until then. Only + /// a GPU that retired nothing for [`LIVENESS_NS`] has stopped, and then the + /// kill proceeds. A context whose compute failed ([`Self::compute_failed`]) + /// is not left alone, since its held commands never retire: its kill waits + /// only for other contexts' compute ([`Self::foreign_compute_running`]). + fn kill_unsafe(&self, context: &Arc, now: u64) -> bool { + let stalled = crate::g17::recovery::since_retired(now).is_none_or(|since| since >= LIVENESS_NS); + if stalled { + return false; + } + self.queues.render.foreign_in_flight(context) + || self + .queues + .compute + .iter() + .flatten() + .filter_map(|entry| entry.queue.as_deref()) + .any(|queue| { + queue.in_flight() && !queue.context().is_some_and(|bound| Arc::ptr_eq(bound, context)) + }) + } + pub(in crate::g17) fn compute_exit_polling(&self, now: u64) -> bool { if self .queues @@ -158,6 +482,9 @@ impl Firmware { { return true; } + if self.queues.teardown.render_kill_polling(now, exit::POLL_NS) { + return true; + } (0..self.queues.teardown.len()).any(|index| { self.queues .teardown @@ -175,3 +502,36 @@ impl Firmware { }) } } + +#[kernel::macros::kunit_tests(asahi_g17_render_kill_target)] +mod render_kill_target_tests { + use super::{hold_owner, render_kill_eligible, render_kill_target, RENDER_KILL_DELAY_NS}; + + #[test] + fn a_killed_context_is_never_killed_again() { + assert!(render_kill_eligible(RENDER_KILL_DELAY_NS, false, true, true)); + assert!(!render_kill_eligible(RENDER_KILL_DELAY_NS, true, true, true)); + assert!(!render_kill_eligible(RENDER_KILL_DELAY_NS - 1, false, true, true)); + assert!(!render_kill_eligible(RENDER_KILL_DELAY_NS, false, false, true)); + assert!(!render_kill_eligible(RENDER_KILL_DELAY_NS, false, true, false)); + } + + #[test] + fn a_render_kill_holds_pairs_of_one_owner_only() { + assert_eq!(hold_owner([Some(5), Some(5)].into_iter()), Some(5)); + assert_eq!(hold_owner([Some(5), Some(6)].into_iter()), None); + assert_eq!(hold_owner([Some(5), None].into_iter()), None); + assert_eq!(hold_owner([Some(0)].into_iter()), None); + assert_eq!(hold_owner(core::iter::empty()), None); + } + + #[test] + fn a_render_kill_names_an_own_listed_queue_never_0_or_1() { + // Listed by the scheduler page but not the context's own: no target. + assert_eq!(render_kill_target(1 << 40, 1 << 41), None); + // The reserved queues are never named. + assert_eq!(render_kill_target(0b11, 0b11), None); + assert_eq!(render_kill_target(0b111 | 1 << 40, 0b111 | 1 << 40), Some(2)); + assert_eq!(render_kill_target(1 << 40 | 1 << 90, 1 << 90), Some(90)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/runtime/compute_replay.rs b/drivers/gpu/drm/asahi/g17/runtime/compute_replay.rs index d71f410c76fb4..32b9acef94c0f 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/compute_replay.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/compute_replay.rs @@ -3,7 +3,7 @@ //! Ordered republication of witnessed, unexecuted compute commands. use super::DeferredBatch; -use crate::g17::{completion::Deferred, qos, recovery, Firmware}; +use crate::g17::{completion::Deferred, recovery, Firmware}; use kernel::prelude::*; impl Firmware { @@ -31,12 +31,12 @@ impl Firmware { return Ok(()); } deferred.reserve_replays(commands)?; - let view = qos::View::new(self.init.qos()?)?; + let port = &self.port; for entry in self.queues.compute.iter_mut().flatten() { - if let Some(queue) = entry.queue.as_deref_mut() { + if let Some(mut queue) = entry.queue.as_deref_mut() { queue.prepare_replays( state, - |owner| self.queues.accounting.complete(&view, &[owner]), + |owner| port.qos_complete(&[owner]), &mut |event| deferred.push(event), )?; } @@ -46,20 +46,14 @@ impl Firmware { if self.recovery.pending() || recovery::Memory::recovery_state(&self.init)? != 0 { return Ok(()); } - let Some(queue) = self.queues.compute[index] + let Some((packet, owner)) = self.queues.compute[index] .as_ref() .and_then(|entry| entry.queue.as_deref()) + .and_then(|queue| Some((queue.replay_front()?, queue.owner()))) else { break; }; - let Some(packet) = queue.replay_front() else { - break; - }; - let owner = queue.owner(); - let outer_ready = self.init.work_ready( - packet.context.policy().priority(), - crate::g17::fw::queue::DataMaster::Compute, - )?; + let outer_ready = self.port.compute_ready(packet.context.policy().priority())?; if !outer_ready { let now = super::now_ns(); let since = *self.queues.replay_outer_wait.get_or_insert(now); @@ -90,7 +84,7 @@ impl Firmware { let recovery_wait = matches!(result, Err(EAGAIN) | Err(EBUSY)) && (self.recovery.pending() || recovery::Memory::recovery_state(&self.init)? != 0); - let queue = self.queues.compute[index] + let mut queue = self.queues.compute[index] .as_mut() .and_then(|entry| entry.queue.as_deref_mut()) .ok_or(EIO)?; @@ -110,6 +104,8 @@ impl Firmware { } } self.queues.replay_outer_wait = None; + // Publication behind these replays may proceed. + self.primary.state.shared.progress.notify(); Ok(()) } } diff --git a/drivers/gpu/drm/asahi/g17/runtime/events.rs b/drivers/gpu/drm/asahi/g17/runtime/events.rs index 4fd6e56741986..74eb6b7228c72 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/events.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/events.rs @@ -15,6 +15,9 @@ use kernel::{prelude::*, sync::Arc}; /// Wake interval while a tiler heap may still shrink: the native check period. const SHRINK_WAKE_NS: u64 = 1_000_000_000; +/// Longest interval between two event passes that run every housekeeping step while +/// completion records alone keep the worker busy. +const HOUSEKEEPING_NS: u64 = 1_000_000; struct Service<'a> { firmware: &'a mut Firmware, @@ -44,7 +47,7 @@ impl event::Transport for Service<'_> { role: Role, mut consume: impl FnMut(&mut Self, Event) -> Result, ) -> Result { - let mut events = channel::Events::new(role); + let mut events = channel::Events::new(role, self.firmware.primary.state.shared.variant.graph); while let Some(event) = events.next(&self.firmware.primary.state.shared.dev, &self.firmware.init)? { @@ -72,6 +75,15 @@ impl event::Queues for Service<'_> { fn context_killed(&mut self, cookie: u64) { self.firmware.queues.acknowledge_compute_exit(cookie); } + fn compute_fault(&mut self, qid: u8, stamp: u32) { + for entry in self.firmware.queues.compute.iter_mut().flatten() { + if let Some(mut queue) = entry.queue.as_deref_mut() { + if queue.qid() == qid { + queue.note_fault(stamp); + } + } + } + } fn flush_tvb_reply(&mut self) -> Result { self.firmware.flush_tvb_reply() } @@ -131,8 +143,8 @@ impl recovery::Memory for Service<'_> { fn clear_timestamps(&self) -> Result { self.firmware.init.clear_timestamps() } - fn entries(&self) -> Result { - self.firmware.init.entries() + fn entry(&self, index: usize) -> Result<(u32, u32)> { + self.firmware.init.entry(index) } fn wait_tick(&self) { self.firmware.init.wait_tick(); @@ -203,6 +215,25 @@ impl recovery::Queues for Service<'_> { } self.firmware.queues.classify_render(class) } + fn note_interrupted(&mut self, qid: u8, stamp: u32) -> recovery::Interrupted { + let render = self.firmware.queues.note_render_interrupted(qid, stamp); + if render != recovery::Interrupted::None { + return render; + } + self.firmware + .queues + .compute + .iter() + .flatten() + .filter_map(|entry| entry.queue.as_deref()) + .find(|queue| queue.qid() == qid) + .map_or(recovery::Interrupted::None, |queue| { + queue.note_interrupted(stamp) + }) + } + fn report_blamed_render(&self, qid: u8) { + self.firmware.queues.report_render_recovery(qid, &self.firmware.port); + } fn settle_blamed_render(&mut self, qid: u8, error: Error) { let result = self .firmware @@ -220,14 +251,34 @@ impl recovery::Queues for Service<'_> { let result = self.firmware.release_compute_witnesses(self.deferred); self.settlement_error = self.settlement_error.or(result.err()); } + fn announce_withheld(&mut self) { + let mut doorbells = [0u64; 4]; + for mut queue in self + .firmware + .queues + .compute + .iter_mut() + .flatten() + .filter_map(|entry| entry.queue.as_deref_mut()) + { + if let Some(doorbell) = queue.take_withheld_doorbell() { + doorbells[((doorbell >> 2) & 3) as usize] = doorbell; + } + } + // One doorbell per priority class announces every queue's work slots on that ring. + for doorbell in doorbells.into_iter().filter(|doorbell| *doorbell != 0) { + let result = self.firmware.port.notify_compute(((doorbell >> 2) & 3) as u8, doorbell); + self.settlement_error = self.settlement_error.or(result.err()); + } + } } impl Firmware { fn release_compute_witnesses(&mut self, deferred: &mut DeferredBatch) -> Result { let state = recovery::Memory::recovery_state(&self.init)?; - let view = crate::g17::qos::View::new(self.init.qos()?)?; + let port = &self.port; let registry = &mut self.queues; - for queue in registry + for mut queue in registry .compute .iter_mut() .flatten() @@ -235,7 +286,7 @@ impl Firmware { { queue.release_witnessed( state, - |owner| registry.accounting.complete(&view, &[owner]), + |owner| port.qos_complete(&[owner]), &mut |event| deferred.push(event), )?; } @@ -266,7 +317,10 @@ impl Firmware { /// Preparation admission is already closed and all builders have joined. pub(super) fn service_runtime_recovery(&mut self, deferred: &mut DeferredBatch) -> Result { - if self.recovery.restart_limit(super::now_ns()) { return Err(ETIMEDOUT); } + if self.recovery.restart_limit(super::now_ns()) { + pr_warn!("G17: restart storm limit reached\n"); + return Err(ETIMEDOUT); + } let shared = self.primary.state.shared.clone(); let mut service = Service { firmware: self, @@ -276,7 +330,11 @@ impl Firmware { }; let result = recovery::service(&mut service, || { shared.crashed.load(core::sync::atomic::Ordering::Acquire) - })?; + }); + // Publishers without the device mutex stamp their tickets with this + // mirror; they rejoin only after preparation admission reopens. + service.firmware.port.set_generation(service.firmware.recovery.generation()); + let result = result?; if let Some(error) = result.target_error { dev_warn!( shared.dev.as_ref(), @@ -319,8 +377,10 @@ impl crate::g17::Shared { }; deferred }; + crate::g17::trace::event(0); let result = self.runtime_event_turn(&mut deferred); deferred.finish(); + crate::g17::trace::event(2); self.queue_feed(); if let Some(firmware) = (*self.state.lock()).as_deref_mut() { firmware.queues.worker_deferred = Some(deferred); @@ -333,6 +393,10 @@ impl crate::g17::Shared { } fn runtime_event_turn(self: &Arc, deferred: &mut DeferredBatch) -> Result { + // Taken before consuming records: a request made during this pass gets the next one. + let requested = self + .housekeeping + .swap(false, core::sync::atomic::Ordering::AcqRel); let (recover, masks) = { let mut state = self.state.lock(); let Some(firmware) = (*state).as_deref_mut() else { @@ -348,6 +412,7 @@ impl crate::g17::Shared { (recover, masks) }; deferred.finish(); + crate::g17::trace::event(1); if recover { self.preparations.wait_drained_until(super::now_ns().saturating_add(super::WORK_RING_WAIT_NS))?; } @@ -392,19 +457,42 @@ impl crate::g17::Shared { .any(|queue| queue.in_flight()); let in_flight = compute || firmware.queues.render_in_flight(); let idle = firmware.idle_check(now, in_flight)?; - firmware.reclaim_drained_renders(deferred)?; - firmware.service_compute_exit(deferred)?; - let pools = firmware.service_compute_pools(deferred, now)?; + // Expiries, drained pairs, exits, pool and heap trimming and teardown give back what + // retirement freed; a completion needs none of them to retire or to signal. While work + // is in flight and nothing waits for capacity, a pass for firmware records alone skips + // them. Any requested pass, recovery, teardown, a waiter, the last retirement and, at + // the latest, the first pass after HOUSEKEEPING_NS run them all. + let housekeeping = requested + || recover + || !in_flight + || firmware.recovery.pending() + || !firmware.queues.teardown.is_empty() + || self.channels.has_waiters() + || self.progress.has_waiters() + || self.queues.has_waiters() + || self.renders.has_waiters() + || now.saturating_sub(self.housekeeping_ns.load(core::sync::atomic::Ordering::Relaxed)) + >= HOUSEKEEPING_NS; + let mut lingers = None; + let mut pools = None; let mut shrinking = false; - if !firmware.recovery.pending() { - firmware - .queues - .vacate_evicted_render_pools(&firmware.init, deferred)?; - shrinking = firmware - .queues - .shrink_render_heaps(&firmware.init, now, deferred); - } - { + if housekeeping { + self.housekeeping_ns + .store(now, core::sync::atomic::Ordering::Relaxed); + if !firmware.recovery.pending() { + lingers = firmware.expire_render_lingers(now)?; + } + firmware.reclaim_drained_renders(deferred)?; + firmware.service_compute_exit(deferred)?; + pools = firmware.service_compute_pools(deferred, now)?; + if !firmware.recovery.pending() { + firmware + .queues + .vacate_evicted_render_pools(&firmware.init, deferred)?; + shrinking = firmware + .queues + .shrink_render_heaps(&firmware.init, now, deferred); + } let mut reclaim = self.reclaim.lock(); firmware.service_teardown(&mut reclaim)?; if !reclaim.is_empty() { @@ -434,11 +522,14 @@ impl crate::g17::Shared { } else { idle }; - // A retained compute pool gives its backing back when it expires. - let idle = match (idle, pools) { - (Some(delay), Some(expiry)) => Some(delay.min(expiry)), - (delay, expiry) => delay.or(expiry), - }; + // A retained compute pool gives its backing back when it expires, and + // so does an idle render free-list registration. + let idle = [pools, lingers] + .into_iter() + .fold(idle, |idle, expiry| match (idle, expiry) { + (Some(delay), Some(expiry)) => Some(delay.min(expiry)), + (delay, expiry) => delay.or(expiry), + }); if let Some(delay) = idle { self.queue_idle(delay); } @@ -453,13 +544,18 @@ impl crate::g17::Shared { { // A full bounded pass must not depend on a fresh notification for // records that arrived while it was consuming the same ring. - self.queue_events(); + self.queue_event_records(); } let grow_ready = firmware.render_grow_ready(); drop(state); if grow_ready { self.queue_grow(); } + // Firmware ring room, consumed releases and teardown steps have no + // event of their own: recheck render publishers' conditions every pass. + if self.renders.has_waiters() { + self.renders.notify(); + } Ok(()) } } diff --git a/drivers/gpu/drm/asahi/g17/runtime/render.rs b/drivers/gpu/drm/asahi/g17/runtime/render.rs index 59db76b9f109a..4e7eaa2d8d246 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render.rs @@ -6,6 +6,7 @@ mod exit; mod dependencies; +mod direct; mod grow; mod observe; mod pools; @@ -16,6 +17,7 @@ mod settle; mod submit; pub(super) mod teardown; +pub(super) use submit::FALLBACK; use super::{DeferredBatch, Registry}; use crate::g17::{context::Context, fw::queue::DataMaster, job::Packet, kick, queue::render::Pair}; use crate::hw::t8140::queues::RENDER_SLOTS; @@ -74,6 +76,9 @@ pub(super) struct RenderState { pools: pools::Pools, grow: grow::Grow, retired: KVec, + /// Graphs of released contexts the firmware may still walk (see `detach_context`): kept + /// until the firmware stops, when this state is dropped. + kept_graphs: KVec, next_pm_generation: u64, } @@ -100,6 +105,7 @@ impl RenderState { pools: pools::Pools::new()?, grow: grow::Grow::new()?, retired: KVec::with_capacity(128, GFP_KERNEL)?, + kept_graphs: KVec::new(), next_pm_generation: 1, }) } @@ -298,6 +304,7 @@ impl Registry { if let Some(pair) = entry.pair.as_ref() { self.buffers.unreserve_owner(pair.buffer_id())?; } + self.note_build_room(); } return Ok(Some(UnusedRenderSpare { _entry: entry, @@ -325,6 +332,28 @@ impl Registry { Ok(None) } + /// The installed pair in `slot` belongs to `owner` and its context, is + /// neither borrowed nor settling a failure, and its pool has backing: the + /// state in which building the pair would do nothing. + pub(super) fn render_ready(&self, slot: u8, owner: u64, context: &Arc) -> Result { + let Ok(entry) = self.render.entry(slot) else { + return Ok(false); + }; + if entry.pending_error.is_some() + || entry.pending_timeout + || entry.constructing + || entry.preparing.is_some() + || entry.growing + || entry.parked.is_some() + { + return Ok(false); + } + let Some(pair) = entry.pair.as_ref() else { + return Ok(false); + }; + Ok(pair.matches(owner, context)? && !pair.pool().vacant()) + } + fn has_render_ids(&self, owner: u64) -> bool { self.render .entries @@ -333,11 +362,25 @@ impl Registry { .any(|(slot, entry)| entry.is_some() && self.admission.owns_slot(owner, slot)) } + /// Tell admission whether another render pair can still be built. + pub(super) fn note_build_room(&self) { + self.admission.note_build_room(self.qids.render_pair_fits()); + } + pub(super) fn ensure_render_ids(&mut self, slot: u8, owner: u64) -> Result { if owner == 0 || slot == 0 || usize::from(slot) >= RENDER_SLOTS { return Err(EINVAL); } - if self.render.entries[usize::from(slot)].is_none() { + if self.render.entries[usize::from(slot)].is_some() { + return Ok(()); + } + let built = self.build_render_ids(slot, owner); + self.note_build_room(); + built + } + + fn build_render_ids(&mut self, slot: u8, owner: u64) -> Result { + { let identity = self.identities.reserve(2)?.start; let tiling = self.qids.reserve(identity, DataMaster::Tiling, None)?; let fragment = match self.qids.reserve(identity + 1, DataMaster::Fragment, None) { @@ -381,6 +424,13 @@ impl Registry { /// ensure_render_ids cancels a first ID if the second cannot be reserved; /// validation, identity overflow, and cancellation errors still propagate. fn prepare_render_ids(&mut self, slot: u8, owner: u64, optional: bool) -> Result { + // A full queue table refuses a sibling without scanning it on every preparation. + if optional + && self.render.entries.get(usize::from(slot)).is_some_and(Option::is_none) + && !self.qids.render_pair_fits() + { + return Ok(false); + } match self.ensure_render_ids(slot, owner) { Ok(()) => Ok(true), Err(error) if optional && error == ENOSPC => Ok(false), @@ -507,27 +557,16 @@ impl crate::g17::queue::render::Host for Host<'_> { core::sync::atomic::fence(Ordering::Acquire); Ok(stamps) } - fn qos_publish( + fn qos_publish_pair( &mut self, - owner: crate::g17::qos::Owner, + owners: [crate::g17::qos::Owner; 2], scheduler: u64, policy: crate::g17::fw::queue::Policy, - ) -> Result { - use crate::g17::qos; - self.firmware.queues.accounting.publish( - &qos::View::new(self.firmware.init.qos()?)?, - owner, - scheduler, - policy, - qos::clock, - ) + ) -> Result<[crate::g17::qos::Publication; 2]> { + self.firmware.port.qos_publish_pair(owners, scheduler, policy) } fn qos_cancel(&mut self, publication: crate::g17::qos::Publication) -> Result { - use crate::g17::qos; - self.firmware - .queues - .accounting - .cancel(&qos::View::new(self.firmware.init.qos()?)?, publication) + self.firmware.port.qos_cancel(publication) } /// Idle descent is held for the whole first install. A point-in-time power /// check let the firmware gate the cores between the host's configuration @@ -558,7 +597,7 @@ impl crate::g17::queue::render::Host for Host<'_> { self.firmware.queues.qids.publish(id) } fn next_publication(&mut self) -> Result { - self.firmware.queues.qids.next_publication() + Ok(self.firmware.port.next_publication()) } fn publish_outer_pair( &mut self, @@ -599,6 +638,9 @@ impl crate::g17::queue::render::Host for Host<'_> { /// Pipe 0 (tiling) in bits 1:0, priority class in bits 3:2. fn notify_render(&mut self, priority: u8) -> Result { self.release_registration(); + crate::g17::trace::doorbell(crate::g17::trace::RENDER, || { + self.firmware.bridge.regs.g17_core_power() + }); let result = self .firmware .primary @@ -618,13 +660,20 @@ impl crate::g17::queue::render::Host for Host<'_> { generation: u64, buffer_id: u8, ) -> Result { - use crate::g17::qos; let queues = &mut self.firmware.queues; - let next = queues.render.pools.completed(pool_id, generation)?; + // Each final release is a control record the firmware processes before + // it returns to the work rings. While other pairs keep it busy, a + // registration left idle lingers instead, and the owner's next render + // reuses it. On an otherwise idle device the release is cheaper than + // reusing a registration that went idle, so it is sent at once. + // The pair retiring here is lent out of its entry meanwhile. + let linger = queues.render.entries.iter().flatten().any(|entry| { + entry.pair.as_ref().is_some_and(|pair| pair.published_in_flight()) + }); + let next = queues.render.pools.completed(pool_id, generation, linger)?; queues.buffers.can_release(buffer_id)?; - let view = qos::View::new(self.firmware.init.qos()?)?; if let Some(owners) = owners { - queues.accounting.complete(&view, &owners)?; + self.firmware.port.qos_complete(&owners)?; } queues.render.pools.commit_completed(pool_id, next); queues.buffers.release(buffer_id) @@ -657,6 +706,30 @@ impl crate::g17::Firmware { result } + /// Event-worker turn: ends free-list lingers that elapsed or whose owner + /// closed or moved on, publishes their releases, and returns the delay to + /// the next expiry, which the worker turns into a timed wake. + pub(in crate::g17) fn expire_render_lingers(&mut self, now: u64) -> Result> { + let render = &mut self.queues.render; + let entries = &render.entries; + let (ended, next) = render.pools.expire_lingers(now, |owner| { + entries.iter().flatten().any(|entry| { + !entry.closed + && entry + .pool + .as_ref() + .is_some_and(|pool| pools::Pools::owner(pool) == *owner) + }) + }); + if ended { + match self.release_render_lists() { + Err(error) if error == EAGAIN => {} + result => result?, + } + } + Ok(next) + } + fn release_render_lists(&mut self) -> Result { if self.queues.render.grow.reply.is_some() { return Err(EAGAIN); @@ -783,6 +856,22 @@ impl Registry { .find(|pair| pair.qids().contains(&qid)) .and_then(|pair| pair.oldest_spared()) } + /// Marks the interrupted group of the pair that owns `qid`. + pub(in crate::g17) fn note_render_interrupted( + &self, + qid: u8, + stamp: u32, + ) -> crate::g17::recovery::Interrupted { + self.render + .entries + .iter() + .flatten() + .filter_map(|entry| entry.pair.as_ref()) + .find(|pair| pair.qids().contains(&qid)) + .map_or(crate::g17::recovery::Interrupted::None, |pair| { + pair.note_interrupted(stamp) + }) + } pub(in crate::g17) fn visit_render_owners( &self, qid: u8, @@ -968,10 +1057,10 @@ impl Registry { |pair| pair.published_in_flight(), ); active - && entry.reservation.ids.into_iter().all(|id| { - let qid = usize::from(id.qid()); - entry.event_masks[qid / 64] & (1u64 << (qid % 64)) != 0 - }) + && crate::g17::queue::render::completion_noted( + entry.reservation.ids.map(kick::Id::qid), + entry.event_masks, + ) }) } pub(in crate::g17) fn defer_render_timeout( @@ -1056,6 +1145,8 @@ impl Registry { impl RenderState { pub(super) fn write_capacity(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { writeln!(f, "retired_render_bindings used={} total={}", self.retired.len(), self.retired.capacity())?; + writeln!(f, "firmware_linked_render_graphs kept={} leaked={}", self.kept_graphs.len(), + teardown::LEAKED_GRAPHS.load(core::sync::atomic::Ordering::Relaxed))?; self.pools.write_capacity(f)?; for (slot, entry) in self.entries.iter().enumerate() { if let Some(context) = entry.as_ref().and_then(|entry| entry.context.as_ref()) { diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/dependencies.rs b/drivers/gpu/drm/asahi/g17/runtime/render/dependencies.rs index 2b14b4548e0db..e150a10e2c9bc 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/dependencies.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/dependencies.rs @@ -12,6 +12,7 @@ impl Registry { packet: &Arc, ) -> Result { let mut frontier = Frontier::new(); + let compute = packet.engine() == crate::g17::job::Engine::Compute; for entry in self.render.entries.iter().flatten() { if let Some(pair) = entry.pair.as_ref() { pair.collect_dependencies( @@ -19,6 +20,7 @@ impl Registry { packet.completion.status(), packet.order.wait_through[0], &mut frontier, + compute, )?; } else if let Some(dependencies) = entry.borrowed_dependencies.as_ref() { dependencies.collect( @@ -26,6 +28,7 @@ impl Registry { packet.completion.status(), packet.order.wait_through[0], &mut frontier, + compute, )?; } } diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/direct.rs b/drivers/gpu/drm/asahi/g17/runtime/render/direct.rs new file mode 100644 index 0000000000000..36eae4816f967 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/runtime/render/direct.rs @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Steady-state render publication without the device mutex. +//! +//! The device mutex lends the owner's installed pair out, together with its +//! free-list preparation and a parameter-buffer reference, and takes it back. +//! Between the two, command preparation and publication take only the port: +//! its render publication lock (the render work rings' single producer and +//! the publication order of attachment hazards), QoS accounting, the stamps +//! and the doorbell. While the pair is lent, the event worker skips it, so no +//! retirement can complete its commands or their free-list references. + +use super::super::{Backend, DeferredBatch}; +use crate::g17::{ + job::Packet, + object::Allocator, + port::{Port, RenderRoom}, + queue::render::{Pair, PublicationLease, RenderAddresses}, +}; +use core::sync::atomic::Ordering; +use kernel::{ + prelude::*, + sync::{Arc, MutexGuard}, +}; + +/// A pair lent out for one publication, with what that publication holds. +pub(in crate::g17::runtime) struct Loan { + pair: Option>, + preparation: Option, + buffer: u8, + lease: PublicationLease, + addresses: RenderAddresses, + uat: Arc, +} + +/// Publication host of a lent pair. Registration, first-publication and +/// growth paths never run here: the loan requires an installed, registered +/// graph that needs no growth. +struct DirectHost<'a> { + port: &'a Port, + render: &'a MutexGuard<'a, ()>, + room: RenderRoom, + generation: u64, + activation: Option<(u16, u64)>, +} + +impl crate::g17::queue::render::Host for DirectHost<'_> { + fn next_publication(&mut self) -> Result { + Ok(self.port.next_publication()) + } + fn epoch(&self) -> Result<(u64, u32)> { + Ok((self.generation, self.port.recovery_state()?)) + } + fn stamps(&self, qids: [u8; 2]) -> Result<[u32; 2]> { + Ok([self.port.stamp(qids[0])?, self.port.stamp(qids[1])?]) + } + fn qos_publish_pair( + &mut self, + owners: [crate::g17::qos::Owner; 2], + scheduler: u64, + policy: crate::g17::fw::queue::Policy, + ) -> Result<[crate::g17::qos::Publication; 2]> { + self.port.qos_publish_pair(owners, scheduler, policy) + } + fn qos_cancel(&mut self, publication: crate::g17::qos::Publication) -> Result { + self.port.qos_cancel(publication) + } + fn install_pair(&mut self, _registration: &crate::g17::fw::kick::RenderRegistration) -> Result { + Err(EAGAIN) + } + fn publish_qid(&mut self, _id: crate::g17::kick::Id) -> Result { + // An installed graph published its identities with its first command. + Ok(()) + } + fn publish_outer_pair( + &mut self, + priority: u8, + fragment: &crate::g17::fw::channels::WorkSlot, + tiling: &crate::g17::fw::channels::WorkSlot, + commit: impl FnOnce() -> Result, + late_tiling: impl FnOnce(), + ) -> Result<[u32; 2]> { + if priority != self.room.priority() { + return Err(EINVAL); + } + self.port + .write_render_pair(self.render, &self.room, fragment, tiling, commit, late_tiling) + } + fn activate_render_pool(&mut self, id: u16, generation: u64) -> Result { + // The lease leaves preparation when the pair is returned; nothing can + // observe it in between because the pair is lent. + self.activation = Some((id, generation)); + Ok(()) + } + fn publish_retained_pair( + &mut self, + priority: u8, + _fragment: &crate::g17::fw::channels::WorkSlot, + _tiling: &crate::g17::fw::channels::WorkSlot, + next: [u32; 2], + ) -> Result { + if priority != self.room.priority() { + return Err(EINVAL); + } + self.port.expose_render_pair(self.render, &self.room, next) + } + /// Pipe 0 (tiling) in bits 1:0, priority class in bits 3:2. + fn notify_render(&mut self, priority: u8) -> Result { + self.port + .notify((0x83 << crate::g17::MSG_TYPE_SHIFT) | u64::from(priority) << 2) + } + fn note_submission(&mut self) -> Result { + // Counted, with full effort witnessed, before the pair was lent. + Ok(()) + } + fn complete_render( + &mut self, + _owners: Option<[crate::g17::qos::Owner; 2]>, + _pool_id: u16, + _generation: u64, + _buffer_id: u8, + ) -> Result { + Err(EINVAL) + } +} + +impl crate::g17::Firmware { + /// Lends `owner`'s pair in `slot` for a publication of `packet`, or None if + /// the pair is not in the steady state the direct path requires. + fn lend_render( + &mut self, + slot: u8, + owner: u64, + packet: &Arc, + priority: u8, + generation: u64, + ) -> Result> { + if self.recovery.pending() || self.recovery.generation() != generation { + return Ok(None); + } + let Ok(entry) = self.queues.render.entry_mut(slot) else { + return Ok(None); + }; + if !entry.published + || entry.closed + || entry.constructing + || entry.preparing.is_some() + || entry.growing + || entry.parked.is_some() + || entry.pending_error.is_some() + || entry.pending_timeout + { + return Ok(None); + } + let Some(pair) = entry.pair.as_mut() else { + return Ok(None); + }; + if !pair.matches(owner, &packet.context)? + || !pair.direct_ready(&packet.context, priority) + || pair.pool().vacant() + || !matches!(pair.host_growth_target(packet), Ok(None)) + { + return Ok(None); + } + let qids = pair.qids(); + let pool = pair.pool().clone(); + let buffer_id = pair.buffer_id(); + let addresses = RenderAddresses { + stamps: [ + self.init.stamp_addresses(qids[0])?, + self.init.stamp_addresses(qids[1])?, + ], + }; + let (buffer, buffer_token) = self.queues.buffers.acquire_owner(buffer_id)?; + let preparation = match self.queues.render.pools.prepare(&self.init, &pool) { + Ok(preparation) => preparation, + Err(error) => { + self.queues.buffers.release(buffer)?; + return if error == EAGAIN || error == EBUSY { + Ok(None) + } else { + Err(error) + }; + } + }; + let lease = PublicationLease { + free_list_generation: preparation.generation, + buffer_token, + }; + let pair = match self.queues.render.take_pair(slot, owner, packet) { + Ok(pair) => pair, + Err(error) => { + self.queues.render.pools.rollback(preparation); + self.queues.buffers.release(buffer)?; + return if error == EBUSY { Ok(None) } else { Err(error) }; + } + }; + // The selected scheduler is retained through release acknowledgement, + // as on the device-mutex path. + packet.context.mark_published(); + Ok(Some(Loan { + pair: Some(pair), + preparation: Some(preparation), + buffer, + lease, + addresses, + uat: self.uat.clone(), + })) + } + + /// Takes a lent pair back and settles what its publication held: the + /// free-list preparation is activated once published, rolled back with the + /// parameter-buffer reference otherwise. + fn return_render( + &mut self, + slot: u8, + loan: &mut Loan, + activation: Option<(u16, u64)>, + packet: &Arc, + ) -> Result { + self.queues.render.return_pair(slot, &mut loan.pair)?; + if self.queues.render.take_missed_event(slot) { + // The record is retired by the next pass's observation, like any completion. + self.primary.state.shared.queue_event_records(); + } + let entry = self.queues.render.entry_mut(slot)?; + // A failure or timeout recorded against the lent pair is settled by the + // event worker's collector, as after any other loan. + if entry.pending_error.is_some() || entry.pending_timeout { + self.primary.state.shared.queue_events(); + } + let pair = entry.pair.as_mut().ok_or(EIO)?; + if entry.closed { + pair.release_owner(); + } + if !packet.is_published() { + pair.discard_prepared(packet)?; + } + if let Some((id, generation)) = activation { + if self.queues.render.pools.activate(id, generation).is_err() { + self.primary.state.shared.lose_device_quietly(); + } + } + let preparation = loan.preparation.take().ok_or(EIO)?; + if packet.is_published() { + self.queues.render.entry_mut(slot)?.published = true; + } else { + self.queues.render.pools.rollback(preparation); + self.queues.buffers.release(loan.buffer)?; + } + Ok(()) + } +} + +impl Backend { + /// Publishes `packet` into the owner's installed pair with the device + /// mutex held only to lend the pair out and take it back. None leaves the + /// packet unpublished for the device-mutex path. + pub(in crate::g17::runtime) fn render_direct( + &self, + packet: &Arc, + slot: u8, + deferred: &mut DeferredBatch, + ) -> Option { + // A compute prefix needs the compute queue's state; leave it to the + // device-mutex path. + if packet.is_published() + || packet.cancel_requested() + || packet.completion.status().get() != 0 + || packet.order.wait_through[1].unwrap_or(0) != 0 + || self.admission_open().is_err() + { + return None; + } + let lease = self.shared.preparations.try_enter().ok()?; + let port = self.shared.port(&lease)?; + if self.shared.crashed.load(Ordering::Acquire) || port.recovery_state().ok()? != 0 { + return None; + } + // Full effort is applied by the device-mutex path when it is not already. + if !port.note_submission() { + return None; + } + match packet.completion.work_state().ok()?.acquire() { + Ok(_) => {} + Err(error) if error == EBUSY => return None, + Err(error) => return Some(Err(error)), + } + let priority = packet.context.policy().priority(); + let generation = port.generation(); + let dev = self.shared.drm().ok()?; + let mut loan = { + let mut state = self.shared.state.lock(); + let firmware = (*state).as_deref_mut()?; + if !lease.is_current() { + return None; + } + match firmware.lend_render(slot, self.owner, packet, priority, generation) { + Ok(Some(loan)) => loan, + Ok(None) => return None, + Err(error) => return Some(Err(error)), + } + }; + let mut activation = None; + let result = (|| { + let pair = loan.pair.as_mut().ok_or(EIO)?; + let alloc = Allocator { + dev: &dev, + uat: &loan.uat, + }; + pair.prepare(&alloc, packet, loan.addresses)?; + let render = port.render_lock(); + if !lease.is_current() { + return Err(EBUSY); + } + let room = port.observe_render_room(&render, priority)?.ok_or(EBUSY)?; + let mut dependencies = pair.own_dependencies(self.owner, packet)?; + if let Some(footprint) = packet.footprint.as_ref() { + dependencies.hazard_count = self.admission.publication_barriers( + self.owner, + slot, + footprint, + &mut dependencies.hazards, + )?; + } + let mut host = DirectHost { + port, + render: &render, + room, + generation, + activation: None, + }; + let result = pair.publish( + packet.clone(), + &dependencies, + loan.lease, + &mut host, + &mut |event| deferred.push(event), + ); + activation = host.activation; + result?; + // A recovery that started meanwhile may have discarded the commands. + if port.generation() != generation || port.recovery_state()? != 0 { + return Err(EIO); + } + Ok(()) + })(); + let returned = { + let mut state = self.shared.state.lock(); + match (*state).as_deref_mut() { + Some(firmware) => { + let returned = firmware.return_render(slot, &mut loan, activation, packet); + let grow_ready = firmware.render_grow_ready(); + drop(state); + if grow_ready { + self.shared.queue_grow(); + } + returned + } + // Shutdown joins every preparation lease before detaching the + // session, so this cannot happen while `lease` is held. + None => Err(ENODEV), + } + }; + self.shared.note_changed(); + drop(lease); + if let Err(error) = returned { + self.shared.lose_device_quietly(); + return Some(Err(error)); + } + match result { + Err(error) if (error == EAGAIN || error == EBUSY) && !packet.is_published() => None, + result => { + if result.is_ok() { + self.direct_slot.store(u32::from(slot), Ordering::Relaxed); + } + Some(result) + } + } + } +} diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs b/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs index c47110cb76fad..6c58f728b3eb5 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/exit.rs @@ -26,6 +26,27 @@ impl RenderState { (own, foreign, slots) } + /// Slots of `context`'s pairs whose commands all ran but stay unretired until its kill. + pub(in crate::g17::runtime) fn executed_slots(&self, context: &Arc) -> u64 { + let mut slots = 0; + for (slot, entry) in self.entries.iter().enumerate() { + if entry.as_ref().and_then(|entry| entry.pair.as_ref()) + .is_some_and(|pair| pair.executed_for_kill(context)) + { + slots |= 1u64 << slot; + } + } + slots + } + + /// The admission owner whose identities the pair in `slot` was built with. + pub(in crate::g17::runtime) fn slot_owner(&self, slot: u8) -> Option { + self.entries + .get(usize::from(slot)) + .and_then(Option::as_ref) + .map(|entry| entry.reservation.owner) + } + pub(in crate::g17::runtime) fn qids_for_slot(&self, slot: u8) -> u128 { self.entries .get(usize::from(slot)) diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/grow.rs b/drivers/gpu/drm/asahi/g17/runtime/render/grow.rs index e3cb174721aaa..277534328406e 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/grow.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/grow.rs @@ -14,8 +14,8 @@ use kernel::prelude::*; const RETRY_LIMIT: u16 = 1000; pub(super) struct Grow { - requests: KVec, - active: Option, + pub(super) requests: KVec, + pub(super) active: Option, pub(super) reply: Option<(TvbGrowRequest, bool, u16)>, release_attempts: u16, } @@ -280,7 +280,7 @@ impl crate::g17::Shared { return Err(EIO); } firmware.queues.render.grow.active = None; - self.changed.notify_all(); + self.note_changed(); if !lease.is_current() || request.halt_count != firmware.recovery.generation() { continue; } diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/observe.rs b/drivers/gpu/drm/asahi/g17/runtime/render/observe.rs index 4ede20d316c64..51682b43728c0 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/observe.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/observe.rs @@ -1,21 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT -//! Event-driven render snapshots outside the device mutex. The preparation -//! epoch and exact pair owner survive every read, cancellation and retry. +//! Event-driven render snapshots. A pair's completion memory is read and the +//! pair retired in one device-mutex hold, inside the preparation epoch. use super::super::DeferredBatch; -use crate::g17::queue::render::Pair; use core::sync::atomic::Ordering; use kernel::{prelude::*, sync::Arc}; impl crate::g17::Firmware { - fn take_render_observation(&mut self, start: usize) -> Result)>> { + /// The first slot from `start` whose pair has news to observe: published work in flight, the + /// fragment queue's notification seen (`completion_noted`), and an observation the pair accepts. + fn next_render_observation(&self, start: usize) -> Result> { if self.recovery.pending() { return Ok(None); } let state = crate::g17::recovery::Memory::recovery_state(&self.init)?; for slot in start..self.queues.render.entries.len() { - let Some(entry) = self.queues.render.entries[slot].as_mut() else { + let Some(entry) = self.queues.render.entries[slot].as_ref() else { continue; }; if entry.preparing.is_some() || entry.growing { @@ -26,9 +27,7 @@ impl crate::g17::Firmware { }; let qids = pair.qids(); if !pair.published_in_flight() - || !qids - .into_iter() - .all(|qid| entry.event_masks[usize::from(qid) / 64] & (1u64 << (qid % 64)) != 0) + || !crate::g17::queue::render::completion_noted(qids, entry.event_masks) { continue; } @@ -37,35 +36,13 @@ impl crate::g17::Firmware { self.init.stamp(qids[1])?.load(Ordering::Relaxed), ]; core::sync::atomic::fence(Ordering::Acquire); - let Some(packet) = pair.observation_packet(entry.event_masks, stamps, state)? else { - continue; - }; - entry.borrowed_active = pair.published_in_flight(); - entry.borrowed_dependencies = Some(pair.dependency_snapshot()); - entry.borrowed_switch = pair.switch_in_flight(); - entry.preparing = Some(packet); - return Ok(Some((slot, entry.pair.take().ok_or(EIO)?))); + if pair.observation_packet(entry.event_masks, stamps, state)?.is_some() { + return Ok(Some(slot)); + } } Ok(None) } - fn return_render_observation(&mut self, slot: usize, pair: &mut Option>) -> Result { - let returning = pair.as_ref().ok_or(EINVAL)?; - let entry = self.queues.render.entry_mut(slot as u8)?; - if entry.pair.is_some() - || entry.preparing.is_none() - || returning.slot() as usize != slot - || returning.qids() != entry.reservation.ids.map(crate::g17::kick::Id::qid) - { - return Err(EIO); - } - entry.pair = pair.take(); - entry.borrowed_dependencies = None; - entry.borrowed_switch = None; - entry.preparing = None; - Ok(()) - } - pub(super) fn settle_render_terminal( &mut self, slot: usize, @@ -122,57 +99,36 @@ impl crate::g17::Shared { Err(error) if error == EAGAIN || error == ENODEV => break, result => result?, }; - let loan = { - let mut state = self.state.lock(); - let Some(firmware) = (*state).as_deref_mut() else { - return Ok(()); - }; - if !epoch.is_current() || self.crashed.load(Ordering::Acquire) { - break; - } - firmware.take_render_observation(cursor)? + // Selecting, reading and retiring the front share one hold: a + // separate hold for the read would let publishers and the + // retirement contend twice per observed pair. + let mut state = self.state.lock(); + let Some(firmware) = (*state).as_deref_mut() else { + return Ok(()); }; - let Some((slot, pair)) = loan else { + if !epoch.is_current() + || firmware.recovery.pending() + || self.crashed.load(Ordering::Acquire) + { + break; + } + let Some(slot) = firmware.next_render_observation(cursor)? else { break; }; if slot != cursor { remaining = crate::hw::t8140::queues::RENDER_DEPTH; retries = 0; } - let observed = pair.observe_front(); - let mut pair = Some(pair); - let mut state = self.state.lock(); - // Return installed ownership before examining read errors or epoch. - let restored = (*state) - .as_deref_mut() - .ok_or(ENODEV) - .and_then(|firmware| firmware.return_render_observation(slot, &mut pair)); - if let Err(error) = restored { - // A failed invariant must not drop firmware-owned storage. Join - // the other preparers only after releasing our own epoch, and - // keep this detached owner until both processors have stopped. - drop(state); - drop(epoch); - self.lose_device_quietly(); - self.stop_runtime(); - if let Some(mut pair) = pair { - pair.stopped(&mut |event| { - event.finish(); - Ok(()) - })?; - } - return Err(error); - } - let firmware = (*state).as_deref_mut().ok_or(EIO)?; - self.changed.notify_all(); + let observed = firmware.queues.render.entries[slot] + .as_ref() + .and_then(|entry| entry.pair.as_ref()) + .ok_or(EIO)? + .observe_front(); + // Publishers waiting for render capacity recheck once this hold ends. + self.note_changed(); if firmware.render_grow_pending() || firmware.queues.render.take_missed_event(slot as u8) { - self.queue_events(); - } - if !epoch.is_current() - || firmware.recovery.pending() - || self.crashed.load(Ordering::Acquire) - { - break; + // Observation and the growth check run on every pass. + self.queue_event_records(); } let result = (|| { let state = crate::g17::recovery::Memory::recovery_state(&firmware.init)?; diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/pools.rs b/drivers/gpu/drm/asahi/g17/runtime/render/pools.rs index 4713eb5b6b152..1e3354d90dbfb 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/pools.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/pools.rs @@ -6,7 +6,7 @@ use crate::g17::{ channel::Rings, - freelist::{Lease, Owner, RenderPool}, + freelist::{Lease, Owner, RenderPool, LINGER_NS}, fw::{ channels::{ControlRecord, FreeListCompletion}, initdata::PagePoolDescriptor, @@ -49,7 +49,7 @@ impl Pools { Ok(slot) } - fn owner(pool: &RenderPool) -> Owner { + pub(super) fn owner(pool: &RenderPool) -> Owner { Owner { serial: pool.serial(), epoch: pool.epoch(), @@ -154,12 +154,12 @@ impl Pools { } /// Preflight a paired retirement before QoS or PB reference accounting. - pub(super) fn completed(&self, id: u16, generation: u64) -> Result { + pub(super) fn completed(&self, id: u16, generation: u64, linger: bool) -> Result { let mut next = self.leases[Self::slot(id)?].ok_or(EIO)?; if next.generation() != generation { return Err(EIO); } - next.complete_render()?; + next.complete_render(linger)?; Ok(next) } @@ -169,6 +169,32 @@ impl Pools { self.leases[usize::from(id)] = Some(next); } + /// Ends the linger of every idle registration that `keep` (an open owner + /// still bound to the pool) does not retain or whose linger has elapsed. + /// Returns whether a release became pending, and the next expiry delay. + pub(super) fn expire_lingers( + &mut self, + now: u64, + keep: impl Fn(&Owner) -> bool, + ) -> (bool, Option) { + let mut ended = false; + let mut next: Option = None; + for lease in self.leases[FIRST_RENDER_POOL..].iter_mut().flatten() { + let Some(since) = lease.lingering() else { + continue; + }; + let age = now.saturating_sub(since); + if age >= LINGER_NS || !keep(&lease.owner()) { + lease.end_linger(); + ended = true; + } else { + let delay = LINGER_NS - age; + next = Some(next.map_or(delay, |next| next.min(delay))); + } + } + (ended, next) + } + pub(super) fn pending(&self) -> bool { self.leases .iter() @@ -186,7 +212,7 @@ impl Pools { fence(Ordering::Acquire); let consumer = init.control_consumer()?.load(Ordering::Relaxed); let producer = init.control_producer()?.load(Ordering::Relaxed); - for lease in self.leases.iter_mut().flatten() { + for lease in self.leases[FIRST_RENDER_POOL..].iter_mut().flatten() { lease.observe_releases(consumer, producer)?; } Ok(()) @@ -199,7 +225,7 @@ impl Pools { init: &InitData, mut announce: impl FnMut() -> Result, ) -> Result { - for lease in self.leases.iter_mut().flatten() { + for lease in self.leases[FIRST_RENDER_POOL..].iter_mut().flatten() { let Some(release) = lease.pending_release() else { continue; }; diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/prepare.rs b/drivers/gpu/drm/asahi/g17/runtime/render/prepare.rs index e2034c3176595..62ca7895a86f8 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/prepare.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/prepare.rs @@ -426,7 +426,7 @@ impl super::super::Backend { } firmware.render_grow_ready() }; - self.shared.changed.notify_all(); + self.shared.note_changed(); if grow_ready { self.shared.queue_grow(); } @@ -548,7 +548,7 @@ impl super::super::Backend { .release(id)?; } } - self.shared.changed.notify_all(); + self.shared.note_changed(); if grow_ready { self.shared.queue_grow(); } @@ -593,6 +593,10 @@ impl super::super::Backend { pub(super) fn ensure_render(&self, packet: &Arc, slot: u8, sleep: bool) -> Result { self.build_render_pair(slot, packet, false, sleep)?; + // An owner has exactly one pair (kick.rs asserts it): no sibling to warm. + if crate::hw::t8140::queues::RENDER_PAIRS_PER_OWNER == 1 { + return Ok(()); + } let sibling = { let state = self.shared.state.lock(); (*state) @@ -711,7 +715,7 @@ impl super::super::Backend { .ok_or(EIO)? .release_owner(); } - self.shared.changed.notify_all(); + self.shared.note_changed(); let published = (|| { let entry = firmware.queues.render.entry(slot)?; if entry.pending_error.is_some() || entry.pending_timeout { diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs b/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs index 3a5b042fff683..7a6d987591c76 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/recovery.rs @@ -6,6 +6,22 @@ use crate::g17::runtime::Registry; use kernel::prelude::*; impl Registry { + pub(in crate::g17::runtime) fn report_render_recovery(&self, qid: u8, port: &crate::g17::port::Port) { + pr_warn!("G17: recovery grow pending={} active={:?} reply={:?}\n", + self.render.grow.requests.len(), self.render.grow.active, self.render.grow.reply); + for entry in self.render.entries.iter().flatten().filter(|entry| { + entry.reservation.ids.iter().any(|id| id.qid() == qid) + }) { + if let Some(pair) = &entry.pair { + let qids = pair.qids(); + pair.report_recovery((|| Ok([port.stamp(qids[0])?, port.stamp(qids[1])?]))()); + } else { + pr_warn!("G17: recovery render queue={} unavailable borrowed={} constructing={}\n", + qid, entry.borrowed_active, entry.constructing); + } + } + } + pub(in crate::g17::runtime) fn render_pass_started(&self, qid: u8, stamp: u64) -> Option { self.render.entries.iter().flatten() .filter_map(|entry| entry.pair.as_ref()) diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs b/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs index b77fc13b7ed2f..e6379178138d1 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs @@ -8,22 +8,18 @@ use crate::g17::{ queue::admission::{RenderLease, Request}, }; use core::sync::atomic::Ordering; -use kernel::{ - prelude::*, - sync::Arc, - time::{delay::fsleep, Delta}, -}; +use kernel::{prelude::*, sync::Arc}; /// Internal result of a publication attempted from the submitting ioctl that /// met contention before anything became visible: the scheduler publishes the /// command instead. Never returned to userspace. -pub(super) const FALLBACK: Error = EINPROGRESS; +pub(in crate::g17::runtime) const FALLBACK: Error = EINPROGRESS; impl Backend { /// The submitting ioctl holds the logical queue's mutex and never sleeps /// for contention or a closed preparation gate: it returns FALLBACK, and /// the scheduler's worker publishes the command and owns the waits. - pub(super) fn enter_preparation(&self, sleep: bool) -> Result { + pub(in crate::g17::runtime) fn enter_preparation(&self, sleep: bool) -> Result { if sleep { return self.shared.preparations.enter(false); } @@ -48,18 +44,20 @@ impl Backend { /// Reserve identities before attaching a lease to the packet. An empty /// logical slot must not hide reusable physical channels when the shared /// queue-ID namespace is full. + /// Also reports whether the owner's installed pair is ready to take the + /// command, so that the publication skips graph construction. fn acquire_render_with_ids( &self, packet: &Arc, mut next: Option, sleep: bool, - ) -> Result { - let pool = { - let state = self.shared.state.lock(); - (*state).as_deref().ok_or(ENODEV)?.queues.admission.clone() - }; + ) -> Result<(RenderLease, bool)> { + let pool = &self.admission; let started = super::super::now_ns(); let deadline_ns = started.saturating_add(u64::from(crate::g17::job::TIMEOUT_MS) * 1_000_000); + // A capacity refusal samples the render domain and retries once; only + // a refusal after that sample waits, so no release can be missed. + let mut observed = None; loop { self.admission_open()?; if super::super::now_ns() >= deadline_ns { return Err(EBUSY); } @@ -97,7 +95,7 @@ impl Backend { .reclaim_unused_render_spare(Some(self.owner), false)? }; if reclaimed.is_none() { - self.wait_render_capacity(started, sleep)?; + self.wait_render_capacity(&mut observed, started, sleep)?; } drop(reclaimed); continue; @@ -109,7 +107,10 @@ impl Backend { let mut state = self.shared.state.lock(); let firmware = (*state).as_deref_mut().ok_or(ENODEV)?; match firmware.queues.ensure_render_ids(slot, self.owner) { - Ok(()) => return Ok(lease), + Ok(()) => { + let ready = firmware.queues.render_ready(slot, self.owner, &self.context)?; + return Ok((lease, ready)); + } Err(error) if error == ENOSPC => { let reclaimed = firmware.queues.reclaim_unused_render_spare(None, true)?; if reclaimed.is_some() { @@ -130,8 +131,9 @@ impl Backend { .reclaim_unused_render_spare(Some(self.owner), true)?; let ready = firmware.queues.has_render_ids(self.owner); drop(state); - if reclaimed.is_none() && !ready { - self.wait_render_capacity(started, sleep)?; + // A full queue table makes admission reuse a free killed pair instead. + if reclaimed.is_none() && !ready && !pool.killed_pair_free() { + self.wait_render_capacity(&mut observed, started, sleep)?; } drop(reclaimed); } @@ -140,31 +142,178 @@ impl Backend { } } - /// A pair is borrowed, still settling, or waiting for the release of the - /// scheduler it last ran for to be consumed and the pair parked. The event - /// worker observes both, so kick it on every poll; time out only after the - /// same bound as a capacity wait. - fn wait_render_busy(&self, since: &mut Option, sleep: bool) -> Result { + /// Scheduler admission of a render packet. A work state of its context or + /// a pair admission it lacks becomes a fence that the return providing it + /// signals, so run_job finds both ready instead of waiting for them. + /// Errors, and anything else publication may wait for, are left to it. + pub(in crate::g17::runtime) fn admit_render(&self, packet: &Arc) -> Option { + if packet.early_result().is_some() + || packet.cancel_requested() + || packet.completion.status().get() != 0 + || self.admission_open().is_err() + { + return None; + } + // Each check runs once more after sampling its domain, so the common + // case takes no domain lock and no return between check and wait is lost. + let work = packet.completion.work_state().ok()?; + loop { + match work.acquire() { + Err(error) if error == EBUSY => { + let states = work.returns().observe(); + match work.acquire() { + Err(error) if error == EBUSY => match work.returns().wait(states) { + Ok(Some(fence)) => return Some(fence), + Ok(None) => continue, + Err(_) => return None, + }, + Err(_) => return None, + Ok(_) => {} + } + } + Err(_) => return None, + Ok(_) => {} + } + let request = Request::of(packet); + if self.admission.admits_render(self.owner, request) { + return None; + } + let observed = self.shared.renders.observe(); + if self.admission.admits_render(self.owner, request) { + return None; + } + self.shared.queue_events(); + match self.shared.renders.wait(observed) { + Ok(Some(fence)) => return Some(fence), + Ok(None) => continue, + Err(_) => return None, + } + } + } + + /// Conditions which end a publication that has not become visible yet. + fn publication_open(&self, packet: &Packet) -> Result { + self.admission_open()?; + if packet.cancel_requested() { + return Err(ECANCELED); + } + if packet.completion.status().get() != 0 { + return Err(EIO); + } + Ok(()) + } + + /// Blocks on `domain` until a notification after `observed`, or the + /// deadline, which only bounds the wait. Slices of at most 10 ms let the + /// publisher observe cancellation, which has no notification here. + fn wait_domain( + &self, + domain: &crate::g17::availability::Availability, + observed: u64, + deadline_ns: u64, + packet: &Packet, + ) -> Result { + self.publication_open(packet)?; + let remaining = deadline_ns.saturating_sub(super::super::now_ns()); + if remaining == 0 { + return Err(ETIMEDOUT); + } + domain.block(observed, remaining.div_ceil(1_000_000).min(10) as u32, false) + } + + /// A pair is borrowed, still settling, waiting for firmware room or for the + /// release of the scheduler it last ran for. Every pair return, retirement + /// and teardown step signals the render domain, and each event pass does + /// while a publisher waits; the deadline is only the failure bound. + fn wait_render_busy( + &self, + since: &mut Option, + observed: &mut Option, + sleep: bool, + packet: &Packet, + ) -> Result { if !sleep { return Err(FALLBACK); } let now = super::super::now_ns(); - if now.saturating_sub(*since.get_or_insert(now)) >= super::super::WORK_RING_WAIT_NS { - return Err(ETIMEDOUT); - } + let deadline = since.get_or_insert(now).saturating_add(super::super::WORK_RING_WAIT_NS); + // Sample, then retry once: only a refusal after the sample waits. + let Some(sample) = observed.take() else { + *observed = Some(self.shared.renders.observe()); + return Ok(()); + }; self.shared.queue_events(); - fsleep(Delta::from_micros(200)); - Ok(()) + self.wait_domain(&self.shared.renders, sample, deadline, packet) } - /// A closing owner may still hold its pair until exact retirement. Wait - /// outside both the device mutex and preparation gate so the event worker - /// and recovery can return capacity. No packet is firmware-visible here. - fn wait_render_capacity(&self, started: u64, sleep: bool) -> Result { + /// Render capacity is short: return what idle owners hold, then block until + /// a pair, lease or owner returns. No packet is firmware-visible here, and + /// neither the device mutex nor a preparation lease is held while waiting. + fn wait_render_capacity(&self, observed: &mut Option, started: u64, sleep: bool) -> Result { if !sleep { return Err(FALLBACK); } - self.wait_capacity(started) + if self.reclaim_render_capacity()? { + *observed = None; + return Ok(()); + } + let Some(sample) = observed.take() else { + *observed = Some(self.shared.renders.observe()); + return Ok(()); + }; + self.shared.queue_events(); + self.admission_open()?; + let remaining = started + .saturating_add(super::super::WORK_RING_WAIT_NS) + .saturating_sub(super::super::now_ns()); + if remaining == 0 { + return Err(EBUSY); + } + self.shared + .renders + .block(sample, remaining.div_ceil(1_000_000).min(10) as u32, false) + } + + /// Gives idle render backing back under capacity pressure. Returns whether + /// anything was returned, in which case the caller retries at once. + fn reclaim_render_capacity(&self) -> Result { + let mut deferred = DeferredBatch::publication()?; + let result: Result = (|| { + let lease = self.shared.preparations.try_enter()?; + let mut state = self.shared.state.lock(); + let firmware = (*state).as_deref_mut().ok_or(ENODEV)?; + if !lease.is_current() || self.shared.crashed.load(Ordering::Acquire) { + return Err(EAGAIN); + } + if firmware.recovery.pending() + || crate::g17::recovery::Memory::recovery_state(&firmware.init)? != 0 + { + return Ok(false); + } + firmware.queues.observe_pool_releases(&firmware.init, &mut deferred)?; + firmware.queues.reclaim_idle_render_pools(&firmware.init, &mut deferred)?; + let mut published = 0; + if !firmware.render_control_backpressured() { + (published, _) = firmware.queues.vacate_idle_pools( + &firmware.init, + 0, + super::super::now_ns(), + 0, + )?; + if published != 0 { + firmware.primary.notify(crate::g17::MSG_CONTROL_NOTIFY)?; + } + } + Ok(published != 0 + || !deferred.render.is_empty() + || !deferred.vacated.is_empty() + || !deferred.bindings.is_empty()) + })(); + deferred.finish(); + match result { + Err(error) if error == EAGAIN => Ok(false), + result => result, + } } fn render_with_lease( @@ -185,14 +334,44 @@ impl Backend { // A publication from the submitting ioctl (an early lease) never // sleeps for contention; it falls back to the scheduler instead. let sleep = early.is_none(); - let admission = self.acquire_render_with_ids(packet, early, sleep)?; + let mut early = early; + // The pair this owner last published to directly: a lease on it goes + // straight to the direct path, whose lend checks everything the + // identity check would. + let known = self.direct_slot.load(Ordering::Relaxed); + let lease = match early.take() { + Some(lease) => Some(lease), + None if known != 0 => self + .admission + .try_acquire_render(self.owner, Request::of(packet)) + .ok() + .flatten(), + None => None, + }; + let early = match lease { + Some(lease) if u32::from(lease.slot()) == known => { + let slot = lease.slot(); + packet.completion.attach_render_lease(lease)?; + if let Some(result) = self.render_direct(packet, slot, deferred) { + return result; + } + packet.completion.detach_render_lease() + } + lease => lease, + }; + let (admission, mut ready) = self.acquire_render_with_ids(packet, early, sleep)?; let slot = admission.slot(); packet.completion.attach_render_lease(admission)?; - let mut state_polls = 0; - let mut state_delay = 20i64; + if ready { + if let Some(result) = self.render_direct(packet, slot, deferred) { + return result; + } + } let mut busy_since = None; - let mut recovery_polls = 0; let capacity_started = super::super::now_ns(); + let deadline_ns = capacity_started.saturating_add(super::super::WORK_RING_WAIT_NS); + let mut seen = None; + let mut states = None; loop { if packet.cancel_requested() { return Err(ECANCELED); @@ -223,54 +402,66 @@ impl Backend { if !sleep { return Err(FALLBACK); } - if state_polls == 1000 { - return Err(ETIMEDOUT); + // Every node of this context belongs to unretired work: + // sample, retry once, then wait for the next returned one. + let work = packet.completion.work_state()?; + match states.take() { + None => states = Some(work.returns().observe()), + Some(sample) => { + self.wait_domain(work.returns(), sample, deadline_ns, packet)? + } } - state_polls += 1; - fsleep(Delta::from_micros(state_delay)); - state_delay = (state_delay * 2).min(1000); continue; } result => { result?; } } - match self.ensure_render(packet, slot, sleep) { + // A pair found ready under the identity check needs no build; any + // retry revalidates through ensure_render. + let ensured = if core::mem::take(&mut ready) { + Ok(()) + } else { + self.ensure_render(packet, slot, sleep) + }; + match ensured { Err(error) if error == ENOSPC => { - self.wait_render_capacity(capacity_started, sleep)?; + self.wait_render_capacity(&mut seen, capacity_started, sleep)?; continue; } Err(error) if error == EAGAIN => continue, Err(error) if error == EBUSY => { - self.wait_render_busy(&mut busy_since, sleep)?; + self.wait_render_busy(&mut busy_since, &mut seen, sleep, packet)?; continue; } result => result?, } match self.prepare_render(slot, packet, deferred, sleep) { Err(error) if !packet.is_published() && error == ENOSPC => { - self.wait_render_capacity(capacity_started, sleep)?; + self.wait_render_capacity(&mut seen, capacity_started, sleep)?; continue; } Err(error) if !packet.is_published() && [EAGAIN, EBUSY].contains(&error) => { - let recovering = { + let (recovering, gate) = { let state = self.shared.state.lock(); let firmware = (*state).as_deref().ok_or(ENODEV)?; - firmware.recovery.pending() - || crate::g17::recovery::Memory::recovery_state(&firmware.init)? != 0 + ( + firmware.recovery.pending() + || crate::g17::recovery::Memory::recovery_state(&firmware.init)? != 0, + self.shared.preparations.snapshot(), + ) }; if recovering { if !sleep { return Err(FALLBACK); } - if recovery_polls == super::super::RECOVERY_SETTLE_POLLS { - return Err(ETIMEDOUT); - } - recovery_polls += 1; + // Recovery closes the preparation gate before touching + // any queue; the next pass blocks in enter() until it + // reopens. The worker raises the closure. self.shared.queue_events(); - fsleep(Delta::from_millis(1)); + self.shared.preparations.wait_closure(gate, deadline_ns)?; } else if error == EBUSY { - self.wait_render_busy(&mut busy_since, sleep)?; + self.wait_render_busy(&mut busy_since, &mut seen, sleep, packet)?; } else if !sleep { return Err(FALLBACK); } @@ -284,19 +475,9 @@ impl Backend { if self.backlog.load(Ordering::Acquire) != 0 || packet.has_dependencies() { return false; } - let admission = { - let state = self.shared.state.lock(); - let Some(firmware) = (*state).as_deref() else { - return false; - }; - match firmware - .queues - .admission - .try_acquire_render(self.owner, Request::of(packet)) - { - Ok(Some(lease)) => lease, - _ => return false, - } + let admission = match self.admission.try_acquire_render(self.owner, Request::of(packet)) { + Ok(Some(lease)) => lease, + _ => return false, }; if !packet.begin_publication() { packet.completion.fail_unpublished(ECANCELED); @@ -448,15 +629,28 @@ impl crate::g17::Firmware { if packet.completion.status().get() != 0 { return Err(EIO); } + let mut dependencies = self.queues.render_dependencies(owner, packet, state)?; + // Every render publication, with or without the device mutex, holds the + // port's render lock: the render work rings have one host producer at a + // time and attachment overlaps are ordered by publication order. + let port = self.port.clone(); + let _render = port.render_lock(); // Both outer destinations must have room before pool accounting or - // either inner queue becomes visible. All host producers share this lock. + // either inner queue becomes visible. let priority = packet.context.policy().priority(); if !self.init.work_ready(priority, crate::g17::fw::queue::DataMaster::Fragment)? || !self.init.work_ready(priority, crate::g17::fw::queue::DataMaster::Tiling)? { return Err(EBUSY); } - let dependencies = self.queues.render_dependencies(owner, packet, state)?; + if let Some(footprint) = packet.footprint.as_ref() { + dependencies.hazard_count = self.queues.admission.publication_barriers( + owner, + slot, + footprint, + &mut dependencies.hazards, + )?; + } let pair = self.queues.render.entry(slot)?.pair.as_ref().ok_or(EIO)?; // An owner change taken from a live owner which closed meanwhile: its // first command waits until that scheduler's release has been consumed. diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/teardown.rs b/drivers/gpu/drm/asahi/g17/runtime/render/teardown.rs index eb04f80361955..8f3ee6f3b15bd 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/teardown.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/teardown.rs @@ -38,7 +38,7 @@ impl RenderState { return Ok(false); } if let Some(pair) = entry.pair.as_ref() { - if !pair.idle_for_context(context)? { + if !pair.idle_for_context(context)? && !pair.executed_for_kill(context) { return Ok(false); } } @@ -46,6 +46,14 @@ impl RenderState { Ok(true) } + /// Some render pair runs another context's commands. + pub(in crate::g17::runtime) fn foreign_in_flight(&self, context: &Arc) -> bool { + self.entries.iter().flatten().any(|entry| { + !entry.context.as_ref().is_some_and(|owner| Arc::ptr_eq(owner, context)) + && entry.pair.as_ref().is_some_and(|pair| pair.in_flight()) + }) + } + pub(in crate::g17::runtime) fn teardown_pools_released(&self, context: &Arc) -> bool { context.render_pools_released(|id, control| self.pools.released(id, control)) && self @@ -115,6 +123,7 @@ impl RenderState { if !out.has_room(count, 1) { return Err(EBUSY); } + let linked = context.firmware_may_link(); for entry in self.entries.iter_mut().flatten() { if !entry .context @@ -132,7 +141,14 @@ impl RenderState { let (mut parked, retired) = KBox::into_inner(pair).park(); // This park follows the consumed release of `context`. parked.release_consumed(context.scheduler_va()); - *target = Some(retired); + if linked { + // Guard (kill before free): the firmware may still walk this context's work + // list into the graph, so it is not freed or recycled before the firmware stops. + kernel::pr_warn_once!("G17: keeping render graphs of an unkilled context that still links work\n"); + keep_graph(&mut self.kept_graphs, retired); + } else { + *target = Some(retired); + } entry.parked = Some(parked); // Detached backing retains these references until the worker runs. entry.context = None; @@ -147,8 +163,12 @@ impl RenderState { if let Some(id) = self.retired[index].buffer_id() { buffers.unreserve_owner(id)?; } - let target = out.binding_slot()?; - *target = Some(self.retired.remove(index).map_err(|_| EIO)?); + let retired = self.retired.remove(index).map_err(|_| EIO)?; + if linked { + keep_graph(&mut self.kept_graphs, retired); + } else { + *out.binding_slot()? = Some(retired); + } } Ok(()) } @@ -173,3 +193,23 @@ impl Registry { Ok(()) } } + +/// Keeps `graph` until the firmware stops; if even that cannot be recorded, it is leaked +/// rather than freed. +/// Graphs that could not be recorded in `kept_graphs` and were leaked instead, for debugfs. +pub(super) static LEAKED_GRAPHS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); + +fn keep_graph( + kept: &mut KVec, + graph: crate::g17::queue::render::RetiredBinding, +) { + if kept.reserve(1, GFP_KERNEL).is_ok() { + if let Err(error) = kept.push_within_capacity(graph) { + LEAKED_GRAPHS.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + core::mem::forget(error.0); + } + } else { + LEAKED_GRAPHS.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + core::mem::forget(graph); + } +} diff --git a/drivers/gpu/drm/asahi/g17/runtime/teardown.rs b/drivers/gpu/drm/asahi/g17/runtime/teardown.rs index f8020e8962f84..0222ae4c478c8 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/teardown.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/teardown.rs @@ -100,6 +100,8 @@ impl ReclaimBatch { struct Service<'a> { render: &'a mut super::render::RenderState, + admission: &'a crate::g17::queue::admission::Pool, + objects: &'a Arc, compute: &'a [Option], buffers: &'a mut BufferIds, init: &'a InitData, @@ -115,7 +117,7 @@ impl Host for Service<'_> { self.compute .iter() .flatten() - .filter_map(|entry| entry.queue.as_ref()) + .filter_map(|entry| entry.queue.as_deref()) .all(|queue| queue.idle_for_context(context) && !queue.switching_from(context)) } fn flists_released(&self, context: &Arc) -> bool { @@ -137,8 +139,15 @@ impl Host for Service<'_> { self.primary.notify(MSG_CONTROL_NOTIFY) } fn detach(&mut self, context: &Arc) -> Result { + let linked = context.retain_linked_work(self.objects); + let (_, _, slots) = self.render.exit_scope(context); + crate::g17::trace::teardown(5, u64::from(context.id()), slots, u64::from(linked)); self.render .detach_context(context, self.buffers, self.reclaim)?; + // The firmware consumed this context's scheduler release after its kill (if any) + // was acknowledged and its pairs went idle: it no longer names these pairs, so + // their kill hold ends here rather than at the next processor stop. + self.admission.release_killed_slots(slots); // detach_context preflights this slot together with every graph slot. // Retain it before Pending removes its reference under the device lock. self.reclaim.context(context) @@ -173,6 +182,7 @@ impl Firmware { self.queues.render.observe_teardown_releases(&self.init)?; let recovery = self.recovery.pending() || self.init.recovery_state()? != 0; let Registry { + admission, render, compute, buffers, @@ -181,6 +191,8 @@ impl Firmware { } = &mut self.queues; let mut service = Service { render, + admission, + objects: &self.pool, compute, buffers, init: &self.init, @@ -190,10 +202,17 @@ impl Firmware { teardown.publish_ready(&mut service, recovery, super::now_ns())?; let consumer = self.init.control_consumer()?.load(Ordering::Relaxed); let producer = self.init.control_producer()?.load(Ordering::Relaxed); + let mut consumed = false; while teardown .take_consumed(consumer, producer, &mut service)? .is_some() - {} + { + consumed = true; + } + if consumed { + // A channel whose last scheduler release is consumed can change owner. + self.primary.state.shared.channels.notify(); + } Ok(()) } } diff --git a/drivers/gpu/drm/asahi/g17/sync_input.c b/drivers/gpu/drm/asahi/g17/sync_input.c new file mode 100644 index 0000000000000..19d5b8b92f934 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/sync_input.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#include +#include +#include +#include "sync_input.h" +#include "sync_scope.h" + +static int collect_leaf(struct dma_fence *leaf, + struct asahi_g17_fence_leaf *out, size_t capacity, + size_t *count) +{ + if (*count == capacity) + return -ENOSPC; + out[*count].fence = dma_fence_get(leaf); + out[*count].status = dma_fence_get_status(leaf); + ++*count; + return 0; +} + +/* Called without the device mutex: status polling may run fence callbacks. */ +int asahi_g17_collect_fences(struct dma_fence *head, + struct asahi_g17_fence_leaf *out, size_t capacity) +{ + struct dma_fence *pending[128]; + size_t count = 0, depth = 1; + int error = 0; + + pending[0] = dma_fence_get(head); + while (depth && !error) { + struct dma_fence *node = pending[--depth]; + struct dma_fence_unwrap cursor; + struct dma_fence *leaf; + + if (asahi_g17_is_scope(node)) { + struct dma_fence *children[2]; + unsigned int n = asahi_g17_scope_children(node, children), i; + if (!n) { + /* Preserve a failed scope as a failed leaf, never drop its error. */ + if (dma_fence_get_status(node) < 0) + error = collect_leaf(node, out, capacity, &count); + } else if (depth + n > ARRAY_SIZE(pending)) { + for (i = 0; i < n; ++i) + dma_fence_put(children[i]); + error = -ENOSPC; + } else { + for (i = 0; i < n; ++i) + pending[depth++] = children[i]; + } + dma_fence_put(node); + continue; + } + + dma_fence_unwrap_for_each(leaf, &cursor, node) { + if (asahi_g17_is_scope(leaf)) { + if (depth == ARRAY_SIZE(pending)) + error = -ENOSPC; + else + pending[depth++] = dma_fence_get(leaf); + } else if (dma_fence_is_container(leaf)) { + error = -EOPNOTSUPP; + } else { + error = collect_leaf(leaf, out, capacity, &count); + } + if (error) + break; + } + /* A bounded early exit still owns the iterator's current chain. */ + dma_fence_put(cursor.chain); + dma_fence_put(node); + } + while (depth) + dma_fence_put(pending[--depth]); + if (error) { + while (count) + dma_fence_put(out[--count].fence); + return error; + } + return count; +} + +void asahi_g17_put_fence(struct dma_fence *fence) +{ + dma_fence_put(fence); +} diff --git a/drivers/gpu/drm/asahi/g17/sync_input.h b/drivers/gpu/drm/asahi/g17/sync_input.h new file mode 100644 index 0000000000000..2b6e80f4b7f66 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/sync_input.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef ASAHI_G17_SYNC_INPUT_H +#define ASAHI_G17_SYNC_INPUT_H + +#include + +struct dma_fence; + +struct asahi_g17_fence_leaf { + struct dma_fence *fence; + int status; +}; + +int asahi_g17_collect_fences(struct dma_fence *head, + struct asahi_g17_fence_leaf *out, size_t capacity); +void asahi_g17_put_fence(struct dma_fence *fence); + +#endif diff --git a/drivers/gpu/drm/asahi/g17/sync_input.rs b/drivers/gpu/drm/asahi/g17/sync_input.rs new file mode 100644 index 0000000000000..1cd9a9ba761aa --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/sync_input.rs @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Bounded input-fence components. Collection happens before the device mutex, +//! because polling a container's status can signal it and run callbacks. + +use kernel::{bindings, dma_fence::{Fence, RawDmaFence}, prelude::*}; + +#[repr(C)] +pub(super) struct Leaf { + pub(super) fence: *mut bindings::dma_fence, + pub(super) status: i32, +} + +unsafe extern "C" { + fn asahi_g17_collect_fences(head: *mut bindings::dma_fence, + out: *mut Leaf, capacity: usize) -> i32; + fn asahi_g17_put_fence(fence: *mut bindings::dma_fence); +} + +impl Drop for Leaf { + fn drop(&mut self) { + // SAFETY: Successful collection transfers exactly one owned reference. + unsafe { asahi_g17_put_fence(self.fence) }; + } +} + +/// Allocation, unsupported containers, or a large prefix retain normal waits. +pub(super) fn collect(input: &Fence) -> Option> { + let mut capacity = 4; + loop { + let mut leaves = KVec::with_capacity(capacity, GFP_KERNEL).ok()?; + // SAFETY: The input owns its fence. The C helper writes at most capacity + // fully initialized records and releases every reference on failure. + let count = unsafe { + asahi_g17_collect_fences(input.raw(), leaves.as_mut_ptr(), capacity) + }; + if count == ENOSPC.to_errno() && capacity < 128 { + capacity = (capacity * 4).min(128); + continue; + } + if count < 0 { return None; } + // SAFETY: The helper returned this many initialized, owned records. + unsafe { leaves.set_len(count as usize) }; + return Some(leaves); + } +} + + +use super::fence::Submission; +use core::sync::atomic::{AtomicU32, Ordering}; +use kernel::{new_mutex, sync::{Arc, Mutex}}; + +pub(super) struct Source { + pub(super) submission: Arc, +} + +/// The counter is independent of a physical queue's allocation and survives +/// processor stop until the last consumer releases its binding loan. +pub(super) struct Hold(Arc); +impl Hold { + fn new(readers: &Arc) -> Result { + readers.fetch_update(Ordering::AcqRel, Ordering::Acquire, + |count| count.checked_add(1)).map_err(|_| EOVERFLOW)?; + Ok(Self(readers.clone())) + } +} +impl Drop for Hold { + fn drop(&mut self) { self.0.fetch_sub(1, Ordering::AcqRel); } +} + +/// A new owner starts with no loans. Acquiring a loan requires exclusive access +/// and arms the hint; eligibility queries stay read-only. A validated owner +/// transition can clear the hint after all independent loans have been released. +#[repr(transparent)] +pub(super) struct ReaderHint { + possible: bool, +} +impl ReaderHint { + pub(super) const fn new() -> Self { + Self { possible: false } + } + pub(super) fn hold(&mut self, count: &Arc) -> Result { + let hold = Hold::new(count)?; + self.possible = true; + Ok(hold) + } + pub(super) fn has_readers(&self, count: &AtomicU32) -> bool { + self.possible && count.load(Ordering::Acquire) != 0 + } + pub(super) fn reset_for_owner(&mut self, count: &AtomicU32) -> Result { + if self.has_readers(count) { return Err(EBUSY); } + self.possible = false; + Ok(()) + } +} + +#[pin_data] +pub(super) struct Sources { + inputs: KVec, + publication_waits: KVec, + pub(super) sources: KVec, + #[pin] + holds: Mutex>, +} +impl Sources { + pub(super) fn new(inputs: KVec, sources: KVec, + publication_waits: KVec) -> Result> { + CONVERTED.fetch_add(1, Ordering::Relaxed); + if !publication_waits.is_empty() { PENDING.fetch_add(1, Ordering::Relaxed); } + Arc::pin_init(pin_init!(Self { + inputs, sources, publication_waits, + holds <- new_mutex!(KVec::new(), "G17 source binding loans"), + }), GFP_KERNEL) + } + pub(super) fn contains_input(&self, input: &Fence) -> bool { + self.inputs.iter().any(|fence| fence.raw() == input.raw()) + } + pub(super) fn publication_waits(&self) -> &[Fence] { &self.publication_waits } + pub(super) fn check(&self) -> Result { + for source in &self.sources { source.submission.source_status()?; } + Ok(()) + } + pub(super) fn set_holds(&self, holds: KVec) { + let old = core::mem::replace(&mut *self.holds.lock(), holds); + drop(old); + } + pub(super) fn release(&self) { self.set_holds(KVec::new()); } +} + + +static CONVERTED: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); +static PUBLISHED: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); +static PENDING: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); +pub(super) fn pending_count() -> u64 { PENDING.load(Ordering::Relaxed) } +pub(super) fn note_published() { PUBLISHED.fetch_add(1, Ordering::Relaxed); } +pub(super) fn counters() -> (u64, u64) { + (CONVERTED.load(Ordering::Relaxed), PUBLISHED.load(Ordering::Relaxed)) +} + +/// A revalidated compute completion dependency for a direct work descriptor. +#[derive(Clone, Copy)] +pub(super) struct NativeWait { + pub(super) sequence: u64, + pub(super) dependency: super::fw::kick::KickDependency, + pub(super) stamp_va: u64, + pub(super) event_slot: u32, +} +impl NativeWait { + pub(super) fn bytes(&self, consumer_stamp: u32, uuid: u64) -> [u8; 0x48] { + let mut out = [0; 0x48]; + let timestamp = self.dependency.word() & ((1u64 << 40) - 1); + for (offset, value) in [ + (0, 4u32), + (0x14, u32::from(self.dependency.qid())), + (0x18, super::fw::stamp_value(timestamp as u32)), + (0x24, self.event_slot), + (0x28, consumer_stamp), + (0x34, 1), + ] { + out[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + } + for (offset, value) in [(4, self.stamp_va), (0xc, timestamp), (0x2c, uuid)] { + out[offset..offset + 8].copy_from_slice(&value.to_le_bytes()); + } + out + } +} + +static NATIVE: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); +pub(super) fn note_native(count: usize) { NATIVE.fetch_add(count as u64, Ordering::Relaxed); } +pub(super) fn native_count() -> u64 { NATIVE.load(Ordering::Relaxed) } + +#[kunit_tests(asahi_g17_input_wait)] +mod tests { + use super::*; + use crate::g17::fw::kick::{KickDependency, KickTimestamp}; + + #[test] + fn completion_record_preserves_distinct_event_and_queue_domains() { + let wait = NativeWait { + sequence: 1, + dependency: KickDependency::new(37, + KickTimestamp::new(0x12_3456_789a).unwrap()).unwrap(), + stamp_va: 0x1122_3344_5566_7788, + event_slot: 7, + }; + // Protocol fixture includes the unaligned stamp pointer, full 40-bit + // timestamp, wrapped 24-bit stamp and independent legacy event slot. + let expected: [u8; 0x48] = [ + 4, 0, 0, 0, 0x88, 0x77, 0x66, 0x55, + 0x44, 0x33, 0x22, 0x11, 0x9a, 0x78, 0x56, 0x34, + 0x12, 0, 0, 0, 37, 0, 0, 0, + 0, 0x9a, 0x78, 0x56, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 0, 0, 0, + 0, 0xcc, 0xbb, 0xaa, 8, 7, 6, 5, + 4, 3, 2, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ]; + assert_eq!(wait.bytes(0xaabb_cc00, 0x0102_0304_0506_0708), expected); + } + + #[test] + fn failed_loan_acquisition_never_wraps_or_releases_another_reader() { + let readers = Arc::new(AtomicU32::new(u32::MAX - 1), GFP_KERNEL).unwrap(); + let last = Hold::new(&readers).unwrap(); + assert!(matches!(Hold::new(&readers), Err(error) if error == EOVERFLOW)); + assert_eq!(readers.load(Ordering::Acquire), u32::MAX); + drop(last); + assert_eq!(readers.load(Ordering::Acquire), u32::MAX - 1); + } + + #[test] + fn loan_outlives_original_queue_counter_reference() { + let readers = Arc::new(AtomicU32::new(0), GFP_KERNEL).unwrap(); + let mut hint = ReaderHint::new(); + let first = hint.hold(&readers).unwrap(); + let second = hint.hold(&readers).unwrap(); + drop(readers); + assert_eq!(second.0.load(Ordering::Acquire), 2); + drop(first); + assert_eq!(second.0.load(Ordering::Acquire), 1); + drop(second); + } + + #[test] + fn reader_guard_tracks_partial_release_and_rearms_after_zero() { + let readers = Arc::new(AtomicU32::new(0), GFP_KERNEL).unwrap(); + let mut hint = ReaderHint::new(); + assert!(!hint.has_readers(&readers)); + let first = hint.hold(&readers).unwrap(); + let second = hint.hold(&readers).unwrap(); + assert!(hint.has_readers(&readers)); + assert!(matches!(hint.reset_for_owner(&readers), Err(error) if error == EBUSY)); + drop(first); + assert!(hint.has_readers(&readers)); + drop(second); + assert!(!hint.has_readers(&readers)); + hint.reset_for_owner(&readers).unwrap(); + let next = hint.hold(&readers).unwrap(); + assert!(hint.has_readers(&readers)); + drop(next); + assert!(!hint.has_readers(&readers)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/sync_output.c b/drivers/gpu/drm/asahi/g17/sync_output.c new file mode 100644 index 0000000000000..08b4a95f26c9b --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/sync_output.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#include +#include +#include +#include +#include "sync_output.h" +#include "sync_scope.h" + +struct asahi_g17_signal_target { + struct drm_syncobj *syncobj; + struct dma_fence_chain *chain; + u64 point; +}; + +struct asahi_g17_signal_bundle { + size_t count; + size_t anchor; + bool published; + struct asahi_g17_signal_target targets[]; +}; + +void asahi_g17_signals_free(struct asahi_g17_signal_bundle *bundle) +{ + size_t i; + + if (!bundle) + return; + for (i = 0; i < bundle->count; ++i) { + struct asahi_g17_signal_target *target = &bundle->targets[i]; + if (target->chain) + dma_fence_chain_free(target->chain); + if (target->syncobj) + drm_syncobj_put(target->syncobj); + } + kfree(bundle); +} + +/* The Rust caller supplies its validated, immutable snapshot, never a user pointer. */ +struct asahi_g17_signal_bundle * +asahi_g17_signals_new(const struct asahi_g17_signal_spec *syncs, + size_t count, size_t anchor, int *error) +{ + struct asahi_g17_signal_bundle *bundle; + size_t i; + + *error = -EINVAL; + if (!count || anchor >= count || !syncs[anchor].point) + return NULL; + *error = -ENOMEM; + bundle = kzalloc(struct_size(bundle, targets, count), GFP_KERNEL); + if (!bundle) + return NULL; + bundle->count = count; + bundle->anchor = anchor; + for (i = 0; i < count; ++i) { + struct asahi_g17_signal_target *target = &bundle->targets[i]; + target->syncobj = syncs[i].syncobj; + if (target->syncobj) + drm_syncobj_get(target->syncobj); + if (!target->syncobj) { + *error = -ENOENT; + goto fail; + } + target->point = syncs[i].point; + if (syncs[i].timeline) { + target->chain = dma_fence_chain_alloc(); + if (!target->chain) + goto fail; + } + if (i != anchor && target->syncobj == bundle->targets[anchor].syncobj) { + *error = -EINVAL; + goto fail; + } + } + /* The anchor may follow the other outputs. Reject aliases in either order. */ + for (i = 0; i < count; ++i) { + if (i != anchor && bundle->targets[i].syncobj == bundle->targets[anchor].syncobj) { + *error = -EINVAL; + goto fail; + } + } + *error = 0; + return bundle; +fail: + asahi_g17_signals_free(bundle); + return NULL; +} + +static void asahi_g17_signal_install(struct asahi_g17_signal_target *target, + struct dma_fence *fence) +{ + if (target->chain) { + drm_syncobj_add_point(target->syncobj, target->chain, fence, target->point); + target->chain = NULL; + } else { + drm_syncobj_replace_fence(target->syncobj, fence); + } +} + +int asahi_g17_signals_publish(struct asahi_g17_signal_bundle *bundle, + struct dma_fence *submitted) +{ + struct dma_fence *previous, *scope; + size_t i; + + if (bundle->published) + return -EINVAL; + previous = drm_syncobj_fence_get(bundle->targets[bundle->anchor].syncobj); + scope = asahi_g17_scope_create(previous, submitted, + bundle->targets[bundle->anchor].point); + dma_fence_put(previous); + if (!scope) + return -ENOMEM; + /* Only exported completion includes earlier work. GPU execution gets no new wait. */ + asahi_g17_signal_install(&bundle->targets[bundle->anchor], scope); + previous = drm_syncobj_fence_get(bundle->targets[bundle->anchor].syncobj); + asahi_g17_scope_bind(scope, previous); + dma_fence_put(previous); + for (i = 0; i < bundle->count; ++i) + if (i != bundle->anchor) + asahi_g17_signal_install(&bundle->targets[i], scope); + bundle->published = true; + dma_fence_put(scope); + return 0; +} + +/* Allocation or validation failed before enqueue: install an error promise without allocating. */ +void asahi_g17_signals_fail(struct asahi_g17_signal_bundle *bundle, + struct dma_fence *failure) +{ + size_t i; + + if (bundle->published) + return; + for (i = 0; i < bundle->count; ++i) + asahi_g17_signal_install(&bundle->targets[i], failure); + bundle->published = true; +} diff --git a/drivers/gpu/drm/asahi/g17/sync_output.h b/drivers/gpu/drm/asahi/g17/sync_output.h new file mode 100644 index 0000000000000..a752d9ceaf4b0 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/sync_output.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef ASAHI_G17_SYNC_OUTPUT_H +#define ASAHI_G17_SYNC_OUTPUT_H +#include + +struct dma_fence; +struct drm_syncobj; +struct asahi_g17_signal_bundle; +struct asahi_g17_signal_spec { + struct drm_syncobj *syncobj; + u64 point; + u32 timeline; +}; + +struct asahi_g17_signal_bundle * +asahi_g17_signals_new(const struct asahi_g17_signal_spec *specs, + size_t count, size_t anchor, int *error); +void asahi_g17_signals_free(struct asahi_g17_signal_bundle *bundle); +int asahi_g17_signals_publish(struct asahi_g17_signal_bundle *bundle, + struct dma_fence *submitted); +void asahi_g17_signals_fail(struct asahi_g17_signal_bundle *bundle, + struct dma_fence *failure); +#endif diff --git a/drivers/gpu/drm/asahi/g17/sync_output.rs b/drivers/gpu/drm/asahi/g17/sync_output.rs new file mode 100644 index 0000000000000..928ebe2319394 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/sync_output.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +//! Atomic preparation of a queue completion scope and its exported signals. + +use kernel::{bindings, dma_fence::{Fence, RawDmaFence}, prelude::*}; + +// Wire values also documented in include/uapi/drm/asahi_drm.h. Keeping these +// local permits building this module against an older kernel's Rust bindings. +pub(crate) const SYNC_CUMULATIVE: u32 = 2; +pub(crate) const FEATURE_CUMULATIVE_SIGNALS: u64 = 1 << 29; + +unsafe extern "C" { + fn asahi_g17_signals_new(syncs: *const SignalSpec, count: usize, anchor: usize, + error: *mut i32) -> *mut core::ffi::c_void; + fn asahi_g17_signals_free(bundle: *mut core::ffi::c_void); + fn asahi_g17_signals_publish(bundle: *mut core::ffi::c_void, + current: *mut bindings::dma_fence) -> i32; + fn asahi_g17_signals_fail(bundle: *mut core::ffi::c_void, + failure: *mut bindings::dma_fence); +} + +#[repr(C)] +struct SignalSpec { + syncobj: *mut core::ffi::c_void, + point: u64, + timeline: u32, +} + +/// Owns syncobj references and preallocated chain nodes; never owns user pointers. +pub(crate) struct SignalBundle(*mut core::ffi::c_void); + +impl SignalBundle { + /// C retains references before the borrowed sync items can be dropped. + pub(crate) fn new(syncs: &[crate::file::SyncItem], anchor: usize) -> Result + { + let mut specs = KVec::with_capacity(syncs.len(), GFP_KERNEL)?; + for sync in syncs { + specs.push(SignalSpec { + syncobj: sync.syncobj.as_raw().cast(), point: sync.timeline_value, + timeline: u32::from(sync.timeline), + }, GFP_KERNEL)?; + } + let mut error = 0; + // SAFETY: All entries borrow live sync objects; C copies the entries and + // acquires its own references before returning. + let raw = unsafe { asahi_g17_signals_new( + specs.as_ptr(), specs.len(), anchor, &mut error) }; + if raw.is_null() { return Err(Error::from_errno(error)); } + Ok(Self(raw)) + } + + pub(crate) fn publish(&mut self, current: &Fence) -> Result { + // SAFETY: The bundle and current fence are owned for this call; C takes + // its own references on publication and reports failure before any store. + let error = unsafe { asahi_g17_signals_publish(self.0, current.raw()) }; + if error == 0 { Ok(()) } else { Err(Error::from_errno(error)) } + } + + pub(crate) fn fail(&mut self, failure: &Fence) { + // SAFETY: Live owned arguments; this only consumes uninstalled chain nodes. + unsafe { asahi_g17_signals_fail(self.0, failure.raw()) }; + } +} + +impl Drop for SignalBundle { + fn drop(&mut self) { + // SAFETY: Exactly one owner; published syncobjs retain their own fences. + unsafe { asahi_g17_signals_free(self.0) }; + } +} diff --git a/drivers/gpu/drm/asahi/g17/sync_scope.c b/drivers/gpu/drm/asahi/g17/sync_scope.c new file mode 100644 index 0000000000000..a3153f3122b94 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/sync_scope.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sync_scope.h" +#include "sync_input.h" + +int asahi_g17_completion_fence_status(struct dma_fence *fence) +{ + /* These private fences have no signaled callback. A pending flag is + * sufficient to keep the frontier member; a concurrent signal can only + * defer pruning until the next submission. Read terminal errors through + * the normal locked API after observing the signalled flag. + */ + if (!dma_fence_test_signaled_flag(fence)) + return 0; + return dma_fence_get_status(fence); +} + +struct asahi_g17_scope; +struct asahi_g17_scope_callback { + struct dma_fence_cb callback; + struct asahi_g17_scope *scope; +}; + +struct asahi_g17_scope { + struct dma_fence base; + atomic_t pending; + struct dma_fence *children[2]; + struct asahi_g17_scope_callback callbacks[2]; + struct irq_work work; + struct llist_node ready; + /* Identifies the exact anchor point this scope replaced. */ + u64 anchor_context; + u64 anchor_point; +}; + +static const struct dma_fence_ops scope_ops; +static DEFINE_PER_CPU(struct llist_head *, active_scopes); +#define SCOPE_ATOMIC_BATCH 64 + +bool asahi_g17_is_scope(struct dma_fence *fence) +{ + return fence && fence->ops == &scope_ops; +} + +static struct asahi_g17_scope *to_scope(struct dma_fence *fence) +{ + return container_of(fence, struct asahi_g17_scope, base); +} + +static const char *scope_driver_name(struct dma_fence *fence) +{ + return "asahi"; +} + +static const char *scope_timeline_name(struct dma_fence *fence) +{ + return "g17-queue-scope"; +} + +static bool scope_signaled(struct dma_fence *fence) +{ + struct asahi_g17_scope *scope = to_scope(fence); + if (atomic_read_acquire(&scope->pending)) + return false; + cmpxchg(&fence->error, 1, 0); + return true; +} + +static bool scope_enable_signaling(struct dma_fence *fence) +{ + /* Child callbacks are already armed; no recursive callback installation. */ + return !scope_signaled(fence); +} + +unsigned int asahi_g17_scope_children(struct dma_fence *fence, + struct dma_fence *children[2]) +{ + struct asahi_g17_scope *scope = to_scope(fence); + unsigned long flags; + unsigned int i, count = 0; + + spin_lock_irqsave(&fence->inline_lock, flags); + if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags)) + for (i = 0; i < ARRAY_SIZE(scope->children); ++i) + if (scope->children[i]) + children[count++] = dma_fence_get(scope->children[i]); + spin_unlock_irqrestore(&fence->inline_lock, flags); + return count; +} + +static void scope_release_completed(struct asahi_g17_scope *scope) +{ + struct dma_fence *children[2]; + unsigned long flags; + unsigned int i; + + /* Readers take references under this lock. Signalled scopes need no history. */ + spin_lock_irqsave(&scope->base.inline_lock, flags); + for (i = 0; i < ARRAY_SIZE(children); ++i) { + children[i] = scope->children[i]; + scope->children[i] = NULL; + } + spin_unlock_irqrestore(&scope->base.inline_lock, flags); + for (i = 0; i < ARRAY_SIZE(children); ++i) + dma_fence_put(children[i]); + dma_fence_put(&scope->base); +} + +static void scope_complete(struct asahi_g17_scope *scope) +{ + cmpxchg(&scope->base.error, 1, 0); + dma_fence_signal(&scope->base); + scope_release_completed(scope); +} + +static void scope_work(struct irq_work *work) +{ + struct asahi_g17_scope *scope = container_of(work, struct asahi_g17_scope, work); + cmpxchg(&scope->base.error, 1, 0); + asahi_g17_signal_fence(&scope->base); + scope_release_completed(scope); +} + +static void scope_defer_list(struct llist_node *nodes) +{ + struct llist_node *node, *next; + llist_for_each_safe(node, next, nodes) { + struct asahi_g17_scope *scope = + container_of(node, struct asahi_g17_scope, ready); + /* Transfer the ready-list reference to the irq_work callback. */ + irq_work_queue(&scope->work); + } +} + +void asahi_g17_signal_fence(struct dma_fence *fence) +{ + struct llist_head ready = LLIST_HEAD_INIT(ready); + struct llist_node *nodes, *node, *next; + unsigned long flags; + unsigned int completed = 0; + + /* Fence callbacks are atomic. Keep the local ready list pinned while + * signaling, then finish ready scopes after the source fence is unlocked. + * New ready scopes append to this list instead of recursing or sending IPIs. + */ + preempt_disable(); + if (__this_cpu_read(active_scopes)) { + dma_fence_signal(fence); + preempt_enable(); + return; + } + __this_cpu_write(active_scopes, &ready); + dma_fence_signal(fence); + for (;;) { + /* An interrupt must not append after the final empty test. */ + local_irq_save(flags); + nodes = llist_del_all(&ready); + if (!nodes) + __this_cpu_write(active_scopes, NULL); + local_irq_restore(flags); + if (!nodes) + break; + llist_for_each_safe(node, next, nodes) { + if (completed == SCOPE_ATOMIC_BATCH) { + struct llist_node *more; + local_irq_save(flags); + more = llist_del_all(&ready); + __this_cpu_write(active_scopes, NULL); + local_irq_restore(flags); + preempt_enable(); + scope_defer_list(node); + scope_defer_list(more); + return; + } + scope_complete(container_of(node, struct asahi_g17_scope, ready)); + ++completed; + } + } + preempt_enable(); +} + +static void scope_arrived(struct asahi_g17_scope *scope, int error) +{ + if (error < 0) + cmpxchg(&scope->base.error, 1, error); + if (atomic_dec_and_test(&scope->pending)) { + struct llist_head *ready; + dma_fence_get(&scope->base); + preempt_disable(); + ready = __this_cpu_read(active_scopes); + if (ready) + llist_add(&scope->ready, ready); + preempt_enable(); + /* Foreign fence signalers have no driver completion pass to drain us. */ + if (!ready) + irq_work_queue(&scope->work); + } +} + +static void scope_callback(struct dma_fence *child, struct dma_fence_cb *callback) +{ + struct asahi_g17_scope_callback *cb = + container_of(callback, typeof(*cb), callback); + scope_arrived(cb->scope, child->error); + dma_fence_put(&cb->scope->base); +} + +static void scope_release(struct dma_fence *fence) +{ + struct asahi_g17_scope *scope = to_scope(fence); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(scope->children); ++i) + dma_fence_put(scope->children[i]); + dma_fence_free(fence); +} + +static void scope_set_deadline(struct dma_fence *fence, ktime_t deadline) +{ + struct asahi_g17_fence_leaf *leaves; + int count, i; + + if (dma_fence_is_signaled(fence)) + return; + /* A deadline is an advisory hint. Keep its traversal bounded and iterative, + * including when a foreign timeline boundary contains earlier scopes. + */ + leaves = kmalloc_array(128, sizeof(*leaves), GFP_ATOMIC | __GFP_NOWARN); + if (!leaves) + return; + count = asahi_g17_collect_fences(fence, leaves, 128); + for (i = 0; i < count; ++i) { + if (!asahi_g17_is_scope(leaves[i].fence)) + dma_fence_set_deadline(leaves[i].fence, deadline); + dma_fence_put(leaves[i].fence); + } + kfree(leaves); +} + +static const struct dma_fence_ops scope_ops = { + .get_driver_name = scope_driver_name, + .get_timeline_name = scope_timeline_name, + .enable_signaling = scope_enable_signaling, + .signaled = scope_signaled, + .release = scope_release, + .set_deadline = scope_set_deadline, +}; + +void asahi_g17_scope_bind(struct dma_fence *fence, struct dma_fence *root) +{ + struct asahi_g17_scope *scope = to_scope(fence); + /* A concurrent raw syncobj reset is invalid for the private queue anchor, + * but must not turn a hostile userspace race into a kernel dereference. + */ + if (!root || dma_fence_chain_contained(root) != fence) + return; + /* Written once before the submit returns; an unmatched snapshot takes fallback. */ + WRITE_ONCE(scope->anchor_point, root->seqno); + WRITE_ONCE(scope->anchor_context, root->context); +} + +struct dma_fence *asahi_g17_scope_create(struct dma_fence *previous, + struct dma_fence *submitted, u64 point) +{ + struct dma_fence *contained = previous ? dma_fence_chain_contained(previous) : NULL; + struct dma_fence *parent = NULL; + struct asahi_g17_scope *scope, *prior = NULL; + u64 context; + unsigned int i; + + if (asahi_g17_is_scope(contained)) { + prior = to_scope(contained); + if (READ_ONCE(prior->anchor_context) != previous->context || + READ_ONCE(prior->anchor_point) != previous->seqno || + point <= contained->seqno) + prior = NULL; + } + if (prior) { + parent = dma_fence_get(contained); + context = contained->context; + } else { + /* Empty submissions or external edits may insert a foreign timeline point. + * Flatten that boundary once; the ordinary path retains just the prior scope. + */ + if (previous) { + parent = dma_fence_unwrap_merge(previous); + if (!parent) + return NULL; + } + context = dma_fence_context_alloc(1); + } + scope = kzalloc_obj(*scope); + if (!scope) { + dma_fence_put(parent); + return NULL; + } + dma_fence_init64(&scope->base, &scope_ops, NULL, context, point); + init_irq_work(&scope->work, scope_work); + scope->children[0] = parent; + scope->children[1] = dma_fence_get(submitted); + /* The constructor guard prevents completion while callbacks are being installed. */ + atomic_set(&scope->pending, 3); + scope->base.error = 1; + for (i = 0; i < ARRAY_SIZE(scope->children); ++i) { + struct dma_fence *child = scope->children[i]; + if (!child) { + scope_arrived(scope, 0); + continue; + } + scope->callbacks[i].scope = scope; + dma_fence_get(&scope->base); + if (dma_fence_add_callback(child, &scope->callbacks[i].callback, scope_callback)) { + scope_arrived(scope, child->error); + dma_fence_put(&scope->base); + } + } + scope_arrived(scope, 0); + return &scope->base; +} diff --git a/drivers/gpu/drm/asahi/g17/sync_scope.h b/drivers/gpu/drm/asahi/g17/sync_scope.h new file mode 100644 index 0000000000000..e88b78aacdd7b --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/sync_scope.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef ASAHI_G17_SYNC_SCOPE_H +#define ASAHI_G17_SYNC_SCOPE_H +#include +struct dma_fence; +void asahi_g17_signal_fence(struct dma_fence *fence); +/* Only for explicitly signalled driver completion fences, with no poll op. */ +int asahi_g17_completion_fence_status(struct dma_fence *fence); + +struct dma_fence *asahi_g17_scope_create(struct dma_fence *previous, + struct dma_fence *submitted, u64 point); +void asahi_g17_scope_bind(struct dma_fence *scope, struct dma_fence *root); +bool asahi_g17_is_scope(struct dma_fence *fence); +/* Returns owned child references, or zero once the scope is signalled. */ +unsigned int asahi_g17_scope_children(struct dma_fence *fence, + struct dma_fence *children[2]); +#endif diff --git a/drivers/gpu/drm/asahi/g17/teardown.rs b/drivers/gpu/drm/asahi/g17/teardown.rs index 53518a76e91a4..fa2a72a77f060 100644 --- a/drivers/gpu/drm/asahi/g17/teardown.rs +++ b/drivers/gpu/drm/asahi/g17/teardown.rs @@ -24,6 +24,45 @@ struct Entry { phase: Phase, armed: u64, announce_pending: bool, + render_kill: Option, + /// Since when, and whether reported, a render-only kill has waited for other contexts' + /// compute to pause. + kill_wait: Option<(u64, bool)>, + /// A render-only kill of this context was refused (counted once per context). + kill_refused: bool, +} + +/// The kill of a closed context whose compute side is clean, published so that its render +/// pairs' executed but unstamped commands can be released (see `Pair::firmware_drained`). +/// The scheduler page's killed state, not this record, proves the kill to the pairs. +#[derive(Clone, Copy)] +struct RenderKill { + cookie: u64, + published: u64, + announced: bool, + acknowledged: bool, +} + +impl RenderKill { + fn new(cookie: u64, now: u64) -> Self { + Self { + cookie, + published: now, + announced: false, + acknowledged: false, + } + } + /// Only this kill's exact cookie acknowledges it, once. + fn acknowledge(&mut self, cookie: u64) -> bool { + if cookie == 0 || cookie != self.cookie || self.acknowledged { + return false; + } + self.acknowledged = true; + true + } + fn awaiting(&self, now: u64, window: u64) -> bool { + !self.acknowledged && now.saturating_sub(self.published) < window + } } /// Publication and announcement errors retain the entry; no timeout frees @@ -104,6 +143,9 @@ impl Pending { phase, armed: now, announce_pending: false, + render_kill: None, + kill_wait: None, + kill_refused: false, }; self.entries.push(entry, GFP_KERNEL)?; Ok(()) @@ -233,6 +275,94 @@ impl Pending { Ok(None) } + /// A render-only kill of the closed context at `index` was published. + pub(crate) fn render_kill_published(&self, index: usize) -> bool { + self.entries.get(index).is_some_and(|entry| entry.render_kill.is_some()) + } + + /// Record a published render-only kill. Publication is irrevocable: the record stays + /// with the entry until the context's scheduler release is consumed. + pub(crate) fn record_render_kill(&mut self, index: usize, cookie: u64, now: u64) -> Result { + let entry = self.entries.get_mut(index).ok_or(EINVAL)?; + if entry.render_kill.is_some() { + return Err(EINVAL); + } + entry.render_kill = Some(RenderKill::new(cookie, now)); + Ok(()) + } + + /// Ring the doorbell for published render-only kills not yet announced. A failed + /// doorbell is retried on a later pass and never duplicates the record. + pub(crate) fn announce_render_kills(&mut self, mut notify: impl FnMut() -> Result) { + for entry in self.entries.iter_mut() { + if let Some(kill) = entry.render_kill.as_mut() { + if !kill.announced && !kill.acknowledged && notify().is_ok() { + kill.announced = true; + } + } + } + } + + /// Only the exact cookie of an unacknowledged render-only kill is accepted. + pub(crate) fn acknowledge_render_kill(&mut self, cookie: u64) -> bool { + self.entries + .iter_mut() + .filter_map(|entry| entry.render_kill.as_mut()) + .any(|kill| kill.acknowledge(cookie)) + } + + /// A render-only kill awaits its acknowledgement within the polling window. + pub(crate) fn render_kill_polling(&self, now: u64, window: u64) -> bool { + self.entries + .iter() + .any(|entry| entry.render_kill.is_some_and(|kill| kill.awaiting(now, window))) + } + + /// The render-only kill of the entry at `index` waits; how long, and whether this is the + /// first time it crossed `slow`. + pub(crate) fn note_kill_wait(&mut self, index: usize, now: u64, slow: u64) -> (u64, bool) { + let Some(entry) = self.entries.get_mut(index) else { + return (0, false); + }; + let (since, warned) = entry.kill_wait.get_or_insert((now, false)); + let waited = now.saturating_sub(*since); + let crossed = waited >= slow && !*warned; + *warned |= crossed; + (waited, crossed) + } + + /// The render-only kill was published: how long it waited, if it did. + pub(crate) fn take_kill_wait(&mut self, index: usize, now: u64) -> u64 { + self.entries + .get_mut(index) + .and_then(|entry| entry.kill_wait.take()) + .map_or(0, |(since, _)| now.saturating_sub(since)) + } + + /// A render-only kill of the context at `index` was refused: it no longer waits, and this + /// returns whether it is the first refusal for this context. + pub(crate) fn refuse_render_kill(&mut self, index: usize) -> bool { + let Some(entry) = self.entries.get_mut(index) else { + return false; + }; + entry.kill_wait = None; + !core::mem::replace(&mut entry.kill_refused, true) + } + + /// The context at `index` is not waiting for a render-only kill (any more). + pub(crate) fn clear_kill_wait(&mut self, index: usize) { + if let Some(entry) = self.entries.get_mut(index) { + entry.kill_wait = None; + } + } + + /// How long each waiting render-only kill has waited. + pub(crate) fn kill_waits(&self, now: u64) -> impl Iterator + '_ { + self.entries + .iter() + .filter_map(move |entry| entry.kill_wait.map(|(since, _)| now.saturating_sub(since))) + } + /// Poll for a bounded window at close and again at actual release publication. /// Failed announcements remain retryable on later service even after polling /// expires. Time passage never substitutes for a firmware-consumption witness. @@ -242,3 +372,21 @@ impl Pending { }) } } + +#[kernel::macros::kunit_tests(asahi_g17_render_kill)] +mod render_kill_tests { + use super::RenderKill; + + #[test] + fn only_the_exact_cookie_acknowledges_a_render_kill_once() { + let mut kill = RenderKill::new(7, 1_000); + assert!(kill.awaiting(1_000, 10)); + assert!(!kill.acknowledge(0)); + assert!(!kill.acknowledge(8)); + assert!(kill.awaiting(1_009, 10)); + assert!(!kill.awaiting(1_010, 10)); + assert!(kill.acknowledge(7)); + assert!(!kill.acknowledge(7)); + assert!(!kill.awaiting(1_001, 10)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/timeout.rs b/drivers/gpu/drm/asahi/g17/timeout.rs index d951bd164d6e9..b83ea5d07f406 100644 --- a/drivers/gpu/drm/asahi/g17/timeout.rs +++ b/drivers/gpu/drm/asahi/g17/timeout.rs @@ -53,6 +53,37 @@ pub(crate) fn renew_execution(published: bool, enabled: bool, renewals: &mut u8) true } +/// Polls a published job may be renewed while the GPU keeps retiring work. A job +/// shares the GPU's cores with every other runnable queue, so its wall time grows +/// with the other clients' work: beside four other clients a 1.5 s dispatch took up +/// to 5 s, and lower-priority work waits behind higher-priority work. It has not +/// hung while the GPU keeps completing commands. Fourteen renewals bound the wait at +/// thirty seconds, after which a job that still has not finished fails as before. +const SHARED_RENEWALS: u8 = 14; + +/// The GPU counts as live while some command retired this recently. Under heavy +/// sharing the longest gap between retirements measured 1.2 s; a GPU that retires +/// nothing for this long has stopped. +pub(crate) const LIVENESS_NS: u64 = 5_000_000_000; + +/// Whether some command retired within [`LIVENESS_NS`] (`since_retired`). +pub(crate) fn retiring(since_retired: Option) -> bool { + since_retired.is_some_and(|since| since < LIVENESS_NS) +} + +/// Renews a published job's deadline while the GPU is `shared`, at most +/// [`SHARED_RENEWALS`] times. It is shared while some command retires +/// ([`retiring`]), or while a long job of another queue runs beside a long job: +/// two ray traversal dispatches of about 6 s each took 10 to 17 s side by side, and +/// neither retires anything until it ends. +pub(crate) fn renew_shared(published: bool, shared: bool, renewals: &mut u8) -> bool { + if !published || !shared || *renewals >= SHARED_RENEWALS { + return false; + } + *renewals += 1; + true +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct Head { /// The owning VM's [`crate::mmu::Vm::id`], one per VM status; the watchdog lines print it. @@ -215,6 +246,37 @@ mod tests { assert_eq!(POLL_MS * (1 + u32::from(ray) + u32::from(MAX_RENEWALS)), 26_000); } #[test] + fn a_live_gpu_renews_a_published_job_for_thirty_seconds() { + let mut renewals = 0; + for poll in 1..=14u8 { + assert!(renew_shared(true, retiring(Some(4_900_000_000)), &mut renewals)); + assert_eq!(renewals, poll); + } + assert!(!renew_shared(true, true, &mut renewals)); + assert_eq!(POLL_MS * (u32::from(renewals) + 1), 30_000); + } + #[test] + fn a_stopped_gpu_or_unpublished_job_keeps_the_deadline() { + let mut renewals = 0; + assert!(!renew_shared(true, retiring(Some(LIVENESS_NS)), &mut renewals)); + assert!(!renew_shared(true, retiring(None), &mut renewals)); + assert!(!renew_shared(false, true, &mut renewals)); + assert_eq!(renewals, 0); + } + #[test] + fn a_long_job_beside_another_shares_the_gpu_without_retirements() { + // Two traversal dispatches side by side: nothing retires, yet both progress. + let mut renewals = 0; + let long_job_beside = true; + let shared = retiring(Some(LIVENESS_NS + 1)) || long_job_beside; + for poll in 1..=14u8 { + assert!(renew_shared(true, shared, &mut renewals)); + assert_eq!(renewals, poll); + } + // The thirty-second bound still ends a pair that never finishes. + assert!(!renew_shared(true, true, &mut renewals)); + } + #[test] fn exhausted_or_invalid_credit_never_wraps() { for value in 4..=u8::MAX { let mut renewals = value; diff --git a/drivers/gpu/drm/asahi/g17/trace.c b/drivers/gpu/drm/asahi/g17/trace.c new file mode 100644 index 0000000000000..62307838bb36e --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/trace.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#include +#include + +#include "trace_api.h" + +#define CREATE_TRACE_POINTS +#include "trace.h" + +bool asahi_g17_trace_publish_enabled(void) +{ + return trace_asahi_g17_publish_enabled(); +} + +void asahi_g17_trace_publish(u8 engine, u8 qid, u32 ordinal, u32 ctx, u64 seq, + struct dma_fence *fence) +{ + trace_asahi_g17_publish(engine, qid, ordinal, ctx, seq, + fence ? fence->context : 0, fence ? fence->seqno : 0, + __arch_counter_get_cntvct()); +} + +void asahi_g17_trace_complete(u8 engine, u8 qid, u32 ordinal, u32 ctx, u64 seq, + u64 gpu_start, u64 gpu_end) +{ + if (trace_asahi_g17_complete_enabled()) + trace_asahi_g17_complete(engine, qid, ordinal, ctx, seq, gpu_start, + gpu_end, __arch_counter_get_cntvct()); +} + +void asahi_g17_trace_bind(u8 engine, u8 qid, u32 ctx, u8 kind) +{ + trace_asahi_g17_bind(engine, qid, ctx, kind); +} + +bool asahi_g17_trace_doorbell_enabled(void) +{ + return trace_asahi_g17_doorbell_enabled(); +} + +void asahi_g17_trace_doorbell(u8 engine, u32 power) +{ + trace_asahi_g17_doorbell(engine, power, __arch_counter_get_cntvct()); +} + +void asahi_g17_trace_message(u8 kind, u64 value) +{ + if (trace_asahi_g17_message_enabled()) + trace_asahi_g17_message(kind, value, __arch_counter_get_cntvct()); +} + +void asahi_g17_trace_event(u8 phase) +{ + if (trace_asahi_g17_event_enabled()) + trace_asahi_g17_event(phase, __arch_counter_get_cntvct()); +} + +void asahi_g17_trace_teardown(u8 kind, u64 a, u64 b, u64 c) +{ + if (trace_asahi_g17_teardown_enabled()) + trace_asahi_g17_teardown(kind, a, b, c); +} + +bool asahi_g17_trace_report_enabled(void) +{ + return trace_asahi_g17_report_enabled(); +} + +void asahi_g17_trace_report(u8 role, u8 ring, const u32 *words) +{ + trace_asahi_g17_report(role, ring, words, __arch_counter_get_cntvct()); +} + +void asahi_g17_trace_inputs(u32 ctx, u64 seq, u8 phase, u32 inputs, u32 sources, + u32 waits, bool converted) +{ + if (trace_asahi_g17_inputs_enabled()) + trace_asahi_g17_inputs(ctx, seq, phase, inputs, sources, waits, + converted, __arch_counter_get_cntvct()); +} + +void asahi_g17_trace_schedule(u8 engine, u32 ctx, u64 seq, u8 phase, s32 detail) +{ + if (trace_asahi_g17_schedule_enabled()) + trace_asahi_g17_schedule(engine, ctx, seq, phase, detail, + __arch_counter_get_cntvct()); +} diff --git a/drivers/gpu/drm/asahi/g17/trace.h b/drivers/gpu/drm/asahi/g17/trace.h new file mode 100644 index 0000000000000..c8cecbcc7f400 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/trace.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Command lifecycle tracepoints of the G17 runtime. Times are joined by + * (ctx, seq); `counter` is the CPU's virtual counter and `gpu_start`/`gpu_end` + * come from the firmware's completion record, so a reader can place firmware + * dispatch and completion on the host timeline. + */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM asahi_g17 + +#if !defined(ASAHI_G17_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define ASAHI_G17_TRACE_H + +#include + +TRACE_EVENT(asahi_g17_publish, + TP_PROTO(u8 engine, u8 qid, u32 ordinal, u32 ctx, u64 seq, + u64 fence_context, u64 fence_seqno, u64 counter), + TP_ARGS(engine, qid, ordinal, ctx, seq, fence_context, fence_seqno, counter), + TP_STRUCT__entry( + __field(u8, engine) + __field(u8, qid) + __field(u32, ordinal) + __field(u32, ctx) + __field(u64, seq) + __field(u64, fence_context) + __field(u64, fence_seqno) + __field(u64, counter) + ), + TP_fast_assign( + __entry->engine = engine; + __entry->qid = qid; + __entry->ordinal = ordinal; + __entry->ctx = ctx; + __entry->seq = seq; + __entry->fence_context = fence_context; + __entry->fence_seqno = fence_seqno; + __entry->counter = counter; + ), + TP_printk("engine=%u qid=%u ordinal=%u ctx=%u seq=%llu fence=%llu:%llu counter=%llu", + __entry->engine, __entry->qid, __entry->ordinal, __entry->ctx, + __entry->seq, __entry->fence_context, __entry->fence_seqno, + __entry->counter) +); + +TRACE_EVENT(asahi_g17_complete, + TP_PROTO(u8 engine, u8 qid, u32 ordinal, u32 ctx, u64 seq, + u64 gpu_start, u64 gpu_end, u64 counter), + TP_ARGS(engine, qid, ordinal, ctx, seq, gpu_start, gpu_end, counter), + TP_STRUCT__entry( + __field(u8, engine) + __field(u8, qid) + __field(u32, ordinal) + __field(u32, ctx) + __field(u64, seq) + __field(u64, gpu_start) + __field(u64, gpu_end) + __field(u64, counter) + ), + TP_fast_assign( + __entry->engine = engine; + __entry->qid = qid; + __entry->ordinal = ordinal; + __entry->ctx = ctx; + __entry->seq = seq; + __entry->gpu_start = gpu_start; + __entry->gpu_end = gpu_end; + __entry->counter = counter; + ), + TP_printk("engine=%u qid=%u ordinal=%u ctx=%u seq=%llu gpu=%llu..%llu counter=%llu", + __entry->engine, __entry->qid, __entry->ordinal, __entry->ctx, + __entry->seq, __entry->gpu_start, __entry->gpu_end, __entry->counter) +); + +TRACE_EVENT(asahi_g17_bind, + TP_PROTO(u8 engine, u8 qid, u32 ctx, u8 kind), + TP_ARGS(engine, qid, ctx, kind), + TP_STRUCT__entry( + __field(u8, engine) + __field(u8, qid) + __field(u32, ctx) + __field(u8, kind) + ), + TP_fast_assign( + __entry->engine = engine; + __entry->qid = qid; + __entry->ctx = ctx; + __entry->kind = kind; + ), + TP_printk("engine=%u qid=%u ctx=%u kind=%u", + __entry->engine, __entry->qid, __entry->ctx, __entry->kind) +); + +TRACE_EVENT(asahi_g17_doorbell, + TP_PROTO(u8 engine, u32 power, u64 counter), + TP_ARGS(engine, power, counter), + TP_STRUCT__entry( + __field(u8, engine) + __field(u32, power) + __field(u64, counter) + ), + TP_fast_assign( + __entry->engine = engine; + __entry->power = power; + __entry->counter = counter; + ), + TP_printk("engine=%u power=%#x counter=%llu", __entry->engine, __entry->power, + __entry->counter) +); + +TRACE_EVENT(asahi_g17_message, + TP_PROTO(u8 kind, u64 value, u64 counter), + TP_ARGS(kind, value, counter), + TP_STRUCT__entry( + __field(u8, kind) + __field(u64, value) + __field(u64, counter) + ), + TP_fast_assign( + __entry->kind = kind; + __entry->value = value; + __entry->counter = counter; + ), + TP_printk("kind=%u value=%#llx counter=%llu", __entry->kind, __entry->value, + __entry->counter) +); + +/* Failed-context teardown and render slot reuse (kind: see g17/trace.rs teardown()). */ +TRACE_EVENT(asahi_g17_teardown, + TP_PROTO(u8 kind, u64 a, u64 b, u64 c), + TP_ARGS(kind, a, b, c), + TP_STRUCT__entry( + __field(u8, kind) + __field(u64, a) + __field(u64, b) + __field(u64, c) + ), + TP_fast_assign( + __entry->kind = kind; + __entry->a = a; + __entry->b = b; + __entry->c = c; + ), + TP_printk("kind=%u a=%#llx b=%#llx c=%#llx", __entry->kind, __entry->a, __entry->b, __entry->c) +); + +TRACE_EVENT(asahi_g17_event, + TP_PROTO(u8 phase, u64 counter), + TP_ARGS(phase, counter), + TP_STRUCT__entry( + __field(u8, phase) + __field(u64, counter) + ), + TP_fast_assign( + __entry->phase = phase; + __entry->counter = counter; + ), + TP_printk("phase=%u counter=%llu", __entry->phase, __entry->counter) +); + +/* Input conversion: phase 0 entry, 1 result. converted=0 also covers no live input. */ +TRACE_EVENT(asahi_g17_inputs, + TP_PROTO(u32 ctx, u64 seq, u8 phase, u32 inputs, u32 sources, u32 waits, + bool converted, u64 counter), + TP_ARGS(ctx, seq, phase, inputs, sources, waits, converted, counter), + TP_STRUCT__entry( + __field(u32, ctx) + __field(u64, seq) + __field(u8, phase) + __field(u32, inputs) + __field(u32, sources) + __field(u32, waits) + __field(bool, converted) + __field(u64, counter) + ), + TP_fast_assign( + __entry->ctx = ctx; + __entry->seq = seq; + __entry->phase = phase; + __entry->inputs = inputs; + __entry->sources = sources; + __entry->waits = waits; + __entry->converted = converted; + __entry->counter = counter; + ), + TP_printk("ctx=%u seq=%llu phase=%u inputs=%u sources=%u waits=%u converted=%u counter=%llu", + __entry->ctx, __entry->seq, __entry->phase, __entry->inputs, + __entry->sources, __entry->waits, __entry->converted, __entry->counter) +); + +/* phase: 0 enqueue, 1 inline published, 2 scheduler fallback, 3 run begin, + * 4 run end, 5 prepare begin, 6 prepare end. detail: dependencies, error or wait. */ +TRACE_EVENT(asahi_g17_schedule, + TP_PROTO(u8 engine, u32 ctx, u64 seq, u8 phase, s32 detail, u64 counter), + TP_ARGS(engine, ctx, seq, phase, detail, counter), + TP_STRUCT__entry( + __field(u8, engine) + __field(u32, ctx) + __field(u64, seq) + __field(u8, phase) + __field(s32, detail) + __field(u64, counter) + ), + TP_fast_assign( + __entry->engine = engine; + __entry->ctx = ctx; + __entry->seq = seq; + __entry->phase = phase; + __entry->detail = detail; + __entry->counter = counter; + ), + TP_printk("engine=%u ctx=%u seq=%llu phase=%u detail=%d counter=%llu", + __entry->engine, __entry->ctx, __entry->seq, __entry->phase, + __entry->detail, __entry->counter) +); + +/* Raw 18-word records: trace=0, statistics=1, event=2. No unit inference. */ +TRACE_EVENT(asahi_g17_report, + TP_PROTO(u8 role, u8 ring, const u32 *words, u64 counter), + TP_ARGS(role, ring, words, counter), + TP_STRUCT__entry( + __field(u8, role) + __field(u8, ring) + __array(u32, words, 18) + __field(u64, counter) + ), + TP_fast_assign( + __entry->role = role; + __entry->ring = ring; + memcpy(__entry->words, words, sizeof(__entry->words)); + __entry->counter = counter; + ), + TP_printk("role=%u ring=%u words=%s counter=%llu", __entry->role, + __entry->ring, __print_array(__entry->words, 18, sizeof(u32)), + __entry->counter) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace +#include diff --git a/drivers/gpu/drm/asahi/g17/trace.rs b/drivers/gpu/drm/asahi/g17/trace.rs new file mode 100644 index 0000000000000..78d88db4718c6 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/trace.rs @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Command lifecycle tracepoints (tracefs `events/asahi_g17`). Each call is a +//! static-key test in C while its event is disabled. + +use kernel::{bindings, dma_fence::RawDmaFence, sync::Arc}; + +unsafe extern "C" { + fn asahi_g17_trace_publish_enabled() -> bool; + fn asahi_g17_trace_publish(engine: u8, qid: u8, ordinal: u32, ctx: u32, seq: u64, + fence: *mut bindings::dma_fence); + fn asahi_g17_trace_complete(engine: u8, qid: u8, ordinal: u32, ctx: u32, seq: u64, + gpu_start: u64, gpu_end: u64); + fn asahi_g17_trace_bind(engine: u8, qid: u8, ctx: u32, kind: u8); + fn asahi_g17_trace_message(kind: u8, value: u64); + fn asahi_g17_trace_doorbell_enabled() -> bool; + fn asahi_g17_trace_doorbell(engine: u8, power: u32); + fn asahi_g17_trace_event(phase: u8); + fn asahi_g17_trace_teardown(kind: u8, a: u64, b: u64, c: u64); +} + +pub(super) const RENDER: u8 = 0; +pub(super) const COMPUTE: u8 = 1; + +/// Called once the command's doorbell has been rung. +pub(super) fn publish(engine: u8, qid: u8, ordinal: u32, packet: &Arc) { + // SAFETY: Pure static-key query. + if !unsafe { asahi_g17_trace_publish_enabled() } { + return; + } + let fence = packet.completion.fence(); + // SAFETY: `fence` holds a reference for the duration of the call. + unsafe { + asahi_g17_trace_publish(engine, qid, ordinal, packet.context.id(), packet.order.sequence, + fence.raw()) + }; +} + +/// Called when exact completion with GPU timestamps is retired. +pub(super) fn complete(engine: u8, qid: u8, ordinal: u32, packet: &super::job::Packet, + gpu: [u64; 2]) +{ + // SAFETY: Plain values only. + unsafe { + asahi_g17_trace_complete(engine, qid, ordinal, packet.context.id(), packet.order.sequence, + gpu[0], gpu[1]) + }; +} + +/// A physical queue was bound to a logical owner: 0 fresh, 1 rebound, 2 same VM. +pub(super) fn bind(engine: u8, qid: u8, ctx: u32, kind: u8) { + // SAFETY: Plain values only. + unsafe { asahi_g17_trace_bind(engine, qid, ctx, kind) }; +} + +/// A work doorbell is about to ring; `power` samples the core power state +/// only while the event is enabled. +pub(super) fn doorbell(engine: u8, power: impl FnOnce() -> u32) { + // SAFETY: Pure static-key query. + if !unsafe { asahi_g17_trace_doorbell_enabled() } { + return; + } + // SAFETY: Plain values only. + unsafe { asahi_g17_trace_doorbell(engine, power()) }; +} + +pub(super) const MAILBOX: u8 = 0; +pub(super) const CONTROL: u8 = 1; +pub(super) const EFFORT: u8 = 2; + +/// A host-to-firmware message: a mailbox message, a device-control record by +/// opcode, or a new performance effort value. +pub(super) fn message(kind: u8, value: u64) { + // SAFETY: Plain values only. + unsafe { asahi_g17_trace_message(kind, value) }; +} + +/// Failed-context teardown: 1 compute fault (qid, stamp), 2 unstamped render retire (slot, +/// ordinal), 3 kill published (cookie, context, held slots | 1 if it skipped the wait for other +/// contexts' compute under memory pressure; slot 0 is never held), 4 kill acknowledged (cookie, +/// qid, or 0x100 for a render-only kill), 5 detach (context, slots, linked), 6 render slot to a +/// new owner (slot, old, new | killed << 63), 7 render-only kill published (as 3). +pub(crate) fn teardown(kind: u8, a: u64, b: u64, c: u64) { + // SAFETY: Plain values only. + unsafe { asahi_g17_trace_teardown(kind, a, b, c) }; +} + +/// Event worker pass boundaries: 0 begin, 1 fences signalled, 2 end. +pub(super) fn event(phase: u8) { + // SAFETY: Plain values only. + unsafe { asahi_g17_trace_event(phase) }; +} + +unsafe extern "C" { + fn asahi_g17_trace_report_enabled() -> bool; + fn asahi_g17_trace_report(role: u8, ring: u8, words: *const u32); +} + +/// Copies a complete firmware record while the host still owns its slot. +/// Static-key gated; no MMIO reads or firmware requests are issued. +pub(super) fn report(role: u8, ring: u8, words: &[u32; super::fw::channels::EVENT_WORDS]) { + // SAFETY: Pure static-key query. + if !unsafe { asahi_g17_trace_report_enabled() } { + return; + } + // SAFETY: C synchronously copies the complete initialized 18-word array. + unsafe { asahi_g17_trace_report(role, ring, words.as_ptr()) }; +} + + +unsafe extern "C" { + fn asahi_g17_trace_inputs(ctx: u32, seq: u64, phase: u8, inputs: u32, + sources: u32, waits: u32, converted: bool); + fn asahi_g17_trace_schedule(engine: u8, ctx: u32, seq: u64, phase: u8, detail: i32); +} + +/// Input conversion boundaries. No fence status is polled for tracing. +pub(super) fn inputs(ctx: u32, seq: u64, phase: u8, inputs: usize, + sources: Option<&super::sync_input::Sources>) +{ + let (count, waits) = sources.map_or((0, 0), + |s| (s.sources.len(), s.publication_waits().len())); + // SAFETY: Bounded UAPI counts and plain values only. C reads the timer only + // behind this event's static key; no GPU registers or pointers are exposed. + unsafe { asahi_g17_trace_inputs(ctx, seq, phase, inputs as u32, + count as u32, waits as u32, sources.is_some()) }; +} + +/// Host scheduling boundaries, joined to publish/complete by engine/context/sequence. +pub(super) fn schedule(packet: &super::job::Packet, phase: u8, detail: i32) { + let engine = match packet.command { + super::command::Validated::Render { .. } => RENDER, + super::command::Validated::Compute { .. } => COMPUTE, + }; + // SAFETY: Plain values only; counter sampling is static-key gated in C. + unsafe { asahi_g17_trace_schedule(engine, packet.context.id(), + packet.order.sequence, phase, detail) }; +} diff --git a/drivers/gpu/drm/asahi/g17/trace_api.h b/drivers/gpu/drm/asahi/g17/trace_api.h new file mode 100644 index 0000000000000..cd4d360c55595 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/trace_api.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Entry points of the G17 command lifecycle tracepoints, called from Rust + * (g17/trace.rs). + */ +#ifndef _ASAHI_G17_TRACE_API_H +#define _ASAHI_G17_TRACE_API_H + +#include + +struct dma_fence; + +bool asahi_g17_trace_publish_enabled(void); +void asahi_g17_trace_publish(u8 engine, u8 qid, u32 ordinal, u32 ctx, u64 seq, + struct dma_fence *fence); +void asahi_g17_trace_complete(u8 engine, u8 qid, u32 ordinal, u32 ctx, u64 seq, + u64 gpu_start, u64 gpu_end); +void asahi_g17_trace_bind(u8 engine, u8 qid, u32 ctx, u8 kind); +bool asahi_g17_trace_doorbell_enabled(void); +void asahi_g17_trace_doorbell(u8 engine, u32 power); +void asahi_g17_trace_message(u8 kind, u64 value); +void asahi_g17_trace_event(u8 phase); +void asahi_g17_trace_teardown(u8 kind, u64 a, u64 b, u64 c); + +void asahi_g17_trace_inputs(u32 ctx, u64 seq, u8 phase, u32 inputs, u32 sources, + u32 waits, bool converted); +void asahi_g17_trace_schedule(u8 engine, u32 ctx, u64 seq, u8 phase, s32 detail); + +bool asahi_g17_trace_report_enabled(void); +void asahi_g17_trace_report(u8 role, u8 ring, const u32 *words); + +#endif /* _ASAHI_G17_TRACE_API_H */ diff --git a/drivers/gpu/drm/asahi/g17/validation.rs b/drivers/gpu/drm/asahi/g17/validation.rs index b2a6cf6e8bfe3..b0f2861382ad7 100644 --- a/drivers/gpu/drm/asahi/g17/validation.rs +++ b/drivers/gpu/drm/asahi/g17/validation.rs @@ -8,19 +8,41 @@ use core::{cell::RefCell, ops::Range}; use kernel::prelude::*; const MAX_RANGES: usize = 48; +// The persistent proof must retain a deep queue's rotating command buffers. +// Keep the transient parser request set small to bound its stack frame. +const MAX_CACHED_RANGES: usize = 128; type Request = (u64, u64, u8); +/// Match the page footprint already checked by `Vm::covers_range_locked`. +fn pages((address, size, access): Request) -> Option { + if size == 0 || access == 0 || access & !3 != 0 { + return None; + } + let mask = mmu::UAT_PGSZ as u64 - 1; + let end = address.checked_add(size)?; + let end = end.checked_add(mask)? & !mask; + let start = address & !mask; + Some((start, end.checked_sub(start)?, access)) +} + +fn contains((start, size, access): Request, (other, length, needed): Request) -> bool { + access & needed == needed + && other >= start + && other.checked_add(length).is_some_and(|end| + start.checked_add(size).is_some_and(|limit| end <= limit)) +} + #[derive(Clone, Copy)] -struct Requests { - ranges: [Request; MAX_RANGES], +struct Requests { + ranges: [Request; CAPACITY], count: usize, overflow: bool, } -impl Requests { +impl Requests { const fn new() -> Self { Self { - ranges: [(0, 0, 0); MAX_RANGES], + ranges: [(0, 0, 0); CAPACITY], count: 0, overflow: false, } @@ -30,24 +52,55 @@ impl Requests { &self.ranges[..self.count] } + fn covers(&self, request: Request) -> bool { + self.as_slice().iter().any(|&range| contains(range, request)) + } + fn insert(&mut self, request: Request) { - if self.as_slice().contains(&request) { + let Some(mut request) = pages(request) else { + self.overflow = true; + return; + }; + if self.covers(request) { return; } - if self.count == MAX_RANGES { + // Merge only equal permissions, so an adjacent writable page never + // grants write access to a read-only page. Restart after each merge + // because it can join a range visited earlier. + let mut index = 0; + while index < self.count { + let range = self.ranges[index]; + let end = request.0 + request.1; + let other_end = range.0 + range.1; + if contains(request, range) + || (request.2 == range.2 && request.0 <= other_end && range.0 <= end) + { + if request.2 == range.2 { + let start = request.0.min(range.0); + request = (start, end.max(other_end) - start, request.2); + } + self.count -= 1; + self.ranges[index] = self.ranges[self.count]; + index = 0; + } else { + index += 1; + } + } + if self.count == CAPACITY { self.overflow = true; return; } self.ranges[self.count] = request; self.count += 1; } + } -/// One logical queue's bounded cache of exact address, size and permission tuples. +/// One logical queue's bounded cache of validated page ranges and permissions. /// Mapping mutations make the epoch unavailable until their final page-table write. pub(crate) struct Proof { epoch: Option, - requests: Requests, + requests: Requests, } impl Proof { @@ -64,7 +117,7 @@ impl Proof { && requests .as_slice() .iter() - .all(|request| self.requests.as_slice().contains(request)) + .all(|&request| self.requests.covers(request)) } fn record(&mut self, epoch: Option, requests: Requests) { @@ -79,7 +132,10 @@ impl Proof { } // An incomplete union cannot be reused; retain the latest complete set. self.epoch = if requests.overflow { None } else { epoch }; - self.requests = requests; + self.requests = Requests::new(); + for &request in requests.as_slice() { + self.requests.insert(request); + } } /// The VM-job guard must already pin the mappings for this command. Keep the @@ -144,3 +200,101 @@ impl AddressSpace for Space<'_> { } } } + + +#[kunit_tests(asahi_g17_validation_pages)] +mod tests { + use super::*; + const PAGE: u64 = mmu::UAT_PGSZ as u64; + + fn requests(range: Request) -> Requests { + let mut result = Requests::new(); + result.insert(range); + result + } + + #[test] + fn different_offsets_share_the_already_validated_page() { + let mut proof = Proof::new(); + proof.record(Some(7), requests((PAGE + 4, 4, 3))); + assert!(proof.covers(Some(7), &requests((PAGE + 0x100, 8, 1)))); + assert!(proof.covers(Some(7), &requests((2 * PAGE - 8, 8, 2)))); + assert!(!proof.covers(Some(7), &requests((2 * PAGE - 8, 16, 1)))); + assert!(!proof.covers(Some(8), &requests((PAGE + 4, 4, 1)))); + assert!(!proof.covers(None, &requests((PAGE + 4, 4, 1)))); + } + + #[test] + fn adjacent_equal_permissions_merge_but_holes_do_not() { + let mut ranges = requests((PAGE, 1, 1)); + ranges.insert((3 * PAGE, 1, 1)); + assert_eq!(ranges.count, 2); + assert!(!ranges.covers((2 * PAGE, PAGE, 1))); + ranges.insert((2 * PAGE, 1, 1)); + assert_eq!(ranges.count, 1); + assert_eq!(ranges.as_slice(), &[(PAGE, 3 * PAGE, 1)]); + } + + #[test] + fn adjacent_permissions_never_grant_access_to_another_page() { + let mut proof = Proof::new(); + let mut ranges = requests((PAGE, 1, 1)); + ranges.insert((2 * PAGE, 1, 3)); + proof.record(Some(1), ranges); + assert!(!proof.covers(Some(1), &requests((PAGE, 1, 2)))); + assert!(proof.covers(Some(1), &requests((2 * PAGE, 1, 2)))); + assert!(!proof.covers(Some(1), &requests((PAGE, 2 * PAGE, 3)))); + } + + #[test] + fn stronger_coverage_removes_only_fully_covered_ranges() { + let mut ranges = requests((PAGE, PAGE, 1)); + ranges.insert((2 * PAGE, PAGE, 2)); + ranges.insert((PAGE, 2 * PAGE, 3)); + assert_eq!(ranges.as_slice(), &[(PAGE, 2 * PAGE, 3)]); + assert!(!ranges.covers((0, PAGE, 1))); + } + + #[test] + fn persistent_cache_retains_a_deep_rotating_working_set() { + let mut proof = Proof::new(); + for index in 0..64 { + proof.record(Some(7), requests((index * 2 * PAGE, 8, 1))); + } + assert_eq!(proof.requests.count, 64); + for index in 0..64 { + assert!(proof.covers(Some(7), &requests((index * 2 * PAGE + 16, 8, 1)))); + } + assert_eq!(core::mem::size_of::(), core::mem::size_of::>()); + } + + #[test] + fn persistent_overflow_keeps_only_a_new_complete_proof() { + let mut proof = Proof::new(); + for index in 0..=MAX_CACHED_RANGES { + proof.record(Some(7), requests((index as u64 * 2 * PAGE, 8, 1))); + } + assert_eq!(proof.requests.count, 1); + assert!(!proof.covers(Some(7), &requests((0, 8, 1)))); + assert!(proof.covers(Some(7), + &requests((MAX_CACHED_RANGES as u64 * 2 * PAGE, 8, 1)))); + } + + #[test] + fn invalid_rounding_and_full_cache_disable_reuse() { + for range in [(0, 0, 1), (u64::MAX - 1, 1, 1), (0, 1, 0), (0, 1, 4)] { + assert!(pages(range).is_none()); + let mut proof = Proof::new(); + proof.record(Some(1), requests(range)); + assert!(!proof.covers(Some(1), &requests((0, 1, 1)))); + } + let mut ranges = Requests::new(); + for index in 0..=MAX_RANGES { + ranges.insert(((2 * index) as u64 * PAGE, PAGE, 1)); + } + assert!(ranges.overflow); + let mut proof = Proof::new(); + proof.record(Some(1), ranges); + assert!(!proof.covers(Some(1), &requests((0, PAGE, 1)))); + } +} diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs index 24f586cb1c2fb..3f2672f803ce9 100644 --- a/drivers/gpu/drm/asahi/gpu.rs +++ b/drivers/gpu/drm/asahi/gpu.rs @@ -260,9 +260,8 @@ pub(crate) struct GpuManager { stats: Arc, buffer_mgr: buffer::BufferManager::ver, ids: SequenceIDs, - #[allow(clippy::vec_box)] #[pin] - garbage_contexts: Mutex>>>, + garbage_contexts: Mutex>, } /// Operations the DRM driver performs on a GPU, independent of its firmware interface. @@ -416,8 +415,8 @@ pub(crate) trait GpuManager: Send + Sync { fn get_cfg(&self) -> &'static hw::HwConfig; /// Get the dynamic GPU configuration for this SoC. fn get_dyncfg(&self) -> &hw::DynConfig; - /// Register an unused context as garbage - fn free_context(&self, data: KBox>); + /// Register an unused context, with the objects it retains, for invalidation. + fn free_context(&self, ctx: workqueue::RetiredContext); } /// Private generic trait for functions that don't need to escape this module. @@ -1471,7 +1470,7 @@ impl GpuManager for GpuManager::ver { core::mem::swap(&mut *self.garbage_contexts.lock(), &mut garbage_ctx); for ctx in garbage_ctx { - if self.invalidate_context(&ctx).is_err() { + if self.invalidate_context(&ctx.data).is_err() { dev_err!( self.dev.as_ref(), "GpuContext: Failed to invalidate GPU context!\n" @@ -1480,6 +1479,9 @@ impl GpuManager for GpuManager::ver { panic!("GPU firmware timed out"); } } + // The firmware no longer knows the context's queues, so their structures may go now, + // ahead of the context data they point to. + core::mem::drop(ctx.retained); } let mut guard = self.alloc.lock(); @@ -1748,7 +1750,7 @@ impl GpuManager for GpuManager::ver { &self.dyncfg } - fn free_context(&self, ctx: KBox>) { + fn free_context(&self, ctx: workqueue::RetiredContext) { let mut garbage = self.garbage_contexts.lock(); if garbage.push(ctx, GFP_KERNEL).is_err() { diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index 8df2cff4f6541..11ed37a7c1ef8 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -7,7 +7,7 @@ use crate::g17::fw::{ self, - initdata::{v25g83, PerfConfig, RegionCConfig, RegisterWindow, PERF_STATES}, + initdata::{PerfConfig, RegionCConfig, RegisterWindow}, }; use crate::g17::{Config, CoreLayout, CrashBuffers, FixedBuffer, Identity}; use crate::hw::{GpuGen, GpuVariant}; @@ -62,17 +62,41 @@ pub(crate) const CONFIG: Config = Config { firmware: fw::variant::Firmware::Identified(&[fw::variant::Build { os_version: [26, 6, 2], name: "macOS 26.6.2 (25G83, iBoot mBoot-18000.161.10)", - image: crate::fwid::FirmwareBuild { - name: "Apple GFX 25G83 for T6040 (normalized loaded image)", - text_size: 0x60000, - data_size: 0x138000, - entropy: [0x59839, 0x5991c], - text_sha256: [ - 0x03, 0xdf, 0x75, 0x48, 0x4a, 0x7f, 0xdd, 0x6e, 0x4b, 0x47, 0x7f, 0x95, 0x6c, 0xaa, - 0x5b, 0x44, 0x06, 0xdf, 0xff, 0x4d, 0x19, 0x17, 0x28, 0x62, 0x7f, 0x53, 0x10, 0xe0, - 0x63, 0x87, 0x6d, 0xcd, - ], - }, + images: &[ + crate::fwid::FirmwareBuild { + name: "Apple GFX 25G83 for T6040 (normalized loaded image)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x59839, 0x5991c], + text_sha256: [ + 0x03, 0xdf, 0x75, 0x48, 0x4a, 0x7f, 0xdd, 0x6e, 0x4b, 0x47, 0x7f, 0x95, 0x6c, + 0xaa, 0x5b, 0x44, 0x06, 0xdf, 0xff, 0x4d, 0x19, 0x17, 0x28, 0x62, 0x7f, 0x53, + 0x10, 0xe0, 0x63, 0x87, 0x6d, 0xcd, + ], + }, + crate::fwid::FirmwareBuild { + name: "Apple GFX 25G83 for T6040 (normalized loaded image, alternate J616s fill)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x59839, 0x5991c], + text_sha256: [ + 0x19, 0x0c, 0xef, 0x19, 0x46, 0xa2, 0x3d, 0xfb, 0xde, 0x18, 0x05, 0x92, 0x62, + 0x96, 0x31, 0xc3, 0xb2, 0x8d, 0x05, 0x09, 0xa1, 0x7e, 0xb7, 0x61, 0x08, 0xb7, + 0xc9, 0xa5, 0x2a, 0xcc, 0xd9, 0xac, + ], + }, + crate::fwid::FirmwareBuild { + name: "Apple GFX 25G83 for T6040 (normalized loaded image, second alternate J616s fill)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x59839, 0x5991c], + text_sha256: [ + 0xd7, 0x25, 0x26, 0x4d, 0x48, 0x31, 0xb9, 0x5a, 0x5b, 0x8a, 0x37, 0x5a, 0xbf, + 0x2f, 0xb5, 0xcd, 0xfb, 0xd6, 0xd7, 0xcb, 0xc7, 0xf1, 0xba, 0xa2, 0xcc, 0x8b, + 0x37, 0xee, 0x5d, 0x6e, 0x5e, 0x98, + ], + }, + ], variant: &fw::variant::V25G83, }]), // Measured on the 20-core M4 Pro; other core counts are refused at probe. @@ -122,7 +146,11 @@ const fn instances(window: RegisterWindow, instances: u32, phys_stride: u64) -> /// controllers (ADT /arm-io/mcc), slot 20 the four AFR blocks. pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ window(0, 0x5_0241_4000, 0x20_0000, 0x4000, 0, 2), - instances(window(3, 0x2_201c_4000, 0x20_8000, 0x1_8000, 0, 2), 4, 0x200_0000), + instances( + window(3, 0x2_201c_4000, 0x20_8000, 0x1_8000, 0, 2), + 4, + 0x200_0000, + ), window(4, 0x5_0244_c000, 0x26_c000, 0x4000, 0, 0), window(9, 0x5_003d_0000, 0x27_4000, 0x4000, 0, 2), window(10, 0x5_003c_0000, 0x27_c000, 0x4000, 0, 0), @@ -131,7 +159,11 @@ pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ window(14, 0x5_0040_0000, 0x29_4000, 0x1_0000, 0, 0), window(15, 0x5_0cdc_0000, 0x2a_8000, 0x4000, 0, 0), window(17, 0x2_8800_0000, 0x2b_0000, 0x2_1400, 0, 2), - instances(window(20, 0x2_8b00_0000, 0x2d_8000, 0x4_0000, 0, 2), 4, 0x20_0000), + instances( + window(20, 0x2_8b00_0000, 0x2d_8000, 0x4_0000, 0, 2), + 4, + 0x20_0000, + ), window(22, 0x2_8900_0000, 0x3d_c000, 0x8000, 0, 2), window(26, 0x2_88d0_4000, 0x3e_8000, 0x8000, 0xd0_4000, 2), window(28, 0x2_88d5_8000, 0x3f_4000, 0x8000, 0xd5_8000, 2), @@ -148,33 +180,23 @@ pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ window(43, 0x2_88e6_0000, 0x47_4000, 0x58, 0xe6_0000, 2), ]; -/// Performance-table configuration. The device tree lists the sixteen operating points in -/// performance-state order, with one voltage per cluster. +/// Inert defaults. The 25G83 builder requires the loader's per-chip performance maps. pub(crate) const PERF: PerfConfig = PerfConfig { - states_a: [0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15], - states_b: [0, 1, 2, 3, 4, 6, 7, 9, 11, 13, 15], - relative_a: [0, 9, 16, 23, 29, 38, 49, 60, 69, 79, 100], - relative_b: [0, 0, 22, 36, 47, 57, 68, 78, 84, 91, 100], + states_a: [0; 11], + states_b: [0; 11], + relative_a: [0; 11], + relative_b: [0; 11], sram_min_mv: 810, }; /// Operating points the performance tables reference. pub(crate) const OPPS: usize = 16; -const _: () = { - let mut index = 0; - while index < PERF_STATES { - assert!((PERF.states_a[index] as usize) < OPPS); - assert!((PERF.states_b[index] as usize) < OPPS); - index += 1; - } -}; - /// Region C. const REGION_C: RegionCConfig = RegionCConfig { unk_54: 0x0fa0, unk_78: [1, 0, 0], - max_power_mw: v25g83::MAX_POWER_MW, + max_power_mw: 0, unk_9c: 0, unk_a0: 0, unk_7c4: 1500, diff --git a/drivers/gpu/drm/asahi/hw/t8140.rs b/drivers/gpu/drm/asahi/hw/t8140.rs index 75925713bf10b..47dc4d583a5fc 100644 --- a/drivers/gpu/drm/asahi/hw/t8140.rs +++ b/drivers/gpu/drm/asahi/hw/t8140.rs @@ -417,9 +417,10 @@ pub(crate) mod scheduling { pub(crate) mod queues { pub(crate) const RENDER_SLOTS: usize = 64; // One physical pair per logical owner leaves room for every execution - // context in the shared 128-QID namespace. Commands queue at RENDER_DEPTH. + // context in the shared 128-QID namespace. Commands queue at RENDER_DEPTH, + // as many as a pair has command records (the render memory's RECORDS). pub(crate) const RENDER_PAIRS_PER_OWNER: usize = 1; - pub(crate) const RENDER_DEPTH: u8 = 16; + pub(crate) const RENDER_DEPTH: u8 = 32; pub(crate) const COMPUTE_DEPTH: usize = 16; } diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs index 0a0a51a02cfba..3522010949e35 100644 --- a/drivers/gpu/drm/asahi/mmu.rs +++ b/drivers/gpu/drm/asahi/mmu.rs @@ -1201,56 +1201,85 @@ pub(crate) struct KernelMappingInner { mapped_size: usize, } -/// Prepared CPU timestamp access; destruction happens after completion signalling. -pub(crate) struct TimestampMapping { - mapping: kernel::drm::gem::shmem::VMapOwned, - offset: usize, -} +/// A kernel CPU mapping of a timestamp buffer's GEM backing. Dropping it takes the object's +/// reservation lock, so it must not be dropped where fence producers run. +pub(crate) struct TimestampVmap(kernel::drm::gem::shmem::VMapOwned); -impl TimestampMapping { - /// Prepare one bounded CPU timestamp destination on retained GEM backing. - pub(crate) fn new(gem: &gem::Object, offset: usize) -> Result { +impl TimestampVmap { + pub(crate) fn new(gem: &gem::Object) -> Result { + Ok(Self(gem.owned_vmap::<0>()?)) + } + + fn size(&self) -> usize { use kernel::drm::gem::BaseObject; - if offset.checked_add(size_of::()).ok_or(EOVERFLOW)? > gem.size() { - return Err(ERANGE); - } - Ok(Self { - mapping: gem.owned_vmap::<0>()?, - offset, - }) + self.0.owner().size() } - pub(crate) fn write(&self, value: u64) { + /// Writes a timestamp at byte `offset` of the GEM object. Aligned results use a complete + /// atomic store; byte stores support unaligned destinations without creating mutable + /// aliases. An unaligned complete timestamp is not atomic. Out-of-range offsets are refused + /// when the destination is prepared, and ignored here. + pub(crate) fn write(&self, offset: usize, value: u64) { + if offset.checked_add(size_of::()).is_none_or(|end| end > self.size()) { + return; + } // Completion runs in task context. Bindings of the same BO share this lock, // so aligned u64 and overlapping unaligned byte stores never race in Rust. - let _guard = self.mapping.owner().timestamp_lock.lock(); - // SAFETY: timestamp_mapping checked the complete extent inside this pinned vmap. - let base = self.mapping.addr.cast::(); - let pointer = unsafe { base.add(self.offset) }; + let _guard = self.0.owner().timestamp_lock.lock(); + // SAFETY: The complete extent was checked above, inside this pinned vmap. + let base = self.0.addr.cast::(); + let pointer = unsafe { base.add(offset) }; if pointer as usize % core::mem::align_of::() == 0 { - // SAFETY: timestamp_mapping checked all eight bytes; the vmap pins the backing. + // SAFETY: All eight bytes were checked above; the vmap pins the backing. // A naturally aligned query result becomes available in one complete store. let destination = unsafe { &*pointer.cast::() }; destination.store(value.to_le(), Ordering::Relaxed); } else { for (index, byte) in value.to_le_bytes().into_iter().enumerate() { - // SAFETY: timestamp_mapping checked the complete eight-byte extent. - // The owned vmap pins its backing; byte atomics permit unaligned and - // overlapping timestamp destinations without mutable slice aliases. - let destination = unsafe { &*base.add(self.offset + index).cast::() }; + // SAFETY: The complete eight-byte extent was checked above. The owned + // vmap pins its backing; byte atomics permit unaligned and overlapping + // timestamp destinations without mutable slice aliases. + let destination = unsafe { &*base.add(offset + index).cast::() }; destination.store(byte, Ordering::Relaxed); } } // A CPU-retired query can be consumed by a later GPU command. WRITEBACK // objects use a cacheable vmap, while GPU mappings need not snoop that cache. // Publish the bytes before signalling the completion fence or queue frontier. - if self.mapping.owner().is_writeback() { + if self.0.owner().is_writeback() { crate::pgtable::clean_poc(pointer, size_of::()); } crate::pgtable::poc_barrier(); } } +/// Prepared CPU timestamp access with its own vmap; destruction happens after completion +/// signalling. +#[cfg(CONFIG_DRM_ASAHI_G16)] +pub(crate) struct TimestampMapping { + mapping: TimestampVmap, + offset: usize, +} + +#[cfg(CONFIG_DRM_ASAHI_G16)] +impl TimestampMapping { + /// Prepare one bounded CPU timestamp destination on retained GEM backing. + pub(crate) fn new(gem: &gem::Object, offset: usize) -> Result { + use kernel::drm::gem::BaseObject; + if offset.checked_add(size_of::()).ok_or(EOVERFLOW)? > gem.size() { + return Err(ERANGE); + } + Ok(Self { + mapping: TimestampVmap::new(gem)?, + offset, + }) + } + + pub(crate) fn write(&self, value: u64) { + self.mapping.write(self.offset, value); + } +} + /// An object mapping into a [`Vm`], which reserves the address range from use by other mappings. pub(crate) struct KernelMapping(ManuallyDrop>); @@ -1285,7 +1314,15 @@ impl KernelMapping { /// use a complete atomic store; byte stores support unaligned destinations without /// creating mutable aliases. An unaligned complete timestamp is not atomic. /// Retain CPU access before this destination's completion fence is published. + #[cfg(CONFIG_DRM_ASAHI_G16)] pub(crate) fn timestamp_mapping(&self, offset: usize) -> Result { + let gem = self.0._gem.as_ref().ok_or(EINVAL)?; + TimestampMapping::new(gem, self.timestamp_offset(offset)?) + } + + /// The GEM object offset of the timestamp at `offset` in this mapping, whose complete + /// extent lies inside both the mapping and the object. + pub(crate) fn timestamp_offset(&self, offset: usize) -> Result { use kernel::drm::gem::BaseObject; let gem = self.0._gem.as_ref().ok_or(EINVAL)?; if offset.checked_add(size_of::()).ok_or(EOVERFLOW)? > self.size() { @@ -1296,7 +1333,12 @@ impl KernelMapping { if end > gem.size() { return Err(ERANGE); } - TimestampMapping::new(gem, start) + Ok(start) + } + + /// A kernel CPU mapping of this mapping's GEM backing, for timestamp writes. + pub(crate) fn timestamp_vmap(&self) -> Result { + TimestampVmap::new(self.0._gem.as_ref().ok_or(EINVAL)?) } /// Gives a fresh kernel mapping a firmware-cached prefix, preserving its tail. diff --git a/drivers/gpu/drm/asahi/queue/mod.rs b/drivers/gpu/drm/asahi/queue/mod.rs index 2b32b97b5e169..2d9052c2edb51 100644 --- a/drivers/gpu/drm/asahi/queue/mod.rs +++ b/drivers/gpu/drm/asahi/queue/mod.rs @@ -525,6 +525,15 @@ impl Queue::ver { let buffer = buffer::Buffer::ver::new(gpu, alloc, ualloc.clone(), ualloc_priv, mgr)?; + let gpu_context = Arc::new( + workqueue::GpuContext::new(dev.clone(), alloc, buffer.any_ref())?, + GFP_KERNEL, + )?; + // Every command of this queue points the firmware at the notifier, and the notifier links + // into the notifier list of the work queues. Keep it as long as they are kept, until the + // context is invalidated, instead of freeing it with the last command. + gpu_context.retain(KBox::new(notifier.clone(), GFP_KERNEL)?)?; + let mut ret = Queue::ver { dev: dev.clone(), _sched: sched, @@ -538,11 +547,7 @@ impl Queue::ver { inner: QueueInner::ver { dev: dev.clone(), ualloc, - gpu_context: Arc::new( - workqueue::GpuContext::new(dev.clone(), alloc, buffer.any_ref())?, - GFP_KERNEL, - )?, - + gpu_context, buffer, notifier_list: Arc::new(notifier_list, GFP_KERNEL)?, notifier, diff --git a/drivers/gpu/drm/asahi/regs.rs b/drivers/gpu/drm/asahi/regs.rs index e7f6fd810b4ad..e8b845cba989f 100644 --- a/drivers/gpu/drm/asahi/regs.rs +++ b/drivers/gpu/drm/asahi/regs.rs @@ -709,6 +709,11 @@ impl G17Resources { self.sgx_read32::() & 0xf != 0 } + /// Raw core power state, for tracing only. + pub(crate) fn g17_core_power(&self) -> u32 { + self.sgx_read32::() + } + /// Enable dynamic power gating of idle G17 GPU cores. The firmware expects this before it is /// initialized. pub(crate) fn enable_g17_gating(&self) -> Result { diff --git a/drivers/gpu/drm/asahi/timestamp.rs b/drivers/gpu/drm/asahi/timestamp.rs index 976ec9f6bf48b..9f13436b4caf5 100644 --- a/drivers/gpu/drm/asahi/timestamp.rs +++ b/drivers/gpu/drm/asahi/timestamp.rs @@ -13,10 +13,20 @@ use kernel::{prelude::*, sync::Arc}; #[cfg(CONFIG_DRM_ASAHI_G16)] use kernel::{uapi, xarray}; +/// A firmware-mapped timestamp buffer with a kernel CPU mapping of its backing, kept for the +/// buffer's lifetime so that commands need no mapping of their own. +pub(crate) struct FirmwareBuffer { + mapping: Arc, + cpu: mmu::TimestampVmap, +} + /// A bound timestamp destination. Only backends which publish firmware writes need a UAT alias. /// CPU-retired timestamps retain their backing without consuming global firmware address space. pub(crate) enum Buffer { - Firmware(Arc), + /// The last reference may be a command's, dropped where fence producers run, while + /// releasing the CPU mapping takes the reservation lock: the deferral runs the release + /// on the cleanup queue. + Firmware(crate::cleanup::Deferred), #[cfg(CONFIG_DRM_ASAHI_G16)] Cpu { // The GEM pin keeps the object's held SG table alive through CPU publication. @@ -27,7 +37,11 @@ pub(crate) enum Buffer { impl Buffer { pub(crate) fn firmware(mapping: mmu::KernelMapping) -> Result { - Ok(Self::Firmware(Arc::new(mapping, GFP_KERNEL)?)) + let cpu = mapping.timestamp_vmap()?; + Ok(Self::Firmware(crate::cleanup::Deferred::new(FirmwareBuffer { + mapping: Arc::new(mapping, GFP_KERNEL)?, + cpu, + })?)) } /// Retain a CPU-written byte range without publishing any firmware address. @@ -46,15 +60,16 @@ impl Buffer { pub(crate) fn size(&self) -> usize { match self { - Self::Firmware(mapping) => mapping.size(), + Self::Firmware(buffer) => buffer.mapping.size(), #[cfg(CONFIG_DRM_ASAHI_G16)] Self::Cpu { range, .. } => range.end - range.start, } } + #[cfg(CONFIG_DRM_ASAHI_G16)] pub(crate) fn timestamp_mapping(&self, offset: usize) -> Result { match self { - Self::Firmware(mapping) => mapping.timestamp_mapping(offset), + Self::Firmware(buffer) => buffer.mapping.timestamp_mapping(offset), #[cfg(CONFIG_DRM_ASAHI_G16)] Self::Cpu { gem, range, .. } => { if offset.checked_add(size_of::()).ok_or(EOVERFLOW)? > self.size() { @@ -65,10 +80,30 @@ impl Buffer { } } + /// The object offset of the timestamp at `offset` of a firmware buffer, for + /// [`Self::write_timestamp`]. + pub(crate) fn timestamp_offset(&self, offset: usize) -> Result { + match self { + Self::Firmware(buffer) => buffer.mapping.timestamp_offset(offset), + #[cfg(CONFIG_DRM_ASAHI_G16)] + Self::Cpu { .. } => Err(EINVAL), + } + } + + /// Writes a timestamp at an object offset returned by [`Self::timestamp_offset`], + /// through the buffer's own CPU mapping. + pub(crate) fn write_timestamp(&self, offset: usize, value: u64) { + match self { + Self::Firmware(buffer) => buffer.cpu.write(offset, value), + #[cfg(CONFIG_DRM_ASAHI_G16)] + Self::Cpu { .. } => {} + } + } + /// Legacy command encoders publish firmware pointers and must never accept a CPU-only buffer. pub(crate) fn firmware_mapping(&self) -> Result> { match self { - Self::Firmware(mapping) => Ok(mapping.clone()), + Self::Firmware(buffer) => Ok(buffer.mapping.clone()), #[cfg(CONFIG_DRM_ASAHI_G16)] Self::Cpu { .. } => Err(EINVAL), } diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs index 6281ef684e251..d6695a1b3ee96 100644 --- a/drivers/gpu/drm/asahi/workqueue.rs +++ b/drivers/gpu/drm/asahi/workqueue.rs @@ -95,10 +95,31 @@ impl From for kernel::error::Error { } } +/// An object the firmware may still reference while its GPU context is live. +pub(crate) type RetainedObject = KBox; + +/// Objects retained by every context: the notifier and the vertex, fragment and compute work +/// queues of the owning queue. +const CONTEXT_RETAINED_OBJECTS: usize = 4; + +/// A dropped GPU context waiting for the firmware to invalidate it. +pub(crate) struct RetiredContext { + /// The context data named in the invalidation request. + pub(crate) data: KBox>, + /// Firmware structures of the context's queues. They must not be freed, and so must not be + /// reused, until the invalidation has completed. + pub(crate) retained: KVec, +} + /// A GPU context tracking structure, which must be explicitly invalidated when dropped. +/// +/// The firmware learns about the work queues of a context as they are run, and only forgets them +/// when the context is invalidated. Their firmware structures are therefore handed to the context +/// when their owner drops them, and freed after the invalidation. pub(crate) struct GpuContext { dev: driver::AsahiDevice, data: Option>>, + retained: Pin>>>, } no_debug!(GpuContext); @@ -118,27 +139,42 @@ impl GpuContext { )?, GFP_KERNEL, )?), + retained: KBox::pin_init( + new_mutex!( + KVec::with_capacity(CONTEXT_RETAINED_OBJECTS, GFP_KERNEL)?, + "GpuContext::retained" + ), + GFP_KERNEL, + )?, }) } - /// Returns the GPU pointer to the inner GPU context data structure. - pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, fw::workqueue::GpuContextData> { - self.data.as_ref().unwrap().gpu_pointer() + /// Returns a weak GPU pointer to the inner GPU context data structure. + pub(crate) fn weak_pointer(&self) -> GpuWeakPointer { + self.data.as_ref().unwrap().weak_pointer() + } + + /// Keep `object` allocated until the firmware has invalidated this context. + pub(crate) fn retain(&self, object: RetainedObject) -> Result { + self.retained.lock().push(object, GFP_KERNEL)?; + Ok(()) } } impl Drop for GpuContext { fn drop(&mut self) { + let data = self.data.take().unwrap(); + let retained = core::mem::take(&mut *self.retained.lock()); + let Some(guard) = self.dev.registration_guard() else { return; }; guard.registration_data_with(|reg_data| { - let data = self.data.take().unwrap(); // A context only exists for the manager whose allocators created it, so the manager is // always present. if let Some(gpu) = reg_data.gpu.manager() { - gpu.free_context(data); + gpu.free_context(RetiredContext { data, retained }); } }) } @@ -237,7 +273,10 @@ impl) + Send + Sync> GenSubm struct WorkQueueInner { dev: driver::AsahiDevice, event_manager: Arc, - info: GpuObject, + /// The firmware structures of this queue. Always `Some` until the queue is dropped, when they + /// move to `gpu_context`. + info: Option>>, + gpu_context: Arc, new: bool, pipe_type: PipeType, size: u32, @@ -265,10 +304,22 @@ pub(crate) struct WorkQueue { #[versions(AGX)] impl WorkQueueInner::ver { + fn info(&self) -> &GpuObject { + self.info + .as_deref() + .expect("WorkQueue: lost its queue info") + } + + fn info_mut(&mut self) -> &mut GpuObject { + self.info + .as_deref_mut() + .expect("WorkQueue: lost its queue info") + } + /// Return the GPU done pointer, representing how many work items have been completed by the /// GPU. fn doneptr(&self) -> u32 { - self.info + self.info() .state .with(|raw, _inner| raw.gpu_doneptr.load(Ordering::Acquire)) } @@ -485,7 +536,7 @@ impl Job::ver { let next_wptr = (wptr + 1) % inner.size; assert!(inner.doneptr() != next_wptr); - inner.info.ring[wptr as usize] = command.inner.gpu_va().get(); + inner.info_mut().ring[wptr as usize] = command.inner.gpu_va().get(); wptr = next_wptr; // Cannot fail, since we did a reserve(1) above @@ -515,7 +566,7 @@ impl<'a> JobSubmission::ver<'a> { core::mem::forget(self); inner - .info + .info() .state .with(|raw, _inner| raw.cpu_wptr.store(wptr, Ordering::Release)); @@ -527,7 +578,7 @@ impl<'a> JobSubmission::ver<'a> { let msg = fw::channels::RunWorkQueueMsg::ver { pipe_type: inner.pipe_type, - work_queue: Some(inner.info.weak_pointer()), + work_queue: Some(inner.info().weak_pointer()), wptr: inner.wptr, event_slot, is_new: inner.new, @@ -617,6 +668,23 @@ impl<'a> Drop for JobSubmission::ver<'a> { } } +#[versions(AGX)] +impl Drop for WorkQueueInner::ver { + fn drop(&mut self) { + // The firmware keeps referring to this queue until its context is invalidated. When the + // queue's owner went away with work in flight, the queue is dropped as soon as that work + // completes, which can be well before the invalidation. + if let Some(info) = self.info.take() { + if self.gpu_context.retain(info).is_err() { + pr_err!( + "WorkQueue({:?}): Failed to keep the queue until its context is invalidated\n", + self.pipe_type + ); + } + } + } +} + #[versions(AGX)] impl WorkQueueInner::ver { /// Return the number of free entries in the workqueue @@ -680,61 +748,65 @@ impl WorkQueue::ver { prio.5 = 1; } + let gpu_context_pointer = gpu_context.weak_pointer(); + + // Use shared (coherent) state with verbose faults so we can dump state correctly + let info = if debug_enabled(DebugFlags::VerboseFaults) { + &mut alloc.shared + } else { + &mut alloc.private + } + .new_init( + try_init!(QueueInfo::ver { + state: { + state.with_mut(|raw, _inner| { + raw.rb_size = size; + }); + state + }, + ring, + gpu_buf, + notifier_list: notifier_list, + }), + |inner, _p| { + try_init!(raw::QueueInfo::ver { + state: inner.state.gpu_pointer(), + ring: inner.ring.gpu_pointer(), + notifier_list: inner.notifier_list.gpu_pointer(), + gpu_buf: inner.gpu_buf.gpu_pointer(), + gpu_rptr1: Default::default(), + gpu_rptr2: Default::default(), + gpu_rptr3: Default::default(), + event_id: AtomicI32::new(-1), + priority: prio, + unk_4c: -1, + uuid: id as u32, + unk_54: -1, + unk_58: Default::default(), + busy: Default::default(), + __pad: Default::default(), + #[ver(V >= V13_2 && G < G14X)] + unk_84_0: 0, + unk_84_state: Default::default(), + error_count: Default::default(), + unk_8c: 0, + unk_90: 0, + unk_94: 0, + pending: Default::default(), + unk_9c: 0, + gpu_context: gpu_context_pointer, + unk_a8: Default::default(), + #[ver(V >= V13_2 && G < G14X)] + unk_b0: 0, + }) + }, + )?; + let inner = WorkQueueInner::ver { dev: dev.into(), event_manager, - // Use shared (coherent) state with verbose faults so we can dump state correctly - info: if debug_enabled(DebugFlags::VerboseFaults) { - &mut alloc.shared - } else { - &mut alloc.private - } - .new_init( - try_init!(QueueInfo::ver { - state: { - state.with_mut(|raw, _inner| { - raw.rb_size = size; - }); - state - }, - ring, - gpu_buf, - notifier_list: notifier_list, - gpu_context: gpu_context, - }), - |inner, _p| { - try_init!(raw::QueueInfo::ver { - state: inner.state.gpu_pointer(), - ring: inner.ring.gpu_pointer(), - notifier_list: inner.notifier_list.gpu_pointer(), - gpu_buf: inner.gpu_buf.gpu_pointer(), - gpu_rptr1: Default::default(), - gpu_rptr2: Default::default(), - gpu_rptr3: Default::default(), - event_id: AtomicI32::new(-1), - priority: prio, - unk_4c: -1, - uuid: id as u32, - unk_54: -1, - unk_58: Default::default(), - busy: Default::default(), - __pad: Default::default(), - #[ver(V >= V13_2 && G < G14X)] - unk_84_0: 0, - unk_84_state: Default::default(), - error_count: Default::default(), - unk_8c: 0, - unk_90: 0, - unk_94: 0, - pending: Default::default(), - unk_9c: 0, - gpu_context: inner.gpu_context.gpu_pointer(), - unk_a8: Default::default(), - #[ver(V >= V13_2 && G < G14X)] - unk_b0: 0, - }) - }, - )?, + info: Some(KBox::new(info, GFP_KERNEL)?), + gpu_context, new: true, pipe_type, size, @@ -751,7 +823,7 @@ impl WorkQueue::ver { last_submitted: None, }; - let info_pointer = inner.info.weak_pointer(); + let info_pointer = inner.info().weak_pointer(); Arc::pin_init( pin_init!(Self { @@ -835,7 +907,7 @@ impl WorkQueue::ver { pub(crate) fn dump_info(&self) { pr_info!("WorkQueue @ {:?}:", self.info_pointer); - self.inner.lock().info.with(|raw, _inner| { + self.inner.lock().info().with(|raw, _inner| { pr_info!(" GPU rptr1: {:#x}", raw.gpu_rptr1.load(Ordering::Relaxed)); pr_info!(" GPU rptr1: {:#x}", raw.gpu_rptr2.load(Ordering::Relaxed)); pr_info!(" GPU rptr1: {:#x}", raw.gpu_rptr3.load(Ordering::Relaxed)); @@ -953,7 +1025,7 @@ impl WorkQueue for WorkQueue::ver { ); inner - .info + .info() .state .with(|raw, _inner| raw.cpu_freeptr.store(last_wptr, Ordering::Release)); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 54eb5745105ff..9b6b0593310da 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1252,10 +1252,13 @@ config SENSORS_MACSMC_HWMON_KUNIT_TEST depends on SENSORS_MACSMC_HWMON && KUNIT=y default KUNIT_ALL_TESTS help - Unit tests for the channel numbering of the Apple SMC hwmon driver. + Unit tests for the channel numbering and the manual fan control of + the Apple SMC hwmon driver. Thermal zones refer to temperature channels by index, so a sensor that cannot be set up must keep its channel; the tests check that. - No SMC is needed. Only useful for kernel developers. + No SMC is needed: the fan tests model its fan keys and need + MFD_MACSMC_KUNIT_TEST for its read and write stubs. Only useful for + kernel developers. config SENSORS_MAX1111 tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles" diff --git a/drivers/hwmon/macsmc-hwmon-test.c b/drivers/hwmon/macsmc-hwmon-test.c index e7239986ce3c9..596e904a6f3e8 100644 --- a/drivers/hwmon/macsmc-hwmon-test.c +++ b/drivers/hwmon/macsmc-hwmon-test.c @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* * KUnit tests for the Apple SMC hwmon driver: channel numbering of the - * temperature sensors, which thermal zones depend on. + * temperature sensors, which thermal zones depend on, and manual fan control. * * Included by macsmc-hwmon.c. */ +#include #include /* What macsmc_hwmon_create_sensor() leaves behind for one node. */ @@ -84,4 +85,261 @@ static struct kunit_suite macsmc_hwmon_test_suite = { .test_cases = macsmc_hwmon_test_cases, }; -kunit_test_suite(macsmc_hwmon_test_suite); +/* + * Manual fan control against a model of the SMC fan keys. In "locked" firmware + * the fans start in system mode (3) and refuse manual mode until Ftst is 1; + * Ftst back to 0 returns them to system mode. "Legacy" firmware takes the mode + * write directly. The model needs the SMC read/write stubs of + * CONFIG_MFD_MACSMC_KUNIT_TEST. + */ +#define FAN_TEST_F1350 0x44a8c000 /* 1350.0f */ +#define FAN_TEST_F2500 0x451c4000 /* 2500.0f */ +#define FAN_TEST_F5777 0x45b48800 /* 5777.0f */ + +struct macsmc_fan_test { + struct apple_smc smc; + struct kunit *test; + struct macsmc_hwmon hwmon; + struct macsmc_hwmon_fan fans[2]; + bool legacy; + u8 mode[2]; + u32 target[2]; + u8 ftst; + int ftst_writes; + int refused; + smc_key fail_key; +}; + +static int fan_test_index(smc_key key) +{ + return ((key >> 16) & 0xff) - '0'; +} + +static int fan_test_read(struct apple_smc *smc, smc_key key, void *buf, size_t size) +{ + struct macsmc_fan_test *t = container_of(smc, struct macsmc_fan_test, smc); + u32 value; + int i = fan_test_index(key); + + switch (key & 0xff00ffff) { + case __SMC_KEY('F', 0, 'M', 'd'): + value = t->mode[i]; + break; + case __SMC_KEY('F', 0, 'M', 'n'): + value = FAN_TEST_F1350; + break; + case __SMC_KEY('F', 0, 'M', 'x'): + value = FAN_TEST_F5777; + break; + case __SMC_KEY('F', 0, 'T', 'g'): + value = t->target[i]; + break; + default: + KUNIT_FAIL(t->test, "unexpected read of %08x", key); + return -EINVAL; + } + memcpy(buf, &value, size); + return size; +} + +static int fan_test_write(struct apple_smc *smc, smc_key key, const void *buf, size_t size) +{ + struct macsmc_fan_test *t = container_of(smc, struct macsmc_fan_test, smc); + u32 value = 0; + int i = fan_test_index(key); + + memcpy(&value, buf, min(size, sizeof(value))); + if (key == t->fail_key) + return -EIO; + + if (key == SMC_KEY(Ftst)) { + t->ftst_writes++; + t->ftst = value; + for (i = 0; i < ARRAY_SIZE(t->mode); i++) { + if (value && t->mode[i] == MACSMC_FAN_MODE_SYSTEM) + t->mode[i] = MACSMC_FAN_MODE_AUTO; + else if (!value) + t->mode[i] = MACSMC_FAN_MODE_SYSTEM; + } + return size; + } + + switch (key & 0xff00ffff) { + case __SMC_KEY('F', 0, 'M', 'd'): + if (!t->legacy && t->mode[i] == MACSMC_FAN_MODE_SYSTEM) { + t->refused++; + return -EIO; + } + t->mode[i] = value; + return size; + case __SMC_KEY('F', 0, 'T', 'g'): + t->target[i] = value; + return size; + } + KUNIT_FAIL(t->test, "unexpected write of %08x", key); + return -EINVAL; +} + +static void fan_test_key(struct macsmc_hwmon_sensor *sensor, const char *key, u32 type) +{ + sensor->macsmc_key = _SMC_KEY(key); + sensor->info.type_code = type; + sensor->info.flags = APPLE_SMC_READABLE | APPLE_SMC_WRITABLE; +} + +static int macsmc_fan_test_init(struct kunit *test) +{ + static const char *const keys[2][5] = { + { "F0Ac", "F0Mn", "F0Mx", "F0Tg", "F0Md" }, + { "F1Ac", "F1Mn", "F1Mx", "F1Tg", "F1Md" }, + }; + const u32 flt = __SMC_KEY('f', 'l', 't', ' '), ui8 = __SMC_KEY('u', 'i', '8', ' '); + struct macsmc_fan_test *t; + int i; + + if (!IS_ENABLED(CONFIG_MFD_MACSMC_KUNIT_TEST)) + kunit_skip(test, "needs CONFIG_MFD_MACSMC_KUNIT_TEST for the SMC stubs"); + + t = kunit_kzalloc(test, sizeof(*t), GFP_KERNEL); + if (!t) + return -ENOMEM; + t->test = test; + for (i = 0; i < 2; i++) { + fan_test_key(&t->fans[i].now, keys[i][0], flt); + fan_test_key(&t->fans[i].min, keys[i][1], flt); + fan_test_key(&t->fans[i].max, keys[i][2], flt); + fan_test_key(&t->fans[i].set, keys[i][3], flt); + fan_test_key(&t->fans[i].mode, keys[i][4], ui8); + t->mode[i] = MACSMC_FAN_MODE_SYSTEM; + } + t->hwmon.smc = &t->smc; + t->hwmon.fan.fans = t->fans; + t->hwmon.fan.count = 2; + t->hwmon.fan.unlock_key = SMC_KEY(Ftst); + mutex_init(&t->hwmon.fan.lock); + test->priv = t; + fan_control = true; + kunit_activate_static_stub(test, apple_smc_read, fan_test_read); + kunit_activate_static_stub(test, apple_smc_write, fan_test_write); + return 0; +} + +static void macsmc_fan_test_exit(struct kunit *test) +{ + fan_control = false; +} + +static void fan_test_released(struct kunit *test, struct macsmc_fan_test *t) +{ + KUNIT_EXPECT_EQ(test, t->ftst, 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->mode[1], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 0); + KUNIT_EXPECT_FALSE(test, t->hwmon.fan.unlocked); + KUNIT_EXPECT_FALSE(test, t->fans[0].manual); +} + +/* Locked firmware: unlock on the first manual request, release on auto. */ +static void macsmc_fan_test_unlock_and_release(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->ftst, 1); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_MANUAL); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F2500); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 1); + + /* A second target needs no new unlock. */ + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 1); + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + fan_test_released(test, t); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 2); +} + +/* Targets are clamped to [min, max]; negative ones are refused. */ +static void macsmc_fan_test_clamp(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 100), 0); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F1350); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 100000), 0); + KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F5777); + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, -1), -EINVAL); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + fan_test_released(test, t); +} + +/* A failed target write hands the fan straight back. */ +static void macsmc_fan_test_target_write_fails(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->fail_key = SMC_KEY(F0Tg); + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), -EIO); + fan_test_released(test, t); +} + +/* Two manual fans: the unlock is released only with the last one. */ +static void macsmc_fan_test_two_fans(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 1, 2500), 0); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 2); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + KUNIT_EXPECT_EQ(test, t->ftst, 1); + KUNIT_EXPECT_EQ(test, t->mode[1], MACSMC_FAN_MODE_MANUAL); + /* Removal, shutdown and suspend all restore through this. */ + macsmc_hwmon_fans_restore(&t->hwmon); + fan_test_released(test, t); +} + +/* Locked firmware without the unlock key: refused, nothing else written. */ +static void macsmc_fan_test_no_unlock_key(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->hwmon.fan.unlock_key = 0; + KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), -EIO); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_SYSTEM); + KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 0); +} + +/* Firmware that takes the mode directly keeps the mode 0 restore. */ +static void macsmc_fan_test_legacy(struct kunit *test) +{ + struct macsmc_fan_test *t = test->priv; + + t->legacy = true; + t->mode[0] = MACSMC_FAN_MODE_AUTO; + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_MANUAL); + KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0); + KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_AUTO); + KUNIT_EXPECT_EQ(test, t->ftst_writes, 0); + KUNIT_EXPECT_EQ(test, t->refused, 0); +} + +static struct kunit_case macsmc_fan_test_cases[] = { + KUNIT_CASE(macsmc_fan_test_unlock_and_release), + KUNIT_CASE(macsmc_fan_test_clamp), + KUNIT_CASE(macsmc_fan_test_target_write_fails), + KUNIT_CASE(macsmc_fan_test_two_fans), + KUNIT_CASE(macsmc_fan_test_no_unlock_key), + KUNIT_CASE(macsmc_fan_test_legacy), + {} +}; + +static struct kunit_suite macsmc_fan_test_suite = { + .name = "macsmc-hwmon-fan", + .init = macsmc_fan_test_init, + .exit = macsmc_fan_test_exit, + .test_cases = macsmc_fan_test_cases, +}; +kunit_test_suites(&macsmc_hwmon_test_suite, &macsmc_fan_test_suite); diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c index 06d2c6e2d0ac3..76f07add003d3 100644 --- a/drivers/hwmon/macsmc-hwmon.c +++ b/drivers/hwmon/macsmc-hwmon.c @@ -23,12 +23,15 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include #include #include #include @@ -189,10 +192,30 @@ struct macsmc_hwmon_sensors { u32 count; }; +/* Fan mode key values */ +#define MACSMC_FAN_MODE_AUTO 0 +#define MACSMC_FAN_MODE_MANUAL 1 +/* The firmware's own fan policy; it refuses a manual mode request. */ +#define MACSMC_FAN_MODE_SYSTEM 3 + +/* How long the firmware gets to leave or re-enter its system mode */ +#define MACSMC_FAN_MODE_TIMEOUT_MS 10000 +#define MACSMC_FAN_MODE_POLL_MS 100 + struct macsmc_hwmon_fans { struct hwmon_channel_info channel_info; struct macsmc_hwmon_fan *fans; u32 count; + /* Serialises manual fan control and its unlock. */ + struct mutex lock; + /* + * Firmware that keeps its fans in system mode releases them to the + * OS only while this key is 1. Zero if the SMC has no such key. + */ + smc_key unlock_key; + bool unlocked; + /* Fans in manual mode */ + u32 manual; }; struct macsmc_hwmon { @@ -466,57 +489,199 @@ static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan, } } -static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, - long val) +static int macsmc_hwmon_fan_read_mode(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan, u8 *mode) { - struct macsmc_hwmon *hwmon = dev_get_drvdata(dev); + return apple_smc_read_u8(hwmon->smc, fan->mode.macsmc_key, mode); +} + +/* + * Hand the fans back to the firmware once no fan is manual: clear the unlock + * key and wait for every fan to return to system mode. Called with the fan + * lock held. + */ +static int macsmc_hwmon_fans_relock(struct macsmc_hwmon *hwmon) +{ + unsigned long timeout; + u32 i; + u8 mode; + int ret; + + ret = apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 0); + if (ret < 0) { + dev_err(hwmon->dev, "cannot hand the fans back to the SMC (%d)\n", ret); + return ret; + } + hwmon->fan.unlocked = false; + + timeout = jiffies + msecs_to_jiffies(MACSMC_FAN_MODE_TIMEOUT_MS); + for (i = 0; i < hwmon->fan.count; i++) { + struct macsmc_hwmon_fan *fan = &hwmon->fan.fans[i]; + + if (!fan->mode.macsmc_key) + continue; + for (;;) { + ret = macsmc_hwmon_fan_read_mode(hwmon, fan, &mode); + if (ret) + return ret; + if (mode == MACSMC_FAN_MODE_SYSTEM) + break; + if (time_after(jiffies, timeout)) { + dev_warn(hwmon->dev, "%s stays in mode %u after release\n", + fan->label, mode); + return -ETIMEDOUT; + } + msleep(MACSMC_FAN_MODE_POLL_MS); + } + } + + return 0; +} + +/* Return a fan to firmware control. Called with the fan lock held. */ +static int macsmc_hwmon_fan_auto(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan) +{ + int ret, relock; + + if (!fan->manual) + return 0; + + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, MACSMC_FAN_MODE_AUTO); + fan->manual = false; + hwmon->fan.manual--; + + if (hwmon->fan.unlocked && !hwmon->fan.manual) { + relock = macsmc_hwmon_fans_relock(hwmon); + if (relock) + return relock; + } + + return ret < 0 ? ret : 0; +} + +/* + * Put a fan in manual mode. Firmware in system mode refuses that until the + * unlock key is set; it then leaves system mode and accepts the request. + * Called with the fan lock held. + */ +static int macsmc_hwmon_fan_manual(struct macsmc_hwmon *hwmon, + struct macsmc_hwmon_fan *fan) +{ + unsigned long timeout; + u8 mode; + int ret; + + if (fan->manual) + return 0; + + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, MACSMC_FAN_MODE_MANUAL); + if (ret < 0 && hwmon->fan.unlock_key && !hwmon->fan.unlocked && + !macsmc_hwmon_fan_read_mode(hwmon, fan, &mode) && + mode == MACSMC_FAN_MODE_SYSTEM) { + ret = apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 1); + if (ret < 0) { + /* The key may have been set anyway; clear it. */ + apple_smc_write_u8(hwmon->smc, hwmon->fan.unlock_key, 0); + return ret; + } + hwmon->fan.unlocked = true; + + /* Ask again once the fan has left system mode. */ + timeout = jiffies + msecs_to_jiffies(MACSMC_FAN_MODE_TIMEOUT_MS); + for (;;) { + ret = macsmc_hwmon_fan_read_mode(hwmon, fan, &mode); + if (!ret && mode == MACSMC_FAN_MODE_SYSTEM) + ret = -EBUSY; + else if (!ret) + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->mode, + MACSMC_FAN_MODE_MANUAL); + if (ret >= 0 || time_after(jiffies, timeout)) + break; + msleep(MACSMC_FAN_MODE_POLL_MS); + } + if (ret < 0) { + dev_err(hwmon->dev, "%s refused manual mode after unlock (%d)\n", + fan->label, ret); + if (!hwmon->fan.manual) + macsmc_hwmon_fans_relock(hwmon); + return ret; + } + } + if (ret < 0) + return ret; + + fan->manual = true; + hwmon->fan.manual++; + return 0; +} + +static int macsmc_hwmon_set_fan(struct macsmc_hwmon *hwmon, int channel, long val) +{ + struct macsmc_hwmon_fan *fan = &hwmon->fan.fans[channel]; long min, max; int ret; - if (!fan_control || hwmon->fan.fans[channel].mode.macsmc_key == 0) + if (!fan_control || fan->mode.macsmc_key == 0) return -EOPNOTSUPP; + if (val < 0) + return -EINVAL; + + guard(mutex)(&hwmon->fan.lock); + + /* 0 hands the fan back to the firmware. */ + if (!val) + return macsmc_hwmon_fan_auto(hwmon, fan); /* * The SMC does no sanity checks on requested fan speeds, so we need to. */ - ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].min, - 1, &min); + ret = macsmc_hwmon_read_key(hwmon->smc, &fan->min, 1, &min); if (ret) return ret; - ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].max, - 1, &max); + ret = macsmc_hwmon_read_key(hwmon->smc, &fan->max, 1, &max); if (ret) return ret; - if (val >= min && val <= max) { - if (!hwmon->fan.fans[channel].manual) { - /* Write 1 to mode key for manual control */ - ret = macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].mode, 1); - if (ret < 0) - return ret; + if (min <= 0 || max < min) + return -EIO; + val = clamp(val, min, max); - hwmon->fan.fans[channel].manual = true; - } - return macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].set, val); - } else if (!val) { - if (hwmon->fan.fans[channel].manual) { - ret = macsmc_hwmon_write_key(hwmon->smc, - &hwmon->fan.fans[channel].mode, 0); - if (ret < 0) - return ret; + ret = macsmc_hwmon_fan_manual(hwmon, fan); + if (ret) + return ret; - hwmon->fan.fans[channel].manual = false; - } - } else { - return -EINVAL; + ret = macsmc_hwmon_write_key(hwmon->smc, &fan->set, val); + if (ret < 0) { + macsmc_hwmon_fan_auto(hwmon, fan); + return ret; } return 0; } +static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, + long val) +{ + return macsmc_hwmon_set_fan(dev_get_drvdata(dev), channel, val); +} + +/* Hand every manual fan back to the firmware. */ +static void macsmc_hwmon_fans_restore(struct macsmc_hwmon *hwmon) +{ + u32 i; + + guard(mutex)(&hwmon->fan.lock); + for (i = 0; i < hwmon->fan.count; i++) + macsmc_hwmon_fan_auto(hwmon, &hwmon->fan.fans[i]); +} + +static void macsmc_hwmon_fans_restore_action(void *data) +{ + macsmc_hwmon_fans_restore(data); +} + static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -1343,6 +1508,11 @@ static int macsmc_hwmon_probe(struct platform_device *pdev) hwmon->dev = &pdev->dev; hwmon->smc = smc; hwmon->register_thermal_zones = true; + platform_set_drvdata(pdev, hwmon); + + ret = devm_mutex_init(&pdev->dev, &hwmon->fan.lock); + if (ret) + return ret; ret = macsmc_hwmon_populate_sensors(hwmon, hwmon->dev->of_node); if (ret) { @@ -1372,6 +1542,21 @@ static int macsmc_hwmon_probe(struct platform_device *pdev) if (ret) return ret; + if (hwmon->fan.count) { + struct apple_smc_key_info info; + + if (!apple_smc_get_key_info(smc, SMC_KEY(Ftst), &info) && + info.type_code == __SMC_KEY('u', 'i', '8', ' ') && + (info.flags & APPLE_SMC_WRITABLE)) + hwmon->fan.unlock_key = SMC_KEY(Ftst); + + /* Runs after the hwmon device is gone, so no write races it. */ + ret = devm_add_action_or_reset(&pdev->dev, + macsmc_hwmon_fans_restore_action, hwmon); + if (ret) + return ret; + } + hwmon->chip_info.ops = &macsmc_hwmon_ops; hwmon->chip_info.info = (const struct hwmon_channel_info *const *)&hwmon->channel_infos; @@ -1407,11 +1592,29 @@ static const struct of_device_id macsmc_hwmon_of_table[] = { }; MODULE_DEVICE_TABLE(of, macsmc_hwmon_of_table); +static void macsmc_hwmon_shutdown(struct platform_device *pdev) +{ + struct macsmc_hwmon *hwmon = platform_get_drvdata(pdev); + + if (hwmon) + macsmc_hwmon_fans_restore(hwmon); +} + +static int macsmc_hwmon_suspend(struct device *dev) +{ + macsmc_hwmon_fans_restore(dev_get_drvdata(dev)); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(macsmc_hwmon_pm_ops, macsmc_hwmon_suspend, NULL); + static struct platform_driver macsmc_hwmon_driver = { .probe = macsmc_hwmon_probe, + .shutdown = macsmc_hwmon_shutdown, .driver = { .name = "macsmc-hwmon", .of_match_table = macsmc_hwmon_of_table, + .pm = pm_sleep_ptr(&macsmc_hwmon_pm_ops), }, }; module_platform_driver(macsmc_hwmon_driver); diff --git a/drivers/irqchip/irq-apple-aic.c b/drivers/irqchip/irq-apple-aic.c index 3214501a10635..1125008130ef4 100644 --- a/drivers/irqchip/irq-apple-aic.c +++ b/drivers/irqchip/irq-apple-aic.c @@ -203,6 +203,17 @@ #define SYS_IMP_APL_UPMSR_EL1 sys_reg(3, 7, 15, 6, 4) #define UPMSR_IACT BIT(0) +/* + * System interrupt configuration. AICv2 and later have no per-IRQ CPU target: + * the controller delivers each IRQ to one of the CPUs that accept IRQs, and + * this per-CPU field says whether a CPU does. A CPU whose field reads + * SIQ_CFG_ROUTE_NONE never receives an AIC IRQ. Only used on SoCs where this + * has been verified on hardware (aic_info.siq_cfg_routing). + */ +#define SYS_IMP_APL_SIQ_CFG_EL1 sys_reg(3, 4, 15, 10, 4) +#define SIQ_CFG_ROUTE GENMASK(1, 0) +#define SIQ_CFG_ROUTE_NONE 3 + /* MPIDR fields */ #define MPIDR_CPU(x) MPIDR_AFFINITY_LEVEL(x, 0) #define MPIDR_CLUSTER(x) MPIDR_AFFINITY_LEVEL(x, 1) @@ -261,6 +272,15 @@ struct aic_info { bool fast_ipi; bool local_fast_ipi; bool vm_timer_fiq_readonly; + /* + * SIQ_CFG routing verified on hardware: at EL2, AICv2+ IRQs report the + * CPUs that accept them as their effective affinity. The set grows as + * CPUs start and IRQs keep the set they were given, which is enough + * while only one CPU accepts IRQs. A SoC listed here with more than one + * accepting CPU would also need those masks refreshed when such a CPU + * comes online. + */ + bool siq_cfg_routing; }; static const struct aic_info aic1_info __initconst = { @@ -307,6 +327,14 @@ static const struct aic_info aic3_info __initconst = { .local_fast_ipi = true, }; +static const struct aic_info aic3_t8140_info __initconst = { + .version = 3, + .irq_cfg = AIC3_IRQ_CFG, + .fast_ipi = true, + .local_fast_ipi = true, + .siq_cfg_routing = true, +}; + static const struct aic_info aic3_t8142_info __initconst = { .version = 3, .irq_cfg = AIC3_IRQ_CFG, @@ -316,6 +344,10 @@ static const struct aic_info aic3_t8142_info __initconst = { }; static const struct of_device_id aic_info_match[] = { + { + .compatible = "apple,t8140-aic3", + .data = &aic3_t8140_info, + }, { .compatible = "apple,t8142-aic3", .data = &aic3_t8142_info, @@ -363,6 +395,9 @@ static DEFINE_PER_CPU(uint32_t, aic_fiq_unmasked); static struct aic_irq_chip *aic_irqc; +/* CPUs that accept hardware IRQs, read from SIQ_CFG where siq_cfg_routing is set */ +static struct cpumask aic_irq_cpus; + static void aic_handle_ipi(struct pt_regs *regs); static u32 aic_ic_read(struct aic_irq_chip *ic, u32 reg) @@ -510,6 +545,32 @@ static struct irq_chip aic2_chip = { .flags = IRQCHIP_SKIP_SET_WAKE, }; +/* + * AICv2 and later cannot be steered: the controller delivers each IRQ to one of + * the CPUs that accept IRQs, whatever the requested mask. Where SIQ_CFG says + * which CPUs those are, report them, so that threaded handlers run where the + * hard IRQ is taken. The requested mask is accepted and kept only for + * reporting. + */ +static int aic2_routed_irq_set_affinity(struct irq_data *d, + const struct cpumask *mask_val, bool force) +{ + irq_data_update_effective_affinity(d, cpumask_empty(&aic_irq_cpus) ? + cpu_possible_mask : &aic_irq_cpus); + return IRQ_SET_MASK_OK; +} + +/* AIC2 on SoCs whose SIQ_CFG routing is read (aic_info.siq_cfg_routing, at EL2) */ +static struct irq_chip aic2_routed_chip = { + .name = "AIC2", + .irq_mask = aic_irq_mask, + .irq_unmask = aic_irq_unmask, + .irq_eoi = aic_irq_eoi, + .irq_set_affinity = aic2_routed_irq_set_affinity, + .irq_set_type = aic_irq_set_type, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + /* * FIQ irqchip */ @@ -684,7 +745,8 @@ static int aic_irq_domain_map(struct irq_domain *id, unsigned int irq, struct irq_chip *chip = &aic_chip; if (ic->info.version == 2 || ic->info.version == 3) - chip = &aic2_chip; + chip = ic->info.siq_cfg_routing && is_kernel_in_hyp_mode() ? + &aic2_routed_chip : &aic2_chip; if (type == AIC_EVENT_TYPE_IRQ) { irq_domain_set_info(id, irq, hw, chip, id->host_data, @@ -958,6 +1020,19 @@ static int aic_init_cpu(unsigned int cpu) } } + /* + * Record whether the controller delivers IRQs to this CPU. SIQ_CFG is + * read only on SoCs where its routing has been verified on hardware, + * and only at EL2, since a hypervisor or secure monitor may trap it. + */ + if (aic_irqc->info.siq_cfg_routing && is_kernel_in_hyp_mode()) { + if (FIELD_GET(SIQ_CFG_ROUTE, read_sysreg_s(SYS_IMP_APL_SIQ_CFG_EL1)) == + SIQ_CFG_ROUTE_NONE) + cpumask_clear_cpu(cpu, &aic_irq_cpus); + else + cpumask_set_cpu(cpu, &aic_irq_cpus); + } + /* Initialize the local mask state */ __this_cpu_write(aic_fiq_unmasked, 0); diff --git a/drivers/media/platform/apple/isp/Kconfig b/drivers/media/platform/apple/isp/Kconfig index b51d19ee115de..7a797a1a7e1a3 100644 --- a/drivers/media/platform/apple/isp/Kconfig +++ b/drivers/media/platform/apple/isp/Kconfig @@ -24,8 +24,9 @@ config VIDEO_APPLE_ISP_KUNIT_TEST depends on KUNIT=y || VIDEO_APPLE_ISP=m default KUNIT_ALL_TESTS help - Builds unit tests for the videobuf2 queue set-up and for the - shared memory the firmware frees into the Apple ISP driver. They + Builds unit tests for queue setup, P010 layouts and buffer identity, + firmware sensor geometry, the capture command stream and shared + memory retirement. The command tests stub the IPC transport and need no hardware. Only useful for kernel developers running KUnit. If unsure, say N. diff --git a/drivers/media/platform/apple/isp/isp-cam-test.c b/drivers/media/platform/apple/isp/isp-cam-test.c new file mode 100644 index 0000000000000..27c9e23f348aa --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-cam-test.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Synthetic geometry checks: no camera firmware or setfile data. */ +#include +#include +#include +#include "isp-ipc.h" + +static void isp_test_sensor_geometry(struct kunit *test) +{ + struct cmd_ch_camera_config config = { + .in_width = 1920, .in_height = 2160, + .out_width = 1920, .out_height = 1080, + .in_width2 = 1920, .in_height2 = 2160, + .out_width2 = 1920, .out_height2 = 1080, + }; + + KUNIT_EXPECT_TRUE(test, isp_sensor_config_usable(&config)); + /* A future larger output is accepted only when both descriptions agree. */ + config.in_width = 4096; + config.in_width2 = 4096; + config.in_height = 3072; + config.in_height2 = 3072; + config.out_width = 4096; + config.out_width2 = 4096; + config.out_height = 3072; + config.out_height2 = 3072; + KUNIT_EXPECT_TRUE(test, isp_sensor_config_usable(&config)); + config.out_height2 = 2160; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); + config.out_height2 = 3072; + config.in_height2 = 2160; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); + config.in_height2 = 3072; + config.out_width = 4097; + config.out_width2 = 4097; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); + config.out_width = 0; + config.out_width2 = 0; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); + config.out_width = 8192; + config.out_width2 = 8192; + KUNIT_EXPECT_FALSE(test, isp_sensor_config_usable(&config)); +} + +struct isp_test_command { + u32 words[39]; + u32 size; +}; + +/* Derived from the proxy command fixture, with contiguous V4L2 plane sizes. */ +static const struct isp_test_command isp_test_preview_commands[] = { + {{0, 0x24, 0}, 0xc}, + {{0, 0x13b, 0, 1}, 0x10}, + {{0, 0x107, 0, 5}, 0x10}, + {{0, 0x10e, 0, 1}, 0x10}, + {{0, 0x10f, 0}, 0xc}, + {{0, 0x80c, 0, 0, 0, 1280, 720}, 0x1c}, + {{0, 0xb09, 0, 1280, 720, 1, 0x12, 2560, 2560, 0, 0, 720, 0, 1280}, 0x38}, + {{0, 0x208, 0, 7680}, 0x10}, + {{0, 0x20a, 0, 3840}, 0x10}, + {{[1] = 0x117, [3] = 8 << 16, [4] = 0x8000, [5] = 0x8000, [37] = 1}, 0x9c}, + {{[1] = 0x117, [3] = 9 | 2 << 16, [4] = 1843200, [5] = 2560, + [12] = 921600, [13] = 2560, [37] = 2}, 0x9c}, + {{0, 0x125, 0, 1}, 0x10}, + {{0, 0xb0d, 0, 1}, 0x10}, + {{0, 0x138, 0, 0}, 0x10}, +}; + +struct isp_test_command_trace { + unsigned int count; + unsigned int fail_at; +}; + +static int isp_test_send(struct apple_isp *isp, void *args, u32 insize, + u32 outsize, int timeout) +{ + struct kunit *test = kunit_get_current_test(); + struct isp_test_command_trace *trace = test->priv; + const struct isp_test_command *expected; + + if (trace->count >= ARRAY_SIZE(isp_test_preview_commands)) { + KUNIT_FAIL(test, "unexpected extra command"); + return -EIO; + } + expected = &isp_test_preview_commands[trace->count++]; + KUNIT_EXPECT_EQ(test, insize, expected->size); + KUNIT_EXPECT_EQ(test, outsize, expected->words[1] == 0x117 ? insize : 0); + if (insize == expected->size) + KUNIT_EXPECT_MEMEQ(test, args, expected->words, insize); + if (trace->count == trace->fail_at) + return -ETIME; + return 0; +} + +static void isp_test_free_controls(void *ptr) +{ + v4l2_ctrl_handler_free(ptr); +} + +static struct apple_isp *isp_test_capture_alloc(struct kunit *test) +{ + static const struct apple_isp_hw hw = { + .gen = ISP_GEN_T6040, .fw_abi = ISP_FW_ABI_H17, + .scl1 = true, .meta_size = ISP_META_SIZE_T8140, + }; + static struct isp_preset preset = { + .index = 5, .input_dim = {1920, 2160}, + .output_dim = {1280, 720}, .crop_size = {1280, 720}, + }; + struct apple_isp *isp = kunit_kzalloc(test, sizeof(*isp), GFP_KERNEL); + struct isp_format *fmt; + int err; + + KUNIT_ASSERT_NOT_NULL(test, isp); + isp->dev = kunit_device_register(test, "apple-isp-camera-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, isp->dev); + isp->hw = &hw; + isp->frame_rate = 30; + fmt = isp_get_current_format(isp); + fmt->version = 0x958; + fmt->pixelformat = V4L2_PIX_FMT_P010; + fmt->preset = &preset; + fmt->strides[0] = 2560; + fmt->strides[1] = 2560; + fmt->plane_size[0] = 1843200; + fmt->plane_size[1] = 921600; + v4l2_ctrl_handler_init(&isp->ctrl_handler, 1); + err = kunit_add_action_or_reset(test, isp_test_free_controls, &isp->ctrl_handler); + KUNIT_ASSERT_EQ(test, err, 0); + isp->exposure_priority = + v4l2_ctrl_new_std(&isp->ctrl_handler, NULL, + V4L2_CID_EXPOSURE_AUTO_PRIORITY, 0, 1, 1, 1); + KUNIT_ASSERT_EQ(test, isp->ctrl_handler.error, 0); + kunit_activate_static_stub(test, isp_cmd_send, isp_test_send); + return isp; +} + +static void isp_test_preview_stream(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_test_command_trace trace = {}; + + test->priv = &trace; + KUNIT_EXPECT_EQ(test, isp_ch_configure_capture(isp, 0), 0); + KUNIT_EXPECT_EQ(test, trace.count, ARRAY_SIZE(isp_test_preview_commands)); +} + +static void isp_test_preview_command_failures(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_test_command_trace trace; + + test->priv = &trace; + for (unsigned int i = 1; i <= ARRAY_SIZE(isp_test_preview_commands); i++) { + trace = (struct isp_test_command_trace){ .fail_at = i }; + KUNIT_EXPECT_EQ(test, isp_ch_configure_capture(isp, 0), -ETIME); + KUNIT_EXPECT_EQ(test, trace.count, i); + } +} + +static int isp_test_config_reply(struct apple_isp *isp, void *args, u32 insize, + u32 outsize, int timeout) +{ + struct kunit *test = kunit_get_current_test(); + u32 *count = test->priv; + struct cmd_ch_camera_config_h17 *reply = isp->cmd_virt; + struct cmd_ch_camera_config *config = &reply->config; + + KUNIT_EXPECT_EQ(test, insize, 0x120); + KUNIT_EXPECT_EQ(test, outsize, 0x120); + memcpy(reply, args, sizeof(*reply)); + KUNIT_EXPECT_EQ(test, config->preset, (*count)++); + config->in_width = 1920; + config->in_height = 2160; + config->out_width = 1920; + config->out_height = 1080; + if (config->preset == 1) { + config->in_width = 4096; + config->in_height = 3072; + config->out_width = 4096; + config->out_height = 3072; + } + config->in_width2 = config->in_width; + config->in_height2 = config->in_height; + config->out_width2 = config->out_width; + config->out_height2 = config->out_height; + if (config->preset == 2) + config->out_height2 = 2160; + reply->unknown[sizeof(reply->unknown) - 1] = 0x5a; + return 0; +} + +static void isp_test_free_configs(void *ptr) +{ + kfree(ptr); +} + +static void isp_test_sensor_config_list(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_preset presets[] = { + { .index = 5, .input_dim = {1920, 2160}, + .output_dim = {1920, 1080}, .crop_size = {1920, 1080} }, + { .index = 5, .input_dim = {1920, 2160}, + .output_dim = {1280, 720}, .crop_size = {1280, 720} }, + }; + u32 count = 0; + int err; + + isp->cmd_virt = kunit_kzalloc(test, 0x120, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + isp->presets = presets; + isp->num_presets = ARRAY_SIZE(presets); + test->priv = &count; + kunit_activate_static_stub(test, isp_cmd_send, isp_test_config_reply); + KUNIT_ASSERT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 6), 0); + err = kunit_add_action_or_reset(test, isp_test_free_configs, isp->sensor_configs); + KUNIT_ASSERT_EQ(test, err, 0); + KUNIT_EXPECT_EQ(test, get_unaligned_le32(isp->sensor_configs[5].reply + 0xc), 5); + KUNIT_EXPECT_EQ(test, isp->sensor_configs[5].reply[ISP_SENSOR_CONFIG_SIZE - 1], 0x5a); + KUNIT_EXPECT_EQ(test, count, 6); + KUNIT_EXPECT_EQ(test, isp->num_sensor_configs, 6); + KUNIT_EXPECT_EQ(test, isp->num_presets, 3); + KUNIT_EXPECT_EQ(test, isp->presets[0].index, 5); + KUNIT_EXPECT_EQ(test, isp->presets[0].output_dim.y, 1080); + KUNIT_EXPECT_TRUE(test, isp->presets[1].p010); + KUNIT_EXPECT_TRUE(test, isp->presets[2].p010); + KUNIT_EXPECT_EQ(test, isp->presets[2].index, 1); + KUNIT_EXPECT_EQ(test, isp->presets[2].output_dim.x, 4096); + KUNIT_EXPECT_EQ(test, isp->presets[2].crop_size.y, 3072); + KUNIT_EXPECT_FALSE(test, isp->sensor_configs[2].usable); + KUNIT_EXPECT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 0), -EINVAL); + KUNIT_EXPECT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 65), -EINVAL); + KUNIT_EXPECT_EQ(test, count, 6); +} + +static int isp_test_bad_config_reply(struct apple_isp *isp, void *args, u32 insize, + u32 outsize, int timeout) +{ + struct cmd_ch_camera_config_h17 *reply = isp->cmd_virt; + + isp_test_config_reply(isp, args, insize, outsize, timeout); + reply->config.preset = 65; + return 0; +} + +static void isp_test_sensor_config_failure(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_preset preset = {}; + u32 count = 0; + + isp->cmd_virt = kunit_kzalloc(test, ISP_SENSOR_CONFIG_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + isp->presets = &preset; + isp->num_presets = 1; + test->priv = &count; + kunit_activate_static_stub(test, isp_cmd_send, isp_test_bad_config_reply); + KUNIT_EXPECT_EQ(test, isp_ch_cache_sensor_configs(isp, 0, 6), -EIO); + KUNIT_EXPECT_EQ(test, count, 1); + KUNIT_EXPECT_EQ(test, isp->num_sensor_configs, 0); + KUNIT_EXPECT_PTR_EQ(test, isp->sensor_configs, NULL); + KUNIT_EXPECT_PTR_EQ(test, isp->presets, &preset); +} + +struct isp_test_ack_state { + unsigned int fault, calls; +}; + +static int isp_test_ack_reply(struct apple_isp *isp, struct isp_channel *chan, + unsigned long timeout) +{ + struct kunit *test = kunit_get_current_test(); + struct isp_test_ack_state *state = test->priv; + + state->calls++; + chan->rsp = (struct isp_message){ .arg0 = chan->req.arg0 | ISP_IPC_FLAG_ACK, + .arg1 = chan->req.arg2 }; + switch (state->fault) { + case 1: + chan->rsp.arg2 = 1; + break; + case 2: + put_unaligned_le32(1, isp->cmd_virt); + break; + case 3: + chan->rsp.arg1 += 4; + break; + case 4: + return -ETIME; + } + return 0; +} + +static void isp_test_command_ack_failure_latches(struct kunit *test) +{ + struct apple_isp *isp = isp_test_capture_alloc(test); + struct isp_test_ack_state state = {}; + + isp->cmd_virt = kunit_kzalloc(test, 0x400, GFP_KERNEL); + isp->chan_io = kunit_kzalloc(test, sizeof(*isp->chan_io), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + KUNIT_ASSERT_NOT_NULL(test, isp->chan_io); + isp->chan_io->name = "IO"; + test->priv = &state; + kunit_deactivate_static_stub(test, isp_cmd_send); + kunit_activate_static_stub(test, ipc_chan_send, isp_test_ack_reply); + KUNIT_EXPECT_EQ(test, isp_cmd_ch_camera_config_select(isp, 0, 5), 0); + for (state.fault = 1; state.fault <= 4; state.fault++) { + isp->ipc_failed = false; + state.calls = 0; + KUNIT_EXPECT_EQ(test, isp_cmd_ch_camera_config_select(isp, 0, 5), + state.fault == 4 ? -ETIME : -EIO); + KUNIT_EXPECT_TRUE(test, isp->ipc_failed); + /* A late ACK cannot admit another command on an uncertain slot. */ + KUNIT_EXPECT_EQ(test, isp_cmd_ch_camera_config_select(isp, 0, 5), -EIO); + KUNIT_EXPECT_EQ(test, state.calls, 1); + } +} + +static struct kunit_case isp_cam_test_cases[] = { + KUNIT_CASE(isp_test_command_ack_failure_latches), + KUNIT_CASE(isp_test_sensor_geometry), + KUNIT_CASE(isp_test_sensor_config_list), + KUNIT_CASE(isp_test_sensor_config_failure), + KUNIT_CASE(isp_test_preview_stream), + KUNIT_CASE(isp_test_preview_command_failures), + {} +}; + +static struct kunit_suite isp_cam_test_suite = { + .name = "apple-isp-camera", + .test_cases = isp_cam_test_cases, +}; + +kunit_test_suite(isp_cam_test_suite); diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index a266c60609614..a5cbb2b4d8eb0 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -10,8 +10,6 @@ #include "isp-iommu.h" #include "isp-regs.h" -#define ISP_MAX_PRESETS 32 - struct isp_setfile { u32 version; u32 magic; @@ -217,6 +215,122 @@ static int isp_check_presets(struct apple_isp *isp, u32 num_fw_presets) return 0; } +/* Do not mistake stacked sensor input rows for a processed output mode. */ +static bool isp_sensor_config_usable(const struct cmd_ch_camera_config *config) +{ + return config->in_width && config->in_height && + config->out_width && config->out_height && + config->out_width <= 16384 && config->out_height <= 16384 && + !(config->out_width & 1) && !(config->out_height & 1) && + config->out_width <= config->in_width && + config->out_height <= config->in_height && + config->in_width == config->in_width2 && + config->in_height == config->in_height2 && + config->out_width == config->out_width2 && + config->out_height == config->out_height2; +} + +static int isp_ch_cache_sensor_configs(struct apple_isp *isp, u32 ch, u32 count) +{ + struct cmd_ch_camera_config_h17 args; + struct isp_sensor_config *configs; + struct isp_preset *presets; + unsigned int num = isp->num_presets; + int err; + + /* The extended reply is established only for the IMX958 T6040 path. */ + if (!isp_supports_p010(isp)) + return 0; + if (!count || count > ISP_MAX_SENSOR_CONFIGS) + return -EINVAL; + + presets = devm_kcalloc(isp->dev, num + count, sizeof(*presets), GFP_KERNEL); + if (!presets) + return -ENOMEM; + memcpy(presets, isp->presets, num * sizeof(*presets)); + configs = kcalloc(count, sizeof(*configs), GFP_KERNEL); + if (!configs) + return -ENOMEM; + + for (u32 i = 0; i < count; i++) { + struct cmd_ch_camera_config *config = &args.config; + struct isp_sensor_config *cached = &configs[i]; + bool duplicate = false; + + err = isp_cmd_ch_camera_config_get(isp, ch, i, &args); + if (err) + goto free_configs; + if (config->opcode != (u64)CISP_CMD_CH_CAMERA_CONFIG_GET << 32 || + config->chan != ch || config->preset != i) { + err = -EIO; + goto free_configs; + } + + memcpy(cached->reply, &args, sizeof(args)); + cached->input_dim = (struct coord){config->in_width, config->in_height}; + cached->output_dim = (struct coord){config->out_width, config->out_height}; + cached->sensor_clk = config->sensor_clk; + cached->usable = isp_sensor_config_usable(config); + dev_dbg(isp->dev, "sensor config %u: input %ux%u output %ux%u clock %u%s\n", + i, config->in_width, config->in_height, + config->out_width, config->out_height, config->sensor_clk, + cached->usable ? "" : " (unverified geometry)"); + print_hex_dump_debug("apple-isp config: ", DUMP_PREFIX_OFFSET, + 16, 4, &args, sizeof(args), false); + /* Both processed preview sizes are recorded in the proxy source. */ + for (unsigned int j = 0; j < num; j++) { + struct isp_preset *preset = &presets[j]; + bool preview = i == 5 && config->in_width == 1920 && + config->in_height == 2160 && + ((preset->output_dim.x == 1280 && + preset->output_dim.y == 720) || + (preset->output_dim.x == 1920 && + preset->output_dim.y == 1080)); + bool reported = cached->usable && + preset->output_dim.x == config->out_width && + preset->output_dim.y == config->out_height; + + if (preset->index == i && + preset->input_dim.x == config->in_width && + preset->input_dim.y == config->in_height && + !preset->crop_offset.x && !preset->crop_offset.y && + preset->crop_size.x == preset->output_dim.x && + preset->crop_size.y == preset->output_dim.y) + preset->p010 = preview || reported; + } + if (!cached->usable) + continue; + + /* Keep the board's config-5 presets, including the default, first. */ + for (unsigned int j = 0; j < num; j++) { + if (presets[j].output_dim.x == config->out_width && + presets[j].output_dim.y == config->out_height) { + duplicate = true; + break; + } + } + if (duplicate) + continue; + + presets[num++] = (struct isp_preset){ + .index = i, + .input_dim = cached->input_dim, + .output_dim = cached->output_dim, + .crop_size = cached->output_dim, + .p010 = true, + }; + } + isp->sensor_configs = configs; + isp->num_sensor_configs = count; + isp->presets = presets; + isp->num_presets = num; + return 0; + +free_configs: + kfree(configs); + return err; +} + static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) { struct isp_format *fmt = isp_get_format(isp, ch); @@ -264,6 +378,8 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) } err = isp_check_presets(isp, args->num_presets); + if (!err) + err = isp_ch_cache_sensor_configs(isp, ch, args->num_presets); exit: kfree(args); @@ -475,7 +591,7 @@ static int isp_ch_configure_pools(struct apple_isp *isp, u32 ch) return 0; return isp_cmd_ch_buffer_pool_config_set_rendered(isp, ch, - ISP_MAX_BUFFERS, + isp_num_rendered(isp), fmt->plane_size[0], fmt->strides[0], fmt->plane_size[1], @@ -488,7 +604,13 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) bool h17 = isp->hw->fw_abi == ISP_FW_ABI_H17; int err; - isp_cmd_flicker_sensor_set(isp, 0); + err = isp_cmd_flicker_sensor_set(isp, 0); + if (isp_is_p010(isp) && err) + return err; + + /* The proxy-qualified P010 route uses the firmware defaults. */ + if (isp_is_p010(isp)) + goto select_config; /* * The setfile isn't requisite but then we don't get calibration. If @@ -504,6 +626,7 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) return err; } +select_config: err = isp_cmd_ch_sbs_enable(isp, ch, 1); if (err) return err; @@ -531,7 +654,8 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) err = isp_cmd_ch_output_config_set(isp, ch, fmt->preset->output_dim.x, fmt->preset->output_dim.y, fmt->strides, CISP_COLORSPACE_REC709, - CISP_OUTPUT_FORMAT_YUV_2PLANE); + isp_is_p010(isp) ? CISP_OUTPUT_FORMAT_P010 : + CISP_OUTPUT_FORMAT_YUV_2PLANE); if (err) return err; @@ -559,6 +683,9 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; + if (isp_is_p010(isp)) + return isp_cmd_ch_master_slave_sync_mode_set(isp, ch, 0); + err = isp_cmd_ch_cnr_start(isp, ch); if (err) return err; @@ -633,7 +760,10 @@ int apple_isp_start_camera(struct apple_isp *isp) err = isp_configure_capture(isp); if (err) { dev_err(isp->dev, "failed to configure capture: %d\n", err); - apple_isp_firmware_shutdown(isp); + if (isp->ipc_failed) + apple_isp_firmware_halt(isp); + else + apple_isp_firmware_shutdown(isp); return err; } @@ -642,7 +772,10 @@ int apple_isp_start_camera(struct apple_isp *isp) void apple_isp_stop_camera(struct apple_isp *isp) { - apple_isp_firmware_shutdown(isp); + if (isp->ipc_failed) + apple_isp_firmware_halt(isp); + else + apple_isp_firmware_shutdown(isp); } /* @@ -714,9 +847,15 @@ int apple_isp_start_capture(struct apple_isp *isp) void apple_isp_stop_capture(struct apple_isp *isp) { + if (isp->ipc_failed || isp->fw_state == ISP_FW_DEAD) + return; isp_cmd_ch_stop(isp, 0); // TODO channel mask isp_cmd_ch_buffer_return(isp, isp->current_ch); /* ... until its buffers are back. */ apple_isp_wdt_stop(isp); } + +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) +#include "isp-cam-test.c" +#endif diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index fd7dcfd52899a..a525e68c230f5 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -5,30 +5,54 @@ #include "isp-drv.h" #include "isp-iommu.h" #include "isp-ipc.h" +#include + +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) +#include +#endif #define CISP_OPCODE_SHIFT 32UL #define CISP_OPCODE(x) (((u64)(x)) << CISP_OPCODE_SHIFT) #define CISP_OPCODE_GET(x) (((u64)(x)) >> CISP_OPCODE_SHIFT) #define CISP_TIMEOUT msecs_to_jiffies(3000) -#define CISP_SEND_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0, CISP_TIMEOUT)) -#define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), CISP_TIMEOUT)) +#define CISP_SEND_IN(x, a) (isp_cmd_send((x), &(a), sizeof(a), 0, CISP_TIMEOUT)) +#define CISP_SEND_INOUT(x, a) (isp_cmd_send((x), &(a), sizeof(a), sizeof(a), CISP_TIMEOUT)) #define CISP_SEND_OUT(x, a) (cisp_send_read((x), (a), sizeof(*a), sizeof(*a))) -#define CISP_POST_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0, 0)) -#define CISP_POST_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), 0)) +#define CISP_POST_IN(x, a) (isp_cmd_send((x), &(a), sizeof(a), 0, 0)) +#define CISP_POST_INOUT(x, a) (isp_cmd_send((x), &(a), sizeof(a), sizeof(a), 0)) -static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int timeout) +int isp_cmd_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int timeout) { - struct isp_channel *chan = isp->chan_io; - struct isp_message *req = &chan->req; + struct isp_channel *chan; + struct isp_message *req; int err; +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) + KUNIT_STATIC_STUB_REDIRECT(isp_cmd_send, isp, args, insize, outsize, timeout); +#endif + if (isp->ipc_failed) + return -EIO; + chan = isp->chan_io; + req = &chan->req; + req->arg0 = isp->cmd_iova; req->arg1 = insize; req->arg2 = outsize; memcpy(isp->cmd_virt, args, insize); err = ipc_chan_send(isp, chan, timeout); + if (isp_is_p010(isp)) { + struct isp_message *rsp = &chan->rsp; + + if (!err && timeout && + (rsp->arg1 != outsize || rsp->arg2 || rsp->arg3 || + rsp->arg4 || rsp->arg5 || rsp->arg6 || rsp->arg7 || + get_unaligned_le64(isp->cmd_virt) != get_unaligned_le64(args))) + err = -EIO; + if (err) + isp->ipc_failed = true; + } if (err) { u64 opcode; memcpy(&opcode, args, sizeof(opcode)); @@ -45,7 +69,7 @@ static int cisp_send_read(struct apple_isp *isp, void *args, u32 insize, u32 outsize) { /* TODO do I need to lock the iova space? */ - int err = cisp_send(isp, args, insize, outsize, CISP_TIMEOUT); + int err = isp_cmd_send(isp, args, insize, outsize, CISP_TIMEOUT); if (err) return err; @@ -165,7 +189,7 @@ int isp_cmd_set_dsid_clr_multi_bc(struct apple_isp *isp, const u64 *bases, return -EINVAL; memcpy(args.dsid_clr_base, bases, count * sizeof(*bases)); - return cisp_send(isp, &args, + return isp_cmd_send(isp, &args, offsetof(typeof(args), dsid_clr_base) + count * sizeof(*bases), 0, CISP_TIMEOUT); @@ -232,6 +256,8 @@ int isp_cmd_flicker_sensor_set(struct apple_isp *isp, u32 mode) .opcode = CISP_OPCODE(CISP_CMD_FLICKER_SENSOR_SET), .mode = mode, }; + if (isp_is_p010(isp)) + return CISP_SEND_IN(isp, args); return CISP_SEND_INOUT(isp, args); } @@ -260,6 +286,16 @@ int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan, return err; } +int isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 index, + struct cmd_ch_camera_config_h17 *args) +{ + memset(args, 0, sizeof(*args)); + args->config.opcode = CISP_OPCODE(CISP_CMD_CH_CAMERA_CONFIG_GET); + args->config.chan = chan; + args->config.preset = index; + return CISP_SEND_OUT(isp, args); +} + int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan, struct cmd_ch_camera_config *args) { @@ -468,9 +504,9 @@ int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type) .opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_POOL_CONFIG_SET), .chan = chan, .type = type, - .count = ISP_MAX_BUFFERS, - .meta_size0 = size, - .meta_size1 = size, + .count = isp_num_meta(isp), + .meta_size0 = isp_is_p010(isp) ? 0x8000 : size, + .meta_size1 = isp_is_p010(isp) ? 0x8000 : size, .unk0 = 0, .unk1 = 0, .unk2 = 0, diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h index 695c0a9f7ab70..a3167108b128e 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.h +++ b/drivers/media/platform/apple/isp/isp-cmd.h @@ -124,6 +124,7 @@ #define CISP_COLORSPACE_REC709 0x1 #define CISP_OUTPUT_FORMAT_YUV_2PLANE 0x0 #define CISP_OUTPUT_FORMAT_YUV_1PLANE 0x1 +#define CISP_OUTPUT_FORMAT_P010 0x12 #define CISP_OUTPUT_FORMAT_RGB 0x2 #define CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY 0x1 @@ -255,6 +256,8 @@ struct cmd_flicker_sensor_set { } __packed; static_assert(sizeof(struct cmd_flicker_sensor_set) == 0xc); +int isp_cmd_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, + int timeout); int isp_cmd_start(struct apple_isp *isp, u32 mode); int isp_cmd_stop(struct apple_isp *isp, u32 mode); int isp_cmd_power_down(struct apple_isp *isp); @@ -380,6 +383,15 @@ struct cmd_ch_camera_config { } __packed; static_assert(sizeof(struct cmd_ch_camera_config) == 0xdc); +/* H17 appends 0x44 bytes to the common camera configuration record. */ +struct cmd_ch_camera_config_h17 { + struct cmd_ch_camera_config config; + u8 unknown[0x44]; +} __packed; +static_assert(sizeof(struct cmd_ch_camera_config_h17) == 0x120); +static_assert(offsetof(struct cmd_ch_camera_config, in_width) == 0x10); +static_assert(offsetof(struct cmd_ch_camera_config, out_width2) == 0xd4); + struct cmd_ch_camera_config_select { u64 opcode; u32 chan; @@ -516,6 +528,8 @@ int isp_cmd_ch_start(struct apple_isp *isp, u32 chan); int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan); int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan, struct cmd_ch_info *args); +int isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 index, + struct cmd_ch_camera_config_h17 *args); int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan, struct cmd_ch_camera_config *args); int isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan, diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index da9d459dd8bdb..084cca4dc3707 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -538,6 +538,7 @@ static int apple_isp_probe(struct platform_device *pdev) mutex_init(&isp->gc_lock); INIT_LIST_HEAD(&isp->bufs_pending); INIT_LIST_HEAD(&isp->bufs_submitted); + INIT_LIST_HEAD(&isp->bufs_returned); isp->wq = alloc_workqueue("apple-isp-wq", WQ_UNBOUND, 0); if (!isp->wq) { dev_err(dev, "failed to create workqueue\n"); @@ -585,6 +586,7 @@ static int apple_isp_probe(struct platform_device *pdev) detach_genpd: apple_isp_detach_genpd(isp); free_isp: + kfree(isp->sensor_configs); /* Nothing else can reach it before the video device is registered. */ kfree(isp); return err; diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index ed3e48eef569e..dbe6b6ed5a2c4 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -30,6 +30,8 @@ /* used to limit the user space buffers to the buffer_pool_config */ #define ISP_MAX_BUFFERS 16 +#define ISP_MAX_SENSOR_CONFIGS 64 +#define ISP_SENSOR_CONFIG_SIZE 0x120 /* capture metadata buffers, for firmware that has that pool */ #define ISP_CAPMETA_BUFFERS 8 @@ -116,6 +118,8 @@ struct isp_channel { void *virt; u32 doorbell; u32 cursor; + unsigned long deadline; + bool deadline_active; struct mutex lock; struct isp_message req; struct isp_message rsp; @@ -139,6 +143,8 @@ struct isp_preset { struct coord output_dim; struct coord crop_offset; struct coord crop_size; + /* Processed P010 geometry established by preview or a config reply. */ + bool p010; }; struct apple_isp_hw { @@ -255,9 +261,18 @@ enum isp_sensor_id { ISP_VD56G0_6222_01, }; +struct isp_sensor_config { + struct coord input_dim; + struct coord output_dim; + u32 sensor_clk; + bool usable; + u8 reply[ISP_SENSOR_CONFIG_SIZE]; +}; + struct isp_format { enum isp_sensor_id id; u32 version; + u32 pixelformat; struct isp_preset *preset; unsigned int num_planes; u32 strides[VB2_MAX_PLANES]; @@ -274,6 +289,9 @@ struct apple_isp { u32 cam_connections_scheme; struct isp_preset *presets; int num_presets; + /* Read-only sensor configuration replies, cached before video registration. */ + unsigned int num_sensor_configs; + struct isp_sensor_config *sensor_configs; int num_channels; struct isp_format fmts[ISP_MAX_CHANNELS]; @@ -287,7 +305,6 @@ struct apple_isp { struct vb2_queue vbq; struct mutex video_lock; unsigned int sequence; - bool multiplanar; /* capture rate in frames per second, changes under video_lock */ unsigned int frame_rate; @@ -358,16 +375,25 @@ struct apple_isp { spinlock_t buf_lock; struct list_head bufs_pending; struct list_head bufs_submitted; + struct list_head bufs_returned; + u64 buffer_tag; + unsigned long meta_returned; + /* A timed-out command leaves slot ownership ambiguous until reboot. */ + bool ipc_failed; }; struct isp_chan_ops { int (*handle)(struct apple_isp *isp, struct isp_channel *chan); + /* Called after the response slot and its doorbell are published. */ + void (*complete)(struct apple_isp *isp); }; struct isp_buffer { struct vb2_v4l2_buffer vb; struct list_head link; struct isp_surf surfs[VB2_MAX_PLANES]; + u64 tag; + enum vb2_buffer_state return_state; }; #define to_isp_buffer(x) container_of((x), struct isp_buffer, vb) @@ -395,4 +421,36 @@ enum { ((isp)->hw->capture_meta_size ? ISP_CAPMETA_BUFFERS : 0) #define isp_get_current_format(isp) (isp_get_format(isp, isp->current_ch)) +static inline bool isp_supports_p010(struct apple_isp *isp) +{ + return isp->hw->gen == ISP_GEN_T6040 && + isp_get_current_format(isp)->version == 0x958; +} + +static inline bool isp_has_p010(struct apple_isp *isp) +{ + if (!isp_supports_p010(isp)) + return false; + for (int i = 0; i < isp->num_presets; i++) { + if (isp->presets[i].p010) + return true; + } + return false; +} + +static inline bool isp_is_p010(struct apple_isp *isp) +{ + return isp_get_current_format(isp)->pixelformat == V4L2_PIX_FMT_P010; +} + +static inline unsigned int isp_num_rendered(struct apple_isp *isp) +{ + return isp_is_p010(isp) ? 2 : ISP_MAX_BUFFERS; +} + +static inline unsigned int isp_num_meta(struct apple_isp *isp) +{ + return isp_is_p010(isp) ? 8 : ISP_MAX_BUFFERS; +} + #endif /* __ISP_DRV_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 30ff704ef193f..d448225fceec5 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -212,6 +212,7 @@ static const struct isp_chan_ops sm_ops = { static const struct isp_chan_ops bt_ops = { .handle = ipc_bt_handle, + .complete = ipc_bt_complete, }; static irqreturn_t apple_isp_isr(int irq, void *dev) diff --git a/drivers/media/platform/apple/isp/isp-ipc-test.c b/drivers/media/platform/apple/isp/isp-ipc-test.c index 6521cecb4cfb6..caa9e92f735f1 100644 --- a/drivers/media/platform/apple/isp/isp-ipc-test.c +++ b/drivers/media/platform/apple/isp/isp-ipc-test.c @@ -144,7 +144,69 @@ static void isp_test_sm_free_log_bt(struct kunit *test) KUNIT_EXPECT_NULL(test, t->isp->bt_surf); } +static void isp_test_command_ring_wrap(struct kunit *test) +{ + struct isp_sm_test *t = isp_sm_test_alloc(test); + struct isp_channel *chan = t->chan; + struct isp_message *slots; + + chan->num = 8; + chan->virt = kunit_kcalloc(test, chan->num, sizeof(*slots), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, chan->virt); + slots = chan->virt; + chan->req.arg0 = 0x5000000; + isp_get_current_format(t->isp)->pixelformat = V4L2_PIX_FMT_P010; + for (unsigned int i = 0; i < 100000; i++) { + unsigned int cursor = chan->cursor; + + /* Re-publication must clear the old ACK before waiting on a wrap. */ + slots[cursor].arg0 = chan->req.arg0; + KUNIT_ASSERT_FALSE(test, chan_tx_done(t->isp, chan)); + KUNIT_ASSERT_EQ(test, chan->cursor, cursor); + slots[cursor].arg0 |= ISP_IPC_FLAG_ACK; + KUNIT_ASSERT_TRUE(test, chan_tx_done(t->isp, chan)); + KUNIT_ASSERT_EQ(test, chan->cursor, (cursor + 1) % chan->num); + } + KUNIT_EXPECT_EQ(test, chan->cursor, 0); +} + +static void isp_test_late_ack_keeps_cursor(struct kunit *test) +{ + struct isp_sm_test *t = isp_sm_test_alloc(test); + struct isp_message response = { .arg0 = 0x5000001 }; + + isp_get_current_format(t->isp)->pixelformat = V4L2_PIX_FMT_P010; + t->chan->virt = &response; + t->chan->num = 8; + t->chan->req.arg0 = 0x5000000; + t->chan->deadline_active = true; + t->chan->deadline = jiffies; + KUNIT_EXPECT_FALSE(test, chan_tx_done(t->isp, t->chan)); + KUNIT_EXPECT_EQ(test, t->chan->cursor, 0); + t->chan->deadline = jiffies + HZ; + KUNIT_EXPECT_TRUE(test, chan_tx_done(t->isp, t->chan)); + KUNIT_EXPECT_EQ(test, t->chan->cursor, 1); +} + +static void isp_test_message_payload_copy(struct kunit *test) +{ + struct isp_sm_test *t = isp_sm_test_alloc(test); + struct isp_message response = { + .arg0 = 0x5000001, .arg1 = 1, .arg2 = 2, .arg3 = 3, + .arg4 = 4, .arg5 = 5, .arg6 = 6, .arg7 = 7, + }; + struct isp_message copied = {}; + + isp_get_current_format(t->isp)->pixelformat = V4L2_PIX_FMT_P010; + t->chan->virt = &response; + chan_read_msg_index(t->isp, t->chan, &copied, 0); + KUNIT_EXPECT_MEMEQ(test, &copied, &response, sizeof(response)); +} + static struct kunit_case isp_ipc_test_cases[] = { + KUNIT_CASE(isp_test_message_payload_copy), + KUNIT_CASE(isp_test_late_ack_keeps_cursor), + KUNIT_CASE(isp_test_command_ring_wrap), KUNIT_CASE(isp_test_sm_free_releases), KUNIT_CASE(isp_test_sm_free_alias), KUNIT_CASE(isp_test_sm_free_unknown), diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index f709f6e305763..74cc717eadc07 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -6,6 +6,10 @@ #include "isp-regs.h" #include "isp-fw.h" +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) +#include +#endif + #define ISP_IPC_FLAG_TERMINAL_ACK 0x3 /* longer firmware log lines are cut */ #define ISP_IPC_TERMINAL_MAX_LEN 512 @@ -66,7 +70,17 @@ static inline void chan_read_msg_index(struct apple_isp *isp, struct isp_channel *chan, struct isp_message *msg, u32 index) { - memcpy(msg, chan_msg_virt(chan, index), sizeof(*msg)); + if (isp_is_p010(isp)) { + u64 *slot = chan_msg_virt(chan, index); + u64 arg0 = READ_ONCE(*slot); + + /* Acquire validity before consuming the firmware-owned payload. */ + dma_rmb(); + memcpy(msg, slot, sizeof(*msg)); + msg->arg0 = arg0; + } else { + memcpy(msg, chan_msg_virt(chan, index), sizeof(*msg)); + } } static inline void chan_read_msg(struct apple_isp *isp, @@ -124,6 +138,8 @@ static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan) isp_mbox2_write32(isp, ISP_MBOX2_IRQ_DOORBELL, chan->doorbell); chan_update_cursor(chan); + if (chan->ops->complete) + chan->ops->complete(isp); return 0; } @@ -139,10 +155,12 @@ static inline bool chan_rx_done(struct apple_isp *isp, struct isp_channel *chan) int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) { + unsigned int budget = isp_is_p010(isp) ? chan->num : UINT_MAX; int err = 0; mutex_lock(&chan->lock); - while (1) { + /* A busy producer must not starve the other completion/control rings. */ + while (budget--) { chan_read_msg(isp, chan, &chan->req); if (chan_rx_done(isp, chan)) { err = 0; @@ -160,6 +178,9 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) static inline bool chan_tx_done(struct apple_isp *isp, struct isp_channel *chan) { + if (isp_is_p010(isp) && chan->deadline_active && + time_after_eq(jiffies, chan->deadline)) + return false; dma_rmb(); chan_read_msg(isp, chan, &chan->rsp); @@ -176,6 +197,15 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, { long t; +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) + KUNIT_STATIC_STUB_REDIRECT(ipc_chan_send, isp, chan, timeout); +#endif + if (isp->ipc_failed) + return -EIO; + if (isp_is_p010(isp) && timeout) { + chan->deadline = jiffies + timeout; + chan->deadline_active = true; + } chan_write_msg(isp, chan, &chan->req); dma_wmb(); @@ -186,6 +216,8 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, t = wait_event_timeout(isp->wait, chan_tx_done(isp, chan), timeout); if (t == 0) { + if (isp_is_p010(isp)) + isp->ipc_failed = true; dev_err(isp->dev, "%s: timed out on request [0x%llx, 0x%llx, 0x%llx]\n", chan->name, chan->req.arg0, chan->req.arg1, @@ -193,6 +225,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, return -ETIME; } + chan->deadline_active = false; isp_dbg(isp, "%s: request success (%ld)\n", chan->name, t); return 0; diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 895a6d1299c22..398e8e92356ab 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -7,6 +7,7 @@ #include #include +#include #include #define ISP_TEST_FRAME_SIZE 0x1000 @@ -19,6 +20,8 @@ static struct apple_isp *isp_test_alloc(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, isp); mutex_init(&isp->video_lock); + spin_lock_init(&isp->buf_lock); + INIT_LIST_HEAD(&isp->bufs_returned); isp_get_current_format(isp)->total_size = ISP_TEST_FRAME_SIZE; return isp; @@ -34,6 +37,8 @@ static void isp_test_queue_init(struct kunit *test) KUNIT_ASSERT_EQ(test, isp_vb2_queue_init(isp), 0); KUNIT_EXPECT_GE(test, isp->vbq.max_num_buffers, VB2_MAX_FRAME); + KUNIT_EXPECT_EQ(test, isp->vbq.type, V4L2_BUF_TYPE_VIDEO_CAPTURE); + KUNIT_EXPECT_FALSE(test, isp->vbq.is_multiplanar); vb2_queue_release(&isp->vbq); } @@ -99,6 +104,12 @@ static void isp_test_create_bufs_limit(struct kunit *test) KUNIT_EXPECT_EQ(test, isp_test_setup(test, ISP_MAX_BUFFERS, &nbuffers, &num_planes, sizes), -ENOBUFS); + /* The contiguous API must refuse a second independent memory plane. */ + nbuffers = 1; + num_planes = 2; + KUNIT_EXPECT_EQ(test, isp_test_setup(test, 0, &nbuffers, &num_planes, + sizes), -EINVAL); + /* A plane smaller than a frame is still refused. */ nbuffers = 1; num_planes = 1; @@ -107,7 +118,243 @@ static void isp_test_create_bufs_limit(struct kunit *test) sizes), -EINVAL); } +static const struct apple_isp_hw isp_test_t6040 = { + .gen = ISP_GEN_T6040, + .fw_abi = ISP_FW_ABI_H17, +}; + +static void isp_test_p010_format(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset preset = { .output_dim = { 1920, 1080 }, .p010 = true }; + struct v4l2_format f = {}; + unsigned int sizes[VB2_MAX_PLANES] = {}; + unsigned int nbuffers = 16, num_planes = 0; + + isp->hw = &isp_test_t6040; + fmt->version = 0x958; + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + isp_get_sp_pix_format(isp, &f, fmt); + KUNIT_EXPECT_EQ(test, f.fmt.pix.pixelformat, V4L2_PIX_FMT_P010); + KUNIT_EXPECT_EQ(test, f.fmt.pix.bytesperline, 3840); + KUNIT_EXPECT_EQ(test, f.fmt.pix.sizeimage, 6220800); + KUNIT_EXPECT_EQ(test, fmt->plane_size[0], (size_t)4147200); + KUNIT_EXPECT_EQ(test, fmt->plane_size[1], (size_t)2073600); + KUNIT_EXPECT_EQ(test, f.fmt.pix.quantization, V4L2_QUANTIZATION_LIM_RANGE); + + isp->vbq.drv_priv = isp; + isp_test_set_num_buffers(test, &isp->vbq, 0); + KUNIT_ASSERT_EQ(test, + isp_vb2_queue_setup(&isp->vbq, &nbuffers, &num_planes, sizes, NULL), 0); + KUNIT_EXPECT_EQ(test, nbuffers, 2); + KUNIT_EXPECT_EQ(test, num_planes, 1); + KUNIT_EXPECT_EQ(test, sizes[0], 6220800); + KUNIT_EXPECT_EQ(test, isp_num_meta(isp), 8); + + preset.output_dim = (struct coord){1280, 720}; + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + KUNIT_EXPECT_EQ(test, fmt->strides[0], 2560); + KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)2764800); + /* NV12 remains the default and restores the existing pool capacity. */ + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_NV12), 0); + KUNIT_EXPECT_EQ(test, fmt->pixelformat, V4L2_PIX_FMT_NV12); + KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)1382400); + KUNIT_EXPECT_EQ(test, isp_num_rendered(isp), ISP_MAX_BUFFERS); + KUNIT_EXPECT_EQ(test, isp_num_meta(isp), ISP_MAX_BUFFERS); + +} + +static void isp_test_p010_admission(struct kunit *test) +{ + static const struct apple_isp_hw neo = { .gen = ISP_GEN_T8140 }; + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset preset = { .output_dim = {1280, 720}, .p010 = true }; + struct v4l2_fmtdesc desc = { .index = 0 }; + + isp->presets = &preset; + isp->num_presets = 1; + isp->hw = &isp_test_t6040; + fmt->version = 0x958; + KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); + KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_NV12); + desc.index = 1; + KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); + KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_P010); + desc.index = 2; + KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); + desc.index = 1; + isp->hw = &neo; + KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + KUNIT_EXPECT_EQ(test, fmt->pixelformat, V4L2_PIX_FMT_NV12); + KUNIT_EXPECT_EQ(test, fmt->strides[0], 1280); +} + +static void isp_test_p010_completion_identity(struct kunit *test) +{ + struct isp_format fmt = { .plane_size = {4147200, 2073600} }; + struct isp_buffer buf = { .surfs = {{ .iova = 0x10020000000ULL }}, .tag = 42 }; + struct isp_buflist_buffer desc = { + .iovas = {0x20000000, 0x203f4800}, + .num_planes = 2, .pool_type = CISP_POOL_TYPE_RENDERED_SCL1, .tag = 42, + }; + + KUNIT_EXPECT_TRUE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.tag--; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.tag++; + desc.iovas[1]++; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[1]--; + desc.iovas[0] |= BIT_ULL(40); + /* Reject mixed encodings, accept both exact full submitted addresses. */ + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[1] |= BIT_ULL(40); + KUNIT_EXPECT_TRUE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[0] |= BIT_ULL(41); + desc.iovas[1] |= BIT_ULL(41); + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[0] = (u32)desc.iovas[0]; + desc.iovas[1] = (u32)desc.iovas[1]; + desc.pool_type = CISP_POOL_TYPE_RENDERED; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.pool_type = CISP_POOL_TYPE_RENDERED_SCL1; + desc.num_planes = 1; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); +} + +static void isp_test_p010_report_defers_completion(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_buffer *buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); + struct { + u64 type, count; + struct isp_buflist_buffer desc; + } report = { + .type = 2, .count = 1, + .desc = { .iovas = {0x20000000, 0x203f4800}, + .num_planes = 2, .pool_type = 9, .tag = 42 }, + }; + struct isp_surf report_surf = { + .iova = 0x30000000, .size = sizeof(report), .virt = &report, + }; + struct isp_channel chan = { + .req = { .arg0 = report_surf.iova, .arg1 = sizeof(report), + .arg2 = ISP_IPC_BUFEXC_FLAG_RENDER }, + }; + + KUNIT_ASSERT_NOT_NULL(test, buf); + fmt->pixelformat = V4L2_PIX_FMT_P010; + fmt->plane_size[0] = 4147200; + isp->fw_iova_mask = U64_MAX; + isp->bt_surf = &report_surf; + buf->surfs[0].iova = 0x10020000000ULL; + buf->tag = 42; + INIT_LIST_HEAD(&isp->bufs_submitted); + list_add(&buf->link, &isp->bufs_submitted); + /* There is no vb2 queue: completing it before the ACK would fault. */ + KUNIT_ASSERT_EQ(test, ipc_bt_handle(isp, &chan), 0); + KUNIT_EXPECT_TRUE(test, list_empty(&isp->bufs_submitted)); + KUNIT_EXPECT_PTR_EQ(test, + list_first_entry(&isp->bufs_returned, struct isp_buffer, link), buf); + KUNIT_EXPECT_EQ(test, buf->return_state, VB2_BUF_STATE_DONE); + KUNIT_EXPECT_EQ(test, chan.rsp.arg0, report_surf.iova | ISP_IPC_FLAG_ACK); +} + +static void isp_test_p010_mode_selection(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_preset presets[] = { + { .index = 5, .output_dim = {1920, 1080} }, + { .index = 5, .output_dim = {1280, 720}, .p010 = true }, + { .index = 1, .output_dim = {4096, 3072} }, + }; + + isp->hw = &isp_test_t6040; + isp_get_current_format(isp)->version = 0x958; + isp->presets = presets; + isp->num_presets = ARRAY_SIZE(presets); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 0, 0, V4L2_PIX_FMT_P010), &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 1920, 1080, V4L2_PIX_FMT_P010), + &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 4096, 3072, V4L2_PIX_FMT_P010), + &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 1920, 1080, V4L2_PIX_FMT_NV12), + &presets[0]); + /* Only a matching firmware output enables the larger P010 size. */ + presets[2].p010 = true; + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 4096, 3072, V4L2_PIX_FMT_P010), + &presets[2]); +} + +static int isp_test_buffer_ack(struct apple_isp *isp, struct isp_channel *chan, + unsigned long timeout) +{ + unsigned int *calls = kunit_get_current_test()->priv; + + (*calls)++; + return 0; +} + +static void isp_test_imx958_last_lease_requeue(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_buffer *buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); + struct isp_buffer *submitted; + unsigned int calls = 0; + + KUNIT_ASSERT_NOT_NULL(test, buf); + isp->hw = &isp_test_t6040; + fmt->version = 0x958; + fmt->pixelformat = V4L2_PIX_FMT_P010; + fmt->num_planes = 2; + fmt->plane_size[0] = 1843200; + fmt->plane_size[1] = 921600; + INIT_LIST_HEAD(&isp->bufs_pending); + INIT_LIST_HEAD(&isp->bufs_submitted); + isp->cmd_virt = kunit_kzalloc(test, ISP_CMD_AREA_SIZE(0), GFP_KERNEL); + isp->chan_bh = kunit_kzalloc(test, sizeof(*isp->chan_bh), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + KUNIT_ASSERT_NOT_NULL(test, isp->chan_bh); + for (unsigned int i = 0; i < ISP_MAX_BUFFERS; i++) { + isp->meta_surfs[i] = kunit_kzalloc(test, sizeof(struct isp_surf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->meta_surfs[i]); + isp->meta_surfs[i]->submitted = true; + } + isp->vbq.drv_priv = isp; + buf->vb.vb2_buf.vb2_queue = &isp->vbq; + buf->surfs[0].iova = 0x20000000; + set_bit(ISP_STATE_STREAMING, &isp->state); + test->priv = &calls; + kunit_activate_static_stub(test, ipc_chan_send, isp_test_buffer_ack); + for (unsigned int format = 0; format < 2; format++) { + fmt->pixelformat = format ? V4L2_PIX_FMT_NV12 : V4L2_PIX_FMT_P010; + calls = 0; + for (unsigned int i = 1; i <= 10000; i++) { + /* Both lists empty, as after the last completion was dequeued. */ + isp_vb2_buf_queue(&buf->vb.vb2_buf); + KUNIT_ASSERT_EQ(test, calls, i); + if (!format) + KUNIT_ASSERT_EQ(test, buf->tag, (u64)i); + KUNIT_ASSERT_TRUE(test, list_empty(&isp->bufs_pending)); + submitted = list_first_entry(&isp->bufs_submitted, struct isp_buffer, link); + KUNIT_ASSERT_PTR_EQ(test, submitted, buf); + list_del(&buf->link); + } + } +} + static struct kunit_case isp_v4l2_test_cases[] = { + KUNIT_CASE(isp_test_imx958_last_lease_requeue), + KUNIT_CASE(isp_test_p010_report_defers_completion), + KUNIT_CASE(isp_test_p010_mode_selection), + KUNIT_CASE(isp_test_p010_format), + KUNIT_CASE(isp_test_p010_admission), + KUNIT_CASE(isp_test_p010_completion_identity), KUNIT_CASE(isp_test_queue_init), KUNIT_CASE(isp_test_reqbufs_limit), KUNIT_CASE(isp_test_create_bufs_limit), diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 072b8523279c2..ff0133e34dee5 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -21,24 +21,18 @@ #define ISP_MIN_FRAMES 2 #define ISP_MAX_PLANES 4 -#define ISP_MAX_PIX_FORMATS 2 #define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500) #define ISP_STRIDE_ALIGNMENT 64 /* Capture rates offered to userspace, in frames per second */ static const unsigned int isp_frame_rates[] = { 30, 25, 24, 20, 15 }; -static bool multiplanar = false; -module_param(multiplanar, bool, 0644); -MODULE_PARM_DESC(multiplanar, "Enable multiplanar API"); - struct isp_buflist_buffer { u64 iovas[ISP_MAX_PLANES]; u32 flags[ISP_MAX_PLANES]; u32 num_planes; u32 pool_type; - u32 tag; - u32 pad; + u64 tag; } __packed; static_assert(sizeof(struct isp_buflist_buffer) == ISP_BUFLIST_DESC_SIZE); @@ -57,6 +51,38 @@ static_assert(ISP_CMD_AREA_SIZE(0) >= ISP_IPC_BUFEXC_STAT_SIZE); */ #define ISP_BUFLIST_POOL_CAPTURE_META 2 +static bool isp_p010_buffer_matches(const struct isp_format *fmt, + const struct isp_buffer *buf, + const struct isp_buflist_buffer *desc) +{ + u64 y = buf->surfs[0].iova; + u64 uv = y + fmt->plane_size[0]; + bool full = desc->iovas[0] == y && desc->iovas[1] == uv; + bool low = desc->iovas[0] == (u32)y && desc->iovas[1] == (u32)uv; + + /* Accept the submitted pair or the zero-extended pair observed on H17. */ + return desc->num_planes == 2 && + desc->pool_type == CISP_POOL_TYPE_RENDERED_SCL1 && (full || low) && + !desc->iovas[2] && !desc->iovas[3] && desc->tag == buf->tag; +} + +void ipc_bt_complete(struct apple_isp *isp) +{ + struct isp_buffer *buf, *tmp; + unsigned long flags, i; + + /* The firmware has received its ACK before userspace can reuse a lease. */ + spin_lock_irqsave(&isp->buf_lock, flags); + for_each_set_bit(i, &isp->meta_returned, ISP_MAX_BUFFERS) + isp->meta_surfs[i]->submitted = false; + isp->meta_returned = 0; + list_for_each_entry_safe(buf, tmp, &isp->bufs_returned, link) { + list_del(&buf->link); + vb2_buffer_done(&buf->vb.vb2_buf, buf->return_state); + } + spin_unlock_irqrestore(&isp->buf_lock, flags); +} + int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) { struct isp_message *req = &chan->req, *rsp = &chan->rsp; @@ -96,11 +122,14 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) bufd->pool_type); */ if (bufd->pool_type == 0) { - for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { + for (int j = 0; j < isp_num_meta(isp); j++) { struct isp_surf *meta = isp->meta_surfs[j]; if ((u32)bufd->iovas[0] == (u32)meta->iova) { WARN_ON(!meta->submitted); - meta->submitted = false; + if (isp_is_p010(isp)) + __set_bit(j, &isp->meta_returned); + else + meta->submitted = false; } } } else if (bufd->pool_type == ISP_BUFLIST_POOL_CAPTURE_META && @@ -114,8 +143,10 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) } else { list_for_each_entry_safe_reverse( buf, tmp, &isp->bufs_submitted, link) { - if ((u32)buf->surfs[0].iova == - (u32)bufd->iovas[0]) { + if (isp_is_p010(isp) ? + isp_p010_buffer_matches(isp_get_current_format(isp), + buf, bufd) : + (u32)buf->surfs[0].iova == (u32)bufd->iovas[0]) { enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; @@ -126,9 +157,13 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER) state = VB2_BUF_STATE_DONE; - vb2_buffer_done(&buf->vb.vb2_buf, - state); - list_del(&buf->link); + if (isp_is_p010(isp)) { + buf->return_state = state; + list_move_tail(&buf->link, &isp->bufs_returned); + } else { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->link); + } } } } @@ -166,7 +201,7 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) bl->type = 1; spin_lock_irqsave(&isp->buf_lock, flags); - for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { + for (int i = 0; i < isp_num_meta(isp); i++) { struct isp_surf *meta = isp->meta_surfs[i]; if (meta->submitted) @@ -205,9 +240,13 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) } /* One rendered pool per batch; the rest stays pending for the next. */ - while (num_rendered < ISP_MAX_BUFFERS && + while (num_rendered < isp_num_rendered(isp) && (buf = list_first_entry_or_null(&isp->bufs_pending, struct isp_buffer, link))) { + if (isp_is_p010(isp)) { + buf->tag = ++isp->buffer_tag; + bufd->tag = buf->tag; + } bufd->num_planes = fmt->num_planes; bufd->pool_type = isp->hw->scl1 ? CISP_POOL_TYPE_RENDERED_SCL1 : CISP_POOL_TYPE_RENDERED; @@ -249,6 +288,11 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); if (err) { + if (isp_is_p010(isp)) { + /* Keep leases owned until stop_camera halts the coprocessor. */ + isp->ipc_failed = true; + return err; + } /* If we fail, consider the buffer not submitted. */ dev_err(isp->dev, "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", @@ -269,7 +313,7 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) &isp->bufs_pending); } } - for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { + for (int j = 0; j < isp_num_meta(isp); j++) { struct isp_surf *meta = isp->meta_surfs[j]; if (bufd->iovas[0] == meta->iova) { meta->submitted = false; @@ -308,13 +352,13 @@ static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, * REQBUFS frees the old buffers first, CREATE_BUFS adds to them, so * count the buffers the queue already has. */ - if (q_num_bufs >= ISP_MAX_BUFFERS) + if (q_num_bufs >= isp_num_rendered(isp)) return -ENOBUFS; *nbuffers = min_t(unsigned int, *nbuffers, - ISP_MAX_BUFFERS - q_num_bufs); + isp_num_rendered(isp) - q_num_bufs); if (*num_planes) { - if (sizes[0] < fmt->total_size) + if (*num_planes != 1 || sizes[0] < fmt->total_size) return -EINVAL; return 0; @@ -407,8 +451,15 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) list_add_tail(&buf->link, &isp->bufs_pending); spin_unlock_irqrestore(&isp->buf_lock, flags); - if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty) - isp_submit_buffers(isp, false); + /* Restart an empty IMX958 queue when its last returned lease is queued. */ + if (test_bit(ISP_STATE_STREAMING, &isp->state) && + (isp_supports_p010(isp) || !empty)) { + if (isp_submit_buffers(isp, false) && isp_is_p010(isp)) { + apple_isp_firmware_halt(isp); + isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR); + vb2_queue_error(&isp->vbq); + } + } } static int apple_isp_start_streaming(struct apple_isp *isp) @@ -565,18 +616,23 @@ static const struct vb2_ops isp_vb2_ops = { }; static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, - struct isp_preset *preset) + struct isp_preset *preset, u32 pixelformat) { + unsigned int bytes_per_sample; int i; size_t total_size; fmt->preset = preset; - /* I really fucking hope they all use NV12. */ + /* P010 has MSB-aligned 10-bit samples in little-endian 16-bit words. */ + fmt->pixelformat = pixelformat == V4L2_PIX_FMT_P010 && + isp_supports_p010(isp) && preset->p010 ? + V4L2_PIX_FMT_P010 : V4L2_PIX_FMT_NV12; + bytes_per_sample = fmt->pixelformat == V4L2_PIX_FMT_P010 ? 2 : 1; fmt->num_planes = 2; - fmt->strides[0] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); - /* UV subsampled interleaved */ - fmt->strides[1] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); + fmt->strides[0] = ALIGN(preset->output_dim.x * bytes_per_sample, + ISP_STRIDE_ALIGNMENT); + fmt->strides[1] = fmt->strides[0]; fmt->plane_size[0] = fmt->strides[0] * preset->output_dim.y; fmt->plane_size[1] = fmt->strides[1] * preset->output_dim.y / 2; @@ -589,17 +645,25 @@ static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, } static struct isp_preset *isp_select_preset(struct apple_isp *isp, u32 width, - u32 height) + u32 height, u32 pixelformat) { struct isp_preset *preset, *best = &isp->presets[0]; int i, score, best_score = INT_MAX; - /* Default if no dimensions */ - if (width == 0 || height == 0) - return &isp->presets[0]; + bool p010 = pixelformat == V4L2_PIX_FMT_P010 && isp_has_p010(isp); + + /* An unspecified P010 size selects the processed 720p preview first. */ + if (width == 0 || height == 0) { + if (!p010) + return &isp->presets[0]; + width = 1280; + height = 720; + } for (i = 0; i < isp->num_presets; i++) { preset = &isp->presets[i]; + if (p010 && !preset->p010) + continue; score = abs((int)preset->output_dim.x - (int)width) + abs((int)preset->output_dim.y - (int)height); if (score < best_score) { @@ -623,44 +687,47 @@ static int isp_vidioc_querycap(struct file *file, void *priv, return 0; } -static int isp_vidioc_enum_format(struct file *file, void *fh, - struct v4l2_fmtdesc *f) +static int isp_enum_pixelformat(struct apple_isp *isp, struct v4l2_fmtdesc *f) { - struct apple_isp *isp = video_drvdata(file); - - if (f->index >= ISP_MAX_PIX_FORMATS) - return -EINVAL; - - switch (f->index) { - case 0: + /* Browser/WebRTC compatibility: the shipped first format remains NV12. */ + if (!f->index) f->pixelformat = V4L2_PIX_FMT_NV12; - break; - case 1: - if (!isp->multiplanar) - return -EINVAL; - f->pixelformat = V4L2_PIX_FMT_NV12M; - break; - default: + else if (f->index == 1 && isp_has_p010(isp)) + f->pixelformat = V4L2_PIX_FMT_P010; + else return -EINVAL; - } - return 0; } +static int isp_vidioc_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + return isp_enum_pixelformat(video_drvdata(file), f); +} + static int isp_vidioc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_preset *preset = NULL; + u32 index = 0; - if (f->index >= isp->num_presets) + if (f->pixel_format != V4L2_PIX_FMT_NV12 && + !(f->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) return -EINVAL; - if ((f->pixel_format != V4L2_PIX_FMT_NV12) && - (f->pixel_format != V4L2_PIX_FMT_NV12M)) + for (int i = 0; i < isp->num_presets; i++) { + if (f->pixel_format == V4L2_PIX_FMT_P010 && !isp->presets[i].p010) + continue; + if (index++ == f->index) { + preset = &isp->presets[i]; + break; + } + } + if (!preset) return -EINVAL; - - f->discrete.width = isp->presets[f->index].output_dim.x; - f->discrete.height = isp->presets[f->index].output_dim.y; + f->discrete.width = preset->output_dim.x; + f->discrete.height = preset->output_dim.y; f->type = V4L2_FRMSIZE_TYPE_DISCRETE; return 0; @@ -676,10 +743,12 @@ static int isp_vidioc_enum_frameintervals(struct file *file, void *fh, return -EINVAL; if (interval->pixel_format != V4L2_PIX_FMT_NV12 && - interval->pixel_format != V4L2_PIX_FMT_NV12M) + !(interval->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) return -EINVAL; for (i = 0; i < isp->num_presets; i++) { + if (interval->pixel_format == V4L2_PIX_FMT_P010 && !isp->presets[i].p010) + continue; if (isp->presets[i].output_dim.x == interval->width && isp->presets[i].output_dim.y == interval->height) break; @@ -726,29 +795,12 @@ static inline void isp_get_sp_pix_format(struct apple_isp *isp, f->fmt.pix.sizeimage = fmt->total_size; f->fmt.pix.field = V4L2_FIELD_NONE; - f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; + f->fmt.pix.pixelformat = fmt->pixelformat; f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_709; f->fmt.pix.xfer_func = V4L2_XFER_FUNC_709; -} - -static inline void isp_get_mp_pix_format(struct apple_isp *isp, - struct v4l2_format *f, - struct isp_format *fmt) -{ - f->fmt.pix_mp.width = fmt->preset->output_dim.x; - f->fmt.pix_mp.height = fmt->preset->output_dim.y; - f->fmt.pix_mp.num_planes = fmt->num_planes; - for (int i = 0; i < fmt->num_planes; i++) { - f->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->plane_size[i]; - f->fmt.pix_mp.plane_fmt[i].bytesperline = fmt->strides[i]; - } - - f->fmt.pix_mp.field = V4L2_FIELD_NONE; - f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; - f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; - f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_709; - f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_709; + if (fmt->pixelformat == V4L2_PIX_FMT_P010) + f->fmt.pix.quantization = V4L2_QUANTIZATION_LIM_RANGE; } static int isp_vidioc_get_format(struct file *file, void *fh, @@ -774,8 +826,9 @@ static int isp_vidioc_set_format(struct file *file, void *fh, if (vb2_is_busy(&isp->vbq)) return -EBUSY; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); - err = isp_set_preset(isp, fmt, preset); + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat); + err = isp_set_preset(isp, fmt, preset, f->fmt.pix.pixelformat); if (err) return err; @@ -794,8 +847,9 @@ static int isp_vidioc_try_format(struct file *file, void *fh, struct isp_preset *preset; int err; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); - err = isp_set_preset(isp, &fmt, preset); + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat); + err = isp_set_preset(isp, &fmt, preset, f->fmt.pix.pixelformat); if (err) return err; @@ -804,69 +858,6 @@ static int isp_vidioc_try_format(struct file *file, void *fh, return 0; } -static int isp_vidioc_get_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format *fmt = isp_get_current_format(isp); - - if (!isp->multiplanar) - return -ENOTTY; - - isp_get_mp_pix_format(isp, f, fmt); - - return 0; -} - -static int isp_vidioc_set_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format *fmt = isp_get_current_format(isp); - struct isp_preset *preset; - int err; - - if (!isp->multiplanar) - return -ENOTTY; - - if (vb2_is_busy(&isp->vbq)) - return -EBUSY; - - preset = isp_select_preset(isp, f->fmt.pix_mp.width, - f->fmt.pix_mp.height); - err = isp_set_preset(isp, fmt, preset); - if (err) - return err; - - isp_get_mp_pix_format(isp, f, fmt); - - isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - - return 0; -} - -static int isp_vidioc_try_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format fmt = *isp_get_current_format(isp); - struct isp_preset *preset; - int err; - - if (!isp->multiplanar) - return -ENOTTY; - - preset = isp_select_preset(isp, f->fmt.pix_mp.width, - f->fmt.pix_mp.height); - err = isp_set_preset(isp, &fmt, preset); - if (err) - return err; - - isp_get_mp_pix_format(isp, f, &fmt); - - return 0; -} - static int isp_vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *inp) { @@ -899,9 +890,7 @@ static int isp_vidioc_get_param(struct file *file, void *fh, { struct apple_isp *isp = video_drvdata(file); - if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && - (!isp->multiplanar || - a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; @@ -917,9 +906,7 @@ static int isp_vidioc_set_param(struct file *file, void *fh, struct apple_isp *isp = video_drvdata(file); struct v4l2_fract *tpf = &a->parm.capture.timeperframe; - if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && - (!isp->multiplanar || - a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; /* The rate is configured when the stream starts. */ @@ -940,6 +927,27 @@ static int isp_vidioc_set_param(struct file *file, void *fh, return 0; } +static int isp_vidioc_log_status(struct file *file, void *fh) +{ + struct apple_isp *isp = video_drvdata(file); + + v4l2_ctrl_log_status(file, fh); + for (unsigned int i = 0; i < isp->num_sensor_configs; i++) { + const struct isp_sensor_config *config = &isp->sensor_configs[i]; + + v4l2_info(&isp->v4l2_dev, + "sensor config %u: input %ux%u output %ux%u clock %u%s\n", + i, config->input_dim.x, config->input_dim.y, + config->output_dim.x, config->output_dim.y, + config->sensor_clk, + config->usable ? "" : " (unverified geometry)"); + /* This explicit diagnostic ioctl replaces a probe-time parameter. */ + print_hex_dump(KERN_INFO, "apple-isp config: ", DUMP_PREFIX_OFFSET, + 16, 4, config->reply, sizeof(config->reply), false); + } + return 0; +} + static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_querycap = isp_vidioc_querycap, @@ -947,9 +955,6 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_g_fmt_vid_cap = isp_vidioc_get_format, .vidioc_s_fmt_vid_cap = isp_vidioc_set_format, .vidioc_try_fmt_vid_cap = isp_vidioc_try_format, - .vidioc_g_fmt_vid_cap_mplane = isp_vidioc_get_format_mplane, - .vidioc_s_fmt_vid_cap_mplane = isp_vidioc_set_format_mplane, - .vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane, .vidioc_enum_framesizes = isp_vidioc_enum_framesizes, .vidioc_enum_frameintervals = isp_vidioc_enum_frameintervals, @@ -969,7 +974,7 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, - .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_log_status = isp_vidioc_log_status, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; @@ -1001,6 +1006,7 @@ static void apple_isp_release(struct v4l2_device *v4l2_dev) v4l2_ctrl_handler_free(&isp->ctrl_handler); media_device_cleanup(&isp->mdev); + kfree(isp->sensor_configs); kfree(isp); } @@ -1044,7 +1050,7 @@ int apple_isp_setup_video(struct apple_isp *isp) struct isp_format *fmt = isp_get_current_format(isp); int err; - err = isp_set_preset(isp, fmt, &isp->presets[0]); + err = isp_set_preset(isp, fmt, &isp->presets[0], V4L2_PIX_FMT_NV12); if (err) { dev_err(isp->dev, "failed to set default preset: %d\n", err); return err; @@ -1054,7 +1060,8 @@ int apple_isp_setup_video(struct apple_isp *isp) for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { isp->meta_surfs[i] = - isp_alloc_surface_vmap(isp, isp->hw->meta_size); + isp_alloc_surface_vmap(isp, isp_has_p010(isp) ? 0x8000 : + isp->hw->meta_size); if (!isp->meta_surfs[i]) { isp_err(isp, "failed to alloc meta surface\n"); err = -ENOMEM; @@ -1086,8 +1093,6 @@ int apple_isp_setup_video(struct apple_isp *isp) goto media_cleanup; } - isp->multiplanar = multiplanar; - /* * Auto exposure is always on. By default it holds the frame rate, as * V4L2 specifies; with exposure priority it may slow down in low light. @@ -1119,8 +1124,6 @@ int apple_isp_setup_video(struct apple_isp *isp) vdev->fops = &isp_v4l2_fops; vdev->ioctl_ops = &isp_v4l2_ioctl_ops; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - if (isp->multiplanar) - vdev->device_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE; vdev->entity.flags = MEDIA_ENT_FL_DEFAULT; vdev->v4l2_dev = &isp->v4l2_dev; vdev->vfl_type = VFL_TYPE_VIDEO; diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h index 622288296aaae..55135594576b2 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.h +++ b/drivers/media/platform/apple/isp/isp-v4l2.h @@ -9,6 +9,7 @@ int apple_isp_setup_video(struct apple_isp *isp); void apple_isp_remove_video(struct apple_isp *isp); void apple_isp_free_video(struct apple_isp *isp); +void ipc_bt_complete(struct apple_isp *isp); int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan); int apple_isp_video_suspend(struct apple_isp *isp); diff --git a/drivers/phy/apple/atc-t6040-dp2-data.h b/drivers/phy/apple/atc-t6040-dp2-data.h new file mode 100644 index 0000000000000..c76fbe14d5ac3 --- /dev/null +++ b/drivers/phy/apple/atc-t6040-dp2-data.h @@ -0,0 +1,462 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Two-lane DisplayPort link sequences for the T6040 ATC core. + * From Dj's public T6050 combo PHY tables, licensed GPL-2.0-only OR MIT: + * https://github.com/aurora-silicon/linux/pull/480 + * The common ATC banks and two USB3/DP lane groups match the T6040 core. + */ +static const struct atc_op dp_group0_off[] = { + { ATC_MASK, 39, 0x14, 0x1007, 0x7, 0 }, + { ATC_MASK, 39, 0x18, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x1c, 0x1, 0x0, 5000 }, + { ATC_MASK, 14, 0x0, 0x1, 0x1, 0 }, + { ATC_MASK, 22, 0x1c, 0xc00000, 0xc00000, 0 }, + { ATC_MASK, 22, 0x10, 0x600, 0x400, 0 }, + { ATC_MASK, 22, 0x4, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x100000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x400000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x4000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x1000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x1000, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x100, 0x100, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x44, 0x6000000, 0x4000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x44, 0x18000000, 0x10000000, 0 }, + { ATC_MASK, 19, 0x24, 0xc0000000, 0x80000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x24, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x28, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x40, 0xc000, 0xc000, 0 }, + { ATC_MASK, 16, 0x1d0, 0x3, 0x1, 0 }, + { ATC_MASK, 19, 0x40, 0x10000, 0x0, 0 }, + { ATC_MASK, 2, 0xc, 0x4000, 0x4000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x40, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x400, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 14, 0x0, 0xc, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x40000, 0x40000, 0 }, + { ATC_MASK, 3, 0x8, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x80000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x80000, 0x80000, 0 }, + { ATC_MASK, 3, 0x8, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x40000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_POLL, 14, 0x34, 0x8, 0x0, 500000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 6, 0x34, 0xf, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x0, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_MASK, 8, 0x0, 0xaaa, 0x0, 0 }, +}; + +static const struct atc_rate_op dp_group0_on[] = { + { { ATC_MASK, 39, 0x14, 0x7, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 39, 0x18, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x6c, 0x1c, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 14, 0x0, 0x40fff0, 0, 0 }, 0xf, { 0x1110, 0x1110, 0x1110, 0x1110 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x1, { 0x1e0e021c, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x1, { 0x156600, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x1, { 0x70000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x1, { 0x1, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x2, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x2, { 0x0, 0x1e0e01c2, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x2, { 0x0, 0x7fffffe, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x2, { 0x0, 0x155200, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x2, { 0x0, 0x70000, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x2, { 0x0, 0x1, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x4, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x1e0e01c2, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x7fffffe, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x554800, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x4, { 0x0, 0x0, 0x70000, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x4, { 0x0, 0x0, 0x1, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1e0e02a3 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0xbff7ffc } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x564800 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x70000 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1 } }, + { { ATC_MASK, 8, 0x0, 0xaaa, 0, 0 }, 0xf, { 0xaaa, 0xaaa, 0xaaa, 0xaaa } }, + { { ATC_MASK, 6, 0x30, 0x4000, 0, 0 }, 0xf, { 0x4000, 0x4000, 0x4000, 0x4000 } }, + { { ATC_MASK, 6, 0x34, 0xf, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x18, 0x1fe000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_MASK, 6, 0x20, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x1c, 0x7, 0, 0 }, 0xf, { 0x7, 0x7, 0x7, 0x7 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 14, 0x34, 0x8, 0, 500000 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 19, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 14, 0x0, 0xc, 0, 0 }, 0xf, { 0xc, 0xc, 0xc, 0xc } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x10, 0, 0 }, 0xf, { 0x10, 0x10, 0x10, 0x10 } }, + { { ATC_MASK, 2, 0x120, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x4, 0, 0 }, 0xf, { 0x4, 0x4, 0x4, 0x4 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x100, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0x120, 0x400, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0xfffffc0, 0, 0 }, 0xf, { 0xaafb800, 0xaafb800, 0xaafb800, 0xaafb800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x1000000, 0, 0 }, 0xf, { 0x1000000, 0x1000000, 0x1000000, 0x1000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x4000000, 0, 0 }, 0xf, { 0x4000000, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x7c0, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x6c000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 21, 0x100, 0x100000, 0, 0 }, 0xf, { 0x100000, 0x100000, 0x100000, 0x100000 } }, + { { ATC_MASK, 21, 0x58, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 21, 0x104, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 21, 0x100, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x100000, 0, 0 }, 0x7, { 0x100000, 0x100000, 0x100000, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0x7, { 0x1, 0x1, 0x1, 0x0 } }, + { { ATC_MASK, 22, 0x44, 0x400000, 0, 0 }, 0x7, { 0x400000, 0x400000, 0x400000, 0x0 } }, + { { ATC_MASK, 22, 0x4, 0x3000000, 0, 0 }, 0xf, { 0x2000000, 0x2000000, 0x2000000, 0x2000000 } }, + { { ATC_MASK, 22, 0x4, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_MASK, 22, 0xc, 0x7000000, 0, 0 }, 0x1, { 0x5000000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 22, 0xc, 0x7000000, 0, 0 }, 0xe, { 0x0, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_MASK, 22, 0x8, 0x3, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 22, 0x10, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_MASK, 22, 0x1c, 0xc00000, 0, 0 }, 0xf, { 0x800000, 0x800000, 0x800000, 0x800000 } }, + { { ATC_MASK, 19, 0xf4, 0x3000, 0, 0 }, 0xf, { 0x3000, 0x3000, 0x3000, 0x3000 } }, + { { ATC_MASK, 16, 0x188, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d4, 0x700, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_MASK, 19, 0x24, 0x3f0000, 0, 0 }, 0xf, { 0x2a0000, 0x2a0000, 0x2a0000, 0x2a0000 } }, + { { ATC_MASK, 19, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 19, 0x78, 0xc0000, 0, 0 }, 0xf, { 0x80000, 0x80000, 0x80000, 0x80000 } }, + { { ATC_MASK, 19, 0x7c, 0xc0, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 19, 0x84, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_MASK, 19, 0xd4, 0x600000, 0, 0 }, 0xf, { 0x400000, 0x400000, 0x400000, 0x400000 } }, + { { ATC_MASK, 19, 0xd8, 0x300, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_MASK, 19, 0x40, 0xc00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 19, 0x48, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 19, 0x28, 0x1ff030, 0, 0 }, 0xf, { 0x11c020, 0x11c020, 0x11c020, 0x11c020 } }, + { { ATC_MASK, 19, 0x2c, 0xfc, 0, 0 }, 0xf, { 0xb0, 0xb0, 0xb0, 0xb0 } }, + { { ATC_MASK, 19, 0x28, 0xe00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 19, 0x28, 0x1c0, 0, 0 }, 0xf, { 0x180, 0x180, 0x180, 0x180 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x10, 0, 0 }, 0xf, { 0x10, 0x10, 0x10, 0x10 } }, + { { ATC_MASK, 2, 0x10, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x4, 0, 0 }, 0xf, { 0x4, 0x4, 0x4, 0x4 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x400, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 2, 0xc, 0x1000, 0, 0 }, 0xf, { 0x1000, 0x1000, 0x1000, 0x1000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_MASK, 2, 0xc, 0x100, 0, 0 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x4000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0xc, 0x10000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d0, 0x3, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d0, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x40, 0x30000, 0, 0 }, 0xf, { 0x30000, 0x30000, 0x30000, 0x30000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x40, 0xc000, 0, 0 }, 0xf, { 0xc000, 0xc000, 0xc000, 0xc000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x3010, 0, 0 }, 0xf, { 0x1010, 0x1010, 0x1010, 0x1010 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x3000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x24, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x24, 0xc0000000, 0, 0 }, 0xf, { 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 19, 0x44, 0x18000000, 0, 0 }, 0xf, { 0x18000000, 0x18000000, 0x18000000, 0x18000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 19, 0x40, 0x38, 0, 0 }, 0x1, { 0x28, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 19, 0x40, 0x38, 0, 0 }, 0xe, { 0x0, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 19, 0x44, 0x60000000, 0, 0 }, 0xf, { 0x40000000, 0x40000000, 0x40000000, 0x40000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x44, 0x6000000, 0, 0 }, 0xf, { 0x6000000, 0x6000000, 0x6000000, 0x6000000 } }, + { { ATC_MASK, 19, 0x40, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 20, 0x68, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 20, 0xac, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 20, 0xa8, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 20, 0x18, 0x38, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 14, 0x0, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 39, 0x1c, 0x1, 0, 5000 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, +}; + +static const struct atc_op dp_group1_off[] = { + { ATC_MASK, 39, 0x14, 0x1007, 0x7, 0 }, + { ATC_MASK, 39, 0x18, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x1c, 0x1, 0x0, 5000 }, + { ATC_MASK, 14, 0x0, 0x1, 0x1, 0 }, + { ATC_MASK, 31, 0x1c, 0xc00000, 0xc00000, 0 }, + { ATC_MASK, 31, 0x10, 0x600, 0x400, 0 }, + { ATC_MASK, 31, 0x4, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x100000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x400000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x4000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x1000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x1000, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x200, 0x200, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x44, 0x6000000, 0x4000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x44, 0x18000000, 0x10000000, 0 }, + { ATC_MASK, 28, 0x24, 0xc0000000, 0x80000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x24, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x28, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x40, 0xc000, 0xc000, 0 }, + { ATC_MASK, 25, 0x1d0, 0x3, 0x1, 0 }, + { ATC_MASK, 28, 0x40, 0x10000, 0x0, 0 }, + { ATC_MASK, 2, 0xc, 0x8000, 0x8000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x80, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x800, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 14, 0x0, 0xc, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x400000, 0x400000, 0 }, + { ATC_MASK, 3, 0x8, 0x4, 0x4, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x800000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x800000, 0x800000, 0 }, + { ATC_MASK, 3, 0x8, 0x8, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x400000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_POLL, 14, 0x34, 0x8, 0x0, 500000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 6, 0x34, 0xf, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x0, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_MASK, 8, 0x0, 0xaaa, 0x0, 0 }, +}; + +static const struct atc_rate_op dp_group1_on[] = { + { { ATC_MASK, 39, 0x14, 0x7, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 39, 0x18, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x6c, 0xe0, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_MASK, 14, 0x0, 0x40fff0, 0, 0 }, 0xf, { 0x1110, 0x1110, 0x1110, 0x1110 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x1, { 0x1e0e021c, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x1, { 0x156600, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x1, { 0x70000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x1, { 0x1, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x2, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x2, { 0x0, 0x1e0e01c2, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x2, { 0x0, 0x7fffffe, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x2, { 0x0, 0x155200, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x2, { 0x0, 0x70000, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x2, { 0x0, 0x1, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x4, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x1e0e01c2, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x7fffffe, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x554800, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x4, { 0x0, 0x0, 0x70000, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x4, { 0x0, 0x0, 0x1, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1e0e02a3 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0xbff7ffc } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x564800 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x70000 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1 } }, + { { ATC_MASK, 8, 0x0, 0xaaa, 0, 0 }, 0xf, { 0xaaa, 0xaaa, 0xaaa, 0xaaa } }, + { { ATC_MASK, 6, 0x30, 0x4000, 0, 0 }, 0xf, { 0x4000, 0x4000, 0x4000, 0x4000 } }, + { { ATC_MASK, 6, 0x34, 0xf, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x18, 0x1fe000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_MASK, 6, 0x20, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x1c, 0x7, 0, 0 }, 0xf, { 0x7, 0x7, 0x7, 0x7 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 14, 0x34, 0x8, 0, 500000 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 28, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 14, 0x0, 0xc, 0, 0 }, 0xf, { 0xc, 0xc, 0xc, 0xc } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x20, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 2, 0x120, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 2, 0x120, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x200, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0x120, 0x800, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0xfffffc0, 0, 0 }, 0xf, { 0xaafb800, 0xaafb800, 0xaafb800, 0xaafb800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x1000000, 0, 0 }, 0xf, { 0x1000000, 0x1000000, 0x1000000, 0x1000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x4000000, 0, 0 }, 0xf, { 0x4000000, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x7c0, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x6c000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 30, 0x100, 0x100000, 0, 0 }, 0xf, { 0x100000, 0x100000, 0x100000, 0x100000 } }, + { { ATC_MASK, 30, 0x58, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 30, 0x104, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 30, 0x100, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x100000, 0, 0 }, 0x7, { 0x100000, 0x100000, 0x100000, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0x7, { 0x1, 0x1, 0x1, 0x0 } }, + { { ATC_MASK, 31, 0x44, 0x400000, 0, 0 }, 0x7, { 0x400000, 0x400000, 0x400000, 0x0 } }, + { { ATC_MASK, 31, 0x4, 0x3000000, 0, 0 }, 0xf, { 0x2000000, 0x2000000, 0x2000000, 0x2000000 } }, + { { ATC_MASK, 31, 0x4, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_MASK, 31, 0xc, 0x7000000, 0, 0 }, 0x1, { 0x5000000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 31, 0xc, 0x7000000, 0, 0 }, 0xe, { 0x0, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_MASK, 31, 0x8, 0x3, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 31, 0x10, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_MASK, 31, 0x1c, 0xc00000, 0, 0 }, 0xf, { 0x800000, 0x800000, 0x800000, 0x800000 } }, + { { ATC_MASK, 28, 0xf4, 0x3000, 0, 0 }, 0xf, { 0x3000, 0x3000, 0x3000, 0x3000 } }, + { { ATC_MASK, 25, 0x188, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d4, 0x700, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_MASK, 28, 0x24, 0x3f0000, 0, 0 }, 0xf, { 0x2a0000, 0x2a0000, 0x2a0000, 0x2a0000 } }, + { { ATC_MASK, 28, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 28, 0x78, 0xc0000, 0, 0 }, 0xf, { 0x80000, 0x80000, 0x80000, 0x80000 } }, + { { ATC_MASK, 28, 0x7c, 0xc0, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 28, 0x84, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_MASK, 28, 0xd4, 0x600000, 0, 0 }, 0xf, { 0x400000, 0x400000, 0x400000, 0x400000 } }, + { { ATC_MASK, 28, 0xd8, 0x300, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_MASK, 28, 0x40, 0xc00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 28, 0x48, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 28, 0x28, 0x1ff030, 0, 0 }, 0xf, { 0x11c020, 0x11c020, 0x11c020, 0x11c020 } }, + { { ATC_MASK, 28, 0x2c, 0xfc, 0, 0 }, 0xf, { 0xb0, 0xb0, 0xb0, 0xb0 } }, + { { ATC_MASK, 28, 0x28, 0xe00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 28, 0x28, 0x1c0, 0, 0 }, 0xf, { 0x180, 0x180, 0x180, 0x180 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x20, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 2, 0x10, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 2, 0x10, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x800, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 2, 0xc, 0x2000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 2, 0xc, 0x200, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x8000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0xc, 0x20000, 0, 0 }, 0xf, { 0x20000, 0x20000, 0x20000, 0x20000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d0, 0x3, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d0, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x40, 0x30000, 0, 0 }, 0xf, { 0x30000, 0x30000, 0x30000, 0x30000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x40, 0xc000, 0, 0 }, 0xf, { 0xc000, 0xc000, 0xc000, 0xc000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x3010, 0, 0 }, 0xf, { 0x1010, 0x1010, 0x1010, 0x1010 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x3000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x24, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x24, 0xc0000000, 0, 0 }, 0xf, { 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 28, 0x44, 0x18000000, 0, 0 }, 0xf, { 0x18000000, 0x18000000, 0x18000000, 0x18000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 28, 0x40, 0x38, 0, 0 }, 0x1, { 0x28, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 28, 0x40, 0x38, 0, 0 }, 0xe, { 0x0, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 28, 0x44, 0x60000000, 0, 0 }, 0xf, { 0x40000000, 0x40000000, 0x40000000, 0x40000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x44, 0x6000000, 0, 0 }, 0xf, { 0x6000000, 0x6000000, 0x6000000, 0x6000000 } }, + { { ATC_MASK, 28, 0x40, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 29, 0x68, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 29, 0xac, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 29, 0xa8, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 29, 0x18, 0x38, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 14, 0x0, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 39, 0x1c, 0x1, 0, 5000 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, +}; diff --git a/drivers/phy/apple/atc-test.c b/drivers/phy/apple/atc-test.c index c832f2b734f01..366b4b86f115e 100644 --- a/drivers/phy/apple/atc-test.c +++ b/drivers/phy/apple/atc-test.c @@ -1008,6 +1008,149 @@ static void atcphy_test_usb2_init_after_safe(struct kunit *test) KUNIT_EXPECT_EQ(test, atcphy->mode, APPLE_ATCPHY_MODE_OFF); } +/* An idle T8132 PHY may let its domain drop; anything it still runs holds it. */ +static void atcphy_test_t8132_rpm_busy(struct kunit *test) +{ + struct apple_atcphy *atcphy = atcphy_test_alloc(test, false); + + atcphy->hw = &atcphy_hw_t8132; + atcphy->mode = APPLE_ATCPHY_MODE_OFF; + atcphy->pipe_state = ATCPHY_PIPEHANDLER_STATE_DUMMY; + KUNIT_EXPECT_FALSE(test, atcphy_rpm_busy(atcphy)); + + atcphy->mode = APPLE_ATCPHY_MODE_USB2; + KUNIT_EXPECT_TRUE(test, atcphy_rpm_busy(atcphy)); + atcphy->mode = APPLE_ATCPHY_MODE_OFF; + + atcphy->host_active = true; + KUNIT_EXPECT_TRUE(test, atcphy_rpm_busy(atcphy)); + atcphy->host_active = false; + + atcphy->pipe_pending = true; + KUNIT_EXPECT_TRUE(test, atcphy_rpm_busy(atcphy)); + atcphy->pipe_pending = false; + + atcphy->pipe_state = ATCPHY_PIPEHANDLER_STATE_USB3; + KUNIT_EXPECT_TRUE(test, atcphy_rpm_busy(atcphy)); + atcphy->pipe_state = ATCPHY_PIPEHANDLER_STATE_DUMMY; + + /* A failed USB3 admission keeps its common-power epoch */ + atcphy->t8132_common_owned = true; + KUNIT_EXPECT_TRUE(test, atcphy_rpm_busy(atcphy)); +} + +/* The hold follows the busy state, with one reference at most. */ +static void atcphy_test_t8132_rpm_hold(struct kunit *test) +{ + struct apple_atcphy *atcphy = atcphy_test_alloc_powered_off(test); + + atcphy->hw = &atcphy_hw_t8132; + atcphy->idle_rpm = true; + atcphy->mode = APPLE_ATCPHY_MODE_USB2; + atcphy_rpm_sync(atcphy); + KUNIT_EXPECT_TRUE(test, atcphy->rpm_held); + KUNIT_EXPECT_EQ(test, atomic_read(&atcphy->dev->power.usage_count), 1); + atcphy_rpm_sync(atcphy); + KUNIT_EXPECT_EQ(test, atomic_read(&atcphy->dev->power.usage_count), 1); + + atcphy->mode = APPLE_ATCPHY_MODE_OFF; + atcphy_rpm_sync(atcphy); + KUNIT_EXPECT_FALSE(test, atcphy->rpm_held); + KUNIT_EXPECT_EQ(test, atomic_read(&atcphy->dev->power.usage_count), 0); + + /* Unbind drops a hold that is still taken */ + atcphy->mode = APPLE_ATCPHY_MODE_USB2; + atcphy_rpm_sync(atcphy); + atcphy_rpm_release(atcphy); + KUNIT_EXPECT_FALSE(test, atcphy->rpm_held); + KUNIT_EXPECT_EQ(test, atomic_read(&atcphy->dev->power.usage_count), 0); +} + +/* Record the mode a restore asks for, without touching MMIO. */ +static int atcphy_test_configure_record(struct apple_atcphy *atcphy, enum atcphy_mode mode) +{ + struct kunit *test = kunit_get_current_test(); + struct atcphy_test_configure *config = test->priv; + + lockdep_assert_held(&atcphy->lock); + config->calls++; + if (mode != APPLE_ATCPHY_MODE_OFF) + config->lane_requests++; + atcphy->mode = mode; + return 0; +} + +/* A T8132 PHY whose domain was off: zeroed memory stands in for the reset block. */ +static struct apple_atcphy *atcphy_test_alloc_t8132_pd_off(struct kunit *test, + enum atcphy_mode mode) +{ + struct apple_atcphy *atcphy = atcphy_test_alloc_powered_off(test); + void *core = kunit_kzalloc(test, ATCPHY_MISC + 4, GFP_KERNEL); + void *pipehandler = kunit_kzalloc(test, PIPEHANDLER_T8132_USB2_CONFIG + 4, GFP_KERNEL); + void *usb2phy = kunit_kzalloc(test, USB2PHY_MISCTUNE + 4, GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, core); + KUNIT_ASSERT_NOT_NULL(test, pipehandler); + KUNIT_ASSERT_NOT_NULL(test, usb2phy); + atcphy->regs.core = (__force void __iomem *)core; + atcphy->regs.pipehandler = (__force void __iomem *)pipehandler; + atcphy->regs.usb2phy = (__force void __iomem *)usb2phy; + atcphy->hw = &atcphy_hw_t8132; + atcphy->mode = mode; + atcphy->pd_was_off = true; + + return atcphy; +} + +/* The probe baseline comes back: controller in reset, USB2 off, dummy PIPE clock on. */ +static void atcphy_test_t8132_restore_idle(struct kunit *test) +{ + struct apple_atcphy *atcphy = atcphy_test_alloc_t8132_pd_off(test, APPLE_ATCPHY_MODE_OFF); + struct atcphy_test_configure config = {}; + u32 aon; + + test->priv = &config; + kunit_activate_static_stub(test, atcphy_configure, atcphy_test_configure_record); + scoped_guard(mutex, &atcphy->lock) + atcphy_restore_after_pd_off(atcphy); + + KUNIT_EXPECT_FALSE(test, atcphy->pd_was_off); + KUNIT_EXPECT_EQ(test, config.calls, 0U); + KUNIT_EXPECT_EQ(test, atcphy->mode, APPLE_ATCPHY_MODE_OFF); + KUNIT_EXPECT_EQ(test, atcphy->pipe_state, ATCPHY_PIPEHANDLER_STATE_DUMMY); + aon = readl(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN); + KUNIT_EXPECT_FALSE(test, aon & PIPEHANDLER_AON_GEN_DWC3_RESET_N); + KUNIT_EXPECT_TRUE(test, aon & PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN); + KUNIT_EXPECT_TRUE(test, readl(atcphy->regs.pipehandler + PIPEHANDLER_T8132_USB2_CONFIG) & + PIPEHANDLER_T8132_USB2_CONFIG_EN); + KUNIT_EXPECT_TRUE(test, readl(atcphy->regs.usb2phy + USB2PHY_CTL) & USB2PHY_CTL_SIDDQ); + /* As at probe, the common block is left for a USB3 admission to acquire. */ + KUNIT_EXPECT_EQ(test, readl(atcphy->regs.core + ATCPHY_POWER_CTRL), 0U); + + /* Nothing to do once the state is back. */ + scoped_guard(mutex, &atcphy->lock) + atcphy_restore_after_pd_off(atcphy); + KUNIT_EXPECT_EQ(test, config.calls, 0U); +} + +/* After the baseline, the mode the Type-C mux selected is requested again. */ +static void atcphy_test_t8132_restore_mode(struct kunit *test) +{ + struct apple_atcphy *atcphy = atcphy_test_alloc_t8132_pd_off(test, APPLE_ATCPHY_MODE_USB2); + struct atcphy_test_configure config = {}; + + test->priv = &config; + kunit_activate_static_stub(test, atcphy_configure, atcphy_test_configure_record); + scoped_guard(mutex, &atcphy->lock) + atcphy_restore_after_pd_off(atcphy); + + KUNIT_EXPECT_EQ(test, config.calls, 1U); + KUNIT_EXPECT_EQ(test, config.lane_requests, 1U); + KUNIT_EXPECT_EQ(test, atcphy->mode, APPLE_ATCPHY_MODE_USB2); + KUNIT_EXPECT_FALSE(test, readl(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN) & + PIPEHANDLER_AON_GEN_DWC3_RESET_N); +} + static void atcphy_test_t8103_usb3_machine_scope(struct kunit *test) { static const struct { @@ -1084,3 +1227,18 @@ static struct kunit_suite atcphy_mux_test_suite = { }; kunit_test_suite(atcphy_mux_test_suite); + +static struct kunit_case atcphy_t8132_pm_test_cases[] = { + KUNIT_CASE(atcphy_test_t8132_rpm_busy), + KUNIT_CASE(atcphy_test_t8132_rpm_hold), + KUNIT_CASE(atcphy_test_t8132_restore_idle), + KUNIT_CASE(atcphy_test_t8132_restore_mode), + {} +}; + +static struct kunit_suite atcphy_t8132_pm_test_suite = { + .name = "phy-apple-atc-t8132-pm", + .test_cases = atcphy_t8132_pm_test_cases, +}; + +kunit_test_suite(atcphy_t8132_pm_test_suite); diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 21d3d79f75b43..09b2f86cff0a3 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -55,6 +55,9 @@ #include "atc-t8122-dp.h" #include "atc-tunnel.h" +/* Runtime-suspend delay, which covers the mux, orientation and reset calls of a plug */ +#define ATCPHY_AUTOSUSPEND_MS 2000 + #define AUSPLL_FSM_CTRL 0x1014 #define AUSPLL_APB_CMD_OVERRIDE 0x2000 @@ -657,8 +660,13 @@ struct apple_atcphy; * it; the lock handshake that starts the next PIPE change is * only acknowledged while it runs * @restore_after_pd_off: The PHY's power domain may be switched off in system - * sleep, which resets the block; bring the tracked - * state back once the domain is on again + * sleep, or at runtime with @idle_power_off, which + * resets the block; bring the tracked state back once + * the domain is on again + * @idle_power_off: Let an idle PHY runtime-suspend, so that its power domain + * can also drop while the system runs, see atcphy_rpm_get(). + * Only for PHYs without DisplayPort, whose phy ops and tunnel + * calls take no runtime PM reference */ struct atcphy_hw { void (*configure_post_tunable)(struct apple_atcphy *, enum atcphy_mode); @@ -677,6 +685,7 @@ struct atcphy_hw { bool park_pipe_unlocked; bool park_dummy_phy; bool restore_after_pd_off; + bool idle_power_off; }; enum atcphy_t8132_bank { @@ -759,6 +768,9 @@ enum atcphy_t8132_bank { * @pd_nb: Power domain notifier, registered with restore_after_pd_off * @pd_was_off: The power domain was switched off since the state was last * brought back; set from the notifier, cleared under @lock + * @idle_rpm: An idle PHY runtime-suspends, see atcphy_rpm_get() + * @rpm_held: A runtime PM reference is held while busy, see atcphy_rpm_busy(); + * under @lock * @regs: Memory-mapped registers * @regs.core: Core registers * @regs.axi2af: AXI to Apple Fabric interface registers @@ -840,6 +852,8 @@ struct apple_atcphy { struct notifier_block pd_nb; bool pd_was_off; + bool idle_rpm; + bool rpm_held; bool tunnel_dual_stream; bool tunnel_routes_present; @@ -3945,6 +3959,73 @@ static int atcphy_configure(struct apple_atcphy *atcphy, enum atcphy_mode mode) static void _atcphy_dwc3_reset_assert(struct apple_atcphy *atcphy); +/* + * On T8132 an idle PHY lets its power domain drop. That domain is the USB + * controller's, which also holds the PIPE handler: the PHY writes it to drive + * the controller's reset and to route the PIPE, also while the Type-C mux + * changes mode before the controller has resumed. So every operation that can + * reach the hardware powers the device first, and a reference stays held while + * the PHY runs anything, see atcphy_rpm_busy(). A running controller keeps the + * shared domain on by itself. + */ +static int atcphy_rpm_get(struct apple_atcphy *atcphy) +{ + if (!atcphy->idle_rpm) + return 0; + return pm_runtime_resume_and_get(atcphy->dev); +} + +/* + * What a power-down would lose: a mode or a host, a PIPE away from its parked + * dummy backend or between backends, or on T8132 a common-power epoch, which + * only its own stop may end. Without these the PHY is in the baseline that + * atcphy_restore_after_pd_off() sets up again. + */ +static bool atcphy_rpm_busy(const struct apple_atcphy *atcphy) +{ + return atcphy->mode != APPLE_ATCPHY_MODE_OFF || atcphy->host_active || + atcphy->pipe_pending || atcphy->pipe_state != ATCPHY_PIPEHANDLER_STATE_DUMMY || + atcphy->t8132_common_owned; +} + +/* Take or drop the reference held while busy; the device must be on. */ +static void atcphy_rpm_sync(struct apple_atcphy *atcphy) +{ + bool busy; + + if (!atcphy->idle_rpm) + return; + guard(mutex)(&atcphy->lock); + busy = atcphy_rpm_busy(atcphy); + if (busy == atcphy->rpm_held) + return; + if (busy) + pm_runtime_get_noresume(atcphy->dev); + else + pm_runtime_put_noidle(atcphy->dev); + atcphy->rpm_held = busy; + dev_dbg(atcphy->dev, "%s the power domain\n", busy ? "holding" : "releasing"); +} + +static void atcphy_rpm_put(struct apple_atcphy *atcphy) +{ + if (!atcphy->idle_rpm) + return; + atcphy_rpm_sync(atcphy); + pm_runtime_put_autosuspend(atcphy->dev); +} + +/* Drop the hold of an unbound PHY or a failed probe, before runtime PM is disabled. */ +static void atcphy_rpm_release(void *data) +{ + struct apple_atcphy *atcphy = data; + + guard(mutex)(&atcphy->lock); + if (atcphy->rpm_held) + pm_runtime_put_noidle(atcphy->dev); + atcphy->rpm_held = false; +} + /* * A T8122 PHY loses its register state whenever its power domain is switched * off. In system sleep that happens to a domain that no running device holds: @@ -3965,6 +4046,9 @@ static void _atcphy_dwc3_reset_assert(struct apple_atcphy *atcphy); * resume callback, whichever comes first, so the order in which the Type-C * controller, the USB controller and the display controller resume does not * matter. + * + * On T8132 the domain also switches off at runtime, once an idle PHY and its + * USB controller have both suspended, see atcphy_rpm_get(). */ static void atcphy_restore_after_pd_off(struct apple_atcphy *atcphy) { @@ -3990,20 +4074,29 @@ static void atcphy_restore_after_pd_off(struct apple_atcphy *atcphy) atcphy->host_active = false; atcphy_usb2_power_off(atcphy); } - ret = atcphy_power_off(atcphy); - if (ret) - dev_warn(atcphy->dev, "PHY did not power down after its domain was off: %d\n", - ret); + /* As at probe, M4 acquires its common bank only on USB3 admission. */ + if (!atcphy->hw->usb2_only) { + ret = atcphy_power_off(atcphy); + if (ret) + dev_warn(atcphy->dev, + "PHY did not power down after its domain was off: %d\n", ret); + } atcphy->mode = APPLE_ATCPHY_MODE_OFF; if (atcphy->dp_only) { dev_dbg(atcphy->dev, "power domain was off, restoring mode %d\n", mode); } else { - ret = atcphy_configure_pipehandler_dummy(atcphy, false); - if (ret) - dev_warn(atcphy->dev, "Failed to park the PIPE after its domain was off: %d\n", - ret); - atcphy->pipe_state = ATCPHY_PIPEHANDLER_STATE_DUMMY; + if (atcphy->hw->usb2_only) { + /* As at probe, with the dummy PIPE clock the USB2 controller needs */ + atcphy_setup_pipehandler(atcphy); + } else { + ret = atcphy_configure_pipehandler_dummy(atcphy, false); + if (ret) + dev_warn(atcphy->dev, + "Failed to park the PIPE after its domain was off: %d\n", + ret); + atcphy->pipe_state = ATCPHY_PIPEHANDLER_STATE_DUMMY; + } dev_dbg(atcphy->dev, "power domain was off, restoring mode %d: PIPE mux=%08x lock=%08x/%08x aon=%08x nonsel=%08x\n", mode, readl(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL), @@ -4095,10 +4188,40 @@ static int atcphy_usb2_init(struct phy *phy) return 0; } +/* + * The phy core also powers the device for init and power_off; these wrappers + * also take or drop the hold for the state an operation leaves behind. + */ +static int atcphy_rpm_usb2_init(struct phy *phy) +{ + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + int ret; + + ret = atcphy_rpm_get(atcphy); + if (ret) + return ret; + ret = atcphy_usb2_init(phy); + atcphy_rpm_put(atcphy); + return ret; +} + +static int atcphy_rpm_usb2_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + int ret; + + ret = atcphy_rpm_get(atcphy); + if (ret) + return ret; + ret = atcphy_usb2_set_mode(phy, mode, submode); + atcphy_rpm_put(atcphy); + return ret; +} + static const struct phy_ops apple_atc_usb2_phy_ops = { .owner = THIS_MODULE, - .init = atcphy_usb2_init, - .set_mode = atcphy_usb2_set_mode, + .init = atcphy_rpm_usb2_init, + .set_mode = atcphy_rpm_usb2_set_mode, }; static int atcphy_usb3_power_off(struct phy *phy) @@ -4260,11 +4383,37 @@ static int atcphy_usb3_exit(struct phy *phy) return 0; } +static int atcphy_rpm_usb3_power_off(struct phy *phy) +{ + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + int ret; + + ret = atcphy_rpm_get(atcphy); + if (ret) + return ret; + ret = atcphy_usb3_power_off(phy); + atcphy_rpm_put(atcphy); + return ret; +} + +static int atcphy_rpm_usb3_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + int ret; + + ret = atcphy_rpm_get(atcphy); + if (ret) + return ret; + ret = atcphy_usb3_set_mode(phy, mode, submode); + atcphy_rpm_put(atcphy); + return ret; +} + static const struct phy_ops apple_atc_usb3_phy_ops = { .owner = THIS_MODULE, .exit = atcphy_usb3_exit, - .power_off = atcphy_usb3_power_off, - .set_mode = atcphy_usb3_set_mode, + .power_off = atcphy_rpm_usb3_power_off, + .set_mode = atcphy_rpm_usb3_set_mode, }; static int atcphy_dpphy_set_mode(struct phy *phy, enum phy_mode mode, int submode) @@ -4626,9 +4775,35 @@ static int atcphy_dwc3_reset_deassert(struct reset_controller_dev *rcdev, unsign return 0; } +static int atcphy_rpm_dwc3_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct apple_atcphy *atcphy = container_of(rcdev, struct apple_atcphy, rcdev); + int ret; + + ret = atcphy_rpm_get(atcphy); + if (ret) + return ret; + ret = atcphy_dwc3_reset_assert(rcdev, id); + atcphy_rpm_put(atcphy); + return ret; +} + +static int atcphy_rpm_dwc3_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct apple_atcphy *atcphy = container_of(rcdev, struct apple_atcphy, rcdev); + int ret; + + ret = atcphy_rpm_get(atcphy); + if (ret) + return ret; + ret = atcphy_dwc3_reset_deassert(rcdev, id); + atcphy_rpm_put(atcphy); + return ret; +} + static const struct reset_control_ops atcphy_dwc3_reset_ops = { - .assert = atcphy_dwc3_reset_assert, - .deassert = atcphy_dwc3_reset_deassert, + .assert = atcphy_rpm_dwc3_reset_assert, + .deassert = atcphy_rpm_dwc3_reset_deassert, }; static int atcphy_reset_xlate(struct reset_controller_dev *rcdev, @@ -4976,11 +5151,17 @@ static void atcphy_usb3_port_work(struct work_struct *work) struct atcphy_usb3_pm pm = {}; int ret; + ret = atcphy_rpm_get(atcphy); + if (ret) { + atcphy_usb3_port_retry(atcphy, false, ret); + return; + } ret = atcphy_usb3_pm_get(atcphy, &pm); if (!ret) { ret = atcphy_usb3_port_restore(atcphy, &pm); atcphy_usb3_pm_put(&pm); } + atcphy_rpm_put(atcphy); if (ret) atcphy_usb3_port_retry(atcphy, false, ret); } @@ -5350,7 +5531,15 @@ static int atcphy_mux_update(struct apple_atcphy *atcphy, const struct typec_mux static int atcphy_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { - return atcphy_mux_update(typec_mux_get_drvdata(mux), state); + struct apple_atcphy *atcphy = typec_mux_get_drvdata(mux); + int ret; + + ret = atcphy_rpm_get(atcphy); + if (ret) + return ret; + ret = atcphy_mux_update(atcphy, state); + atcphy_rpm_put(atcphy); + return ret; } static void atcphy_typec_mux_unregister(void *data) @@ -5853,12 +6042,13 @@ static void atcphy_pd_notifier_remove(void *data) dev_pm_genpd_remove_notifier(data); } -static void atcphy_probe_pd_notifier(struct apple_atcphy *atcphy) +/* Returns whether the PHY follows its power domain. */ +static bool atcphy_probe_pd_notifier(struct apple_atcphy *atcphy) { int ret; if (!atcphy->hw->restore_after_pd_off) - return; + return false; atcphy->pd_nb.notifier_call = atcphy_pd_notify; ret = dev_pm_genpd_add_notifier(atcphy->dev, &atcphy->pd_nb); @@ -5872,6 +6062,7 @@ static void atcphy_probe_pd_notifier(struct apple_atcphy *atcphy) dev_warn(atcphy->dev, "Cannot follow the PM domain (%d), PHY state is not restored after it was off\n", ret); + return !ret; } /* The T8103 PHY compatible is also a fallback on unqualified M1/M2 variants. */ @@ -5886,6 +6077,7 @@ static int atcphy_probe(struct platform_device *pdev) { struct apple_atcphy *atcphy; struct device *dev = &pdev->dev; + bool followed; int ret; atcphy = devm_kzalloc(&pdev->dev, sizeof(*atcphy), GFP_KERNEL); @@ -5917,9 +6109,29 @@ static int atcphy_probe(struct platform_device *pdev) atcphy->pipe_state = ATCPHY_PIPEHANDLER_STATE_DUMMY; atcphy->fixed_usb2 = atcphy_usb2_behind_fixed_hub(atcphy); atcphy->typec_mode = APPLE_ATCPHY_MODE_USB2; - atcphy_probe_pd_notifier(atcphy); + followed = atcphy_probe_pd_notifier(atcphy); - return atcphy_probe_finalize(atcphy); + /* + * Only a PHY that follows its domain may let it drop. Before the PHYs are + * created, so that they follow runtime PM too. + */ + if (atcphy->hw->idle_power_off && followed) { + pm_runtime_set_autosuspend_delay(dev, ATCPHY_AUTOSUSPEND_MS); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + ret = devm_add_action_or_reset(dev, atcphy_rpm_release, atcphy); + if (ret) + return ret; + atcphy->idle_rpm = true; + } + + ret = atcphy_probe_finalize(atcphy); + if (ret) + return ret; + atcphy_rpm_sync(atcphy); + return 0; } static int atcphy_suspend(struct device *dev) @@ -5950,6 +6162,12 @@ static int atcphy_resume(struct device *dev) guard(mutex)(&atcphy->lock); atcphy->t8132_suspended = false; + /* + * A suspended PHY holds nothing, and its domain may drop again once system + * resume completes: leave the restore to its next operation. + */ + if (atcphy->idle_rpm && pm_runtime_status_suspended(dev)) + return 0; atcphy_restore_after_pd_off(atcphy); return 0; @@ -6009,6 +6227,8 @@ static const struct atcphy_hw atcphy_hw_t8132 = { .usb2phy_sig_host = USB2PHY_SIG_HOST_T8132, .pipe_mux_clk = PIPEHANDLER_MUX_CTRL_CLK_T8103, .pipe_mux_data = PIPEHANDLER_MUX_CTRL_DATA_T8103, + .restore_after_pd_off = true, + .idle_power_off = true, }; static const struct atcphy_hw atcphy_hw_t8140 = { diff --git a/drivers/phy/apple/t6040-usb2.c b/drivers/phy/apple/t6040-usb2.c index 868d4304b2395..cb4146d91faf1 100644 --- a/drivers/phy/apple/t6040-usb2.c +++ b/drivers/phy/apple/t6040-usb2.c @@ -106,7 +106,7 @@ #define T6040_PIPEHANDLER_DUMMY_PHY_READY BIT(15) /* - * DisplayPort (PHY mode 3). Offsets are into the "core" and "cfg" windows + * DisplayPort (four-lane mode 3 and two-lane mode 4). Offsets are into the "core" and "cfg" windows * the USB3 path uses; names follow the paired AppleT6040TypeCPhy driver. */ #define T6040_CORE_AUX_CTRL 0x28000 /* ACIOPHY_AUX_TOP_BLK_AUX_CTRL */ @@ -237,14 +237,20 @@ struct t6040_usb2_phy { struct completion app_reply; enum typec_orientation orientation; bool usb3_up; /* tuned by the coprocessor this power cycle */ + bool usb3_on; /* dwc3 holds the USB3 phy powered on */ + bool usb3_pipe; /* dwc3 asked for the PIPE switch this power cycle */ + bool usb3_lent; /* DP mode took the lanes of this Type-C port */ bool pipe_switched; /* the controller's PIPE runs on this PHY */ - /* DisplayPort (PHY mode 3); dp_ok is false when the DT lacks a DP route */ + /* DisplayPort; dp_ok is false when the DT lacks a DP route */ struct phy *dp; struct typec_mux_dev *mux; bool dp_ok; bool dp_up; /* DP mode is programmed on the PHY */ - bool dp_alt; /* the Type-C port is in DP alt mode, pin assignment C/E */ + bool dp_alt; /* the Type-C port is in DP alt mode */ + bool dp_two_lanes; /* pin D keeps USB3 on the other pair */ + bool dp_mode_two_lanes; /* latched until DP power_off, even after mux exit */ + bool dp_mode_flip; bool dp_flip; /* plug orientation reversed for the DP lanes */ int dp_rate; /* link rate index of the running link sequence, -1 = off */ }; @@ -882,15 +888,19 @@ static void t6040_usb3_pipe_park(struct t6040_usb2_phy *tphy) * its PIPE on the dummy PHY. A failure here leaves the port at USB2, so it is * logged and not returned: the USB2 PHY and the core must still come up. */ -static int t6040_usb3_power_on(struct phy *phy) +static void t6040_usb3_up_locked(struct t6040_usb2_phy *tphy) { - struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); bool swap; int ret; - guard(mutex)(&tphy->lock); + lockdep_assert_held(&tphy->lock); if (!tphy->usb3_ok || tphy->usb3_up) - return 0; + return; + /* DP mode owns all four lanes; USB3 comes back in t6040_usb3_reclaim_locked(). */ + if (tphy->dp_up) { + dev_dbg(tphy->dev, "USB3: lanes held by DP, USB2 only for now\n"); + return; + } switch (tphy->orientation) { case TYPEC_ORIENTATION_NORMAL: @@ -901,7 +911,7 @@ static int t6040_usb3_power_on(struct phy *phy) break; default: dev_info(tphy->dev, "USB3: no plug orientation, USB2 only\n"); - return 0; + return; } /* PIPE_HANDLER_DEFAULT: the dummy PIPE clock the controller keeps using at USB2 */ @@ -918,7 +928,7 @@ static int t6040_usb3_power_on(struct phy *phy) tphy->usb3_up = true; dev_dbg(tphy->dev, "USB3 PHY up, lane %d\n", swap); - return 0; + return; usb2_only: /* power_off skips a PHY that never came up: undo here, so the next attach starts clean */ @@ -926,6 +936,15 @@ static int t6040_usb3_power_on(struct phy *phy) sleep: t6040_usb3_sleep(tphy); dev_warn(tphy->dev, "USB3 PHY bring-up failed (%d), USB2 only\n", ret); +} + +static int t6040_usb3_power_on(struct phy *phy) +{ + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + + guard(mutex)(&tphy->lock); + tphy->usb3_on = true; + t6040_usb3_up_locked(tphy); return 0; } @@ -936,6 +955,9 @@ static void t6040_usb3_down_locked(struct t6040_usb2_phy *tphy) t6040_usb3_pipe_park(tphy); tphy->pipe_switched = false; } + /* DWC3 can drop its role before the DP fabric has released pin D. */ + if (tphy->dp_up && tphy->dp_mode_two_lanes) + return; if (tphy->usb3_up) { u32 uc = readl(t6040_core(tphy) + T6040_CORE_UC_CTRL); @@ -955,6 +977,8 @@ static int t6040_usb3_power_off(struct phy *phy) struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); guard(mutex)(&tphy->lock); + tphy->usb3_on = false; + tphy->usb3_pipe = false; t6040_usb3_down_locked(tphy); return 0; } @@ -972,6 +996,7 @@ static int t6040_usb3_set_mode(struct phy *phy, enum phy_mode mode, int submode) return -EOPNOTSUPP; guard(mutex)(&tphy->lock); + tphy->usb3_pipe = true; if (!tphy->usb3_up || tphy->pipe_switched) return 0; t6040_usb3_pipe_switch(tphy); @@ -1014,8 +1039,8 @@ static void t6040_usb3_switch_unregister(void *sw) * link rate through phy_configure(): the per-rate link sequence is the T6050 * fixed-DP table (atc-t6050-dp-data.h; the T6050 ADT names its PHY * "atc-phy,t6050", "atc-phy,t6040"), followed by the T6040 DP clock switch in - * the "cfg" window. Pin assignment D (two DP lanes next to USB3) is not - * supported. + * the "cfg" window. Pin assignment D shares USB3's running PHY and uses + * the two-lane group link sequences below. */ /* The fixed DPTX tunable writes (apply_dptx_tunables, shared with USB3). */ @@ -1253,6 +1278,7 @@ struct atc_rate_op { }; #include "atc-t6050-dp-data.h" +#include "atc-t6040-dp2-data.h" static const struct { u8 bank; u32 offset; } t6040_dp_core_banks[] = { {2, 0}, {3, 0x400}, {4, 0x800}, {5, 0xa00}, {6, 0x1000}, @@ -1306,12 +1332,29 @@ static int t6040_dp_seq_step(struct t6040_usb2_phy *tphy, const struct atc_op *s /* Take the link down if it is up, then bring it up at rate index @idx (-1: off). */ static int t6040_dp_set_rate(struct t6040_usb2_phy *tphy, int idx) { + const struct atc_op *off = dp_link_off; + const struct atc_rate_op *on = dp_link_on; + size_t nr_off = ARRAY_SIZE(dp_link_off); + size_t nr_on = ARRAY_SIZE(dp_link_on); size_t i; int ret; + if (tphy->dp_mode_two_lanes) { + if (tphy->dp_mode_flip) { + off = dp_group0_off; + on = dp_group0_on; + nr_off = ARRAY_SIZE(dp_group0_off); + nr_on = ARRAY_SIZE(dp_group0_on); + } else { + off = dp_group1_off; + on = dp_group1_on; + nr_off = ARRAY_SIZE(dp_group1_off); + nr_on = ARRAY_SIZE(dp_group1_on); + } + } if (tphy->dp_rate >= 0) { - for (i = 0; i < ARRAY_SIZE(dp_link_off); i++) { - ret = t6040_dp_seq_step(tphy, &dp_link_off[i]); + for (i = 0; i < nr_off; i++) { + ret = t6040_dp_seq_step(tphy, &off[i]); if (ret) return ret; } @@ -1319,12 +1362,12 @@ static int t6040_dp_set_rate(struct t6040_usb2_phy *tphy, int idx) } if (idx < 0) return 0; - for (i = 0; i < ARRAY_SIZE(dp_link_on); i++) { - struct atc_op op = dp_link_on[i].op; + for (i = 0; i < nr_on; i++) { + struct atc_op op = on[i].op; - if (!(dp_link_on[i].rates & BIT(idx))) + if (!(on[i].rates & BIT(idx))) continue; - op.value = dp_link_on[i].values[idx]; + op.value = on[i].values[idx]; ret = t6040_dp_seq_step(tphy, &op); if (ret) return ret; @@ -1340,7 +1383,8 @@ static int t6040_dp_set_rate(struct t6040_usb2_phy *tphy, int idx) /* Drive settings of the T6050 sequence: lanes 0/1 on PMA group 1, 2/3 on group 0. */ static void t6040_dp_drive(struct t6040_usb2_phy *tphy, unsigned int lane, u32 preset) { - unsigned int group = lane < 2 ? 1 : 0; + unsigned int group = (lane < 2 ? 1 : 0) ^ + (tphy->dp_mode_two_lanes && tphy->dp_mode_flip); bool tx = lane & 1; void __iomem *base = t6040_dp_bank(tphy, (tx ? 21 : 20) + 9 * group); u32 ctrl = ((preset >> 2) & 0xf) | ((preset & 3) << 14); @@ -1353,6 +1397,31 @@ static void t6040_dp_drive(struct t6040_usb2_phy *tphy, unsigned int lane, u32 p t6040_usb2_mask32(base + (tx ? 0x100 : 0xa8), 0x7ffff, eq); } +/* + * Give the lanes back to USB3 once the DP session is over and the port has + * left DP alt mode while dwc3 still holds the USB3 phy on: the USB3 bring-up + * programs the PHY afresh, as it does after DP on the next attach, and the + * PIPE returns to the PHY if the controller had asked for it. Called when + * the DP phy powers off and when the mux leaves DP, as either can come last. + * tipd drops the USB role before it leaves DP alt mode, so on an unplug or a + * DP mode change dwc3 powers the USB3 phy off first and this does nothing. + */ +static void t6040_usb3_reclaim_locked(struct t6040_usb2_phy *tphy) +{ + lockdep_assert_held(&tphy->lock); + if (!tphy->usb3_lent || tphy->dp_up || tphy->dp_alt) + return; + tphy->usb3_lent = false; + if (!tphy->usb3_on || tphy->usb3_up) + return; + t6040_usb3_up_locked(tphy); + if (tphy->usb3_up && tphy->usb3_pipe && !tphy->pipe_switched) { + t6040_usb3_pipe_switch(tphy); + tphy->pipe_switched = true; + } + dev_info(tphy->dev, "USB3 %s after DP\n", tphy->usb3_up ? "back" : "not restored"); +} + /* ---- DP phy_ops ---- */ static int t6040_dp_power_on(struct phy *phy) @@ -1363,13 +1432,29 @@ static int t6040_dp_power_on(struct phy *phy) guard(mutex)(&tphy->lock); if (!tphy->dp_ok || tphy->dp_up) return 0; - /* - * A Type-C PHY in DP alt mode (pin assignment C/E) hands all four - * lanes to DP: aciophy_phy_shutdown of the USB3 side first, then the - * same mode-3 init as the fixed DP PHY. - */ - if (!t6040_dp_only(tphy)) + /* Pin D shares the running PHY with USB3. Keep its PIPE and firmware. */ + if (tphy->dp_two_lanes) { + if (!tphy->usb3_ok || !tphy->dp_alt) + return -ENOLINK; + if (!tphy->usb3_up) + t6040_usb3_up_locked(tphy); + if (!tphy->usb3_up) + return -ENOLINK; + /* USB3 startup already applied these DPTX tunables before firmware. */ + t6040_dp_aux_init(tphy); + tphy->dp_rate = -1; + tphy->dp_mode_two_lanes = true; + tphy->dp_mode_flip = tphy->dp_flip; + tphy->dp_up = true; + dev_info(tphy->dev, "DP PHY pin D: two lanes, USB3 retained, %s orientation\n", + tphy->dp_flip ? "reverse" : "normal"); + return 0; + } + /* C/E and the fixed HDMI PHY hand all four lanes to DP. */ + if (!t6040_dp_only(tphy)) { t6040_usb3_down_locked(tphy); + tphy->usb3_lent = true; + } ret = t6040_dp_phy_init(tphy, tphy->dp_flip); if (ret) { dev_err(tphy->dev, "DP PHY init failed: %d\n", ret); @@ -1377,6 +1462,7 @@ static int t6040_dp_power_on(struct phy *phy) } t6040_dp_mode_setup(tphy, tphy->dp_flip); tphy->dp_rate = -1; + tphy->dp_mode_two_lanes = false; tphy->dp_up = true; dev_dbg(tphy->dev, "DP PHY mode set, lane map %s\n", tphy->dp_flip ? "reversed" : "normal"); @@ -1386,21 +1472,33 @@ static int t6040_dp_power_on(struct phy *phy) static int t6040_dp_power_off(struct phy *phy) { struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + int ret = 0; guard(mutex)(&tphy->lock); + if (tphy->dp_up && tphy->dp_mode_two_lanes) { + ret = t6040_dp_set_rate(tphy, -1); + if (ret) + return ret; + } tphy->dp_up = false; - return 0; + if (tphy->dp_mode_two_lanes && !tphy->usb3_on) + t6040_usb3_down_locked(tphy); + tphy->dp_mode_two_lanes = false; + t6040_usb3_reclaim_locked(tphy); + return ret; } -/* GET_MAX_LANE_COUNT: DP on all four lanes in mode 3. */ +/* GET_MAX_LANE_COUNT follows the negotiated pin assignment. */ static int t6040_dp_validate(struct phy *phy, enum phy_mode mode, int submode, union phy_configure_opts *opts_) { + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); struct phy_configure_opts_dp *opts = &opts_->dp; if (mode != PHY_MODE_DP) return -EINVAL; - opts->lanes = 4; + guard(mutex)(&tphy->lock); + opts->lanes = tphy->dp_two_lanes ? 2 : 4; return 0; } @@ -1414,6 +1512,9 @@ static int t6040_dp_configure(struct phy *phy, union phy_configure_opts *opts_) guard(mutex)(&tphy->lock); if (!tphy->dp_up) return -EINVAL; + if ((opts->set_lanes || opts->set_voltages) && + opts->lanes > (tphy->dp_mode_two_lanes ? 2 : 4)) + return -EINVAL; if (opts->set_rate) { switch (opts->link_rate) { @@ -1444,7 +1545,7 @@ static int t6040_dp_configure(struct phy *phy, union phy_configure_opts *opts_) if (opts->set_voltages && tphy->dp_rate >= 0) { unsigned int i; - for (i = 0; i < opts->lanes && i < 4; i++) { + for (i = 0; i < opts->lanes; i++) { unsigned int v = min_t(unsigned int, opts->voltage[i], 3); unsigned int pe = min_t(unsigned int, opts->pre[i], 3); @@ -1483,10 +1584,9 @@ static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *s tphy->dp_flip = (tphy->orientation == TYPEC_ORIENTATION_REVERSE); /* * Latch the DP alt mode state for the DCP's Type-C route. Pin - * assignments C and E put DP on all four lanes (PHY mode 3, as the - * fixed DP route); D (two DP lanes next to USB3, PHY mode 4 with the - * coprocessor) is not supported. The DP lanes themselves are brought - * up by the DCP through phy_power_on()/phy_configure() on the DP phy. + * assignments C and E put DP on all four lanes. D leaves the USB3 + * pair with its running coprocessor and gives DP the other pair. + * The DCP brings the DP lanes up through phy_power_on()/phy_configure(). */ if (dp) { switch (state->mode) { @@ -1495,29 +1595,22 @@ static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *s alt = true; break; case TYPEC_DP_STATE_D: - dev_warn_once(tphy->dev, "Type-C mux: DP pin assignment D (two lanes next to USB3) not supported\n"); + alt = true; break; default: break; } } tphy->dp_alt = alt; + tphy->dp_two_lanes = alt && state->mode == TYPEC_DP_STATE_D; dev_dbg(tphy->dev, "Type-C mux: %s state %lu -> DP alt mode %d, lane map %s\n", dp ? "DP" : "non-DP", state->mode, alt, tphy->dp_flip ? "reversed" : "normal"); - if (alt && tphy->dp_up) + if (alt && tphy->dp_up && !tphy->dp_mode_two_lanes && !tphy->dp_two_lanes) t6040_dp_xbar_config(tphy, tphy->dp_flip); + t6040_usb3_reclaim_locked(tphy); return 0; } -/* For the DCP's Type-C route: is this PHY's port in DP alt mode (C/E)? */ -bool apple_t6040_phy_dp_alt(struct phy *phy) -{ - struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); - - return tphy && tphy->dp_alt; -} -EXPORT_SYMBOL_GPL(apple_t6040_phy_dp_alt); - static void t6040_dp_mux_unregister(void *mux) { typec_mux_unregister(mux); diff --git a/drivers/pmdomain/apple/pmp-report-test.c b/drivers/pmdomain/apple/pmp-report-test.c index e04a91d339f23..d99ece1ac4b6f 100644 --- a/drivers/pmdomain/apple/pmp-report-test.c +++ b/drivers/pmdomain/apple/pmp-report-test.c @@ -276,4 +276,5 @@ static struct kunit_suite apple_pmp_report_suite = { }; kunit_test_suite(apple_pmp_report_suite); +MODULE_DESCRIPTION("KUnit tests for Apple PMP report validation"); MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/pmdomain/apple/pmp-report.c b/drivers/pmdomain/apple/pmp-report.c index 823669e27ad74..aaa38c06daaaf 100644 --- a/drivers/pmdomain/apple/pmp-report.c +++ b/drivers/pmdomain/apple/pmp-report.c @@ -1473,6 +1473,13 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) ent->genpd.power_on = apple_pmp_report_entry_power_on; ent->genpd.power_off = apple_pmp_report_entry_power_off; + /* + * An entry that is the parent of PMGR power states (which are IRQ safe) + * must be IRQ safe too. Setting the state takes a spinlock and polls + * the acknowledgment atomically, so it may be called with IRQs off. + */ + if (of_property_read_bool(node, "apple,irq-safe")) + ent->genpd.flags |= GENPD_FLAG_IRQ_SAFE; ret = pm_genpd_init(&ent->genpd, NULL, true); if (ret) { diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index 9033a75140946..cbd8a0f0d430a 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -269,8 +269,10 @@ config APPLE_T6041_DISPLAY_GATE boot loader has handed the running display coprocessor over. This option enables those nodes before devices are created, only when the boot loader has published a complete T6041 handoff for the - 26.6.2 display firmware. Without it, or with nomodeset, it does - nothing. + 26.6.2 display firmware. It also powers up the J616s external + display chain (HDMI, Type-C DisplayPort) once the boot loader has + given the external display processors their firmware memory. + Without these, or with nomodeset, it does nothing. Say 'y' if you want the native display on M4 Pro MacBook Pros. diff --git a/drivers/soc/apple/bio.rs b/drivers/soc/apple/bio.rs index 6b4e1edbe2966..573830b7973d0 100644 --- a/drivers/soc/apple/bio.rs +++ b/drivers/soc/apple/bio.rs @@ -6,7 +6,7 @@ use kernel::ioctl::{_IO, _IOR, _IOW, _IOWR}; use kernel::prelude::*; use kernel::uaccess::{UserPtr, UserSlice}; -pub(crate) const IFACE_VERSION: u32 = 4; +pub(crate) const IFACE_VERSION: u32 = 5; pub(crate) const ENROL_USER_ID: i32 = 1000; @@ -100,10 +100,35 @@ struct List { #[repr(C)] struct EnrolStart { flags: u32, + account_uid: u32, + finger: u32, reserved: u32, label: [u8; LABEL_LEN], } +#[repr(C)] +pub(crate) struct EnclaveList { + pub(crate) count: u32, + pub(crate) reserved: u32, + pub(crate) uuid: [[u8; UUID_LEN]; MAX_IDENTITIES], +} + +impl EnclaveList { + pub(crate) fn new() -> Self { + Self { count: 0, reserved: 0, uuid: [[0; UUID_LEN]; MAX_IDENTITIES] } + } + + pub(crate) fn push(&mut self, uuid: [u8; UUID_LEN]) -> Result<()> { + let at = self.count as usize; + if at >= MAX_IDENTITIES { + return Err(EOVERFLOW); + } + self.uuid[at] = uuid; + self.count += 1; + Ok(()) + } +} + #[repr(C)] #[derive(Default)] struct EnrolPoll { @@ -136,8 +161,8 @@ struct VerifyPoll { } #[repr(C)] -struct Delete { - uuid: [u8; UUID_LEN], +pub(crate) struct Delete { + pub(crate) uuid: [u8; UUID_LEN], } pub(crate) const ATTEST_CHALLENGE_LEN: usize = 32; @@ -162,7 +187,8 @@ pub(crate) struct Attest { static_assert!(core::mem::size_of::() == 144); static_assert!(core::mem::size_of::() == 32); static_assert!(core::mem::size_of::() == 4616); -static_assert!(core::mem::size_of::() == 136); +static_assert!(core::mem::size_of::() == 144); +static_assert!(core::mem::size_of::() == 520); static_assert!(core::mem::size_of::() == 40); static_assert!(core::mem::size_of::() == 40); static_assert!(core::mem::size_of::() == 72); @@ -182,7 +208,7 @@ macro_rules! ioctl_pod { }; } ioctl_pod! { - to_user: Info, List, EnrolPoll, VerifyPoll, Attest; + to_user: Info, List, EnclaveList, EnrolPoll, VerifyPoll, Attest; from_user: EnrolStart, VerifyStart, Delete, Attest } @@ -198,6 +224,9 @@ const IOC_CANCEL: u32 = _IO(MAGIC, 0x07); const IOC_DELETE: u32 = _IOW::(MAGIC, 0x08); const IOC_DELETE_ALL: u32 = _IO(MAGIC, 0x09); pub(crate) const IOC_ATTEST: u32 = _IOWR::(MAGIC, 0x0a); +pub(crate) const IOC_LIST_ENCLAVE: u32 = _IOR::(MAGIC, 0x0b); +pub(crate) const IOC_DELETE_ORPHAN: u32 = _IOW::(MAGIC, 0x0c); +pub(crate) const IOC_PREPARE_ENROL: u32 = _IOW::(MAGIC, 0x0d); pub(crate) struct MatchEvidence { identity: [u8; UUID_LEN], @@ -338,6 +367,10 @@ impl IdentityIndex { self.save(store) } + pub(crate) fn can_persist_next(&self, store: &Store) -> bool { + store.can_write(&identity_key(), 8 + (self.entries.len() + 1) * (UUID_LEN + LABEL_LEN)) + } + pub(crate) fn identity_v1_for( &self, uuid: &[u8; UUID_LEN], @@ -481,7 +514,7 @@ pub(crate) struct Handled { pub(crate) delete_identities: KVec, } -fn ok() -> Result { +pub(crate) fn ok() -> Result { Ok(Handled { ret: 0, wake: false, @@ -492,7 +525,7 @@ fn ok() -> Result { }) } -fn require_admin() -> Result<()> { +pub(crate) fn require_admin() -> Result<()> { if crate::shim::capable_admin() { Ok(()) } else { @@ -500,6 +533,12 @@ fn require_admin() -> Result<()> { } } +pub(crate) fn orphan_delete(identity: crate::sbio::IdentityV1) -> Result { + let mut handled = ok()?; + handled.delete_identity = Some(identity); + Ok(handled) +} + fn require_sensor(present: bool) -> Result<()> { if present { Ok(()) @@ -567,22 +606,85 @@ fn list(ctx: &mut Context<'_>, user: UserPtr) -> Result { ok() } +fn validate_enrol_request(request: &EnrolStart) -> Result<()> { + // NSS account resolution occurs in libfprint. This ABI requires its + // resolved uid, the named finger and the generated user label. + if request.flags != 0 || request.reserved != 0 + || request.account_uid == u32::MAX || !(1..=10).contains(&request.finger) + || !request.label.starts_with(b"FP1-") + || request.label[12] != b'-' || request.label[14] != b'-' + || request.label[23] != b'-' || request.label[24] == 0 + || request.label[13] != b"123456789A"[request.finger as usize - 1] + || !request.label.contains(&0) + { + return Err(EINVAL); + } + Ok(()) +} + +pub(crate) fn validate_enrol_start_arg(arg: usize) -> Result<()> { + let user = UserPtr::from_addr(arg); + let request: EnrolStart = UserSlice::new(user, core::mem::size_of::()) + .reader().read()?; + validate_enrol_request(&request) +} + +#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] +#[::kernel::macros::kunit_tests(apple_sep_enrol_request)] +mod enrol_request_tests { + use super::*; + + fn valid() -> EnrolStart { + let mut request = EnrolStart { + flags: 0, account_uid: 1000, finger: 7, reserved: 0, + label: [0; LABEL_LEN], + }; + request.label[..28].copy_from_slice(b"FP1-20261027-7-12345678-user"); + request + } + + #[test] + fn resolved_account_and_known_finger() { + assert!(validate_enrol_request(&valid()).is_ok()); + } + + #[test] + fn missing_uid_is_refused() { + let mut request = valid(); + request.account_uid = u32::MAX; + assert!(validate_enrol_request(&request).is_err()); + } + + #[test] + fn unknown_or_mismatched_finger_is_refused() { + let mut request = valid(); + request.finger = 0; + assert!(validate_enrol_request(&request).is_err()); + request.finger = 6; + assert!(validate_enrol_request(&request).is_err()); + } + + #[test] + fn missing_username_is_refused() { + let mut request = valid(); + request.label[24] = 0; + assert!(validate_enrol_request(&request).is_err()); + } +} + fn enrol_start(ctx: &mut Context<'_>, user: UserPtr) -> Result { require_admin()?; let request: EnrolStart = UserSlice::new(user, core::mem::size_of::()) .reader() .read()?; - if request.flags != 0 || request.reserved != 0 { - return Err(EINVAL); - } + validate_enrol_request(&request)?; require_sensor(ctx.sensor_present)?; if ctx.session.worker_epoch.is_some() || !ctx.session.op.may_start() { return Err(EBUSY); } - if matches!(ctx.live_identity_count, Some(count) if count >= SEP_USER_CAPACITY) - || ctx.index.total() >= MAX_IDENTITIES - { + let count = ctx.live_identity_count.ok_or(EIO)?; + if count >= SEP_USER_CAPACITY || ctx.index.total() >= MAX_IDENTITIES { return Err(ENOSPC); } @@ -960,6 +1062,10 @@ pub(crate) fn starts_capture(cmd: u32) -> bool { cmd == IOC_ENROL_START || cmd == IOC_VERIFY_START } +pub(crate) fn idle_for_mutation(session: &Session) -> bool { + session.worker_epoch.is_none() && session.op.may_start() +} + fn delete(ctx: &mut Context<'_>, user: UserPtr) -> Result { require_admin()?; if !ctx.session.op.may_start() { diff --git a/drivers/soc/apple/catacomb.rs b/drivers/soc/apple/catacomb.rs index 8b18efb4ead77..d71b415fcb321 100644 --- a/drivers/soc/apple/catacomb.rs +++ b/drivers/soc/apple/catacomb.rs @@ -28,6 +28,24 @@ pub(crate) fn is_kind(kind: u8) -> bool { path_for(kind).is_some() } +pub(crate) fn writable(kind: u8) -> bool { + path_for(kind) + .and_then(|path| shim::StoreFile::open_writable_existing(path).ok()) + .is_some() +} + +/// Reserve a missing Linux owner file before SELECT_CONTEXT changes SEP state. +/// A corrupt existing file is never truncated here: it is failure evidence. +pub(crate) fn prepare_missing_owner() -> Result<()> { + let path = path_for(crate::PRIVATE_TYPE_CATACOMB_OWNER).ok_or(EINVAL)?; + let (file, created) = shim::StoreFile::open_host(path)?; + if !created { + return Err(ENODATA); + } + file.sync()?; + file.sync_parent() +} + pub(crate) fn write(kind: u8, blob: &[u8]) -> Result<()> { let path = path_for(kind).ok_or(EINVAL)?; if blob.len() > MAX_CATACOMB { diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index cc7a620c1c772..2620532424cf2 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -21,10 +21,17 @@ */ #include +#include +#include +#include +#include +#include +#include #include #include #include #include +#include #include #include #include @@ -48,6 +55,13 @@ enum apple_clpc_rail { #define APPLE_CLPC_BUSY_PCT 50 /* Time a rail keeps its last request after demand stops */ #define APPLE_CLPC_HOLD_MS 32 +/* + * Time the GPU floor stays released after the PMP last limited the DCS. Under + * sustained heavy rendering the PMP limits the DCS again well within this, so + * the floor stays released; light submissions that follow get the floor back + * this long after the PMP stops limiting. + */ +#define APPLE_CLPC_GPU_BACKOFF_MS 100 /** * struct apple_clpc_hw - per-SoC CLPC parameters @@ -56,12 +70,22 @@ enum apple_clpc_rail { * 0 or 1 for the first * @map: state of each rail for a busy cluster below 25, 50, 75 and 100 * percent of its maximum frequency + * @gpu_demand: streaming GPU work requests @gpu + * @gpu: state of each rail for streaming GPU work + * @gpu_sustain_ms: time graphics stays powered without a break before it + * counts as streaming, see apple_clpc_gpu_streaming(); 0 counts it at once + * @pmp_backoff: release that GPU floor while the PMP, voter agent + * @pmp_agent, limits the DCS, see apple_clpc_gpu_limited() */ struct apple_clpc_hw { unsigned int rail[APPLE_CLPC_RAILS]; unsigned int sustain; u8 map[APPLE_CLPC_RAILS][APPLE_CLPC_LEVELS]; bool gpu_demand; + u8 gpu[APPLE_CLPC_RAILS]; + unsigned int gpu_sustain_ms; + bool pmp_backoff; + unsigned int pmp_agent; }; struct apple_clpc_cluster { @@ -78,6 +102,16 @@ struct apple_clpc_vote { unsigned long last_demand; }; +/* Independent lifetime: a late consumer put never dereferences a removed provider. */ +struct apple_clpc_gpu { + struct kref ref; + spinlock_t lock; + bool active; + u64 updated_ns; + u64 sequence; + struct delayed_work *wake; +}; + struct apple_clpc { struct device *dev; struct device *pmc; @@ -86,6 +120,7 @@ struct apple_clpc { /* Samples while a vote is held; idle_work defers to busy CPUs. */ struct delayed_work work; struct delayed_work idle_work; + struct mutex sample_lock; bool stopped; struct apple_clpc_cluster clusters[APPLE_CLPC_MAX_CLUSTERS]; unsigned int nr_clusters; @@ -100,9 +135,114 @@ struct apple_clpc { unsigned long update_errors; struct dentry *debugfs; struct regmap *gpu_pmgr; + struct apple_clpc_gpu *gpu; + u32 last_floor; + u32 last_reason; + u64 last_vote_ns; u32 gpu_offset; + /* Graphics powered at the last sample, since when, and that sample's time */ + bool gpu_on; + unsigned long gpu_on_since; + unsigned long gpu_sampled; + /* Whether, and when, the PMP last limited the DCS while GPU work streamed */ + bool gpu_limited; + unsigned long gpu_limited_at; }; +/* Protect publication/removal of provider drvdata, never a submission path. */ +static DEFINE_MUTEX(apple_clpc_clients_lock); + +static void apple_clpc_gpu_free(struct kref *ref) +{ + kfree(container_of(ref, struct apple_clpc_gpu, ref)); +} + +void apple_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active) +{ + unsigned long flags; + + if (!gpu) + return; + spin_lock_irqsave(&gpu->lock, flags); + /* A cold GPU publisher must not wait for a deferrable CPU-idle timer. + * Removal clears wake under this same lock before draining work. + */ + if (active && !gpu->active && gpu->wake) + mod_delayed_work(system_freezable_power_efficient_wq, gpu->wake, 0); + gpu->active = active; + gpu->updated_ns = ktime_get_mono_fast_ns(); + gpu->sequence++; + spin_unlock_irqrestore(&gpu->lock, flags); +} +EXPORT_SYMBOL_GPL(apple_clpc_gpu_set_active); + +void apple_clpc_gpu_put(struct apple_clpc_gpu *gpu) +{ + if (gpu) { + apple_clpc_gpu_set_active(gpu, false); + kref_put(&gpu->ref, apple_clpc_gpu_free); + } +} +EXPORT_SYMBOL_GPL(apple_clpc_gpu_put); + +int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) +{ + struct device_node *np; + struct platform_device *provider; + struct apple_clpc *clpc; + struct device_link *link; + int ret = -EPROBE_DEFER; + + *out = NULL; + np = of_parse_phandle(dev->of_node, "apple,clpc", 0); + if (!np) + return of_find_property(dev->of_node, "apple,clpc", NULL) ? -EINVAL : 0; + if (!of_device_is_compatible(np, "apple,t6041-clpc")) { + of_node_put(np); + return -EINVAL; + } + provider = of_find_device_by_node(np); + of_node_put(np); + if (!provider) + return ret; + link = device_link_add(dev, &provider->dev, DL_FLAG_AUTOREMOVE_CONSUMER); + if (!link) { + put_device(&provider->dev); + return -ENOMEM; + } + mutex_lock(&apple_clpc_clients_lock); + clpc = dev_get_drvdata(&provider->dev); + if (clpc && clpc->gpu && refcount_read(&clpc->gpu->ref.refcount) == 1) { + kref_get(&clpc->gpu->ref); + *out = clpc->gpu; + ret = 0; + } + mutex_unlock(&apple_clpc_clients_lock); + put_device(&provider->dev); + return ret; +} +EXPORT_SYMBOL_GPL(apple_clpc_gpu_get); + +static bool apple_clpc_gpu_active(struct apple_clpc_gpu *gpu, u64 *updated, u64 *sequence) +{ + unsigned long flags; + bool active; + + spin_lock_irqsave(&gpu->lock, flags); + active = gpu->active; + *updated = gpu->updated_ns; + *sequence = gpu->sequence; + spin_unlock_irqrestore(&gpu->lock, flags); + return active; +} + +static void apple_clpc_gpu_owner_put(void *data) +{ + struct apple_clpc_gpu *gpu = data; + + kref_put(&gpu->ref, apple_clpc_gpu_free); +} + /* cpufreq policies can appear after probe; pick up new ones as they do. */ static void apple_clpc_find_clusters(struct apple_clpc *clpc) { @@ -222,17 +362,78 @@ static void apple_clpc_queue(struct apple_clpc *clpc, bool idle) * stops; with no vote held, sampling can wait for a CPU that wakes up * anyway rather than waking an idle one every period. */ + /* A demand edge can queue work while the deferrable pass is pending. + * Keep one sampling cadence once the serialized pass settles. + */ + cancel_delayed_work(idle ? &clpc->work : &clpc->idle_work); queue_delayed_work(system_freezable_power_efficient_wq, idle ? &clpc->idle_work : &clpc->work, msecs_to_jiffies(APPLE_CLPC_SAMPLE_MS)); } +/* + * Whether the GPU streams work: graphics has stayed powered for the SoC's + * gpu_sustain_ms, with no sample in between that found it off and no gap in + * sampling long enough to have missed that. The firmware powers graphics off + * a few milliseconds after the GPU idles, so a frame-paced load such as video + * playback powers it up and down with each frame and does not count. A failed + * read counts as powered, so read errors alone do not end a stream that is + * already under way. + */ +static bool apple_clpc_gpu_streaming(struct apple_clpc *clpc, unsigned long now) +{ + bool gap = time_after(now, clpc->gpu_sampled + + msecs_to_jiffies(2 * APPLE_CLPC_SAMPLE_MS)); + u32 status; + int err; + + clpc->gpu_sampled = now; + err = regmap_read(clpc->gpu_pmgr, clpc->gpu_offset, &status); + if (!err && (status & GENMASK(7, 4)) != GENMASK(7, 4)) { + clpc->gpu_on = false; + return false; + } + + if (!clpc->gpu_on || gap) + clpc->gpu_on_since = now; + clpc->gpu_on = true; + return time_after_eq(now, clpc->gpu_on_since + + msecs_to_jiffies(clpc->hw->gpu_sustain_ms)); +} + +/* + * Whether the PMP holds the GPU to a power budget: while GPU work streams, it + * votes the DCS below the top state, which it does under sustained heavy + * rendering but not for streams of light submissions. The GPU floor would + * then only take power and thermal headroom from the GPU, so it stays + * released for GPU_BACKOFF_MS after the PMP last did so. + */ +static bool apple_clpc_gpu_limited(struct apple_clpc *clpc, unsigned long now) +{ + unsigned int rail = clpc->hw->rail[APPLE_CLPC_DCS]; + unsigned int dcs; + + if (!clpc->hw->pmp_backoff) + return false; + + dcs = (apple_pmc_floor_read(clpc->pmc, clpc->hw->pmp_agent) >> + APPLE_PMC_RAIL_SHIFT(rail)) & 0xf; + if (dcs < clpc->hw->gpu[APPLE_CLPC_DCS]) { + clpc->gpu_limited = true; + clpc->gpu_limited_at = now; + } + return clpc->gpu_limited && + time_before(now, clpc->gpu_limited_at + + msecs_to_jiffies(APPLE_CLPC_GPU_BACKOFF_MS)); +} + static void apple_clpc_sample(struct apple_clpc *clpc) { unsigned int demand[APPLE_CLPC_RAILS] = {}; unsigned int state[APPLE_CLPC_RAILS]; unsigned long now = jiffies; unsigned int i, rail; + u32 reason = 0, floor; clpc->samples++; if (!cpumask_equal(clpc->covered, cpu_possible_mask) && @@ -248,39 +449,62 @@ static void apple_clpc_sample(struct apple_clpc *clpc) for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) demand[rail] = max(demand[rail], cl->request[rail]); } - if (clpc->gpu_pmgr) { - u32 status; + for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) + if (demand[rail]) + reason |= 1; + if (clpc->gpu && !clpc->hw->gpu_demand) { + u64 updated, sequence; - /* Keep the qualified floor while graphics is powered. A failed - * read must not silently starve a running graphics consumer. - */ - if (regmap_read(clpc->gpu_pmgr, clpc->gpu_offset, &status) || - (status & GENMASK(7, 4)) == GENMASK(7, 4)) + if (apple_clpc_gpu_active(clpc->gpu, &updated, &sequence)) { + reason |= 2; for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) - demand[rail] = clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]; + demand[rail] = max(demand[rail], + clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]); + } + } + if (clpc->gpu_pmgr && apple_clpc_gpu_streaming(clpc, now) && + !apple_clpc_gpu_limited(clpc, now)) { + reason |= 4; + for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) + demand[rail] = max(demand[rail], clpc->hw->gpu[rail]); } for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) state[rail] = apple_clpc_vote(&clpc->vote[rail], demand[rail], now); - if (apple_pmc_floor_update(clpc->pmc, clpc->agent, clpc->mask, - apple_clpc_floor(clpc, state))) + floor = apple_clpc_floor(clpc, state); + for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) + if (state[rail] > demand[rail]) + reason |= 16; + clpc->last_floor = floor; + clpc->last_reason = reason; + clpc->last_vote_ns = ktime_get_mono_fast_ns(); + if (apple_pmc_floor_update(clpc->pmc, clpc->agent, clpc->mask, floor)) clpc->update_errors++; - apple_clpc_queue(clpc, !apple_clpc_floor(clpc, state)); + /* Sample on time while graphics is powered, to see it power off. */ + apple_clpc_queue(clpc, !apple_clpc_floor(clpc, state) && !clpc->gpu_on); } static void apple_clpc_work(struct work_struct *work) { - apple_clpc_sample(container_of(to_delayed_work(work), struct apple_clpc, - work)); + struct apple_clpc *clpc = container_of(to_delayed_work(work), struct apple_clpc, work); + + mutex_lock(&clpc->sample_lock); + if (!READ_ONCE(clpc->stopped)) + apple_clpc_sample(clpc); + mutex_unlock(&clpc->sample_lock); } static void apple_clpc_idle_work(struct work_struct *work) { - apple_clpc_sample(container_of(to_delayed_work(work), struct apple_clpc, - idle_work)); + struct apple_clpc *clpc = container_of(to_delayed_work(work), struct apple_clpc, idle_work); + + mutex_lock(&clpc->sample_lock); + if (!READ_ONCE(clpc->stopped)) + apple_clpc_sample(clpc); + mutex_unlock(&clpc->sample_lock); } static int apple_clpc_status_show(struct seq_file *s, void *unused) @@ -299,8 +523,27 @@ static int apple_clpc_status_show(struct seq_file *s, void *unused) clpc->hw->rail[rail]), clpc->hw->map[rail][0], clpc->hw->map[rail][1], clpc->hw->map[rail][2], clpc->hw->map[rail][3]); + seq_printf(s, "last_vote: %#x reason_bits %#x sampled_ns %llu (cpu=1 host_gpu=2 streaming_gpu=4 hold=16)\n", + READ_ONCE(clpc->last_floor), READ_ONCE(clpc->last_reason), + READ_ONCE(clpc->last_vote_ns)); + if (clpc->gpu) { + u64 updated, sequence; + bool active = apple_clpc_gpu_active(clpc->gpu, &updated, &sequence); + + seq_printf(s, "host_gpu: active %u request_ns %llu sequence %llu\n", + active, updated, sequence); + } seq_printf(s, "samples: %lu update errors: %lu\n", clpc->samples, clpc->update_errors); + if (clpc->gpu_pmgr) { + seq_printf(s, "gpu: powered %u for %u ms, pmp limited ", clpc->gpu_on, + clpc->gpu_on ? jiffies_to_msecs(jiffies - clpc->gpu_on_since) : 0); + if (clpc->gpu_limited) + seq_printf(s, "%u ms ago\n", + jiffies_to_msecs(jiffies - clpc->gpu_limited_at)); + else + seq_puts(s, "never\n"); + } for (i = 0; i < clpc->nr_clusters; i++) seq_printf(s, "cluster cpu%u: busy %u%% level %u%% -> dcs %u soc %u\n", clpc->clusters[i].policy_cpu, clpc->clusters[i].busy, @@ -346,6 +589,16 @@ static void apple_clpc_stop(void *data) { struct apple_clpc *clpc = data; + mutex_lock(&apple_clpc_clients_lock); + dev_set_drvdata(clpc->dev, NULL); + mutex_unlock(&apple_clpc_clients_lock); + if (clpc->gpu) { + unsigned long flags; + + spin_lock_irqsave(&clpc->gpu->lock, flags); + clpc->gpu->wake = NULL; + spin_unlock_irqrestore(&clpc->gpu->lock, flags); + } apple_clpc_halt(clpc); debugfs_remove_recursive(clpc->debugfs); apple_clpc_free_clusters(clpc); @@ -365,6 +618,19 @@ static int apple_clpc_probe(struct platform_device *pdev) clpc->dev = dev; clpc->pmc = dev->parent; clpc->hw = of_device_get_match_data(dev); + /* T6040 firmware keeps GFX powered for its whole lifetime. A separate + * host publication/idle input, not that supplier state, drives demand. + */ + if (of_device_is_compatible(dev->of_node, "apple,t6041-clpc")) { + clpc->gpu = kzalloc_obj(*clpc->gpu); + if (!clpc->gpu) + return -ENOMEM; + kref_init(&clpc->gpu->ref); + spin_lock_init(&clpc->gpu->lock); + ret = devm_add_action_or_reset(dev, apple_clpc_gpu_owner_put, clpc->gpu); + if (ret) + return ret; + } if (clpc->hw->gpu_demand) { struct device_node *gpu = of_parse_phandle(dev->of_node, "apple,gpu-power-state", 0); @@ -420,8 +686,11 @@ static int apple_clpc_probe(struct platform_device *pdev) clpc->mask; apple_clpc_find_clusters(clpc); clpc->next_scan = jiffies + HZ; + mutex_init(&clpc->sample_lock); INIT_DELAYED_WORK(&clpc->work, apple_clpc_work); INIT_DEFERRABLE_WORK(&clpc->idle_work, apple_clpc_idle_work); + if (clpc->gpu) + clpc->gpu->wake = &clpc->work; clpc->debugfs = debugfs_create_dir(dev_name(dev), NULL); debugfs_create_file("status", 0400, clpc->debugfs, clpc, @@ -431,7 +700,9 @@ static int apple_clpc_probe(struct platform_device *pdev) if (ret) return ret; + mutex_lock(&apple_clpc_clients_lock); platform_set_drvdata(pdev, clpc); + mutex_unlock(&apple_clpc_clients_lock); apple_clpc_queue(clpc, true); return 0; } @@ -472,28 +743,40 @@ static const struct apple_clpc_hw apple_clpc_hw_t8132 = { [APPLE_CLPC_SOC] = { 3, 3, 3, 3 }, }, .gpu_demand = true, + .gpu = { [APPLE_CLPC_DCS] = 5, [APPLE_CLPC_SOC] = 3 }, }; /* * T8140: DCS states 0-4 on rail 0, fabric states 0-3 on rail 1. A busy - * cluster raises the DCS one state per frequency quartile; only a cluster - * at its top quartile also raises the fabric. + * cluster raises the DCS one state per frequency quartile and leaves the + * fabric alone. * * GPU work needs both rails at their top states: the GPU firmware takes up * and completes every command over the fabric. With the fabric left at * state 0, light GPU submissions take 30-50% longer than at state 3, and * macOS holds the fabric at its top states while such work streams. The - * graphics power state is on only while the GPU has work (the firmware - * powers it off when idle), so the floor follows GPU activity and is - * released HOLD_MS after it stops. + * firmware powers graphics off a few milliseconds after the GPU idles, so + * graphics that stays powered for 64 ms marks such a stream. Loads + * paced by frames power it up and down with each frame instead: on J700, + * 1080p video playback and light browsing keep it powered for at most 63 ms + * at a time, and leave the floor down. The floor is released HOLD_MS after + * graphics powers off. + * + * The PMP (agent 0) votes the DCS to its top state for streams of light GPU + * submissions, and lowers it under sustained heavy rendering, which needs + * neither rail's floor; see apple_clpc_gpu_limited(). */ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { .rail = { [APPLE_CLPC_DCS] = 0, [APPLE_CLPC_SOC] = 1 }, .map = { [APPLE_CLPC_DCS] = { 1, 2, 3, 4 }, - [APPLE_CLPC_SOC] = { 0, 0, 0, 3 }, + [APPLE_CLPC_SOC] = { 0, 0, 0, 0 }, }, .gpu_demand = true, + .gpu = { [APPLE_CLPC_DCS] = 4, [APPLE_CLPC_SOC] = 3 }, + .gpu_sustain_ms = 64, + .pmp_backoff = true, + .pmp_agent = 0, }; /* @@ -508,6 +791,9 @@ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { * A cluster requests only after three busy samples in a row (12 ms). At idle * a single busy sample raised the floor about 60 % of the time, from short * bursts of background work; three in a row bring that to about 11 %. + * GPU demand bypasses the CPU streak and uses the same qualified DCS 5 / + * fabric 2 states until publication/in-flight demand ends and HOLD_MS expires. + * GFX supplier power cannot drive this input: firmware keeps it always on. */ static const struct apple_clpc_hw apple_clpc_hw_t6040 = { .rail = { [APPLE_CLPC_DCS] = 0, [APPLE_CLPC_SOC] = 1 }, @@ -516,7 +802,96 @@ static const struct apple_clpc_hw apple_clpc_hw_t6040 = { [APPLE_CLPC_SOC] = { 0, 1, 2, 2 }, }, .sustain = 3, + .gpu_demand = false, +}; + +#if IS_BUILTIN(CONFIG_KUNIT) && !defined(MODULE) +#include +#include + +static void apple_clpc_gpu_request_lifetime_test(struct kunit *test) +{ + struct apple_clpc_gpu *gpu = kzalloc_obj(*gpu); + u64 updated, sequence; + + KUNIT_ASSERT_NOT_NULL(test, gpu); + kref_init(&gpu->ref); + spin_lock_init(&gpu->lock); + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + KUNIT_EXPECT_EQ(test, sequence, 0ull); + /* Provider and one consumer own independent references. */ + kref_get(&gpu->ref); + apple_clpc_gpu_set_active(gpu, true); + KUNIT_EXPECT_TRUE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + KUNIT_EXPECT_GT(test, updated, 0ull); + KUNIT_EXPECT_EQ(test, sequence, 1ull); + apple_clpc_gpu_put(gpu); + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + KUNIT_EXPECT_EQ(test, sequence, 2ull); + kref_put(&gpu->ref, apple_clpc_gpu_free); +} + +static void apple_clpc_gpu_provider_removal_test(struct kunit *test) +{ + struct apple_clpc_gpu *gpu = kzalloc_obj(*gpu); + u64 updated, sequence; + + KUNIT_ASSERT_NOT_NULL(test, gpu); + kref_init(&gpu->ref); + spin_lock_init(&gpu->lock); + kref_get(&gpu->ref); + /* Provider removal must not leave a late consumer with freed storage. */ + kref_put(&gpu->ref, apple_clpc_gpu_free); + apple_clpc_gpu_set_active(gpu, true); + KUNIT_EXPECT_TRUE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + apple_clpc_gpu_set_active(gpu, false); + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_active(gpu, &updated, &sequence)); + apple_clpc_gpu_put(gpu); +} + +struct apple_clpc_wake_test { + struct delayed_work work; + struct completion done; +}; + +static void apple_clpc_wake_complete(struct work_struct *work) +{ + struct apple_clpc_wake_test *wake = container_of(to_delayed_work(work), + struct apple_clpc_wake_test, work); + complete(&wake->done); +} + +static void apple_clpc_gpu_cold_wake_test(struct kunit *test) +{ + struct apple_clpc_gpu *gpu = kzalloc_obj(*gpu); + struct apple_clpc_wake_test wake; + + KUNIT_ASSERT_NOT_NULL(test, gpu); + kref_init(&gpu->ref); + spin_lock_init(&gpu->lock); + INIT_DELAYED_WORK(&wake.work, apple_clpc_wake_complete); + init_completion(&wake.done); + gpu->wake = &wake.work; + apple_clpc_gpu_set_active(gpu, true); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&wake.done, HZ), 0ul); + /* Model provider withdrawal before its work storage is destroyed. */ + gpu->wake = NULL; + cancel_delayed_work_sync(&wake.work); + apple_clpc_gpu_put(gpu); +} + +static struct kunit_case apple_clpc_cases[] = { + KUNIT_CASE(apple_clpc_gpu_cold_wake_test), + KUNIT_CASE(apple_clpc_gpu_request_lifetime_test), + KUNIT_CASE(apple_clpc_gpu_provider_removal_test), + {} +}; +static struct kunit_suite apple_clpc_suite = { + .name = "apple-clpc-gpu", + .test_cases = apple_clpc_cases, }; +kunit_test_suite(apple_clpc_suite); +#endif static const struct of_device_id apple_clpc_of_match[] = { { .compatible = "apple,t8132-clpc", .data = &apple_clpc_hw_t8132 }, diff --git a/drivers/soc/apple/profile.rs b/drivers/soc/apple/profile.rs index 0ba0eb510122b..0e632c65da81f 100644 --- a/drivers/soc/apple/profile.rs +++ b/drivers/soc/apple/profile.rs @@ -9,7 +9,7 @@ //! here once. The driver reads addresses from the device tree, never scans for //! them, and never patches properties at runtime. //! -//! Five bring-up targets are modelled: +//! These bring-up targets are modelled: //! //! * `T8103` / J313 (MacBook Air, M1): the host boots the SEP with the boot //! endpoint handshake over a 0x30000 shared-memory window. @@ -24,6 +24,8 @@ //! a powered sensor, so the driver uses the warm registration path. //! * `T8122` / J613, J615 (MacBook Air, M3): the T8112 warm registration with //! the T8140 SEP DART aperture above 4 GiB. +//! * `T6040` / J616s (MacBook Pro 16", M4 Pro): iBoot boots sepOS, so the +//! driver uses the warm registration path; the sensor is the J700 part. // The boot handshake, identity source, sensor and DART fields are consumed by // the boot-endpoint, identity and sensor-transport paths. @@ -426,6 +428,28 @@ pub(crate) const T8122: PlatformProfile = PlatformProfile { ..T8112 }; +/// MacBook Pro 16" M4 Pro (J616s). The J616s ADT (26.6.2, 25G83) records a +/// pre-booted SEP (`sepfw-load-at-boot`), an `iop-sep,ascwrap-v6` SEP whose +/// DART aperture starts at the BIT(40) alias (`vm-base` 0x10000004000), and +/// the Mesa sensor J700 carries (`sensor-id` 0x3356) on spi2 at +/// `0x429108000`, 8 MHz, chip select 0. Its power is NUB GPIO 53 and its +/// data-ready line AP GPIO 116. `/defaults` carries `cpx-encryption-mode = 2`. +/// +/// A warm-registration platform like T8140, with T8140's authenticated +/// sensor session: the enclave serves this sensor only after the nonce and +/// the signed root key. Device-tree nodes remain authoritative for addresses. +pub(crate) const T6040: PlatformProfile = PlatformProfile { + name: "T6040/J616s", + sensor: SensorProfile { + controller_base: 0x4_2910_8000, + ..T8140.sensor + }, + // macOS shares this enclave and Linux has no records here yet: keep + // Linux to its own xART records from the first boot. + xart_guard: 1, + ..T8140 +}; + static_assert!(T8103.shmem_capacity == 0x30000); static_assert!(T6020.shmem_capacity == 0x40000); static_assert!(T8140.shmem_capacity == 0x40000); @@ -441,6 +465,8 @@ static_assert!(T8112.sensor.controller_base == T8103.sensor.controller_base); static_assert!(T8122.shmem_capacity == T8112.shmem_capacity); static_assert!(T8122.sensor.controller_base == 0x2a1108000); static_assert!(T8122.wide_dma_mask); +static_assert!(T6040.sensor.controller_base == 0x429108000); +static_assert!(T6040.sensor.expected_id == T8140.sensor.expected_id); /// Whether the machine root declares `compatible`. /// @@ -587,6 +613,11 @@ pub(crate) fn detect() -> Result<&'static PlatformProfile> { if machine_has(b"apple,t8122") && (machine_has(b"apple,j613") || machine_has(b"apple,j615")) { return Ok(&T8122); } + // Only the board whose ADT was read: J616s. The other T6040 Macs share + // the SoC but their sensor wiring is unchecked. + if machine_has(b"apple,t6040") && machine_has(b"apple,j616s") { + return Ok(&T6040); + } Err(ENODEV) } @@ -637,6 +668,11 @@ mod tests { assert!(T8122.wide_dma_mask); assert!(T8122.shmem_capacity == T8112.shmem_capacity); assert!(T8122.sensor.controller_base == 0x2_a110_8000); + assert!(T6040.nonce_session); + assert!(T6040.wide_dma_mask); + assert!(T6040.bootstrap == Bootstrap::WarmRegister); + assert!(T6040.identity == IdentitySource::Chosen); + assert!(T6040.xart_guard == 1); } } @@ -667,7 +703,7 @@ mod dart_window_tests { #[test] fn the_other_profiles_keep_the_bit40_window() { - for profile in [&T8103, &T6000, &T6020, &T8140, &T6020_SEPOS13, &T8122] { + for profile in [&T8103, &T6000, &T6020, &T8140, &T6020_SEPOS13, &T8122, &T6040] { assert_eq!(offset(profile.dart_window), 1 << 40); assert_eq!(range(profile.dart_window), (0, 1 << 32)); } diff --git a/drivers/soc/apple/sbio.rs b/drivers/soc/apple/sbio.rs index 88be672817b1c..a3917e11338c9 100644 --- a/drivers/soc/apple/sbio.rs +++ b/drivers/soc/apple/sbio.rs @@ -10,6 +10,128 @@ use crate::sks::SKS_AUTH_TOKEN_LEN; use kernel::prelude::*; use kernel::soc::apple::mailbox::Message; +struct EnrolPrerequisites { + lane: bool, + live_count: Option, + bag_bound: bool, + catacomb_readable: [bool; 3], + catacomb_writable: [bool; 3], + keybag_writable: bool, + host_store_capacity: bool, +} + +fn admit_enrol(p: &EnrolPrerequisites) -> Result<()> { + if !p.lane { return Err(ENXIO); } + let count = p.live_count.ok_or(EIO)?; + if count >= 3 { return Err(ENOSPC); } + if !p.bag_bound || p.catacomb_readable.contains(&false) { + return Err(ENODATA); + } + if p.catacomb_writable.contains(&false) || !p.keybag_writable { + return Err(EROFS); + } + if !p.host_store_capacity { return Err(ENOSPC); } + Ok(()) +} + +fn catacomb_needed_for_enrol(kind: u8, live_count: Option) -> bool { + live_count != Some(0) || kind == PRIVATE_TYPE_CATACOMB_OWNER +} + +fn admissible_orphan_target( + listed_linux: &[[u8; bio::UUID_LEN]], + uuid: &[u8; bio::UUID_LEN], +) -> Result<()> { + if *uuid == [0; bio::UUID_LEN] { return Err(EINVAL); } + if !listed_linux.contains(uuid) { return Err(ENOENT); } + Ok(()) +} + +#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] +#[::kernel::macros::kunit_tests(apple_sep_enrol_admission)] +mod admission_tests { + use super::*; + + fn ready() -> EnrolPrerequisites { + EnrolPrerequisites { + lane: true, live_count: Some(1), bag_bound: true, + catacomb_readable: [true; 3], catacomb_writable: [true; 3], + keybag_writable: true, host_store_capacity: true, + } + } + + fn refused(p: &EnrolPrerequisites, expected: Error) { + assert_eq!(admit_enrol(p).unwrap_err().to_errno(), expected.to_errno()); + } + + #[test] + fn missing_os_identity() { + let mut p = ready(); p.lane = false; refused(&p, ENXIO); + } + + #[test] + fn unavailable_enclave_list() { + let mut p = ready(); p.live_count = None; refused(&p, EIO); + } + + #[test] + fn enclave_full() { + let mut p = ready(); p.live_count = Some(3); refused(&p, ENOSPC); + } + + #[test] + fn missing_identity_bag_binding() { + let mut p = ready(); p.bag_bound = false; refused(&p, ENODATA); + } + + #[test] + fn missing_catacomb() { + for i in 0..3 { + let mut p = ready(); p.catacomb_readable[i] = false; refused(&p, ENODATA); + } + } + + #[test] + fn unwritable_catacomb_or_keybag() { + for i in 0..3 { + let mut p = ready(); p.catacomb_writable[i] = false; refused(&p, EROFS); + } + let mut p = ready(); p.keybag_writable = false; refused(&p, EROFS); + } + + #[test] + fn host_store_full() { + let mut p = ready(); p.host_store_capacity = false; refused(&p, ENOSPC); + } + + #[test] + fn ready_binding_is_admitted() { + assert!(admit_enrol(&ready()).is_ok()); + } + + #[test] + fn clean_lane_requires_prepared_owner_before_first_capture() { + assert!(catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_OWNER, Some(0))); + assert!(!catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_USER, Some(0))); + assert!(!catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_MASTER, Some(0))); + assert!(catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_USER, Some(1))); + } + + #[test] + fn orphan_delete_requires_exact_linux_identity() { + let linux = [1u8; bio::UUID_LEN]; + let other_lane = [2u8; bio::UUID_LEN]; + assert!(admissible_orphan_target(&[linux], &linux).is_ok()); + refused_orphan(&[linux], &other_lane, ENOENT); + refused_orphan(&[linux], &[0; bio::UUID_LEN], EINVAL); + } + + fn refused_orphan(list: &[[u8; bio::UUID_LEN]], uuid: &[u8; bio::UUID_LEN], expected: Error) { + assert_eq!(admissible_orphan_target(list, uuid).unwrap_err().to_errno(), + expected.to_errno()); + } +} + impl SepData { fn sbio_enrol_step(&self, op: &crate::sbio::SbioOp) -> Option> { match self.sbio_call(op) { @@ -349,6 +471,12 @@ impl SepData { return self.enrol_into_existing_context(user); }; + // PREPARE_ENROL may already have selected the empty Linux context in + // this boot. A file left from an earlier boot is not that proof. + if self.empty_context_prepared.xchg(false, Relaxed) { + return self.enrol_into_existing_context(user); + } + self.open_fresh_context(user, &proof) } @@ -1777,6 +1905,7 @@ impl SepData { }; if !parsed.matches(user) { + dev_info!(self.dev, "verify: enclave found no identity in the Linux user context\n"); return bio::VerifyOutcome::NoMatch; } @@ -1784,6 +1913,12 @@ impl SepData { let identity = *parsed.identity_uuid(); let known = self.bio_index.lock().contains_uuid(&identity); + dev_info!( + self.dev, + "verify: enclave matched identity {} (host index {})\n", + Hex(&identity), if known { "present" } else { "absent" } + ); + if !known { dev_err!( self.dev, @@ -2183,14 +2318,15 @@ impl SepData { self.sensor_trace(c"SENSOR_NONCE"); if !self.sensor_key_loaded.load(Relaxed) { - let firmware = match kernel::firmware::Firmware::request( - c"apple/msrk-j700.bin", &self.dev, - ) { + // Per-device data, so the board names it; J700 predates that. + let name = sensor::root_key_firmware_name().unwrap_or(ROOT_KEY_FIRMWARE); + let firmware = match kernel::firmware::Firmware::request(name, &self.dev) { Ok(firmware) => firmware, Err(error) => { dev_err!( self.dev, - "sensor: signed root-key container unavailable: {:?}\n", + "sensor: signed root-key container {} unavailable: {:?}\n", + name, error ); return false; @@ -3403,6 +3539,133 @@ impl SepData { Some(bytes) } + fn require_linux_bio_lane(&self) -> Result<()> { + if !self.touchid_started.load(Relaxed) || self.xarm.lock().os_uuid.is_none() { + return Err(ENXIO); + } + if !self.keybag_designated.load(Relaxed) + || self.enrol_material.lock().is_none() + || !matches!(keybag::read(keybag::Slot::Identity), + Ok(keybag::State::Present(_))) { + return Err(ENODATA); + } + Ok(()) + } + + /// Refuse before ENROL_START schedules work or sends BEGIN_ENROL. A + /// userspace passwd lookup proves the host account; these are the durable + /// Linux-lane bindings that only the kernel can inspect. + fn preflight_enrol(&self, live_count: Option) -> Result<()> { + let lane = self.touchid_started.load(Relaxed) && self.xarm.lock().os_uuid.is_some(); + let bag_bound = self.keybag_designated.load(Relaxed) && self.enrol_material.lock() + .as_ref().is_some_and(|material| material.snapshot_identity.is_some()); + let kinds = [PRIVATE_TYPE_CATACOMB_OWNER, PRIVATE_TYPE_CATACOMB_USER, + PRIVATE_TYPE_CATACOMB_MASTER]; + let readable = kinds.map(|kind| !catacomb_needed_for_enrol(kind, live_count) + || crate::catacomb::read(kind).is_some()); + let writable = kinds.map(|kind| !catacomb_needed_for_enrol(kind, live_count) + || crate::catacomb::writable(kind)); + let index = self.bio_index.lock(); + let preconditions = EnrolPrerequisites { + lane, + live_count, + bag_bound, + catacomb_readable: readable, + catacomb_writable: writable, + keybag_writable: shim::StoreFile::open_writable_existing( + keybag::Slot::Identity.path()).is_ok(), + host_store_capacity: self.with_host_store(|store| index.can_persist_next(store)) + .unwrap_or(false), + }; + let result = admit_enrol(&preconditions); + if let Err(e) = &result { + dev_warn!(self.dev, "enrol: prerequisites failed before enclave capture: {:?}\n", e); + } + result + } + + fn bio_list_enclave(&self, arg: usize) -> Result { + bio::require_admin()?; + self.require_linux_bio_lane()?; + let ids = self.enclave_identities_for(SBIO_PROBE_USER_ID).ok_or(EIO)?; + let mut out = bio::EnclaveList::new(); + for uuid in ids.iter() { + out.push(*uuid)?; + } + kernel::uaccess::UserSlice::new( + kernel::uaccess::UserPtr::from_addr(arg), core::mem::size_of::() + ).writer().write(&out)?; + bio::ok() + } + + fn bio_delete_orphan(&self, arg: usize) -> Result { + bio::require_admin()?; + self.require_linux_bio_lane()?; + if !bio::idle_for_mutation(&self.bio_session.lock()) { return Err(EBUSY); } + let request: bio::Delete = kernel::uaccess::UserSlice::new( + kernel::uaccess::UserPtr::from_addr(arg), core::mem::size_of::() + ).reader().read()?; + // The exact SEP user and built-in selector are checked by the parser. + // The host index may still hold an orphan after fprintd lost its file, + // so that index cannot decide whether this exact-ID recovery is valid. + let ids = self.enclave_identities_for(SBIO_PROBE_USER_ID).ok_or(EIO)?; + admissible_orphan_target(&ids, &request.uuid)?; + bio::orphan_delete(crate::sbio::IdentityV1::from_index( + SBIO_PROBE_USER_ID, request.uuid)) + } + + fn bio_prepare_enrol(&self, arg: usize) -> Result { + bio::require_admin()?; + bio::validate_enrol_start_arg(arg)?; + self.require_linux_bio_lane()?; + if !bio::idle_for_mutation(&self.bio_session.lock()) { return Err(EBUSY); } + if !sensor::is_bound() { return Err(ENODEV); } + if !self.xart_writable() { return Err(EROFS); } + let ids = self.enclave_identities_for(SBIO_PROBE_USER_ID).ok_or(EIO)?; + if !ids.is_empty() { + // An existing identity must already have a durable owner file; + // selecting SYSTEM here could disturb its live context. + return if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_some() { + bio::ok() + } else { Err(ENODATA) }; + } + if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_some() { + return bio::ok(); + } + if !self.keybag_designated.load(Relaxed) + || !self.enrol_material.lock().as_ref() + .is_some_and(|material| material.snapshot_identity.is_some()) { + return Err(ENODATA); + } + if shim::StoreFile::open_writable_existing(keybag::Slot::Identity.path()).is_err() { + return Err(EROFS); + } + let index = self.bio_index.lock(); + if !self.with_host_store(|store| index.can_persist_next(store)).unwrap_or(false) { + return Err(ENOSPC); + } + drop(index); + let user = crate::sbio::UserId::new(SBIO_PROBE_USER_ID).ok_or(EINVAL)?; + // The host must be able to create and sync the owner file before a + // system-context write. A pre-existing invalid file is left intact. + crate::catacomb::prepare_missing_owner()?; + loop { + self.wait_until_awake()?; + let _writer = self.catacomb_writer.lock(); + if !self.capture_begin() { continue; } + let prepared = self.open_enrolment_context(user); + self.capture_end(); + if !prepared { return Err(EIO); } + break; + } + if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none() + || !crate::catacomb::writable(PRIVATE_TYPE_CATACOMB_OWNER) { + return Err(EROFS); + } + self.empty_context_prepared.store(true, Relaxed); + bio::ok() + } + pub(crate) fn bio_ioctl(&self, cmd: u32, arg: usize) -> Result { if self.shutting_down.load(Relaxed) { return Err(ENODEV); @@ -3410,6 +3673,12 @@ impl SepData { if cmd == bio::IOC_ATTEST { return self.bio_attest(arg); } + if cmd == bio::IOC_LIST_ENCLAVE { + return self.bio_list_enclave(arg); + } + if cmd == bio::IOC_PREPARE_ENROL { + return self.bio_prepare_enrol(arg); + } // With xART writes off the enclave cannot persist what an enrolment or // a match changes. It saves anti-replay state after every match, and a // refused save has left it unresponsive until reboot. Refuse the @@ -3434,14 +3703,21 @@ impl SepData { let live_identity_count = if (cmd == bio::IOC_GET_INFO || cmd == bio::IOC_ENROL_START) && self.touchid_started.load(Relaxed) { - self.enclave_identity_count() + self.enclave_identities_for(SBIO_PROBE_USER_ID).map(|ids| ids.len()) } else { None }; + if cmd == bio::IOC_ENROL_START { + bio::require_admin()?; + bio::validate_enrol_start_arg(arg)?; + self.preflight_enrol(live_identity_count)?; + } // The SPI probe may have deferred until its SMC power supplier bound. // A saved attach-time snapshot would incorrectly reject later ioctls. let sensor_present = sensor::is_bound(); - let handled = { + let handled = if cmd == bio::IOC_DELETE_ORPHAN { + self.bio_delete_orphan(arg)? + } else { let mut session = self.bio_session.lock(); if bio::starts_capture(cmd) { self.wait_for_worker_exit(&mut session, false)?; @@ -3478,7 +3754,7 @@ impl SepData { if !self.capture_begin() { continue; } - let deleted = self.delete_and_persist(&handled); + let deleted = self.delete_and_persist(&handled, cmd == bio::IOC_DELETE_ORPHAN); self.capture_end(); break deleted; }; @@ -3721,7 +3997,7 @@ impl SepData { /// Called with `catacomb_writer` held. A host index entry goes only once /// its deletion is saved: if the save fails, the entry stays, userspace /// still lists the fingerprint, and a retried deletion saves again. - fn delete_and_persist(&self, handled: &bio::Handled) -> Result<()> { + fn delete_and_persist(&self, handled: &bio::Handled, recovery: bool) -> Result<()> { let retry = self.deletion_unsaved.load(Relaxed); let wanted = Iterator::chain(handled.delete_identity.iter(), handled.delete_identities.iter()); @@ -3729,10 +4005,13 @@ impl SepData { let mut gone: KVec<[u8; bio::UUID_LEN]> = KVec::with_capacity(wanted.clone().count(), GFP_KERNEL)?; let mut delete_err: Option = None; + let mut orphan_removed = false; for identity in wanted { - match self.delete_identity(identity) { + let orphan = recovery || !self.bio_index.lock().contains_uuid(identity.uuid()); + match self.delete_identity(identity, orphan) { Ok(true) => { let _ = gone.push_within_capacity(*identity.uuid()); + orphan_removed |= orphan; } Ok(false) => {} Err(e) => delete_err = delete_err.or(Some(e)), @@ -3740,7 +4019,7 @@ impl SepData { } // Save what was removed even if another deletion failed. if !gone.is_empty() { - match self.persist_deletion(retry) { + match self.persist_deletion(retry, orphan_removed) { Ok(()) => { self.deletion_unsaved.store(false, Relaxed); for uuid in gone.iter() { @@ -3765,7 +4044,7 @@ impl SepData { /// Returns whether the enclave no longer holds the identity and the host /// entry must wait for [`Self::persist_deletion`]. - fn delete_identity(&self, identity: &crate::sbio::IdentityV1) -> Result { + fn delete_identity(&self, identity: &crate::sbio::IdentityV1, orphan: bool) -> Result { if self.enclave_lists(identity.uuid()) == Some(false) { // Unless an earlier deletion's save failed, which leaves the // enclave without an identity the saved Catacomb still holds. @@ -3776,18 +4055,44 @@ impl SepData { return Ok(false); } - // 0x57 cannot be undone. Check first that its deletion can be saved. + // 0x57 cannot be undone. Ensure the host can save the affected user + // and master Catacombs before sending it. For an explicit recovery the + // owner Catacomb provides the recovery path even when the bag snapshot + // has no binding; export that owner first if this install lacks a file. + for kind in [PRIVATE_TYPE_CATACOMB_USER, PRIVATE_TYPE_CATACOMB_MASTER] { + if crate::catacomb::read(kind).is_none() || !crate::catacomb::writable(kind) { + dev_err!( + self.dev, + "bio: not deleting: user/master Catacomb is unavailable or unwritable\n" + ); + return Err(EIO); + } + } + if orphan && crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none() { + let Some(reply) = self.read_component_states() else { return Err(EIO); }; + let state = crate::sbio::ComponentStates::new(&reply) + .state_for(crate::sbio::CatacombUser::OWNER.value()); + if !state.is_some_and(|s| s & crate::sbio::COMPONENT_STATE_ACTIVE != 0) + || !self.save_catacomb(crate::sbio::CatacombUser::OWNER, + PRIVATE_TYPE_CATACOMB_OWNER, + c"orphan-recovery owner Catacomb") { + dev_err!(self.dev, "bio: not deleting: owner Catacomb export failed\n"); + return Err(EIO); + } + } if crate::catacomb::read(PRIVATE_TYPE_CATACOMB_OWNER).is_none() - || !self - .enrol_material - .lock() - .as_ref() - .is_some_and(|material| material.snapshot_identity.is_some()) + || !crate::catacomb::writable(PRIVATE_TYPE_CATACOMB_OWNER) { - dev_err!( - self.dev, - "bio: not deleting: there is no owner Catacomb file or identity-bag binding to save the deletion with; nothing was changed\n" - ); + return Err(EIO); + } + if !orphan && !self.enrol_material.lock().as_ref() + .is_some_and(|material| material.snapshot_identity.is_some()) { + dev_err!(self.dev, "bio: not deleting: indexed identity has no identity-bag binding\n"); + return Err(EIO); + } + if orphan && !matches!(keybag::read(keybag::Slot::Identity), + Ok(keybag::State::Present(_))) { + dev_err!(self.dev, "bio: not deleting: Linux identity bag file is unavailable\n"); return Err(EIO); } @@ -3810,10 +4115,11 @@ impl SepData { /// the enclave marks save-pending (a SAVE_CATACOMB of one that is not has /// been refused), the user before the master because saving the user can /// change the master, state re-read before each, then the lockout and the - /// identity bag, which must match the Catacombs. Safe to repeat after a - /// failure partway: a `retry` accepts a user Catacomb an earlier attempt - /// already saved. - fn persist_deletion(&self, retry: bool) -> Result<()> { + /// identity bag when a verified binding exists. An exact-ID orphan + /// recovery without that binding saves the Linux owner Catacomb instead. + /// Safe to repeat after a failure partway: a `retry` accepts a user + /// Catacomb an earlier attempt already saved. + fn persist_deletion(&self, retry: bool, orphan: bool) -> Result<()> { let Some(user) = crate::sbio::UserId::new(SBIO_PROBE_USER_ID) else { return Err(EIO); }; @@ -3828,6 +4134,11 @@ impl SepData { PRIVATE_TYPE_CATACOMB_MASTER, c"master Catacomb after deletion", ), + ( + crate::sbio::CatacombUser::OWNER, + PRIVATE_TYPE_CATACOMB_OWNER, + c"owner Catacomb after deletion", + ), ] { let Some(reply) = self.read_component_states() else { dev_err!(self.dev, "bio: cannot read component state before saving the {}\n", what); @@ -3843,7 +4154,8 @@ impl SepData { dev_err!(self.dev, "bio: the {} is not active; the deletion cannot be saved\n", what); return Err(EIO); } - if state & crate::sbio::COMPONENT_STATE_SAVE_PENDING != 0 { + if state & crate::sbio::COMPONENT_STATE_SAVE_PENDING != 0 || + (orphan && who.value() == crate::sbio::CatacombUser::OWNER.value()) { if !self.save_catacomb(who, kind, what) { dev_err!(self.dev, "bio: saving the {} failed\n", what); return Err(EIO); @@ -3864,11 +4176,22 @@ impl SepData { dev_err!(self.dev, "bio: lockout persistence failed after the deletion\n"); return Err(EIO); } - if !self.resnapshot_identity_keybag() { - dev_err!( + let bag_bound = self.enrol_material.lock().as_ref() + .is_some_and(|material| material.snapshot_identity.is_some()); + if bag_bound { + if !self.resnapshot_identity_keybag() { + dev_err!( + self.dev, + "bio: Catacombs saved after the deletion but the identity-bag snapshot failed\n" + ); + return Err(EIO); + } + } else if orphan { + dev_info!( self.dev, - "bio: Catacombs saved after the deletion but the identity-bag snapshot failed\n" + "bio: orphan deletion saved via Linux user/master/owner Catacombs without a bag snapshot binding\n" ); + } else { return Err(EIO); } Ok(()) @@ -4677,6 +5000,47 @@ impl<'a> IdentityRecords<'a> { } } +#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] +#[::kernel::macros::kunit_tests(apple_sep_enclave_identity_scope)] +mod identity_scope_tests { + use super::*; + + #[test] + fn list_excludes_other_users_and_selectors() { + let mut reply = [0u8; 3 * IDENTITY_RECORD_LEN]; + for (i, (user, selector, byte)) in [ + (SBIO_PROBE_USER_ID, IR_SELECTOR_BUILTIN, 1u8), + (501, IR_SELECTOR_BUILTIN, 2u8), + (SBIO_PROBE_USER_ID, 2, 3u8), + ].iter().enumerate() { + let at = i * IDENTITY_RECORD_LEN; + reply[at..at + 4].copy_from_slice(&user.to_le_bytes()); + reply[at + 4..at + IDENTITY_V1_LEN].fill(*byte); + reply[at + IR_SELECTOR..at + IR_SELECTOR + 4] + .copy_from_slice(&selector.to_le_bytes()); + } + let records = IdentityRecords::new(&reply).unwrap(); + let mut seen = 0; + records.each_built_in_for(SBIO_PROBE_USER_ID, |identity| { + seen += 1; + assert!(*identity.uuid() == [1u8; IDENTITY_UUID_LEN]); + }); + assert_eq!(seen, 1); + } + + #[test] + fn match_result_carries_the_exact_enclave_identity() { + let mut reply = [0u8; SBIO_MATCH_RESULT_LEN]; + reply[MR_USER_ID..MR_USER_ID + 4] + .copy_from_slice(&SBIO_PROBE_USER_ID.to_le_bytes()); + reply[MR_IDENTITY..MR_IDENTITY + IDENTITY_UUID_LEN].fill(0xa5); + let result = MatchResult::parse(&reply).unwrap(); + let user = UserId::new(SBIO_PROBE_USER_ID).unwrap(); + assert!(result.matches(user)); + assert!(*result.identity_uuid() == [0xa5; IDENTITY_UUID_LEN]); + } +} + const FIC_DEVICE_BUILTIN: u32 = 1; pub(crate) const SBIO_FREE_COUNT_LEN: usize = 4 + 20; static_assert!(SBIO_FREE_COUNT_LEN == SBIO_SAVE_SELECTOR_LEN); diff --git a/drivers/soc/apple/sensor.rs b/drivers/soc/apple/sensor.rs index 63a4ae28fbc6d..13bb470c89b3b 100644 --- a/drivers/soc/apple/sensor.rs +++ b/drivers/soc/apple/sensor.rs @@ -25,15 +25,26 @@ extern "C" { fn sep_sensor_irq_available() -> c_int; fn sep_sensor_irq_arm(); fn sep_sensor_irq_wait(timeout_ms: c_uint) -> c_int; - fn sep_sensor_firmware_name() -> *const c_char; + fn sep_sensor_firmware_name(index: c_uint) -> *const c_char; } /// The calibration blob's name from the board description, or `None` when the /// sensor node has no `firmware-name` property (the caller then uses a default). pub(crate) fn firmware_name() -> Option<&'static CStr> { + firmware_name_at(0) +} + +/// The signed root-key container's name from the board description: the +/// second `firmware-name` entry, or `None` when the board gives only the +/// calibration (the caller then uses a default). +pub(crate) fn root_key_firmware_name() -> Option<&'static CStr> { + firmware_name_at(1) +} + +fn firmware_name_at(index: c_uint) -> Option<&'static CStr> { // SAFETY: the shim returns NULL or a pointer to a NUL-terminated string // owned by the device property and valid for the device's lifetime. - let ptr = unsafe { sep_sensor_firmware_name() }; + let ptr = unsafe { sep_sensor_firmware_name(index) }; if ptr.is_null() { return None; } diff --git a/drivers/soc/apple/sensor_shim.c b/drivers/soc/apple/sensor_shim.c index 68405e0a74507..bed228b4d1ca3 100644 --- a/drivers/soc/apple/sensor_shim.c +++ b/drivers/soc/apple/sensor_shim.c @@ -568,19 +568,23 @@ static int sep_sensor_power_line_locked(void) } /* - * The per-device calibration blob name, as the board description gives it in - * the sensor node's "firmware-name" property. NULL when absent, so the caller - * can fall back to a default. + * A per-device blob name, as the board description gives it in the sensor + * node's "firmware-name" list: index 0 is the calibration, index 1 the signed + * root key. NULL when that entry is absent, so the caller can fall back to a + * default. */ -static const char *sep_sensor_firmware_name_locked(void) +static const char *sep_sensor_firmware_name_locked(unsigned int index) { - const char *name = NULL; + const char *names[2] = { NULL, NULL }; + int count; - if (!sep_spi) + if (!sep_spi || index >= ARRAY_SIZE(names)) return NULL; - if (device_property_read_string(&sep_spi->dev, "firmware-name", &name)) + count = device_property_read_string_array(&sep_spi->dev, "firmware-name", + names, ARRAY_SIZE(names)); + if (count <= (int)index) return NULL; - return name; + return names[index]; } /* Powers the sensor on/off, holding the hardware settling delay. */ @@ -871,14 +875,14 @@ int sep_sensor_power_line(void) return result; } -const char *sep_sensor_firmware_name(void) +const char *sep_sensor_firmware_name(unsigned int index) { struct sep_sensor_lifetime *sensor = sep_lock_sensor(); const char *result; if (!sensor) return NULL; - result = sep_sensor_firmware_name_locked(); + result = sep_sensor_firmware_name_locked(index); mutex_unlock(&sensor->lock); return result; } diff --git a/drivers/soc/apple/sep.rs b/drivers/soc/apple/sep.rs index df71496a564fe..5fd41c5bebf60 100644 --- a/drivers/soc/apple/sep.rs +++ b/drivers/soc/apple/sep.rs @@ -449,6 +449,9 @@ const PATCH_POLL_MS: u32 = 20; const PATCH_POLL_ATTEMPTS: u32 = 250; const CALIBRATION_FIRMWARE: &CStr = c"apple/mesa_calibration.bin"; +/// The signed root-key container on boards whose sensor node names only the +/// calibration (J700). +const ROOT_KEY_FIRMWARE: &CStr = c"apple/msrk-j700.bin"; const SBIO_PROBE_USER_ID: i32 = 1000; static_assert!(SBIO_PROBE_USER_ID >= crate::sks::SKS_DESIGNATE_USER_MIN); @@ -781,6 +784,9 @@ struct SepData { // only under `catacomb_writer`. deletion_unsaved: Atomic, + // PREPARE_ENROL selected the empty Linux context during this boot. + empty_context_prepared: Atomic, + #[pin] rx_work: Work, @@ -980,6 +986,7 @@ impl SepData { worker_gone <- new_condvar!("SepData::worker_gone"), catacomb_writer <- new_mutex!(()), deletion_unsaved: Atomic::new(false), + empty_context_prepared: Atomic::new(false), rx_work <- new_work!("SepData::rx_work"), enrol_work <- new_work!("SepData::enrol_work"), verify_work <- new_work!("SepData::verify_work"), diff --git a/drivers/soc/apple/shim.h b/drivers/soc/apple/shim.h index 0425bf2cdaa6e..ca0d3d8a797ee 100644 --- a/drivers/soc/apple/shim.h +++ b/drivers/soc/apple/shim.h @@ -44,6 +44,7 @@ void sep_hwrng_unregister(void *mem); void *sep_store_open(const char *path); void *sep_store_open_host(const char *path, bool *created); void *sep_store_open_trunc(const char *path); +void *sep_store_open_writable_existing(const char *path); void *sep_store_open_ro(const char *path); void *sep_store_open_block(const char *path, int writable); void sep_store_close(void *handle); @@ -132,7 +133,7 @@ int sep_sensor_cs_timing_mode(void); int sep_sensor_power_cycle(void); int sep_sensor_power_source(void); int sep_sensor_power(int on); -const char *sep_sensor_firmware_name(void); +const char *sep_sensor_firmware_name(unsigned int index); int sep_sensor_xfer(const void *tx, void *rx, size_t len); int sep_sensor_xfer_tx(const void *tx, size_t len); int sep_sensor_xfer2(const void *tx, size_t tx_len, void *rx, size_t rx_len); diff --git a/drivers/soc/apple/shim.rs b/drivers/soc/apple/shim.rs index 8e30c3f54b817..6d546a5603f37 100644 --- a/drivers/soc/apple/shim.rs +++ b/drivers/soc/apple/shim.rs @@ -9,6 +9,7 @@ extern "C" { fn sep_store_open(path: *const c_char) -> *mut c_void; fn sep_store_open_host(path: *const c_char, created: *mut bool) -> *mut c_void; fn sep_store_open_trunc(path: *const c_char) -> *mut c_void; + fn sep_store_open_writable_existing(path: *const c_char) -> *mut c_void; fn sep_store_open_ro(path: *const c_char) -> *mut c_void; fn sep_store_open_block(path: *const c_char, writable: c_int) -> *mut c_void; fn sep_store_close(handle: *mut c_void); @@ -80,6 +81,15 @@ impl StoreFile { Ok(StoreFile { handle }) } + pub(crate) fn open_writable_existing(path: &CStr) -> Result { + // SAFETY: `path` is NUL-terminated; this open cannot create or truncate. + let handle = unsafe { sep_store_open_writable_existing(path.as_char_ptr()) }; + if handle.is_null() { + return Err(EROFS); + } + Ok(StoreFile { handle }) + } + pub(crate) fn open_readonly(path: &CStr) -> Result { // SAFETY: `path` is NUL-terminated; the shim returns NULL on failure. let handle = unsafe { sep_store_open_ro(path.as_char_ptr()) }; diff --git a/drivers/soc/apple/store.rs b/drivers/soc/apple/store.rs index 1d04917229779..d9930332cab99 100644 --- a/drivers/soc/apple/store.rs +++ b/drivers/soc/apple/store.rs @@ -362,6 +362,12 @@ impl Store { self.slots.iter().position(|s| !s.used) } + /// Admission check for the copy-on-write slot used by the next write. + /// Even replacing an existing key needs a free slot until commit. + pub(crate) fn can_write(&self, key: &Key, len: usize) -> bool { + valid_kind(key.kind) && len <= MAX_VALUE && self.find_free().is_some() + } + fn block_offset(slot: usize) -> u64 { ((slot + 1) * BLOCK_SIZE) as u64 } diff --git a/drivers/soc/apple/store_shim.c b/drivers/soc/apple/store_shim.c index d50270d717be5..77319a4702164 100644 --- a/drivers/soc/apple/store_shim.c +++ b/drivers/soc/apple/store_shim.c @@ -83,6 +83,14 @@ void *sep_store_open_trunc(const char *path) return IS_ERR(f) ? NULL : f; } +/* Admission check only: never create or truncate an existing record. */ +void *sep_store_open_writable_existing(const char *path) +{ + struct file *f = open_as_kernel(path, O_WRONLY | O_LARGEFILE, 0); + + return IS_ERR(f) ? NULL : f; +} + /* * Opens an existing file read-only, NULL if absent. O_RDONLY and no O_CREAT so * the seed cannot be created, truncated or written by mistake. diff --git a/drivers/soc/apple/t6041-display-gate.c b/drivers/soc/apple/t6041-display-gate.c index aa088ddb957de..3f27fe2cff832 100644 --- a/drivers/soc/apple/t6041-display-gate.c +++ b/drivers/soc/apple/t6041-display-gate.c @@ -156,3 +156,500 @@ static int __init apple_t6041_display_gate(void) /* Runs before of_platform_default_populate_init() at arch_initcall_sync. */ arch_initcall(apple_t6041_display_gate); + +/* + * External displays (HDMI behind dcpext0, Type-C behind dcpext1). The + * device tree describes the whole chain disabled; it is enabled here once the + * boot loader has given each external display processor its firmware memory + * (the memory-region nodes of the dcpext are available, or they stay disabled + * placeholders). Before any device exists only the Type-C routes are enabled + * (the port controllers then wait for dcpext1's mode switches) and the + * display subsystem is held back. The power-up waits for the PMP firmware, + * whose acknowledgement the DISPEXT domains need: HDMI chain, Type-C chain, + * then both processors and the display subsystem together, so the display + * master is created once with every DART in its iommus. + */ +#include +#include +#include +#include +#include +#include +#include + +/* + * Two stages, each with its own display DART (a DART still disabled in the + * display subsystem's iommus would make of_iommu drop its whole fwspec): the + * HDMI chain, then the Type-C chain. Run at the same time the two processors + * crashed (dcpext0: axi_rd_err), as they never did one after the other. + * Parents first; the genpd links in the tree do the real ordering. + */ +struct gate_stage { + const char *dart; /* display DART of this stage */ + const char *const *nodes; + unsigned int nr; +}; + +static const char *const gate_hdmi_nodes[] = { + /* ATC3 power, PMP DISPEXT0 request, DISPEXT0, AOP pins, PHY, crossbar, DARTs, mailbox, dcpext0 */ + "/power-management@502800000/power-controller@408", + "/power-management@508300000/power-controller@e0", + "/power-management@508300000/power-controller@168", + "/power-management@508300000/power-controller@b8", + "/power-management@508300000/power-controller@148", + "/pmp-report@5003c0000/report@11", + "/power-management@502800000/power-controller@340", + "/power-management@502800000/power-controller@348", + "/pinctrl@510824000", + "/mailbox@39b148000", + "/phy@39aa90000", + "/mux@39b24c000", + "/iommu@3a2340000", + "/iommu@3a2300000", + "/mailbox@3a2e08000", + "/dcp@3a2e00000/typec-routes/route@0", + "/dcp@3a2e00000/typec-routes/route@1", + "/dcp@3a2e00000/typec-routes/route@2", + "/dcp@3a2e00000", +}; + +static const char *const gate_usbc_nodes[] = { + /* PMP DISPEXT1 request, FAB7_SOC, DISPEXT1, crossbars, DARTs, mailbox, routes, dcpext1 */ + "/pmp-report@5003c0000/report@12", + "/power-management@502800000/power-controller@488", + "/power-management@502800000/power-controller@490", + "/power-management@502800000/power-controller@498", + "/mux@38324c000", + "/mux@38b24c000", + "/mux@39324c000", + "/iommu@602340000", + "/iommu@602300000", + "/mailbox@602e08000", + "/dcp@602e00000/typec-routes/route@0", + "/dcp@602e00000/typec-routes/route@1", + "/dcp@602e00000/typec-routes/route@2", + "/dcp@602e00000", +}; + +static const struct gate_stage gate_stages[] = { + { "/iommu@3a2300000", gate_hdmi_nodes, ARRAY_SIZE(gate_hdmi_nodes) }, + { "/iommu@602300000", gate_usbc_nodes, ARRAY_SIZE(gate_usbc_nodes) }, +}; + +/* A display processor (a direct child of the root) and a Type-C display route. */ +static bool gate_is_dcp(const char *path) +{ + return !strncmp(path, "/dcp@", 5) && !strchr(path + 5, '/'); +} + +static bool gate_is_route(const char *path) +{ + return strstr(path, "/typec-routes/"); +} + +static int gate_display_add_dart(struct of_changeset *cs, struct device_node *display, + struct device_node *dart) +{ + struct property *old, *prop; + __be32 *value; + + old = of_find_property(display, "iommus", NULL); + if (!old || !dart->phandle || old->length % (2 * sizeof(*value))) + return -EINVAL; + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + value = kmalloc(old->length + 2 * sizeof(*value), GFP_KERNEL); + if (!prop || !value) { + kfree(prop); + kfree(value); + return -ENOMEM; + } + memcpy(value, old->value, old->length); + value[old->length / sizeof(*value)] = cpu_to_be32(dart->phandle); + value[old->length / sizeof(*value) + 1] = cpu_to_be32(0); + prop->name = kstrdup("iommus", GFP_KERNEL); + prop->length = old->length + 2 * sizeof(*value); + prop->value = value; + if (!prop->name) { + kfree(value); + kfree(prop); + return -ENOMEM; + } + return of_changeset_update_property(cs, display, prop); +} + +/* + * The processor's firmware memory must be described: every memory-region of + * the dcpext node (the stage's last node) must be an available reserved-memory + * node. A loader that fills the regions from the ADT (m4-loader + * m4_dcpext_fixup) leaves disabled placeholders when it refuses, and an older + * loader leaves them all disabled; enabling the processor then would only + * make its driver fail. /chosen m4,dcpext is that loader's record (0 filled, + * 2 static memory), printed when the check fails. + */ +static bool gate_firmware_memory(const char *dcp) +{ + struct device_node *np, *region; + int i, n; + u32 record[2]; + + np = of_find_node_by_path(dcp); + if (!np) + return false; + n = of_count_phandle_with_args(np, "memory-region", NULL); + for (i = 0; i < n; i++) { + region = of_parse_phandle(np, "memory-region", i); + if (region && of_device_is_available(region) && + of_device_is_compatible(region, "apple,asc-mem")) { + of_node_put(region); + continue; + } + of_node_put(region); + n = -ENODEV; + break; + } + of_node_put(np); + if (n > 0) + return true; + np = of_find_node_by_path("/chosen"); + if (np && !of_property_read_u32_array(np, "m4,dcpext", record, 2)) + pr_err("%s: no firmware memory (loader record <%u %u>), nothing enabled\n", dcp, + record[0], record[1]); + else + pr_err("%s: no firmware memory, nothing enabled\n", dcp); + of_node_put(np); + return false; +} + +/* Every node of @st is there and still disabled, and its processor has firmware memory. */ +static bool gate_stage_ready(const struct gate_stage *st) +{ + struct device_node *np; + unsigned int i; + + if (!gate_firmware_memory(st->nodes[st->nr - 1])) + return false; + for (i = 0; i < st->nr; i++) { + np = of_find_node_by_path(st->nodes[i]); + if (!np) { + pr_err("%s: no such node, nothing enabled\n", st->nodes[i]); + return false; + } + if (of_device_is_available(np)) { + pr_err("%s: already enabled, nothing changed\n", st->nodes[i]); + of_node_put(np); + return false; + } + of_node_put(np); + } + return true; +} + +/* Enable @st's nodes and put its display DART into the display subsystem's iommus. */ +static int gate_stage_apply(const struct gate_stage *st, struct device_node *display) +{ + struct device_node *np; + struct of_changeset *cs; + unsigned int i; + int ret; + + cs = kzalloc(sizeof(*cs), GFP_KERNEL); + if (!cs) + return -ENOMEM; + of_changeset_init(cs); + + np = of_find_node_by_path(st->dart); + ret = np ? gate_display_add_dart(cs, display, np) : -ENODEV; + of_node_put(np); + for (i = 0; !ret && i < st->nr; i++) { + if (gate_is_dcp(st->nodes[i]) || gate_is_route(st->nodes[i])) + continue; + np = of_find_node_by_path(st->nodes[i]); + ret = np ? of_changeset_update_prop_string(cs, np, "status", "okay") : -ENODEV; + of_node_put(np); + } + if (!ret) + ret = of_changeset_apply(cs); + if (ret) { + of_changeset_destroy(cs); + kfree(cs); + } + /* Applied, it stays: the live tree refers to it. */ + return ret; +} + +#define GATE_PMP_WAIT_MS 20000 + +/* A tether boot reserves ATC0 for the debug link. Its DP PHY is disabled, + * so exposing a route@0 would make its DCP (dcpext1, or the dcpext0 hybrid) + * defer on that missing supplier and take the usable ATC1/2 display routes + * (and dcpext0's HDMI) down with it. + */ +static bool gate_route_phy_available(struct device_node *route) +{ + struct device_node *routes, *dcp; + struct of_phandle_args phy; + char name[16]; + u32 index; + int match; + bool available = false; + + if (of_property_read_u32(route, "reg", &index)) + return false; + routes = of_get_parent(route); + dcp = routes ? of_get_parent(routes) : NULL; + of_node_put(routes); + if (!dcp) + return false; + /* The fabric takes a route's PHY by name ("typecN"), not by position. */ + snprintf(name, sizeof(name), "typec%u", index); + match = of_property_match_string(dcp, "phy-names", name); + if (match >= 0) + index = match; + if (!of_parse_phandle_with_args(dcp, "phys", "#phy-cells", index, &phy)) { + available = of_device_is_available(phy.np); + of_node_put(phy.np); + } + of_node_put(dcp); + return available; +} + +/* Set the status of the processors (externals) or the routes, and the display subsystem, in one changeset. */ +static int gate_set(struct device_node *display, bool dcps, const char *dcp_status, bool routes, + const char *route_status, const char *display_status) +{ + struct device_node *np; + struct of_changeset *cs; + unsigned int i, j; + int ret = 0; + + cs = kzalloc(sizeof(*cs), GFP_KERNEL); + if (!cs) + return -ENOMEM; + of_changeset_init(cs); + for (i = 0; !ret && i < ARRAY_SIZE(gate_stages); i++) + for (j = 0; !ret && j < gate_stages[i].nr; j++) { + const char *path = gate_stages[i].nodes[j]; + + if (!(dcps && gate_is_dcp(path)) && !(routes && gate_is_route(path))) + continue; + np = of_find_node_by_path(path); + if (np && gate_is_route(path) && route_status && + !strcmp(route_status, "okay") && !gate_route_phy_available(np)) { + pr_info("%s remains disabled: DP PHY unavailable\n", path); + of_node_put(np); + continue; + } + ret = np ? of_changeset_update_prop_string(cs, np, "status", + gate_is_dcp(path) ? dcp_status : + route_status) : -ENODEV; + of_node_put(np); + } + if (!ret && display_status) + ret = of_changeset_update_prop_string(cs, display, "status", display_status); + if (!ret) + ret = of_changeset_apply(cs); + if (ret) { + of_changeset_destroy(cs); + kfree(cs); + } + return ret; +} + +static struct device_node *gate_display; +static struct device_node *gate_pmp; +static atomic_t gate_started = ATOMIC_INIT(0); + +/* Internal panel only: the routes go again, so the port controllers probe without mode switches. */ +static void gate_fallback(const char *why) +{ + int ret = gate_set(gate_display, false, NULL, true, "disabled", "okay"); + + pr_err("%s: external displays stay off, internal panel only (%d)\n", why, ret); +} + +/* + * The Type-C core treats a mode switch below a disabled node as absent, so the + * port controllers, which probe before dcpext1 is enabled, only took the USB + * muxes. Once dcpext1 is bound (its route mode switches are registered), let + * each controller wired to a route probe again, so that it takes them too. + */ +static void gate_reprobe_port_controllers(void) +{ + struct device *done[ARRAY_SIZE(gate_usbc_nodes)] = {}; + struct device_node *dcp, *np, *ep, *connector, *ctrl; + struct device *dev = NULL; + unsigned int i, n = 0, tries; + + dcp = of_find_node_by_path("/dcp@602e00000"); + for (tries = 0; dcp && tries < 150; tries++) { + dev = dcp->fwnode.dev; + if (dev && READ_ONCE(dev->driver)) + break; + msleep(200); + } + of_node_put(dcp); + if (!dev || !READ_ONCE(dev->driver)) { + pr_err("dcpext1 not bound, port controllers left as they are\n"); + return; + } + for (i = 0; i < ARRAY_SIZE(gate_usbc_nodes); i++) { + unsigned int k; + + if (!gate_is_route(gate_usbc_nodes[i])) + continue; + np = of_find_node_by_path(gate_usbc_nodes[i]); + ep = np ? of_graph_get_next_endpoint(np, NULL) : NULL; + connector = ep ? of_graph_get_remote_port_parent(ep) : NULL; + ctrl = connector ? of_get_parent(connector) : NULL; + dev = ctrl ? get_device(ctrl->fwnode.dev) : NULL; + of_node_put(ctrl); + of_node_put(connector); + of_node_put(ep); + of_node_put(np); + if (!dev) + continue; + for (k = 0; k < n && done[k] != dev; k++) + ; + if (k < n || !READ_ONCE(dev->driver)) { + put_device(dev); + continue; + } + done[n++] = dev; + if (device_reprobe(dev)) + dev_err(dev, "probe again failed\n"); + else + dev_info(dev, "probed again for the display routes\n"); + } + while (n) + put_device(done[--n]); +} + +/* + * A processor that finds its crossbar or DP PHY without a driver is given up + * for good (the deferred probe timeout is 0 here), and those drivers are + * modules that load only once the stage has created their devices. Wait until + * every such device of the enabled infrastructure has its driver. Returns the + * first path still without one, or NULL. + */ +static const char *gate_unbound_infra(void) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(gate_stages); i++) + for (j = 0; j < gate_stages[i].nr; j++) { + const char *path = gate_stages[i].nodes[j]; + struct device_node *np; + struct device *dev; + + /* Power domains are the genpd core's to wait for; the modules are the point here. */ + if (gate_is_dcp(path) || gate_is_route(path) || + !strncmp(path, "/power-management@", 18)) + continue; + np = of_find_node_by_path(path); + dev = np ? READ_ONCE(np->fwnode.dev) : NULL; + of_node_put(np); + if (dev && !READ_ONCE(dev->driver)) + return path; + } + return NULL; +} + +#define GATE_DRIVER_WAIT_MS 10000 + +static void gate_wait_for_drivers(void) +{ + const char *path = NULL; + unsigned int tries; + + for (tries = 0; tries < GATE_DRIVER_WAIT_MS / 100; tries++) { + path = gate_unbound_infra(); + if (!path) + return; + msleep(100); + } + pr_warn("%s has no driver after " __stringify(GATE_DRIVER_WAIT_MS) " ms, enabling the processors anyway\n", + path); +} + +static void gate_power_up(struct work_struct *work) +{ + unsigned int i; + int ret = 0; + + for (i = 0; !ret && i < ARRAY_SIZE(gate_stages); i++) { + ret = gate_stage_apply(&gate_stages[i], gate_display); + if (!ret) + pr_info("stage %u enabled\n", i); + } + if (!ret) + gate_wait_for_drivers(); + /* Both processors and the display subsystem together: the master binds once, with all DCPs. */ + if (!ret) + ret = gate_set(gate_display, true, "okay", false, NULL, "okay"); + if (ret) { + gate_fallback("could not enable the external displays"); + return; + } + pr_info("processors and display subsystem enabled\n"); + gate_reprobe_port_controllers(); +} +static DECLARE_WORK(gate_power_up_work, gate_power_up); + +static void gate_timeout(struct work_struct *work) +{ + if (!atomic_xchg(&gate_started, 1)) + gate_fallback("no PMP within " __stringify(GATE_PMP_WAIT_MS) " ms"); +} +static DECLARE_DELAYED_WORK(gate_timeout_work, gate_timeout); + +/* The PMP's driver is bound: DISPEXT can be acknowledged now. */ +static int gate_pmp_bound(struct notifier_block *nb, unsigned long action, void *data) +{ + struct device *dev = data; + + if (action != BUS_NOTIFY_BOUND_DRIVER || dev->of_node != gate_pmp || atomic_xchg(&gate_started, 1)) + return NOTIFY_DONE; + cancel_delayed_work(&gate_timeout_work); + queue_work(system_long_wq, &gate_power_up_work); + return NOTIFY_OK; +} +static struct notifier_block gate_pmp_nb = { .notifier_call = gate_pmp_bound }; + +static int __init apple_t6041_extdisp_gate(void) +{ + unsigned int i; + int ret; + + if (!of_machine_is_compatible("apple,j616s")) + return 0; + if (IS_ENABLED(CONFIG_VIDEO) && video_firmware_drivers_only()) + return 0; + + /* Everything must be there and disabled, with firmware memory, before anything is changed. */ + for (i = 0; i < ARRAY_SIZE(gate_stages); i++) + if (!gate_stage_ready(&gate_stages[i])) + return 0; + gate_display = of_find_node_by_path("/display-subsystem"); + gate_pmp = of_find_node_by_path("/pmp@500700000"); + if (!gate_display || !gate_pmp || !of_device_is_available(gate_display) || + !of_device_is_available(gate_pmp)) { + pr_err("no display subsystem or PMP, nothing enabled\n"); + return 0; + } + + /* No device exists yet: routes on, display subsystem held back until the power-up. */ + ret = gate_set(gate_display, false, NULL, true, "okay", "disabled"); + if (ret) { + pr_err("could not prepare the tree: %d, nothing enabled\n", ret); + return 0; + } + ret = bus_register_notifier(&platform_bus_type, &gate_pmp_nb); + if (ret) { + gate_fallback("no bus notifier"); + return 0; + } + schedule_delayed_work(&gate_timeout_work, msecs_to_jiffies(GATE_PMP_WAIT_MS)); + pr_info("routes enabled, waiting for the PMP\n"); + return 0; +} +arch_initcall(apple_t6041_extdisp_gate); diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index 998b6cffe6297..c11d299840d80 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -90,10 +90,11 @@ * dwc3_apple_init(). In host mode xHCI then keeps running in suspend-to-idle when a USB device * may wake the system, as a halted xHC could not signal the wakeup. * - * Runtime PM (T6040 only, see dwc3_apple_core_probe()): the glue holds a reference from the - * reset deassert to the end of the shutdown, so with no cable the controller can runtime-suspend - * and its power domain drop, its DARTs (which save and restore their state) and, through the - * device link of its phys, the PHY with it. + * Runtime PM (T6040 and T8132, see dwc3_apple_core_probe()): the glue holds a reference from + * the reset deassert to the end of the shutdown, so with no cable the controller can + * runtime-suspend and its power domain drop, its DARTs (which save and restore their state) and, + * through the device link of its phys, the PHY with it. On T8132 a port that has not seen a cable + * yet can suspend as well, with runtime PM held by the glue until the first core probe. * * enum dwc3_apple_state is defined in dwc3-apple-state.h. */ @@ -123,6 +124,7 @@ * @usb3_power_off_pending: Core-owned USB3 references still to release * @idle_rpm: Let the controller runtime-suspend while the core is down * @rpm_held: The glue holds a runtime PM reference for the running core + * @pre_core_rpm: Runtime PM is the glue's own until the first core bring-up * @drd_cfg: The controller's configuration block (optional, for its tunable) * @tunables: Controller tunables from the device tree, see dwc3_apple_tunable_list */ @@ -151,6 +153,7 @@ struct dwc3_apple { unsigned long usb3_power_off_pending; bool idle_rpm; bool rpm_held; + bool pre_core_rpm; void __iomem *drd_cfg; struct apple_tunable *tunables[DWC3_APPLE_NR_TUNABLES]; @@ -524,8 +527,21 @@ static int dwc3_apple_core_start(struct dwc3_apple *appledwc, enum dwc3_apple_st } /* - * Before the first core probe runtime PM is not enabled yet and the controller stays - * powered; dwc3_apple_core_probe() takes the reference then. + * Power an idle port's controller up for its first core probe, then leave runtime PM + * to dwc3_core_probe(), which expects it disabled with the device active. + */ + if (appledwc->pre_core_rpm) { + ret = pm_runtime_resume_and_get(appledwc->dev); + if (ret) + return ret; + pm_runtime_disable(appledwc->dev); + pm_runtime_put_noidle(appledwc->dev); + appledwc->pre_core_rpm = false; + } + + /* + * Before the first core probe the controller is powered, with runtime PM off or handed + * over above; dwc3_apple_core_probe() takes the reference then. */ if (appledwc->state != DWC3_APPLE_PROBE_PENDING && !appledwc->rpm_held) { ret = pm_runtime_resume_and_get(appledwc->dev); @@ -839,8 +855,9 @@ static int dwc3_apple_probe(struct platform_device *pdev) appledwc->dev = &pdev->dev; appledwc->fixed_hub = dwc3_apple_has_fixed_hub(dev); appledwc->host_only = dwc3_apple_host_only(dev); - /* Tested on T6040 only, where the PHY driver and the DARTs follow runtime PM. */ - appledwc->idle_rpm = of_device_is_compatible(dev->of_node, "apple,t6040-dwc3"); + /* The PHY driver and the DARTs follow runtime PM on these SoCs. */ + appledwc->idle_rpm = of_device_is_compatible(dev->of_node, "apple,t6040-dwc3") || + of_device_is_compatible(dev->of_node, "apple,t8132-dwc3"); mutex_init(&appledwc->lock); /* A never-connected port has no generic core probe to publish this. */ appledwc->dwc.dev = dev; @@ -906,9 +923,23 @@ static int dwc3_apple_probe(struct platform_device *pdev) * details. */ appledwc->state = DWC3_APPLE_PROBE_PENDING; + /* + * Until the first cable there is no core to keep powered: let the controller + * runtime-suspend, and its DARTs with it, so that its power domain can drop. + * Tested on T8132 only. + */ + if (appledwc->idle_rpm && !appledwc->fixed_hub && + of_device_is_compatible(dev->of_node, "apple,t8132-dwc3")) { + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + appledwc->pre_core_rpm = true; + } ret = dwc3_apple_setup_role_switch(appledwc); - if (ret) + if (ret) { + if (appledwc->pre_core_rpm) + pm_runtime_disable(dev); return dev_err_probe(&pdev->dev, ret, "Failed to setup role switch\n"); + } if (!appledwc->fixed_hub) return 0; @@ -951,6 +982,8 @@ static void dwc3_apple_remove(struct platform_device *pdev) dev_err(appledwc->dev, "Removal left pending PHY/reset cleanup\n"); if (appledwc->state != DWC3_APPLE_PROBE_PENDING) dwc3_core_remove_no_shutdown(&appledwc->dwc); + if (appledwc->pre_core_rpm) + pm_runtime_disable(appledwc->dev); } /* Stop the consumer and release its PHY epoch before the supplier shutdown. */ diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index aa630b95dafe9..dd6f7b99e698a 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -290,6 +290,11 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) static void tps6598x_drop_partner(struct tps6598x *tps, u32 status) { + if (tps->partner_altmode_dp) { + typec_altmode_update_active(tps->partner_altmode_dp, false); + typec_unregister_altmode(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + } if (!IS_ERR(tps->partner)) typec_unregister_partner(tps->partner); tps->partner = NULL; @@ -1171,6 +1176,11 @@ static void cd321x_update_work(struct work_struct *work) /* Process partner disconnection or change */ if (!new_connected || partner_changed) { + if (tps->partner_altmode_dp) { + typec_altmode_update_active(tps->partner_altmode_dp, false); + typec_unregister_altmode(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + } if (!IS_ERR(tps->partner)) typec_unregister_partner(tps->partner); tps->partner = NULL; @@ -1245,6 +1255,29 @@ static void cd321x_update_work(struct work_struct *work) typec_partner_set_identity(tps->partner); } + /* The CD321x firmware owns VDM discovery and Enter Mode. Publish the + * discovered DP mode only after it reports an entered DP connection; + * keep Linux's DP Alt Mode driver from issuing a second Enter Mode. + */ + if (dp_connected && !tps->partner_altmode_dp) { + struct typec_altmode_desc desc = { + .svid = USB_TYPEC_DP_SID, + .mode = USB_TYPEC_DP_MODE, + .vdo = le32_to_cpu(st.dp_sid_status.mode_data), + .mode_selection = true, + }; + + tps->partner_altmode_dp = typec_partner_register_altmode(tps->partner, + &desc); + if (IS_ERR(tps->partner_altmode_dp)) { + ret = PTR_ERR(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + goto retry; + } + } + if (tps->partner_altmode_dp) + typec_altmode_update_active(tps->partner_altmode_dp, dp_connected); + /* Update the TypeC MUX/PHY state */ if (dp_route_was_active && dp_connected && !was_disconnected && !dp_mode_changed && st.dp_sid_event) { @@ -1997,7 +2030,8 @@ static int cd321x_register_port_altmodes(struct cd321x *cd321x) memset(&desc, 0, sizeof(desc)); desc.svid = USB_TYPEC_DP_SID; desc.mode = USB_TYPEC_DP_MODE; - desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)); + desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | + BIT(DP_PIN_ASSIGN_D) | BIT(DP_PIN_ASSIGN_E)); desc.vdo |= DP_CAP_DFP_D; amode = typec_port_register_altmode(cd321x->tps.port, &desc); if (IS_ERR(amode)) @@ -2111,6 +2145,7 @@ EXPORT_SYMBOL_IF_KUNIT(cd321x_init_work); static int cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) { + struct fwnode_handle *connector_fwnode; struct cd321x *cd321x = container_of(tps, struct cd321x, tps); int ret; @@ -2130,9 +2165,12 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) goto err_unregister_altmodes; } - cd321x->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); - if (IS_ERR(cd321x->connector_fwnode)) - cd321x->connector_fwnode = NULL; + if (fwnode_property_present(fwnode, "displayport")) + connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); + else + connector_fwnode = fwnode_handle_get(fwnode); + if (!IS_ERR_OR_NULL(connector_fwnode)) + cd321x->connector_fwnode = connector_fwnode; cd321x->tbt_switch = cd321x_tbt_switch_get(tps->dev, fwnode); if (IS_ERR(cd321x->tbt_switch)) { diff --git a/drivers/usb/typec/tipd/spmi.c b/drivers/usb/typec/tipd/spmi.c index ba89b4b57a255..4f0ceca25ee7f 100644 --- a/drivers/usb/typec/tipd/spmi.c +++ b/drivers/usb/typec/tipd/spmi.c @@ -318,9 +318,14 @@ static int sn201202x_probe(struct spmi_device *device) return ret; ret = tipd_init(tps); - if (ret) + if (ret) { spmi_command_sleep(device); - return ret; + return ret; + } + + /* Events can latch before the IRQ is registered. Consume that state now. */ + tps->data->irq_handler(tps->irq, tps); + return 0; } static void sn201202x_remove(struct spmi_device *device) diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index caac3cb771bd4..f6d51724a1f5d 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -371,6 +371,7 @@ struct tps6598x { const struct tipd_data *data; struct tipd_debugfs *debugfs; + struct typec_altmode *partner_altmode_dp; }; /* TPS_REG_USB4_STATUS */ diff --git a/include/linux/soc/apple/clpc.h b/include/linux/soc/apple/clpc.h new file mode 100644 index 0000000000000..37b2cfce5594a --- /dev/null +++ b/include/linux/soc/apple/clpc.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef _LINUX_SOC_APPLE_CLPC_H +#define _LINUX_SOC_APPLE_CLPC_H + +#include +#include +#include + +struct apple_clpc_gpu; + +#if IS_REACHABLE(CONFIG_APPLE_CLPC) +int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out); +void apple_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active); +void apple_clpc_gpu_put(struct apple_clpc_gpu *gpu); +#else +static inline int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) +{ + *out = NULL; + return device_property_present(dev, "apple,clpc") ? -ENODEV : 0; +} +static inline void apple_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active) {} +static inline void apple_clpc_gpu_put(struct apple_clpc_gpu *gpu) {} +#endif +#endif diff --git a/include/linux/soc/apple/dp-tunnel.h b/include/linux/soc/apple/dp-tunnel.h index 868de62db1156..540d48fe8e0dc 100644 --- a/include/linux/soc/apple/dp-tunnel.h +++ b/include/linux/soc/apple/dp-tunnel.h @@ -61,21 +61,6 @@ int apple_atc_dp_tunnel_rate_source(struct phy *phy, unsigned int source, */ int apple_atc_dp_tunnel_open(struct phy *phy); -/* - * T6040 Type-C PHY: true while the Type-C controller holds the PHY's port in - * DisplayPort alt mode with pin assignment C or E (DP on all four lanes), so - * appledrm can route a DCP that lists several Type-C DP PHYs to the one that - * has a DisplayPort sink plugged. - */ -#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) -bool apple_t6040_phy_dp_alt(struct phy *phy); -#else -static inline bool apple_t6040_phy_dp_alt(struct phy *phy) -{ - return false; -} -#endif - /* * Display crossbar: take the connection of an already selected output down or * bring it back up (clock gates and enables) around a link reconfiguration. diff --git a/include/uapi/drm/asahi_drm.h b/include/uapi/drm/asahi_drm.h index 66396f89cc333..66d5188199383 100644 --- a/include/uapi/drm/asahi_drm.h +++ b/include/uapi/drm/asahi_drm.h @@ -368,6 +368,13 @@ enum drm_asahi_feature { * DRM_ASAHI_COMPUTE_AS_BUILD for a bounded long execution allowance. */ DRM_ASAHI_FEATURE_BOUNDED_AS_BUILD = (1UL) << 16, + + /** + * @DRM_ASAHI_FEATURE_CUMULATIVE_SIGNALS: SUBMIT accepts one + * DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ_CUMULATIVE output. All outputs + * include the earlier points of that timeline without adding GPU waits. + */ + DRM_ASAHI_FEATURE_CUMULATIVE_SIGNALS = (1UL) << 29, }; #define DRM_ASAHI_PARAM_GROUP_VM_STATUS 1 @@ -901,6 +908,16 @@ enum drm_asahi_sync_type { /** @DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ: Timeline sync object */ DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ = 1, + + /** + * @DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ_CUMULATIVE: Output-only queue + * timeline anchor, requiring CUMULATIVE_SIGNALS and a nonzero point. + * At most one is allowed. All outputs receive completion of this submit + * and the earlier anchor points. + * Other outputs must not alias the anchor. No execution dependency is + * added. Calls updating this private anchor must be externally serialized. + */ + DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ_CUMULATIVE = 2, }; /** diff --git a/include/uapi/linux/apple-sep.h b/include/uapi/linux/apple-sep.h index 394b497ff7a59..fbae982a4c0ef 100644 --- a/include/uapi/linux/apple-sep.h +++ b/include/uapi/linux/apple-sep.h @@ -14,7 +14,7 @@ #include #include -#define SEP_BIO_IFACE_VERSION 4 +#define SEP_BIO_IFACE_VERSION 5 #define SEP_BIO_UUID_LEN 16 #define SEP_BIO_LABEL_LEN 128 @@ -64,10 +64,22 @@ struct sep_bio_list { struct sep_bio_enrol_start { __u32 flags; + __u32 account_uid; /* resolved passwd uid; UINT32_MAX is invalid */ + __u32 finger; /* FP_FINGER_LEFT_THUMB..FP_FINGER_RIGHT_LITTLE (1..10) */ __u32 reserved; __u8 label[SEP_BIO_LABEL_LEN]; }; +/* Linux's built-in SEP user context only. This does not enumerate other SEP + * users or OS lanes. DELETE_ORPHAN uses an exact UUID; the kernel index may + * still hold it when fprintd has already lost its own record. + */ +struct sep_bio_enclave_list { + __u32 count; + __u32 reserved; + __u8 uuid[SEP_BIO_MAX_IDENTITIES][SEP_BIO_UUID_LEN]; +}; + #define SEP_BIO_GUIDANCE_NONE 0 #define SEP_BIO_GUIDANCE_PLACE 1 #define SEP_BIO_GUIDANCE_LIFT_AND_MOVE 2 @@ -129,5 +141,11 @@ struct sep_bio_attest { #define SEP_BIO_DELETE _IOW(SEP_BIO_IOC_MAGIC, 0x08, struct sep_bio_delete) #define SEP_BIO_DELETE_ALL _IO(SEP_BIO_IOC_MAGIC, 0x09) #define SEP_BIO_ATTEST _IOWR(SEP_BIO_IOC_MAGIC, 0x0a, struct sep_bio_attest) +#define SEP_BIO_LIST_ENCLAVE _IOR(SEP_BIO_IOC_MAGIC, 0x0b, struct sep_bio_enclave_list) +#define SEP_BIO_DELETE_ORPHAN _IOW(SEP_BIO_IOC_MAGIC, 0x0c, struct sep_bio_delete) +/* Creates the empty Linux owner context only when SEP lists zero Linux + * identities. ENROL_START itself never repairs a missing persistence binding. + */ +#define SEP_BIO_PREPARE_ENROL _IOW(SEP_BIO_IOC_MAGIC, 0x0d, struct sep_bio_enrol_start) #endif /* _UAPI_LINUX_APPLE_SEP_H */ diff --git a/rust/Makefile b/rust/Makefile index 410987a88f7a8..8f45c44177693 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -513,16 +513,10 @@ endif # architecture instead of generating `usize`. bindgen_c_flags_final = $(bindgen_c_flags_lto) -fno-builtin -D__BINDGEN__ -# Layout assertions are off by default, as upstream: stock bindgen 0.73 -# computes a different total size for a few kernel types, so the bindings -# would not build with it. `make BINDGEN_LAYOUT_TESTS=1` keeps them, for -# checking a bindings generator; see -# Documentation/rust/bindgen-layout-checks.rst. -ifeq ($(BINDGEN_LAYOUT_TESTS),1) -bindgen_layout_flags := -else -bindgen_layout_flags := --no-layout-tests -endif +# Keep bindgen's unconditional layout assertions in every build. An empty, +# cacheline-aligned sched_statistics (CONFIG_SCHEDSTATS=n) exposes a generator +# bug that shifts task_struct.mm, so even pointer field access can be unsafe. +# See Documentation/rust/bindgen-layout-checks.rst for the corrected generator. # `--rust-target` points to our minimum supported Rust version. # Keep bindgen layout assertions enabled: a lost alignment can change field @@ -532,7 +526,6 @@ quiet_cmd_bindgen = BINDGEN $@ cmd_bindgen = \ $(BINDGEN) $< $(bindgen_target_flags) --rust-target 1.85 \ --use-core --with-derive-default --ctypes-prefix ffi \ - $(bindgen_layout_flags) \ --no-debug '.*' --enable-function-attribute-detection \ -o $@ -- $(bindgen_c_flags_final) -DMODULE \ $(bindgen_target_cflags) $(bindgen_target_extra) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index e54bf419c7a66..f047b90e7b04a 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -268,7 +268,6 @@ impl Device { } /// Returns a reference to the parent device, if any. - #[cfg_attr(not(CONFIG_AUXILIARY_BUS), expect(dead_code))] pub fn parent(&self) -> Option<&Device> { // SAFETY: // - By the type invariant `self.as_raw()` is always valid. diff --git a/rust/kernel/drm/syncobj.rs b/rust/kernel/drm/syncobj.rs index a022e08223588..72ba50c57286b 100644 --- a/rust/kernel/drm/syncobj.rs +++ b/rust/kernel/drm/syncobj.rs @@ -15,6 +15,14 @@ pub struct SyncObj { } impl SyncObj { + /// Borrows the raw sync object for synchronous calls into DRM helpers. + /// The caller must not release the owned reference or retain this pointer + /// beyond the lifetime of `self` without taking another reference. + #[inline(always)] + pub fn as_raw(&self) -> *mut bindings::drm_syncobj { + self.ptr + } + /// Looks up a sync object by its handle for a given `File`. pub fn lookup_handle( file: &drm::File, diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 1e70f5d365e23..1d1c8924eb2fb 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -62,6 +63,7 @@ #define MACAUDIO_NUM_FE_LINKS 3 /* milliseconds */ #define SPEAKER_LOCK_TIMEOUT 250 +#define MACAUDIO_MAX_SPEAKER_ID_GPIOS 8 enum macaudio_amp_type { AMP_NONE, @@ -166,6 +168,7 @@ struct macaudio_snd_data { bool has_sense; bool has_safety; unsigned int max_channels; + struct gpio_descs *speaker_id_gpios; struct macaudio_link_props { /* frontend props */ @@ -1440,6 +1443,50 @@ static const struct snd_soc_jack_pin macaudio_jack_pins[] = { }, }; +static int macaudio_speaker_id_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = BIT(ma->speaker_id_gpios->ndescs) - 1; + uinfo->value.integer.step = 1; + + return 0; +} + +static int macaudio_speaker_id_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + unsigned int value = 0; + unsigned int i; + int ret; + + for (i = 0; i < ma->speaker_id_gpios->ndescs; i++) { + ret = gpiod_get_value_cansleep(ma->speaker_id_gpios->desc[i]); + if (ret < 0) + return ret; + if (ret) + value |= BIT(i); + } + uvalue->value.integer.value[0] = value; + + return 0; +} + +static const struct snd_kcontrol_new macaudio_speaker_id_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Speaker Variant ID", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = macaudio_speaker_id_info, + .get = macaudio_speaker_id_get, +}; + static int macaudio_probe(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -1594,6 +1641,13 @@ static int macaudio_late_probe(struct snd_soc_card *card) mutex_unlock(&ma->volume_lock_mutex); } + /* Append identity metadata without renumbering the existing controls. */ + if (ma->speaker_id_gpios) { + ret = snd_soc_add_card_controls(card, &macaudio_speaker_id_control, 1); + if (ret) + return ret; + } + /* The notifier walks the card's controls: only while the card is bound */ return register_pm_notifier(&ma->pm_nb); } @@ -2207,6 +2261,16 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) if (ret) return ret; + data->speaker_id_gpios = devm_gpiod_get_array_optional(dev, "speaker-id", + GPIOD_IN); + if (IS_ERR(data->speaker_id_gpios)) + return dev_err_probe(dev, PTR_ERR(data->speaker_id_gpios), + "Failed to get speaker ID inputs\n"); + if (data->speaker_id_gpios && + (!data->speaker_id_gpios->ndescs || + data->speaker_id_gpios->ndescs > MACAUDIO_MAX_SPEAKER_ID_GPIOS)) + return dev_err_probe(dev, -EINVAL, "Invalid speaker ID input count\n"); + /* * Without sense the PCM2 link is gone, and a card whose front-ends * are not the MCA's (the J616 jack) has no PCM2 RX widget at all. diff --git a/tools/asahi/clpc-gpu-demand-tests.py b/tools/asahi/clpc-gpu-demand-tests.py new file mode 100644 index 0000000000000..ecf0fff465711 --- /dev/null +++ b/tools/asahi/clpc-gpu-demand-tests.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Run production CLPC demand/hold logic with controlled PMGR reads and time.""" +from pathlib import Path +import os +import re +import subprocess +import tempfile + +ROOT = Path(os.environ.get('CLPC_ROOT', Path(__file__).resolve().parents[2])) + + +def item(text, signature): + start = text.index(signature) + begin = text.index('{', start) + depth, end = 1, begin + 1 + while depth: + depth += (text[end] == '{') - (text[end] == '}') + end += 1 + return text[start:end] + + +def main(): + source = (ROOT / 'drivers/soc/apple/clpc.c').read_text() + header = (ROOT / 'include/linux/soc/apple/pmc.h').read_text() + rail_shift = re.search(r'^#define APPLE_PMC_RAIL_SHIFT\(rail\).*$', header, re.M).group(0) + types = '\n'.join(item(source, s) + ';' for s in [ + 'struct apple_clpc_hw {', 'struct apple_clpc_vote {']) + tables = '\n'.join(item(source, 'static const struct apple_clpc_hw ' + n) + ';' + for n in ['apple_clpc_hw_t8132', 'apple_clpc_hw_t8140', 'apple_clpc_hw_t6040']) + methods = '\n'.join(item(source, s) for s in [ + 'static unsigned int apple_clpc_vote(', 'static u32 apple_clpc_floor(', + 'static bool apple_clpc_gpu_streaming(', + 'static bool apple_clpc_gpu_limited(', + 'static void apple_clpc_sample(']) + code = r''' +#include +#include +#include +#include +#include +typedef uint8_t u8; +typedef uint32_t u32; +typedef uint64_t u64; +#define APPLE_CLPC_LEVELS 4 +#define APPLE_CLPC_HOLD_MS 32 +#define APPLE_CLPC_SAMPLE_MS 4 +#define APPLE_CLPC_GPU_BACKOFF_MS 100 +#define HZ 1000 +#define max(a,b) ((a) > (b) ? (a) : (b)) +#define GENMASK(h,l) (((1u << ((h)-(l)+1))-1) << (l)) +#define APPLE_PMC_RAIL_SHIFT(r) ((r)*8) +#define time_after_eq(a,b) ((long)((a)-(b)) >= 0) +#define time_after(a,b) ((long)((a)-(b)) > 0) +#define time_before(a,b) ((long)((a)-(b)) < 0) +#define msecs_to_jiffies(ms) (ms) +enum apple_clpc_rail { APPLE_CLPC_DCS, APPLE_CLPC_SOC, APPLE_CLPC_RAILS }; +''' + types + r''' +struct apple_clpc_gpu { bool active; }; +struct apple_clpc_cluster { unsigned int request[APPLE_CLPC_RAILS]; }; +struct apple_clpc { + const struct apple_clpc_hw *hw; + struct apple_clpc_cluster clusters[1]; + struct apple_clpc_vote vote[APPLE_CLPC_RAILS]; + unsigned int nr_clusters, agent; + unsigned long samples, next_scan, update_errors; + void *covered, *pmc, *gpu_pmgr; + u32 gpu_offset, mask, last_floor, last_reason; + bool gpu_on, gpu_limited; + unsigned long gpu_on_since, gpu_sampled, gpu_limited_at; + u64 last_vote_ns; + struct apple_clpc_gpu *gpu; +}; +static unsigned long jiffies; +static u64 ktime_get_mono_fast_ns(void) { return jiffies * 1000000ull; } +static bool apple_clpc_gpu_active(struct apple_clpc_gpu *g, u64 *u, u64 *s) { *u=jiffies; *s=1; return g->active; } +static void *cpu_possible_mask; +static u32 pmgr_status, pmp_floor, published; +static int read_error, vote_error; +static bool queued_idle; +static bool cpumask_equal(void *a, void *b) { return a == b; } +static void apple_clpc_find_clusters(struct apple_clpc *c) { (void)c; } +static void apple_clpc_sample_cluster(struct apple_clpc *c, struct apple_clpc_cluster *cl) { (void)c; (void)cl; } +static int regmap_read(void *map, u32 offset, u32 *status) { + assert(map); (void)offset; *status=pmgr_status; return read_error; +} +static u32 apple_pmc_floor_read(void *pmc, unsigned int agent) { + (void)pmc; assert(agent==0); return pmp_floor; +} +static int apple_pmc_floor_update(void *pmc, unsigned int agent, u32 mask, u32 floor) { + (void)pmc; assert(agent==1 && mask==3); published=floor; return vote_error; +} +static void apple_clpc_queue(struct apple_clpc *c, bool idle) { (void)c; queued_idle=idle; } +''' + tables + '\n' + methods + r''' +static void check_t8132(void) { + struct apple_clpc c={.hw=&apple_clpc_hw_t8132,.agent=1,.mask=3,.gpu_pmgr=(void *)1}; + read_error=vote_error=0; pmgr_status=0; pmp_floor=0; jiffies=0; + apple_clpc_sample(&c); assert(published==0 && queued_idle); + jiffies=100; pmgr_status=0xf0; + apple_clpc_sample(&c); assert(published==0x35 && c.last_reason==4); + jiffies=131; pmgr_status=0; apple_clpc_sample(&c); + assert(published==0x35 && c.last_reason==16); + jiffies=132; apple_clpc_sample(&c); assert(published==0 && queued_idle); + // A failed power-state read still protects an active graphics consumer. + jiffies=200; pmgr_status=0xe0; read_error=-EIO; + apple_clpc_sample(&c); assert(published==0x35); + jiffies=232; read_error=0; apple_clpc_sample(&c); assert(published==0); +} + +static void check_t8140(void) { + struct apple_clpc c={.hw=&apple_clpc_hw_t8140,.agent=1,.mask=3,.gpu_pmgr=(void *)1}; + read_error=vote_error=0; pmp_floor=4; pmgr_status=0xf0; + for (jiffies=100; jiffies<=164; jiffies+=4) { + apple_clpc_sample(&c); + assert(published==(jiffies<164 ? 0 : 0x34)); + } + // The PMP's lower DCS vote suppresses the GPU floor during heavy work. + pmp_floor=3; + for (jiffies=168; jiffies<=204; jiffies+=4) apple_clpc_sample(&c); + assert(published==0 && c.gpu_limited && !queued_idle); + pmp_floor=4; + for (jiffies=208; jiffies<=304; jiffies+=4) apple_clpc_sample(&c); + assert(published==0x34 && c.last_reason==4); + jiffies=308; pmgr_status=0; apple_clpc_sample(&c); + jiffies=340; apple_clpc_sample(&c); assert(published==0 && queued_idle); +} +int main(void) { + // T6040's supplier is always powered: it must NEVER substitute for work. + struct apple_clpc_gpu gpu={0}; + struct apple_clpc c={.hw=&apple_clpc_hw_t6040,.agent=1,.mask=3,.gpu=&gpu}; + assert(!c.hw->gpu_demand); + jiffies=0; read_error=vote_error=0; pmgr_status=0xf0; published=~0u; + apple_clpc_sample(&c); assert(published==0 && queued_idle && c.last_reason==0); + gpu.active=true; jiffies=100; apple_clpc_sample(&c); + assert(published==0x25 && c.last_reason==2 && c.last_floor==0x25); + assert(c.last_vote_ns==100000000ull); + // Core/firmware supplier is still powered, but continuous idle ends demand. + gpu.active=false; jiffies=131; apple_clpc_sample(&c); + assert(published==0x25 && c.last_reason==16); + jiffies=132; apple_clpc_sample(&c); + assert(published==0 && queued_idle && c.last_reason==0); + // A new submission reasserts demand; independent CPU requests survive release. + c.nr_clusters=1; c.clusters[0].request[0]=2; c.clusters[0].request[1]=1; + gpu.active=true; jiffies=200; apple_clpc_sample(&c); + assert(published==0x25 && c.last_reason==3); + gpu.active=false; jiffies=232; apple_clpc_sample(&c); + assert(published==0x12 && c.last_reason==1); + c.clusters[0].request[0]=c.clusters[0].request[1]=0; + jiffies=264; apple_clpc_sample(&c); assert(published==0); + check_t8132(); + check_t8140(); + puts("PASS: T6040 host demand; T8132 powered floor; T8140 streaming and PMP backoff; idle release"); +} +''' + code = code.replace('#define APPLE_PMC_RAIL_SHIFT(r) ((r)*8)', rail_shift) + with tempfile.TemporaryDirectory(prefix='apple-clpc-gpu-') as tmp: + root = Path(tmp) + for name, text in [ + ('fixed', code), + ('omit-host-demand', code.replace('if (clpc->gpu && !clpc->hw->gpu_demand) {', 'if (false) {')), + ('omit-streaming-demand', code.replace('if (clpc->gpu_pmgr && apple_clpc_gpu_streaming(clpc, now) &&', 'if (false && apple_clpc_gpu_streaming(clpc, now) &&')), + ('omit-pmp-backoff', code.replace('!apple_clpc_gpu_limited(clpc, now))', + '(!apple_clpc_gpu_limited(clpc, now) || true))')), + ('release-too-early', code.replace('msecs_to_jiffies(APPLE_CLPC_HOLD_MS)', '0')), + ('retain-at-idle', code.replace('vote->state = 0;', '')), + ]: + path = root / (name + '.c') + path.write_text(text) + binary = path.with_suffix('') + subprocess.run([os.environ.get('CC', 'cc'), '-std=gnu11', '-Wall', '-Wextra', + '-Werror', str(path), '-o', str(binary)], check=True) + run = subprocess.run([str(binary)], capture_output=True, text=True) + if name == 'fixed': + assert run.returncode == 0, run.stderr + print(run.stdout.strip()) + else: + assert run.returncode != 0, name + print('PASS negative control:', name) + + +if __name__ == '__main__': + main() diff --git a/tools/asahi/compute-preempt-tests.py b/tools/asahi/compute-preempt-tests.py index a13f103341b95..fdab1e9e7406b 100755 --- a/tools/asahi/compute-preempt-tests.py +++ b/tools/asahi/compute-preempt-tests.py @@ -48,7 +48,7 @@ def policy(text): pub static compute_preempt: Parameter = Parameter; pub static mut PARAM: i32 = -1; pub struct Parameter; - impl Parameter { pub fn value(&self) -> &i32 { unsafe { &*std::ptr::addr_of!(PARAM) } } } + impl Parameter { pub fn value(&self) -> i32 { unsafe { *std::ptr::addr_of!(PARAM) } } } } #[derive(Clone, Copy, Debug, PartialEq)] struct U64(u64); #[derive(Clone, Copy, Debug, PartialEq)] enum PipeType { Vertex, Fragment, Compute } diff --git a/tools/asahi/g17-admission-deadline-tests.py b/tools/asahi/g17-admission-deadline-tests.py index ac92be72e65ff..b58715f6fcc58 100644 --- a/tools/asahi/g17-admission-deadline-tests.py +++ b/tools/asahi/g17-admission-deadline-tests.py @@ -89,6 +89,9 @@ def fixture(): reset(mode);let pool=new_pool(false,2); assert!(matches!(pool.acquire_render(1,None,2_000_000_000,||Ok(())),Err(EIO))); assert!(NOW.load(Ordering::SeqCst)<=1_010_000_000,"quarantine budget extended"); + reset(mode);let pool=new_pool(false,2);pool.state.lock().spared=2; + assert!(matches!(pool.acquire_render(1,None,2_000_000_000,||Ok(())),Err(EBUSY))); + assert!(NOW.load(Ordering::SeqCst)<=1_010_000_000,"spared quarantine budget extended"); } reset(2);let pool=new_pool(true,0); assert!(matches!(pool.acquire_render(1,Some(Request(&())),2_000_000_000,||Ok(())),Err(ERESTARTSYS))); diff --git a/tools/asahi/g17-capacity-fault-tests.py b/tools/asahi/g17-capacity-fault-tests.py index 09a70caaaccb4..7c33e92a74812 100644 --- a/tools/asahi/g17-capacity-fault-tests.py +++ b/tools/asahi/g17-capacity-fault-tests.py @@ -28,13 +28,13 @@ def fixture(): retirement = (ROOT / 'queue/compute/retirement.rs').read_text() previous = item(source, 'struct Previous<') + '\n' + item(source, 'impl Previous') methods = '\n'.join(item(source, sig) for sig in [ - 'pub(crate) fn release_owner(', 'pub(crate) fn reusable(', + 'fn witness_stamp(', 'pub(crate) fn release_owner(', 'pub(crate) fn reusable(', 'pub(crate) fn recycle_quarantined_idle(', 'pub(crate) fn recycle_killed(', - 'fn recycle_settled(', 'fn release_witnessed_inner(', 'pub(crate) fn share_idle(', 'pub(crate) fn can_rearm(', 'pub(crate) fn recycled_pending(']) + 'fn recycle_settled(', 'fn release_witnessed_inner(', 'pub(crate) fn share_idle(', 'pub(crate) fn shareable(', 'pub(crate) fn can_rearm(', 'pub(crate) fn recycled_pending(']) exit_source = (ROOT / 'queue/compute/exit.rs').read_text() methods += '\n' + item(exit_source, 'pub(crate) fn settle_exit(').replace('super::Previous', 'Previous').replace('crate::g17::qos::Owner', 'qos::Owner') drain = item((ROOT / 'mod.rs').read_text(), 'fn drain_reclaims(') - proof = item(retirement, 'pub(crate) const fn stamp_covers(') + '\n' + item(retirement, 'pub(crate) fn quarantined_retired(') + proof = item(retirement, 'pub(crate) const fn stamp_newer(') + '\n' + item(retirement, 'pub(crate) const fn stamp_covers(') + '\n' + item(retirement, 'pub(crate) fn quarantined_retired(') return r''' #![allow(dead_code)] use std::sync::{Arc, atomic::{AtomicUsize, AtomicU32, Ordering}}; @@ -48,6 +48,7 @@ def fixture(): fn now_ns() -> u64 { 123 } struct Context { id: u64, live: Arc, killed: AtomicU32 } impl Context { + fn note_compute_binding(&self, _: bool) {} fn new(id: u64, live: &Arc) -> Arc { live.fetch_add(1, Ordering::SeqCst); Arc::new(Self { id, live: live.clone(), killed: AtomicU32::new(1) }) @@ -55,14 +56,14 @@ def fixture(): } impl Drop for Context { fn drop(&mut self) { self.live.fetch_sub(1, Ordering::SeqCst); } } struct Binding { context: Arc } -''' + previous + r''' +''' + previous + '\n' + item((ROOT / 'sync_input.rs').read_text(), 'pub(super) struct ReaderHint').replace('pub(super) ', '') + '\nimpl ReaderHint {\n' + item((ROOT / 'sync_input.rs').read_text(), 'pub(super) fn has_readers(').replace('pub(super) ', '') + '\n}\n' + r''' #[derive(Clone, Copy, PartialEq)] enum PoolPhase { Populated, ReleaseSent(u32), Vacant } #[derive(Clone, Copy)] struct Ticket { owner: u64, item_count: u32, descriptor: u64, item_producer: u32, kick: u64, } #[derive(Clone, Copy)] struct Cursors { count: u32, producer: u32, consumer: u32 } ''' + proof + r''' -mod retirement { pub(crate) use super::quarantined_retired; } +mod retirement { pub(crate) use super::{quarantined_retired, stamp_newer}; } struct Graph { cursors: Cursors, stamp: u32, fail: bool, resets: usize, owner: u64 } impl Graph { fn set_owner(&mut self, context: &Context) -> Result { if self.fail { Err(EIO) } else { self.owner = context.id; Ok(()) } } @@ -107,6 +108,7 @@ def fixture(): retired_by_teardown: bool, spared_quarantine: bool, spared_deferred: bool, retire_pending: bool, retirement_proved: bool, replay_retirement: bool, replay_error: Option, replay_prepared: bool, submitted: u32, kick: Kick, + config_cache: Option<()>, source_hint: ReaderHint, source_readers: AtomicU32, claims: u32, fault_stamp: Option, } impl Queue { fn valid_ticket(&self, _: &Active, _: &Ticket) -> Result { Ok(()) } @@ -126,6 +128,7 @@ def fixture(): retired_by_teardown: false, spared_quarantine: false, spared_deferred: false, retire_pending: false, retirement_proved: false, replay_retirement: false, replay_error: None, replay_prepared: false, submitted: 0, kick: Kick, + fault_stamp: None, config_cache: None, source_hint: ReaderHint { possible: false }, source_readers: AtomicU32::new(0), claims: 0, } } fn fault(live: &Arc) -> Queue { @@ -146,20 +149,34 @@ def fixture(): impl Reclaims { fn take_one(&mut self) -> Option { self.0.pop() } } struct Mutex(std::sync::Mutex); impl Mutex { fn lock(&self) -> std::sync::MutexGuard<'_, T> { self.0.lock().unwrap() } } -struct Shared { reclaim: Mutex, wakes: AtomicUsize } +struct Available(AtomicUsize); impl Available {fn notify(&self){self.0.fetch_add(1, Ordering::SeqCst);}} +struct Shared { reclaim: Mutex, wakes: AtomicUsize, queues: Available } impl Shared { fn queue_events(&self) { self.wakes.fetch_add(1, Ordering::SeqCst); } ''' + drain + r''' } fn main() { let finished = Arc::new(AtomicUsize::new(0)); - let shared = Arc::new(Shared { reclaim: Mutex(std::sync::Mutex::new(Reclaims(vec![Reclaim(finished.clone())]))), wakes: AtomicUsize::new(0) }); + let shared = Arc::new(Shared { reclaim: Mutex(std::sync::Mutex::new(Reclaims(vec![Reclaim(finished.clone())]))), wakes: AtomicUsize::new(0), queues: Available(AtomicUsize::new(0)) }); shared.drain_reclaims(); assert_eq!(finished.load(Ordering::SeqCst), 1); assert_eq!(shared.wakes.load(Ordering::SeqCst), 1); shared.drain_reclaims(); assert_eq!(shared.wakes.load(Ordering::SeqCst), 1); + assert_eq!(shared.queues.0.load(Ordering::SeqCst), 1); let live = Arc::new(AtomicUsize::new(0)); + let mut borrowed = idle(&live); + borrowed.retirement_ready = true; + borrowed.source_hint.possible = true; + borrowed.source_readers.store(1, Ordering::Release); + assert!(!borrowed.shareable()); + borrowed.release_owner().unwrap(); + assert!(!borrowed.reusable()); + borrowed.pool_phase = PoolPhase::Vacant; + assert!(!borrowed.can_rearm()); + borrowed.source_readers.store(0, Ordering::Release); + assert!(borrowed.reusable() && borrowed.can_rearm()); + drop(borrowed); for _ in 0..512 { // Rebound detached queue; lost VM / killed publisher / allocation error // before the first new command. No future completion exists to free it. @@ -266,8 +283,8 @@ def main(): ('missing-fault-release', source.replace(fault_fix, '', 1), False), ('missing-kill-release', source.replace('Previous::retire_witnessed(&mut self.previous);\n self.exit', 'self.exit'), False), ('missing-reclaim-wake', source.replace('self.queue_events();', ''), False), - ('share-before-retirement', source.replace('|| !self.retirement_ready', ''), False), - ('share-during-kill', source.replace('|| self.exit.is_some() || self.active.len', '|| self.active.len'), False)] + ('share-before-retirement', source.replace('&& self.owner.is_some() && self.retirement_ready', '&& self.owner.is_some()'), False), + ('share-during-kill', source.replace('&& self.exit.is_none() && self.active.len', '&& self.active.len'), False)] with tempfile.TemporaryDirectory(prefix='g17-capacity-') as tmp: for name, rust, passes in controls: path = Path(tmp) / (name + '.rs') diff --git a/tools/asahi/g17-compute-constructor-tests.py b/tools/asahi/g17-compute-constructor-tests.py index f4fdcf2c68222..8c0c4dc772885 100644 --- a/tools/asahi/g17-compute-constructor-tests.py +++ b/tools/asahi/g17-compute-constructor-tests.py @@ -50,7 +50,7 @@ def fixture(): fn notify_all(&self){} } struct Shared{preparations:Gate,state:State,changed:Changed,crashed:AtomicBool} -impl Shared{fn drm(&self)->Result<()>{Ok(())}} +impl Shared{fn drm(&self)->Result<()>{Ok(())} fn note_changed(&self){self.changed.notify_all();}} struct Context;impl Context{fn vm(&self)->&(){&()}} struct Backend{shared:Shared,context:Context} struct Allocator<'a>{dev:&'a (),uat:&'a ()} diff --git a/tools/asahi/g17-compute-deadline-tests.py b/tools/asahi/g17-compute-deadline-tests.py index eca8b73c5b12c..88c89f6857799 100644 --- a/tools/asahi/g17-compute-deadline-tests.py +++ b/tools/asahi/g17-compute-deadline-tests.py @@ -49,7 +49,7 @@ def fixture(): static MODE:AtomicUsize=AtomicUsize::new(0);static CANCEL:AtomicU64=AtomicU64::new(u64::MAX); static STATUS:AtomicU64=AtomicU64::new(u64::MAX);static CLOSED:AtomicBool=AtomicBool::new(false); fn now_ns()->u64{NOW.load(Ordering::SeqCst)} -fn advance(ns:u64){NOW.fetch_add(ns,Ordering::SeqCst);} +fn advance(ns:u64){NOW.fetch_add(ns,Ordering::SeqCst);assert!(now_ns()<=6_000_000_000,"admission budget exceeded");} mod kernel{pub mod time{ pub fn msecs_to_jiffies(ms:u32)->u32{ms} pub struct Delta(pub u64);impl Delta{pub fn from_millis(ms:u64)->Self{Self(ms*1_000_000)}pub fn from_micros(us:i64)->Self{Self(us as u64*1_000)}} @@ -76,20 +76,33 @@ def fixture(): impl Packet{fn check_dependencies(&self)->Result{Ok(())}fn cancel_requested(&self)->bool{now_ns()>=CANCEL.load(Ordering::SeqCst)}fn is_published(&self)->bool{false}} struct DeferredBatch;impl DeferredBatch{fn publication()->Result{Ok(Self)}fn finish(&mut self){}} struct Recovery;impl Recovery{fn pending(&self)->bool{MODE.load(Ordering::SeqCst)==1}} -struct Memory;impl Memory{fn recovery_state(_:&())->Result{Ok(0)}} +struct Init;impl Init{fn compute_vm_cached(&self,_:&())->bool{true}} +struct Context;impl Context{fn vm(&self)->&(){&()}} +struct Memory;impl Memory{fn recovery_state(_:&Init)->Result{Ok(0)}} struct Queues; impl Queues{ + fn compute_bound(&self,_:u64,_:&Context,_:usize)->Result{Ok(MODE.load(Ordering::SeqCst)!=0)} fn compute(&self,_:u64)->Option<()>{if MODE.load(Ordering::SeqCst)==0{None}else{Some(())}} fn render_prefix(&self,_:u64,_:&Arc)->Result>{Ok(vec![])} - fn release_compute_witness(&self,_:&(),_:u64,_:&mut DeferredBatch)->Result{Ok(MODE.load(Ordering::SeqCst)==2)} + fn release_compute_witness(&self,_:&Init,_:&(),_:u64,_:&mut DeferredBatch)->Result{Ok(MODE.load(Ordering::SeqCst)==2)} } -struct Firmware{recovery:Recovery,init:(),queues:Queues} +struct Firmware{recovery:Recovery,init:Init,queues:Queues,port:()} impl Firmware{fn replay_compute(&mut self,_:&mut DeferredBatch)->Result{Ok(())}fn publish_compute(&mut self,_:u64,_:Arc,_:&[()],_:&mut DeferredBatch)->Result{Err(EAGAIN)}} struct State(Mutex>>);impl State{fn lock(&self)->MutexGuard<'_,Option>>{self.0.lock().unwrap()}} -struct Shared{preparations:Arc,state:State,crashed:AtomicBool,active_cores:u32}impl Shared{fn queue_events(&self){}} -struct Backend{shared:Shared,owner:u64} +struct Available;impl Available{fn observe(&self)->u64{now_ns()}} +struct Shared{preparations:Arc,state:State,crashed:AtomicBool,active_cores:u32,progress:Available}impl Shared{fn queue_events(&self){}} +struct ComputeDemand;impl ComputeDemand{fn new(_:&Shared)->Self{Self}} +struct Backend{shared:Shared,owner:u64,context:Context} fn required_blocks(_:u32,_:u16)->Result{Ok(1)} impl Backend{ + fn compute_direct(&self,_:&Arc,_:usize,_:&mut DeferredBatch)->Option{None} + fn sources_published(&self,_:&Arc)->Result{Ok(true)} + fn remember_direct(&self,_:&Queues){} + fn wait_release(&self,_:&Available,_:u64,admission:&Admission<'_>)->Result{ + let _=admission.slice_ms()?; + advance(1_000_000); + admission.check() + } fn admission_open(&self)->Result{Ok(())} // The real ensure_compute forwards the original context into this allocation // boundary. Each success loses its binding before the publication mutex. @@ -99,7 +112,7 @@ def fixture(): ''' + ensure + '\n' + packet + r''' } fn main(){ - let b=Backend{owner:1,shared:Shared{active_cores:1,preparations:Arc::new(Gate{wait:Wait,changed:Changed}),state:State(Mutex::new(Some(Box::new(Firmware{recovery:Recovery,init:(),queues:Queues})))),crashed:AtomicBool::new(false)}}; + let b=Backend{owner:1,context:Context,shared:Shared{active_cores:1,progress:Available,preparations:Arc::new(Gate{wait:Wait,changed:Changed}),state:State(Mutex::new(Some(Box::new(Firmware{recovery:Recovery,init:Init,queues:Queues,port:()})))),crashed:AtomicBool::new(false)}}; let p=Arc::new(Packet{command:Validated::Compute{scratch:Scratch},completion:Completion}); for mode in 0..4 { MODE.store(mode,Ordering::SeqCst);NOW.store(0,Ordering::SeqCst);CALLS.store(0,Ordering::SeqCst); @@ -144,8 +157,8 @@ def main(): source = fixture() mutants = [ ('fixed', source, ''), - ('renew-on-binding-loss', source.replace('let admission = Admission::new(deadline, &check);\n loop {', 'loop {\n let admission = Admission::new(now_ns().saturating_add(WORK_RING_WAIT_NS), &check);'), 'one admission budget'), - ('omit-nested-check', source.replace(' admission.check()?;\n let pending', ' let pending'), 'nested unavailable-capacity'), + ('renew-on-binding-loss', source.replace('let admission = Admission::new(deadline, &check);\n let progress = &self.shared.progress;\n loop {', 'let progress = &self.shared.progress;\n loop {\n let admission = Admission::new(now_ns().saturating_add(WORK_RING_WAIT_NS), &check);'), 'admission budget exceeded'), + ('ignore-deadline', source.replace('if now_ns() >= self.deadline', 'if false'), 'admission budget exceeded'), ('omit-gate-check', source.replace(' admission.check()?;\n match self.try_enter()', ' match self.try_enter()').replace('admission.slice_ms()?', '10'), 'gate wake storm'), ] # Bind the old unbounded mutant with the review's eventual cancellation. diff --git a/tools/asahi/g17-compute-growth-deadline-tests.py b/tools/asahi/g17-compute-growth-deadline-tests.py index 1e1e715de8377..3c652a96df759 100644 --- a/tools/asahi/g17-compute-growth-deadline-tests.py +++ b/tools/asahi/g17-compute-growth-deadline-tests.py @@ -50,7 +50,8 @@ def fixture(): impl Lease{fn is_current(&self)->bool{true}} struct State(Mutex>>); impl State{fn lock(&self)->MutexGuard<'_,Option>>{self.0.lock().unwrap()}} -struct Shared{preparations:Gate,state:State,crashed:AtomicBool} +struct Available;impl Available{fn observe(&self)->u64{now_ns()}fn notify(&self){}} +struct Shared{preparations:Gate,state:State,crashed:AtomicBool,progress:Available,channels:Available} impl Shared{fn drm(&self)->Result<()>{Ok(())}fn queue_events(&self){}} struct Context;impl Context{fn vm(&self)->&(){&()}} struct Backend{shared:Shared,context:Context,owner:u64} @@ -85,12 +86,17 @@ def fixture(): impl Firmware{fn scan_compute(&mut self,_:Option<()>,_:&mut DeferredBatch)->Result{Ok(())}} struct DeferredBatch;impl DeferredBatch{fn publication()->Result{Ok(Self)}fn finish(&mut self){}} impl Backend{ + fn wait_release(&self,_:&Available,_:u64,admission:&Admission<'_>)->Result{ + let _=admission.slice_ms()?; + kernel::time::delay::fsleep(kernel::time::Delta::from_millis(1)); + admission.check() + } ''' + methods + r''' } fn main(){ for fresh in [false,true]{for cancel in [false,true]{ NOW.store(0,Ordering::SeqCst);ABORTED.store(false,Ordering::SeqCst);RESERVED.store(true,Ordering::SeqCst); - let b=Backend{owner:1,context:Context,shared:Shared{preparations:Gate,crashed:AtomicBool::new(false),state:State(Mutex::new(Some(Box::new(Firmware{uat:(),init:(),primary:Primary,queues:Registry{compute:vec![Some(Entry{reservation:ComputeReservation,queue:Some(Box::new(Queue{fresh}))})]}}))))}}; + let b=Backend{owner:1,context:Context,shared:Shared{preparations:Gate,progress:Available,channels:Available,crashed:AtomicBool::new(false),state:State(Mutex::new(Some(Box::new(Firmware{uat:(),init:(),primary:Primary,queues:Registry{compute:vec![Some(Entry{reservation:ComputeReservation,queue:Some(Box::new(Queue{fresh}))})]}}))))}}; let check=||{if cancel&&now_ns()>=10_000_000{Err(ECANCELED)}else{Ok(())}}; let admission=Admission::new(20_000_000,&check); assert_eq!(b.grow_compute(42,&admission),Err(if cancel{ECANCELED}else{EBUSY}),"growth must propagate cancel/deadline"); @@ -106,7 +112,7 @@ def fixture(): def main(): source = fixture() mutants = [('fixed', source, ''), - ('omit-growth-check', source.replace(' admission.check()?;\n let firmware', ' let firmware'), 'growth admission exceeded'), + ('ignore-shared-deadline', source.replace('if now_ns() >= self.deadline', 'if false').replace('if remaining == 0 { return Err(EBUSY); }', ''), 'growth admission exceeded'), ('keep-growth-alias', source.replace(' drop(alias.take());', ''), 'growth aliases must precede')] with tempfile.TemporaryDirectory(prefix='g17-compute-growth-') as tmp: for name, code, expected in mutants: diff --git a/tools/asahi/g17-firmware-selection-tests.py b/tools/asahi/g17-firmware-selection-tests.py new file mode 100644 index 0000000000000..771c9c19c6e1e --- /dev/null +++ b/tools/asahi/g17-firmware-selection-tests.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Compile the G17 build selector with a modelled image-identity result. + +The shared fwid KUnit tests check the actual SHA-256 normalization. This host +check ensures OS metadata cannot admit a different image or veto a known one. +""" + +from pathlib import Path +import subprocess +import tempfile + +DRIVER = Path(__file__).resolve().parents[2] / "drivers/gpu/drm/asahi" + + +def method(source, signature): + start = source.index(signature) + end = source.index("{", start) + 1 + depth = 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[start:end] + + +HEADER = r''' +#![allow(dead_code)] +type Result = std::result::Result; +const ENODEV: i32 = -19; +macro_rules! dev_info { ($($t:tt)*) => { () }; } +macro_rules! dev_err { ($($t:tt)*) => { () }; } +struct AsahiDrmDevice { + version: Option<[u32; 3]>, + image: u32, + checked: std::cell::RefCell>, +} +mod fw { + pub struct Variant(pub u32); + pub mod variant { + use super::Variant; + pub struct Build { + pub os_version: [u32; 3], + pub name: &'static str, + pub image: u32, + pub variant: &'static Variant, + } + pub enum Firmware { Fixed(&'static Variant), Identified(&'static [Build]) } + } +} +struct Gpu; +impl Gpu { + fn os_firmware_version(dev: &AsahiDrmDevice) -> Option<[u32; 3]> { dev.version } + fn identify_image(dev: &AsahiDrmDevice, build: &'static fw::variant::Build) -> Result { + dev.checked.borrow_mut().push(build.image); + if dev.image == build.image { Ok(()) } else { Err(ENODEV) } + } +''' + +TESTS = r''' +} +static FIRST: fw::Variant = fw::Variant(1); +static SECOND: fw::Variant = fw::Variant(2); +static BUILDS: [fw::variant::Build; 2] = [ + fw::variant::Build { os_version: [26,6,2], name: "first", image: 1, variant: &FIRST }, + fw::variant::Build { os_version: [27,0,0], name: "second", image: 2, variant: &SECOND }, +]; +fn device(version: Option<[u32; 3]>, image: u32) -> AsahiDrmDevice { + AsahiDrmDevice { version, image, checked: std::cell::RefCell::new(Vec::new()) } +} +fn main() { + use fw::variant::Firmware; + for version in [None, Some([26,6,2]), Some([26,7,0]), Some([99,0,0])] { + let dev = device(version, 1); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0, 1); + } + let dev = device(Some([27,0,0]), 1); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0, 1); + assert_eq!(*dev.checked.borrow(), [2,1], "the OS version only orders the candidates"); + let dev = device(Some([26,6,2]), 9); + assert!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).is_err()); + assert_eq!(*dev.checked.borrow(), [1,2]); + let dev = device(None, 9); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Fixed(&SECOND)).unwrap().0, 2); + assert!(dev.checked.borrow().is_empty(), "fixed Neo admission is unchanged"); + println!("PASS G17 selection: exact image required, OS hint optional, fixed Neo unchanged"); +} +''' + + +def main(): + source = (DRIVER / "g17/mod.rs").read_text() + selector = method(source, " fn firmware_variant(") + with tempfile.TemporaryDirectory(prefix="asahi-firmware-selection-") as tmp: + root = Path(tmp) + (root / "main.rs").write_text(HEADER + selector + TESTS) + binary = root / "test" + subprocess.run(["rustc", "--edition=2021", "-Dwarnings", str(root / "main.rs"), + "-o", str(binary)], check=True) + subprocess.run([str(binary)], check=True) + + +if __name__ == "__main__": + main() diff --git a/tools/asahi/g17-growth-gate-tests.py b/tools/asahi/g17-growth-gate-tests.py index d6ad5f9beef30..8b4285782f25b 100644 --- a/tools/asahi/g17-growth-gate-tests.py +++ b/tools/asahi/g17-growth-gate-tests.py @@ -38,6 +38,7 @@ def fixture(): struct Gate{state:AtomicU64,wait:Mutex,changed:CondVar} struct Lease{gate:Arc,epoch:u64} impl Gate { + fn wake_waiters(&self) {} ''' + methods + '\n}\n' + item(gate,'impl Drop for Lease') + r''' struct Backend {gate:Arc,ready:mpsc::Sender<()>,proceed:StdMutex>} impl Backend { diff --git a/tools/asahi/g17-growth-tests.py b/tools/asahi/g17-growth-tests.py index 1236f166876af..2c46ccbf6359a 100644 --- a/tools/asahi/g17-growth-tests.py +++ b/tools/asahi/g17-growth-tests.py @@ -23,7 +23,7 @@ def fixture(): const EIO:i32=-5;const EBUSY:i32=-16;const EINVAL:i32=-22; fn now_ns()->u64{123} struct Context {live:Arc} -impl Context {fn new(live:&Arc)->Arc{live.fetch_add(1,Ordering::SeqCst);Arc::new(Self{live:live.clone()})}} +impl Context {fn note_compute_binding(&self,_:bool){} fn new(live:&Arc)->Arc{live.fetch_add(1,Ordering::SeqCst);Arc::new(Self{live:live.clone()})}} impl Drop for Context {fn drop(&mut self){self.live.fetch_sub(1,Ordering::SeqCst);}} struct Binding {context:Arc,pool_alias:Option,descriptors:Option} struct Previous {context:Arc,binding:Option,publication:Option<(u64,u64)>} @@ -31,17 +31,19 @@ def fixture(): struct Fifo {len:usize} struct Graph; impl Graph {fn set_owner(&mut self,_:&Context)->Result{Ok(())}} +struct ReaderHint; impl ReaderHint {fn has_readers(&self,count:&AtomicU32)->bool{count.load(Ordering::Acquire)!=0}} struct Queue { owner:Option,installed:bool,growing:bool,growth_blocks:usize,free_list_generation:u64, pool_phase:PoolPhase,release_sent_ns:Option,pool:Pool,binding:Option,previous:Option,graph:Graph, active:Fifo,replays:Fifo,quarantined:bool,released:bool,retirement_ready:bool, retired_by_teardown:bool,released_at:u64,exit:Option<()>,needs_fresh_backing:bool, + source_hint:ReaderHint,source_readers:AtomicU32, } fn idle(live:&Arc)->Queue{Queue{owner:Some(1),installed:true,growing:false,growth_blocks:0, free_list_generation:0,pool_phase:PoolPhase::Populated,release_sent_ns:None,pool:Pool{pages:Some(Pages(10)),fail:false}, binding:Some(Binding{context:Context::new(live),pool_alias:Some(1),descriptors:Some(2)}),previous:None,graph:Graph, active:Fifo{len:0},replays:Fifo{len:0},quarantined:false,released:false,retirement_ready:true, - retired_by_teardown:false,released_at:0,exit:None,needs_fresh_backing:false}} + retired_by_teardown:false,released_at:0,exit:None,needs_fresh_backing:false,source_hint:ReaderHint,source_readers:AtomicU32::new(0)}} impl Queue { ''' + item(source,'pub(crate) fn release_owner(') + item(source,'pub(crate) fn reusable(') + '\n}\n' methods = '\n'.join(item(source, sig) for sig in ['pub(crate) fn begin_growth(', 'pub(crate) fn finish_growth(', 'pub(crate) fn growth_token(', 'pub(crate) fn abort_growth(', 'pub(crate) fn observe_vacancy(']) @@ -156,7 +158,7 @@ def fixture(): def main(): source = fixture() runtime = (ROOT / 'runtime.rs').read_text() - assert 'growth = queue.growth_token();' in runtime and 'queue.abort_growth(token)?' in runtime + assert 'growth = queue.growth_token();' in runtime and 'queue.abort_growth(token)' in runtime cases = [('fixed', source), ('missing-abort-transfer', source.replace('self.growing = false;\n self.growth_blocks = 0;\n self.release_owner().map(Some)', 'self.release_owner().map(Some)')), ('ignore-consumption', source.replace('if !crate::g17::freelist::control_consumed(consumer, producer, cursor)? {', 'if false {')), diff --git a/tools/asahi/g17-owner-release-order-tests.py b/tools/asahi/g17-owner-release-order-tests.py index fd0e2c4a80936..55b95af1b279d 100755 --- a/tools/asahi/g17-owner-release-order-tests.py +++ b/tools/asahi/g17-owner-release-order-tests.py @@ -13,7 +13,8 @@ import subprocess import tempfile -ROOT = Path(__file__).resolve().parents[2] / 'drivers/gpu/drm/asahi/g17' +ROOT = Path(os.environ.get('G17_ROOT', + Path(__file__).resolve().parents[2] / 'drivers/gpu/drm/asahi/g17')) def item(source, signature): @@ -64,19 +65,27 @@ def check_guards(): ordered(detach, '.park();', 'parked.release_consumed(context.scheduler_va());') runtime = (ROOT / 'runtime.rs').read_text() # A compute queue does not change owner while its scheduler is released. - ordered(item(runtime, 'pub(super) fn rebind_compute('), '.filter(|queue| queue.reusable())', + ordered(item(runtime, 'fn bind_reusable('), '.filter(|queue| queue.reusable())', 'teardown.releasing(bound)', 'continue;', 'queue.bind_owner(owner, context.clone())?;') ordered(item(runtime, 'pub(super) fn bind_rearmed('), 'teardown.releasing(bound)', 'return Err(EAGAIN);', 'queue.bind_owner(') - ordered(item(runtime, 'fn publish_compute('), '.pending_switch_from()', 'queues.teardown.releasing(previous)', + ordered(item(runtime, 'fn publish_compute_raw('), '.pending_switch_from()', 'queues.teardown.releasing(previous)', 'return Err(EAGAIN);', 'queue.publish(') # A busy pair (for example one whose scheduler release is still being consumed) # kicks the event worker, which observes the release and parks the pair. - ordered(item(submit, 'fn wait_render_busy('), 'WORK_RING_WAIT_NS', 'return Err(ETIMEDOUT);', - 'self.shared.queue_events();', 'fsleep(') - for name, path in [('take_pair', 'runtime/render.rs'), ('take_render_observation', 'runtime/render/observe.rs')]: - body = item((ROOT / path).read_text(), f'fn {name}(') - ordered(body, 'entry.borrowed_switch = pair.switch_in_flight();', 'entry.pair.take()') + ordered(item(submit, 'fn wait_render_busy('), 'WORK_RING_WAIT_NS', + 'self.shared.queue_events();', 'self.wait_domain(') + ordered(item(submit, 'fn wait_domain('), 'if remaining == 0', 'return Err(ETIMEDOUT);', 'domain.block(') + borrowed = item((ROOT / 'runtime/render.rs').read_text(), 'fn take_pair(') + ordered(borrowed, 'entry.borrowed_switch = pair.switch_in_flight();', + 'entry.pair.take()') + # The current event collector observes and retires the pair under one + # device-mutex hold; it no longer borrows it through a second take path. + observed = item((ROOT / 'runtime/render/observe.rs').read_text(), + 'fn observe_renders(') + ordered(observed, 'let mut state = self.state.lock();', + 'firmware.next_render_observation(cursor)?', '.observe_front()', + 'pair.retire_observed(') print('PASS guard placement: rebind decision, rebind commit, first publication, switch record, borrows, parked scheduler, compute owner changes, busy wait') @@ -110,7 +119,10 @@ def fixture(): } #[derive(Clone, Copy, PartialEq, Eq)] enum Phase { Deferred, Draining, Released(u32) } -struct Entry { context: Arc, phase: Phase, armed: u64, announce_pending: bool } +struct Entry { + context: Arc, phase: Phase, armed: u64, announce_pending: bool, + render_kill: Option<()>, kill_wait: Option<(u64, bool)>, kill_refused: bool, +} struct Pending { entries: Vec } impl Pending { PENDING @@ -120,6 +132,8 @@ def fixture(): struct Packet { context: Arc } struct Pair { executed: u64, switch: Option<(u64, u64)>, replay: Vec<()>, active: Vec, idle: bool } impl Pair { + // This ordering fixture models no faulted/executed unstamped pair. + fn executed_for_kill(&self, _: &Arc) -> bool { false } SWITCH /// Host stand-in for a successful publication of `ordinal` (stand-in, not production). fn publish(&mut self, packet: &Packet, ordinal: u64) { diff --git a/tools/asahi/g17-pool-release-tests.py b/tools/asahi/g17-pool-release-tests.py index b7c715727acab..70004fe94ecf9 100644 --- a/tools/asahi/g17-pool-release-tests.py +++ b/tools/asahi/g17-pool-release-tests.py @@ -31,7 +31,7 @@ def fixture(): predicate = '.any(|queue| queue.retirement_polling() || queue.pool_release_pending())' assert events.count(predicate) == 1 methods = '\n'.join(item(runtime, name) for name in [ - 'fn compute_admission_error(', 'pub(super) fn reserve_compute(']).replace('pub(super) ', '') + 'fn compute_admission_error(', 'pub(super) fn reserve_compute(']).replace('pub(super) ', '').replace('queue: ComputeSlot::new()?', 'queue: None') pending = item(compute, 'pub(crate) fn pool_release_pending(') + '\n' + \ item(compute, 'pub(crate) fn release_settling(') return r''' @@ -72,10 +72,12 @@ def fixture(): struct Id { qid: u8 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct ComputeReservation { slot: usize, id: Id, owner: u64 } -struct ComputeEntry { reservation: ComputeReservation, queue: Option> } +type ComputeSlot = Option>; +struct ComputeEntry { reservation: ComputeReservation, queue: ComputeSlot } +struct Admission; impl Admission {fn queues(&self)->u32{4}} struct QueueIds { free: bool, fail: Option } impl QueueIds { - fn reserve(&mut self, _: u64, _: DataMaster, preferred: Option) -> Result { + fn reserve_for(&mut self, _: u64, _: DataMaster, preferred: Option, _: usize) -> Result { if let Some(error) = self.fail { return Err(error); } if !self.free { return Err(ENOSPC); } self.free = false; @@ -93,8 +95,11 @@ def fixture(): compute: Vec>, identities: Identities, qids: QueueIds, + admission: Admission, } impl Registry { + // Physical-QID capacity notification does not change this pool-release fixture. + fn note_build_room(&self) {} METHODS fn poll(&self) -> bool { self.compute.iter().flatten().filter_map(|entry| entry.queue.as_deref())PREDICATE @@ -109,7 +114,7 @@ def fixture(): })); } entries.push(None); - Registry { compute: entries, identities: Identities { next: 100 }, qids: QueueIds { free, fail: None } } + Registry { compute: entries, identities: Identities { next: 100 }, qids: QueueIds { free, fail: None }, admission: Admission } } fn main() { // Captured failure: four idle released queues, full physical namespace, diff --git a/tools/asahi/g17-resource-tests.py b/tools/asahi/g17-resource-tests.py index 37f74d724c602..ae7e53ec6cf6f 100644 --- a/tools/asahi/g17-resource-tests.py +++ b/tools/asahi/g17-resource-tests.py @@ -24,7 +24,7 @@ def item(start): limits='\n'.join(re.search(r'const '+name+r': [^;]+;',mmu).group(0) for name in ['EXECUTION_CONTEXT_MASK','MAX_EXECUTION_CONTEXTS']) header='''#![allow(dead_code, unused_macros)] type Result = std::result::Result; - mod g17 { pub mod capacity { + mod g17 { pub mod trace { pub fn teardown(_:u8,_:u64,_:u64,_:u64) {} } pub mod capacity { pub enum Site { RenderSlots, LogicalQueues, ComputeReserve, PhysicalQids } pub fn refused(_: Site) -> i32 { -28 } }} @@ -134,10 +134,10 @@ def item(s,start): return s[at:end] s=(source/'kick.rs').read_text() fw=(source/'fw/kick.rs').read_text() - constants=re.search(r'const QID_MAX: [^;]+;',fw).group(0)+'\n'+'\n'.join(re.search(r'const '+name+r': [^;]+;',s).group(0) for name in ['QID_COUNT','COMPUTE_RENDER_RESERVE']) + constants=re.search(r'const QID_MAX: [^;]+;',fw).group(0)+'\n'+'\n'.join(re.search(r'const '+name+r': [^;]+;',s).group(0) for name in ['QID_COUNT','RENDER_HEADROOM','FULL_RENDER_RESERVE']) header='''#![allow(dead_code, unused_macros)] type Result = std::result::Result; - mod g17 { pub mod capacity { + mod g17 { pub mod trace { pub fn teardown(_:u8,_:u64,_:u64,_:u64) {} } pub mod capacity { pub enum Site { RenderSlots, LogicalQueues, ComputeReserve, PhysicalQids } pub fn refused(_: Site) -> i32 { -28 } }} @@ -148,16 +148,19 @@ def item(s,start): #[derive(Clone,Copy,PartialEq,Eq,Debug)] enum DataMaster {Compute,Tiling,Fragment} #[derive(Clone,Copy,PartialEq,Eq,Debug)] struct Id {qid:u8,owner:u64,engine:DataMaster} #[derive(Clone,Copy)] struct Identity {id:Id,published:bool} - '''+constants+'\n'+item(s,'pub(crate) struct QueueIds')+'\nimpl QueueIds {\n'+''.join(item(s,'pub(crate) fn '+name+'(')+'\n' for name in ['reserve_initial_render','reserve','publish','can_cancel_unpublished','cancel_unpublished'])+'}\n' + '''+constants+'\n'+item(s,'const fn render_reserve(')+'\n'+item(s,'pub(crate) struct QueueIds')+'\nimpl QueueIds {\n'+''.join(item(s,'pub(crate) fn '+name+'(')+'\n' for name in ['reserve_initial_render','reserve','reserve_for','publish','can_cancel_unpublished','cancel_unpublished'])+''.join(item(s, sig)+'\n' for sig in ['fn render_ids(', 'fn compute_room('])+'}\n' test=''' - fn fresh()->QueueIds {QueueIds {entries:[None;QID_COUNT],initial_render:[None;2],publication:0,leased:0,floor:0}} + fn fresh()->QueueIds {QueueIds {entries:[None;QID_COUNT],initial_render:[None;2],leased:0,floor:0}} fn main(){ - for render in [0,2,16,32,112,126] { - let mut q=fresh(); + for floor in [0,32] { + for render in [0,2,16,32,80,96,112,126].into_iter().filter(|render|*render<=QID_COUNT-floor) { + let mut q=fresh();q.floor=floor; for i in 0..render {q.reserve(i as u64+1,if i%2==0 {DataMaster::Tiling}else{DataMaster::Fragment},None).unwrap();} let mut compute=0; while q.reserve(1000+compute,DataMaster::Compute,None).is_ok(){compute+=1;} - assert_eq!(compute as usize,QID_COUNT-render.max(COMPUTE_RENDER_RESERVE)); + assert_eq!(compute as usize,QID_COUNT-floor-render.max(FULL_RENDER_RESERVE-floor)); + assert!(q.entries[..floor].iter().all(Option::is_none)); + } } let mut q=fresh();let first=q.reserve(1,DataMaster::Tiling,None).unwrap(); assert!(q.can_cancel_unpublished(first));q.publish(first).unwrap(); @@ -165,7 +168,7 @@ def item(s,start): let unused=q.reserve(2,DataMaster::Fragment,None).unwrap();q.cancel_unpublished(unused).unwrap();assert_eq!(q.leased,1); let next=q.reserve(3,DataMaster::Fragment,None).unwrap();assert_eq!(next.qid,unused.qid); assert!(!q.can_cancel_unpublished(unused));assert_eq!(q.cancel_unpublished(unused),Err(EINVAL)); - println!("queue IDs: six render/compute capacity boundaries; compute-only render reserve retained; published and stale identities protected"); + println!("queue IDs: render/compute capacity boundaries at zero and reserved floors; compute-only render reserve retained; published and stale identities protected"); // Reproduce the hardware snapshot: 124 installed render identities, two // installed compute identities, and two initial reservations fill128 IDs. let mut q=fresh();q.reserve_initial_render().unwrap(); @@ -192,8 +195,8 @@ def item(s,start): assert_eq!(q.entries[0].unwrap().id,protected); // Replacement must not consume the protected render minimum. let mut q=fresh();q.reserve_initial_render().unwrap(); - for owner in 3..=COMPUTE_RENDER_RESERVE as u64 {q.reserve(owner,DataMaster::Tiling,None).unwrap();} - for owner in 1000..1000+(QID_COUNT-COMPUTE_RENDER_RESERVE) as u64 {q.reserve(owner,DataMaster::Compute,None).unwrap();} + for owner in 3..=FULL_RENDER_RESERVE as u64 {q.reserve(owner,DataMaster::Tiling,None).unwrap();} + for owner in 1000..1000+(QID_COUNT-FULL_RENDER_RESERVE) as u64 {q.reserve(owner,DataMaster::Compute,None).unwrap();} assert_eq!(q.leased,QID_COUNT);assert_eq!(q.reserve(2000,DataMaster::Compute,None),Err(ENOSPC)); assert!(q.initial_render.iter().all(Option::is_some)); // Ordinary free capacity leaves the initialization reservations untouched. diff --git a/tools/asahi/g17-uninstalled-cleanup-tests.py b/tools/asahi/g17-uninstalled-cleanup-tests.py index 33d2f252eb81c..d74f67aba6267 100644 --- a/tools/asahi/g17-uninstalled-cleanup-tests.py +++ b/tools/asahi/g17-uninstalled-cleanup-tests.py @@ -61,7 +61,8 @@ def fixture(): impl Drop for Guard<'_>{fn drop(&mut self){LOCKED.store(false,Ordering::SeqCst);}} struct State(Mutex>>); impl State{fn lock(&self)->Guard<'_>{let g=self.0.lock().unwrap();assert!(!LOCKED.swap(true,Ordering::SeqCst));Guard(g)}} -struct Shared{state:State} +struct Available; impl Available {fn notify(&self){}} +struct Shared{state:State,channels:Available} impl Shared{fn queue_events(&self){}} struct Backend{shared:Arc} impl Backend { @@ -76,7 +77,7 @@ def fixture(): } } fn main(){ - let b=Backend{shared:Arc::new(Shared{state:State(Mutex::new(Some(Box::new(Firmware{queues:Registry}))))})}; + let b=Backend{shared:Arc::new(Shared{state:State(Mutex::new(Some(Box::new(Firmware{queues:Registry})))),channels:Available})}; b.finish_uninstalled_compute(None).unwrap(); for _ in 0..512 { MAPPED.store(true,Ordering::SeqCst);RESERVED.store(true,Ordering::SeqCst); diff --git a/tools/asahi/t6040-calibration-tests.py b/tools/asahi/t6040-calibration-tests.py new file mode 100644 index 0000000000000..d802013175b97 --- /dev/null +++ b/tools/asahi/t6040-calibration-tests.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Compile the T6040 wire builders and calibration checks without a GPU.""" + +from pathlib import Path +import re +import subprocess +import tempfile + +DRIVER = Path(__file__).resolve().parents[2] / "drivers/gpu/drm/asahi" + + + +HEADER = r''' +#![allow(dead_code)] +extern crate self as kernel; +mod prelude { + pub type KVec = Vec; + pub use std::mem::size_of; + pub type Result = std::result::Result; + #[derive(Debug)] + pub struct Error; + impl From for Error { + fn from(_: std::num::TryFromIntError) -> Self { Self } + } + pub const EINVAL: Error = Error; + pub const EIO: Error = Error; + pub const EOVERFLOW: Error = Error; + pub unsafe trait Zeroable {} +} +macro_rules! static_assert { ($e:expr) => { const _: () = assert!($e); } } +fn clear(value: &mut T) { + unsafe { std::ptr::write_bytes(value, 0, 1); } +} +#[macro_export] +macro_rules! c_str { + ($s:literal) => { std::ffi::CStr::from_bytes_with_nul(concat!($s, "\0").as_bytes()).unwrap() }; +} +pub mod str { pub use std::ffi::CStr; } +mod of { + use crate::prelude::*; + #[derive(Clone)] + pub struct Node(pub std::collections::BTreeMap>); + pub trait Property: Sized { fn decode(value: &[u32]) -> Result; } + impl Property for u32 { + fn decode(value: &[u32]) -> Result { + if value.len() == 1 { Ok(value[0]) } else { Err(EINVAL) } + } + } + impl Property for Vec { fn decode(value: &[u32]) -> Result { Ok(value.to_vec()) } } + impl Node { + pub fn get_property(&self, name: &crate::str::CStr) -> Result { + T::decode(self.0.get(name.to_str().unwrap()).ok_or(EINVAL)?) + } + } +} +mod driver { + pub struct AsahiDrmDevice(pub DrmDevice); + pub struct DrmDevice(pub Device); + pub struct Device(pub crate::of::Node); + impl AsRef for AsahiDrmDevice { fn as_ref(&self) -> &DrmDevice { &self.0 } } + impl AsRef for DrmDevice { fn as_ref(&self) -> &Device { &self.0 } } + impl Device { pub fn of_node(&self) -> Option { Some(self.0.clone()) } } +} +mod float; +mod initdata; +mod g17 { + pub mod fw { pub(crate) use crate::initdata; } + mod calibration; + pub fn check_property_adapter() { + use crate::{driver::*, of::Node}; + let fields: &[(&str, &[u32])] = &[ + ("apple,t6040-handoff-version", &[2]), + ("apple,core-mask", &[0xfffff]), + ("apple,max-power-mw", &[70000]), + ("apple,perf-base-pstate", &[1]), + ("apple,perf-map-a", &[0,1,2,3,4,5,6,7,8,9,15,0,0,0,0,0]), + ("apple,perf-map-b", &[0,1,2,3,4,5,6,7,8,9,15,0,0,0,0,0]), + ("apple,perf-relative-a", &[0,10,20,30,40,50,60,70,80,90,100,0,0,0,0,0]), + ("apple,perf-relative-b", &[0,0,11,22,33,44,55,66,77,88,100,0,0,0,0,0]), + ("apple,core-leak-coef", &[0x44480000, 0x44800000]), + ("apple,sram-leak-coef", &[0x43000000, 0x43000000]), + ("apple,cs-leak-coef", &[0x43800000]), + ("apple,afr-leak-coef", &[0x44000000]), + ]; + let node = Node(fields.iter().map(|(k,v)| (k.to_string(), v.to_vec())).collect()); + let read = |n| calibration::Calibration::read(&AsahiDrmDevice(DrmDevice(Device(n)))); + let chip = read(node.clone()).unwrap(); + assert_eq!(chip.max_power_mw, 70000); + assert_eq!(chip.leakage[0], 0x44480000); + assert_eq!(chip.leakage[1], 0x44800000); + assert_eq!(chip.leakage[32..34], [0x43000000; 2]); + assert_eq!(chip.leakage[52], 0x43800000); + assert_eq!(chip.leakage[54], 0x44000000); + for (key, _) in fields { + let mut missing = node.clone(); missing.0.remove(*key); + assert!(read(missing).is_err(), "accepted missing {key}"); + let mut short = node.clone(); short.0.get_mut(*key).unwrap().pop(); + assert!(read(short).is_err(), "accepted short {key}"); + } + for key in ["apple,perf-map-a", "apple,perf-map-b", "apple,perf-relative-a", "apple,perf-relative-b"] { + for index in 11..16 { + let mut bad = node.clone(); bad.0.get_mut(key).unwrap()[index] = 1; + assert!(read(bad).is_err(), "accepted nonzero tail {key}[{index}]"); + } + } + for (key, value) in [ + ("apple,t6040-handoff-version", 1), + ("apple,core-mask", 0x100000), + ("apple,max-power-mw", 0), + ("apple,perf-base-pstate", 0), + ("apple,perf-base-pstate", 2), + ("apple,cs-leak-coef", 0x7fc00000), + ("apple,afr-leak-coef", 0xbf800000), + ] { + let mut bad = node.clone(); bad.0.insert(key.to_string(), vec![value]); + assert!(read(bad).is_err(), "accepted invalid {key}"); + } + } +} +''' + +TEST = r''' +#[test] +fn property_adapter_requires_complete_chip_data() { g17::check_property_adapter(); } + +#[test] +fn power_configuration_uses_only_its_inputs() { + use initdata::v25g83::PowerConfig25G83; + let mut first = PowerConfig25G83([0xcc; 0x6510]); + let mut leakage = [0; 56]; + leakage[0] = 0x44480000; + leakage[1] = 0x44800000; + leakage[32] = 0x43000000; + leakage[33] = 0x43000000; + leakage[52] = 0x43800000; + leakage[54] = 0x44000000; + first.init(0x12340000, 0x56780000, 0xabcdef, 76543, &leakage); + let word = |bytes: &[u8], off| u32::from_le_bytes(bytes[off..off + 4].try_into().unwrap()); + for off in [0xa04, 0xabc, 0x28c8, 0x28cc, 0x28d0, 0x2a08] { + assert_eq!(word(&first.0, off), 76543); + } + for off in [0x9e0, 0x2a04] { + assert_eq!(word(&first.0, off), (76543f32).to_bits()); + } + for (i, value) in leakage.iter().enumerate() { + assert_eq!(word(&first.0, 0x642c + 4 * i), *value); + } + assert_eq!(u64::from_le_bytes(first.0[0x3b50..0x3b58].try_into().unwrap()), 0x12340080); + assert_eq!(u64::from_le_bytes(first.0[0x6318..0x6320].try_into().unwrap()), 0x56780000); + assert_eq!(word(&first.0, 0x63d4), 0xabcdef); + let mut second = PowerConfig25G83([0; 0x6510]); + second.init(0x12340000, 0x56780000, 0xabcdef, 76543, &leakage); + assert_eq!(first.0, second.0); + second.init(0x12340000, 0x56780000, 0xabcdef, 80000, &[0; 56]); + assert_eq!(word(&second.0, 0xa04), 80000); + assert_eq!(word(&second.0, 0x9e0), (80000f32).to_bits()); + for i in 0..56 { + assert_eq!(word(&second.0, 0x642c + 4 * i), 0); + } +} +''' + + +def main(): + with tempfile.TemporaryDirectory(prefix="asahi-t6040-") as tmp: + root = Path(tmp) + (root / "initdata").mkdir() + (root / "g17").mkdir() + for source, target in [ + ("g17/fw/initdata.rs", "initdata.rs"), + ("g17/fw/initdata/v25g83.rs", "initdata/v25g83.rs"), + ]: + text = (DRIVER / source).read_text() + (root / target).write_text(text.replace("use kernel::prelude::*;", "use crate::prelude::*;")) + text = (DRIVER / "float.rs").read_text().split("impl<'a> TryFrom"$pkgdir/usr/lib/systemd/system/fprintd.service.d/50-aurora-sep-no-timeout.conf" install -Dm644 logind-aurora-sep.conf \ "$pkgdir/usr/lib/systemd/logind.conf.d/50-aurora-sep.conf" + install -Dm755 sep-bio-identities "$pkgdir/usr/bin/sep-bio-identities" install -Dm644 -t "$pkgdir/usr/share/doc/$pkgname" README.md XART-SAFETY.md OS-IDENTITY.md } diff --git a/tools/aurora-sep/packaging/arch/aurora-sep/sep-bio-identities b/tools/aurora-sep/packaging/arch/aurora-sep/sep-bio-identities new file mode 120000 index 0000000000000..ddd469de602a7 --- /dev/null +++ b/tools/aurora-sep/packaging/arch/aurora-sep/sep-bio-identities @@ -0,0 +1 @@ +../../../sep-bio-identities \ No newline at end of file diff --git a/tools/aurora-sep/packaging/fedora/aurora-sep.spec b/tools/aurora-sep/packaging/fedora/aurora-sep.spec index 01e39051e9ec5..b3f0fbc7f2fe7 100644 --- a/tools/aurora-sep/packaging/fedora/aurora-sep.spec +++ b/tools/aurora-sep/packaging/fedora/aurora-sep.spec @@ -8,7 +8,7 @@ Name: aurora-sep Version: 1 -Release: 8%{?dist} +Release: 9%{?dist} Summary: Loader and desktop integration for the Apple SEP fingerprint driver License: GPL-2.0-only @@ -27,6 +27,7 @@ Source10: aurora-sep-greeter-fingerprint.service Source11: aurora_sep.te Source12: aurora_sep.fc Source13: OS-IDENTITY.md +Source14: sep-bio-identities BuildArch: noarch BuildRequires: systemd-rpm-macros @@ -91,6 +92,7 @@ sed 's|@FPRINTD@|%{_libexecdir}/fprintd|' %{SOURCE7} \ >%{buildroot}%{_unitdir}/fprintd.service.d/50-aurora-sep-no-timeout.conf install -Dpm 0644 %{SOURCE8} \ %{buildroot}%{_prefix}/lib/systemd/logind.conf.d/50-aurora-sep.conf +install -Dpm 0755 %{SOURCE14} %{buildroot}%{_bindir}/sep-bio-identities # Load the driver at boot, so the enclave is up before the login screen asks. install -d %{buildroot}%{_presetdir} echo 'enable aurora-sep.service' >%{buildroot}%{_presetdir}/80-aurora-sep.preset @@ -131,6 +133,7 @@ fi %{_libexecdir}/aurora-sep-load %{_libexecdir}/aurora-sep-ready %{_libexecdir}/aurora-sep-greeter-fingerprint +%{_bindir}/sep-bio-identities %{_unitdir}/aurora-sep.service %{_unitdir}/aurora-sep-ready.service %{_unitdir}/aurora-sep-greeter-fingerprint.service @@ -145,6 +148,9 @@ fi %ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename} %changelog +* Tue Oct 27 2026 Aurora Silicon contributors - 1-9 +- Add the Linux-lane orphan identity list and delete tool + * Mon Oct 19 2026 Ryan Murray - 1-7 - XART-SAFETY.md: what the xART guard allows for restore, use, setup and recovery, per platform, and what a rollback does to its list diff --git a/tools/aurora-sep/sep-bio-identities b/tools/aurora-sep/sep-bio-identities new file mode 100644 index 0000000000000..2231434678645 --- /dev/null +++ b/tools/aurora-sep/sep-bio-identities @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""List Linux SEP identities and remove one fprintd orphan by its UUID. + +The kernel limits both ioctls to the selected Linux SEP OS identity's built-in +user context. The kernel index may still hold a UUID after fprintd lost its +record, so the operator must identify the exact orphan. This utility has no +delete-all operation. +""" + +import argparse +import fcntl +import os +import struct +import sys +import uuid + +MAGIC = 0xB1 +LIST_SIZE = 8 + 32 * 16 +DELETE_SIZE = 16 + + +def ioctl_number(direction, number, size): + return (direction << 30) | (size << 16) | (MAGIC << 8) | number + + +LIST_ENCLAVE = ioctl_number(2, 0x0B, LIST_SIZE) +DELETE_ORPHAN = ioctl_number(1, 0x0C, DELETE_SIZE) + + +def list_identities(fd): + reply = bytearray(LIST_SIZE) + fcntl.ioctl(fd, LIST_ENCLAVE, reply, True) + count, reserved = struct.unpack_from("=II", reply) + if reserved != 0 or count > 32: + raise ValueError("invalid SEP identity-list reply") + return [uuid.UUID(bytes=bytes(reply[8 + 16 * i:24 + 16 * i])) + for i in range(count)] + + +def check_target(before, target, keep): + if target not in before: + raise ValueError("identity is absent from the Linux-lane list") + if len(before) > 1 and not keep: + raise ValueError("name the identity to preserve with --keep before deleting") + if target in keep or any(identity not in before for identity in keep): + raise ValueError("--keep must list a different identity present in the Linux lane") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--delete-orphan", type=uuid.UUID, metavar="UUID", + help="delete exactly this Linux-lane identity with no fprintd record") + parser.add_argument("--keep", type=uuid.UUID, action="append", default=[], metavar="UUID", + help="identity that must remain; required when more than one is listed") + args = parser.parse_args() + + try: + with os.fdopen(os.open("/dev/sep-bio", os.O_RDWR | os.O_CLOEXEC), "rb", closefd=True) as device: + fd = device.fileno() + before = list_identities(fd) + if args.delete_orphan is None: + for identity in before: + print(identity) + return 0 + check_target(before, args.delete_orphan, args.keep) + fcntl.ioctl(fd, DELETE_ORPHAN, args.delete_orphan.bytes) + after = list_identities(fd) + if args.delete_orphan in after: + raise RuntimeError("SEP still lists the identity after deletion") + if any(identity not in after for identity in args.keep): + raise RuntimeError("a protected identity is absent after deletion") + print("deleted orphan", args.delete_orphan) + print("Linux-lane identities:", len(before), "->", len(after)) + return 0 + except (OSError, ValueError, RuntimeError) as error: + print(f"sep-bio-identities: {error}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/aurora-sep/tests/test_enrol_admission.py b/tools/aurora-sep/tests/test_enrol_admission.py new file mode 100644 index 0000000000000..5bb688d3b5571 --- /dev/null +++ b/tools/aurora-sep/tests/test_enrol_admission.py @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: MIT +"""Execute the kernel's enrol request and persistence admission functions.""" + +from pathlib import Path +import re +import subprocess +import tempfile +import unittest + + +APPLE = Path(__file__).resolve().parents[3] / "drivers/soc/apple" + + +def function(file, name): + source = (APPLE / file).read_text() + match = re.search(r"^fn " + name + r"\(", source, re.M) + if match is None: + raise AssertionError(f"missing driver function {name}") + opening = source.index("{", match.start()) + depth, end = 1, opening + 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[match.start():end] + + +HARNESS = r''' +#![allow(dead_code)] +type Error = i32; +type Result = std::result::Result; +const EINVAL: Error = -22; +const ENXIO: Error = -6; +const EIO: Error = -5; +const ENOSPC: Error = -28; +const ENODATA: Error = -61; +const EROFS: Error = -30; +const PRIVATE_TYPE_CATACOMB_OWNER: u8 = 0xf2; +const PRIVATE_TYPE_CATACOMB_USER: u8 = 0xf3; +const PRIVATE_TYPE_CATACOMB_MASTER: u8 = 0xf4; + +struct EnrolPrerequisites { + lane: bool, + live_count: Option, + bag_bound: bool, + catacomb_readable: [bool; 3], + catacomb_writable: [bool; 3], + keybag_writable: bool, + host_store_capacity: bool, +} +const LABEL_LEN: usize = 128; +struct EnrolStart { + flags: u32, + account_uid: u32, + finger: u32, + reserved: u32, + label: [u8; LABEL_LEN], +} + +FUNCTIONS + +fn ready() -> EnrolPrerequisites { + EnrolPrerequisites { + lane: true, live_count: Some(1), bag_bound: true, + catacomb_readable: [true; 3], catacomb_writable: [true; 3], + keybag_writable: true, host_store_capacity: true, + } +} +fn request() -> EnrolStart { + let mut r = EnrolStart { + flags: 0, account_uid: 1000, finger: 7, reserved: 0, + label: [0; LABEL_LEN], + }; + r.label[..28].copy_from_slice(b"FP1-20261027-7-12345678-user"); + r +} + +fn main() { + assert!(admit_enrol(&ready()).is_ok()); + let mut p = ready(); p.lane = false; assert_eq!(admit_enrol(&p), Err(ENXIO)); + let mut p = ready(); p.live_count = None; assert_eq!(admit_enrol(&p), Err(EIO)); + let mut p = ready(); p.live_count = Some(3); assert_eq!(admit_enrol(&p), Err(ENOSPC)); + let mut p = ready(); p.bag_bound = false; assert_eq!(admit_enrol(&p), Err(ENODATA)); + for i in 0..3 { + let mut p = ready(); p.catacomb_readable[i] = false; + assert_eq!(admit_enrol(&p), Err(ENODATA)); + let mut p = ready(); p.catacomb_writable[i] = false; + assert_eq!(admit_enrol(&p), Err(EROFS)); + } + let mut p = ready(); p.keybag_writable = false; assert_eq!(admit_enrol(&p), Err(EROFS)); + let mut p = ready(); p.host_store_capacity = false; assert_eq!(admit_enrol(&p), Err(ENOSPC)); + + assert!(catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_OWNER, Some(0))); + assert!(!catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_USER, Some(0))); + assert!(!catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_MASTER, Some(0))); + assert!(catacomb_needed_for_enrol(PRIVATE_TYPE_CATACOMB_USER, Some(1))); + + assert!(validate_enrol_request(&request()).is_ok()); + let mut r = request(); r.account_uid = u32::MAX; + assert_eq!(validate_enrol_request(&r), Err(EINVAL)); + let mut r = request(); r.finger = 0; + assert_eq!(validate_enrol_request(&r), Err(EINVAL)); + let mut r = request(); r.finger = 11; + assert_eq!(validate_enrol_request(&r), Err(EINVAL)); + let mut r = request(); r.finger = 6; + assert_eq!(validate_enrol_request(&r), Err(EINVAL)); + let mut r = request(); r.label[24] = 0; + assert_eq!(validate_enrol_request(&r), Err(EINVAL)); +} +''' + + +class EnrolAdmissionTests(unittest.TestCase): + def test_refused_cases_before_capture(self): + methods = "\n".join([ + function("sbio.rs", "admit_enrol"), + function("sbio.rs", "catacomb_needed_for_enrol"), + function("bio.rs", "validate_enrol_request"), + ]) + source = HARNESS.replace("FUNCTIONS", methods) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) + (path / "test.rs").write_text(source) + subprocess.run(["rustc", "--edition=2021", "test.rs", "-o", "test"], + cwd=path, check=True, capture_output=True, text=True) + subprocess.run([str(path / "test")], check=True, capture_output=True, text=True) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/aurora-sep/tests/test_orphan_delete.py b/tools/aurora-sep/tests/test_orphan_delete.py new file mode 100644 index 0000000000000..229f6a2c43e38 --- /dev/null +++ b/tools/aurora-sep/tests/test_orphan_delete.py @@ -0,0 +1,151 @@ +# SPDX-License-Identifier: MIT +"""Run the driver's delete-and-persist method with scripted save failures.""" + +from pathlib import Path +import re +import subprocess +import tempfile +import unittest + + +SOURCE = Path(__file__).resolve().parents[3] / "drivers/soc/apple/sbio.rs" + + +def driver_method(name): + source = SOURCE.read_text() + match = re.search(r"^ fn " + name + r"\(", source, re.M) + if match is None: + raise AssertionError(f"missing driver method {name}") + opening = source.index("{", match.start()) + depth, end = 1, opening + 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[match.start():end] + + +HARNESS = r''' +#![allow(dead_code, unused_variables)] +use std::ffi::CStr; +use std::ops::Deref; +use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; +use std::sync::{Mutex as StdMutex, MutexGuard}; + +type Error = i32; +type Result = std::result::Result; +const GFP_KERNEL: u32 = 0; +const EIO: Error = -5; + +macro_rules! dev_err { ($($arg:tt)*) => {}; } + +struct Mutex(StdMutex); +impl Mutex { + fn new(value: T) -> Self { Self(StdMutex::new(value)) } + fn lock(&self) -> MutexGuard<'_, T> { self.0.lock().unwrap() } +} + +struct KVec(Vec); +impl KVec { + fn with_capacity(count: usize, _: u32) -> Result { Ok(Self(Vec::with_capacity(count))) } + fn push_within_capacity(&mut self, value: T) -> Result<()> { self.0.push(value); Ok(()) } + fn is_empty(&self) -> bool { self.0.is_empty() } + fn iter(&self) -> std::slice::Iter<'_, T> { self.0.iter() } +} +impl Deref for KVec { + type Target = [T]; + fn deref(&self) -> &[T] { &self.0 } +} + +mod sbio { + #[derive(Clone)] + pub struct IdentityV1 { pub uuid: [u8; 16] } + impl IdentityV1 { pub fn uuid(&self) -> &[u8; 16] { &self.uuid } } +} +mod bio { + pub const UUID_LEN: usize = 16; + pub struct Handled { + pub delete_identity: Option, + pub delete_identities: Vec, + } +} + +struct Index { held: Vec<[u8; 16]> } +impl Index { + fn contains_uuid(&self, uuid: &[u8; 16]) -> bool { self.held.contains(uuid) } +} + +struct SepData { + dev: (), + deletion_unsaved: AtomicBool, + bio_index: Mutex, + delete_replies: Mutex>>, + save_replies: Mutex>>, + events: Mutex>, +} +impl SepData { + fn new(uuid: [u8; 16], saves: Vec>) -> Self { + Self { + dev: (), deletion_unsaved: AtomicBool::new(false), + bio_index: Mutex::new(Index { held: vec![uuid] }), + delete_replies: Mutex::new(vec![Ok(true), Ok(true)]), + save_replies: Mutex::new(saves), events: Mutex::new(Vec::new()), + } + } + fn delete_identity(&self, _: &sbio::IdentityV1, recovery: bool) -> Result { + self.events.lock().push(format!("delete:{recovery}")); + self.delete_replies.lock().remove(0) + } + fn persist_deletion(&self, retry: bool, recovery: bool) -> Result<()> { + self.events.lock().push(format!("save:{retry}:{recovery}")); + self.save_replies.lock().remove(0) + } + fn forget_identity(&self, uuid: &[u8; 16], _: &CStr) { + self.events.lock().push("forget".into()); + self.bio_index.lock().held.retain(|held| held != uuid); + } +METHOD +} + +fn handled(uuid: [u8; 16]) -> bio::Handled { + bio::Handled { delete_identity: Some(sbio::IdentityV1 { uuid }), delete_identities: vec![] } +} + +fn main() { + let uuid = [0xa5; 16]; + let sep = SepData::new(uuid, vec![Ok(())]); + sep.delete_and_persist(&handled(uuid), true).unwrap(); + assert_eq!(*sep.events.lock(), ["delete:true", "save:false:true", "forget"]); + assert!(!sep.bio_index.lock().contains_uuid(&uuid)); + + let sep = SepData::new(uuid, vec![Err(EIO), Ok(())]); + assert!(sep.delete_and_persist(&handled(uuid), true).is_err()); + assert!(sep.deletion_unsaved.load(Relaxed)); + assert!(sep.bio_index.lock().contains_uuid(&uuid)); + sep.delete_and_persist(&handled(uuid), true).unwrap(); + assert_eq!(*sep.events.lock(), ["delete:true", "save:false:true", + "delete:true", "save:true:true", "forget"]); + assert!(!sep.deletion_unsaved.load(Relaxed)); + assert!(!sep.bio_index.lock().contains_uuid(&uuid)); + + let sep = SepData::new(uuid, vec![]); + *sep.delete_replies.lock() = vec![Err(EIO)]; + assert!(sep.delete_and_persist(&handled(uuid), false).is_err()); + assert_eq!(*sep.events.lock(), ["delete:false"]); + assert!(sep.bio_index.lock().contains_uuid(&uuid)); +} +''' + + +class OrphanDeleteTests(unittest.TestCase): + def test_failure_keeps_index_and_retry_saves(self): + source = HARNESS.replace("METHOD", driver_method("delete_and_persist")) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) + (path / "test.rs").write_text(source) + subprocess.run(["rustc", "--edition=2021", "test.rs", "-o", "test"], + cwd=path, check=True, capture_output=True, text=True) + subprocess.run([str(path / "test")], check=True, capture_output=True, text=True) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/aurora-sep/tests/test_owner_preparation.py b/tools/aurora-sep/tests/test_owner_preparation.py new file mode 100644 index 0000000000000..29c1217d7eb24 --- /dev/null +++ b/tools/aurora-sep/tests/test_owner_preparation.py @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: MIT +"""Execute the kernel's owner-file preflight against scripted file outcomes.""" + +from pathlib import Path +import re +import subprocess +import tempfile +import unittest + + +SOURCE = Path(__file__).resolve().parents[3] / "drivers/soc/apple/catacomb.rs" + + +def method(): + source = SOURCE.read_text() + match = re.search(r"^pub\(crate\) fn prepare_missing_owner\(", source, re.M) + if match is None: + raise AssertionError("missing owner preparation function") + opening = source.index("{", match.start()) + depth, end = 1, opening + 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[match.start():end] + + +HARNESS = r''' +use std::ffi::CStr; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering::Relaxed}; +type Error = i32; +type Result = std::result::Result; +const EINVAL: Error = -22; +const ENODATA: Error = -61; +const EROFS: Error = -30; +const PRIVATE_TYPE_CATACOMB_OWNER: u8 = 0xf2; + +fn path_for(kind: u8) -> Option<&'static CStr> { + (kind == PRIVATE_TYPE_CATACOMB_OWNER).then_some(c"/var/lib/apple-sep-catacomb-owner.bin") +} + +mod shim { + use super::*; + pub static CREATE: AtomicBool = AtomicBool::new(true); + pub static FAIL_OPEN: AtomicBool = AtomicBool::new(false); + pub static SYNC: AtomicU32 = AtomicU32::new(0); + pub static PARENT: AtomicU32 = AtomicU32::new(0); + pub struct StoreFile; + impl StoreFile { + pub fn open_host(_: &CStr) -> Result<(StoreFile, bool)> { + if FAIL_OPEN.load(Relaxed) { Err(EROFS) } + else { Ok((StoreFile, CREATE.load(Relaxed))) } + } + pub fn sync(&self) -> Result<()> { SYNC.fetch_add(1, Relaxed); Ok(()) } + pub fn sync_parent(&self) -> Result<()> { PARENT.fetch_add(1, Relaxed); Ok(()) } + } +} + +METHOD + +fn main() { + assert_eq!(prepare_missing_owner(), Ok(())); + assert_eq!(shim::SYNC.load(Relaxed), 1); + assert_eq!(shim::PARENT.load(Relaxed), 1); + + shim::CREATE.store(false, Relaxed); + assert_eq!(prepare_missing_owner(), Err(ENODATA)); + assert_eq!(shim::SYNC.load(Relaxed), 1); + assert_eq!(shim::PARENT.load(Relaxed), 1); + + shim::FAIL_OPEN.store(true, Relaxed); + assert_eq!(prepare_missing_owner(), Err(EROFS)); + assert_eq!(shim::SYNC.load(Relaxed), 1); + assert_eq!(shim::PARENT.load(Relaxed), 1); +} +''' + + +class OwnerPreparationTests(unittest.TestCase): + def test_invalid_file_is_preserved_before_sep_write(self): + source = HARNESS.replace("METHOD", method()) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) + (path / "test.rs").write_text(source) + subprocess.run(["rustc", "--edition=2021", "test.rs", "-o", "test"], + cwd=path, check=True, capture_output=True, text=True) + subprocess.run([str(path / "test")], check=True, capture_output=True, text=True) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/aurora-sep/tests/test_sep_bio_identities.py b/tools/aurora-sep/tests/test_sep_bio_identities.py new file mode 100644 index 0000000000000..23505157866f5 --- /dev/null +++ b/tools/aurora-sep/tests/test_sep_bio_identities.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0-only +"""Synthetic tests for the exact-ID Linux-lane recovery utility.""" + +import runpy +import struct +import unittest +import uuid +from pathlib import Path +from unittest.mock import patch + + +CLI = runpy.run_path(str(Path(__file__).parents[1] / "sep-bio-identities"), run_name="sep_bio_test") + + +class IdentityToolTests(unittest.TestCase): + def test_list_decodes_only_counted_uuids(self): + a = uuid.UUID(int=1) + b = uuid.UUID(int=2) + + def fake_ioctl(_fd, request, reply, _mutate): + self.assertEqual(request, CLI["LIST_ENCLAVE"]) + struct.pack_into("=II", reply, 0, 2, 0) + reply[8:24] = a.bytes + reply[24:40] = b.bytes + + with patch.object(CLI["fcntl"], "ioctl", fake_ioctl): + self.assertEqual(CLI["list_identities"](7), [a, b]) + + def test_exact_target_and_keep_are_required(self): + a = uuid.UUID(int=1) + b = uuid.UUID(int=2) + self.assertIsNone(CLI["check_target"]([a, b], a, [b])) + for before, target, keep in [ + ([a, b], a, []), + ([a, b], a, [a]), + ([a, b], uuid.UUID(int=3), [b]), + ([a, b], a, [uuid.UUID(int=3)]), + ]: + with self.assertRaises(ValueError): + CLI["check_target"](before, target, keep) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/Makefile b/tools/testing/selftests/drivers/gpu/apple-dcp/Makefile index af6d27b20f062..2c25d757aca5e 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/Makefile +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only OR MIT TEST_PROGS := run.sh m4-error-paths.py plane-layout.py -TEST_FILES := run.py common.py edid.py recovery.py rate.py phy.py lifecycle.py pm.py idle.py boot.py quiesce.py transaction.py accounting.py m4.py m4-fixtures native-m4-kms.c run-native-m4-kms.py dwc3 README +TEST_FILES := run.py common.py desktop.py display.py edid.py recovery.py rate.py phy.py lifecycle.py pm.py idle.py boot.py quiesce.py transaction.py accounting.py m4.py m4-fixtures native-m4-kms.c run-native-m4-kms.py dwc3 README include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/README b/tools/testing/selftests/drivers/gpu/apple-dcp/README index c49b2d48949af..6777d85cf3431 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/README +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/README @@ -12,7 +12,13 @@ Or keep translation units, commands, failure logs and source hashes: An installed copy needs --source /path/to/kernel. --group edid, rate, phy or lifecycle or boot selects one group. HOSTCC selects the host compiler (default clang). Python 3 and a compiler supporting AddressSanitizer/UndefinedBehaviorSanitizer -are required. No target access, firmware command, captured fixture or kernel +are required. The usb-kconfig producer also needs GNU Make 4 or newer. The +runner uses MAKE when set, otherwise gmake if available, then make. On macOS, +install GNU Make or select an existing producer with KCONFIG_CONF. The host +fixtures use native errno definitions; Linux-only reply statuses stay local +to the synthetic callback test. + +No target access, firmware command, captured fixture or kernel configuration is required. One TAP entry point uses shared production extraction and compiler handling. @@ -158,3 +164,41 @@ surviving an unrelated failed AGX request, both reply/timeout lock orderings, malformed replies, true owner failure, an unowned or invalid fault SID, probe rollback versus published DMA lifetime, exactly-once DRM event ownership, and mixed-owner, truncated, failed-translation and invalid-owner IOMMU tuples. + +Internal panel policy +--------------------- +The display group covers the shared nits ramp on a 600-nit panel, retargeting, +blanking, suspend, rejected submits and both submit/completion callback orders. +Brightness-only commits retain the active gamma state without marking it changed. +Synthetic TimingElements exercise 120 Hz and fractional lower refresh rates, +DCP color/timing identities, invalid geometry and adaptive-sync ranges. The +adaptive-sync callback test verifies the desktop parameter record, mode-info +ordering and one fixed-refresh retry after a rejected enable or modeset. The +watchdog test checks fixed-refresh recovery, retained-buffer arming and late +completion isolation. +These tests do not establish which timings a particular panel reports or prove +refresh rate, flicker, brightness smoothness or adaptive-sync on hardware. + +Desktop panel brightness regressions also cover panel EDID parsing and +publication from ProductAttributes without optional color data. Both bare and +descriptor-wrapped powerlog reports pass through the production compact receiver, +systemep handler and mNits parser. Rapid key repeats with interleaved stale and +new reports must converge monotonically to the last user target in both +directions. Report observations cannot restart a fade or request another write. +The callback cases exercise the desktop D202/D454 wire adapters, physical resource +mapping and release, and unknown-callback failure followed by a successful set. + +Panel identity tests also retain the EDID and generation across repeated and +partial dictionaries, and notify only when valid identity data actually changes. + +The identity case also exercises the real D552/D561 table bindings, nested +DisplayAttributes and direct ProductAttributes, legacy manufacturer codes, +minimal identity data, and unchanged byte/padded boolean acknowledgments. + +Desktop identity diagnostics cover bounded traversal of key names and types, +including truncated and oversized dictionaries. ADT identity tests use only +synthetic nodes and panel serials, reject truncated trees transactionally and +ensure the machine's serial cannot override the panel's. Panel EDID tests cover +a missing firmware identity, real EDID priority and invalid replacement retention. +Desktop backlight notification tests exercise accepted, rejected, duplicate and +out-of-order completions and preserve the requested target during notification. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/accounting.py b/tools/testing/selftests/drivers/gpu/apple-dcp/accounting.py index ce2791cc3a379..9c74ec637be24 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/accounting.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/accounting.py @@ -103,7 +103,7 @@ def resources(ROOT): puts("PASS successful DCP finalizer is not repeated after a later provider failure");return 0; } if(argv[1][0]=='h')hw_failures=1; - if(argv[1][0]=='f') {display.fixed_connector_type=2;display.hdmi_hpd=(void *)1;gpio_level=1;display.xbar=&fixed_xbar;fixed_restore_failures=1;} + if(argv[1][0]=='f') {display.fixed_connector_type=2;display.hdmi_hpd=(void *)1;gpio_level=1;display.xbar=display.fixed_xbar=&fixed_xbar;fixed_restore_failures=1;} assert(typec_mux_set(&mux,&safe)==-EIO&&xbar.lock==1&&port.owner==&route&&port.safe_pending&&!port.safe_finish_ready); bool selected_after_error=route.selected; int ret=typec_mux_set(&mux,&safe); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/common.py b/tools/testing/selftests/drivers/gpu/apple-dcp/common.py index cdf20256dfa4f..8ef176769ebf8 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/common.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/common.py @@ -47,6 +47,8 @@ def source_defines(source): def tipd_pm_header(root): """CD321x PM state machine for host units, with a host clamp() for minmax.h.""" header = (root / 'drivers/usb/typec/tipd/cd321x-pm.h').read_text() + # Use the host errno namespace, not the AFK Linux-numbered shim. + header = header.replace('#include ', '#include ') # Host integer types; a repeated identical typedef is valid C11. header = header.replace('#include ', '#include \ntypedef uint32_t u32;') @@ -109,6 +111,23 @@ def atc_function(source, name): return block(source, marker.start()) + '\n' if marker else '' +# The runtime PM wrappers of a PHY that powers down while idle (T8132 idle_power_off). +# Fixture PHYs leave idle_rpm false, so the wrappers must not reach runtime PM; older +# sources have none and atc_function() then yields ''. +ATC_RPM = ('atcphy_rpm_get', 'atcphy_rpm_busy', 'atcphy_rpm_sync', 'atcphy_rpm_put') + + +def atc_restore_helpers(source): + """What atcphy_restore_after_pd_off() needs beyond the parking helpers. + + With T8132 idle runtime PM a USB2-only PHY is brought back as at probe, through + atcphy_setup_pipehandler(); older sources only park the PIPE. '' when unused. + """ + if 'atcphy_setup_pipehandler(' not in atc_function(source, 'atcphy_restore_after_pd_off'): + return '' + return atc_function(source, 'atcphy_setup_pipehandler') + + def atc_mux_without_pm(source): """Lane/teardown fixtures omit USB PM, keeping mode mapping and lane work real. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py new file mode 100644 index 0000000000000..b38fb5e951997 --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/desktop.py @@ -0,0 +1,934 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright 2026 Ryan Murray +"""Desktop modeset callbacks through the production dispatcher and DMA policy.""" +from common import block +import lifecycle + + +def modeset_unmap(root): + base = root / 'drivers/gpu/drm/apple' + iomfb = (base / 'iomfb.c').read_text() + template = (base / 'iomfb_template.c').read_text() + desktop = (base / 'iomfb_v26_6_desktop.c').read_text() + header = (base / 'iomfb_v26_6_desktop.h').read_text() + # Reuse the transport boundary mocks, retaining the real callback dispatcher. + prefix = lifecycle.dispatcher(root).split('static bool dcp_external_zero_reply(')[0] + prefix = prefix.replace('struct dcp_channel { unsigned depth; void *output[8]; unsigned end[8]; };', r''' +struct apple_dcp; +typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); +struct dcp_channel { + unsigned depth; void *output[8], *cookies[8]; unsigned end[8]; + dcp_callback_t callbacks[8]; struct dcp_packet_header header[8]; +}; +struct sg_table {void *sgl;}; +struct dcp_mem_descriptor {void *buf;u64 dva,reg;size_t size;bool piodma_mapped;struct sg_table map;}; +''') + prefix = prefix.replace(' struct dcp_channel channel;', r''' + struct dcp_channel channel, command; + struct dcp_mem_descriptor memdesc[4]; + unsigned long memdesc_map[1];void *iommu_dom; + struct resource {u64 start,end;} *disp_registers[1];int nr_disp_registers; +''') + prefix = prefix.replace('DCP_CONTEXT_CB = 0, DCP_CONTEXT_ASYNC = 3', + 'DCP_CONTEXT_CB = 0, DCP_CONTEXT_CMD = 1, DCP_CONTEXT_ASYNC = 3') + prefix = prefix.replace('(void)c; return &d->channel;', + 'return c==DCP_CONTEXT_CMD?&d->command:&d->channel;') + prefix = prefix.replace('((void)dev)', '((void)dcp->dev)') + prefix += r''' +#include +typedef u64 __le64;typedef u32 __le32; +#define __packed __attribute__((packed)) +typedef u64 dma_addr_t; +#define DCP_FW_NAME(n) n +#define DCP_FW_H17(v) 0 +#define DCP_FW_DESKTOP_26(v) 1 +#define SZ_16K 16384U +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) +#define DCP_MAX_MAPPINGS 4 +#undef static_assert +#define static_assert(c) _Static_assert(c,#c) +#define trace_iomfb_callback(...) ((void)0) +#define dcp_trace_panel_property(...) ((void)0) +#define GFP_KERNEL 0 +#define kmalloc_objs(t,n,f) malloc(sizeof(t)*(n)) +#define dev_warn_dma(...) ((void)0) +struct dcp_map_physical_req {u64 paddr,size;u32 flags;u8 dva_null,dva_size_null,padding[2];} __attribute__((packed)); +struct dcp_map_physical_resp {u64 dva,dva_size;u32 mem_desc_id;} __attribute__((packed)); +struct dcp_map_buf_req {u64 buffer;}; +struct dcp_map_buf_resp {u64 buffer,dva;}; +struct dcp_unmap_buf_resp {u64 buffer,vaddr,dva;u8 unk,buf_null;}; +struct dcp_wait_cookie {unsigned status;bool replied;int done,refcount;}; +#define dev_warn_release(...) ((void)0) +#define dev_err_release(...) ((void)0) +#define DMA_BIDIRECTIONAL 0 +#define round_down(x,y) ((x) & ~((__typeof__(x))(y)-1)) +#define check_add_overflow(a,b,p) __builtin_add_overflow(a,b,p) +static unsigned physical_maps; +static unsigned find_first_zero_bit(unsigned long *map,unsigned count) +{for(unsigned i=0;isgl);} +static void dma_free_coherent(struct device *dev,size_t size,void *buf,u64 dva) +{assert(dev && size==SZ_16K && buf && dva==0x8000);frees++;} +static void dma_unmap_resource(struct device *dev,u64 dva,size_t size,int dir,int flags) +{assert(dev && dva==0x8000 && size==4096 && !dir && !flags);resource_unmaps++;} +static void clear_bit(unsigned b,unsigned long *p) {*p &= ~(1UL<sgl && flags==3);maps++;return SZ_16K;} +static size_t iommu_unmap(void *domain,u64 dva,size_t size) +{assert(domain && dva==0x8000 && size==SZ_16K);unmaps++;return size;} +''' + # dev logging in the DMA helper has no local 'dev', unlike the dispatcher. + unmap = block(template, 'static bool dcpep_unmap_piodma(') + unmap = unmap.replace('dev_warn(', 'dev_warn_dma(').replace('dev_err(', 'dev_warn_dma(') + handlers = block(desktop, 'static const iomfb_cb_handler cb_handlers[') + binding = '\n'.join(line for line in handlers.splitlines() if '[202]' in line or '[454]' in line) + mapping = block(template, 'static bool dcpep_map_piodma(').replace('dev_err(', 'dev_warn_dma(') + release = block(template, 'static u8 dcpep_cb_release_mem_desc(').replace('dev_warn(', 'dev_warn_release(').replace('dev_err(', 'dev_err_release(') + physical = block(template, 'static bool is_disp_register(') + '\n' + block(template, 'static struct dcp_map_physical_resp\ndcpep_cb_map_physical(').replace('dev_err(', 'dev_err_release(').replace('dev_warn(', 'dev_warn_release(') + physical += r""" +static bool trampoline_map_physical(struct apple_dcp *dcp,int tag,void *out,void *in) +{struct dcp_map_physical_resp r=dcpep_cb_map_physical(dcp,in);(void)tag;memcpy(out,&r,sizeof(r));return true;} +""" + # Assert the table still binds the shared production mapper. + assert '[452] = trampoline_map_physical,' in handlers + binding += '\n[452] = trampoline_map_physical,' + wire = block(desktop, 'struct dcp_unmap_buf_req_v26_6 {')+';\n' + production = (wire + '\n' + physical + '\n' + release + '\n' + block(desktop, 'static bool trampoline_release_mem_desc_v26_6(') + '\n' + mapping + '\n' + unmap + '\n' + block(desktop, 'static bool trampoline_unmap_piodma_v26_6(') + + '\n' + block(iomfb, 'static bool dcp_external_zero_reply(') + + '\n' + block(iomfb, 'static void dcpep_handle_cb(').replace('struct device *dev =', 'struct device *dev __attribute__((unused)) =') + + '\n' + block(iomfb, 'static void dcpep_handle_ack(') + + '\n' + block(template, 'static void complete_set_digital_out_mode(')) + return prefix + production + '\n' + r''' +int main(void) +{ + struct device_node node={0};struct device dev={&node}; + bool (*handlers[IOMFB_MAX_CB])(struct apple_dcp *,int,void *,void *)={ +''' + binding + r''' + }; + struct apple_dcp d={.dev=&dev,.fw_compat=DCP_FIRMWARE_V_26_6_DESKTOP, + .cb_handlers=handlers,.memdesc_map={2},.iommu_dom=&d}; + union {u64 align;u8 data[sizeof(struct dcp_packet_header)+24];} packet; + struct dcp_packet_header *h=(void *)packet.data; + struct {struct dcp_packet_header h;u32 input[2];u32 status;} command={ + .h={.in_len=8,.out_len=4},.status=0}; + struct dcp_wait_cookie wait={.status=~0U,.refcount=2}; + /* The firmware pauses an A413 modeset until its D202 is acknowledged. */ + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + d.command.depth=1;d.command.header[0]=command.h; + d.command.callbacks[0]=complete_set_digital_out_mode; + d.command.cookies[0]=&wait;d.command.output[0]=&command.status; + d.memdesc[1]=(struct dcp_mem_descriptor){.buf=&d,.dva=0x8000,.size=4096,.map={.sgl=&d}}; + struct dcp_map_buf_req map_request={.buffer=1};struct dcp_map_buf_resp map_reply; + assert(dcpep_map_piodma(&d,&map_request,&map_reply)); + assert(maps==1 && map_reply.buffer==1 && map_reply.dva==0x8000); + memset(&packet,0,sizeof(packet));h->in_len=24; + struct dcp_unmap_buf_req_v26_6 fields={.buffer=1,.id=1,.dva=0x8000};memcpy(packet.data+sizeof(*h),&fields,24); + case_tag=202;acks=0; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+24,0); + assert(acks==1 && !d.crashed && !d.channel.depth && unmaps==1); + assert(!d.memdesc[1].piodma_mapped && d.command.depth==1 && !wait.done); + /* No firmware response can arrive until the empty callback ACK above. */ + dcpep_handle_ack(&d,DCP_CONTEXT_CMD); + assert(wait.done && wait.replied && !wait.status && wait.refcount==1 && !d.command.depth); + /* A later mode can reuse a descriptor after the firmware unmaps it. */ + assert(dcpep_map_piodma(&d,&map_request,&map_reply) && maps==2); + /* Descriptor release handles both RAM and mapped resources, once. */ + fields.buffer=1;fields.dva=0x8000;memcpy(packet.data+sizeof(*h),&fields,24); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+24,0); + assert(unmaps==2); + case_tag=454;h->in_len=4;h->out_len=4; + put_unaligned_le32(1,packet.data+sizeof(*h)); + memset(packet.data+sizeof(*h)+4,0xa5,4); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(get_unaligned_le32(packet.data+sizeof(*h)+4)==1); + assert(frees==1 && !d.memdesc_map[0] && !d.memdesc[1].buf && !d.memdesc[1].size); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(!get_unaligned_le32(packet.data+sizeof(*h)+4) && frees==1); + d.memdesc_map[0]=1; + struct resource reg={.start=0x123400,.end=0x1243ff};d.disp_registers[0]=®d.nr_disp_registers=1; + struct dcp_map_physical_req request={.paddr=0x123400,.size=4096}; + struct {struct dcp_packet_header h;struct dcp_map_physical_req in;struct dcp_map_physical_resp out;} __attribute__((packed)) phys={.h={.in_len=24,.out_len=20},.in=request}; + case_tag=452; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,&phys,sizeof(phys),0); + assert(physical_maps==1 && phys.out.mem_desc_id==1 && phys.out.dva==0x8000 && phys.out.dva_size==4096); + assert(d.memdesc_map[0]==3 && d.memdesc[1].reg==0x123400); + case_tag=454;put_unaligned_le32(1,packet.data+sizeof(*h)); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(get_unaligned_le32(packet.data+sizeof(*h)+4)==1 && resource_unmaps==1 && d.memdesc_map[0]==1); + for(u32 id=4;id<8;id++) { + put_unaligned_le32(id,packet.data+sizeof(*h)); + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(!get_unaligned_le32(packet.data+sizeof(*h)+4) && frees==1); + } + /* Out-of-range and overflowing physical regions receive no mapping. */ + case_tag=452;phys.in.paddr=0x124000; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,&phys,sizeof(phys),0); + assert(!phys.out.dva_size && physical_maps==1); + phys.in.paddr=~0ULL;phys.in.size=8192; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,&phys,sizeof(phys),0); + assert(!phys.out.dva_size && physical_maps==1); + unsigned before=acks; + assert(!iomfb_check_callback_v26_6_desktop(454,4,1)); + assert(!iomfb_check_callback_v26_6_desktop(452,23,20)); + /* The declared desktop shape is exact, with no guessed reply bytes. */ + assert(!iomfb_check_callback_v26_6_desktop(202,26,0)); + assert(!iomfb_check_callback_v26_6_desktop(202,24,4)); + case_tag=202;h->in_len=23;h->out_len=0; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+24,0); + assert(d.crashed && acks==before && unmaps==2); + return 0; +} +''' + + +def brightness_takeover(root): + import display + source = (root / 'drivers/gpu/drm/apple/dcp_backlight.c').read_text() + prefix = display.backlight(root).split('static void completed(')[0] + prefix = prefix.replace('static inline ', 'static inline __attribute__((unused)) ') + functions = '\n'.join(block(source, marker) for marker in ( + 'static bool dcp_backlight_desktop(', 'static int dcp_get_brightness(', + 'static void dcp_backlight_sync_hint(', + 'int dcp_backlight_register(', 'static int dcp_set_brightness(', + 'static bool dcp_backlight_controls_device(')) + return prefix + r''' +#include +#include +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define DCP_FIRMWARE_H17P 1 +#define DCP_IOMFB_METHODS_H17G 7 +#define MAX_BRIGHTNESS_PART2 510U +#define BACKLIGHT_PLATFORM 1 +#define BACKLIGHT_SCALE_LINEAR 1 +#define BL_CORE_SUSPENDED 1 +#define BL_CORE_FBBLANK 2 +#define devm_add_action_or_reset(...) (0) +#define DCP_BL_FADE_STEP_MS 16 +#define system_dfl_wq NULL +#define IS_ERR(p) false +#define PTR_ERR(p) (-EINVAL) +#define msecs_to_jiffies(n) (n) +#define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) +#define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) +struct device {int unused;}; +struct backlight_properties {int type,brightness,max_brightness,scale,state;}; +struct backlight_device {struct backlight_properties props;void *data;bool ops_lock;}; +struct apple_dcp { + int fw_compat;bool quiescing; + struct {int iomfb_method_profile;} hw; + struct {u32 maximum,nits,dac;bool update;struct backlight_device *bl_dev;} brightness; + struct {bool lock;struct dcp_backlight_state state;unsigned long fade_start; + int fade_work;void (*kick)(struct apple_dcp *);} backlight; + struct {bool inherited_brightness_valid;u32 inherited_brightness;} *desktop; + struct {struct {struct {struct device *dev;} *dev;int mutex;} base;} *crtc; + struct device *dev; +}; +static unsigned long jiffies; +static bool inject_hint,inject_user; +static void mutex_lock(bool *p) +{ + assert(!*p);*p=true; + if(inject_user) { + inject_user=false; + struct backlight_device *bd=(void *)((char *)p - offsetof(struct backlight_device,ops_lock)); + struct apple_dcp *d=bd->data; + bd->props.brightness=450; + assert(!dcp_bl_request(&d->backlight.state,450,false,false)); + } +} +static void mutex_unlock(bool *p) {assert(*p);*p=false;} +static struct backlight_device registered; +static const int dcp_backlight_ops; +static void *bl_get_data(struct backlight_device *bd) {return bd->data;} +static bool iomfb_is_m4(struct apple_dcp *d) {(void)d;return false;} +bool dcp_backlight_active(struct apple_dcp *d) {(void)d;return false;} +static unsigned notify_count; +static void dcp_backlight_notify(struct apple_dcp *d) {(void)d;notify_count++;} +static void dcp_backlight_desktop_kick(struct apple_dcp *d) {(void)d;} +static void dcp_backlight_kick(struct apple_dcp *d) {(void)d;} +static unsigned fades; +static void queue_delayed_work(void *q,int *work,unsigned delay) +{assert(!q && delay==16);(void)work;fades++;} +static struct backlight_device *devm_backlight_device_register(struct device *dev, + const char *name,struct device *parent,void *data,const int *ops, + const struct backlight_properties *props) +{(void)dev;(void)name;(void)parent;assert(ops==&dcp_backlight_ops); + registered=(struct backlight_device){.props=*props,.data=data}; + if(inject_hint) {inject_hint=false;struct apple_dcp *d=data;assert(dcp_bl_seed(&d->backlight.state,180));} + return ®istered;} +static int backlight_get_brightness(struct backlight_device *bd) +{return bd->props.state?0:bd->props.brightness;} +static bool backlight_is_blank(struct backlight_device *bd) {return !!bd->props.state;} +static u32 calculate_dac(struct apple_dcp *d,int nits) {(void)d;(void)nits;assert(false);return 0;} +static int drm_modeset_lock_single_interruptible(int *p) {(void)p;assert(false);return 0;} +static void drm_modeset_unlock(int *p) {(void)p;assert(false);} +static int dcp_backlight_update(struct apple_dcp *d) {(void)d;assert(false);return 0;} +''' + functions + r''' +int main(void) +{ + struct apple_dcp d={.fw_compat=5,.brightness={.maximum=600}}; + __typeof__(*d.desktop) saved={0};d.desktop=&saved; + assert(!dcp_backlight_register(&d)); + assert(registered.props.brightness==300 && registered.props.max_brightness==600); + assert(dcp_get_brightness(®istered)==300); + assert(!dcp_backlight_controls_device(®istered,NULL)); + /* A save/restore client echoes the exposed value, not a new target. */ + assert(!dcp_set_brightness(®istered)); + assert(!d.backlight.state.controlled && !d.backlight.state.dirty); + registered.props.state=BL_CORE_SUSPENDED|BL_CORE_FBBLANK; + assert(!dcp_set_brightness(®istered)); + registered.props.state=0;assert(!dcp_set_brightness(®istered)); + assert(!d.backlight.state.controlled && !d.backlight.state.dirty); + assert(dcp_get_brightness(®istered)==300); + /* A different explicit target takes over the unknown inherited level. */ + registered.props.brightness=250;assert(!dcp_set_brightness(®istered)); + assert(d.backlight.state.controlled && d.backlight.state.fading && !d.backlight.state.dirty && fades==1); + assert(!dcp_bl_fade_step(&d.backlight.state,250,250)); + assert(dcp_backlight_controls_device(®istered,NULL)); + struct dcp_backlight_present present; + assert(!dcp_bl_prepare(&d.backlight.state,true,&present) && present.nits==250); + assert(dcp_get_brightness(®istered)==250); + assert(dcp_bl_complete(&d.backlight.state,present.sequence,true)); + assert(dcp_get_brightness(®istered)==250); + /* Even the former midpoint is an ordinary faded target after takeover. */ + registered.props.brightness=300;assert(!dcp_set_brightness(®istered)); + assert(d.backlight.state.fading && fades==2 && d.backlight.state.target==300); + /* A real loader hint seeds registration without needing a present. */ + d.brightness.bl_dev=NULL;d.backlight.state=(struct dcp_backlight_state){0}; + inject_hint=true; + assert(!dcp_backlight_register(&d) && registered.props.brightness==180); + assert(dcp_get_brightness(®istered)==180); + assert(dcp_backlight_controls_device(®istered,NULL)); + /* A user write racing the late-hint notification keeps its target. */ + inject_user=true;dcp_backlight_sync_hint(&d); + assert(d.backlight.state.controlled && d.backlight.state.target==450); + assert(registered.props.brightness==450 && !registered.ops_lock); + return 0; +} +''' + + +def modeset_unknown(root): + template = (root / 'drivers/gpu/drm/apple/iomfb_template.c').read_text() + unit = modeset_unmap(root) + helper = block(template, 'static bool dcp_modeset_callback_failed(') + # Keep the real modeset completion and its callback-error check. + helper += r''' +struct dcp_modeset_cookie {struct dcp_wait_cookie wait;bool vrr;}; +''' + block(template, 'static void dcp_modeset_actual_done(') + unit = unit.replace('int main(void)', helper + '\nint main(void)') + tests = r''' + /* A fresh channel has an A413 outstanding; inject a valid but unknown D. */ + d.crashed=false;desktop.modeset_active=true;desktop.modeset_callback_error=false; + struct dcp_modeset_cookie mode={.wait={.status=~0U,.refcount=2},.vrr=true}; + d.command.depth=1;d.command.header[0]=command.h; + d.command.callbacks[0]=dcp_modeset_actual_done; + d.command.cookies[0]=&mode;d.command.output[0]=&command.status; + case_tag=499;h->in_len=4;h->out_len=4; + memset(packet.data+sizeof(*h)+4,0xa5,4);before=acks; + dcpep_handle_cb(&d,DCP_CONTEXT_CB,h,sizeof(*h)+8,0); + assert(acks==before+1 && !d.crashed && !d.channel.depth); + assert(!get_unaligned_le32(packet.data+sizeof(*h)+4)); + assert(desktop.modeset_callback_error && d.command.depth==1 && !mode.wait.done); + /* Draining the real ACK fails the set, even if firmware reports success. */ + dcpep_handle_ack(&d,DCP_CONTEXT_CMD); + assert(mode.wait.done && !mode.wait.replied && mode.wait.status==~0U); + assert(mode.wait.refcount==1 && !d.command.depth); + /* The next fixed modeset is independent and completes normally. */ + desktop.modeset_active=false;desktop.modeset_callback_error=false; + mode=(struct dcp_modeset_cookie){.wait={.status=~0U,.refcount=2}}; + d.command.depth=1;d.command.header[0]=command.h; + d.command.callbacks[0]=dcp_modeset_actual_done; + d.command.cookies[0]=&mode;d.command.output[0]=&command.status; + dcpep_handle_ack(&d,DCP_CONTEXT_CMD); + assert(mode.wait.done && mode.wait.replied && !mode.wait.status); + assert(!d.crashed && !d.command.depth && !d.channel.depth); +''' + return unit.replace(' return 0;\n}', tests + ' return 0;\n}') + + +def panel_identity(root, output=None): + import struct + import rate + f = rate.parser_fixture(root) + attributes = {'MaxHorizontalImageSize': 0, 'MaxVerticalImageSize': 0, + 'ProductAttributes': {'ManufacturerID': 'TST', 'ProductID': 42, + 'ProductName': 'Synthetic Internal Panel', + 'SerialNumber': 0x12345678}} + raw = bytearray(struct.pack('has_panel;} +static void dcp_queue_hotplug(void *con) {assert(con);notifications++;} +''' + helpers + dictionary + '\nstatic const u8 attributes[]={'+','.join(map(str,raw))+'};\n' + 'static const u8 product[]={'+','.join(map(str,product))+'};\n' + 'static const u8 minimal[]={'+','.join(map(str,minimal))+'};\n' + r''' +int main(int argc, char **argv) +{ + struct dcp_parse_ctx ctx; + struct dcp_display_metadata metadata; + assert(!parse(attributes,sizeof(attributes),&ctx)); + assert(!parse_panel_metadata(&ctx,&metadata)); + assert(!strcmp(metadata.manufacturer,"TST") && metadata.product==42 && metadata.serial==0x12345678); + assert(!strcmp(metadata.name,"Synthetic Int")); + /* Optional color/date fields are absent; timings come from the catalog. */ + struct apple_dcp d={.fw_compat=5,.has_panel=true,.panel={345,223},.connector=&d}; + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + struct dcp_display_mode modes[3]={0}; + for(unsigned i=0;i<3;i++)modes[i].mode=(struct drm_display_mode){.clock=i==0?1080005:i==1?540005:432000, + .hdisplay=3456,.hsync_start=3500,.hsync_end=3550,.htotal=4000, + .vdisplay=2160,.vsync_start=2165,.vsync_end=2170,.vtotal=2250, + .flags=DRM_MODE_FLAG_PHSYNC|DRM_MODE_FLAG_PVSYNC,.type=i==0?DRM_MODE_TYPE_PREFERRED:0}; + modes[0].vrr=true;modes[0].min_vrr=24U<<16;modes[0].max_vrr=120U<<16; + u64 generation=99; + desktop.panel_adt_identity=(struct dcp_display_metadata){.manufacturer="APP",.product=0x616}; + const struct drm_edid *fallback=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(fallback) && drm_edid_valid(fallback)); + if(argc==2) { + char path[4096];snprintf(path,sizeof(path),"%s.adt-fallback",argv[1]); + FILE *file=fopen(path,"wb");assert(file);assert(fwrite(&fallback->raw,128,1,file)==1);fclose(file); + } + drm_edid_free(fallback); + bool (*handlers[600])(struct apple_dcp *,int,void *,void *)={ +''' + bindings + r''' + }; + for(unsigned form=0;form<3;form++) { + desktop=(__typeof__(desktop)){0};notifications=0; + struct dcp_dict_passthrough_req_v26_6 wire={0};u32 result=0; + strcpy((char *)wire.key,form?"ProductAttributes":"DisplayAttributes"); + const u8 *data=form==2?minimal:form?product:attributes; + size_t size=form==2?sizeof(minimal):form?sizeof(product):sizeof(attributes); + memcpy(wire.value,data,size); + unsigned tag=form?552:561; + assert(iomfb_check_callback_v26_6_desktop(tag,sizeof(wire),4)); + assert(iomfb_check_callback_v26_6_desktop(tag,sizeof(wire),1)); + assert(!iomfb_check_callback_v26_6_desktop(tag,sizeof(wire)-1,4)); + u8 short_reply[2]={0,0xa5}; + assert(handlers[tag](&d,tag,short_reply,&wire)); + assert(short_reply[0]==1 && short_reply[1]==0xa5); + assert(handlers[tag](&d,tag,&result,&wire) && result==1); + u8 service[sizeof(wire)+4]={0};memcpy(service+4,&wire,sizeof(wire)); + assert(iomfb_check_callback_v26_6_desktop(413,sizeof(service),4)); + assert(!iomfb_check_callback_v26_6_desktop(413,sizeof(service)-1,4)); + assert(handlers[413](&d,413,&result,service) && result==1); + assert(desktop.panel_metadata_valid && notifications==1); + assert(desktop.panel_metadata.serial==(form==2?0:metadata.serial)); + assert(!strcmp(desktop.panel_metadata.manufacturer,"TST")); + if(form==2) { + const struct drm_edid *minimal_edid=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(minimal_edid) && drm_edid_valid(minimal_edid)); + const u8 *m=(const void *)&minimal_edid->raw; + assert(!memcmp(m+72+5,"TST 002a",8) && !memcmp(m+90+5,"unspecified",11)); + drm_edid_free(minimal_edid); + } + /* Null and unrelated setters preserve the existing ACK and identity. */ + wire.value_null=1;result=0; + assert(handlers[tag](&d,tag,&result,&wire) && result==1 && notifications==1); + wire.value_null=0;strcpy((char *)wire.key,"Unrelated");result=0; + assert(handlers[tag](&d,tag,&result,&wire) && result==1 && notifications==1); + } + desktop=(__typeof__(desktop)){0};notifications=0; + dcp_panel_metadata_replace(&d,&metadata); + assert(notifications==1 && desktop.panel_metadata_valid); + const struct drm_edid *edid=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(edid) && drm_edid_valid(edid) && dcp_panel_metadata_current(&d,generation)); + const u8 *bytes=(const void *)&edid->raw; + assert(bytes[8]==0x52 && bytes[9]==0x74 && bytes[10]==42 && !bytes[11]); + assert(bytes[12]==0x78 && bytes[13]==0x56 && bytes[14]==0x34 && bytes[15]==0x12); + assert(bytes[18]==1 && bytes[19]==4 && bytes[21]==35 && bytes[22]==22 && bytes[23]==0xff); + assert(bytes[16]==0xff && bytes[17]==34); + assert(((unsigned)bytes[33]<<2 | ((bytes[26]>>2)&3))==320); + assert(((unsigned)bytes[34]<<2 | (bytes[26]&3))==337); + assert(((unsigned)bytes[66] | (unsigned)(bytes[68] >> 4) << 8)==345); + assert(((unsigned)bytes[67] | (unsigned)(bytes[68] & 15) << 8)==223); + assert(bytes[72+3]==EDID_DETAIL_MONITOR_NAME && bytes[90+3]==EDID_DETAIL_MONITOR_SERIAL); + assert(!memcmp(bytes+90+5,"12345678\n",9)); + if(argc==2) {FILE *file=fopen(argv[1],"wb");assert(file);assert(fwrite(bytes,128,1,file)==1);fclose(file);} + assert(((unsigned)bytes[54]|(unsigned)bytes[55]<<8)==54001); + assert(bytes[108+3]==EDID_DETAIL_MONITOR_RANGE && bytes[108+5]==24 && bytes[108+6]==120); + assert(bytes[108+4] & DRM_EDID_RANGE_OFFSET_MAX_HFREQ); + /* The encoded timing clock rounds to 10 kHz; the DCP catalog is unchanged. */ + assert(modes[1].mode.clock==540005 && modes[0].mode.clock==1080005); + /* A real EDID replaces the fallback; invalid replacement preserves it. */ + u8 real[128];memcpy(real,bytes,128);real[12]^=1;real[127]^=1; + dcp_panel_edid_receive(&d,real,sizeof(real)); + assert(desktop.panel_raw_edid_size==128); + u8 nested[148]={0};u32 word; + word=0xd3;memcpy(nested,&word,4);word=0x01000001;memcpy(nested+4,&word,4); + word=0x09000004;memcpy(nested+8,&word,4);memcpy(nested+12,"EDID",4); + word=0x0a000080;memcpy(nested+16,&word,4);memcpy(nested+20,real,128); + const u8 *nested_raw=NULL;size_t nested_size=0; + assert(!parse_panel_edid_data(nested,sizeof(nested),&nested_raw,&nested_size)); + assert(nested_size==128 && !memcmp(nested_raw,real,128)); + dcp_panel_edid_receive(&d,nested,sizeof(nested)); + generation=desktop.panel_metadata_generation; + const struct drm_edid *copied=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(copied) && !memcmp(&copied->raw,real,128));drm_edid_free(copied); + unsigned previous=notifications; + real[127]^=1;dcp_panel_edid_receive(&d,real,sizeof(real)); + assert(notifications==previous); + /* A checksum-valid block with no serial must not reintroduce null strings. */ + memset(real+12,0,4);memset(real+95,' ',13);real[127]=0; + for(unsigned j=0;j<127;j++)real[127]-=real[j]; + assert(!dcp_panel_edid_has_identity(real)); + dcp_panel_edid_receive(&d,real,sizeof(real));assert(notifications==previous); + desktop.panel_raw_edid_size=0;notifications=1; + drm_edid_free(edid); + /* Repeated and partial dictionaries cannot trigger monitor/brightness restore loops. */ + for(unsigned repeat=0;repeat<32;repeat++) { + dcp_panel_metadata_replace(&d,NULL); + dcp_panel_metadata_replace(&d,&metadata); + assert(dcp_panel_metadata_current(&d,generation) && notifications==1); + } + struct dcp_display_metadata partial={.manufacturer="TST",.product=42}; + dcp_panel_metadata_replace(&d,&partial); + assert(dcp_panel_metadata_current(&d,generation) && notifications==1); + assert(desktop.panel_metadata.serial==metadata.serial); + metadata.serial++; + dcp_panel_metadata_replace(&d,&metadata); + assert(!dcp_panel_metadata_current(&d,generation) && notifications==2); + edid=dcp_panel_edid_snapshot(&d,modes,3,&generation); + assert(!IS_ERR(edid) && dcp_panel_metadata_current(&d,generation)); + drm_edid_free(edid); + /* Another firmware/profile cannot publish a desktop panel identity. */ + desktop=(__typeof__(desktop)){0};d.fw_compat=2;dcp_panel_metadata_replace(&d,&metadata); + assert(!desktop.panel_metadata_valid); + metadata.manufacturer[0]='?';assert(IS_ERR(dcp_panel_edid(&metadata,modes,3,345,223))); + return 0; +} +''' + + return (unit, ((str(output / "desktop-panel-identity.edid"),),)) if output else unit + + +def powerlog_framing(root): + import rate + import struct + f = rate.parser_fixture(root) + # Use only the existing integer/string parser; no captured device data. + raw = bytearray(struct.pack('service->channel==id);return e->service;} +static struct apple_epic_service *afk_service_get(struct apple_epic_service *s) {s->users++;return s;} +static void afk_service_put(struct apple_epic_service *s) {assert(s->users);s->users--;} +static void afk_recv_handle_compact_init(struct apple_dcp_afkep *e,u16 id,const void *p,size_t n) +{(void)e;(void)id;(void)p;(void)n;assert(false);} +static void afk_recv_handle_compact_call(struct apple_dcp_afkep *e,u16 id,const void *p,size_t n) +{(void)e;(void)id;(void)p;(void)n;assert(false);} +static void afk_recv_handle_compact_reply(struct apple_dcp_afkep *e,u16 id,u8 t,const void *p,size_t n) +{(void)e;(void)id;(void)t;(void)p;(void)n;assert(false);} +''' + functions = '\n'.join(block(source, name) for source, name in ( + (system, 'static int powerlog_report('), + (afk, 'static bool afk_compact_report_dictionary('), + (afk, 'static void afk_recv_compact_message('))) + return prefix + '\n' + functions + '\nstatic const u8 report[]={'+','.join(map(str,raw))+'};\n' + r''' +int main(void) +{ + struct apple_dcp d={.fw_compat=5}; + struct apple_dcp_afkep ep={.dcp=&d,.endpoint=0x20}; + struct apple_epic_service_ops ops={.report=powerlog_report}; + struct apple_epic_service service={.ep=&ep,.ops=&ops,.channel=3};ep.service=&service; + u8 wire[16+8+sizeof(report)]={0}; + struct epic_compact_sub_hdr *sub=(void *)(wire+8);sub->category=0;sub->type=0xc0; + memcpy(wire+16,report,sizeof(report)); + afk_recv_compact_message(&ep,3,wire,16+sizeof(report)); + assert(hints==1 && hint==123000 && !service.users); + /* Descriptor-wrapped reports keep their existing transport behavior. */ + memset(wire+16,0,8);memcpy(wire+24,report,sizeof(report)); + afk_recv_compact_message(&ep,3,wire,sizeof(wire)); + assert(hints==2 && hint==123000 && !service.users); + /* The old unconditional descriptor removal loses the serialization header. */ + struct dcp_parse_ctx ctx;assert(parse(report+8,sizeof(report)-8,&ctx)==-EINVAL); + /* Truncated data never creates a takeover hint. */ + memcpy(wire+16,report,8); + afk_recv_compact_message(&ep,3,wire,23); + assert(hints==2 && !service.users); + return 0; +} +''' + + +def keys_and_reports(root): + import display + prefix = display.backlight(root).split('static void completed(')[0] + prefix = prefix.replace('static inline ', 'static inline __attribute__((unused)) ') + return prefix + r''' +static void report(struct dcp_backlight_state *s,u32 nits) +{ + struct dcp_backlight_state before=*s; + assert(!dcp_bl_report(s,nits)); + assert(s->target==before.target && s->level==before.level && s->actual==before.actual); + assert(s->fade_from==before.fade_from && s->fading==before.fading); + assert(s->dirty==before.dirty && s->in_flight==before.in_flight && s->sequence==before.sequence); + assert(s->report_valid && s->reported==nits); +} +static void step(struct dcp_backlight_state *s,u32 elapsed,u32 *id,u32 *previous,bool up) +{ + struct dcp_backlight_swap p; + dcp_bl_fade_step(s,elapsed,250); + report(s,(*id & 1)?50:550); + if(!s->dirty)return; + assert(!dcp_bl_swap_prepare(s,&p,(*id)++,true)); + report(s,(*id & 1)?0:600); + if(*id & 1) { + assert(!dcp_bl_swap_finish(s,&p,p.id,true,true)); + report(s,250); + assert(dcp_bl_swap_finish(s,&p,p.id,false,true)); + } else { + assert(!dcp_bl_swap_finish(s,&p,p.id,false,true)); + report(s,160); + assert(dcp_bl_swap_finish(s,&p,p.id,true,true)); + } + assert(up?s->actual>=*previous:s->actual<=*previous); + *previous=s->actual; +} +int main(void) +{ + for(unsigned direction=0;direction<2;direction++) { + bool up=!direction;u32 initial=up?100:600,previous=initial,id=1; + struct dcp_backlight_state s; + assert(!dcp_bl_init(&s,600,false,0,true,300)); + s.fade=true;s.preserve_inherited=true; + assert(dcp_bl_report(&s,initial) && s.actual==initial && !s.dirty); + report(&s,up?550:50); /* Only the first hint can establish takeover. */ + for(unsigned key=1;key<=10;key++) { + u32 target=up?100+key*50:600-key*50; + assert(!dcp_bl_request(&s,target,false,false)); + report(&s,up?50:550); + /* Key repeat retargets before any fade can finish. */ + step(&s,16,&id,&previous,up);step(&s,32,&id,&previous,up); + assert(s.target==target); + } + u32 last=up?600:100; + for(u32 time=48;time<=256;time+=16)step(&s,time,&id,&previous,up); + assert(s.target==last && s.actual==last && s.level==last && !s.fading && !s.dirty); + for(unsigned late=0;late<64;late++)report(&s,late&1?0:600); + assert(s.target==last && s.actual==last && !s.dirty); + } + return 0; +} +''' + + +def identity_keys(root): + import rate, struct + f = rate.parser_fixture(root) + data = bytearray(struct.pack(' +#include +#include +#include +#include +#include +typedef uint8_t u8;typedef uint32_t u32; +#define U16_MAX UINT16_MAX +#define SZ_16M (16U<<20) +#define ALIGN(x,n) (((x)+(n)-1)&~((n)-1)) +#define ERR_PTR(x) ((void *)(intptr_t)(x)) +#define PTR_ERR(x) ((int)(intptr_t)(x)) +#define IS_ERR(x) ((uintptr_t)(x)>(uintptr_t)-4096) +static u32 get_unaligned_le32(const void *p) {u32 n;memcpy(&n,p,4);return n;} +''' + code+=block((base/'dcp-display-metadata.h').read_text(),'struct dcp_display_metadata {')+';\n' + code+=block(header,'struct dcp_panel_adt {')+';\n'+source + code+='static const u8 fixture[]={'+','.join(map(str,data))+'};\n' + return code+r''' +int main(void) +{ + struct dcp_panel_adt panel={.identity={.manufacturer="APP",.product=0x616}}; + assert(!dcp_panel_adt_parse(fixture,sizeof(fixture),&panel)); + assert(panel.found && panel.width_mm==345 && panel.height_mm==223); + assert(!strcmp(panel.identity.manufacturer,"TST") && panel.identity.product==42); + assert(panel.identity.serial && panel.identity.serial==dcp_adt_serial((const u8 *)"SYNTHETIC-PANEL",15)); + for(unsigned n=0;n +#include +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) +#define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) +#define container_of(p,t,m) ((t *)((char *)(p)-offsetof(t,m))) +#define KOBJ_CHANGE 1 +#define system_dfl_wq NULL +#define msecs_to_jiffies(x) (x) +struct work_struct {int unused;}; +struct backlight_device {struct {int kobj;} dev;struct {u32 brightness;} props;}; +struct apple_dcp { + int fw_compat;bool quiescing,crashed; + struct {struct backlight_device *bl_dev;} brightness; + struct {bool lock;struct dcp_backlight_state state;struct work_struct notify_work;int fade_work,retry_work;} backlight; + struct {struct dcp_backlight_swap backlight;} *desktop; +}; +static unsigned events,brightness,actual,kicks,cancelled,delayed_cancelled; +static void cancel_delayed_work_sync(int *work) {(void)work;delayed_cancelled++;} +static void cancel_work_sync(struct work_struct *w) {(void)w;cancelled++;} +static void kobject_uevent_env(int *k,int action,char **env) +{(void)k;assert(action==1 && !strcmp(env[0],"SOURCE=unknown") && !env[1]);events++;} +static void sysfs_notify(int *k,void *dir,const char *name) +{(void)k;assert(!dir);if(!strcmp(name,"brightness"))brightness++;else {assert(!strcmp(name,"actual_brightness"));actual++;}} +static void dcp_backlight_notify_work(struct work_struct *); +static void schedule_work(struct work_struct *w) {dcp_backlight_notify_work(w);} +static void dcp_backlight_kick(struct apple_dcp *d) {assert(!d->backlight.lock);kicks++;} +static void mod_delayed_work(void *q,int *w,unsigned delay) {(void)q;(void)w;assert(delay);} +'''+functions+r''' +int main(void) +{ + struct backlight_device bd={.props={500}}; + struct apple_dcp d={.fw_compat=5,.brightness={&bd}}; + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + assert(!dcp_bl_init(&d.backlight.state,600,true,100,false,0)); + assert(!dcp_bl_request(&d.backlight.state,500,false,false)); + u32 target=d.backlight.state.target; + /* Accepted/completed presents update userspace once, duplicate callbacks do not. */ + assert(!dcp_bl_swap_prepare(&d.backlight.state,&desktop.backlight,7,true)); + dcp_backlight_desktop_finish(&d,8,false,true);assert(!events); + dcp_backlight_desktop_finish(&d,7,true,true);assert(!events); + dcp_backlight_desktop_finish(&d,7,false,true);assert(events==1 && brightness==1 && actual==1); + dcp_backlight_desktop_finish(&d,7,false,true);assert(events==1); + assert(d.backlight.state.target==target && bd.props.brightness==500); + assert(!dcp_bl_request(&d.backlight.state,250,false,false)); + assert(!dcp_bl_swap_prepare(&d.backlight.state,&desktop.backlight,9,true)); + dcp_backlight_desktop_finish(&d,9,true,false);assert(events==1); + d.quiescing=true;dcp_backlight_notify(&d);assert(events==1); + d.quiescing=false;d.fw_compat=1;dcp_backlight_notify(&d);assert(events==1); + dcp_backlight_stop(&d);assert(!cancelled && delayed_cancelled==2); + d.fw_compat=5;dcp_backlight_notify_release(&d); + assert(!d.brightness.bl_dev && cancelled==1);dcp_backlight_notify(&d);assert(events==1); + dcp_backlight_stop(&d);assert(cancelled==2 && delayed_cancelled==4); + return 0; +} +''' + + +def identity_trace_budget(root): + source=(root/'drivers/gpu/drm/apple/iomfb.c').read_text() + code=r''' +#include +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; +typedef struct {int value;} atomic_t; +static int atomic_read(const atomic_t *n) {return n->value;} +static bool atomic_add_unless(atomic_t *n,int add,int unless) +{if(n->value==unless)return false;n->value+=add;return true;} +#define READ_ONCE(x) (x) +#define min_t(t,a,b) ((t)(a)<(t)(b)?(t)(a):(t)(b)) +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +struct apple_dcp {int fw_compat;bool has_panel;void *dev;struct {atomic_t identity_log_count[4];} *desktop;}; +static bool kms_debug; +#define DRM_UT_KMS 4 +#define fallthrough __attribute__((fallthrough)) +typedef uint8_t u8; +static bool drm_debug_enabled(int category) {assert(category==DRM_UT_KMS);return kms_debug;} +static unsigned messages; +static char output[256]; +static bool dcp_has_panel(struct apple_dcp *d) {return d->has_panel;} +static void dev_info(void *dev,const char *fmt,...) +{ + (void)dev;va_list args;va_start(args,fmt);vsnprintf(output,sizeof(output),fmt,args);va_end(args);messages++; +} +typedef void (*dcp_identity_key_fn)(void *,const char *,u32,u32,u32,u32); +static int parse_identity_keys(const void *blob,size_t len,dcp_identity_key_fn emit,void *data) +{ + (void)blob;(void)len; + for(unsigned i=0;i<70;i++)emit(data,"Key\n",4,9,25,0); + return 0; +} +''' + code+=block(source,'struct dcp_identity_trace {')+';\n' + code+=block(source,'static void dcp_identity_key_log(') + code+=block(source,'void dcp_trace_panel_property(') + code+=block(source,'static void dcp_trace_panel_setter(') + return code+r''' +int main(void) +{ + struct apple_dcp d={.fw_compat=5,.has_panel=true}; + __typeof__(*d.desktop) desktop={0};d.desktop=&desktop; + const char secret[]="SYNTHETIC_VALUE_NOT_FOR_LOGS"; + dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); + dcp_trace_panel_setter(&d,563,NULL,76);assert(!messages); + kms_debug=true;d.fw_compat=1; + dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); + d.fw_compat=5;d.has_panel=false; + dcp_trace_panel_property(&d,561,"Property",8,secret,sizeof(secret));assert(!messages); + d.has_panel=true; + u8 setter[4168]={0};strcpy((char *)setter,"ScalarKey"); + memcpy(setter+64,secret,sizeof(secret));setter[72]=0; + dcp_trace_panel_setter(&d,563,setter,75);assert(!messages); + dcp_trace_panel_setter(&d,563,setter,76); + assert(strstr(output,"ScalarKey type=4 size=8") && !strstr(output,secret)); + dcp_trace_panel_setter(&d,565,setter,72);assert(strstr(output,"type=11")); + dcp_trace_panel_setter(&d,567,setter,128);assert(strstr(output,"type=9")); + setter[72]=1;dcp_trace_panel_setter(&d,563,setter,76);assert(strstr(output,"size=0")); + memset(setter,0,sizeof(setter));strcpy((char *)setter+4,"ServiceKey"); + dcp_trace_panel_setter(&d,414,setter,80);assert(strstr(output,"ServiceKey type=4")); + dcp_trace_panel_setter(&d,415,setter,76);assert(strstr(output,"ServiceKey type=11")); + dcp_trace_panel_setter(&d,102,setter,68);dcp_trace_panel_setter(&d,104,setter,68); + for(unsigned i=0;i<100;i++)dcp_trace_panel_property(&d,114,"Statistics",10,secret,sizeof(secret)); + assert(atomic_read(&desktop.identity_log_count[2])==128 && !atomic_read(&desktop.identity_log_count[0]) && !atomic_read(&desktop.identity_log_count[1])); + unsigned before=messages; + dcp_trace_panel_property(&d,552,"Panel",5,secret,sizeof(secret));assert(messages>before); + dcp_trace_panel_property(&d,561,"Panel",5,secret,sizeof(secret)); + assert(atomic_read(&desktop.identity_log_count[0]) && atomic_read(&desktop.identity_log_count[1])); + assert(strstr(output,"Key?") && !strstr(output,secret)); + for(unsigned i=0;i<100;i++) { + dcp_trace_panel_property(&d,552,"Panel",5,secret,sizeof(secret)); + dcp_trace_panel_property(&d,561,"Panel",5,secret,sizeof(secret)); + dcp_trace_panel_property(&d,0,"Chunks",6,secret,sizeof(secret)); + } + for(unsigned i=0;i<4;i++)assert(atomic_read(&desktop.identity_log_count[i])==128); + before=messages;dcp_trace_panel_property(&d,561,"Panel",5,NULL,0);assert(messages==before); + assert(messages<=520); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/display.py b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py new file mode 100644 index 0000000000000..83f29c1ad7be1 --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/display.py @@ -0,0 +1,383 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright 2026 Ryan Murray +"""Internal panel policy and real TimingElements parser, without hardware.""" +import copy +import struct +from common import block +import rate + + +def backlight(root): + header = (root / 'drivers/gpu/drm/apple/dcp_backlight.h').read_text() + header = '\n'.join(line for line in header.splitlines() + if not line.startswith('#include')) + header = header.replace('static inline ', 'static inline __attribute__((unused)) ') + return r''' +#include +#include +#include +#include +#include +typedef uint32_t u32; +typedef uint64_t u64; +typedef uint8_t u8; +#define min(a,b) ((a)<(b)?(a):(b)) +#define mul_u32_u32(a,b) ((u64)(a)*(b)) +#define div_u64(a,b) ((a)/(b)) +''' + header + r''' +static void completed(struct dcp_backlight_state *s, struct dcp_backlight_swap *p, + u32 id, bool early) +{ + u32 old=s->actual; + if(early) { + assert(!dcp_bl_swap_finish(s,p,id,false,true)); + assert(s->actual==old && s->in_flight); + assert(dcp_bl_swap_finish(s,p,id,true,true)); + } else { + assert(!dcp_bl_swap_finish(s,p,id,true,true)); + assert(s->actual==old && s->in_flight); + assert(dcp_bl_swap_finish(s,p,id,false,true)); + } + assert(!dcp_bl_swap_finish(s,p,id,false,true)); +} +int main(void) +{ + struct dcp_backlight_state s; + struct dcp_backlight_swap p={0}; + u32 id=1, nits; + assert(!dcp_bl_takeover_nits(600,600000,&nits) && nits==600); + /* A registration default must never change the loader's visible level. */ + assert(!dcp_bl_init(&s,600,false,0,true,300)); s.fade=true;s.preserve_inherited=true; + assert(!s.dirty && !s.level_known && dcp_bl_actual(&s)==300); + assert(!dcp_bl_request(&s,300,true,true)); + assert(!dcp_bl_request(&s,300,false,false)); + assert(!s.controlled && !s.dirty && dcp_bl_actual(&s)==300); + assert(dcp_bl_swap_prepare(&s,&p,id,true)==-EALREADY); + assert(!dcp_bl_request(&s,250,false,false)); + assert(s.fading && !s.dirty); + assert(dcp_bl_fade_step(&s,125,250) && s.level==275); + assert(!dcp_bl_fade_step(&s,250,250) && s.level==250); + assert(dcp_bl_swap_prepare(&s,&p,id,false)==-ENODATA); + assert(!dcp_bl_swap_prepare(&s,&p,id,true)); + completed(&s,&p,id++,true); + assert(s.actual==250 && s.level_known && dcp_bl_actual(&s)==250); + assert(!dcp_bl_seed(&s,300)); + /* Same state as a slider request or an ALS retarget through backlight. */ + assert(!dcp_bl_request(&s,600,false,false) && s.fading); + for(u32 t=16;t<250;t+=16) { + u32 before=s.level; + assert(dcp_bl_fade_step(&s,t,250)); + assert(s.level>=before && s.level<600); + if(t==16) { + assert(!dcp_bl_swap_prepare(&s,&p,id,true)); + assert(dcp_bl_swap_finish(&s,&p,id++,true,false)); + assert(s.actual==250 && s.dirty && dcp_bl_retry_delay(&s)==100); + } + } + assert(!dcp_bl_fade_step(&s,250,250)); + assert(!dcp_bl_swap_prepare(&s,&p,id,true)); + assert(!dcp_bl_swap_finish(&s,&p,id+100,false,true)); + completed(&s,&p,id++,false); assert(s.actual==600); + assert(dcp_bl_request(&s,601,false,false)==-ERANGE); + assert(!dcp_bl_request(&s,0,false,false)); + assert(dcp_bl_fade_step(&s,125,250) && s.level==300); + assert(!dcp_bl_request(&s,100,false,false) && s.fade_from==300); + assert(dcp_bl_fade_step(&s,125,250) && s.level==200); + assert(!dcp_bl_request(&s,100,true,false)); + assert(!s.fading && dcp_bl_effective(&s)==0); + assert(!dcp_bl_request(&s,450,true,false) && dcp_bl_effective(&s)==0); + assert(!dcp_bl_request(&s,450,false,false) && s.level==450); + assert(!dcp_bl_request(&s,0,false,true) && dcp_bl_effective(&s)==0); + assert(!dcp_bl_request(&s,450,false,false) && s.level==450); + assert(!dcp_bl_dpms(&s,false) && dcp_bl_effective(&s)==0); + assert(!dcp_bl_dpms(&s,true) && dcp_bl_effective(&s)==450); + assert(!dcp_bl_resend(&s)); + return 0; +} +''' + + +def modes(root): + f = rate.parser_fixture(root) + # Use the exact production parser and public serializer already used by rate. + parser = f['parser'] + prefix = (f['prefix'] + f['epoch'] + f['metadata'] + f['header']).replace('static inline ', 'static inline __attribute__((unused)) ') + baseline = copy.deepcopy(f['timing_fixture'][0]) + checks = [] + blobs = [] + + def case(elements, expected, ranges=None, desktop=True): + raw = bytearray(struct.pack('= 119.88: + check += f'assert(m[{j}].needs_timestamps); ' + if ranges: + for j, (lo, hi) in enumerate(ranges): + check += f'assert(m[{j}].vrr=={int(bool(lo))}); ' + if lo: + check += f'assert(m[{j}].min_vrr=={lo} && m[{j}].max_vrr=={hi}); ' + checks.append(check + 'free(m); }') + + supported = [] + # Synthetic rates exercise catalog handling, not a claim about a panel dump. + for i, hz in enumerate((120, 60, 59.94, 50, 48, 47.952)): + m = copy.deepcopy(baseline) + m['ID'] = 100+i + m['VerticalAttributes']['PreciseSyncRate'] = round(hz*65536) + supported.append(m) + case(supported, (120, 60, 59.94, 50, 48, 47.952), + [(24<<16, 120<<16)] + [(0,0)]*5) + for hz in (119.88, 120, 120.01): + m=copy.deepcopy(supported[0]); m['VerticalAttributes']['PreciseSyncRate']=round(hz*65536) + case([m], [hz], [(24<<16, round(hz*65536))]) + for lo, hi, valid in ((48<<16,120<<16,True), (0,120<<16,False), + (60<<16,0,False), (0,0,True), (120<<16,24<<16,False), + (24<<16,144<<16,False), (120<<16,120<<16,False)): + m=copy.deepcopy(supported[0]); m['MinimumVariableRefreshRate']=lo; m['MaximumVariableRefreshRate']=hi + case([m],[120],[(lo or 24<<16,hi or 120<<16) if valid else (0,0)]) + m=copy.deepcopy(supported[0]);m['MinimumVariableRefreshRate']=0;m['MaximumVariableRefreshRate']=0 + case([m],[120],[(0,0)],desktop=False) + for key in ('MinimumVariableRefreshRate', 'MaximumVariableRefreshRate'): + m=copy.deepcopy(supported[0]);m[key]=0 + case([m],[120],[(0,0)]) + m=copy.deepcopy(supported[0]);m['MinimumVariableRefreshRate']=24<<16 + m=f['ObjectPairs'](list(m.items())+[('MinimumVariableRefreshRate',48<<16)]) + # Check duplicates against the parser directly without dict indexing in case(). + raw=bytearray(struct.pack('','') + return r''' +#include +#include +#include +typedef uint32_t u32; +typedef uint64_t u64; +''' + h + r''' +int main(void) { + u32 lo=0,hi=0; + assert(dcp_refresh_range(120U<<16,0,0,true,&lo,&hi)); + assert(lo==24U<<16 && hi==120U<<16); + assert(!dcp_refresh_range(120U<<16,0,0,false,&lo,&hi)); + assert(!dcp_refresh_range(60U<<16,0,0,true,&lo,&hi)); + assert(!dcp_refresh_range(120U<<16,0,120U<<16,true,&lo,&hi)); + assert(!dcp_refresh_range(120U<<16,120U<<16,24U<<16,true,&lo,&hi)); + assert(!dcp_refresh_range(120U<<16,24U<<16,144U<<16,true,&lo,&hi)); + assert(dcp_desktop_present_deadline(true,true)==120); + assert(dcp_desktop_present_deadline(true,false)==0); + assert(dcp_desktop_present_deadline(false,true)==0); + return 0; +} +''' + + +def takeover(root): + prefix=backlight(root).split('static void completed(')[0] + prefix=prefix.replace('static inline ', 'static inline __attribute__((unused)) ') + function=block((root/'drivers/gpu/drm/apple/dcp_backlight.c').read_text(), + 'int dcp_backlight_takeover(') + return prefix + r''' +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define READ_ONCE(x) (x) +#define spin_lock_irqsave(p,f) do {assert(!*(p));*(p)=true;(f)=0;} while(0) +#define spin_unlock_irqrestore(p,f) do {assert(*(p));*(p)=false;(void)(f);} while(0) +struct apple_dcp { + int fw_compat; + struct {u32 maximum;void *bl_dev;} brightness; + struct {bool lock;struct dcp_backlight_state state;} backlight; + struct {bool inherited_brightness_valid;u32 inherited_brightness;} *desktop; +}; +static unsigned legacy,notifications; +static void dcp_backlight_sync_hint(struct apple_dcp *d) +{assert(d->brightness.bl_dev);notifications++;} +bool dcp_backlight_active(struct apple_dcp *d) {return d->fw_compat==1 && d->backlight.state.ready;} +static int iomfb_configure_backlight_h17p(struct apple_dcp *d,u32 max,bool valid,u32 nits,bool fallback,u32 guessed) +{assert(max==600 && valid && nits==100 && !fallback && !guessed);legacy++;return 0;} +''' + function + r''' +int main(void) { + struct apple_dcp d={.fw_compat=5,.brightness={600}}; + __typeof__(*d.desktop) saved={0};d.desktop=&saved; + assert(!dcp_backlight_takeover(&d,0)); + assert(saved.inherited_brightness_valid && !saved.inherited_brightness && !legacy); + assert(!dcp_backlight_takeover(&d,500000) && !saved.inherited_brightness); + saved.inherited_brightness_valid=false; + assert(dcp_backlight_takeover(&d,1300000)==-ERANGE && !saved.inherited_brightness_valid); + assert(!dcp_backlight_takeover(&d,700000) && saved.inherited_brightness==600); + assert(!dcp_bl_init(&d.backlight.state,600,false,0,true,300)); + d.brightness.bl_dev=&d;saved.inherited_brightness_valid=false; + assert(!dcp_backlight_takeover(&d,100000) && saved.inherited_brightness_valid); + assert(dcp_bl_actual(&d.backlight.state)==100 && !d.backlight.state.dirty && notifications==1); + assert(!dcp_backlight_takeover(&d,200000) && d.backlight.state.target==100 && notifications==1); + saved.inherited_brightness_valid=false; + assert(!dcp_bl_request(&d.backlight.state,250,false,false)); + assert(!dcp_backlight_takeover(&d,100000) && !saved.inherited_brightness_valid); + d.fw_compat=1;d.backlight.state.ready=false; + assert(!dcp_backlight_takeover(&d,100000) && legacy==1); + return 0; +} +''' + + +def watchdog(root): + source=(root/'drivers/gpu/drm/apple/dcp.c').read_text() + production='\n'.join(block(source, marker) for marker in ( + 'bool dcp_desktop_present_complete(', + 'VISIBLE_IF_KUNIT void dcp_swap_watchdog(', + 'void dcp_swap_watchdog_arm(', + 'void dcp_swap_watchdog_complete(')) + definitions='\n'.join(line for line in source.splitlines() + if line.startswith('#define DCP_SWAP_WATCHDOG_')) + return r''' +#include +#include +#include +#include +typedef uint32_t u32; +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +#define xchg(p,v) __atomic_exchange_n((p),(v),__ATOMIC_SEQ_CST) +#define VISIBLE_IF_KUNIT +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 +#define dev_warn(...) ((void)0) +#define container_of(p,t,m) ((t *)((char *)(p)-offsetof(t,m))) +#define to_delayed_work(p) container_of(p,struct delayed_work,work) +#define msecs_to_jiffies(n) (n) +#define time_before(a,b) ((long)((a)-(b))<0) +#define system_dfl_wq NULL +static unsigned long jiffies; +struct work_struct {unsigned unused;}; +struct delayed_work {struct work_struct work;}; +struct apple_dcp { + int fw_compat;bool vrr_enabled,typec; + struct {u32 present_id;bool present_pending,vrr_failed;} *desktop; + struct {bool valid;} mode_state; + struct delayed_work swap_watchdog_wq; + unsigned long swap_watchdog_deadline; + unsigned swap_watchdog_retrains; + void *crtc,*connector,*dev; +}; +static unsigned scheduled,cancelled,vblanks,hotplugs,unarmed,rejected; +static bool allow_retire=true; +static bool iomfb_retire_dropped_present_h17p(struct apple_dcp *d) {return allow_retire;} +static void dcp_unarm_retained_framebuffers(struct apple_dcp *d,u32 id) +{assert(id==d->desktop->present_id);unarmed++;} +static void dcp_backlight_desktop_finish(struct apple_dcp *d,u32 id,bool reply,bool accepted) +{assert(id==d->desktop->present_id && reply && !accepted);rejected++;} +static void dcp_mode_invalidate(__typeof__(((struct apple_dcp *)0)->mode_state) *m) {m->valid=false;} +static void dcp_drm_crtc_vblank(void *c) {vblanks++;} +static void dcp_queue_hotplug(void *c) {hotplugs++;} +static bool dcp_is_typec_output(struct apple_dcp *d) {return d->typec;} +static void mod_delayed_work(void *q,struct delayed_work *w,unsigned long delay) +{assert(delay==1000);scheduled++;} +static void cancel_delayed_work(struct delayed_work *w) {cancelled++;} +''' + definitions + '\n' + production + r''' +int main(void) { + struct apple_dcp d={.fw_compat=5,.vrr_enabled=true,.mode_state={true},.connector=(void *)1}; + __typeof__(*d.desktop) state={.present_id=42,.present_pending=true};d.desktop=&state; + dcp_swap_watchdog_arm(&d);assert(scheduled==1 && d.swap_watchdog_deadline==1000); + jiffies=999;dcp_swap_watchdog(&d.swap_watchdog_wq.work); + assert(d.mode_state.valid && !hotplugs && !unarmed && state.present_pending); + assert(!dcp_desktop_present_complete(&d,41) && state.present_pending); + assert(dcp_desktop_present_complete(&d,42) && !state.present_pending); + assert(!dcp_desktop_present_complete(&d,42)); + dcp_swap_watchdog_complete(&d);assert(cancelled==1); + jiffies=1000;dcp_swap_watchdog(&d.swap_watchdog_wq.work);assert(!hotplugs); + /* Expiry owns this dropped swap, retains its buffers and triggers reset. */ + state.present_id=43;state.present_pending=true; + dcp_swap_watchdog(&d.swap_watchdog_wq.work); + assert(state.vrr_failed && !d.vrr_enabled && !state.present_pending); + assert(!d.mode_state.valid && hotplugs==1 && vblanks==1 && unarmed==1 && rejected==1); + assert(!dcp_desktop_present_complete(&d,43)); + /* A late completion cannot finish the new fixed-refresh present. */ + state.present_id=44;state.present_pending=true; + assert(!dcp_desktop_present_complete(&d,43) && state.present_pending); + assert(dcp_desktop_present_complete(&d,44)); + unsigned before=scheduled;dcp_swap_watchdog_arm(&d);assert(scheduled==before); + /* The established external watchdog remains armed on a Type-C output. */ + d.fw_compat=1;d.typec=true;dcp_swap_watchdog_arm(&d);assert(scheduled==before+1); + allow_retire=false;d.mode_state.valid=true; + dcp_swap_watchdog(&d.swap_watchdog_wq.work);assert(d.mode_state.valid && hotplugs==1); + return 0; +} +''' + + +def brightness_commit(root): + source=(root/'drivers/gpu/drm/apple/dcp_backlight.c').read_text() + production=block(source,'static int drm_crtc_set_brightness(') + return r''' +#include +#include +#include +#include +#define DRM_MODESET_ACQUIRE_INTERRUPTIBLE 0 +#define IS_ERR(p) false +#define PTR_ERR(p) (-EINVAL) +struct drm_crtc_state {bool active,color_mgmt_changed,planes_changed;void *gamma_lut;}; +struct drm_crtc {void *dev;int mutex;}; +struct drm_modeset_acquire_ctx {int unused;}; +struct drm_atomic_commit {struct drm_modeset_acquire_ctx *acquire_ctx;}; +struct apple_dcp {bool desktop,pending;struct {bool update;} brightness;struct {struct drm_crtc base;} *crtc;}; +static struct drm_atomic_commit commit; +static struct drm_crtc_state state; +static unsigned allocations,commits,planes; +static bool fail_alloc; +static bool dcp_backlight_desktop(struct apple_dcp *d) {return d->desktop;} +static bool dcp_backlight_pending(struct apple_dcp *d) {return d->pending;} +static struct drm_atomic_commit *drm_atomic_commit_alloc(void *dev) +{allocations++;return fail_alloc?NULL:&commit;} +static void drm_modeset_acquire_init(struct drm_modeset_acquire_ctx *c,int flags) {} +static int drm_modeset_lock(int *m,struct drm_modeset_acquire_ctx *c) {assert(!*m);*m=1;return 0;} +static struct drm_crtc_state *drm_atomic_get_crtc_state(struct drm_atomic_commit *a,struct drm_crtc *c) +{assert(c->mutex);return &state;} +static int drm_atomic_add_affected_planes(struct drm_atomic_commit *a,struct drm_crtc *c) +{assert(c->mutex);planes++;return 0;} +static int drm_atomic_commit(struct drm_atomic_commit *a) {commits++;return 0;} +static void drm_atomic_commit_clear(struct drm_atomic_commit *a) {assert(false);} +static int drm_modeset_backoff(struct drm_modeset_acquire_ctx *c) {assert(false);return 0;} +static void drm_atomic_commit_put(struct drm_atomic_commit *a) {} +static void drm_modeset_drop_locks(struct drm_modeset_acquire_ctx *c) {} +static void drm_modeset_acquire_fini(struct drm_modeset_acquire_ctx *c) {} +''' + production + r''' +int main(void) { + struct apple_dcp d={.desktop=true,.pending=true,.brightness={true}}; + __typeof__(*d.crtc) crtc={0};d.crtc=&crtc; + state.active=true;state.gamma_lut=&d; + assert(!drm_crtc_set_brightness(&d)); + assert(commits==1 && planes==1 && state.planes_changed && !state.color_mgmt_changed); + crtc.base.mutex=0;state=(struct drm_crtc_state){.active=true};d.desktop=false; + assert(!drm_crtc_set_brightness(&d)); + assert(commits==2 && planes==2 && state.color_mgmt_changed); + crtc.base.mutex=0;state=(struct drm_crtc_state){.active=false};d.desktop=true; + assert(!drm_crtc_set_brightness(&d) && commits==2); + crtc.base.mutex=0;state.active=true;d.pending=false; + assert(!drm_crtc_set_brightness(&d) && commits==2); + fail_alloc=true;assert(drm_crtc_set_brightness(&d)==-ENOMEM && commits==2); + assert(allocations==5); + return 0; +} +''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c b/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c index 938126350876f..22e01ce7be38d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c @@ -85,6 +85,8 @@ struct dwc3_apple { struct phy *usb2_phy, *usb3_phy; bool fixed_hub, core_active, core_exit_pending, reset_pending, shutting_down; bool rpm_held, host_only; + /* T8132 only: runtime PM before the first core probe; never set here. */ + bool pre_core_rpm; unsigned long usb2_power_off_pending, usb3_power_off_pending; }; static unsigned int polls, fail_poll, reset_calls, reset_writes, core_writes; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py b/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py new file mode 100644 index 0000000000000..45af01294a55e --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/j616s.py @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Panel identity with absent ADT panel nodes and notch-hidden timings.""" +import re +from common import block +import desktop + + +def identity(root, output): + base = root / 'drivers/gpu/drm/apple' + unit = desktop.panel_identity(root).split('int main(int argc, char **argv)')[0] + unit = unit.replace('int fw_compat;bool has_panel;', + 'int fw_compat;unsigned notch_height;bool has_panel;') + unit += r''' +#define ALIGN(x,n) (((x)+(n)-1)&~((n)-1)) +#define SZ_16M (16U<<20) +static u32 get_unaligned_le32(const void *p) {u32 n;memcpy(&n,p,4);return n;} +''' + unit += block((base / 'dcp-panel-adt.h').read_text(), 'struct dcp_panel_adt {') + ';\n' + unit += '\n'.join(line for line in (base / 'dcp-panel-adt.c').read_text().splitlines() + if not line.startswith('#include')) + unit += (base / 'dcp-panel-test.h').read_text() + source = (base / 'dcp.c').read_text() + # Also exercise the pre-fix property read, so the regression fails on r5g. + if 'void dcp_panel_read_dimensions(' in source: + reader = block(source, 'void dcp_panel_read_dimensions(') + else: + start = source.index('\t\tof_property_read_u32(panel_np, "width-mm"') + end = source.index('\n\n', start) + reader = ('void dcp_panel_read_dimensions(struct apple_dcp *dcp, ' + 'struct device_node *panel_np) {\n' + 'const char height_prop[2][16] = {"adj-height-mm", "height-mm"};\n' + + source[start:end] + '\n}') + dts = (root / 'arch/arm64/boot/dts/apple/t6040-j616s.dts').read_text() + values = [] + for name in ('width-mm', 'height-mm', 'adj-height-mm'): + match = re.search(r'^\s*' + name + r'\s*=\s*<(\d+)>;', dts, re.M) + values.append(int(match[1]) if match else 0) + unit += r''' +struct device_node { u32 width, height, adjusted; }; +static int of_property_read_u32(struct device_node *np, const char *name, u32 *out) +{ + u32 value = !strcmp(name,"width-mm") ? np->width : + !strcmp(name,"height-mm") ? np->height : np->adjusted; + if (!value) return -EINVAL; + *out=value; + return 0; +} +''' + reader + '\n' + r''' +int main(int argc, char **argv) +{ + assert(argc==3); + struct apple_dcp d={.fw_compat=5,.has_panel=true}; + __typeof__(*d.desktop) state={0};d.desktop=&state; + struct device_node np={DTS_VALUES}; + d.notch_height=74; + if (!strcmp(argv[1],"dimensions")) { + dcp_panel_read_dimensions(&d,&np); + assert(d.panel.width_mm==345 && d.panel.height_mm==216); + /* A missing optional adjusted height must retain the full height. */ + np.adjusted=0;d.panel.height_mm=0; + dcp_panel_read_dimensions(&d,&np); + assert(d.panel.width_mm==345 && d.panel.height_mm==223); + d.notch_height=0;np.adjusted=216; + dcp_panel_read_dimensions(&d,&np); + assert(d.panel.height_mm==223); + return 0; + } + struct dcp_panel_adt panel={ + .identity={.manufacturer="APP",.product=0x616,.name="Apple Panel"}, + .width_mm=345,.height_mm=216, + }, before=panel; + assert(!dcp_panel_adt_parse(panel_test_adt,sizeof(panel_test_adt),&panel)); + assert(!panel.found && !memcmp(&panel,&before,sizeof(panel))); + for (unsigned n=0;nraw; + assert(!memcmp(raw+77,"Apple Panel",11)); + assert(!memcmp(raw+95,"unspecified",11)); + assert((raw[66] | (raw[68]>>4)<<8)==345); + assert((raw[67] | (raw[68]&15)<<8)==216); + FILE *file=fopen(argv[2],"wb");assert(file); + assert(fwrite(raw,128,1,file)==1);fclose(file); + drm_edid_free(edid); + /* Invalid geometry must still be refused rather than repaired. */ + mode.mode.vsync_end=mode.mode.vtotal+1; + assert(IS_ERR(dcp_panel_edid_snapshot(&d,&mode,1,&generation))); + return 0; +} +'''.replace('DTS_VALUES', ','.join(map(str, values))) + return unit, (("dimensions", str(output / 'j616s-panel.edid')), + ("identity", str(output / 'j616s-panel.edid'))) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index d7485a6681747..f27c5b243d32f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -3,7 +3,7 @@ """Actual disconnect authority, DWC3 teardown and firmware resource admission.""" from pathlib import Path import re -from common import block, apple_function, internal_inline, atc_mux_without_pm, source_defines, ATC_POST_TUNABLE, tipd_pm_header, firmware_lineage +from common import block, apple_function, internal_inline, atc_mux_without_pm, source_defines, ATC_POST_TUNABLE, tipd_pm_header, firmware_lineage, atc_restore_helpers def disconnect(root): # Copyright 2026 Ryan Murray @@ -192,8 +192,9 @@ def function(file, name): struct cd321x_status { u32 status, data_status, status_changed, data_status_changed, pwr_status; struct usb_pd_identity partner_identity; bool dp_sid_event,dp_sid_hpd_low,dp_sid_irq; - struct { u32 status_rx,configure; } dp_sid_status; }; -struct tps6598x { void *partner, *dev, *port, *psy; int lock; u32 data_status; enum usb_role *role_sw; }; + struct { u32 status_rx,configure,mode_data; } dp_sid_status; }; +struct tps6598x { void *partner, *dev, *port, *psy; int lock; u32 data_status; enum usb_role *role_sw; + void *partner_altmode_dp; }; struct cd321x { struct tps6598x tps; struct cd321x_status update_status; struct cd321x_pm_state pm; struct delayed_work update_work, resume_work; struct typec_mux_state state; bool state_valid, display_route_active, cur_partner_is_pd; @@ -236,6 +237,20 @@ def function(file, name): static void *typec_register_partner(void *p, struct typec_partner_desc *d) { (void)p; (void)d; return (void *)1; } static void typec_partner_set_identity(void *p) { (void)p; } +#ifndef USB_TYPEC_DP_SID +#define USB_TYPEC_DP_SID 0xff01 +#endif +#ifndef USB_TYPEC_DP_MODE +#define USB_TYPEC_DP_MODE 1 +#endif +struct typec_altmode_desc { unsigned short svid; unsigned char mode; u32 vdo; bool mode_selection; }; +/* The partner DP altmode the firmware entered: registered and dropped in pairs. */ +static int partner_altmodes; +static void *typec_partner_register_altmode(void *p, const struct typec_altmode_desc *d) +{ assert(p && d->svid == USB_TYPEC_DP_SID); partner_altmodes++; return &partner_altmodes; } +static void typec_altmode_update_active(void *a, bool active) { assert(a); (void)active; } +static void __attribute__((unused)) typec_unregister_altmode(void *a) +{ if (a == &partner_altmodes) { assert(partner_altmodes > 0); partner_altmodes--; } } #define typec_set_pwr_opmode(...) ((void)0) #define typec_set_pwr_role(...) ((void)0) #define typec_set_vconn_role(...) ((void)0) @@ -490,6 +505,8 @@ def bounce(root): m = re.search(r'^static int dcp_dptx_connect_raw\(', dcp, re.M) assert m, 'dcp_dptx_connect_raw' raw = block(dcp, m.start()) + # Compact DPTX is the desktop 26.x wire; this scenario's endpoint is not. + raw = raw.replace('READ_ONCE(dcp->dptxport[port].service->ep->compact)', 'false') def swap(old, new): nonlocal unit @@ -510,6 +527,8 @@ def swap(old, new): swap(old_raw, 'static int dcp_dptx_connect_raw(struct apple_dcp *, u32, bool *' + (', const struct dcp_fabric_session *, bool, bool' if sessions else '') + ');') swap('static int dptxport_set_hpd(void *p, bool on)\n{ (void)p; assert(!on); return hpd_error; }', + '/* The route powers a desktop 26.x PHY at connect: nothing to do for this scenario. */\n' + 'static int __attribute__((unused)) dcp_typec_phy_on(struct apple_dcp *d) { (void)d; return 0; }\n' 'static bool fw_hpd, fw_display, link_trains;\n' 'static int hpd_drops, connects, release_error_once, assert_error;\n' '/* A request that times out may still be carried out; a refused one is not. */\n' @@ -1035,6 +1054,10 @@ def fn(n, name): 'dwc3_core_exit_after_phy_power_off', 'dwc3_core_exit', 'dwc3_core_remove_common', 'dwc3_core_remove', 'dwc3_core_remove_no_shutdown') names['atc'] += ('atcphy_dwc3_reset_deassert',) + if atc_restore_helpers(files['atc']): + # A USB2-only PHY is brought back as at probe after its domain was off. + at = names['atc'].index('atcphy_restore_after_pd_off') + names['atc'] = names['atc'][:at] + ('atcphy_setup_pipehandler',) + names['atc'][at:] names['atc'] = tuple(name for name in names['atc'] if name not in ('atcphy_get_mode_config', 'j700_field_update')) names = {n: tuple(name for name in group if re.search(r'^(?:static [^\n]*\b|(?:int|void) )' + name + r'\(', files[n], re.M)) for n, group in names.items()} @@ -1668,7 +1691,8 @@ def vrr(root): struct apple_connector { bool connected; }; /* The native T6030 14.7 backend is never selected in these scenarios. */ enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, DCP_FIRMWARE_V_13_5, - DCP_FIRMWARE_V_14_7, DCP_FIRMWARE_V_14_7_TEMPLATE, DCP_FIRMWARE_V_26_6 }; + DCP_FIRMWARE_V_14_7, DCP_FIRMWARE_V_14_7_TEMPLATE, DCP_FIRMWARE_V_26_6, + DCP_FIRMWARE_V_26_6_DESKTOP }; struct apple_dcp { bool crashed, external, external_native, quiescing; unsigned int nr_typec_routes; @@ -1681,14 +1705,19 @@ def vrr(root): struct platform_device { struct apple_dcp *data; }; struct drm_crtc { struct platform_device *dcp; void *primary; }; struct drm_display_mode { int unused; }; -struct drm_crtc_state { bool enable, vrr_enabled, modeset, color_mgmt_changed; struct drm_display_mode mode; void *ctm, *degamma_lut, *gamma_lut; unsigned plane_mask; }; +struct drm_crtc_state { bool enable, vrr_enabled, modeset, mode_changed, color_mgmt_changed; struct drm_display_mode mode; void *ctm, *degamma_lut, *gamma_lut; unsigned plane_mask; }; /* mainline 7.2 renamed struct drm_atomic_state to struct drm_atomic_commit */ -struct drm_atomic_commit { struct drm_crtc_state crtc; }; +struct drm_atomic_commit { struct drm_crtc_state crtc, old_crtc; bool no_old_crtc; }; #define to_apple_crtc(x) (x) #define platform_get_drvdata(x) ((x)->data) static struct drm_crtc_state *drm_atomic_get_new_crtc_state( struct drm_atomic_commit *s, struct drm_crtc *c) { (void)c; return &s->crtc; } +/* Only the desktop 26.6 firmware compares against the old CRTC state. */ +static unsigned int old_state_lookups; +static struct drm_crtc_state * __attribute__((unused)) drm_atomic_get_old_crtc_state( + struct drm_atomic_commit *s, struct drm_crtc *c) +{ (void)c; old_state_lookups++; return s->no_old_crtc ? NULL : &s->old_crtc; } static bool drm_atomic_crtc_needs_modeset(struct drm_crtc_state *s) { return s->modeset; } static int __attribute__((unused)) iomfb_v14_7_atomic_check(struct apple_dcp *d, @@ -1746,14 +1775,45 @@ def vrr(root): } cases++; } - dcp.crashed = true; + /* No other firmware turns a VRR change into a mode set. */ + assert(!old_state_lookups && !state.crtc.mode_changed); +DESKTOP_VRR dcp.crashed = true; state.crtc.vrr_enabled = false; assert(dcp_crtc_atomic_check(&crtc, &state) == -EINVAL); printf("PASS: %u production atomic VRR admission cases and crashed-device rejection\n", cases); return 0; } """ - return prefix + check + suffix + desktop = '' + if 'DCP_FIRMWARE_V_26_6_DESKTOP' in check: + # Desktop 26.6 applies adaptive sync with a mode set: a VRR_ENABLED + # change of an enabled CRTC must become one, nothing else may. + desktop = r""" dcp.fw_compat = DCP_FIRMWARE_V_26_6_DESKTOP; + dcp.hw.iomfb_method_profile = 0; + dcp.mode_state.valid = true; + dcp.connector = &connector; + connector.connected = true; + state.crtc.modeset = false; + for (unsigned int enable = 0; enable < 2; enable++) + for (unsigned int old_vrr = 0; old_vrr < 3; old_vrr++) + for (unsigned int vrr = 0; vrr < 2; vrr++) { + state.crtc.enable = enable; + state.crtc.vrr_enabled = vrr; + state.crtc.mode_changed = false; + state.no_old_crtc = old_vrr == 2; + state.old_crtc.vrr_enabled = old_vrr == 1; + assert(dcp_crtc_atomic_check(&crtc, &state) == 0); + bool expected = enable && old_vrr != 2 && (old_vrr == 1) != vrr; + if (state.crtc.mode_changed != expected) { + fprintf(stderr, "desktop enable=%u old_vrr=%u vrr=%u: mode_changed=%d expected %d\n", + enable, old_vrr, vrr, state.crtc.mode_changed, expected); + return 1; + } + cases++; + } + dcp.fw_compat = DCP_FIRMWARE_UNKNOWN; +""" + return prefix + check + suffix.replace('DESKTOP_VRR', desktop, 1) def power_reply(root): @@ -1770,6 +1830,10 @@ def power_reply(root): #include #include #include +/* Darwin has no EREMOTEIO; this synthetic status is never passed to libc. */ +#ifndef EREMOTEIO +#define EREMOTEIO 121 +#endif typedef uint32_t u32; typedef uint64_t u64; #define WRITE_ONCE(p,v) ((p)=(v)) #define container_of(p,t,m) ((t *)((char *)(p)-offsetof(t,m))) @@ -1837,13 +1901,17 @@ def dispatcher(root): typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) #define DCP_FIRMWARE_H17P 1 +#define DCP_FIRMWARE_V_26_6_DESKTOP 5 #define DCP_IOMFB_METHODS_H17P_EXTERNAL 2 #define DCP_IOMFB_METHODS_H17G 3 #define DCP_MAX_CALL_DEPTH 8 #define IOMFB_MAX_CB 600 #define DCP_PACKET_ALIGNMENT 64 #define ALIGN(n, a) (((n) + (a) - 1) & ~((a) - 1)) +#define dcp_trace_panel_setter(...) ((void)0) #define dev_err(...) ((void)dev) #define dev_info(...) ((void)dev) #define dev_warn(...) ((void)dev) @@ -1860,6 +1928,7 @@ def dispatcher(root): bool (**cb_handlers)(struct apple_dcp *, int, void *, void *); unsigned long unknown_callbacks[16]; int start_done; + struct {bool modeset_active,modeset_callback_error;} *desktop; struct dcp_channel channel; }; static bool machine; @@ -1919,4 +1988,7 @@ def dispatcher(root): } """ - return prefix + source[start:end] + suffix + desktop = (root / "drivers/gpu/drm/apple/iomfb_v26_6_desktop.c").read_text() + checker = block(desktop, 'static bool iomfb_check_callback_exact_v26_6_desktop(') + alias = '\n#define iomfb_check_callback_v26_6_desktop iomfb_check_callback_exact_v26_6_desktop\n' + return prefix + checker + alias + source[start:end] + suffix diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/phy.py b/tools/testing/selftests/drivers/gpu/apple-dcp/phy.py index 0520a6bdd1226..2c4db1095f005 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/phy.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/phy.py @@ -4,7 +4,7 @@ from pathlib import Path import re from common import (block, source_defines, atc_mux_without_pm, ATC_POST_TUNABLE, - atc_function, ATC_SPLIT, ATC_T8103_LIVE) + atc_function, ATC_SPLIT, ATC_T8103_LIVE, atc_restore_helpers) def parking(root): @@ -51,6 +51,8 @@ def function(source, name): bool usb3_port_off, park_owed; bool t8103_dummy_pending, t8103_usb3_retry, t8103_usb3_workaround; bool dp_only, pd_was_off, tunnel_clock_on; + /* T8132 idle runtime PM; these PHYs never power down while idle. */ + bool idle_rpm, rpm_held; unsigned int tunnel_rate, tunnel_users; struct { bool aux, pll; unsigned int pairs, rate; } dp_t8122; enum atcphy_mode mode, typec_mode; @@ -176,6 +178,7 @@ def function(source, name): for name in ('core_read32', 'atcphy_get_mode_config', 'j700_field_update', 'j700_pma_idle'): parts.append(function(atc, name)) parts.append(dp) + parts.append(atc_restore_helpers(atc)) for name in ('atcphy_configure_lanes', 'j700_aux_enable', 'j700_aux_disable', 'atcphy_enable_dp_aux', 'atcphy_disable_dp_aux', 'atcphy_power_off', 'atcphy_power_on', *ATC_POST_TUNABLE, 'atcphy_configure', 'atcphy_set_orientation', diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py index 75fe10221fcc9..3c2f2b8df2bd5 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py @@ -2,9 +2,9 @@ # Copyright 2026 Ryan Murray """Actual PM/HCD/mux/probe boundaries using the shared PHY extraction fixture.""" from pathlib import Path -import os, re, subprocess +import os, re, shlex, shutil, subprocess import phy -from common import block, tipd_pm_header, atc_function +from common import block, tipd_pm_header, atc_function, ATC_RPM def ownership(root, usb_unreachable=False): ns=phy.parking(root) @@ -171,6 +171,8 @@ def ownership(root, usb_unreachable=False): assert(root_locked&&!pm_phy->lock); if(u==&link_device)assert(link_locked);else assert(u==&roots[3]&&!link_locked); } +/* Only an idle-power-off PHY takes a hold of its own; the fixture PHY never does. */ +static void __attribute__((unused)) pm_runtime_get_noresume(struct device *d) { (void)d; assert(!"PHY runtime PM hold"); } static void pm_runtime_put_autosuspend(struct device *d) { assert(root_locked&&!pm_phy->lock); if(d==&link_device.dev){assert(link_locked&&link_pm_refs==1);link_pm_refs--;} @@ -421,6 +423,8 @@ def ownership(root, usb_unreachable=False): ('atcphy_park_retry_delay','atcphy_park_retry','atcphy_park_due','atcphy_mux_change')) helpers+=ns['function'](s,'atcphy_mux_update')+'\n' # Lane fixtures provide the target mapping and locked body; use the real callback here. + # The mux callback powers an idle-power-off PHY first; this PHY is not one. + helpers+=''.join(atc_function(s,name) for name in ATC_RPM) marker=re.search(r'^static int atcphy_mux_set\(',s,re.M).group() helpers+=ns['block'](s,marker)+'\n' else: @@ -509,6 +513,8 @@ def function(text, name): static struct fwnode_handle *fwnode_find_reference(struct fwnode_handle *f,const char *s,int n) { (void)f;(void)s;(void)n;connector.refs++;return &connector; } +static bool fwnode_property_present(struct fwnode_handle *f,const char *s) { (void)f;(void)s;return true; } +static struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *f) { assert(!"connector is a displayport reference");return f; } static void __attribute__((unused)) fwnode_handle_put(struct fwnode_handle *f) { if(f){assert(f->refs>0);f->refs--;} } @@ -547,7 +553,7 @@ def function(text, name): int main(void) { (void)cd321x_pm_notifications; struct cd321x c; - struct cd321x_pm_state budget; + struct cd321x_pm_state budget={0}; cd321x_pm_init(&budget);assert(cd321x_pm_ready(&budget)); assert(cd321x_display_taken(-ENOSPC,true)&&!cd321x_display_taken(-EBUSY,true)&&!cd321x_display_taken(0,true)&&cd321x_display_taken(-EBUSY,false)&&!cd321x_display_taken(-ENOSPC,false)&&!cd321x_display_taken(0,false)); for(unsigned int i=0;iselected=false;return 0; } static void dcp_typec_port_attach(struct apple_dcp_typec_port *p) { (void)p; } static int gpiod_get_value_cansleep(void *p) { (void)p;return 0; } @@ -89,7 +93,7 @@ def make_unit(source, parent=False, reference=None): struct tps6598x; struct tipd_data { REMOVE_RETURN (*remove)(struct tps6598x *); void (*unregister_port)(struct tps6598x *); int (*reset)(struct tps6598x *); }; struct typec_port { struct typec_mux *mux; }; -struct tps6598x { bool remove_safe_prepared; struct typec_mux *mux; void *partner,*dev,*psy,*role_sw,*reset; int irq,wq_poll; struct typec_port *port; struct tipd_data *data; }; +struct tps6598x { bool remove_safe_prepared; struct typec_mux *mux; void *partner,*dev,*psy,*role_sw,*reset,*partner_altmode_dp; int irq,wq_poll; struct typec_port *port; struct tipd_data *data; }; struct cd321x { struct tps6598x tps; struct typec_mux *mux; void *tbt_switch; }; struct typec_thunderbolt_switch_data { int state; }; static void cd321x_stop_work(struct cd321x *c) { (void)c;event('W'); } @@ -97,6 +101,9 @@ def make_unit(source, parent=False, reference=None): static int usb_role_switch_set_role(void *p,int r) { (void)p;assert(r==USB_ROLE_NONE);if(ordinary_port&&!role_error){int ret=ordinary_power_off();if(ret)return ret;controller_active=false;phy_off=true;}event('R');role_calls++;return role_error; } static enum usb_role __attribute__((unused)) usb_role_switch_get_role(void *p) { (void)p;return controller_active?USB_ROLE_HOST:USB_ROLE_NONE; } static void typec_unregister_partner(void *p) { (void)p;event('P'); } +/* These fixtures never enter DP on the partner, so its altmode stays NULL. */ +static void typec_altmode_update_active(void *a,bool v) { (void)a;(void)v;assert(!"no partner DP altmode here"); } +static void typec_unregister_altmode(void *a) { (void)a;assert(!"no partner DP altmode here"); } #define typec_set_pwr_opmode(...) ((void)0) #define typec_set_pwr_role(...) ((void)0) #define typec_set_vconn_role(...) ((void)0) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 7bd087c364c52..24335a890408b 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -48,6 +48,8 @@ def wire(value, out): metadata = block(metadata, 'struct dcp_display_metadata {') + ';\n' header = (base / 'parser.h').read_text() header = header[header.index('struct apple_dcp;'):header.index('int parse(')] + refresh = (base / 'dcp-refresh.h').read_text().replace('#include ', '') + header = refresh + '\n' + header # Header helpers stay header-like: unused here, as in any includer. header = header.replace('static inline ', 'static inline __attribute__((unused)) ') edid_header = (root / 'include/drm/drm_edid.h').read_text() @@ -142,6 +144,15 @@ def wire(value, out): #define mul_u32_u32(a, b) ((u64)(u32)(a) * (u32)(b)) #define DIV_ROUND_CLOSEST_ULL(n, d) (((n) + (d) / 2) / (d)) #define DIV_ROUND_CLOSEST(n, d) (((n) + (d) / 2) / (d)) +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif +#ifndef max +#define max(a,b) ((a)>(b)?(a):(b)) +#endif +#define max_t(t,a,b) max((t)(a),(t)(b)) +#define div_u64(n,d) ((n)/(d)) +#define DIV_ROUND_UP_ULL(n,d) (((u64)(n)+(d)-1)/(d)) #define trace_iomfb_color_mode(...) ((void)0) #define trace_iomfb_parse_mode_fail(...) ((void)0) #define trace_iomfb_parse_mode_success(...) ((void)0) @@ -360,7 +371,10 @@ def case(value, automatic=True, internal=False, external=True, admitted=True): callback_types = '\n'.join(block(dptx, marker)+' __packed;' for marker in ('struct dptxport_apcall_link_rate {', 'struct dptxport_apcall_set_active_lane_count {')) callback = block(dptx, 'static int dptxport_call_link_config(') dcp = (base/'dcp.c').read_text() - connection = '\n'.join(block(dcp, marker) for marker in ( + # The modelled DCP is the H17P external one, never a desktop 26.x processor. + connection = 'static bool dcp_sink_metadata(struct apple_dcp *d) { (void)d; return false; }\n' + \ + 'static void dcp_desktop_metadata_new_sink(struct apple_dcp *d) { (void)d; assert(0); }\n' + \ + '\n'.join(block(dcp, marker) for marker in ( 'void dcp_firmware_metadata_connection(', 'static void dcp_firmware_metadata_present_locked(', 'static void dcp_announce_kick(', 'void dcp_firmware_metadata_announced(', 'void dcp_firmware_metadata_hotplug(')) @@ -636,6 +650,12 @@ def publication(root): } static const struct drm_edid *dcp_firmware_edid(struct apple_dcp *d,u64 *generation) { (void)d; (void)generation; return ERR_PTR(-ENODATA); } static bool dcp_firmware_metadata_current(struct apple_dcp *d,u64 generation) { (void)d; (void)generation; return false; } +static const struct drm_edid *dcp_panel_edid_snapshot(struct apple_dcp *d,const struct dcp_display_mode *m,unsigned n,u64 *generation) { (void)d;(void)m;(void)n;(void)generation;assert(!"panel identity in external fixture");return ERR_PTR(-ENODATA); } +static bool dcp_panel_metadata_current(struct apple_dcp *d,u64 generation) { (void)d;(void)generation;assert(!"panel identity in external fixture");return false; } +static void dcp_panel_metadata_replace(struct apple_dcp *d,const struct dcp_display_metadata *m) { (void)d;(void)m;assert(!"panel identity in external receipt"); } +static void dcp_panel_edid_receive(struct apple_dcp *d,const void *p,size_t n) { (void)d;(void)p;(void)n;assert(!"panel EDID in external receipt"); } +#define dcp_trace_panel_property(...) ((void)0) + static struct drm_display_mode *drm_mode_duplicate(struct drm_device *dev,const struct drm_display_mode *m) { assert(dev->mode_config.mutex);struct drm_display_mode *copy=malloc(sizeof(*copy));assert(copy);*copy=*m; if(invalidate_during_duplicate) { invalidate_during_duplicate=false;dcp_180hz_invalidate(probing); } @@ -677,8 +697,11 @@ def publication(root): #define DCP_FIRMWARE_V_14_7 (DCP_FIRMWARE_H17P + 1) #define DCP_FIRMWARE_V_14_7_TEMPLATE (DCP_FIRMWARE_H17P + 2) #endif -/* Nor the desktop 26.6 firmware, the one that withholds VRR modes. */ +/* Nor the desktop 26.6 firmware. */ #define DCP_FIRMWARE_V_26_6_DESKTOP (DCP_FIRMWARE_H17P + 3) +#define DIV_ROUND_UP(n,d) (((n)+(d)-1)/(d)) +static void dcp_update_connector_dimensions(struct apple_dcp *d,struct drm_connector *c) +{ (void)c;assert(d->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP); } '''+firmware_lineage(root)+internal_inline(root,'dcp_fw_is_14_7')+r'''/* Off T6030 machines the direct crossbar output is not clocked separately. */ static int __attribute__((unused)) dcp_direct_crossbar_link(struct apple_dcp *d,bool up) { (void)d;(void)up;return 0; } ''' @@ -700,7 +723,9 @@ def publication(root): + ''.join(apple_function(root, name) for name in ('dcp_queue_hotplug', 'dcp_modes_for_connector', 'apple_connector_invalidate_edid', 'apple_connector_edid_set_live')) + mode_helpers) - publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n'+block(iomfb,'int dcp_get_modes(')+block(iomfb,re.search(r'^[^\n;(]*\blookup_mode\(',iomfb,re.M).start()) + # The desktop 26.6 VRR filter is gone once that firmware takes adaptive sync. + vrr_filter = block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n' if 'static bool dcp_mode_vrr_supported(' in iomfb else '' + publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+vrr_filter+block(iomfb,'int dcp_get_modes(')+block(iomfb,'bool lookup_mode(') # Drive malformed replacements through the same chunk/end ingress as firmware. # The dictionary cache is a boundary substitute and takes ownership of the blob. receipt = r''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py index 54bcad84819d2..efc79d1345f7a 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/run.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/run.py @@ -26,9 +26,12 @@ import transaction import accounting import idle +import desktop +import j616s import rate import boot import m4 +import display def parking_unit(root): @@ -40,7 +43,7 @@ def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parents[6]) parser.add_argument('--output', type=Path, help='retain generated C, commands and results here') - parser.add_argument('--group', choices=('all', 'edid', 'rate', 'phy', 'lifecycle', 'boot', 'quiesce', 'm4'), default='all') + parser.add_argument('--group', choices=('all', 'edid', 'rate', 'phy', 'lifecycle', 'boot', 'quiesce', 'm4', 'display'), default='all') args = parser.parse_args() root = args.source.resolve() output = args.output or Path(tempfile.mkdtemp(prefix='apple-dcp-selftest-')) @@ -51,6 +54,23 @@ def main(): 'orientation-faults', 'backend-change', 'safe-first', 'failed-flip-return', 'live-park')) tests = [ + ('lifecycle', 'desktop-modeset-unmap', desktop.modeset_unmap, ((),), ()), + ('lifecycle', 'desktop-unknown-modeset-recovery', desktop.modeset_unknown, ((),), ()), + ('display', 'panel-backlight-ramp', display.backlight, ((),), ()), + ('display', 'panel-takeover-registration', desktop.brightness_takeover, ((),), ()), + ('display', 'panel-brightness-commit', display.brightness_commit, ((),), ()), + ('display', 'panel-brightness-takeover', display.takeover, ((),), ()), + ('display', 'panel-mode-catalog', display.modes, ((),), ()), + ('display', 'desktop-brightness-notifications', desktop.brightness_notifications, ((),), ()), + ('display', 'j616s-panel-identity', lambda r: j616s.identity(r, output), ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), + ('display', 'desktop-adt-identity', desktop.adt_identity, ((),), ()), + ('display', 'desktop-identity-trace-budget', desktop.identity_trace_budget, ((),), ()), + ('display', 'desktop-identity-keys', desktop.identity_keys, ((),), ('-Wno-unused-function',)), + ('display', 'desktop-panel-identity', lambda r: desktop.panel_identity(r, output), ((),), ()), + ('display', 'desktop-powerlog-framing', desktop.powerlog_framing, ((),), ()), + ('display', 'desktop-brightness-keys-reports', desktop.keys_and_reports, ((),), ()), + ('display', 'panel-refresh-range', display.refresh, ((),), ()), + ('display', 'panel-adaptive-watchdog', display.watchdog, ((),), ()), ('m4', 'm4-wire-golden', lambda r: m4.golden_check(r, output), (), ()), ('m4', 'm4-protocol', m4.protocol, ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), ('m4', 'm4-common-plane', m4.plane, ((),), ('-Wno-unused-function', '-Wno-unused-const-variable')), diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/teardown_pm.py b/tools/testing/selftests/drivers/gpu/apple-dcp/teardown_pm.py index b4a4b18a8f468..7381092399252 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/teardown_pm.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/teardown_pm.py @@ -94,7 +94,8 @@ def unit_for(source): #define hcd_to_xhci(h) (&xhci_context) struct dwc3 { bool sys_wakeup; struct platform_device *xhci; }; enum { DWC3_APPLE_PROBE_PENDING, DWC3_APPLE_NO_CABLE, DWC3_APPLE_HOST, DWC3_APPLE_DEVICE }; -struct dwc3_apple { struct dwc3 dwc; struct device *dev; int lock,state; void *role_sw; }; +/* pre_core_rpm is T8132 only: runtime PM before the first core probe; never set here. */ +struct dwc3_apple { struct dwc3 dwc; struct device *dev; int lock,state; void *role_sw; bool pre_core_rpm; }; static struct dwc3_apple apple_context; #define to_dwc3_apple(d) container_of(d,struct dwc3_apple,dwc) static bool inside_host_remove; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py index 5dea64a19054e..6c40adf5d3a85 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py @@ -40,13 +40,21 @@ def check(name, unit, cases=((),), negative=False, flags=()): bool external_power_attempted,dptx_tunnel,tb_clock_ok,fixed_route_selected; int tb_lock; unsigned typec_generation,dptx_dfp_port,dptx_phy,fixed_dptx_phy,fixed_mux_index; void *tb_dpin_set_active,*tb_dpin_ctx,*phy,*fixed_phy; - struct mux_control *xbar; + struct mux_control *xbar,*fixed_xbar; + /* desktop 26.x hybrid hand-over and dual-pipe state; fw_compat is never desktop here */ + bool peer_starved,typec_narrow,hdmi_yielded; struct apple_dcp_typec_route *active_typec_route,*typec_routes; struct connector *connector,*fixed_connector;int connector_type,fixed_connector_type; struct mux_control *direct_xbar_up; bool external_native, ext_dead; int fw_compat; unsigned long long tb_generation; int modes_lock; unsigned long long modes_generation; bool modes_admitted,modes_provisional; struct { int iomfb_method_profile; } hw; ''',1) + # The hybrid hand-over walks a port's routes; the only route is this port's. + actual=actual.replace('struct apple_dcp_typec_port { int link;', 'struct apple_dcp_typec_port { int link,routes; bool typec_yielded;',1) + actual=actual.replace('#define list_for_each_entry(p,h,m) for((void)(h),(p)=sole_port();(p);(p)=NULL)', + '#define list_for_each_entry(p,h,m) for((void)(h),(p)=_Generic((p),struct apple_dcp_typec_route *:sole_route(),default:sole_port());(p);(p)=NULL)',1) + actual=actual.replace('static struct apple_dcp_typec_port *sole_port(void) { return &port; }', + 'static struct apple_dcp_typec_port *sole_port(void) { return &port; }\nstatic struct apple_dcp_typec_route * __attribute__((unused)) sole_route(void) { return &route; }',1) actual=actual.replace('bool applied_valid,safe_pending,dp_wanted,dp_hpd,hpd;', 'struct typec_altmode *applied_alt; unsigned long applied_mode; unsigned applied_status,applied_conf; bool applied_valid,safe_pending,dp_wanted,dp_hpd,hpd;',1) actual=actual.replace('void *xbar; bool deactivate_xbar_done; int deactivate_error; bool selected,tunnel;', 'struct mux_control *xbar,*active_xbar; void *phy; unsigned dptx_phy,tunnel_dpin,mux_index;unsigned long long tunnel_generation;bool deactivate_xbar_done; int deactivate_error;bool xbar_up,selected,tunnel;',1) extra=r''' @@ -70,6 +78,10 @@ def check(name, unit, cases=((),), negative=False, flags=()): static int mux_control_deselect(struct mux_control *m) { (void)m;event('C');xbar_calls++;return xbar_error; } static int mux_control_select(struct mux_control *m,unsigned n) { (void)m;(void)n;return 0; } static void dpavservep_detach(struct apple_dcp *d) { (void)d; } + /* Desktop 26.6 dual-pipe peers: no scenario here is on that firmware. */ + static void __attribute__((unused)) dcp_peer_release_26_6(struct apple_dcp *d) { (void)d;assert(!"desktop 26.6 peer release"); } + /* failed takes of the hybrid, reset when a Type-C display leaves DP */ + static unsigned int __attribute__((unused)) dcp_hybrid_fails; static void dcp_firmware_metadata_connection(struct apple_dcp *d,unsigned n,bool c) { (void)d;(void)n;(void)c; } static bool dcp_typec_keep_order(void) { return false; } static struct apple_dcp_typec_route * __attribute__((unused)) dcp_typec_lowest_free(struct apple_dcp_typec_port *p) { (void)p;return &route; } @@ -115,6 +127,11 @@ def check(name, unit, cases=((),), negative=False, flags=()): '''+available # Route-follow: the fixed output, or a failed T6030 external processor, keeps the route busy. available+=apple_function(ROOT,'dcp_typec_route_busy') + # Hybrid-first policy: the real helper over the module parameter's and the + # device tree's defaults (-1: from the DT; no apple,typec-hybrid-first here). + hybrid_first=apple_function(ROOT,'dcp_typec_hybrid_first') + if hybrid_first: + available='static int typec_hybrid_first=-1;\n static bool typec_hybrid_first_dt;\n '+hybrid_first+available if available: actual=actual.replace('struct apple_dcp { int external_lock,external_error;','struct apple_dcp { int external_lock,external_error;\n struct dcp_fabric_presence hdmi_presence; int hdmi_presence_lock; unsigned nr_typec_routes;',1) available='''#define atomic_read(p) (*(p)) diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/README b/tools/testing/selftests/drivers/gpu/asahi-m4/README index 9f7859015a19e..c88abd122428e 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/README +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/README @@ -39,6 +39,11 @@ queue/context/notifier records and TTBAT generation words. Comparisons include are compared against the actual kernel header by compiling complete C and Rust definitions and measuring sizes, alignments, member offsets and constants. Comments and documentation can change without invalidating that ABI check. +The current global-parameters reply has a documented additive capacity tail. +The frozen 600-byte prefix is compared unchanged; independent C assertions +require the full reply to be exactly 616 bytes, with two eight-byte fields at +600 and 608. Older Mesa headers can still match the shared prefix. Firmware +encoder golden data and its fixture manifest are never regenerated. No Mesa headers are vendored. Set ASAHI_M4_MESA=/path/to/mesa, or run `test-uapi.py --mesa /path/to/mesa`, for a compatibility check against the real userspace source; a default host run reports that Mesa comparison was omitted. diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py b/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py index dd4391a686a14..2f1d78a9e962d 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/abi.py @@ -61,12 +61,18 @@ def run_measurements(command, source, binary): def c_layout(header, bindings, output, tag): keys, _ = expressions(bindings) + capacity_tail = 'client_memory_limit;' in header.read_text() + # Bindings frozen after the capacity tail landed already describe all of it. + frozen_tail = 'client_memory_limit' in bindings.read_text() # Linux 7.2 folds the historical extension structs into the base payloads. # Compare the same wire prefixes and offsets while keeping the C oracle frozen. if 'struct drm_asahi_cmd_render_with_prior {' not in header.read_text(): translated = [] for key, expr in keys: kind, _, field = key.partition(':') + if capacity_tail and not frozen_tail and key == 'drm_asahi_params_global:size': + # Compare the frozen reply prefix; validate the entire known additive tail below. + expr = 'offsetof(struct drm_asahi_params_global, memory_limit)' if key == "drm_asahi_vm_create:pad": expr = "offsetof(struct drm_asahi_vm_create, flags)" base = None @@ -89,6 +95,14 @@ def c_layout(header, bindings, output, tag): source = output / (tag + '.c') lines = ['#include ', '#include ', '#include ' + json.dumps(str(header)), 'int main(void) {'] + if capacity_tail: + lines[3:3] = [ + '_Static_assert(sizeof(struct drm_asahi_params_global) == 616, "capacity reply size");', + '_Static_assert(offsetof(struct drm_asahi_params_global, memory_limit) == 600, "capacity prefix");', + '_Static_assert(offsetof(struct drm_asahi_params_global, client_memory_limit) == 608, "client capacity offset");', + '_Static_assert(sizeof(((struct drm_asahi_params_global *)0)->memory_limit) == 8, "capacity width");', + '_Static_assert(sizeof(((struct drm_asahi_params_global *)0)->client_memory_limit) == 8, "client capacity width");', + ] lines += [f'printf("{key}=%llu\\n", (unsigned long long)({expr}));' for key, expr in keys] source.write_text('\n'.join(lines + ['return 0;', '}']) + '\n') return run_measurements(shlex.split(os.environ.get('CC', 'cc')) + diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py index 842ca85c090fb..9a159e849e86b 100755 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-timestamps.py @@ -24,6 +24,7 @@ def function(source, marker): poll = (backend / 'g16/poll.rs').read_text() cache_policy = function((backend / 'gem.rs').read_text(), 'pub(crate) fn is_writeback(') store = function(mmu, 'pub(crate) fn write(&self, value: u64)') +vmap_store = function(mmu, 'pub(crate) fn write(&self, offset: usize, value: u64)') write = function(shared, 'fn write(&self, ticks: u64, frequency_hz: u64)') checked_offset = function(shared, 'fn checked_offset(') publish = function(shared, 'pub(crate) fn publish(&self, ticks: [u64; 4], frequency_hz: u64)') @@ -50,7 +51,12 @@ def function(source, marker): unsafe impl Send for Map {} unsafe impl Sync for Map {} struct View(*mut u8);impl View{fn as_mut_ptr(&self)->*mut u8{self.0}} impl Map{fn get(&self)->View{View(self.backing.words.as_ptr().cast_mut().cast())}fn owner(&self)->&Backing{&self.backing}} -struct TimestampMapping{mapping:Map,offset:usize} +struct TimestampVmap(Map); +impl TimestampVmap { + fn size(&self)->usize{core::mem::size_of_val(&self.0.backing.words)} + __VMAP_STORE__ +} +struct TimestampMapping{mapping:TimestampVmap,offset:usize} impl TimestampMapping { __STORE__ } __CHECKED_OFFSET__ struct Destination{cpu:TimestampMapping} @@ -63,7 +69,7 @@ def function(source, marker): fn backing()->Arc{backing_with_flags(0)} fn backing_with_flags(flags:u32)->Arc{Arc::new(Backing{flags,words:[const{AtomicU64::new(u64::MAX)};4],timestamp_lock:std::sync::Mutex::new(())})} fn destination(memory:&Arc,offset:usize)->Destination{ - Destination{cpu:TimestampMapping{mapping:Map{backing:memory.clone(),addr:memory.words.as_ptr().cast_mut().cast()},offset}} + Destination{cpu:TimestampMapping{mapping:TimestampVmap(Map{backing:memory.clone(),addr:memory.words.as_ptr().cast_mut().cast()}),offset}} } fn outputs(memory:&Arc)->Destinations{ Destinations(core::array::from_fn(|i|Some(destination(memory,i*8)))) @@ -191,7 +197,7 @@ def function(source, marker): assert_eq!(firmware().publish_timestamps(&flight,&claims),Err(EINVAL)); assert_eq!(memory.words[0].load(Ordering::Relaxed),u64::MAX); } -'''.replace('__CHECKED_OFFSET__', checked_offset).replace('__STORE__', store).replace('__CACHE_POLICY__', cache_policy).replace('__WRITE__', write).replace('__PUBLISH__', publish).replace('__RETIRE__', retire.replace('fn publish_timestamps', 'pub(crate) fn publish_timestamps')) +'''.replace('__CHECKED_OFFSET__', checked_offset).replace('__STORE__', store).replace('__VMAP_STORE__', vmap_store.replace('.timestamp_lock.lock()', '.timestamp_lock.lock().unwrap()')).replace('__CACHE_POLICY__', cache_policy).replace('__WRITE__', write).replace('__PUBLISH__', publish).replace('__RETIRE__', retire.replace('fn publish_timestamps', 'pub(crate) fn publish_timestamps')) path = out / 'timestamps-host.rs' path.write_text(source) subprocess.run(rustc() + ['--edition=2021', '--test', str(path), '-o', str(out / 'timestamps-host')], check=True) diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py index 39d2500f60b19..a6c936f3c0b4a 100755 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py @@ -27,7 +27,7 @@ def main(): if args.mesa: mesa = args.mesa.resolve() / 'include/drm-uapi/asahi_drm.h' errors += differences(kernel, c_layout(mesa, bindings, out, 'mesa-layout')) - report = {'passed': not errors, 'scope': 'Compiled public UAPI structures, field offsets and constants; no kernel parser or feature-semantic coverage', + report = {'passed': not errors, 'scope': 'Compiled public UAPI structures, field offsets and constants; frozen params prefix plus exact known capacity-tail assertions; no parser or feature-semantic coverage', 'kernel': str(header), 'bindings': str(bindings), 'comparisons': len(kernel), 'mesa': str(mesa) if mesa else None, 'mesa_comparison': 'performed' if mesa else 'not requested; set ASAHI_M4_MESA or --mesa', diff --git a/tools/testing/selftests/media/Makefile b/tools/testing/selftests/media/Makefile new file mode 100644 index 0000000000000..b63cb0f120e7e --- /dev/null +++ b/tools/testing/selftests/media/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +CFLAGS += -O2 -Wall -Wextra -Werror +TEST_GEN_PROGS_EXTENDED := apple-isp-stress +include ../lib.mk diff --git a/tools/testing/selftests/media/README.apple-isp b/tools/testing/selftests/media/README.apple-isp new file mode 100644 index 0000000000000..57b95740120c0 --- /dev/null +++ b/tools/testing/selftests/media/README.apple-isp @@ -0,0 +1,34 @@ +Apple ISP capture delivery stress +================================= + +Build with make -C tools/testing/selftests/media. This is a manual hardware +runner; run_tests does not start the camera automatically. No image files are +written. Run only on the agreed J616s test kernel while audio is idle. + +Example for configurations whose expected rate has been established as 30 fps: + + ./apple-isp-stress /dev/video0 600 10 30 | tee capture-results.jsonl + +It enumerates every offered single-planar format and discrete size. Each gets +a ten-minute run, then ten five-second start/stop cycles, switching formats and +sizes between streams. A substituted format/rate, poll timeout, queue error, +V4L2 buffer error, sequence discontinuity or delivery gap exceeding 1.5 nominal +frame periods fails. Delivered FPS must reach 99.8% of the supplied rate, allowing +29.97/30 rounding. The rate argument is an explicit test expectation, not a +rate inferred from the sensor clock or set by the tool as an alleged sensor fact. +For modes with a different nominal rate, filter the run explicitly, for example +`./apple-isp-stress /dev/video0 600 10 25 P010 1920 1080`. A filtered run tests +that mode's restart cycles; the unfiltered run also exercises format switches. This runner stops +on the first failure to preserve evidence and avoid retrying ambiguous firmware. + +The driver's sequence increments for received reports, so consecutive sequence +values alone cannot establish zero sensor/firmware drops. The timing checks +measure delivery gaps and completed frames. A true zero-drop sensor claim also +needs a validated firmware frame counter or an independent sensor-rate oracle. +No RAW packing, Bayer order or nominal per-config FPS is guessed by this tool. +It tests RAW automatically if a verified RAW format is added to ENUM_FMT later. + +Preserve the current firmware config dump, v4l2-ctl --all/--list-formats-ext, +the JSONL output, dmesg before/after, exact kernel and source hashes. Check both +P010 presets for correct image content separately; this runner deliberately +avoids a converter or disk writer in the capture loop. diff --git a/tools/testing/selftests/media/apple-isp-stress.c b/tools/testing/selftests/media/apple-isp-stress.c new file mode 100644 index 0000000000000..cd85e51ecba09 --- /dev/null +++ b/tools/testing/selftests/media/apple-isp-stress.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* V4L2 delivery stress test. Does not infer sensor FPS from a driver counter. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_MODES 256 +#define MAX_BUFFERS 16 + +struct mode { + uint32_t fourcc, width, height; +}; + +struct mapping { + void *address; + size_t size; +}; + +static int xioctl(int fd, unsigned long cmd, void *arg) +{ + int ret; + + do { + ret = ioctl(fd, cmd, arg); + } while (ret < 0 && errno == EINTR); + return ret; +} + +static double monotonic(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1000000000.0; +} + +static int capture(int fd, struct mode mode, unsigned int seconds, + unsigned int fps, unsigned int cycle) +{ + struct v4l2_format format = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE }; + struct v4l2_streamparm parm = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE }; + struct v4l2_requestbuffers req = { + .count = 4, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP, + }; + struct mapping maps[MAX_BUFFERS] = {}; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + unsigned int frames = 0, bad = 0, gaps = 0, sequence_gaps = 0, mapped = 0; + unsigned int last_sequence = 0; + double start, end, first = 0, last = 0, max_gap = 0, measured_fps = 0; + bool streaming = false; + int ret = -1; + + format.fmt.pix.width = mode.width; + format.fmt.pix.height = mode.height; + format.fmt.pix.pixelformat = mode.fourcc; + format.fmt.pix.field = V4L2_FIELD_NONE; + if (xioctl(fd, VIDIOC_S_FMT, &format) < 0) + goto error; + /* Fail instead of counting a silently substituted mode as tested. */ + if (format.fmt.pix.width != mode.width || format.fmt.pix.height != mode.height || + format.fmt.pix.pixelformat != mode.fourcc) { + fprintf(stderr, "requested mode was substituted\n"); + goto out; + } + parm.parm.capture.timeperframe.numerator = 1; + parm.parm.capture.timeperframe.denominator = fps; + if (xioctl(fd, VIDIOC_S_PARM, &parm) < 0) + goto error; + if (parm.parm.capture.timeperframe.numerator != 1 || + parm.parm.capture.timeperframe.denominator != fps) { + fprintf(stderr, "requested rate was substituted\n"); + goto out; + } + if (xioctl(fd, VIDIOC_REQBUFS, &req) < 0) + goto error; + if (req.count < 2 || req.count > MAX_BUFFERS) + goto out; + for (unsigned int i = 0; i < req.count; i++) { + struct v4l2_buffer buf = { + .type = type, .memory = V4L2_MEMORY_MMAP, .index = i, + }; + + if (xioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) + goto error; + maps[i].size = buf.length; + maps[i].address = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, buf.m.offset); + if (maps[i].address == MAP_FAILED) + goto error; + mapped++; + if (xioctl(fd, VIDIOC_QBUF, &buf) < 0) + goto error; + } + if (xioctl(fd, VIDIOC_STREAMON, &type) < 0) + goto error; + streaming = true; + start = monotonic(); + end = start + seconds; + while (monotonic() < end) { + struct pollfd pfd = { .fd = fd, .events = POLLIN }; + struct v4l2_buffer buf = { .type = type, .memory = V4L2_MEMORY_MMAP }; + double timestamp, gap; + int ready = poll(&pfd, 1, 2000); + + if (ready < 0 && errno == EINTR) + continue; + if (ready <= 0 || (pfd.revents & (POLLERR | POLLHUP | POLLNVAL))) { + fprintf(stderr, "stream stalled or queue failed\n"); + goto out; + } + if (xioctl(fd, VIDIOC_DQBUF, &buf) < 0) + goto error; + if (buf.index >= req.count || buf.bytesused > maps[buf.index].size) + goto out; + if (buf.flags & V4L2_BUF_FLAG_ERROR) + bad++; + timestamp = buf.timestamp.tv_sec + buf.timestamp.tv_usec / 1000000.0; + if (frames) { + gap = timestamp - last; + if (gap > max_gap) + max_gap = gap; + if (gap <= 0 || gap > 1.5 / fps) + gaps++; + if ((uint32_t)(buf.sequence - last_sequence) != 1) + sequence_gaps++; + } else { + first = timestamp; + } + last = timestamp; + last_sequence = buf.sequence; + frames++; + /* Requeue immediately; image copies/conversion do not limit the test. */ + if (xioctl(fd, VIDIOC_QBUF, &buf) < 0) + goto error; + } + if (frames > 1 && last > first) + measured_fps = (frames - 1) / (last - first); + /* Allow clock/29.97-vs-30 rounding; gaps and buffer errors must be zero. */ + ret = bad || gaps || sequence_gaps || measured_fps < fps * 0.998 ? 1 : 0; + goto out; +error: + perror("V4L2 ioctl"); +out: + if (streaming && xioctl(fd, VIDIOC_STREAMOFF, &type) < 0) { + perror("STREAMOFF"); + ret = -1; + } + for (unsigned int i = 0; i < mapped; i++) + munmap(maps[i].address, maps[i].size); + req.count = 0; + if (xioctl(fd, VIDIOC_REQBUFS, &req) < 0) + ret = -1; + printf("{\"cycle\":%u,\"fourcc\":\"%.4s\",\"width\":%u,\"height\":%u,", + cycle, (char *)&mode.fourcc, mode.width, mode.height); + printf("\"stride\":%u,\"sizeimage\":%u,\"requested_fps\":%u,\"frames\":%u,", + format.fmt.pix.bytesperline, format.fmt.pix.sizeimage, fps, frames); + printf("\"delivered_fps\":%.6f,\"max_gap_ms\":%.3f,\"timing_gaps\":%u,", + measured_fps, max_gap * 1000, gaps); + printf("\"buffer_errors\":%u,\"sequence_gaps\":%u,\"pass\":%s}\n", + bad, sequence_gaps, ret ? "false" : "true"); + fflush(stdout); + return ret; +} + +int main(int argc, char **argv) +{ + struct mode modes[MAX_MODES]; + unsigned int count = 0, seconds, cycles, fps; + int fd, failed = 0; + char *end; + unsigned long values[3]; + + if (argc != 5 && argc != 8) { + fprintf(stderr, "usage: %s device seconds cycles fps [FOURCC width height]\n", + argv[0]); + return 2; + } + for (int i = 0; i < 3; i++) { + errno = 0; + values[i] = strtoul(argv[i + 2], &end, 10); + if (errno || *end || !values[i] || values[i] > 86400) + return 2; + } + seconds = values[0]; + cycles = values[1]; + fps = values[2]; + if (fps > 240 || cycles > 1000 || seconds < 5) + return 2; + fd = open(argv[1], O_RDWR | O_NONBLOCK); + if (fd < 0) { + perror(argv[1]); + return 2; + } + for (unsigned int i = 0; ; i++) { + struct v4l2_fmtdesc format = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .index = i }; + + if (xioctl(fd, VIDIOC_ENUM_FMT, &format) < 0) { + if (errno != EINVAL) + failed = 1; + break; + } + /* This harness exercises the driver's contiguous, single-planar API. */ + if (format.pixelformat == V4L2_PIX_FMT_NV12M) + continue; + for (unsigned int j = 0; ; j++) { + struct v4l2_frmsizeenum size = { + .pixel_format = format.pixelformat, .index = j, + }; + + if (xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &size) < 0) { + if (errno != EINVAL) + failed = 1; + break; + } + if (size.type != V4L2_FRMSIZE_TYPE_DISCRETE || count == MAX_MODES) { + failed = 1; + break; + } + modes[count++] = (struct mode){format.pixelformat, size.discrete.width, + size.discrete.height}; + } + } + if (argc == 8) { + uint32_t fourcc; + unsigned long width, height; + bool found = false; + + if (strlen(argv[5]) != 4) { + close(fd); + return 2; + } + memcpy(&fourcc, argv[5], sizeof(fourcc)); + errno = 0; + width = strtoul(argv[6], &end, 10); + if (errno || *end || !width || width > UINT32_MAX) { + close(fd); + return 2; + } + errno = 0; + height = strtoul(argv[7], &end, 10); + if (errno || *end || !height || height > UINT32_MAX) { + close(fd); + return 2; + } + for (unsigned int i = 0; i < count; i++) { + if (modes[i].fourcc == fourcc && modes[i].width == width && + modes[i].height == height) { + modes[0] = modes[i]; + found = true; + break; + } + } + count = found ? 1 : 0; + } + if (!count) + failed = 1; + /* Long runs first, then repeated stops/restarts and format/size switches. */ + for (unsigned int cycle = 0; cycle <= cycles && !failed; cycle++) { + for (unsigned int i = 0; i < count; i++) { + if (capture(fd, modes[i], cycle ? 5 : seconds, fps, cycle)) { + failed = 1; + break; + } + } + } + close(fd); + return failed ? 1 : 0; +}