From 25e30a292672090b75233371ea2b883279847711 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:51:35 +0200 Subject: [PATCH 1/5] modesetting: reprobe connectors from illumos DRM sysevents --- .../patches/41-illumos-sysevent-hotplug.patch | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 components/x11/xorg-server/patches/41-illumos-sysevent-hotplug.patch diff --git a/components/x11/xorg-server/patches/41-illumos-sysevent-hotplug.patch b/components/x11/xorg-server/patches/41-illumos-sysevent-hotplug.patch new file mode 100644 index 0000000000..5a89486196 --- /dev/null +++ b/components/x11/xorg-server/patches/41-illumos-sysevent-hotplug.patch @@ -0,0 +1,221 @@ +--- a/hw/xfree86/drivers/modesetting/drmmode_display.c ++++ b/hw/xfree86/drivers/modesetting/drmmode_display.c +@@ -3985,6 +3985,8 @@ + drmmode_output_private_ptr drmmode_output = output->driver_private; + + drmmode_output_detect(output); ++ if (!drmmode_output->mode_output) ++ continue; /* MST connector disappeared during the reprobe. */ + + /* Get an updated view of the properties for the current connector and + * look for the link-status property +@@ -4106,9 +4108,176 @@ + + #endif + ++#ifdef __sun ++/* libsysevent delivers on its own thread. Only the pipe crosses into Xorg; ++ * its callback never accesses RandR, DRM, ScrnInfo or output objects. */ ++#include ++#include ++#include ++#include ++#include ++ ++struct drmmode_sysevent { ++ struct drmmode_sysevent *next; ++ drmmode_ptr drmmode; ++ uint32_t major, minor; ++ int pipefd[2]; ++ InputHandlerProc handler; ++ OsTimerPtr timer; ++}; ++static pthread_mutex_t sysevent_lock = PTHREAD_MUTEX_INITIALIZER; ++static struct drmmode_sysevent *sysevent_screens; ++static sysevent_handle_t *sysevent_handle; ++ ++static void ++drmmode_sysevent_callback(sysevent_t *ev) ++{ ++ nvlist_t *attrs; ++ uint32_t version, driver_major, primary_minor; ++ struct drmmode_sysevent *s; ++ const char byte = 1; ++ ++ if (strcmp(sysevent_get_class_name(ev), "EC_drm") || ++ strcmp(sysevent_get_subclass_name(ev), "ESC_drm_hotplug") || ++ sysevent_get_attr_list(ev, &attrs) != 0) ++ return; ++ if (nvlist_lookup_uint32(attrs, "version", &version) == 0 && version == 1 && ++ nvlist_lookup_uint32(attrs, "driver-major", &driver_major) == 0 && ++ nvlist_lookup_uint32(attrs, "primary-minor", &primary_minor) == 0) { ++ pthread_mutex_lock(&sysevent_lock); ++ for (s = sysevent_screens; s; s = s->next) { ++ if (s->major == driver_major && s->minor == primary_minor) { ++ ssize_t n; ++ do { n = write(s->pipefd[1], &byte, 1); } while (n < 0 && errno == EINTR); ++ /* EAGAIN means a notification is already pending. */ ++ } ++ } ++ pthread_mutex_unlock(&sysevent_lock); ++ } ++ nvlist_free(attrs); ++} ++ ++static CARD32 ++drmmode_sysevent_reprobe(OsTimerPtr timer, CARD32 now, void *closure) ++{ ++ struct drmmode_sysevent *s = closure; ++ if (!s->drmmode->scrn->vtSema) ++ return 1000; /* Keep the last event until this screen owns the VT. */ ++ drmmode_update_kms_state(s->drmmode); ++ return 0; ++} ++ ++static void ++drmmode_sysevent_read(int fd, void *closure) ++{ ++ struct drmmode_sysevent *s = closure; ++ char bytes[256]; ++ ssize_t n; ++ do { n = read(fd, bytes, sizeof(bytes)); } while (n > 0 || (n < 0 && errno == EINTR)); ++ /* TimerSet coalesces bursts; the kernel also bounds event frequency. */ ++ s->timer = TimerSet(s->timer, 0, 100, drmmode_sysevent_reprobe, s); ++} ++ ++static void ++drmmode_sysevent_init(drmmode_ptr drmmode) ++{ ++ struct drmmode_sysevent *s; ++ struct stat st; ++ const char *subclass = "ESC_drm_hotplug"; ++ drmVersionPtr ver; ++ int i; ++ ++ if (fstat(drmmode->fd, &st) != 0) ++ return; ++ /* Facade 2.0 clone opens encode the node in the low nine minor bits. ++ * Do not interpret an unrelated driver's clone ABI this way. */ ++ ver = drmGetVersion(drmmode->fd); ++ if (!ver) ++ return; ++ i = !strcmp(ver->name, "i915") || !strcmp(ver->name, "virtio_gpu"); ++ drmFreeVersion(ver); ++ if (!i || (minor(st.st_rdev) & 0x1ff) >= 128) ++ return; ++ s = calloc(1, sizeof(*s)); ++ if (!s) ++ return; ++ if (pipe(s->pipefd) != 0) { ++ free(s); ++ return; ++ } ++ for (i = 0; i < 2; i++) { ++ if (fcntl(s->pipefd[i], F_SETFL, O_NONBLOCK) < 0 || ++ fcntl(s->pipefd[i], F_SETFD, FD_CLOEXEC) < 0) ++ goto fail; ++ } ++ if (!sysevent_handle) { ++ sysevent_handle = sysevent_bind_handle(drmmode_sysevent_callback); ++ if (!sysevent_handle) ++ goto fail; ++ if (sysevent_subscribe_event(sysevent_handle, "EC_drm", &subclass, 1) != 0) { ++ sysevent_unbind_handle(sysevent_handle); ++ sysevent_handle = NULL; ++ goto fail; ++ } ++ } ++ s->drmmode = drmmode; ++ s->major = major(st.st_rdev); ++ s->minor = minor(st.st_rdev) & 0x1ff; ++ s->handler = xf86AddGeneralHandler(s->pipefd[0], drmmode_sysevent_read, s); ++ if (!s->handler) ++ goto fail; ++ pthread_mutex_lock(&sysevent_lock); ++ s->next = sysevent_screens; ++ sysevent_screens = s; ++ pthread_mutex_unlock(&sysevent_lock); ++ drmmode->sysevent = s; ++ /* Close the subscription race with a complete initial reprobe. */ ++ s->timer = TimerSet(NULL, 0, 100, drmmode_sysevent_reprobe, s); ++ xf86DrvMsg(drmmode->scrn->scrnIndex, X_INFO, "illumos DRM sysevent hotplug enabled\n"); ++ return; ++fail: ++ if (!sysevent_screens && sysevent_handle) { ++ sysevent_unbind_handle(sysevent_handle); ++ sysevent_handle = NULL; ++ } ++ close(s->pipefd[0]); ++ close(s->pipefd[1]); ++ free(s); ++ xf86DrvMsg(drmmode->scrn->scrnIndex, X_WARNING, "illumos DRM sysevent hotplug unavailable\n"); ++} ++ ++static void ++drmmode_sysevent_fini(drmmode_ptr drmmode) ++{ ++ struct drmmode_sysevent *s = drmmode->sysevent, **p; ++ if (!s) ++ return; ++ pthread_mutex_lock(&sysevent_lock); ++ for (p = &sysevent_screens; *p && *p != s; p = &(*p)->next) {} ++ if (*p) ++ *p = s->next; ++ pthread_mutex_unlock(&sysevent_lock); ++ /* No callback can use s after removal under the lock. Unbind outside ++ * that lock, since it waits for callback threads to finish. */ ++ if (!sysevent_screens) { ++ sysevent_unbind_handle(sysevent_handle); ++ sysevent_handle = NULL; ++ } ++ xf86RemoveGeneralHandler(s->handler); ++ TimerFree(s->timer); ++ close(s->pipefd[0]); ++ close(s->pipefd[1]); ++ drmmode->sysevent = NULL; ++ free(s); ++} ++#endif ++ + void + drmmode_uevent_init(ScrnInfoPtr scrn, drmmode_ptr drmmode) + { ++#ifdef __sun ++ drmmode_sysevent_init(drmmode); ++#endif + #ifdef CONFIG_UDEV_KMS + struct udev *u; + struct udev_monitor *mon; +@@ -4142,6 +4311,9 @@ + void + drmmode_uevent_fini(ScrnInfoPtr scrn, drmmode_ptr drmmode) + { ++#ifdef __sun ++ drmmode_sysevent_fini(drmmode); ++#endif + #ifdef CONFIG_UDEV_KMS + if (drmmode->uevent_handler) { + struct udev *u = udev_monitor_get_udev(drmmode->uevent_monitor); +--- a/hw/xfree86/drivers/modesetting/drmmode_display.h ++++ b/hw/xfree86/drivers/modesetting/drmmode_display.h +@@ -98,6 +98,9 @@ + #ifdef CONFIG_UDEV_KMS + struct udev_monitor *uevent_monitor; + InputHandlerProc uevent_handler; ++#endif ++#ifdef __sun ++ struct drmmode_sysevent *sysevent; + #endif + drmEventContext event_context; + drmmode_bo front_bo; +--- a/hw/xfree86/drivers/modesetting/Makefile.am ++++ b/hw/xfree86/drivers/modesetting/Makefile.am +@@ -41,7 +41,7 @@ + + modesetting_drv_la_LTLIBRARIES = modesetting_drv.la + modesetting_drv_la_LDFLAGS = -module -avoid-version +-modesetting_drv_la_LIBADD = $(UDEV_LIBS) $(DRM_LIBS) ++modesetting_drv_la_LIBADD = $(UDEV_LIBS) $(DRM_LIBS) -lsysevent -lnvpair + modesetting_drv_ladir = @moduledir@/drivers + + modesetting_drv_la_SOURCES = \ -- 2.56.0 From fc457bf83abd2f8d084e1d3b3d9303b524094374 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:53:10 +0200 Subject: [PATCH 2/5] modesetting: apply sysevent linkage after existing illumos port patch --- .../patches/41-illumos-sysevent-hotplug.patch | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/components/x11/xorg-server/patches/41-illumos-sysevent-hotplug.patch b/components/x11/xorg-server/patches/41-illumos-sysevent-hotplug.patch index 5a89486196..c4d65f355b 100644 --- a/components/x11/xorg-server/patches/41-illumos-sysevent-hotplug.patch +++ b/components/x11/xorg-server/patches/41-illumos-sysevent-hotplug.patch @@ -210,12 +210,6 @@ drmmode_bo front_bo; --- a/hw/xfree86/drivers/modesetting/Makefile.am +++ b/hw/xfree86/drivers/modesetting/Makefile.am -@@ -41,7 +41,7 @@ - - modesetting_drv_la_LTLIBRARIES = modesetting_drv.la - modesetting_drv_la_LDFLAGS = -module -avoid-version --modesetting_drv_la_LIBADD = $(UDEV_LIBS) $(DRM_LIBS) -+modesetting_drv_la_LIBADD = $(UDEV_LIBS) $(DRM_LIBS) -lsysevent -lnvpair - modesetting_drv_ladir = @moduledir@/drivers - - modesetting_drv_la_SOURCES = \ +@@ -45 +45 @@ +-modesetting_drv_la_LIBADD = $(UDEV_LIBS) $(DRM_LIBS) $(LIBDRM_LIBS) ++modesetting_drv_la_LIBADD = $(UDEV_LIBS) $(DRM_LIBS) $(LIBDRM_LIBS) -lsysevent -lnvpair -- 2.56.0 From d433733cb33b3bedf3727a4b5bfa5c4290fe4f9b Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 22:56:03 +0200 Subject: [PATCH 3/5] modesetting: add bounded connector polling with change detection and backoff --- .../patches/42-illumos-hotplug-poll.patch | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 components/x11/xorg-server/patches/42-illumos-hotplug-poll.patch diff --git a/components/x11/xorg-server/patches/42-illumos-hotplug-poll.patch b/components/x11/xorg-server/patches/42-illumos-hotplug-poll.patch new file mode 100644 index 0000000000..a63d0171e1 --- /dev/null +++ b/components/x11/xorg-server/patches/42-illumos-hotplug-poll.patch @@ -0,0 +1,220 @@ +--- a/hw/xfree86/drivers/modesetting/drmmode_display.c ++++ b/hw/xfree86/drivers/modesetting/drmmode_display.c +@@ -4272,11 +4272,161 @@ + } + #endif + ++#ifdef __sun ++/* A bounded polling cycle: one resource list and at most eight connector ++ * ioctls per timer callback, at most 128 connectors in a snapshot. These ++ * bounds limit work issued, not the kernel's execution time for one ioctl. */ ++#define MS_POLL_MAX_CONNECTORS 128 ++struct drmmode_poll { ++ drmmode_ptr drmmode; ++ OsTimerPtr timer; ++ uint32_t ids[MS_POLL_MAX_CONNECTORS]; ++ unsigned count, pos; ++ uint64_t hash, previous; ++ CARD32 interval; ++ Bool valid, scanning, external; ++}; ++ ++static uint64_t ++poll_hash(uint64_t hash, uint32_t value) ++{ ++ return (hash ^ value) * UINT64_C(1099511628211); ++} ++ ++static int ++poll_id_compare(const void *a, const void *b) ++{ ++ uint32_t x = *(const uint32_t *)a, y = *(const uint32_t *)b; ++ return (x > y) - (x < y); ++} ++ ++static CARD32 ++drmmode_poll_tick(OsTimerPtr timer, CARD32 now, void *closure) ++{ ++ struct drmmode_poll *p = closure; ++ drmmode_ptr drmmode = p->drmmode; ++ drmModeResPtr resources; ++ unsigned budget = 4, i; ++ ++ if (!drmmode->scrn->vtSema) { ++ p->scanning = FALSE; ++ p->valid = FALSE; ++ return 5000; ++ } ++ if (!p->scanning) { ++ resources = drmModeGetResources(drmmode->fd); ++ if (!resources) ++ goto backoff; ++ if (resources->count_connectors < 0 || ++ resources->count_connectors > MS_POLL_MAX_CONNECTORS) { ++ drmModeFreeResources(resources); ++ goto backoff; ++ } ++ p->count = resources->count_connectors; ++ for (i = 0; i < p->count; i++) ++ p->ids[i] = resources->connectors[i]; ++ drmModeFreeResources(resources); ++ qsort(p->ids, p->count, sizeof(p->ids[0]), poll_id_compare); ++ p->pos = 0; ++ p->hash = poll_hash(UINT64_C(14695981039346656037), p->count); ++ /* An empty topology may acquire an MST connector later. */ ++ p->external = p->count == 0; ++ p->scanning = TRUE; ++ } ++ while (p->pos < p->count && budget--) { ++ drmModeConnectorPtr c; ++ uint32_t id = p->ids[p->pos++]; ++ /* Current avoids forcing a panel probe just to find its type. */ ++ c = drmModeGetConnectorCurrent(drmmode->fd, id); ++ if (!c) ++ goto backoff; /* An MST snapshot raced removal; retry later. */ ++ p->hash = poll_hash(p->hash, id); ++ if (c->connector_type == DRM_MODE_CONNECTOR_LVDS || ++ c->connector_type == DRM_MODE_CONNECTOR_eDP || ++ c->connector_type == DRM_MODE_CONNECTOR_DSI) { ++ drmModeFreeConnector(c); ++ continue; ++ } ++ p->external = TRUE; ++ drmModeFreeConnector(c); ++ c = drmModeGetConnector(drmmode->fd, id); ++ if (!c) ++ goto backoff; ++ p->hash = poll_hash(p->hash, c->connection); ++ p->hash = poll_hash(p->hash, c->count_modes); ++ for (i = 0; i < c->count_modes; i++) { ++ p->hash = poll_hash(p->hash, c->modes[i].clock); ++ p->hash = poll_hash(p->hash, c->modes[i].hdisplay); ++ p->hash = poll_hash(p->hash, c->modes[i].vdisplay); ++ p->hash = poll_hash(p->hash, c->modes[i].htotal); ++ p->hash = poll_hash(p->hash, c->modes[i].vtotal); ++ p->hash = poll_hash(p->hash, c->modes[i].flags); ++ } ++ /* Includes EDID blob ID and link-status, without fetching blobs. */ ++ for (i = 0; i < c->count_props; i++) { ++ p->hash = poll_hash(p->hash, c->props[i]); ++ p->hash = poll_hash(p->hash, c->prop_values[i]); ++ p->hash = poll_hash(p->hash, c->prop_values[i] >> 32); ++ } ++ drmModeFreeConnector(c); ++ } ++ if (p->pos < p->count) ++ return 250; ++ p->scanning = FALSE; ++ if (!p->valid || p->hash != p->previous) ++ drmmode_update_kms_state(drmmode); ++ p->previous = p->hash; ++ p->valid = TRUE; ++ p->interval = drmmode->sysevent ? 5000 : 3000; ++ /* Fixed panels alone cannot hotplug. Retain only a slow resource check ++ * for topology changes; no forced panel probing or full RandR walk. */ ++ return p->external ? p->interval : 30000; ++backoff: ++ p->scanning = FALSE; ++ p->interval = p->interval < 15000 ? p->interval * 2 : 30000; ++ return p->interval; ++} ++ ++static void ++drmmode_poll_init(drmmode_ptr drmmode) ++{ ++ struct drmmode_poll *p; ++ if (!xf86ReturnOptValBool(drmmode->Options, OPTION_HOTPLUG_POLL, TRUE)) ++ return; ++ p = calloc(1, sizeof(*p)); ++ if (!p) ++ return; ++ p->drmmode = drmmode; ++ p->interval = drmmode->sysevent ? 5000 : 3000; ++ p->timer = TimerSet(NULL, 0, p->interval, drmmode_poll_tick, p); ++ if (!p->timer) { ++ free(p); ++ return; ++ } ++ drmmode->hotplug_poll = p; ++ xf86DrvMsg(drmmode->scrn->scrnIndex, X_INFO, ++ "bounded connector polling enabled (%u ms; HotplugPoll disables)\n", ++ p->interval); ++} ++ ++static void ++drmmode_poll_fini(drmmode_ptr drmmode) ++{ ++ struct drmmode_poll *p = drmmode->hotplug_poll; ++ if (!p) ++ return; ++ TimerFree(p->timer); ++ drmmode->hotplug_poll = NULL; ++ free(p); ++} ++#endif ++ + void + drmmode_uevent_init(ScrnInfoPtr scrn, drmmode_ptr drmmode) + { + #ifdef __sun + drmmode_sysevent_init(drmmode); ++ drmmode_poll_init(drmmode); + #endif + #ifdef CONFIG_UDEV_KMS + struct udev *u; +@@ -4312,6 +4462,7 @@ + drmmode_uevent_fini(ScrnInfoPtr scrn, drmmode_ptr drmmode) + { + #ifdef __sun ++ drmmode_poll_fini(drmmode); + drmmode_sysevent_fini(drmmode); + #endif + #ifdef CONFIG_UDEV_KMS +--- a/hw/xfree86/drivers/modesetting/drmmode_display.h ++++ b/hw/xfree86/drivers/modesetting/drmmode_display.h +@@ -101,6 +101,7 @@ + #endif + #ifdef __sun + struct drmmode_sysevent *sysevent; ++ struct drmmode_poll *hotplug_poll; + #endif + drmEventContext event_context; + drmmode_bo front_bo; +--- a/hw/xfree86/drivers/modesetting/driver.h ++++ b/hw/xfree86/drivers/modesetting/driver.h +@@ -61,6 +61,7 @@ + OPTION_VARIABLE_REFRESH, + OPTION_USE_GAMMA_LUT, + OPTION_ASYNC_FLIP_SECONDARIES, ++ OPTION_HOTPLUG_POLL, + } modesettingOpts; + + typedef struct +--- a/hw/xfree86/drivers/modesetting/driver.c ++++ b/hw/xfree86/drivers/modesetting/driver.c +@@ -134,6 +134,7 @@ + }; + + static const OptionInfoRec Options[] = { ++ {OPTION_HOTPLUG_POLL, "HotplugPoll", OPTV_BOOLEAN, {0}, FALSE}, + {OPTION_SW_CURSOR, "SWcursor", OPTV_BOOLEAN, {0}, FALSE}, + {OPTION_DEVICE_PATH, "kmsdev", OPTV_STRING, {0}, FALSE}, + {OPTION_SHADOW_FB, "ShadowFB", OPTV_BOOLEAN, {0}, FALSE}, +--- a/hw/xfree86/drivers/modesetting/modesetting.man ++++ b/hw/xfree86/drivers/modesetting/modesetting.man +@@ -109,6 +109,15 @@ + entries, if supported by the kernel. By default, GAMMA_LUT will be used for + kms drivers which are known to be safe for use of GAMMA_LUT. + .TP ++On illumos, Option "HotplugPoll" "false" disables the bounded connector-status ++polling fallback. By default external outputs are checked every three seconds, ++or five seconds when the DRM sysevent subscription is available. Failed queries ++back off to thirty seconds. Fixed panels are not forcibly probed. Resource ++snapshots are limited to 128 connectors, with four connectors per timer batch. ++A single kernel ioctl cannot be preempted by this userspace budget. ++Polling pauses while the screen does not own the VT. Sysevent notifications ++remain enabled independently; both paths reconcile dynamic MST connectors. ++.PP + .SH "SEE ALSO" + @xservername@(@appmansuffix@), @xconfigfile@(@filemansuffix@), Xserver(@appmansuffix@), + X(@miscmansuffix@) -- 2.56.0 From da56a71c2725f44b40bde901cf3844f6d4b7fcdb Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 23:10:27 +0200 Subject: [PATCH 4/5] docs: describe hotplug limits and require clean modesetting layout rebuilds --- components/x11/xorg-server/illumos-hotplug.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 components/x11/xorg-server/illumos-hotplug.md diff --git a/components/x11/xorg-server/illumos-hotplug.md b/components/x11/xorg-server/illumos-hotplug.md new file mode 100644 index 0000000000..74279840a2 --- /dev/null +++ b/components/x11/xorg-server/illumos-hotplug.md @@ -0,0 +1,39 @@ +# illumos connector hotplug pilot + +Patch 41 subscribes modesetting to EC_drm / ESC_drm_hotplug, version 1. +It requires the matching carried DRM KMS producer. The callback only wakes a +nonblocking pipe; Xorg's main thread reconciles KMS/RandR. Device matching uses +the driver major and the facade's primary minor (low nine clone-minor bits). +The existing EnterVT path re-probes; an event received off-VT stays pending. +Screen teardown removes the callback's pipe entry before unbinding/freeing it. +The intel DDX is not changed; use modesetting for this pilot. + +Patch 42 adds Option "HotplugPoll" (default true). Setting it to "false" +disables only polling. External outputs are queried every three seconds when +subscription is unavailable and five seconds when it is available; the latter +is a lost-event watchdog, including when the kernel predates the producer. +Query failures back off to at most 30 seconds. Polling pauses off-VT. + +A snapshot has at most 128 connectors, processed four per timer callback +(at most eight connector queries: current type then forced external probe). +Additional batches are 250 ms apart. Fixed panels are never forcibly probed; +a panel-only device gets only a slow 30-second resource/type check. Empty +resources remain eligible for topology discovery. Connector IDs, connection, +mode timings and property values form the change signature; only a changed +snapshot (or the initial one) invokes the complete MST/RandR reconciliation. +The work budget cannot preempt a single slow kernel ioctl. Upstream Zaphod +restrictions remain; hardware MST and suspend support are not implied. + +Build from a clean component tree after changing drmmode_display.h or driver.h. +This OI recipe disables compiler dependency tracking: an incremental make can +retain objects built for the old embedded drmmode_rec layout, producing a +module that links but crashes in unrelated DRI2/glamor startup. Rebuild all +modesetting translation units, or use the normal clean component build. Never +install directly into a shared build host; use the recipe's prototype DESTDIR. + +Validation: native full Xorg build for patch 41; clean native module rebuild +for patch 42; disposable X230 BE, sysevent device filtering/burst coalescing, +Xorg restart during events, polling cadence/change behavior and disable option. +See the workspace task receipt for exact hashes and which runtime checks have +completed. A guest without an Intel GPU can check payloads/load and desktop +regression, but cannot validate physical HPD, DMC or MST. -- 2.56.0 From f2b70f45618febb3607208e59e0e40b61f0e4c67 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Wed, 14 Oct 2026 23:14:34 +0200 Subject: [PATCH 5/5] test: exercise polling topology, backoff, VT and per-callback limits --- .../xorg-server/tests/test-hotplug-poll.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 components/x11/xorg-server/tests/test-hotplug-poll.py diff --git a/components/x11/xorg-server/tests/test-hotplug-poll.py b/components/x11/xorg-server/tests/test-hotplug-poll.py new file mode 100644 index 0000000000..ba95206231 --- /dev/null +++ b/components/x11/xorg-server/tests/test-hotplug-poll.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Compile the patch's actual polling state machine against fake DRM/Xorg APIs. +Run with CC=/usr/gcc/14/bin/gcc on illumos; no display or privileges required. +""" +from pathlib import Path +import os, subprocess, tempfile +patch = (Path(__file__).resolve().parents[1] / 'patches/42-illumos-hotplug-poll.patch').read_text() +start = patch.index('+#ifdef __sun\n') +end = patch.index('+#endif\n', start) + len('+#endif\n') +code = '\n'.join(line[1:] for line in patch[start:end].splitlines() if line.startswith('+')) +pre = r''' +#include +#include +#include +#include +#include +#define __sun 1 +#define TRUE 1 +#define FALSE 0 +#define X_INFO 0 +#define OPTION_HOTPLUG_POLL 1 +#define DRM_MODE_CONNECTOR_LVDS 7 +#define DRM_MODE_CONNECTOR_eDP 14 +#define DRM_MODE_CONNECTOR_DSI 16 +typedef int Bool; +typedef uint32_t CARD32; +typedef void *OsTimerPtr; +struct scrn { int vtSema, scrnIndex; }; +struct drmmode_poll; +typedef struct { struct scrn *scrn; void *sysevent, *Options; int fd; struct drmmode_poll *hotplug_poll; } drmmode_rec, *drmmode_ptr; +typedef struct { uint32_t clock,hdisplay,vdisplay,htotal,vtotal,flags; } mode; +typedef struct { int count_connectors; uint32_t *connectors; } resources, *drmModeResPtr; +typedef struct { int connector_type, connection, count_modes, count_props; mode *modes; uint32_t *props; uint64_t *prop_values; } connector, *drmModeConnectorPtr; +static uint32_t ids[130]; +static int nids=8, failure, status, calls, forced, reprobes, enabled=1; +static drmModeResPtr drmModeGetResources(int fd) { resources *r=calloc(1,sizeof(*r)); r->count_connectors=nids; r->connectors=ids; return r; } +static void drmModeFreeResources(drmModeResPtr r) { free(r); } +static drmModeConnectorPtr get_connector(uint32_t id) { connector *c; calls++; if (failure) return NULL; c=calloc(1,sizeof(*c)); c->connector_type=id==1 ? DRM_MODE_CONNECTOR_LVDS:10; c->connection=status; return c; } +static drmModeConnectorPtr drmModeGetConnectorCurrent(int fd,uint32_t id) { return get_connector(id); } +static drmModeConnectorPtr drmModeGetConnector(int fd,uint32_t id) { forced++; return get_connector(id); } +static void drmModeFreeConnector(drmModeConnectorPtr c) { free(c); } +static void drmmode_update_kms_state(drmmode_ptr d) { reprobes++; } +static int xf86ReturnOptValBool(void *o,int key,int def) { return enabled; } +static OsTimerPtr TimerSet(OsTimerPtr t,int flags,CARD32 ms,CARD32 (*fn)(OsTimerPtr,CARD32,void*),void *data) { return t ? t:malloc(1); } +static void TimerFree(OsTimerPtr t) { free(t); } +#define xf86DrvMsg(...) ((void)0) +''' +post = r''' +static CARD32 tick(struct drmmode_poll *p) { CARD32 r; calls=forced=0; r=drmmode_poll_tick(p->timer,0,p); assert(calls<=8); return r; } +static CARD32 cycle(struct drmmode_poll *p) { CARD32 r; unsigned batches=0; do { r=tick(p); assert(++batches<=32); } while(r==250); return r; } +int main(void) { + struct scrn scrn={1,0}; drmmode_rec d={0}; struct drmmode_poll *p; int old; + for(unsigned i=0;i<130;i++) ids[i]=i+1; + d.scrn=&scrn; d.sysevent=&scrn; + enabled=0; drmmode_poll_init(&d); assert(!d.hotplug_poll); + enabled=1; drmmode_poll_init(&d); assert(d.hotplug_poll); p=d.hotplug_poll; + assert(tick(p)==250 && forced==3); assert(tick(p)==5000 && forced==4 && reprobes==1); + assert(cycle(p)==5000 && reprobes==1); /* unchanged topology */ + status=1; assert(cycle(p)==5000 && reprobes==2); + nids=9; assert(cycle(p)==5000 && reprobes==3); /* MST addition */ + nids=8; assert(cycle(p)==5000 && reprobes==4); /* removal */ + failure=1; assert(tick(p)==10000); assert(tick(p)==20000); assert(tick(p)==30000); assert(tick(p)==30000); + failure=0; assert(cycle(p)==5000 && reprobes==4); + scrn.vtSema=0; old=reprobes; assert(tick(p)==5000 && calls==0 && reprobes==old); + scrn.vtSema=1; assert(cycle(p)==5000 && reprobes==old+1); + d.sysevent=NULL; assert(cycle(p)==3000); /* unavailable subscription */ + nids=1; assert(cycle(p)==30000 && forced==0); /* fixed panel */ + nids=0; assert(cycle(p)==3000); /* keep discovery alive */ + nids=129; assert(tick(p)==6000 && calls==0); /* resource bound */ + nids=128; assert(cycle(p)==3000); /* maximum bounded snapshot */ + drmmode_poll_fini(&d); assert(!d.hotplug_poll); + puts("PASS: filtering, change detection, MST IDs, backoff, VT, limits, disable, teardown"); + return 0; +} +''' +with tempfile.TemporaryDirectory(prefix='hotplug-poll-') as td: + src=Path(td)/'test.c'; exe=Path(td)/'test'; src.write_text(pre+code+post) + subprocess.run([os.environ.get('CC','cc'),'-std=c99','-Wall','-Wextra','-Wno-unused-parameter','-o',str(exe),str(src)],check=True) + subprocess.run([str(exe)],check=True) -- 2.56.0