Skip to content

Commit 1a49b0b

Browse files
committed
cog: fix drm platform for kiosk use; pin meson options
Six upstream-ready patches against 0.18.5 (developed and verified on a Dell Wyse 5010: radeon PALM/DCE4.1, both llvmpipe/SHM and r600/dmabuf paths): - 001: cold-start initialization. CRTC selection required an existing encoder binding (none exists without a prior fbcon/firmware modeset), kms_screen_create crashed on modeless connectors, init_cursor used the possibly-dead screens[0]. The incomplete display setup from the old CRTC selection is also what left DCE4.1's cursor fetch uninitialized - initially misdiagnosed as a hardware/kernel bug. - 002: SHM exported-buffer handling. Root cause of a crash family: the renderer stored itself in the buffer resource's user_data, which for SHM buffers IS libwayland's wl_shm_buffer - from the second attach on, wl_shm_buffer_get() returned the renderer as buffer metadata. Plus early buffer release (avoids deferred pool-resize stale mappings) and copy hardening. - 003: legacy hardware cursor (drmModeSetCursor) for non-atomic drivers without a universal cursor plane, with correct 64x64 tightly-packed premultiplied cursor image. - 004: software cursor option (COG_PLATFORM_DRM_CURSOR=sw), composited into SHM frames with in-place motion updates; dmabuf-safe. - 005: cursor.x/y are unsigned, so the existing 'if (cursor.x < 0)' lower clamp was dead code; moving past the top/left edge wrapped the position around and the upper clamp teleported the cursor to the opposite edge. Clamp in floating point before the unsigned store. Present upstream (master) as well. - 006: scale the 16x16 cursor artwork by the view's device scale factor (nearest neighbour; up to 4x within the 64x64 hardware cursor buffer, same factor for the modeset renderer's software cursor), so the pointer keeps its apparent size next to a --device-scale'd UI. Makefile: pin the complete meson option set of 0.18.5 (platforms drm/headless/wayland, wpe_api 2.0, libmanette, plugin path). PKG_RELEASE 9. Patchset already proposed upstream: Igalia/cog#794 Assisted-By: Claude Fable + Opus Signed-off-by: Mirko Vogt <mirko-openwrt@nanl.de>
1 parent a951381 commit 1a49b0b

7 files changed

Lines changed: 1290 additions & 1 deletion

libs/cog/Makefile

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
22

33
PKG_NAME:=cog
44
PKG_VERSION:=0.18.5
5-
PKG_RELEASE:=1
5+
PKG_RELEASE:=9
66

77
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
88
PKG_SOURCE_URL:=https://wpewebkit.org/releases
@@ -44,7 +44,18 @@ libcogcore is a library with ready-to-use components typically needed
4444
for implementing applications which use the WPE WebKit API.
4545
endef
4646

47+
# every meson option of this release pinned explicitly, so a version
48+
# bump cannot silently change the platform set or defaults
4749
MESON_ARGS += \
50+
-Dplugin_path=/usr/lib/cog/modules \
51+
-Dplatforms=drm,headless,wayland \
52+
-Dprograms=true \
53+
-Dwpe_api=2.0 \
54+
-Dlibmanette=enabled \
55+
-Dwayland_weston_direct_display=false \
56+
-Dwayland_weston_content_protection=false \
57+
-Dcog_appid=com.igalia.Cog \
58+
-Dcog_home_uri= \
4859
-Dcog_dbus_control=system \
4960
-Dcog_dbus_system_owner=cog \
5061
-Ddocumentation=false \
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
From 27f9125b3975310b19a78b6b86801ce557a216ba Mon Sep 17 00:00:00 2001
2+
From: Mirko Vogt <foss@mirko.in>
3+
Date: Sat, 18 Jul 2026 21:58:17 +0000
4+
Subject: [PATCH 1/6] drm: fix cold-start initialization
5+
6+
On a display with no prior modeset (no fbcon/fbdev emulation, no
7+
firmware POST pass - e.g. a bootloader handing straight over to bare
8+
KMS), the drm platform failed to initialize:
9+
10+
- The CRTC was picked only by matching an encoder's CURRENT binding
11+
(encoder->crtc_id), which is 0 on cold start, aborting with "no crtc
12+
for encoder found". Prefer the target connector's own encoders and
13+
fall back to the first CRTC an encoder can drive (possible_crtcs).
14+
This also fixes picking another output's encoder whose CRTC cannot
15+
drive the chosen connector. Note that the incomplete display setup
16+
resulting from the old selection is also what left e.g. radeon
17+
DCE4.1's hardware cursor fetch uninitialized (cursor rendered as
18+
garbage) - a full cold-start modeset cures that as well.
19+
20+
- kms_screen_create() dereferenced con->modes[0] unconditionally; it
21+
is NULL for disconnected/modeless connectors (any dual-output card
22+
with one output unused), crashing at startup when the cursor is
23+
enabled. A zeroed mode now marks the screen unusable.
24+
25+
- init_cursor() used screens[0], which may be that dead connector;
26+
pick the first usable screen. The initial cursor plane set may fail
27+
while the CRTC is still off (first modeset happens at first frame
28+
commit) - treat that as non-fatal, the pointer-motion handler
29+
retries.
30+
31+
Signed-off-by: Mirko Vogt <foss@mirko.in>
32+
---
33+
platform/drm/cog-platform-drm.c | 75 ++++++++++++++++++++++++++-------
34+
platform/drm/kms.c | 12 ++++--
35+
2 files changed, 69 insertions(+), 18 deletions(-)
36+
37+
diff --git a/platform/drm/cog-platform-drm.c b/platform/drm/cog-platform-drm.c
38+
index b9f28a8..da2c639 100644
39+
--- a/platform/drm/cog-platform-drm.c
40+
+++ b/platform/drm/cog-platform-drm.c
41+
@@ -362,6 +362,15 @@ find_crtc_for_encoder(const drmModeRes *resources, const drmModeEncoder *encoder
42+
}
43+
}
44+
45+
+ /* Cold start: with no prior modeset (no fbcon/firmware POST) the
46+
+ * encoder is bound to no CRTC yet (crtc_id == 0), so the match on
47+
+ * the CURRENT binding above finds nothing. Fall back to the first
48+
+ * CRTC this encoder can physically drive. */
49+
+ for (int i = 0; i < resources->count_crtcs; i++) {
50+
+ if (encoder->possible_crtcs & (1 << i))
51+
+ return resources->crtcs[i];
52+
+ }
53+
+
54+
/* no match found */
55+
return -1;
56+
}
57+
@@ -508,21 +517,35 @@ init_drm(void)
58+
(long)((drm_data.mode - drm_data.connector.obj->modes) / sizeof(drmModeModeInfo *)), drm_data.mode->name,
59+
drm_data.mode->vrefresh);
60+
61+
- /* Try the currently connected encoder+crtc */
62+
- for (int i = 0; i < drm_data.base_resources->count_encoders; ++i) {
63+
- drm_data.encoder = drmModeGetEncoder(drm_data.fd, drm_data.base_resources->encoders[i]);
64+
- if (!drm_data.encoder) {
65+
- /* cannot retrieve encoder, ignoring... */
66+
+ /* Prefer THIS connector's own encoders: scanning all encoders can
67+
+ * pick another output's encoder whose CRTC does not drive our
68+
+ * connector. find_crtc_for_encoder() handles the cold-start case
69+
+ * (no active CRTC binding yet) via its possible_crtcs fallback. */
70+
+ for (int i = 0; i < drm_data.connector.obj->count_encoders && !drm_data.encoder; ++i) {
71+
+ drmModeEncoder *enc = drmModeGetEncoder(drm_data.fd, drm_data.connector.obj->encoders[i]);
72+
+ if (!enc)
73+
continue;
74+
+ const int32_t crtc_id = find_crtc_for_encoder(drm_data.base_resources, enc);
75+
+ if (crtc_id != -1) {
76+
+ drm_data.encoder = enc;
77+
+ drm_data.crtc.obj_id = crtc_id;
78+
+ break;
79+
}
80+
+ drmModeFreeEncoder(enc);
81+
+ }
82+
83+
- const int32_t crtc_id = find_crtc_for_encoder(drm_data.base_resources, drm_data.encoder);
84+
+ /* Last resort: any encoder that yields a CRTC. */
85+
+ for (int i = 0; i < drm_data.base_resources->count_encoders && !drm_data.encoder; ++i) {
86+
+ drmModeEncoder *enc = drmModeGetEncoder(drm_data.fd, drm_data.base_resources->encoders[i]);
87+
+ if (!enc)
88+
+ continue;
89+
+ const int32_t crtc_id = find_crtc_for_encoder(drm_data.base_resources, enc);
90+
if (crtc_id != -1) {
91+
+ drm_data.encoder = enc;
92+
drm_data.crtc.obj_id = crtc_id;
93+
break;
94+
}
95+
-
96+
- g_clear_pointer (&drm_data.encoder, drmModeFreeEncoder);
97+
+ drmModeFreeEncoder(enc);
98+
}
99+
100+
if (!drm_data.encoder) {
101+
@@ -623,8 +646,10 @@ init_cursor (void)
102+
}
103+
104+
cursor.device = kms_device_open(drm_data.fd);
105+
- if (!cursor.device)
106+
+ if (!cursor.device) {
107+
+ g_warning("cursor: kms_device_open failed");
108+
return FALSE;
109+
+ }
110+
111+
cursor.plane = kms_device_find_plane_by_type(cursor.device, DRM_PLANE_TYPE_CURSOR, 0);
112+
if (!cursor.plane) {
113+
@@ -640,21 +665,41 @@ init_cursor (void)
114+
115+
cursor.cursor = create_cursor_framebuffer(cursor.device, format);
116+
if (!cursor.cursor) {
117+
+ g_warning("cursor: framebuffer creation failed");
118+
g_clear_pointer(&cursor.device, kms_device_free);
119+
return FALSE;
120+
}
121+
122+
- cursor.x = (cursor.device->screens[0]->width - cursor.cursor->width) / 2;
123+
- cursor.y = (cursor.device->screens[0]->height - cursor.cursor->height) / 2;
124+
- cursor.screen_width = cursor.device->screens[0]->width;
125+
- cursor.screen_height = cursor.device->screens[0]->height;
126+
-
127+
- if (kms_plane_set(cursor.plane, cursor.cursor, cursor.x, cursor.y)) {
128+
+ /* screens[0] may be a disconnected/modeless connector (e.g. the
129+
+ * unused second output of a dual-output card) - pick the first
130+
+ * usable screen instead. */
131+
+ struct kms_screen *cursor_screen = NULL;
132+
+ for (unsigned int i = 0; i < cursor.device->num_screens; i++) {
133+
+ if (cursor.device->screens[i]->connected && cursor.device->screens[i]->width > 0) {
134+
+ cursor_screen = cursor.device->screens[i];
135+
+ break;
136+
+ }
137+
+ }
138+
+ if (!cursor_screen) {
139+
+ g_warning("cursor: no usable screen (num_screens %u)", cursor.device->num_screens);
140+
g_clear_pointer(&cursor.device, kms_device_free);
141+
g_clear_pointer(&cursor.cursor, kms_framebuffer_free);
142+
return FALSE;
143+
}
144+
145+
+ cursor.x = (cursor_screen->width - cursor.cursor->width) / 2;
146+
+ cursor.y = (cursor_screen->height - cursor.cursor->height) / 2;
147+
+ cursor.screen_width = cursor_screen->width;
148+
+ cursor.screen_height = cursor_screen->height;
149+
+
150+
+ /* The CRTC is usually not lit yet at platform setup time - the
151+
+ * renderer performs the first modeset on the first frame commit -
152+
+ * so this initial cursor upload may fail (display still off). Not
153+
+ * fatal: the pointer-motion handler retries on every move and
154+
+ * succeeds once a mode is active. */
155+
+ if (kms_plane_set(cursor.plane, cursor.cursor, cursor.x, cursor.y))
156+
+ g_message("cursor plane not set yet (display off?) - will appear on first pointer motion");
157+
+
158+
cursor.enabled = TRUE;
159+
160+
return TRUE;
161+
diff --git a/platform/drm/kms.c b/platform/drm/kms.c
162+
index 7b6d894..fe742f5 100644
163+
--- a/platform/drm/kms.c
164+
+++ b/platform/drm/kms.c
165+
@@ -361,9 +361,15 @@ static void kms_screen_probe(struct kms_screen *screen)
166+
else
167+
screen->connected = false;
168+
169+
- memcpy(&screen->mode, &con->modes[0], sizeof(drmModeModeInfo));
170+
- screen->width = screen->mode.hdisplay;
171+
- screen->height = screen->mode.vdisplay;
172+
+ /* Disconnected connectors (or connected ones without EDID) have no
173+
+ * mode list - con->modes is NULL and count_modes 0. screen is
174+
+ * calloc'd, so leaving mode/width/height zeroed marks the screen
175+
+ * unusable instead of reading address 0. */
176+
+ if (con->count_modes > 0 && con->modes) {
177+
+ memcpy(&screen->mode, &con->modes[0], sizeof(drmModeModeInfo));
178+
+ screen->width = screen->mode.hdisplay;
179+
+ screen->height = screen->mode.vdisplay;
180+
+ }
181+
182+
drmModeFreeConnector(con);
183+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
From 4cf80725cde25abc4b0c2751ad72e659d98ae858 Mon Sep 17 00:00:00 2001
2+
From: Mirko Vogt <foss@mirko.in>
3+
Date: Sat, 18 Jul 2026 21:58:17 +0000
4+
Subject: [PATCH 2/6] drm: modeset: fix SHM exported buffer handling
5+
6+
Three related fixes for the modeset renderer's SHM path, found running
7+
WPE on software rendering (llvmpipe/kms_swrast):
8+
9+
- Never store the renderer in the buffer resource's user_data: for SHM
10+
buffers user_data is owned by libwayland's shm implementation - it IS
11+
the wl_shm_buffer. Overwriting it made wl_shm_buffer_get() return the
12+
renderer object from the second attach onwards: garbage width/stride,
13+
spurious "deferred resize pending" warnings, and a crashing copy
14+
loop. Keep the owning renderer in the buffer_object instead.
15+
16+
- Release the exported SHM buffer right after copying it into the dumb
17+
buffer instead of parking it until the frame retires: holding it
18+
keeps an external reference on the wl_shm pool, deferring client-side
19+
pool resizes into stale mappings.
20+
21+
- Harden the copy: bail out on a NULL data pointer, clamp to the bo's
22+
dimensions, and skip frames whose exported buffer carries impossible
23+
geometry (dispatching frame-complete so the client's pacing
24+
survives). Persistently bogus exports terminate the process for a
25+
clean supervisor restart rather than continuing on corrupted state.
26+
27+
Signed-off-by: Mirko Vogt <foss@mirko.in>
28+
---
29+
platform/drm/cog-drm-modeset-renderer.c | 72 ++++++++++++++++++++-----
30+
1 file changed, 59 insertions(+), 13 deletions(-)
31+
32+
diff --git a/platform/drm/cog-drm-modeset-renderer.c b/platform/drm/cog-drm-modeset-renderer.c
33+
index c1c91c2..dbaf2c4 100644
34+
--- a/platform/drm/cog-drm-modeset-renderer.c
35+
+++ b/platform/drm/cog-drm-modeset-renderer.c
36+
@@ -74,6 +74,7 @@ struct buffer_object {
37+
uint32_t fb_id;
38+
struct gbm_bo *bo;
39+
struct wl_resource *buffer_resource;
40+
+ void *renderer; /* owning CogDrmModesetRenderer */
41+
42+
struct {
43+
struct wl_resource *resource;
44+
@@ -137,14 +138,13 @@ static void
45+
destroy_buffer_notify(struct wl_listener *listener, void *data)
46+
{
47+
struct buffer_object *buffer = wl_container_of(listener, buffer, destroy_listener);
48+
- CogDrmModesetRenderer *renderer = wl_resource_get_user_data(buffer->buffer_resource);
49+
+ CogDrmModesetRenderer *renderer = buffer->renderer;
50+
51+
if (renderer->committed_buffer == buffer)
52+
renderer->committed_buffer = NULL;
53+
54+
wl_list_remove(&buffer->link);
55+
56+
- wl_resource_set_user_data(buffer->buffer_resource, NULL);
57+
destroy_buffer(renderer, buffer);
58+
}
59+
60+
@@ -216,7 +216,7 @@ drm_create_buffer_for_bo(CogDrmModesetRenderer *self,
61+
wl_list_insert(&self->buffer_list, &buffer->link);
62+
buffer->destroy_listener.notify = destroy_buffer_notify;
63+
wl_resource_add_destroy_listener(buffer_resource, &buffer->destroy_listener);
64+
- wl_resource_set_user_data(buffer_resource, self);
65+
+ buffer->renderer = self;
66+
67+
buffer->fb_id = fb_id;
68+
buffer->bo = bo;
69+
@@ -271,7 +271,7 @@ drm_create_buffer_for_shm_buffer(CogDrmModesetRenderer *self,
70+
wl_list_insert(&self->buffer_list, &buffer->link);
71+
buffer->destroy_listener.notify = destroy_buffer_notify;
72+
wl_resource_add_destroy_listener(buffer_resource, &buffer->destroy_listener);
73+
- wl_resource_set_user_data(buffer_resource, self);
74+
+ buffer->renderer = self;
75+
76+
buffer->fb_id = fb_id;
77+
buffer->bo = bo;
78+
@@ -287,6 +287,19 @@ drm_copy_shm_buffer_into_bo(struct wl_shm_buffer *shm_buffer, struct gbm_bo *bo)
79+
int32_t height = wl_shm_buffer_get_height(shm_buffer);
80+
int32_t stride = wl_shm_buffer_get_stride(shm_buffer);
81+
82+
+ /* Never write past the bo - clamp and report a size mismatch
83+
+ * (stale buffer_object?) instead of crashing. */
84+
+ uint32_t pre_bo_width = gbm_bo_get_width(bo);
85+
+ uint32_t pre_bo_height = gbm_bo_get_height(bo);
86+
+ if ((uint32_t) width > pre_bo_width || (uint32_t) height > pre_bo_height) {
87+
+ g_warning("SHM->bo size mismatch: shm %dx%d (stride %d) vs bo %ux%u - clamping",
88+
+ width, height, stride, pre_bo_width, pre_bo_height);
89+
+ if ((uint32_t) width > pre_bo_width)
90+
+ width = pre_bo_width;
91+
+ if ((uint32_t) height > pre_bo_height)
92+
+ height = pre_bo_height;
93+
+ }
94+
+
95+
uint32_t bo_stride = 0;
96+
void *map_data = NULL;
97+
gbm_bo_map(bo, 0, 0, width, height, GBM_BO_TRANSFER_WRITE, &bo_stride, &map_data);
98+
@@ -296,6 +309,11 @@ drm_copy_shm_buffer_into_bo(struct wl_shm_buffer *shm_buffer, struct gbm_bo *bo)
99+
wl_shm_buffer_begin_access(shm_buffer);
100+
101+
uint8_t *src = wl_shm_buffer_get_data(shm_buffer);
102+
+ if (!src) {
103+
+ wl_shm_buffer_end_access(shm_buffer);
104+
+ gbm_bo_unmap(bo, map_data);
105+
+ return;
106+
+ }
107+
uint8_t *dst = map_data;
108+
109+
uint32_t bo_width = gbm_bo_get_width(bo);
110+
@@ -522,6 +540,8 @@ on_export_dmabuf_resource(void *data, struct wpe_view_backend_exportable_fdo_dma
111+
}
112+
}
113+
114+
+static unsigned bogus_streak = 0;
115+
+
116+
static void
117+
on_export_shm_buffer(void *data, struct wpe_fdo_shm_exported_buffer *exported_buffer)
118+
{
119+
@@ -530,20 +550,46 @@ on_export_shm_buffer(void *data, struct wpe_fdo_shm_exported_buffer *exported_bu
120+
struct wl_resource *exported_resource = wpe_fdo_shm_exported_buffer_get_resource(exported_buffer);
121+
struct wl_shm_buffer *exported_shm_buffer = wpe_fdo_shm_exported_buffer_get_shm_buffer(exported_buffer);
122+
123+
- struct buffer_object *buffer = drm_buffer_for_resource(self, exported_resource);
124+
- if (buffer) {
125+
- drm_copy_shm_buffer_into_bo(exported_shm_buffer, buffer->bo);
126+
-
127+
- buffer->export.shm_buffer = exported_buffer;
128+
- drm_commit_buffer(self, buffer);
129+
- return;
130+
+ /* Never touch buffers with impossible geometry (a stale or dangling
131+
+ * wl_shm_buffer, e.g. exported by a buggy backend): skip the frame
132+
+ * instead of copying from/into wild memory. Do NOT release the
133+
+ * wrapper - that would poke the possibly-dangling resource; leaking
134+
+ * it is the lesser evil in an already-broken session. */
135+
+ {
136+
+ int32_t w = exported_shm_buffer ? wl_shm_buffer_get_width(exported_shm_buffer) : -1;
137+
+ int32_t h = exported_shm_buffer ? wl_shm_buffer_get_height(exported_shm_buffer) : -1;
138+
+ int32_t st = exported_shm_buffer ? wl_shm_buffer_get_stride(exported_shm_buffer) : -1;
139+
+ g_debug("shm export: resource %p shm %p %dx%d stride %d",
140+
+ (void *) exported_resource, (void *) exported_shm_buffer, w, h, st);
141+
+ if (!exported_resource || w <= 0 || h <= 0 || w > 16384 || h > 16384 || st < w * 4) {
142+
+ g_warning("shm export with bogus geometry (resource %p, shm %p, %dx%d stride %d) - frame skipped (streak %u)",
143+
+ (void *) exported_resource, (void *) exported_shm_buffer, w, h, st, ++bogus_streak);
144+
+ if (bogus_streak >= 4) {
145+
+ g_critical("persistent bogus SHM exports - exiting for a clean respawn");
146+
+ exit(70);
147+
+ }
148+
+ /* keep the frame pacing alive - without this WebKit waits
149+
+ * for the frame callback of the skipped frame forever */
150+
+ wpe_view_backend_exportable_fdo_dispatch_frame_complete(self->exportable);
151+
+ return;
152+
+ }
153+
+ bogus_streak = 0;
154+
}
155+
156+
- buffer = drm_create_buffer_for_shm_buffer(self, exported_resource, exported_shm_buffer);
157+
+ struct buffer_object *buffer = drm_buffer_for_resource(self, exported_resource);
158+
+ if (!buffer)
159+
+ buffer = drm_create_buffer_for_shm_buffer(self, exported_resource, exported_shm_buffer);
160+
if (buffer) {
161+
drm_copy_shm_buffer_into_bo(exported_shm_buffer, buffer->bo);
162+
163+
- buffer->export.shm_buffer = exported_buffer;
164+
+ /* The dumb buffer owns the pixels now: release the client's SHM
165+
+ * buffer right away instead of parking it until the frame retires.
166+
+ * Holding it keeps an external reference on the wl_shm pool, which
167+
+ * turns client-side pool resizes into deferred remaps - the next
168+
+ * copy would then use a stale mapping (libwayland warns "Buffer
169+
+ * address requested when its parent pool has an external reference
170+
+ * and a deferred resize pending.") and walk off its end. */
171+
+ wpe_view_backend_exportable_fdo_dispatch_release_shm_exported_buffer(self->exportable, exported_buffer);
172+
drm_commit_buffer(self, buffer);
173+
}
174+
}

0 commit comments

Comments
 (0)