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')