commit caeb604

shrub  ·  2026-08-10 16:01:33 +0000 UTC
parent 2a4a013
add wsdisplay framebuffer
9 files changed,  +420, -18
+9, -0
 1@@ -302,6 +302,12 @@ handle_socket_data(int socket)
 2 			if (!active) {
 3 				goto fail;
 4 			}
 5+#endif
 6+#ifdef ENABLE_WSDISPLAY
 7+		} else if (device_is_tty(st.st_rdev)) {
 8+			if (!active) {
 9+				goto fail;
10+			}
11 #endif
12 		} else {
13 			fprintf(stderr, "requested fd is not a video or input device\n");
14@@ -646,6 +652,9 @@ main(int argc, char *argv[])
15 	fprintf(stderr, "running on %s\n", vt);
16 	tty_fd = open_tty(vt);
17 	setup_tty(tty_fd);
18+	if (setenv(SWC_LAUNCH_TTY_ENV, vt, 1) == -1) {
19+		die("setenv %s:", SWC_LAUNCH_TTY_ENV);
20+	}
21 
22 	sprintf(buf, "%d", sock[1]);
23 	setenv(SWC_LAUNCH_SOCKET_ENV, buf, 1);
+1, -0
1@@ -29,6 +29,7 @@
2 #include <sys/types.h>
3 
4 #define SWC_LAUNCH_SOCKET_ENV "SWC_LAUNCH_SOCKET"
5+#define SWC_LAUNCH_TTY_ENV "SWC_LAUNCH_TTY"
6 
7 struct iovec;
8 
+5, -5
 1@@ -32,7 +32,7 @@ blend(uint32_t background, uint32_t foreground)
 2 bool
 3 fb_initialize(void)
 4 {
 5-	if (!fbdev_initialize(&swc_fb)) {
 6+	if (!framebuffer_initialize(&swc_fb)) {
 7 		return false;
 8 	}
 9 	swc_fb.pitch = (size_t)swc_fb.width * sizeof(*swc_fb.pixels);
10@@ -58,7 +58,7 @@ error2:
11 error1:
12 	free(swc_fb.pixels);
13 error0:
14-	fbdev_finalize(&swc_fb);
15+	framebuffer_finalize(&swc_fb);
16 	return false;
17 }
18 
19@@ -68,7 +68,7 @@ fb_finalize(void)
20 	wld_destroy_renderer(swc_fb.backend.renderer);
21 	wld_destroy_context(swc_fb.backend.context);
22 	free(swc_fb.pixels);
23-	fbdev_finalize(&swc_fb);
24+	framebuffer_finalize(&swc_fb);
25 }
26 
27 bool
28@@ -77,7 +77,7 @@ fb_create_screens(struct wl_list *screens)
29 	struct output *output;
30 	struct screen *screen;
31 
32-	output = output_new_fb(swc_fb.width, swc_fb.height, "fbdev-0");
33+	output = output_new_fb(swc_fb.width, swc_fb.height, framebuffer_name());
34 	if (!output) {
35 		return false;
36 	}
37@@ -138,5 +138,5 @@ fb_present(struct wld_buffer *buffer, int32_t origin_x, int32_t origin_y)
38 		wld_unmap(cursor);
39 	}
40 	wld_unmap(buffer);
41-	return fbdev_present(&swc_fb);
42+	return framebuffer_present(&swc_fb);
43 }
+4, -3
 1@@ -25,8 +25,9 @@ void fb_finalize(void);
 2 bool fb_create_screens(struct wl_list *screens);
 3 bool fb_present(struct wld_buffer *buffer, int32_t x, int32_t y);
 4 
 5-bool fbdev_initialize(struct swc_fb *fb);
 6-void fbdev_finalize(struct swc_fb *fb);
 7-bool fbdev_present(struct swc_fb *fb);
 8+bool framebuffer_initialize(struct swc_fb *fb);
 9+void framebuffer_finalize(struct swc_fb *fb);
10+bool framebuffer_present(struct swc_fb *fb);
11+const char *framebuffer_name(void);
12 
13 #endif
+9, -3
 1@@ -37,7 +37,7 @@ channel_from_fbdev(struct fb_bitfield field)
 2 }
 3 
 4 bool
 5-fbdev_initialize(struct swc_fb *fb)
 6+framebuffer_initialize(struct swc_fb *fb)
 7 {
 8 	struct fb_fix_screeninfo fixed;
 9 	struct fb_var_screeninfo variable;
10@@ -120,7 +120,7 @@ error:
11 }
12 
13 void
14-fbdev_finalize(struct swc_fb *fb)
15+framebuffer_finalize(struct swc_fb *fb)
16 {
17 	(void)fb;
18 	munmap(fbdev.memory, fbdev.memory_length);
19@@ -168,7 +168,7 @@ store_pixel(uint8_t *destination, uint32_t pixel)
20 }
21 
22 bool
23-fbdev_present(struct swc_fb *fb)
24+framebuffer_present(struct swc_fb *fb)
25 {
26 	uint32_t bytes = fbdev.bits_per_pixel / 8;
27 	uint32_t x, y;
28@@ -183,3 +183,9 @@ fbdev_present(struct swc_fb *fb)
29 	}
30 	return true;
31 }
32+
33+const char *
34+framebuffer_name(void)
35+{
36+	return "fbdev-0";
37+}
+357, -0
  1@@ -0,0 +1,357 @@
  2+#include "fb.h"
  3+#include "launch.h"
  4+#include "launch/protocol.h"
  5+#include "util.h"
  6+
  7+#include <dev/wscons/wsconsio.h>
  8+#include <errno.h>
  9+#include <fcntl.h>
 10+#include <stdint.h>
 11+#include <stdlib.h>
 12+#include <string.h>
 13+#include <sys/ioctl.h>
 14+#include <sys/mman.h>
 15+#include <unistd.h>
 16+
 17+struct fb_channel {
 18+	uint32_t offset;
 19+	uint32_t length;
 20+};
 21+
 22+static struct {
 23+	int fd;
 24+	void *memory;
 25+	size_t memory_length;
 26+	size_t memory_offset;
 27+	uint32_t pitch;
 28+	uint32_t bits_per_pixel;
 29+	struct fb_channel red;
 30+	struct fb_channel green;
 31+	struct fb_channel blue;
 32+	u_int type;
 33+	u_int original_mode;
 34+	bool mode_saved;
 35+} wsdisplay;
 36+
 37+static bool
 38+add_overflows_size(size_t a, size_t b, size_t *result)
 39+{
 40+	if (a > SIZE_MAX - b) {
 41+		return true;
 42+	}
 43+	*result = a + b;
 44+	return false;
 45+}
 46+
 47+static bool
 48+multiply_overflows_size(size_t a, size_t b, size_t *result)
 49+{
 50+	if (a && b > SIZE_MAX / a) {
 51+		return true;
 52+	}
 53+	*result = a * b;
 54+	return false;
 55+}
 56+
 57+static const char *
 58+device_path(void)
 59+{
 60+	const char *path;
 61+
 62+	if (WSDISPLAY_DEVICE[0] != '\0') {
 63+		return WSDISPLAY_DEVICE;
 64+	}
 65+	path = getenv(SWC_LAUNCH_TTY_ENV);
 66+	if (path && path[0] != '\0') {
 67+		return path;
 68+	}
 69+#ifdef __OpenBSD__
 70+	return "/dev/ttyC0";
 71+#else
 72+	return "/dev/ttyE0";
 73+#endif
 74+}
 75+
 76+static bool
 77+default_channels(u_int type, uint32_t depth)
 78+{
 79+	bool blue_first = false;
 80+
 81+	switch (depth) {
 82+	case 15:
 83+		wsdisplay.red = (struct fb_channel){10, 5};
 84+		wsdisplay.green = (struct fb_channel){5, 5};
 85+		wsdisplay.blue = (struct fb_channel){0, 5};
 86+		return true;
 87+	case 16:
 88+		wsdisplay.red = (struct fb_channel){11, 5};
 89+		wsdisplay.green = (struct fb_channel){5, 6};
 90+		wsdisplay.blue = (struct fb_channel){0, 5};
 91+		return true;
 92+	case 24:
 93+	case 32:
 94+#ifdef WSDISPLAY_TYPE_SUN24
 95+		blue_first |= type == WSDISPLAY_TYPE_SUN24;
 96+#endif
 97+#ifdef WSDISPLAY_TYPE_SUNCG12
 98+		blue_first |= type == WSDISPLAY_TYPE_SUNCG12;
 99+#endif
100+#ifdef WSDISPLAY_TYPE_SUNCG14
101+		blue_first |= type == WSDISPLAY_TYPE_SUNCG14;
102+#endif
103+#ifdef WSDISPLAY_TYPE_SUNTCX
104+		blue_first |= type == WSDISPLAY_TYPE_SUNTCX;
105+#endif
106+#ifdef WSDISPLAY_TYPE_SUNFFB
107+		blue_first |= type == WSDISPLAY_TYPE_SUNFFB;
108+#endif
109+		wsdisplay.red = (struct fb_channel){blue_first ? 0 : 16, 8};
110+		wsdisplay.green = (struct fb_channel){8, 8};
111+		wsdisplay.blue = (struct fb_channel){blue_first ? 16 : 0, 8};
112+		return true;
113+	default:
114+		return false;
115+	}
116+}
117+
118+static bool
119+valid_channels(void)
120+{
121+	struct fb_channel channels[] = {
122+	    wsdisplay.red, wsdisplay.green, wsdisplay.blue,
123+	};
124+	size_t i;
125+
126+	if (wsdisplay.bits_per_pixel != 15 && wsdisplay.bits_per_pixel != 16 &&
127+	    wsdisplay.bits_per_pixel != 24 && wsdisplay.bits_per_pixel != 32) {
128+		return false;
129+	}
130+	for (i = 0; i < sizeof(channels) / sizeof(channels[0]); ++i) {
131+		if (!channels[i].length || channels[i].length > 16 ||
132+		    channels[i].offset >= wsdisplay.bits_per_pixel ||
133+		    channels[i].length >
134+		        wsdisplay.bits_per_pixel - channels[i].offset) {
135+			return false;
136+		}
137+	}
138+	return true;
139+}
140+
141+static bool
142+query_framebuffer(struct swc_fb *fb)
143+{
144+	size_t last_row_offset, row_bytes, visible_size, visible_end;
145+
146+	wsdisplay.type = WSDISPLAY_TYPE_UNKNOWN;
147+	(void)ioctl(wsdisplay.fd, WSDISPLAYIO_GTYPE, &wsdisplay.type);
148+
149+#ifdef __NetBSD__
150+	{
151+		struct wsdisplayio_fbinfo info;
152+
153+		memset(&info, 0, sizeof(info));
154+		if (ioctl(wsdisplay.fd, WSDISPLAYIO_GET_FBINFO, &info) == 0) {
155+			if (info.fbi_pixeltype != WSFB_RGB ||
156+			    info.fbi_fbsize > SIZE_MAX || info.fbi_fboffset > SIZE_MAX) {
157+				ERROR("Unsupported wsdisplay framebuffer format\n");
158+				return false;
159+			}
160+			fb->width = info.fbi_width;
161+			fb->height = info.fbi_height;
162+			wsdisplay.pitch = info.fbi_stride;
163+			wsdisplay.bits_per_pixel = info.fbi_bitsperpixel;
164+			wsdisplay.memory_length = info.fbi_fbsize;
165+			wsdisplay.memory_offset = info.fbi_fboffset;
166+			wsdisplay.red = (struct fb_channel){
167+			    info.fbi_subtype.fbi_rgbmasks.red_offset,
168+			    info.fbi_subtype.fbi_rgbmasks.red_size,
169+			};
170+			wsdisplay.green = (struct fb_channel){
171+			    info.fbi_subtype.fbi_rgbmasks.green_offset,
172+			    info.fbi_subtype.fbi_rgbmasks.green_size,
173+			};
174+			wsdisplay.blue = (struct fb_channel){
175+			    info.fbi_subtype.fbi_rgbmasks.blue_offset,
176+			    info.fbi_subtype.fbi_rgbmasks.blue_size,
177+			};
178+			goto validate;
179+		}
180+	}
181+#endif
182+
183+	{
184+		struct wsdisplay_fbinfo info;
185+		u_int linebytes = 0;
186+
187+		memset(&info, 0, sizeof(info));
188+		if (ioctl(wsdisplay.fd, WSDISPLAYIO_GINFO, &info) < 0) {
189+			ERROR("WSDISPLAYIO_GINFO failed: %s\n", strerror(errno));
190+			return false;
191+		}
192+		fb->width = info.width;
193+		fb->height = info.height;
194+		wsdisplay.bits_per_pixel = info.depth;
195+#ifdef __OpenBSD__
196+		wsdisplay.pitch = info.stride;
197+		wsdisplay.memory_offset = info.offset;
198+#else
199+		wsdisplay.memory_offset = 0;
200+#endif
201+		if (!wsdisplay.pitch) {
202+			if (ioctl(wsdisplay.fd, WSDISPLAYIO_LINEBYTES, &linebytes) < 0) {
203+				ERROR("WSDISPLAYIO_LINEBYTES failed: %s\n", strerror(errno));
204+				return false;
205+			}
206+			wsdisplay.pitch = linebytes;
207+		}
208+		if (!default_channels(wsdisplay.type, info.depth)) {
209+			ERROR("Unsupported wsdisplay depth: %u bits per pixel\n", info.depth);
210+			return false;
211+		}
212+		if (multiply_overflows_size(wsdisplay.pitch, fb->height, &visible_size) ||
213+		    add_overflows_size(wsdisplay.memory_offset, visible_size,
214+		                       &wsdisplay.memory_length)) {
215+			ERROR("wsdisplay framebuffer size overflows\n");
216+			return false;
217+		}
218+	}
219+
220+#ifdef __NetBSD__
221+validate:
222+#endif
223+	if (!fb->width || !fb->height || fb->width > UINT16_MAX ||
224+	    fb->height > UINT16_MAX || !wsdisplay.memory_length ||
225+	    !valid_channels() ||
226+	    multiply_overflows_size(fb->width,
227+	                            (wsdisplay.bits_per_pixel + 7) / 8,
228+	                            &row_bytes) ||
229+	    row_bytes > wsdisplay.pitch ||
230+	    multiply_overflows_size(fb->height - 1, wsdisplay.pitch,
231+	                            &last_row_offset) ||
232+	    add_overflows_size(last_row_offset, row_bytes, &visible_size) ||
233+	    add_overflows_size(wsdisplay.memory_offset, visible_size,
234+	                       &visible_end) ||
235+	    visible_end > wsdisplay.memory_length) {
236+		ERROR("Unsupported wsdisplay geometry or channel layout\n");
237+		return false;
238+	}
239+	return true;
240+}
241+
242+bool
243+framebuffer_initialize(struct swc_fb *fb)
244+{
245+	const char *path = device_path();
246+	u_int mode = WSDISPLAYIO_MODE_DUMBFB;
247+
248+	memset(&wsdisplay, 0, sizeof(wsdisplay));
249+	wsdisplay.fd = -1;
250+	wsdisplay.fd = launch_open_device(path, O_RDWR | O_CLOEXEC);
251+	if (wsdisplay.fd < 0) {
252+		ERROR("Could not open wsdisplay device %s\n", path);
253+		return false;
254+	}
255+	if (ioctl(wsdisplay.fd, WSDISPLAYIO_GMODE, &wsdisplay.original_mode) == 0) {
256+		wsdisplay.mode_saved = true;
257+	}
258+	if (ioctl(wsdisplay.fd, WSDISPLAYIO_SMODE, &mode) < 0) {
259+		ERROR("Could not put wsdisplay device %s in dumb framebuffer mode\n",
260+		      path);
261+		goto error;
262+	}
263+	if (!query_framebuffer(fb)) {
264+		goto error;
265+	}
266+	wsdisplay.memory = mmap(NULL, wsdisplay.memory_length,
267+	                        PROT_READ | PROT_WRITE, MAP_SHARED,
268+	                        wsdisplay.fd, 0);
269+	if (wsdisplay.memory == MAP_FAILED) {
270+		wsdisplay.memory = NULL;
271+		ERROR("Could not map wsdisplay device %s (type %u): %s\n", path,
272+		      wsdisplay.type, strerror(errno));
273+		goto error;
274+	}
275+	return true;
276+
277+error:
278+	if (wsdisplay.mode_saved) {
279+		(void)ioctl(wsdisplay.fd, WSDISPLAYIO_SMODE,
280+		            &wsdisplay.original_mode);
281+	}
282+	close(wsdisplay.fd);
283+	wsdisplay.fd = -1;
284+	return false;
285+}
286+
287+void
288+framebuffer_finalize(struct swc_fb *fb)
289+{
290+	(void)fb;
291+	munmap(wsdisplay.memory, wsdisplay.memory_length);
292+	if (wsdisplay.mode_saved) {
293+		(void)ioctl(wsdisplay.fd, WSDISPLAYIO_SMODE,
294+		            &wsdisplay.original_mode);
295+	}
296+	close(wsdisplay.fd);
297+}
298+
299+static uint32_t
300+channel(uint32_t value, struct fb_channel field)
301+{
302+	uint32_t maximum = (1u << field.length) - 1;
303+
304+	return (((uint32_t)value * maximum + 127) / 255) << field.offset;
305+}
306+
307+static uint32_t
308+native_pixel(uint32_t pixel)
309+{
310+	return channel((pixel >> 16) & 0xff, wsdisplay.red) |
311+	       channel((pixel >> 8) & 0xff, wsdisplay.green) |
312+	       channel(pixel & 0xff, wsdisplay.blue);
313+}
314+
315+static void
316+store_pixel(uint8_t *destination, uint32_t pixel)
317+{
318+	uint32_t native = native_pixel(pixel);
319+
320+	if (wsdisplay.bits_per_pixel == 24) {
321+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
322+		destination[0] = (uint8_t)(native >> 16);
323+		destination[1] = (uint8_t)(native >> 8);
324+		destination[2] = (uint8_t)native;
325+#else
326+		destination[0] = (uint8_t)native;
327+		destination[1] = (uint8_t)(native >> 8);
328+		destination[2] = (uint8_t)(native >> 16);
329+#endif
330+	} else {
331+		memcpy(destination, &native, (wsdisplay.bits_per_pixel + 7) / 8);
332+	}
333+}
334+
335+bool
336+framebuffer_present(struct swc_fb *fb)
337+{
338+	uint32_t bytes = (wsdisplay.bits_per_pixel + 7) / 8;
339+	uint32_t x, y;
340+
341+	for (y = 0; y < fb->height; ++y) {
342+		uint32_t *source = (uint32_t *)((uint8_t *)fb->pixels +
343+		                                (size_t)y * fb->pitch);
344+		uint8_t *destination = (uint8_t *)wsdisplay.memory +
345+		                       wsdisplay.memory_offset +
346+		                       (size_t)y * wsdisplay.pitch;
347+		for (x = 0; x < fb->width; ++x) {
348+			store_pixel(destination + (size_t)x * bytes, source[x]);
349+		}
350+	}
351+	return true;
352+}
353+
354+const char *
355+framebuffer_name(void)
356+{
357+	return "wsdisplay-0";
358+}
+2, -1
 1@@ -43,7 +43,8 @@ libswc_src = files(
 2   xwayland.found() ? ['xserver.c', 'xwm.c'] : [],  
 3 
 4   video_backend == 'drm' ? ['dmabuf.c', 'drm.c', 'plane.c'] : [],
 5-  video_backend == 'fb' ? ['fb.c', 'fb_fbdev.c'] : [],
 6+  framebuffer_backend == 'fbdev' ? ['fb.c', 'fb_fbdev.c'] : [],
 7+  framebuffer_backend == 'wsdisplay' ? ['fb.c', 'fb_wsdisplay.c'] : [],
 8 )
 9 libswc_src += [cursor, launch_protocol]
10 
+31, -6
 1@@ -38,19 +38,43 @@ requires_private = [
 2 ]
 3 
 4 video_backend = get_option('video')
 5+framebuffer_backend = get_option('fb')
 6 drm = disabler()
 7 if video_backend == 'drm'
 8+  if framebuffer_backend != 'auto'
 9+    error('-Dfb applies only when -Dvideo=fb')
10+  endif
11   drm = dependency('libdrm')
12   requires_private += drm
13   add_project_arguments('-DENABLE_DRM=1', language: 'c')
14 elif video_backend == 'fb'
15-  if os != 'linux'
16-    error('The fb video backend is currently supported only on Linux')
17+  if framebuffer_backend == 'auto'
18+    if os == 'linux'
19+      framebuffer_backend = 'fbdev'
20+    elif os == 'netbsd' or os == 'openbsd'
21+      framebuffer_backend = 'wsdisplay'
22+    else
23+      error('Failed to determine framebuffer interface for ' + os)
24+    endif
25+  endif
26+
27+  if framebuffer_backend == 'fbdev'
28+    if os != 'linux'
29+      error('The fbdev framebuffer interface is supported only on Linux')
30+    endif
31+    add_project_arguments([
32+      '-DENABLE_FBDEV=1',
33+      '-DFBDEV_DEVICE="@0@"'.format(get_option('fbdev-device')),
34+    ], language: 'c')
35+  elif framebuffer_backend == 'wsdisplay'
36+    if os != 'netbsd' and os != 'openbsd'
37+      error('The wsdisplay framebuffer interface is supported only on NetBSD and OpenBSD')
38+    endif
39+    add_project_arguments([
40+      '-DENABLE_WSDISPLAY=1',
41+      '-DWSDISPLAY_DEVICE="@0@"'.format(get_option('wsdisplay-device')),
42+    ], language: 'c')
43   endif
44-  add_project_arguments([
45-    '-DENABLE_FBDEV=1',
46-    '-DFBDEV_DEVICE="@0@"'.format(get_option('fbdev-device')),
47-  ], language: 'c')
48 endif
49 
50 if video_backend != 'drm' and get_option('xwayland').enabled()
51@@ -114,4 +138,5 @@ summary({
52   'Xwayland support': xwayland.found(),
53   'Input backend': input_backend,
54   'Video backend': video_backend,
55+  'Framebuffer interface': video_backend == 'fb' ? framebuffer_backend : 'n/a',
56 }, bool_yn: true)
+2, -0
 1@@ -1,7 +1,9 @@
 2 option('xwayland', type: 'feature', value: 'auto', description: 'Automatic Xwayland handling')
 3 option('input', type: 'combo', value: 'auto', choices: ['auto', 'libinput', 'wscons'], description: 'Input backend to use')
 4 option('video', type: 'combo', value: 'drm', choices: ['drm', 'fb'], description: 'Video backend to use')
 5+option('fb', type: 'combo', value: 'auto', choices: ['auto', 'fbdev', 'wsdisplay'], description: 'Framebuffer interface to use')
 6 option('fbdev-device', type: 'string', value: '/dev/fb0', description: 'Linux framebuffer device')
 7+option('wsdisplay-device', type: 'string', value: '', description: 'wsdisplay framebuffer device (empty uses the VT selected by swc-launch)')
 8 option('udev', type: 'feature', value: 'auto', description: 'libinput libudev support')
 9 option('extra', type: 'boolean', value: true, description: 'Build swc utility applications')
10 option('example', type: 'boolean', value: false, description: 'Build swc example compositor')