From 172fd58e007f02758c7b5eb57f4d0160ed62f6ef Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 21:12:47 +0200 Subject: [PATCH 01/13] i915n: enable admitted display platforms with per-device headless recovery --- docs/i915n-display.md | 71 +++++++++++++++++++ drivers/i915/display-tus.txt | 6 +- drivers/i915/illumos/i915_illumos.c | 44 +++++++++++- drivers/i915/illumos/i915_illumos_con_kms.c | 8 +++ ...display-interrupt-registers-headless.patch | 4 +- ...11-illumos-display-on-ivybridge-only.patch | 43 ----------- .../patches/0011-illumos-display-policy.patch | 35 +++++++++ packaging/i915n.conf | 20 ++++++ packaging/i915n.p5m | 10 +-- 9 files changed, 187 insertions(+), 54 deletions(-) create mode 100644 docs/i915n-display.md delete mode 100644 drivers/i915/patches/0011-illumos-display-on-ivybridge-only.patch create mode 100644 drivers/i915/patches/0011-illumos-display-policy.patch create mode 100644 packaging/i915n.conf diff --git a/docs/i915n-display.md b/docs/i915n-display.md new file mode 100644 index 0000000..d9c7a83 --- /dev/null +++ b/docs/i915n-display.md @@ -0,0 +1,71 @@ +# i915n display enablement pilot + +The default display build now uses the Linux display description for every +GPU admitted by patch 0009: PCH-based integrated Gen6 through Gen9.5. This +includes Sandy Bridge, Ivy Bridge, Haswell, Broadwell, Skylake and the +Kaby/Coffee/Comet Lake families represented by the pinned PCI tables. +Only Ivy Bridge has prior hardware acceptance. Other families are experimental. +This change does not expand the admission guard to Gen10+, low-power SoCs, +or discrete GPUs. Compiling their display translation units is not support. + +## Recovery policy + +`/kernel/drv/i915n.conf` documents three attach-time integer properties: + +* `i915n-force-no-display=1;` disables display on matching device nodes. +* `i915n-no-display-device-ids=0x1916,0x1926;` matches PCI device IDs. +* `i915n-no-display-generations=9;` matches `GRAPHICS_VER`, so 7 includes + Ivy Bridge and Haswell, and 9 includes SKL/KBL/CFL/CML. It is not the + display IP version and does not take fractional generations. + +The controls are ORed; an empty/unset list matches nothing. Per-node device +specifications can restrict the first property to one observed PCI node. +Properties do not propagate from parent bridges. They are read before display +initialization, and Linux's `no_display` description clears MODESET/ATOMIC. +Patch 0006 is retained: it only programs headless display interrupt masking +when `HAS_DISPLAY` is false and preserves GT interrupts on that path. + +Use a disposable boot environment and preserve the known-good boot entry. +Update the alternate root's boot archive after editing configuration, then +boot it. Headless is not a framebuffer-preservation mode: it also disables +the i915n console. Have SSH/serial recovery access before testing it. + +## Audited boundaries + +* The display IRQ, HPD, power-well, DDI/FDI, AUX, GMBUS, DMC and DP MST + translation units are already built. Display devices take their Linux IRQ + reset/postinstall/handler paths; the native PCI layer supplies the interrupt. +* The OpRegion VBT reader is already present (patch 0013). ACPI notifications, + ASLE, platform audio integration and backlight class services are not. +* The DMC worker calls `request_firmware`; LinuxKPI loads through illumos + `firmware_open/read/close`, using `firmware/i915/` in the module + search path. Missing DMC retains the display INIT power reference and logs + that runtime power management is disabled. The package does not supply the + firmware blobs. This is functional display without DMC power saving, not + validation of Skylake power behavior. GuC/HuC are not needed for display on + these admitted devices. +* The runtime-PM shim keeps devices active. PCI attach/detach currently reject + DDI_RESUME/DDI_SUSPEND; this pilot makes no suspend/resume claim. +* DSI and LPE audio TUs remain excluded, but the admitted PCH GPU families do + not require them. HDCP is deliberately unavailable, including on families + that have hardware support. The TTM/local-memory/GSC stubs and stolen-memory + decoding are additional barriers to newer/discrete hardware. +* Normal console restoration uses atomic modesetting. The register-only panic + restoration is now explicitly Ivy-Bridge-only, avoiding writes through the + wrong plane register layout on newly enabled platforms. Other platforms may + keep a stale/blank panic screen; capture dumps remotely. Generalizing that + path needs a separate hardware-validated implementation. + +## Validation required before a release + +Build both display and headless variants natively; preserve the exact source +revision, Linux PIN, compiler/flags, licence audit and link results. A successful +link against mapfiles does not prove runtime symbol providers or hardware. + +For each platform, record PCI ID/stepping, BIOS, panel/output and dock, exact +package hashes and loaded modules. Exercise first attach, internal panel, +HDMI/DP, DP MST, modesets, vblank/page flips, rendering, VT transitions and +headless overrides (all-device, ID, generation, nonmatching controls). Check +IRQ storms and failure to quiesce. On Gen9 record DMC present/missing outcomes +and power-well warnings. Test kernel HPD separately from automatic Xorg RandR +notification, which this branch does not implement. diff --git a/drivers/i915/display-tus.txt b/drivers/i915/display-tus.txt index 95f7906..07c3bac 100644 --- a/drivers/i915/display-tus.txt +++ b/drivers/i915/display-tus.txt @@ -3,9 +3,9 @@ # with DISPLAY=1, the default): the 145 display objects of Linux's i915.mod # in the R5c configuration (artifacts/i915n-kms/display-tus.txt: display/*.o # and the display glue i915_bo .. i915_panic) minus the families whose -# external dependencies have no illumos counterpart and which Ivy Bridge -# (display version 7, the only display hardware supported on illumos, -# patch 0011) never reaches; illumos/i915_excluded.c (generated by +# external dependencies have no illumos counterpart and which the admitted Gen6-Gen9.5 PCH GPUs +# do not need for unprotected display (DSI/LPE are on excluded SoCs; +# HDCP is unavailable, including where the hardware supports it); illumos/i915_excluded.c (generated by # illumos/gen-nodisplay.py --excluded, which gives each stub's reason) # replaces them (design note notes/i915n-kms-design.md, section 3): # DSI (MIPI DSI bus): display/icl_dsi.c, display/vlv_dsi.c, diff --git a/drivers/i915/illumos/i915_illumos.c b/drivers/i915/illumos/i915_illumos.c index 19a45a0..399f278 100644 --- a/drivers/i915/illumos/i915_illumos.c +++ b/drivers/i915/illumos/i915_illumos.c @@ -5,7 +5,7 @@ * The illumos module of Linux's i915 driver: the kernel module "i915n" * (the name G7 reserved for this stack's Intel driver; its package ships * the driver without PCI aliases and `drmadm select new` moves the Intel - * GPU aliases to it), with display on Ivy Bridge (the display build: + * GPU aliases to it), with display on admitted GPUs (the display build: * compiled with -DI915N_DISPLAY, build.sh's default I915_DISPLAY=1) or * headless (build.sh I915_DISPLAY=0: no I915N_DISPLAY). * @@ -43,6 +43,46 @@ extern void lkpi_module_exit(void); static struct pci_driver *i915n_pci_driver; +/* + * Attach-time policy, before display register access. The driver's create + * path has populated i915's device/runtime info before display probe. + * DDI_PROP_DONTPASS permits per-node and driver.conf defaults but never + * inherits policy from a parent PCI bridge. Lists are owned by DDI. + */ +static bool +i915n_prop_contains(dev_info_t *dip, const char *name, int value) +{ + int *values; + uint_t count, n; + bool found = false; + + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + (char *)name, &values, &count) != DDI_PROP_SUCCESS) + return (false); + for (n = 0; n < count; n++) { + if (values[n] == value) { + found = true; + break; + } + } + ddi_prop_free(values); + return (found); +} + +bool +i915_illumos_force_no_display(struct pci_dev *pdev) +{ + dev_info_t *dip = lkpi_dev_dip(&pdev->dev); + struct drm_i915_private *i915 = to_i915(pci_get_drvdata(pdev)); + + return (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "i915n-force-no-display", 0) != 0 || + i915n_prop_contains(dip, "i915n-no-display-device-ids", + pdev->device) || + i915n_prop_contains(dip, "i915n-no-display-generations", + GRAPHICS_VER(i915))); +} + static int i915n_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { @@ -476,7 +516,7 @@ i915_illumos_de_master_enable(struct drm_i915_private *i915, u32 de_ier, * gen8_irq_reset(): the gen8+ display interrupt registers quiet, so that * nothing the firmware left armed reaches the master interrupt. Pipe A * is in the always-on power well; the other pipes' registers are only - * reachable with their power wells on (display code, not built), and the + * reachable with their power wells on (absent on this headless path), and the * firmware does not arm their interrupts. */ void diff --git a/drivers/i915/illumos/i915_illumos_con_kms.c b/drivers/i915/illumos/i915_illumos_con_kms.c index 3c74f7a..d2f05a8 100644 --- a/drivers/i915/illumos/i915_illumos_con_kms.c +++ b/drivers/i915/illumos/i915_illumos_con_kms.c @@ -428,6 +428,14 @@ i915n_con_kms_panic(uint32_t *surfp) if (!READ_ONCE(i915n_con.active)) return (0); display = to_i915(i915n_con.drm)->display; + /* + * This emergency path programs the Ivy Bridge plane directly, + * outside the normal atomic modeset. Never apply that register + * sequence to a newly enabled platform (SKL uses universal planes). + * Normal console/VT restoration uses the platform's atomic hooks. + */ + if (!display->platform.ivybridge) + return (0); p = to_intel_plane(i915n_con.crtc->primary)->i9xx_plane; cntr = intel_de_read_fw(display, DSPCNTR(display, p)); cntr &= ~(DISP_FORMAT_MASK | DISP_TILED); diff --git a/drivers/i915/patches/0006-illumos-display-interrupt-registers-headless.patch b/drivers/i915/patches/0006-illumos-display-interrupt-registers-headless.patch index 226f28c..6bc9a95 100644 --- a/drivers/i915/patches/0006-illumos-display-interrupt-registers-headless.patch +++ b/drivers/i915/patches/0006-illumos-display-interrupt-registers-headless.patch @@ -3,8 +3,8 @@ Subject: [PATCH 06] i915: display-engine interrupt registers without a display The display code owns the display engine's interrupt registers, and does nothing with them on a device without display (!HAS_DISPLAY): the -headless build (no display code at all) and, in the display build, every -platform but Ivy Bridge (patch 0011). On gen5 to gen7 (ILK..HSW) the +headless build (no display code at all) and, in the display build, devices +explicitly disabled by driver.conf (patch 0011). On gen5 to gen7 (ILK..HSW) the master enable of every interrupt, the GT's included, is DE_MASTER_IRQ_CONTROL in DEIER: without the display code nothing sets it, and the GPU would never interrupt (an Ivy Bridge, ThinkPad X230, diff --git a/drivers/i915/patches/0011-illumos-display-on-ivybridge-only.patch b/drivers/i915/patches/0011-illumos-display-on-ivybridge-only.patch deleted file mode 100644 index 9695a6f..0000000 --- a/drivers/i915/patches/0011-illumos-display-on-ivybridge-only.patch +++ /dev/null @@ -1,43 +0,0 @@ -From: illumos-drm -Subject: [PATCH 11] i915: display on Ivy Bridge only - -The illumos build carries i915's display code for Ivy Bridge (display -version 7), the only display hardware it has been tested on (ThinkPad -X230); the families it leaves out (DSI, HDCP, LPE audio) and the -stubs replacing them are checked against that platform's paths -(illumos/gen-nodisplay.py --excluded). On every other platform -intel_display_device_probe() takes Linux's no_display path, as for a -device ID without display, and says so at info level: the display -structure then has the empty device info, HAS_DISPLAY() is false, and -intel_display_device_info_runtime_init() clears DRIVER_MODESET and -DRIVER_ATOMIC from the device's features, so that the DRM core starts no -mode setting for it (drm_dev_illumos_lkms_register() does nothing) and -the interrupt setup takes the headless registers of patch 0006. - ---- a/drivers/gpu/drm/i915/display/intel_display_device.c -+++ b/drivers/gpu/drm/i915/display/intel_display_device.c -@@ -1707,8 +1707,24 @@ - drm_dbg_kms(display->drm, - "Unknown device ID %04x; disabling display.\n", - pdev->device); -+ goto no_display; -+ } -+ -+#ifdef __illumos__ -+ /* -+ * illumos drives the display of Ivy Bridge only (the hardware it -+ * was tested on); every other platform keeps the headless -+ * behaviour of Linux's no_display description, which also clears -+ * DRIVER_MODESET and DRIVER_ATOMIC in -+ * intel_display_device_info_runtime_init(). -+ */ -+ if (!desc->platforms.ivybridge) { -+ drm_info(display->drm, -+ "display on %s is not supported on illumos; running headless\n", -+ desc->name); - goto no_display; - } -+#endif - - info = desc->info; - if (!info) diff --git a/drivers/i915/patches/0011-illumos-display-policy.patch b/drivers/i915/patches/0011-illumos-display-policy.patch new file mode 100644 index 0000000..2f8b43a --- /dev/null +++ b/drivers/i915/patches/0011-illumos-display-policy.patch @@ -0,0 +1,35 @@ +From: illumos-drm +Subject: [PATCH 11] i915: allow display unless driver.conf requests headless + +Use the native display description on every GPU admitted by the port's +probe guard (0009). Keep Linux's no_display handling as an explicit, +per-device / per-generation recovery option. Patch 0006 continues to +supply headless GT interrupt plumbing only when HAS_DISPLAY is false; +it must not be removed while the headless option remains supported. + +--- a/drivers/gpu/drm/i915/display/intel_display_device.c ++++ b/drivers/gpu/drm/i915/display/intel_display_device.c +@@ -1707,8 +1707,21 @@ + drm_dbg_kms(display->drm, + "Unknown device ID %04x; disabling display.\n", + pdev->device); ++ goto no_display; ++ } ++ ++#ifdef __illumos__ ++ { ++ extern bool i915_illumos_force_no_display(struct pci_dev *); ++ ++ if (i915_illumos_force_no_display(pdev)) { ++ drm_info(display->drm, ++ "display on %s disabled by driver.conf; running headless\n", ++ desc->name); ++ goto no_display; ++ } ++ } ++#endif +- goto no_display; +- } + + info = desc->info; + if (!info) diff --git a/packaging/i915n.conf b/packaging/i915n.conf new file mode 100644 index 0000000..9c1d5b8 --- /dev/null +++ b/packaging/i915n.conf @@ -0,0 +1,20 @@ +# i915n attach-time display recovery policy (integer properties). +# Defaults: display enabled for every GPU admitted by the port. +# These settings do not bypass the unsupported-GPU probe guard. +# +# Force headless on all i915n devices: +# i915n-force-no-display=1; +# +# Force headless on particular PCI device IDs (not vendor IDs): +# i915n-no-display-device-ids=0x1916,0x1926; +# +# Force headless on graphics generations (GRAPHICS_VER, integers): +# 6 Sandy Bridge; 7 Ivy Bridge/Haswell; 8 Broadwell; 9 SKL/KBL/CFL/CML. +# i915n-no-display-generations=9; +# +# For one device instance, place i915n-force-no-display=1 in a device +# specification with its observed name, parent path and unit-address; +# see driver.conf(5). Do not guess the PCI path from another machine. +# Apply in a disposable BE, update its boot archive and reboot. +# Headless also disables the i915n console: retain SSH/serial access and +# the original BE. It is not a promise to preserve the boot framebuffer. diff --git a/packaging/i915n.p5m b/packaging/i915n.p5m index c233dfd..549016d 100644 --- a/packaging/i915n.p5m +++ b/packaging/i915n.p5m @@ -2,8 +2,7 @@ # SPDX-License-Identifier: MIT # Copyright 2026 The illumos-drm authors # -# IPS manifest of i915n, Linux v7.2's i915 (display on Ivy Bridge, every -# other platform headless; no local memory) built for the illumos-drm +# IPS manifest of i915n, Linux v7.2's i915 (display on admitted Gen6-Gen9.5 PCH GPUs; no local memory) built for the illumos-drm # facade, misc/linuxkpi and misc/drm_kms (drivers/i915). Publish from a # directory holding the module linked by # drivers/i915/build.sh as kernel/drv/amd64/i915n and its licence file @@ -29,11 +28,13 @@ # included (build.sh's linux-inputs.txt). No # firmware is delivered: gen6/gen7 submit through rings and gen8 to gen9.5 # through execlists, neither needs firmware; GuC/HuC firmware is optional -# there and absent firmware is not an error (i915 logs it and continues). +# there. Gen9 display DMC firmware is separate: the loader looks in +# firmware/i915 in the module path; without DMC, display power saving is +# disabled by the carried driver. See docs/i915n-display.md. # set name=pkg.fmri value=pkg://illumos-drm/driver/graphics/i915n@0.0.2,5.11-2026.0.0.0 set name=pkg.summary value="Intel GPU driver from Linux i915 (i915n)" -set name=pkg.description value="i915n, the i915 driver of Linux v7.2 built for the illumos-drm DRM core, its Linux compatibility layer and Linux's mode-setting code (misc/drm_kms): GEM, execbuffer, contexts and render nodes for Intel integrated GPUs of gen6 to gen9.5 with a PCH (gen7 and gen9.5 targeted), and mode setting (KMS) on Ivy Bridge; other GPUs run without display. Ships no PCI aliases; select it with drmadm select new." +set name=pkg.description value="i915n, the i915 driver of Linux v7.2 built for the illumos-drm DRM core, its Linux compatibility layer and Linux's mode-setting code (misc/drm_kms): GEM, execbuffer, contexts and render nodes for Intel integrated GPUs of gen6 to gen9.5 with a PCH (gen7 and gen9.5 targeted), and mode setting (KMS) on those admitted GPUs. Other generations are refused. Driver configuration can explicitly disable display. Display beyond Ivy Bridge is experimental. Ships no PCI aliases; select it with drmadm select new." set name=info.classification value=org.opensolaris.category.2008:Drivers/Display set name=info.source-url value=https://code.aopc.cloud/illumos/illumos-drm set name=variant.arch value=i386 @@ -42,6 +43,7 @@ dir path=kernel owner=root group=sys mode=0755 dir path=kernel/drv owner=root group=sys mode=0755 dir path=kernel/drv/amd64 owner=root group=sys mode=0755 file path=kernel/drv/amd64/i915n owner=root group=sys mode=0755 reboot-needed=true +file packaging/i915n.conf path=kernel/drv/i915n.conf owner=root group=sys mode=0644 preserve=true reboot-needed=true # The DRM minors are 0666 (drmadm perms keeps them so); the console minor # gfx0 is 0600 and logindevperm gives it to the console user. devfsadm # applies the last matching minor_perm entry, and pkg hands the perms to -- 2.56.0 From e8e2e89c4fef9bce94bd2d5b80fd4b82562782d3 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 21:18:22 +0200 Subject: [PATCH 02/13] drmadm: bind admitted Intel IDs independently of the legacy alias table --- docs/i915n-display.md | 19 ++++ drivers/i915/fetch-and-patch.sh | 7 ++ drivers/i915/gen-aliases.py | 32 ++++++ packaging/i915n.aliases | 182 ++++++++++++++++++++++++++++++ packaging/i915n.p5m | 6 + tools/drmadm/drmadm | 38 ++++++- tools/drmadm/drmadm.8 | 9 +- tools/test/test-drmadm-aliases.py | 141 +++++++++++++++++++++++ 8 files changed, 428 insertions(+), 6 deletions(-) create mode 100644 drivers/i915/gen-aliases.py create mode 100644 packaging/i915n.aliases create mode 100644 tools/test/test-drmadm-aliases.py diff --git a/docs/i915n-display.md b/docs/i915n-display.md index d9c7a83..5f2d4ac 100644 --- a/docs/i915n-display.md +++ b/docs/i915n-display.md @@ -69,3 +69,22 @@ headless overrides (all-device, ID, generation, nonmatching controls). Check IRQ storms and failure to quiesce. On Gen9 record DMC present/missing outcomes and power-well warnings. Test kernel HPD separately from automatic Xorg RandR notification, which this branch does not implement. + +## PCI binding before display probe + +The legacy graphics-dev manifest contains 110 aliases and no Skylake 1916. +Previously `drmadm select new` only moved that older set; its 0-unbound report +could therefore coexist with an unbound HD520. `packaging/i915n.aliases` now +contains the pinned Linux PCH Gen6-Gen9.5 PCI IDs, generated by +`drivers/i915/gen-aliases.py`. All 178 IDs are covered by LinuxKPI's existing +stolen-memory decoding tables. The data is inactive until `drmadm select new`; +there are still no IPS driver-action aliases on i915n. + +Upgrade i915n and drmadm together in the disposable test BE, then select new +again and update/boot that BE. Existing selections do not acquire new aliases +merely by installing the data. Selecting legacy returns only the legacy +manifest's aliases to i915, leaving new-only IDs unbound. Recorded new IDs are +also excluded from legacy fallback when the manifests/data are later absent. +Conflicting bindings to another driver remain untouched. The host regression +suite `tools/test/test-drmadm-aliases.py` exercises these cases with mocked +system commands; it does not establish native driver binding. diff --git a/drivers/i915/fetch-and-patch.sh b/drivers/i915/fetch-and-patch.sh index dd3f37f..8728ebe 100755 --- a/drivers/i915/fetch-and-patch.sh +++ b/drivers/i915/fetch-and-patch.sh @@ -146,3 +146,10 @@ grep -v '^#' "$HERE/mit-headers.txt" | grep . | while read -r f; do cp "$src" "$d" done echo "fetch-and-patch: $dest ready (Linux $LINUX_TAG $LINUX_REV, $(echo "$series" | grep -c . || true) patches)" + +# Keep opt-in binding data tied to the same PCI table and admission groups. +python3 "$HERE/gen-aliases.py" "$dest" > "$dest/.illumos/i915n.aliases" +if ! cmp -s "$dest/.illumos/i915n.aliases" "$REPO/packaging/i915n.aliases"; then + echo "fetch-and-patch: regenerate packaging/i915n.aliases for this PIN" >&2 + exit 1 +fi diff --git a/drivers/i915/gen-aliases.py b/drivers/i915/gen-aliases.py new file mode 100644 index 0000000..0922024 --- /dev/null +++ b/drivers/i915/gen-aliases.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +# Copyright 2026 The illumos-drm authors +"""Generate opt-in alias data from the pinned Linux PCI IDs, not gfx-drm. + +Usage: gen-aliases.py > packaging/i915n.aliases +The groups match patch 0009's PCH Gen6-Gen9.5 admission boundary. They must +be reviewed together when that boundary changes. Requires a C preprocessor. +""" +import os +from pathlib import Path +import re +import shlex +import subprocess +import sys + +header = Path(sys.argv[1]).resolve() / "include/drm/intel/pciids.h" +groups = ("SNB", "IVB", "HSW", "BDW", "SKL", "KBL", "CFL", "CML") +source = '#include "' + str(header) + '"\n#define ALIAS(id, ...) id\n' +source += "\n".join(f"INTEL_{group}_IDS(ALIAS)" for group in groups) +result = subprocess.run(shlex.split(os.environ.get("CC", "cc")) + + ["-E", "-P", "-x", "c", "-"], input=source, + text=True, capture_output=True, check=True) +ids = sorted(set(int(x, 16) for x in re.findall(r"0x[0-9a-fA-F]+", result.stdout))) +if not ids or 0x1916 not in ids: + sys.exit("Skylake 1916 missing from alias expansion") +print("# SPDX-License-Identifier: MIT") +print("# Generated by drivers/i915/gen-aliases.py from drivers/i915/PIN.") +print("# PCH Gen6-Gen9.5 only; never install as IPS driver-action aliases.") +print("# drmadm select new binds these; legacy removes new-only bindings.") +for device in ids: + print(f"pci8086,{device:x}") diff --git a/packaging/i915n.aliases b/packaging/i915n.aliases new file mode 100644 index 0000000..bf53f03 --- /dev/null +++ b/packaging/i915n.aliases @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: MIT +# Generated by drivers/i915/gen-aliases.py from drivers/i915/PIN. +# PCH Gen6-Gen9.5 only; never install as IPS driver-action aliases. +# drmadm select new binds these; legacy removes new-only bindings. +pci8086,102 +pci8086,106 +pci8086,10a +pci8086,112 +pci8086,116 +pci8086,122 +pci8086,126 +pci8086,152 +pci8086,156 +pci8086,15a +pci8086,162 +pci8086,166 +pci8086,16a +pci8086,402 +pci8086,406 +pci8086,40a +pci8086,40b +pci8086,40e +pci8086,412 +pci8086,416 +pci8086,41a +pci8086,41b +pci8086,41e +pci8086,422 +pci8086,426 +pci8086,42a +pci8086,42b +pci8086,42e +pci8086,a02 +pci8086,a06 +pci8086,a0a +pci8086,a0b +pci8086,a0e +pci8086,a12 +pci8086,a16 +pci8086,a1a +pci8086,a1b +pci8086,a1e +pci8086,a22 +pci8086,a26 +pci8086,a2a +pci8086,a2b +pci8086,a2e +pci8086,c02 +pci8086,c06 +pci8086,c0a +pci8086,c0b +pci8086,c0e +pci8086,c12 +pci8086,c16 +pci8086,c1a +pci8086,c1b +pci8086,c1e +pci8086,c22 +pci8086,c26 +pci8086,c2a +pci8086,c2b +pci8086,c2e +pci8086,d02 +pci8086,d06 +pci8086,d0a +pci8086,d0b +pci8086,d0e +pci8086,d12 +pci8086,d16 +pci8086,d1a +pci8086,d1b +pci8086,d1e +pci8086,d22 +pci8086,d26 +pci8086,d2a +pci8086,d2b +pci8086,d2e +pci8086,1602 +pci8086,1606 +pci8086,160a +pci8086,160b +pci8086,160d +pci8086,160e +pci8086,1612 +pci8086,1616 +pci8086,161a +pci8086,161b +pci8086,161d +pci8086,161e +pci8086,1622 +pci8086,1626 +pci8086,162a +pci8086,162b +pci8086,162d +pci8086,162e +pci8086,1632 +pci8086,1636 +pci8086,163a +pci8086,163b +pci8086,163d +pci8086,163e +pci8086,1902 +pci8086,1906 +pci8086,190a +pci8086,190b +pci8086,190e +pci8086,1912 +pci8086,1913 +pci8086,1915 +pci8086,1916 +pci8086,1917 +pci8086,191a +pci8086,191b +pci8086,191d +pci8086,191e +pci8086,1921 +pci8086,1923 +pci8086,1926 +pci8086,1927 +pci8086,192a +pci8086,192b +pci8086,192d +pci8086,1932 +pci8086,193a +pci8086,193b +pci8086,193d +pci8086,3e90 +pci8086,3e91 +pci8086,3e92 +pci8086,3e93 +pci8086,3e94 +pci8086,3e96 +pci8086,3e98 +pci8086,3e99 +pci8086,3e9a +pci8086,3e9b +pci8086,3e9c +pci8086,3ea5 +pci8086,3ea6 +pci8086,3ea7 +pci8086,3ea8 +pci8086,3ea9 +pci8086,5902 +pci8086,5906 +pci8086,5908 +pci8086,590a +pci8086,590b +pci8086,590e +pci8086,5912 +pci8086,5913 +pci8086,5915 +pci8086,5916 +pci8086,5917 +pci8086,591a +pci8086,591b +pci8086,591c +pci8086,591d +pci8086,591e +pci8086,5921 +pci8086,5923 +pci8086,5926 +pci8086,5927 +pci8086,593b +pci8086,87c0 +pci8086,87ca +pci8086,9b21 +pci8086,9b41 +pci8086,9ba2 +pci8086,9ba4 +pci8086,9ba5 +pci8086,9ba8 +pci8086,9baa +pci8086,9bac +pci8086,9bc2 +pci8086,9bc4 +pci8086,9bc5 +pci8086,9bc6 +pci8086,9bc8 +pci8086,9bca +pci8086,9bcc +pci8086,9be6 +pci8086,9bf6 diff --git a/packaging/i915n.p5m b/packaging/i915n.p5m index 549016d..4bd98e7 100644 --- a/packaging/i915n.p5m +++ b/packaging/i915n.p5m @@ -61,3 +61,9 @@ depend fmri=pkg:/driver/graphics/drm-core@0.0.2 type=incorporate depend fmri=pkg:/driver/graphics/linuxkpi@0.0.2 type=incorporate depend fmri=pkg:/driver/graphics/drm-kms@0.0.2 type=incorporate license LICENSE.i915n license=MIT-i915 + +# Inactive PCI data; installing this package never binds a GPU. +dir path=usr owner=root group=sys mode=0755 +dir path=usr/share owner=root group=sys mode=0755 +dir path=usr/share/drmadm owner=root group=bin mode=0755 +file packaging/i915n.aliases path=usr/share/drmadm/i915n.aliases owner=root group=bin mode=0444 diff --git a/tools/drmadm/drmadm b/tools/drmadm/drmadm index 719082d..15bf4b1 100755 --- a/tools/drmadm/drmadm +++ b/tools/drmadm/drmadm @@ -73,6 +73,8 @@ LOCK=etc/system.d/.drmadm.lock LEGACY_PKG=driver/graphics/drm LEGACY_DRIVER=i915 NEW_DRIVER=i915n +NEW_PKG=driver/graphics/i915n +NEW_ALIASES=usr/share/drmadm/i915n.aliases VIRTIO_DRIVER=virtio_gpu VIRTIO_PKG=driver/graphics/virtio-gpu VIRTIO_ALIASES=usr/share/drmadm/virtio-gpu.aliases @@ -193,6 +195,21 @@ manifest_aliases() { }' | sort -u } +# New Intel IDs are independent of gfx-drm's older PCI table. Missing data +# is allowed for older i915n packages. Record this set separately so a +# later legacy selection never hands new-only aliases to the old driver, +# even after i915n is uninstalled and its data file disappears. +intel_aliases() { + pkgcmd list -H "$NEW_PKG" >/dev/null 2>&1 || return 0 + f=$(under "$NEW_ALIASES") + [ -f "$f" ] || return 0 + awk ' + /^[ \t]*(#|$)/ { next } + NF != 1 || $1 !~ /^pci8086,[0-9a-f]+$/ { exit 1 } + { print } + ' "$f" || die "invalid alias data in $f" +} + # virtio_aliases: package data, never a binding in an IPS driver action. # Require the installed package as well as its file and driver registration. virtio_aliases() { @@ -840,7 +857,8 @@ do_status() { fi printf ' %-7s %s aliases%s\n' "$d" "$n" "$s" done - all=$(words "$man $(aliases_of "$LEGACY_DRIVER") $(aliases_of "$NEW_DRIVER") $(recorded alias)" | + idata=$(intel_aliases) || die "cannot read Intel alias data" + all=$(words "$man $idata $(aliases_of "$LEGACY_DRIVER") $(aliases_of "$NEW_DRIVER") $(recorded alias)" | tr ' ' '\n' | sort -u) unbound=0 other="" @@ -976,9 +994,16 @@ do_select() { man=$(manifest_aliases) bound_to=$(aliases_of "$to") bound_from=$(aliases_of "$from") - all=$(words "$man $bound_to $bound_from $(recorded alias)" | tr ' ' '\n' | sort -u) - if [ "$target" = legacy ] && [ -n "$man" ]; then - want=$man + idata=$(intel_aliases) || die "cannot read Intel alias data" + intel_all=$(words "$idata $(recorded i915n-alias)" | tr ' ' '\n' | sort -u) + all=$(words "$man $intel_all $bound_to $bound_from $(recorded alias)" | tr ' ' '\n' | sort -u) + if [ "$target" = legacy ]; then + if [ -n "$man" ]; then + want=$man + else + # Without a legacy manifest, never assume new IDs work there. + want=$(minus "$all" "$intel_all") + fi else want=$all fi @@ -1028,12 +1053,15 @@ do_select() { echo "* The $target DRM stack is selected: the modules of the other stack" echo "* are excluded from loading. To change it, run drmadm select." echo "* To undo by hand, remove this file, run bootadm update-archive and" - echo "* give the Intel GPU aliases below back to i915 (update_drv -a -i)." + echo "* restore only the aliases declared by the installed legacy i915 package." echo "* drmadm-selected: $target" echo "* drmadm-time: $(date +%s)" for a in $all; do echo "* drmadm-alias: $a" done + for a in $intel_all; do + echo "* drmadm-i915n-alias: $a" + done for a in $virtio_all; do echo "* drmadm-virtio-alias: $a" done diff --git a/tools/drmadm/drmadm.8 b/tools/drmadm/drmadm.8 index bbfa2eb..fb5e24c 100644 --- a/tools/drmadm/drmadm.8 +++ b/tools/drmadm/drmadm.8 @@ -126,7 +126,14 @@ or .Sy i915n , and those .Nm -recorded before. +recorded before. For new it also includes the admitted GPU IDs from +.Pa /usr/share/drmadm/i915n.aliases +when delivered by the installed i915n package. This covers devices such as +Skylake that the legacy driver does not list. Selection records that set +separately; selecting legacy removes new-only bindings instead of handing +them to the old driver. Older i915n packages without this file retain the +legacy-derived behavior. After upgrading, run select new again in the test +boot environment and reboot to add these bindings. When the driver of the selected stack is not installed, the aliases stay unbound, and an Intel GPU falls back to the generic VGA console driver, .Sy vgatext . diff --git a/tools/test/test-drmadm-aliases.py b/tools/test/test-drmadm-aliases.py new file mode 100644 index 0000000..13d1b97 --- /dev/null +++ b/tools/test/test-drmadm-aliases.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MPL-2.0 +"""Host regression tests for drmadm selection; never touches the running OS. + +Run the actual shell script in an alternate root with fake pkg/update_drv/ +bootadm commands. Only its fixed PATH is replaced in the temporary copy. +Real DDI attachment and boot-archive integration need a disposable illumos BE. +""" +import os +from pathlib import Path +import shlex +import subprocess +import sys +import tempfile + +repo = Path(__file__).resolve().parents[2] +source = (repo / "tools/drmadm/drmadm").read_text() +mock = r'''import os +from pathlib import Path +import shlex +import sys +name = Path(sys.argv[0]).name +a = sys.argv[1:] +root = Path(os.environ["TEST_ROOT"]) +if name == "pkg": + if a[:1] == ["-R"]: + assert Path(a[1]) == root + a = a[2:] + if a[0] == "contents": + p = root / "legacy-manifest" + if p.exists(): + print(p.read_text()) + sys.exit(0) + if a[-1] == "driver/graphics/virtio-gpu": + sys.exit(1) + sys.exit(0) +if name == "bootadm": + assert a == ["update-archive", "-R", str(root)] + sys.exit(0) +assert name == "update_drv" +assert a[:3] == ["-n", "-b", str(root)] +a = a[3:] +operation, flag, values, driver = a +assert flag == "-i" and operation in ("-a", "-d") +assert driver in ("i915", "i915n") +if operation == "-a" and driver == "i915n" and (root / "fail-add").exists(): + sys.exit(1) +p = root / "etc/driver_aliases" +rows = [shlex.split(x) for x in p.read_text().splitlines() if x] +if operation == "-d": + # Native update_drv removal takes one unquoted alias, not a list. + assert '"' not in values and " " not in values + rows = [r for r in rows if r != [driver, values]] +else: + for value in shlex.split(values): + assert not any(r[1] == value and r[0] != driver for r in rows) + if [driver, value] not in rows: + rows.append([driver, value]) +p.write_text("".join(f'{d} "{v}"\n' for d, v in rows)) +''' + +with tempfile.TemporaryDirectory(prefix="drmadm-alias-test-") as tmp: + base = Path(tmp) + bindir = base / "bin" + bindir.mkdir() + for name in ("pkg", "update_drv", "bootadm"): + f = bindir / name + f.write_text(f"#!{sys.executable}\n" + mock) + f.chmod(0o755) + script = base / "drmadm" + fixed = "PATH=/usr/bin:/usr/sbin:/sbin" + assert source.count(fixed) == 1 + script.write_text(source.replace(fixed, f"PATH={shlex.quote(str(bindir))}:/usr/bin:/bin:/usr/sbin:/sbin")) + + def fixture(name): + root = base / name + (root / "etc/system.d").mkdir(parents=True) + (root / "usr/share/drmadm").mkdir(parents=True) + (root / "etc/name_to_major").write_text("i915 200\ni915n 201\n") + (root / "etc/driver_aliases").write_text('i915 "pci8086,166"\n') + (root / "legacy-manifest").write_text("driver name=i915 alias=pci8086,166\n") + (root / "usr/share/drmadm/i915n.aliases").write_text("pci8086,166\npci8086,1916\npci8086,5916\n") + return root + + def run(root, selection, ok=True): + result = subprocess.run(["/bin/sh", str(script), "-R", str(root), "select", selection], + env=dict(os.environ, TEST_ROOT=str(root)), + text=True, capture_output=True) + assert (result.returncode == 0) == ok, result.stdout + result.stderr + assert not (root / "etc/system.d/.drmadm.lock").exists() + return result.stdout + + def bindings(root): + return {v: d for d, v in (shlex.split(x) for x in + (root / "etc/driver_aliases").read_text().splitlines())} + + root = fixture("round-trip") + run(root, "new") + assert bindings(root) == {"pci8086,166": "i915n", "pci8086,1916": "i915n", "pci8086,5916": "i915n"} + assert "Nothing changed" in run(root, "new") + run(root, "legacy") + assert bindings(root) == {"pci8086,166": "i915"} + print("PASS new-only Skylake/Kaby Lake binding, idempotence and legacy round-trip") + + root = fixture("conflict") + with (root / "etc/driver_aliases").open("a") as f: + f.write('other "pci8086,1916"\n') + assert "Left alone" in run(root, "new") + assert bindings(root)["pci8086,1916"] == "other" + print("PASS another driver's binding is preserved") + + root = fixture("rollback") + before = (root / "etc/driver_aliases").read_bytes() + (root / "fail-add").touch() + run(root, "new", ok=False) + assert (root / "etc/driver_aliases").read_bytes() == before + assert not (root / "etc/system.d/driver:graphics:drmadm").exists() + print("PASS failed add rolls back aliases and selection") + + root = fixture("removed-packages") + run(root, "new") + (root / "usr/share/drmadm/i915n.aliases").unlink() + (root / "legacy-manifest").unlink() + run(root, "legacy") + assert "pci8086,1916" not in bindings(root) + assert "pci8086,5916" not in bindings(root) + print("PASS recorded new IDs never leak to legacy without package data") + + root = fixture("older-package") + (root / "usr/share/drmadm/i915n.aliases").unlink() + run(root, "new") + assert bindings(root) == {"pci8086,166": "i915n"} + print("PASS older i915n packages without alias data remain usable") + + root = fixture("invalid-data") + before = (root / "etc/driver_aliases").read_bytes() + (root / "usr/share/drmadm/i915n.aliases").write_text("pci8086,1916\nbad data\n") + run(root, "new", ok=False) + assert (root / "etc/driver_aliases").read_bytes() == before + assert not (root / "etc/system.d/driver:graphics:drmadm").exists() + print("PASS invalid alias data fails before mutation") -- 2.56.0 From b97ba1c80aabec894aa33d240d5b8445de8e4366 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 21:22:34 +0200 Subject: [PATCH 03/13] i915n: include Whiskey Lake aliases and check the complete admission inventory --- docs/i915n-display.md | 4 ++-- drivers/i915/gen-aliases.py | 13 ++++++++++++- packaging/i915n.aliases | 5 +++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/i915n-display.md b/docs/i915n-display.md index 5f2d4ac..6419bcf 100644 --- a/docs/i915n-display.md +++ b/docs/i915n-display.md @@ -3,7 +3,7 @@ The default display build now uses the Linux display description for every GPU admitted by patch 0009: PCH-based integrated Gen6 through Gen9.5. This includes Sandy Bridge, Ivy Bridge, Haswell, Broadwell, Skylake and the -Kaby/Coffee/Comet Lake families represented by the pinned PCI tables. +Kaby/Coffee/Whiskey/Comet Lake families represented by the pinned PCI tables. Only Ivy Bridge has prior hardware acceptance. Other families are experimental. This change does not expand the admission guard to Gen10+, low-power SoCs, or discrete GPUs. Compiling their display translation units is not support. @@ -76,7 +76,7 @@ The legacy graphics-dev manifest contains 110 aliases and no Skylake 1916. Previously `drmadm select new` only moved that older set; its 0-unbound report could therefore coexist with an unbound HD520. `packaging/i915n.aliases` now contains the pinned Linux PCH Gen6-Gen9.5 PCI IDs, generated by -`drivers/i915/gen-aliases.py`. All 178 IDs are covered by LinuxKPI's existing +`drivers/i915/gen-aliases.py`. All 183 IDs are covered by LinuxKPI's existing stolen-memory decoding tables. The data is inactive until `drmadm select new`; there are still no IPS driver-action aliases on i915n. diff --git a/drivers/i915/gen-aliases.py b/drivers/i915/gen-aliases.py index 0922024..236eb0f 100644 --- a/drivers/i915/gen-aliases.py +++ b/drivers/i915/gen-aliases.py @@ -15,13 +15,24 @@ import subprocess import sys header = Path(sys.argv[1]).resolve() / "include/drm/intel/pciids.h" -groups = ("SNB", "IVB", "HSW", "BDW", "SKL", "KBL", "CFL", "CML") +groups = ("SNB", "IVB", "HSW", "BDW", "SKL", "KBL", "CFL", "WHL", "CML") source = '#include "' + str(header) + '"\n#define ALIAS(id, ...) id\n' source += "\n".join(f"INTEL_{group}_IDS(ALIAS)" for group in groups) result = subprocess.run(shlex.split(os.environ.get("CC", "cc")) + ["-E", "-P", "-x", "c", "-"], input=source, text=True, capture_output=True, check=True) ids = sorted(set(int(x, 16) for x in re.findall(r"0x[0-9a-fA-F]+", result.stdout))) +# Cross-check the native stolen-memory inventory too. Patch 0009 excludes +# Valleyview (the only SoC in those four tables); every other listed ID +# must receive binding data. This catches separate groups such as WHL. +repo = Path(__file__).resolve().parents[2] +pci = (repo / "linuxkpi/src/lkpi_pci.c").read_text() +known = set() +for body in re.findall(r"static const uint16_t lkpi_intel_gen[6789]\[\] = \{(.*?)\};", pci, re.S): + known.update(int(x, 16) for x in re.findall(r"0x[0-9a-fA-F]+", body)) +known.difference_update((0x0f30, 0x0f31, 0x0f32, 0x0f33)) +if set(ids) != known: + sys.exit("alias groups and admitted stolen-memory inventory disagree") if not ids or 0x1916 not in ids: sys.exit("Skylake 1916 missing from alias expansion") print("# SPDX-License-Identifier: MIT") diff --git a/packaging/i915n.aliases b/packaging/i915n.aliases index bf53f03..83c1e25 100644 --- a/packaging/i915n.aliases +++ b/packaging/i915n.aliases @@ -135,6 +135,11 @@ pci8086,3e99 pci8086,3e9a pci8086,3e9b pci8086,3e9c +pci8086,3ea0 +pci8086,3ea1 +pci8086,3ea2 +pci8086,3ea3 +pci8086,3ea4 pci8086,3ea5 pci8086,3ea6 pci8086,3ea7 -- 2.56.0 From 42413a83acb740868f8a8a5aa0a549b9d03d06a6 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:09:16 +0200 Subject: [PATCH 04/13] i915n: reserve firmware scanout memory before headless GEM init --- docs/i915n-display.md | 10 ++ drivers/i915/illumos/i915_illumos_con.c | 14 +++ drivers/i915/illumos/i915_illumos_con.h | 3 + drivers/i915/illumos/i915_illumos_con_kms.c | 92 ++++++++++++++++++- ...llumos-headless-firmware-framebuffer.patch | 49 ++++++++++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 drivers/i915/patches/0014-illumos-headless-firmware-framebuffer.patch diff --git a/docs/i915n-display.md b/docs/i915n-display.md index 6419bcf..0cb8e1a 100644 --- a/docs/i915n-display.md +++ b/docs/i915n-display.md @@ -88,3 +88,13 @@ also excluded from legacy fallback when the manifests/data are later absent. Conflicting bindings to another driver remain untouched. The host regression suite `tools/test/test-drmadm-aliases.py` exercises these cases with mocked system commands; it does not establish native driver binding. + +Headless firmware console preservation: before GEM initializes the GGTT, the +boot-console device validates the loader framebuffer's complete aperture range +and every backing PTE. For a page-aligned, contiguous framebuffer in initialized +stolen system memory, it reserves both ranges without changing their mappings. +The gfx_private console remains available, but no KMS outputs or modesetting +are enabled. Unsupported backing or reservation failure rejects attach before +GEM clears the boot framebuffer; other devices without the boot console need +no reservation. Reservations are removed on probe failure and driver release. +This does not implement suspend, quiesce, or restoration after a GT reset. diff --git a/drivers/i915/illumos/i915_illumos_con.c b/drivers/i915/illumos/i915_illumos_con.c index 3b47a07..88aaa4d 100644 --- a/drivers/i915/illumos/i915_illumos_con.c +++ b/drivers/i915/illumos/i915_illumos_con.c @@ -322,6 +322,20 @@ i915n_con_attach_undo(dev_info_t *dip) i915n_con_st.dip = NULL; } +boolean_t +i915n_con_boot_fb(dev_info_t *dip, struct i915n_con_fb *fb) +{ + if (i915n_con_st.dip != dip) + return (B_FALSE); + fb->paddr = fb_info.paddr; + fb->size = fb_info.fb_size; + fb->pitch = fb_info.pitch; + fb->width = fb_info.screen.x; + fb->height = fb_info.screen.y; + fb->bpp = fb_info.bpp; + return (B_TRUE); +} + int i915n_con_attach_late(dev_info_t *dip) { diff --git a/drivers/i915/illumos/i915_illumos_con.h b/drivers/i915/illumos/i915_illumos_con.h index 1139492..82243c3 100644 --- a/drivers/i915/illumos/i915_illumos_con.h +++ b/drivers/i915/illumos/i915_illumos_con.h @@ -25,6 +25,9 @@ struct i915n_con_fb { /* --- the illumos side (i915_illumos_con.c) --- */ +/* Loader framebuffer for the device selected as the boot console. */ +extern boolean_t i915n_con_boot_fb(dev_info_t *, struct i915n_con_fb *); + /* attach(9E), before the DRM device exists: the console minor first. */ extern int i915n_con_attach_early(dev_info_t *dip); /* attach(9E), after i915's probe: take the console over, or undo. */ diff --git a/drivers/i915/illumos/i915_illumos_con_kms.c b/drivers/i915/illumos/i915_illumos_con_kms.c index d2f05a8..92826de 100644 --- a/drivers/i915/illumos/i915_illumos_con_kms.c +++ b/drivers/i915/illumos/i915_illumos_con_kms.c @@ -64,6 +64,7 @@ #include "i915_drv.h" #include "i915_vma.h" +#include "intel_memory_region.h" #include "gt/intel_gt.h" #include "gt/intel_gtt.h" #include "display/intel_de.h" @@ -139,6 +140,95 @@ i915n_lastclose(struct drm_device *dev) i915n_con_kms_restore(); } +/* + * Headless does not reconstruct initial planes, but firmware scanout may + * still be active. Keep both its PTEs and stolen pages out of GEM's allocators. + * One boot console is selected by the illumos half. These are reservations, + * not a new binding: never rewrite firmware PTEs or touch display registers. + */ +static struct { + struct drm_i915_private *i915; + struct drm_mm_node ggtt, stolen; +} boot_fb; + +void +i915n_headless_fb_release(struct drm_i915_private *i915) +{ + if (boot_fb.i915 != i915) + return; + mutex_lock(&to_gt(i915)->ggtt->vm.mutex); + if (drm_mm_node_allocated(&boot_fb.ggtt)) + drm_mm_remove_node(&boot_fb.ggtt); + mutex_unlock(&to_gt(i915)->ggtt->vm.mutex); + mutex_lock(&i915->mm.stolen_lock); + if (drm_mm_node_allocated(&boot_fb.stolen)) + drm_mm_remove_node(&boot_fb.stolen); + mutex_unlock(&i915->mm.stolen_lock); + memset(&boot_fb, 0, sizeof (boot_fb)); +} + +int +i915n_headless_fb_reserve(struct drm_i915_private *i915) +{ + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; + struct intel_memory_region *mem = i915->mm.stolen_region; + struct i915n_con_fb fb; + dev_info_t *dip = lkpi_dev_dip(i915->drm.dev); + u64 start, size, phys, off; + bool present, local; + int ret; + + if (HAS_DISPLAY(i915->display) || !i915n_con_boot_fb(dip, &fb)) + return (0); + /* Refuse before GEM clears the mapping if it cannot be protected. */ + if (boot_fb.i915 || !mem || !drm_mm_initialized(&i915->mm.stolen) || + fb.paddr < ggtt->gmadr.start || !fb.size || !fb.pitch || + !fb.height || fb.size < (u64)fb.pitch * fb.height) + return (-EINVAL); + start = fb.paddr - ggtt->gmadr.start; + if (!IS_ALIGNED(start, PAGE_SIZE) || !IS_ALIGNED(fb.size, PAGE_SIZE) || + start >= resource_size(&ggtt->gmadr) || + fb.size > resource_size(&ggtt->gmadr) - start || + start >= ggtt->vm.total - PAGE_SIZE || + fb.size > ggtt->vm.total - PAGE_SIZE - start) + return (-EINVAL); + size = fb.size; + phys = intel_ggtt_read_entry(&ggtt->vm, start, &present, &local); + if (!present || local || phys < mem->region.start || + phys > mem->region.end || size - 1 > mem->region.end - phys) + return (-EINVAL); + /* Validate every page, not just the first: no invented contiguous backing. */ + for (off = 0; off < size; off += PAGE_SIZE) { + u64 addr = intel_ggtt_read_entry(&ggtt->vm, start + off, + &present, &local); + if (!present || local || addr != phys + off) + return (-EINVAL); + } + boot_fb.i915 = i915; + boot_fb.stolen.start = phys - mem->region.start; + boot_fb.stolen.size = size; + mutex_lock(&i915->mm.stolen_lock); + ret = drm_mm_reserve_node(&i915->mm.stolen, &boot_fb.stolen); + mutex_unlock(&i915->mm.stolen_lock); + if (ret) + goto fail; + boot_fb.ggtt.start = start; + boot_fb.ggtt.size = size; + boot_fb.ggtt.color = I915_COLOR_UNEVICTABLE; + mutex_lock(&ggtt->vm.mutex); + ret = drm_mm_reserve_node(&ggtt->vm.mm, &boot_fb.ggtt); + mutex_unlock(&ggtt->vm.mutex); + if (ret) + goto fail; + drm_info(&i915->drm, "headless console: reserved firmware FB " + "GGTT [0x%llx,0x%llx), stolen [0x%llx,0x%llx)\n", + start, start + size, phys, phys + size); + return (0); +fail: + i915n_headless_fb_release(i915); + return (ret); +} + /* Does the console framebuffer match what gfx_private draws into? */ static bool con_fb_matches(struct drm_device *drm, struct drm_framebuffer *fb, @@ -187,7 +277,7 @@ i915n_con_kms_takeover(dev_info_t *dip, const struct i915n_con_fb *want) return (-EINVAL); i915 = to_i915(drm); if (i915->display == NULL || !HAS_DISPLAY(i915->display)) - return (-ENODEV); + return (boot_fb.i915 == i915 ? 0 : -ENODEV); aperture = to_gt(i915)->ggtt->gmadr.start; drm_modeset_lock_all(drm); diff --git a/drivers/i915/patches/0014-illumos-headless-firmware-framebuffer.patch b/drivers/i915/patches/0014-illumos-headless-firmware-framebuffer.patch new file mode 100644 index 0000000..f931b23 --- /dev/null +++ b/drivers/i915/patches/0014-illumos-headless-firmware-framebuffer.patch @@ -0,0 +1,49 @@ +From: illumos-drm +Subject: [PATCH] Preserve firmware scanout mappings before headless GEM init + +--- a/drivers/gpu/drm/i915/i915_driver.c ++++ b/drivers/gpu/drm/i915/i915_driver.c +@@ -106,6 +106,10 @@ + #include "i915_hdcp_gsc.h" + #include "i915_hwmon.h" + #include "i915_initial_plane.h" ++#ifdef I915N_DISPLAY ++extern int i915n_headless_fb_reserve(struct drm_i915_private *); ++extern void i915n_headless_fb_release(struct drm_i915_private *); ++#endif + #include "i915_ioc32.h" + #include "i915_ioctl.h" + #include "i915_irq.h" +@@ -914,6 +918,12 @@ + if (ret) + goto out_cleanup_irq; + ++#ifdef I915N_DISPLAY ++ ret = i915n_headless_fb_reserve(i915); ++ if (ret) ++ goto out_cleanup_modeset2; ++#endif ++ + ret = i915_gem_init(i915); + if (ret) + goto out_cleanup_modeset2; +@@ -956,6 +966,9 @@ + out_cleanup_gvt: + intel_gvt_driver_remove(i915); + out_cleanup_hw: ++#ifdef I915N_DISPLAY ++ i915n_headless_fb_release(i915); ++#endif + i915_driver_hw_remove(i915); + intel_memory_regions_driver_release(i915); + i915_ggtt_driver_release(i915); +@@ -1018,6 +1031,9 @@ + + i915_gem_driver_release(dev_priv); + ++#ifdef I915N_DISPLAY ++ i915n_headless_fb_release(dev_priv); ++#endif + intel_memory_regions_driver_release(dev_priv); + i915_ggtt_driver_release(dev_priv); + i915_gem_drain_freed_objects(dev_priv); -- 2.56.0 From c387a2fb9d608de1ce81efe6131db4656678c2f0 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:14:07 +0200 Subject: [PATCH 05/13] docs: scope headless console preservation to current Ivy Bridge glue --- docs/i915n-display.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/i915n-display.md b/docs/i915n-display.md index 0cb8e1a..92456b2 100644 --- a/docs/i915n-display.md +++ b/docs/i915n-display.md @@ -89,8 +89,8 @@ Conflicting bindings to another driver remain untouched. The host regression suite `tools/test/test-drmadm-aliases.py` exercises these cases with mocked system commands; it does not establish native driver binding. -Headless firmware console preservation: before GEM initializes the GGTT, the -boot-console device validates the loader framebuffer's complete aperture range +Headless firmware console preservation (currently the Ivy Bridge console path): +before GEM initializes the GGTT, the boot-console device validates the loader framebuffer's complete aperture range and every backing PTE. For a page-aligned, contiguous framebuffer in initialized stolen system memory, it reserves both ranges without changing their mappings. The gfx_private console remains available, but no KMS outputs or modesetting -- 2.56.0 From c66730741e930d85eb41513f5255f9c76af40d04 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:44:58 +0200 Subject: [PATCH 06/13] i915n: match boot console aperture and ship Gen9 DMC firmware --- docs/i915n-display.md | 17 +++++--- drivers/i915/illumos/i915_illumos_con.c | 38 ++++++++++------- ...-illumos-keep-firmware-plane-address.patch | 16 +++++++ firmware/LICENSE.i915 | 39 ++++++++++++++++++ firmware/README.md | 20 +++++++++ firmware/i915/kbl_dmc_ver1_04.bin | Bin 0 -> 8840 bytes firmware/i915/skl_dmc_ver1_27.bin | Bin 0 -> 8928 bytes packaging/i915n.p5m | 17 +++++--- 8 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 drivers/i915/patches/0015-illumos-keep-firmware-plane-address.patch create mode 100644 firmware/LICENSE.i915 create mode 100644 firmware/README.md create mode 100644 firmware/i915/kbl_dmc_ver1_04.bin create mode 100644 firmware/i915/skl_dmc_ver1_27.bin diff --git a/docs/i915n-display.md b/docs/i915n-display.md index 92456b2..ed54ef7 100644 --- a/docs/i915n-display.md +++ b/docs/i915n-display.md @@ -27,8 +27,8 @@ when `HAS_DISPLAY` is false and preserves GT interrupts on that path. Use a disposable boot environment and preserve the known-good boot entry. Update the alternate root's boot archive after editing configuration, then -boot it. Headless is not a framebuffer-preservation mode: it also disables -the i915n console. Have SSH/serial recovery access before testing it. +boot it. Headless keeps a validated firmware framebuffer reservation and the +gfx_private console; it provides no KMS outputs. Have SSH recovery access. ## Audited boundaries @@ -40,9 +40,9 @@ the i915n console. Have SSH/serial recovery access before testing it. * The DMC worker calls `request_firmware`; LinuxKPI loads through illumos `firmware_open/read/close`, using `firmware/i915/` in the module search path. Missing DMC retains the display INIT power reference and logs - that runtime power management is disabled. The package does not supply the - firmware blobs. This is functional display without DMC power saving, not - validation of Skylake power behavior. GuC/HuC are not needed for display on + that runtime power management is disabled. The package supplies unmodified + SKL 1.27 and KBL 1.04 DMC blobs with their Intel license (see firmware/README.md). + This is not validation of Skylake power behavior. GuC/HuC are not needed for display on these admitted devices. * The runtime-PM shim keeps devices active. PCI attach/detach currently reject DDI_RESUME/DDI_SUSPEND; this pilot makes no suspend/resume claim. @@ -89,7 +89,12 @@ Conflicting bindings to another driver remain untouched. The host regression suite `tools/test/test-drmadm-aliases.py` exercises these cases with mocked system commands; it does not establish native driver binding. -Headless firmware console preservation (currently the Ivy Bridge console path): +The console selects the Intel device whose BAR2 aperture contains the complete +loader RGB framebuffer, rather than using a generation whitelist. Display +takeover preserves its original GGTT address for gfx_private. This still +requires a supported loader framebuffer format; VGA text is not converted. + +Headless firmware console preservation: before GEM initializes the GGTT, the boot-console device validates the loader framebuffer's complete aperture range and every backing PTE. For a page-aligned, contiguous framebuffer in initialized stolen system memory, it reserves both ranges without changing their mappings. diff --git a/drivers/i915/illumos/i915_illumos_con.c b/drivers/i915/illumos/i915_illumos_con.c index 88aaa4d..1bd5c18 100644 --- a/drivers/i915/illumos/i915_illumos_con.c +++ b/drivers/i915/illumos/i915_illumos_con.c @@ -46,7 +46,7 @@ * - a console device refuses detach (consconfig holds its node for the * system's life anyway). * - * One console per system: the first Ivy Bridge device that takes it. + * One console per system: the device whose aperture contains the loader framebuffer. */ #include @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -119,11 +120,6 @@ struct { uint32_t surf; } i915n_con_panic_log; -/* Ivy Bridge GPUs (Linux's INTEL_IVB_*_IDS): display on illumos. */ -static const uint16_t i915n_con_ids[] = { - 0x0152, 0x0156, 0x015a, 0x0162, 0x0166, 0x016a -}; - static boolean_t con_minor(dev_t dev) { @@ -246,8 +242,10 @@ i915n_con_panic(void *arg, int code) static boolean_t con_wanted(dev_info_t *dip) { - int vendor, device, inst; - uint_t i; + int vendor, inst, *data; + uint_t i, count; + boolean_t match = B_FALSE; + pci_regspec_t *regs; if (i915n_con_st.dip != NULL) return (B_FALSE); @@ -256,16 +254,26 @@ con_wanted(dev_info_t *dip) return (B_FALSE); vendor = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "vendor-id", -1); - device = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, - "device-id", -1); if (vendor != 0x8086) return (B_FALSE); - for (i = 0; i < sizeof (i915n_con_ids) / sizeof (i915n_con_ids[0]); - i++) { - if (device == i915n_con_ids[i]) - break; + /* Gen6-Gen9.5 use BAR2 for the mappable GGTT aperture. Never + * claim another GPU's boot framebuffer merely because it is Intel. */ + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "assigned-addresses", &data, &count) != DDI_PROP_SUCCESS) + return (B_FALSE); + regs = (pci_regspec_t *)(void *)data; + for (i = 0; count % 5 == 0 && i < count / 5; i++) { + uint64_t base, size; + if (PCI_REG_REG_G(regs[i].pci_phys_hi) != PCI_CONF_BASE2) + continue; + base = ((uint64_t)regs[i].pci_phys_mid << 32) | regs[i].pci_phys_low; + size = ((uint64_t)regs[i].pci_size_hi << 32) | regs[i].pci_size_low; + if (fb_info.paddr >= base && fb_info.paddr - base < size && + fb_info.fb_size && fb_info.fb_size <= size - (fb_info.paddr - base)) + match = B_TRUE; } - if (i == sizeof (i915n_con_ids) / sizeof (i915n_con_ids[0])) + ddi_prop_free(data); + if (!match) return (B_FALSE); /* A 32-bit RGB framebuffer from the loader (not VGA text). */ if (fb_info.fb_type != I915N_FB_TYPE_RGB || fb_info.paddr == 0 || diff --git a/drivers/i915/patches/0015-illumos-keep-firmware-plane-address.patch b/drivers/i915/patches/0015-illumos-keep-firmware-plane-address.patch new file mode 100644 index 0000000..90ec51a --- /dev/null +++ b/drivers/i915/patches/0015-illumos-keep-firmware-plane-address.patch @@ -0,0 +1,16 @@ +From: illumos-drm +Subject: [PATCH] Keep the boot framebuffer at its firmware GGTT address + +--- a/drivers/gpu/drm/i915/i915_initial_plane.c ++++ b/drivers/gpu/drm/i915/i915_initial_plane.c +@@ -153,7 +153,10 @@ + * which can cause problems for ggtt_reserve_guc_top(). + * Try to pin it to a low ggtt address instead to avoid that. + */ ++#ifndef __illumos__ + base = 0; ++#endif ++ /* illumos gfx_private keeps using the loader's aperture address. */ + + if (base != plane_config->base) { + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; diff --git a/firmware/LICENSE.i915 b/firmware/LICENSE.i915 new file mode 100644 index 0000000..3a05d15 --- /dev/null +++ b/firmware/LICENSE.i915 @@ -0,0 +1,39 @@ +Copyright (c) 2015, Intel Corporation. +All rights reserved. + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +* Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. +* Neither the name of Intel Corporation nor the names of its suppliers + may be used to endorse or promote products derived from this software + without specific prior written permission. +* No reverse engineering, decompilation, or disassembly of this software + is permitted. + +Limited patent license. Intel Corporation grants a world-wide, +royalty-free, non-exclusive license under patents it now or hereafter +owns or controls to make, have made, use, import, offer to sell and +sell ("Utilize") this software, but solely to the extent that any +such patent is necessary to Utilize the software alone, or in +combination with an operating system licensed under an approved Open +Source license as listed by the Open Source Initiative at +http://opensource.org/licenses. The patent license shall not apply to +any other combinations which include this software. No hardware per +se is licensed hereunder. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/firmware/README.md b/firmware/README.md new file mode 100644 index 0000000..4ca541f --- /dev/null +++ b/firmware/README.md @@ -0,0 +1,20 @@ +# Intel DMC firmware + +Unmodified binaries from linux-firmware commit +6569484e6b57bc1c688b383ba6719bf2e25574c4: +https://kernel.googlesource.com/pub/scm/linux/kernel/git/firmware/linux-firmware/+/6569484e6b57bc1c688b383ba6719bf2e25574c4/ + +The upstream WHENCE identifies these i915 DMC files under LICENSE.i915. +The complete license is included unchanged and shipped in the IPS license action. +These are binary firmware, not MIT source; do not include them in source licence +whitelists or modify them. No GuC/HuC blobs are supplied. + +SHA256: +- i915/skl_dmc_ver1_27.bin: d3b6dc1a39bb2aeb37a1179f2b4e8145c24986da78c533571a5712dc91ec3f61 +- i915/kbl_dmc_ver1_04.bin: 2cde41c3e5ad181423bcc3e98ff9c49f743c88f18646af4d0b3c3a9664b831a1 +- LICENSE.i915: 8542aeabf2761935122d693561e16766ce1bcc2b0d003204f9040b7d6d929f2e + +SKL requests the first file; KBL/CFL/WHL/CML request the second. The driver +retains its INIT power reference if firmware cannot be loaded. Kernel runtime +PM remains constrained by the illumos shim; firmware presence is not evidence +of suspend or runtime power-saving support. diff --git a/firmware/i915/kbl_dmc_ver1_04.bin b/firmware/i915/kbl_dmc_ver1_04.bin new file mode 100644 index 0000000000000000000000000000000000000000..487b7e5684c08006cd40cb56ee3bc8df0d5c7978 GIT binary patch literal 8840 zcmds+F-#oU8OPr&b8qM)FlKspKv7Zh8#aDjA0qHuwfP0mrcgfsd7-h6MF*cTj~ z5=9D<*`L4tzwfB=|9&Ni3;E7Uke+sHEpFaC zOaK4&-R;|H+K4Wc+y&>})ttw=z#jd(*QvNz|Hkz1%YL?RIXCAvoZINx{<80X{NBW^ zAO7r?dlkPLYkx8J{!f4OgZJClzcJW;xBu$UjsAhqCQaQ1`uy+zME*{vj_b*JqQK(? z9?LmC$Dlsa=qgcZ?_zvO?MfT&1<@#(iQUU@X^vdP@Efqgr>r)+*E-i|^TN%jZ{KHo zKcCMiR^Aui%-b{Hu(i**+g{7^1NCLvlarGNaL{^|)|IjneW@ot{k3+a(>27E4`Ua2 zpuocg9xw3RgE--S5S2 z>sCeJqKbG!%#9utKN0(6x(|}<3*&w9zL-VO5w}f0rnR{%zM{DMdD-1J&cq|e+v2_NM>-2{Ci-w8qE9jG-nTwfKy9#5nt{uGe@$d_&A!HN*pAKYnq|G;ML! zIQzKlD#i=qSnS)1k4CJmxFcpfZSkR)`|CPo_sldm#Jk3$;vM5uye0P6^5?SK6zhLc z@>$tE4DpwG=8S9NWiexRe<{0Wh>IJ>iTJ+psCdzs=lq_Swv*y{i)Z4wpwV+OYg`j& z##7?EVy->=TiMku9&5Esi^(}Eo;02kPnf159yeYRkBR+y7AIoXTN>%NY39Vo-_3L0 z7rzvvUlQ+Id{zA1cum|EGqx@9j<{dk7SD@){*uNiW}fH7Q>I@OPl$bf@vxX{SrsS7 zYvKXZZ;7j7pI^Lj)AIM6cMrurzqlo4Jd5I{*yk57h<$$Xobj4CGyRr$M(p#8kH2I2 ze8s4S@Hq?ZfQEC0;cMWHgj$NY!w_&bPzkHl`@JzL>Ym7?cLz&(+y4G6k7S%s{ z=4hh*-^P`RXD!|k&xl!9>*6VkZ;QuG@28)Er0L8+sc~&z?|Sid`t4}U-_G|6_b07)@jJw8=UgUc%nk8f<5X|bJ7V5bQ{vR( zP4R@7ep})(<8|?fX{b$xjj2&?7*nexVms%hT#r2~pTLZPXJ+~I*+^l9`7&oc8`;p( zP%=4`rrFJ#D7|v~Z4WaN?|B2{(qcB$3#_Hod$Jxcbi^Mcv&iAR< z($M-)7!PCAcJgW1PvEeh!0ac|Mm7CDJqt&^fS3$tPpz**_07EMUFO;Ho)9O2|CEkg zYnN)h2f7zwE=H`LVvIhg#u#6^tluS)Cw9H4WD70ygN_Vi#k->hc-@i?O9FydzHA>YR_`-W-ocr?wgj(FySssyV-lt z)jl_EA{C6b%hDe7)-L;?@;)RkB{lC+9y?0rC%Wt^6 zdBvWY&q90F-=TR2>aPm3Cip~?>;HNZ^xT^Rn-|tPtlWOEp0(bz+7cg>cF(j3;Gp&V zn)Z=t*=yjS_57N)ZQ4{SIA}e;rhRH!UK()FdNys?i_&~A^7};bIpaRrE|4yo6H^!H zCz=&g7Z9f&pe|7RXi7|7K%9Djx&Xbe2d?K#JwRPRd_;UF)g2O34^S5n_w~RBIa3c% z7Z9f&pe}%^2dE3+ikP|pj>XgkF!g}13&hj|)CI(k#MA{a^}wX9Tk3&n<3sVRG4;T_ zG4(*xcwfA1Og(U5{9Jr!Og%tdz%}l|de)h%Z83EL@l7#x0pp<_pe}%^2dE3+H8FJo zZC7FKy~Men;hCZy$j?wNS`kwhNEJ~JP#4g4QA}MxTVD^%=1e_6U4SN|tv_42Xj;tK zig8i{Pz%sD6;lhq)Bw~1*TvKT)B<=O)Bw~1)Bzc+^FMlO0BQl48h~0r|BF&FwE%Hy z0BQmHr3RoDfT;nf1<-Kr=ll;RV*6V~ginfJiXNX7zm)63C&e#Cj~|LJ3gd^m7Vm7$ zrx;%pJ$@*@D2yMPiqYeTHpKd0gdd78N*q5FUlhg<#TTV5eki^uj30_GiUvOvUlhg< z#TRAF_@Vfs%po3VO&%s1JW#w)7!R~=j0d_PrY#<5%NP$7FO=sI542-?JW#w)yi7b$ zyigbq6fYFU1H}uaEgmRdC_X42C|)QUJW#w)#)Ain7s?p$K=DF}d2Z|R;TRhN~Y4AYtLeb-a;)TL^pm?FQ#RF}KX^RKiGR>hFFO+-wOpF(bW><_C z3h#*VLSZ~myioGvfjW7i`W-zK84-_vH z<{i%ah_>@$yinqJpm?D$?{5Cq0cT>oP#6ysFO+M?1H}s^J}u^{Bj=8<__pHsobZ+ypA+7MZ4GaT@i~dF!@5Rz4c2(zCt`d~ zn7^G=*%Q^3j!O?<;{G!{`_fkm8W;_q__xA0{6zA)Vtdm`?Ejw0xv8c}3-2M$cAL6Z z;$(ywla7BUFrD~M>4YWx0{Yc|x_=)f$4Vgw9CE-k^q>A6mtU{_ZI$o-M?bl5;%uz{ z;>@a|K|gQNXx}|kqnXo!iSi=hj9l9PyiT8I=99?l^vmnGcZ_o>$?s9_GdWh8*ZCE9 zM!&j4Ugz)B89LQMUPn#DoX{@|A@rNqT#hq@*KsC(%iP6i{Fz+mVb0a8F+1=3@BI0? J@EvFQe*nM^CAI(n literal 0 HcmV?d00001 diff --git a/firmware/i915/skl_dmc_ver1_27.bin b/firmware/i915/skl_dmc_ver1_27.bin new file mode 100644 index 0000000000000000000000000000000000000000..b41d062060ddb522b54102bb0ff49214d10f7e33 GIT binary patch literal 8928 zcmdU#KTI6y8OFa^k6~@CedF_5hjiGCVv;xxiB}015e1zlcO=;YB`Dz}3KtuRNE8$l zE+|gA&1nk?iY;8Ya6#cnD59XCpg=kb3KtYxxS&9~jYL6#beqJHe2MRz=l9L~Epuld zr(Dtm$vZ#ZeZKemGc(`3^WY24xm5q0i~J`agBSlYxLxl5`i`U#r56f1A<+j)DgJJ{Oi+;6W7`5pCTTFvJJIA}df z>q?CheW@poey&fY(;VXRyRi#ARNz|$o+$9b)Tw@@z{dqH7r0*FiTXglueqh1!&n&u zYZ=CxuaD;UY{0qio~|WqZJM6drcv6_yrlhfz}+vbI~=s0rQInFX-Ok}E|$;hoUj~`*y1uiO!x5S+2Vez`yC)0U&WLy!i887`db`OnR zJ9ZDmnsAhen_}|R#P^Id@q+1_;(4)FENY8qEdE$LZR{S!t}Z4|BAzr(#WmA3#bd_9 zZou6VGnW~0YH^nhxMA@idBo3un9nN{KQo>YKQ&$w_r&CBi;s=F;;xvBpeOE_eq7h) zzWA!*?$-nEedC6B%y?IP_=CJ{`WxxRuSs7MuZn5g6iiDR*ED}FX+ZN)t?PCjZ^Xa#x?P> zG57gBF>R;BixzK)7lKCj$-HqQZW!0acg4)T@y`J_WARv5%e0uBHSv^jT|8-;rg*}5 zMLaI{*RwbkbG>DeK25VAe*XPD=lkNPV)QHGuEp2H2gV!Xj+n9Si1)pnSMuH5&Qh&t=pFW+@QNJ_W8waG2>Ylx5Pfb zcuDN@ix-SH#0}H$h-bw(x*6<3Uh#bx7Bacn#$cE;o4=eP1}byEDy zcxp=PVmvMG8PAJPj2Fen#x3!Y@v69Md?Y?JJ`o=nyI;iaiP*2F&dt7s@>i zPR_fAujll<6W=w?^dy}V^PH-SGmE#xlVbX9i^q*O#bc)7-Q<=r?}d7GFV~0%vtxw_DFXm++*C|fw{i}bAJcs{#K4sr_#6->bCqHLg8=z z6Vdl}VXUx?)yKJy8r-@IP51d`-!wsqR-P>IPxzL zQ=#_M_0?0~tgFtiXUqG7I0^hwI&y7XK1hyqF2Y)j+4~e@^f@)g`0|{8pPIY8!7)m% zH?DI{2c~^k-%zjXOW!^}znSA2%{3prfN85YiTs^S+_=8p&k6r8axw?rCFo0g@C){} zz_XrJC$JAS^7jqh!zpWzi021u5A*y8Yfq%Of1`P1=Ux3Ju$uUWp0$4Ew3ZTlq&!2q z=2?e7t20>lRLrs1Z?&L-Y3pN!@gxNejD}+vC(p~%YeXHY?E@R9y7tUC`>bc_K@wjf z585Nsk{Kr4XKCXrwZ8Vyw5e1u+CEELyK=X$Juq!kDj02_rM-J)rLXOnmdzDL+h=K4 zu55WN&vfoT+e?}-VxI5p12E5b_5qmZd)NoIt_;0Sj5!}$e_S^(VSVJR^H@vgQXiDI zYgz{YK96E^X){%<6xOe$HFatm&H+p!j-mFUs4MnOd{y6bKj^Cr zw!VFwJ+qmvrv!Ny**n!XVbkU;?aI~18mEhRC)p@y;h^;_?c=NEs$%Chsz?5~{>Jez zzaI}L*Ez;lSD14&ua<=|>o?uUdhd;={G}1`-d-`LX3F;%)x~1=80PkgHgD)O#~F2h)EXVm6;rxjOgKy!-9- z-p1TngMNF9h4ypR^=i1Lf+wfip}0ogo7uE^>8mcRy#5?}*7~ofd#Pfj_3B1HU-0!_ z%BL}Go#yMomapkq`S#LoKcBwz>&yK{ovxqA*Xy=tvNy(l&&>Bf&)QFps_Sp)R*I;t zsj;Pt7R1!pQcnwKX+1{Zd;~W5d+e)YxdKr>U#qRBV3(i0~irA<^SM;zKeo{6~C9 z^!SYUjW9l=Yx4}%+KTZT(c?4XH^TUgnHW7jV^gf(B78>tM&kI4_>C|=BYq=o@fqC|=BYq=e#%IKDWDW5a6M2JZ@D}kHVZ6l|W4y&BF>Ub{+s1f{c#Pbe zc#A#L<1OMbvdQBu;xWQ_i+GGM-Xb0&ZSfZI81WbJ7V#L-;4R`YG9J7|JVwTVw}{6` z9B&bi5yo4@V}$V*@fcYXyhS`l+Tty?O@p_H$A}(p5swkZTf}3eE#6{NOk2FgwrNhp zc#NFWCt^HCH2Y#aMtDz*#|Yys;xUpRZ_&wP)Ms>GjK@gadyDcF;Z?=)7-`!=FaMBu zlejV7A|4}gyhS`lnCCgqCfY8F@feBYE#fi4JjeMP0^AVeF~WF@c#O;)ZxN4?__Ub2 zj+|3sJVqFAvCfqw##_W=gvZ4zVqJYvDsGFp-kjKbjAFb+JVx^1E#fhv$6Lf>B#yU; z#|Yys;xRHdyhS`l{5iZuJVx^4EoSn;7z5rS9wU0ZMLb6G<1Ma;$%(g!#|Yys;xVGf zTf}1|Ki*RY z|DibZZ)DNeNPJgu{6lz0jDHAk!*&gCiSZAK zZ^D`*ya8)G@VXfP5aw?Z74}4{mtf-l818-P%LNULh9kbH@Fky)ysp^ZbQ1f&6H-GZ zTC`%Fx1+fdCnL<5R0_U;>BJxXGb}3c9!|d@AKz00J}l&bLk<{?Kl zeBUPbV`4j3Us1p753B0qYczW9oT?f3MT=%zP4goj$ydwP2hpDQgk@ zEB@a>uG8_%d^$@$A6M#G(= R@!PwP|IWW_6u#rV=HErQU&;Ug literal 0 HcmV?d00001 diff --git a/packaging/i915n.p5m b/packaging/i915n.p5m index 4bd98e7..ffe70f6 100644 --- a/packaging/i915n.p5m +++ b/packaging/i915n.p5m @@ -11,7 +11,7 @@ # drivers/i915/build.sh all # $OUT/i915n # cp $OUT/i915n /kernel/drv/amd64/i915n # drivers/i915/gen-license.sh $LINUX /LICENSE.i915n $OUT/linux-inputs.txt -# pkgsend publish -s -d packaging/i915n.p5m +# pkgsend publish -s -d -d . packaging/i915n.p5m # # The driver action has no PCI aliases: the Intel GPU aliases belong to # gfx-drm's i915 (driver/graphics/drm) until `drmadm select new` moves them @@ -25,16 +25,16 @@ # -N misc/drm_helpers -N misc/drm_kms, so the package requires their # packages (drm-kms: Linux's mode-setting code, which the display uses). # The licence file covers every Linux file compiled in, the display's -# included (build.sh's linux-inputs.txt). No -# firmware is delivered: gen6/gen7 submit through rings and gen8 to gen9.5 +# included (build.sh's linux-inputs.txt). Intel DMC blobs carry their own +# licence. No GuC/HuC firmware is delivered: gen6/gen7 submit through rings and gen8 to gen9.5 # through execlists, neither needs firmware; GuC/HuC firmware is optional # there. Gen9 display DMC firmware is separate: the loader looks in # firmware/i915 in the module path; without DMC, display power saving is -# disabled by the carried driver. See docs/i915n-display.md. +# disabled by the carried driver. See firmware/README.md and docs/i915n-display.md. # set name=pkg.fmri value=pkg://illumos-drm/driver/graphics/i915n@0.0.2,5.11-2026.0.0.0 set name=pkg.summary value="Intel GPU driver from Linux i915 (i915n)" -set name=pkg.description value="i915n, the i915 driver of Linux v7.2 built for the illumos-drm DRM core, its Linux compatibility layer and Linux's mode-setting code (misc/drm_kms): GEM, execbuffer, contexts and render nodes for Intel integrated GPUs of gen6 to gen9.5 with a PCH (gen7 and gen9.5 targeted), and mode setting (KMS) on those admitted GPUs. Other generations are refused. Driver configuration can explicitly disable display. Display beyond Ivy Bridge is experimental. Ships no PCI aliases; select it with drmadm select new." +set name=pkg.description value="i915n, the i915 driver of Linux v7.2 built for the illumos-drm DRM core, its Linux compatibility layer and Linux's mode-setting code (misc/drm_kms): GEM, execbuffer, contexts and render nodes for Intel integrated GPUs of gen6 to gen9.5 with a PCH (gen7 and gen9.5 targeted), and mode setting (KMS) on those admitted GPUs. Other generations are refused. Driver configuration can explicitly disable display. Display beyond Ivy Bridge is experimental. Includes Intel DMC firmware for SKL/KBL-family display power control. Ships no PCI aliases; select it with drmadm select new." set name=info.classification value=org.opensolaris.category.2008:Drivers/Display set name=info.source-url value=https://code.aopc.cloud/illumos/illumos-drm set name=variant.arch value=i386 @@ -67,3 +67,10 @@ dir path=usr owner=root group=sys mode=0755 dir path=usr/share owner=root group=sys mode=0755 dir path=usr/share/drmadm owner=root group=bin mode=0755 file packaging/i915n.aliases path=usr/share/drmadm/i915n.aliases owner=root group=bin mode=0444 + +# Unmodified Intel DMC firmware; a separate licence from the driver code. +dir path=kernel/firmware owner=root group=sys mode=0755 +dir path=kernel/firmware/i915 owner=root group=sys mode=0755 +file firmware/i915/skl_dmc_ver1_27.bin path=kernel/firmware/i915/skl_dmc_ver1_27.bin owner=root group=sys mode=0444 reboot-needed=true +file firmware/i915/kbl_dmc_ver1_04.bin path=kernel/firmware/i915/kbl_dmc_ver1_04.bin owner=root group=sys mode=0444 reboot-needed=true +license firmware/LICENSE.i915 license=Intel-DMC -- 2.56.0 From 25d35b1811e2fecb2a3aed500fb9da7106d9db82 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:45:07 +0200 Subject: [PATCH 07/13] firmware: preserve upstream license bytes in source archives --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fb6f115 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +firmware/LICENSE.i915 -text -- 2.56.0 From e50b0ceb0b76118ef177d7856014ffe451987d97 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:45:27 +0200 Subject: [PATCH 08/13] firmware: retain original CRLF license contents --- firmware/LICENSE.i915 | 78 +++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/firmware/LICENSE.i915 b/firmware/LICENSE.i915 index 3a05d15..2492b8d 100644 --- a/firmware/LICENSE.i915 +++ b/firmware/LICENSE.i915 @@ -1,39 +1,39 @@ -Copyright (c) 2015, Intel Corporation. -All rights reserved. - -Redistribution. Redistribution and use in binary form, without -modification, are permitted provided that the following conditions are -met: - -* Redistributions must reproduce the above copyright notice and the - following disclaimer in the documentation and/or other materials - provided with the distribution. -* Neither the name of Intel Corporation nor the names of its suppliers - may be used to endorse or promote products derived from this software - without specific prior written permission. -* No reverse engineering, decompilation, or disassembly of this software - is permitted. - -Limited patent license. Intel Corporation grants a world-wide, -royalty-free, non-exclusive license under patents it now or hereafter -owns or controls to make, have made, use, import, offer to sell and -sell ("Utilize") this software, but solely to the extent that any -such patent is necessary to Utilize the software alone, or in -combination with an operating system licensed under an approved Open -Source license as listed by the Open Source Initiative at -http://opensource.org/licenses. The patent license shall not apply to -any other combinations which include this software. No hardware per -se is licensed hereunder. - -DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, -BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. +Copyright (c) 2015, Intel Corporation. +All rights reserved. + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +* Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. +* Neither the name of Intel Corporation nor the names of its suppliers + may be used to endorse or promote products derived from this software + without specific prior written permission. +* No reverse engineering, decompilation, or disassembly of this software + is permitted. + +Limited patent license. Intel Corporation grants a world-wide, +royalty-free, non-exclusive license under patents it now or hereafter +owns or controls to make, have made, use, import, offer to sell and +sell ("Utilize") this software, but solely to the extent that any +such patent is necessary to Utilize the software alone, or in +combination with an operating system licensed under an approved Open +Source license as listed by the Open Source Initiative at +http://opensource.org/licenses. The patent license shall not apply to +any other combinations which include this software. No hardware per +se is licensed hereunder. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. -- 2.56.0 From 2943e027794c848b3b54658b03d0d08c83212695 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:48:48 +0200 Subject: [PATCH 09/13] drm_kms: bridge connector notifications to bounded illumos sysevents --- carried/drm_kms/illumos/drm_kms_hotplug.c | 178 ++++++++++++++++++++++ carried/drm_kms/illumos/drm_kms_illumos.h | 5 + carried/drm_kms/illumos/drm_kms_lkms.c | 9 +- carried/drm_kms/illumos/drm_kms_stubs.c | 6 + carried/drm_kms/private.mapfile | 1 + 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 carried/drm_kms/illumos/drm_kms_hotplug.c diff --git a/carried/drm_kms/illumos/drm_kms_hotplug.c b/carried/drm_kms/illumos/drm_kms_hotplug.c new file mode 100644 index 0000000..7e01fab --- /dev/null +++ b/carried/drm_kms/illumos/drm_kms_hotplug.c @@ -0,0 +1,178 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright 2026 The illumos-drm authors + * + * Notifications carry IDs, never connector pointers. The registry lock makes + * lookup/queue atomic with unregister; unregister removes the entry, then + * drains its worker before the device and its dip can disappear. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "drm_kms_illumos.h" + +struct drm_hotplug { + struct list_head link; + struct drm_device *dev; + struct delayed_work work; + unsigned long next; + uint64_t sequence; + uint32_t connector, reasons; + unsigned retries; + bool dirty, stopped; +}; +static LIST_HEAD(hotplug_devices); +static DEFINE_SPINLOCK(hotplug_lock); +/* mdb-visible counters; updated under hotplug_lock. */ +uint64_t drm_hotplug_sent, drm_hotplug_failed, drm_hotplug_coalesced; + +static void +hotplug_queue(struct drm_hotplug *hp) +{ + unsigned long delay = msecs_to_jiffies(250); + + if (time_before(jiffies, hp->next)) + delay = max(delay, hp->next - jiffies); + /* Do not move an already scheduled deadline during a storm. */ + queue_delayed_work(system_wq, &hp->work, delay); +} + +static int +hotplug_emit(struct drm_hotplug *hp, uint32_t connector, uint32_t reasons) +{ + nvlist_t *attrs; + dev_info_t *dip = lkpi_dev_dip(hp->dev->dev); + char path[MAXPATHLEN]; + int rc; + + if (nvlist_alloc(&attrs, NV_UNIQUE_NAME, KM_NOSLEEP) != 0) + return (-1); + (void) ddi_pathname(dip, path); + rc = nvlist_add_uint32(attrs, "version", 1) | + nvlist_add_uint32(attrs, "driver-major", ddi_driver_major(dip)) | + nvlist_add_uint32(attrs, "primary-minor", hp->dev->primary->index) | + nvlist_add_string(attrs, "device-path", path) | + nvlist_add_uint32(attrs, "connector-id", connector) | + nvlist_add_uint32(attrs, "reasons", reasons) | + nvlist_add_uint64(attrs, "sequence", hp->sequence); + if (rc == 0) + rc = ddi_log_sysevent(dip, "illumos-drm", "EC_drm", + "ESC_drm_hotplug", attrs, NULL, DDI_NOSLEEP); + nvlist_free(attrs); + return (rc); +} + +static void +hotplug_work(struct work_struct *work) +{ + struct drm_hotplug *hp = container_of(to_delayed_work(work), + struct drm_hotplug, work); + unsigned long flags; + uint32_t connector, reasons; + int rc; + + spin_lock_irqsave(&hotplug_lock, flags); + if (hp->stopped || !hp->dirty) { + spin_unlock_irqrestore(&hotplug_lock, flags); + return; + } + connector = hp->connector; + reasons = hp->reasons; + hp->dirty = false; + hp->reasons = 0; + hp->sequence++; + hp->next = jiffies + msecs_to_jiffies(1000); + spin_unlock_irqrestore(&hotplug_lock, flags); + rc = hotplug_emit(hp, connector, reasons); + spin_lock_irqsave(&hotplug_lock, flags); + if (rc == 0) { + drm_hotplug_sent++; + hp->retries = 0; + } else { + drm_hotplug_failed++; + /* Retry delivery at most three times without a new HPD. */ + if (++hp->retries <= 3 && !hp->stopped) { + hp->dirty = true; + hp->connector = 0; + hp->reasons |= reasons; + } + } + if (hp->dirty && !hp->stopped) + hotplug_queue(hp); + spin_unlock_irqrestore(&hotplug_lock, flags); +} + +int +drm_kms_hotplug_register(struct drm_device *dev) +{ + struct drm_hotplug *hp; + unsigned long flags; + + if (dev->primary == NULL || lkpi_dev_dip(dev->dev) == NULL) + return (-ENODEV); + hp = kzalloc(sizeof (*hp), GFP_KERNEL); + if (hp == NULL) + return (-ENOMEM); + hp->dev = dev; + hp->next = jiffies; + INIT_DELAYED_WORK(&hp->work, hotplug_work); + spin_lock_irqsave(&hotplug_lock, flags); + list_add_tail(&hp->link, &hotplug_devices); + spin_unlock_irqrestore(&hotplug_lock, flags); + return (0); +} + +void +drm_kms_hotplug_unregister(struct drm_device *dev) +{ + struct drm_hotplug *hp, *found = NULL; + unsigned long flags; + + spin_lock_irqsave(&hotplug_lock, flags); + list_for_each_entry(hp, &hotplug_devices, link) { + if (hp->dev == dev) { + hp->stopped = true; + list_del(&hp->link); + found = hp; + break; + } + } + spin_unlock_irqrestore(&hotplug_lock, flags); + if (found != NULL) { + cancel_delayed_work_sync(&found->work); + kfree(found); + } +} + +void +drm_kms_hotplug_notify(struct drm_device *dev, uint32_t id, uint32_t reasons) +{ + struct drm_hotplug *hp; + unsigned long flags; + + spin_lock_irqsave(&hotplug_lock, flags); + list_for_each_entry(hp, &hotplug_devices, link) { + if (hp->dev != dev) + continue; + if (hp->dirty) { + drm_hotplug_coalesced++; + if (hp->connector != id) + hp->connector = 0; + } else { + hp->connector = id; + } + hp->dirty = true; + hp->reasons |= reasons; + hp->retries = 0; + hotplug_queue(hp); + break; + } + spin_unlock_irqrestore(&hotplug_lock, flags); +} diff --git a/carried/drm_kms/illumos/drm_kms_illumos.h b/carried/drm_kms/illumos/drm_kms_illumos.h index 678c1e0..349a924 100644 --- a/carried/drm_kms/illumos/drm_kms_illumos.h +++ b/carried/drm_kms/illumos/drm_kms_illumos.h @@ -24,4 +24,9 @@ extern const struct drm_illumos_lkms_funcs drm_kms_illumos_funcs; extern void drm_kms_events_release(struct drm_device *dev, struct drm_file *file); +/* Internal sysevent bridge: reason bits HPD=1, topology=2, property=4. */ +extern int drm_kms_hotplug_register(struct drm_device *); +extern void drm_kms_hotplug_unregister(struct drm_device *); +extern void drm_kms_hotplug_notify(struct drm_device *, uint32_t, uint32_t); + #endif diff --git a/carried/drm_kms/illumos/drm_kms_lkms.c b/carried/drm_kms/illumos/drm_kms_lkms.c index 30d3cde..dda8235 100644 --- a/carried/drm_kms/illumos/drm_kms_lkms.c +++ b/carried/drm_kms/illumos/drm_kms_lkms.c @@ -217,9 +217,15 @@ drm_kms_register_dev(struct drm_device *dev) * the device counts as registered, then its objects register. */ drm_mode_config_validate(dev); + if ((ret = drm_kms_hotplug_register(dev)) != 0) + return (ret); WRITE_ONCE(dev->illumos_kms_registered, true); - if ((ret = drm_modeset_register_all(dev)) != 0) + if ((ret = drm_modeset_register_all(dev)) != 0) { WRITE_ONCE(dev->illumos_kms_registered, false); + drm_kms_hotplug_unregister(dev); + } else { + drm_kms_hotplug_notify(dev, 0, 2); + } return (ret); } @@ -228,6 +234,7 @@ drm_kms_unregister_dev(struct drm_device *dev) { /* Linux's drm_dev_unregister(): unregistered, then the objects. */ WRITE_ONCE(dev->illumos_kms_registered, false); + drm_kms_hotplug_unregister(dev); drm_modeset_unregister_all(dev); } diff --git a/carried/drm_kms/illumos/drm_kms_stubs.c b/carried/drm_kms/illumos/drm_kms_stubs.c index 0b41d59..a17b810 100644 --- a/carried/drm_kms/illumos/drm_kms_stubs.c +++ b/carried/drm_kms/illumos/drm_kms_stubs.c @@ -38,6 +38,7 @@ #include #include +#include "drm_kms_illumos.h" #include "drm_internal.h" #include "drm_crtc_internal.h" @@ -196,12 +197,14 @@ drm_sysfs_connector_add(struct drm_connector *connector) int drm_sysfs_connector_add_late(struct drm_connector *connector) { + drm_kms_hotplug_notify(connector->dev, 0, 2); return (0); } void drm_sysfs_connector_remove_early(struct drm_connector *connector) { + drm_kms_hotplug_notify(connector->dev, 0, 2); } void @@ -218,12 +221,14 @@ drm_sysfs_connector_remove(struct drm_connector *connector) void drm_sysfs_hotplug_event(struct drm_device *dev) { + drm_kms_hotplug_notify(dev, 0, 1); } EXPORT_SYMBOL(drm_sysfs_hotplug_event); void drm_sysfs_connector_hotplug_event(struct drm_connector *connector) { + drm_kms_hotplug_notify(connector->dev, connector->base.id, 1); } EXPORT_SYMBOL(drm_sysfs_connector_hotplug_event); @@ -231,6 +236,7 @@ void drm_sysfs_connector_property_event(struct drm_connector *connector, struct drm_property *property) { + drm_kms_hotplug_notify(connector->dev, connector->base.id, 4); } EXPORT_SYMBOL(drm_sysfs_connector_property_event); diff --git a/carried/drm_kms/private.mapfile b/carried/drm_kms/private.mapfile index b7f0cc9..9fbb27e 100644 --- a/carried/drm_kms/private.mapfile +++ b/carried/drm_kms/private.mapfile @@ -12,6 +12,7 @@ $mapfile_version 2 SYMBOL_SCOPE { global: + ddi_log_sysevent { FLAGS = EXTERN }; # # The kernel's C memory and string functions # (common/util/memstr.c, common/util/string.c), which the -- 2.56.0 From c9094120ef482e25c8de9f124cfd3890c632ed4b Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:49:44 +0200 Subject: [PATCH 10/13] drm_kms: declare kernel pathname provider for hotplug events --- carried/drm_kms/private.mapfile | 1 + 1 file changed, 1 insertion(+) diff --git a/carried/drm_kms/private.mapfile b/carried/drm_kms/private.mapfile index 9fbb27e..beb7d6d 100644 --- a/carried/drm_kms/private.mapfile +++ b/carried/drm_kms/private.mapfile @@ -13,6 +13,7 @@ $mapfile_version 2 SYMBOL_SCOPE { global: ddi_log_sysevent { FLAGS = EXTERN }; + ddi_pathname { FLAGS = EXTERN }; # # The kernel's C memory and string functions # (common/util/memstr.c, common/util/string.c), which the -- 2.56.0 From 335fc76f78b0e6656a2eb454e6c0f232f25b84a6 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 23:06:07 +0200 Subject: [PATCH 11/13] docs: specify sysevent payload, lifetime rules and validation boundaries --- carried/drm_kms/illumos/drm_kms_stubs.c | 4 +- docs/drm-hotplug-sysevent.md | 63 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 docs/drm-hotplug-sysevent.md diff --git a/carried/drm_kms/illumos/drm_kms_stubs.c b/carried/drm_kms/illumos/drm_kms_stubs.c index a17b810..12f4c19 100644 --- a/carried/drm_kms/illumos/drm_kms_stubs.c +++ b/carried/drm_kms/illumos/drm_kms_stubs.c @@ -149,8 +149,8 @@ EXPORT_SYMBOL(drm_lease_filter_crtcs); /* * --- sysfs (drm_sysfs.c) ---------------------------------------------------- - * illumos has no sysfs: hotplug and property events are not sent to user - * space (Xorg on illumos has no udev and probes on its own). A connector + * illumos has no sysfs: hotplug/property notifications use the bounded + * sysevent bridge in drm_kms_hotplug.c. A connector * still gets its class device, as on Linux (connector->kdev, named * card-, child of the DRM device's struct device), because * drivers use it as the parent and name of what hangs off the connector: diff --git a/docs/drm-hotplug-sysevent.md b/docs/drm-hotplug-sysevent.md new file mode 100644 index 0000000..b5479a5 --- /dev/null +++ b/docs/drm-hotplug-sysevent.md @@ -0,0 +1,63 @@ +# illumos DRM connector notifications (pilot ABI version 1) + +The carried KMS module maps drm_sysfs_hotplug_event, connector hotplug and +connector property events to `ddi_log_sysevent`. Class `EC_drm`, subclass +`ESC_drm_hotplug`, vendor `illumos-drm`. Connector registration/removal also +marks topology dirty. The Linux helper's existing client notifications remain +unchanged. This producer covers the carried Linux KMS path (i915n); native Rust +KMS drivers must explicitly integrate a producer or use userland polling. + +| nvlist name | Type | Meaning | +| --- | --- | --- | +| version | uint32 | 1; reject unknown formats | +| driver-major | uint32 | DDI driver major | +| primary-minor | uint32 | Primary DRM node minor before clone-open encoding | +| device-path | string | ddi_pathname path, for diagnostics | +| connector-id | uint32 | DRM object ID; 0 means full/merged topology | +| reasons | uint32 | OR of HPD=1, topology=2, property=4 | +| sequence | uint64 | Per-registration emission-attempt sequence, starts at 1 | + +Events are hints to query current state, not a lossless connector history. +Sequence resets at registration and gaps are allowed. Connector IDs can be +reused; consumers must never cache a pointer or infer current presence from an +event alone. The Xorg consumer always reconciles the full resource list, using +its existing MST path and RRGetInfo, including disappearance and later creation. +Zaphod's upstream CRTC-count restriction remains; this does not add MST support +to Zaphod configurations. + +A per-device entry is inserted before modeset object registration. Notifications +only update IDs/reasons and queue a delayed work item under the registry lock; +they allocate nothing and do not call syseventd. The first deadline is 250 ms, +is not extended by further notifications, and subsequent emissions are limited +to one per second per device. Differing IDs collapse to zero. A worker builds +an nvlist and calls ddi_log_sysevent with DDI_NOSLEEP. Delivery failures get at +most three retries without a new event. `drm_hotplug_sent`, `drm_hotplug_failed` +and `drm_hotplug_coalesced` are mdb-visible counters, serialized by that lock. + +Unregister removes the entry under the same lock used for notification lookup, +then synchronously cancels/drains delayed work before connector teardown and +freeing the entry. A running worker can finish while unregister waits; it cannot +requeue after the stop flag. Registration failures drain the entry too. This +avoids changing the facade's device layout or retaining connector pointers. +The driver/core must still honor its normal device-lifetime contract. + +Xorg's modesetting patch binds one libsysevent subscriber for all its screens. +The callback thread validates the version and matches driver major plus primary +minor, writes only to a nonblocking pipe, and never calls Xorg/DRM/RandR. Facade +clone-open minors encode the primary node in the low nine bits; the consumer +checks the DRM driver name before applying that convention. On the server +thread, pipe notifications coalesce into a timer and invoke the same reprobe as +udev. Events received off-VT remain pending; existing EnterVT also re-probes. +Unbind joins the delivery thread. Screen removal first removes its pipe entry +under the callback mutex, then drains/unbinds before closing/freeing resources. + +No new daemon or SMF service is needed: the existing system/sysevent service is +the transport. A failed subscription leaves the screen usable. The separate +polling patch provides both a fallback and a low-frequency lost-event check; +its HotplugPoll option is independent of event subscription. + +Current i915n DDI glue rejects suspend/resume. This bridge adds neither power +management nor a false suspend claim. VT/re-registration paths rescan; real +resume integration must issue a full topology notification after the driver's +future hardware resume completes. Test physical HPD, MST docks, cable storms, +teardown and suspend only on hardware implementing those paths. -- 2.56.0 From 1c16a034149824f8ece10babd91b04173fce78a4 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 23:12:52 +0200 Subject: [PATCH 12/13] drm_kms: sample hotplug rate-limit clock once to avoid delay underflow --- carried/drm_kms/illumos/drm_kms_hotplug.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/carried/drm_kms/illumos/drm_kms_hotplug.c b/carried/drm_kms/illumos/drm_kms_hotplug.c index 7e01fab..2b869ac 100644 --- a/carried/drm_kms/illumos/drm_kms_hotplug.c +++ b/carried/drm_kms/illumos/drm_kms_hotplug.c @@ -36,10 +36,11 @@ uint64_t drm_hotplug_sent, drm_hotplug_failed, drm_hotplug_coalesced; static void hotplug_queue(struct drm_hotplug *hp) { + unsigned long now = jiffies; unsigned long delay = msecs_to_jiffies(250); - if (time_before(jiffies, hp->next)) - delay = max(delay, hp->next - jiffies); + if (time_before(now, hp->next)) + delay = max(delay, hp->next - now); /* Do not move an already scheduled deadline during a storm. */ queue_delayed_work(system_wq, &hp->work, delay); } -- 2.56.0 From ef2ec8168f08d5cde703d76d0d6d17820deacdc3 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 23:15:37 +0200 Subject: [PATCH 13/13] test: cover sysevent deadline tick boundaries and pending storms --- tools/test/test-hotplug-deadline.py | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tools/test/test-hotplug-deadline.py diff --git a/tools/test/test-hotplug-deadline.py b/tools/test/test-hotplug-deadline.py new file mode 100644 index 0000000..da50c90 --- /dev/null +++ b/tools/test/test-hotplug-deadline.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""Exercise the actual sysevent deadline function, including jiffies wrap.""" +from pathlib import Path +import os, subprocess, tempfile +s=(Path(__file__).resolve().parents[2]/'carried/drm_kms/illumos/drm_kms_hotplug.c').read_text() +s=s[s.index('static void\nhotplug_queue'):s.index('\nstatic int\nhotplug_emit')] +pre=r''' +#include +#include +#include +struct delayed_work { int unused; }; +struct drm_hotplug { unsigned long next; struct delayed_work work; }; +static unsigned long clock_value, step, scheduled; +static int pending, reads; +static void *system_wq; +static unsigned long ticks(void) { unsigned long n=clock_value; clock_value+=step; reads++; return n; } +#define jiffies ticks() +#define msecs_to_jiffies(n) ((unsigned long)(n)) +#define time_before(a,b) ((long)((a)-(b)) < 0) +#define max(a,b) ((a)>(b)?(a):(b)) +static int queue_delayed_work(void *q,struct delayed_work *w,unsigned long delay) { if(pending) return 0; pending=1; scheduled=delay; return 1; } +''' +post=r''' +static void check(unsigned long now,unsigned long next,unsigned long expected) { + struct drm_hotplug hp={.next=next}; clock_value=now; step=2; reads=pending=0; + hotplug_queue(&hp); assert(reads==1); assert(scheduled==expected); + for(int i=0;i<100;i++) hotplug_queue(&hp); + assert(scheduled==expected); /* a storm does not extend the deadline */ +} +int main(void) { + check(10,11,250); /* crossing the next tick cannot underflow */ + check(1000,2000,1000); + check(2000,1000,250); + check(ULONG_MAX-100,400,501); /* unsigned clock wrap */ + puts("PASS: deadline edge, rate floor, overdue, wrap, pending storm"); +} +''' +with tempfile.TemporaryDirectory(prefix='hotplug-deadline-') as td: + p=Path(td); (p/'test.c').write_text(pre+s+post) + subprocess.run([os.environ.get('CC','cc'),'-std=c99','-Wall','-Wextra','-Wno-unused-parameter',str(p/'test.c'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) -- 2.56.0