main shrub/neuswc / libswc / fb_wsdisplay.c
  1#include "fb.h"
  2#include "launch.h"
  3#include "launch/protocol.h"
  4#include "util.h"
  5
  6#include <dev/wscons/wsconsio.h>
  7#include <errno.h>
  8#include <fcntl.h>
  9#include <stdint.h>
 10#include <stdlib.h>
 11#include <string.h>
 12#include <sys/ioctl.h>
 13#include <sys/mman.h>
 14#include <unistd.h>
 15
 16struct fb_channel {
 17	uint32_t offset;
 18	uint32_t length;
 19};
 20
 21static struct {
 22	int fd;
 23	void *memory;
 24	size_t memory_length;
 25	size_t memory_offset;
 26	uint32_t pitch;
 27	uint32_t bits_per_pixel;
 28	struct fb_channel red;
 29	struct fb_channel green;
 30	struct fb_channel blue;
 31	u_int type;
 32	u_int original_mode;
 33	bool mode_saved;
 34} wsdisplay;
 35
 36static bool
 37add_overflows_size(size_t a, size_t b, size_t *result)
 38{
 39	if (a > SIZE_MAX - b) {
 40		return true;
 41	}
 42	*result = a + b;
 43	return false;
 44}
 45
 46static bool
 47multiply_overflows_size(size_t a, size_t b, size_t *result)
 48{
 49	if (a && b > SIZE_MAX / a) {
 50		return true;
 51	}
 52	*result = a * b;
 53	return false;
 54}
 55
 56static const char *
 57device_path(void)
 58{
 59	const char *path;
 60
 61	if (WSDISPLAY_DEVICE[0] != '\0') {
 62		return WSDISPLAY_DEVICE;
 63	}
 64	path = getenv(SWC_LAUNCH_TTY_ENV);
 65	if (path && path[0] != '\0') {
 66		return path;
 67	}
 68#ifdef __OpenBSD__
 69	return "/dev/ttyC0";
 70#else
 71	return "/dev/ttyE0";
 72#endif
 73}
 74
 75static bool
 76default_channels(u_int type, uint32_t depth)
 77{
 78	bool blue_first = false;
 79
 80	switch (depth) {
 81	case 15:
 82		wsdisplay.red = (struct fb_channel){10, 5};
 83		wsdisplay.green = (struct fb_channel){5, 5};
 84		wsdisplay.blue = (struct fb_channel){0, 5};
 85		return true;
 86	case 16:
 87		wsdisplay.red = (struct fb_channel){11, 5};
 88		wsdisplay.green = (struct fb_channel){5, 6};
 89		wsdisplay.blue = (struct fb_channel){0, 5};
 90		return true;
 91	case 24:
 92	case 32:
 93#ifdef WSDISPLAY_TYPE_SUN24
 94		blue_first |= type == WSDISPLAY_TYPE_SUN24;
 95#endif
 96#ifdef WSDISPLAY_TYPE_SUNCG12
 97		blue_first |= type == WSDISPLAY_TYPE_SUNCG12;
 98#endif
 99#ifdef WSDISPLAY_TYPE_SUNCG14
100		blue_first |= type == WSDISPLAY_TYPE_SUNCG14;
101#endif
102#ifdef WSDISPLAY_TYPE_SUNTCX
103		blue_first |= type == WSDISPLAY_TYPE_SUNTCX;
104#endif
105#ifdef WSDISPLAY_TYPE_SUNFFB
106		blue_first |= type == WSDISPLAY_TYPE_SUNFFB;
107#endif
108		wsdisplay.red = (struct fb_channel){blue_first ? 0 : 16, 8};
109		wsdisplay.green = (struct fb_channel){8, 8};
110		wsdisplay.blue = (struct fb_channel){blue_first ? 16 : 0, 8};
111		return true;
112	default:
113		return false;
114	}
115}
116
117static bool
118valid_channels(void)
119{
120	struct fb_channel channels[] = {
121	    wsdisplay.red, wsdisplay.green, wsdisplay.blue,
122	};
123	size_t i;
124
125	if (wsdisplay.bits_per_pixel != 15 && wsdisplay.bits_per_pixel != 16 &&
126	    wsdisplay.bits_per_pixel != 24 && wsdisplay.bits_per_pixel != 32) {
127		return false;
128	}
129	for (i = 0; i < sizeof(channels) / sizeof(channels[0]); ++i) {
130		if (!channels[i].length || channels[i].length > 16 ||
131		    channels[i].offset >= wsdisplay.bits_per_pixel ||
132		    channels[i].length >
133		        wsdisplay.bits_per_pixel - channels[i].offset) {
134			return false;
135		}
136	}
137	return true;
138}
139
140static bool
141query_framebuffer(struct swc_fb *fb)
142{
143	size_t last_row_offset, row_bytes, visible_size, visible_end;
144
145	wsdisplay.type = WSDISPLAY_TYPE_UNKNOWN;
146	(void)ioctl(wsdisplay.fd, WSDISPLAYIO_GTYPE, &wsdisplay.type);
147
148#ifdef __NetBSD__
149	{
150		struct wsdisplayio_fbinfo info;
151
152		memset(&info, 0, sizeof(info));
153		if (ioctl(wsdisplay.fd, WSDISPLAYIO_GET_FBINFO, &info) == 0) {
154			if (info.fbi_pixeltype != WSFB_RGB ||
155			    info.fbi_fbsize > SIZE_MAX || info.fbi_fboffset > SIZE_MAX) {
156				ERROR("Unsupported wsdisplay framebuffer format\n");
157				return false;
158			}
159			fb->width = info.fbi_width;
160			fb->height = info.fbi_height;
161			wsdisplay.pitch = info.fbi_stride;
162			wsdisplay.bits_per_pixel = info.fbi_bitsperpixel;
163			wsdisplay.memory_length = info.fbi_fbsize;
164			wsdisplay.memory_offset = info.fbi_fboffset;
165			wsdisplay.red = (struct fb_channel){
166			    info.fbi_subtype.fbi_rgbmasks.red_offset,
167			    info.fbi_subtype.fbi_rgbmasks.red_size,
168			};
169			wsdisplay.green = (struct fb_channel){
170			    info.fbi_subtype.fbi_rgbmasks.green_offset,
171			    info.fbi_subtype.fbi_rgbmasks.green_size,
172			};
173			wsdisplay.blue = (struct fb_channel){
174			    info.fbi_subtype.fbi_rgbmasks.blue_offset,
175			    info.fbi_subtype.fbi_rgbmasks.blue_size,
176			};
177			goto validate;
178		}
179	}
180#endif
181
182	{
183		struct wsdisplay_fbinfo info;
184		u_int linebytes = 0;
185
186		memset(&info, 0, sizeof(info));
187		if (ioctl(wsdisplay.fd, WSDISPLAYIO_GINFO, &info) < 0) {
188			ERROR("WSDISPLAYIO_GINFO failed: %s\n", strerror(errno));
189			return false;
190		}
191		fb->width = info.width;
192		fb->height = info.height;
193		wsdisplay.bits_per_pixel = info.depth;
194#ifdef __OpenBSD__
195		wsdisplay.pitch = info.stride;
196		wsdisplay.memory_offset = info.offset;
197#else
198		wsdisplay.memory_offset = 0;
199#endif
200		if (!wsdisplay.pitch) {
201			if (ioctl(wsdisplay.fd, WSDISPLAYIO_LINEBYTES, &linebytes) < 0) {
202				ERROR("WSDISPLAYIO_LINEBYTES failed: %s\n", strerror(errno));
203				return false;
204			}
205			wsdisplay.pitch = linebytes;
206		}
207		if (!default_channels(wsdisplay.type, info.depth)) {
208			ERROR("Unsupported wsdisplay depth: %u bits per pixel\n", info.depth);
209			return false;
210		}
211		if (multiply_overflows_size(wsdisplay.pitch, fb->height, &visible_size) ||
212		    add_overflows_size(wsdisplay.memory_offset, visible_size,
213		                       &wsdisplay.memory_length)) {
214			ERROR("wsdisplay framebuffer size overflows\n");
215			return false;
216		}
217	}
218
219#ifdef __NetBSD__
220validate:
221#endif
222	if (!fb->width || !fb->height || fb->width > UINT16_MAX ||
223	    fb->height > UINT16_MAX || !wsdisplay.memory_length ||
224	    !valid_channels() ||
225	    multiply_overflows_size(fb->width,
226	                            (wsdisplay.bits_per_pixel + 7) / 8,
227	                            &row_bytes) ||
228	    row_bytes > wsdisplay.pitch ||
229	    multiply_overflows_size(fb->height - 1, wsdisplay.pitch,
230	                            &last_row_offset) ||
231	    add_overflows_size(last_row_offset, row_bytes, &visible_size) ||
232	    add_overflows_size(wsdisplay.memory_offset, visible_size,
233	                       &visible_end) ||
234	    visible_end > wsdisplay.memory_length) {
235		ERROR("Unsupported wsdisplay geometry or channel layout\n");
236		return false;
237	}
238	return true;
239}
240
241bool
242framebuffer_initialize(struct swc_fb *fb)
243{
244	const char *path = device_path();
245	u_int mode = WSDISPLAYIO_MODE_DUMBFB;
246
247	memset(&wsdisplay, 0, sizeof(wsdisplay));
248	wsdisplay.fd = -1;
249	wsdisplay.fd = launch_open_device(path, O_RDWR | O_CLOEXEC);
250	if (wsdisplay.fd < 0) {
251		ERROR("Could not open wsdisplay device %s\n", path);
252		return false;
253	}
254	if (ioctl(wsdisplay.fd, WSDISPLAYIO_GMODE, &wsdisplay.original_mode) == 0) {
255		wsdisplay.mode_saved = true;
256	}
257	if (ioctl(wsdisplay.fd, WSDISPLAYIO_SMODE, &mode) < 0) {
258		ERROR("Could not put wsdisplay device %s in dumb framebuffer mode\n",
259		      path);
260		goto error;
261	}
262	if (!query_framebuffer(fb)) {
263		goto error;
264	}
265	wsdisplay.memory = mmap(NULL, wsdisplay.memory_length,
266	                        PROT_READ | PROT_WRITE, MAP_SHARED,
267	                        wsdisplay.fd, 0);
268	if (wsdisplay.memory == MAP_FAILED) {
269		wsdisplay.memory = NULL;
270		ERROR("Could not map wsdisplay device %s (type %u): %s\n", path,
271		      wsdisplay.type, strerror(errno));
272		goto error;
273	}
274	return true;
275
276error:
277	if (wsdisplay.mode_saved) {
278		(void)ioctl(wsdisplay.fd, WSDISPLAYIO_SMODE,
279		            &wsdisplay.original_mode);
280	}
281	close(wsdisplay.fd);
282	wsdisplay.fd = -1;
283	return false;
284}
285
286void
287framebuffer_finalize(struct swc_fb *fb)
288{
289	(void)fb;
290	munmap(wsdisplay.memory, wsdisplay.memory_length);
291	if (wsdisplay.mode_saved) {
292		(void)ioctl(wsdisplay.fd, WSDISPLAYIO_SMODE,
293		            &wsdisplay.original_mode);
294	}
295	close(wsdisplay.fd);
296}
297
298static uint32_t
299channel(uint32_t value, struct fb_channel field)
300{
301	uint32_t maximum = (1u << field.length) - 1;
302
303	return (((uint32_t)value * maximum + 127) / 255) << field.offset;
304}
305
306static uint32_t
307native_pixel(uint32_t pixel)
308{
309	return channel((pixel >> 16) & 0xff, wsdisplay.red) |
310	       channel((pixel >> 8) & 0xff, wsdisplay.green) |
311	       channel(pixel & 0xff, wsdisplay.blue);
312}
313
314static void
315store_pixel(uint8_t *destination, uint32_t pixel)
316{
317	uint32_t native = native_pixel(pixel);
318
319	if (wsdisplay.bits_per_pixel == 24) {
320#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
321		destination[0] = (uint8_t)(native >> 16);
322		destination[1] = (uint8_t)(native >> 8);
323		destination[2] = (uint8_t)native;
324#else
325		destination[0] = (uint8_t)native;
326		destination[1] = (uint8_t)(native >> 8);
327		destination[2] = (uint8_t)(native >> 16);
328#endif
329	} else {
330		memcpy(destination, &native, (wsdisplay.bits_per_pixel + 7) / 8);
331	}
332}
333
334bool
335framebuffer_present(struct swc_fb *fb)
336{
337	uint32_t bytes = (wsdisplay.bits_per_pixel + 7) / 8;
338	uint32_t x, y;
339
340	for (y = 0; y < fb->height; ++y) {
341		uint32_t *source = (uint32_t *)((uint8_t *)fb->pixels +
342		                                (size_t)y * fb->pitch);
343		uint8_t *destination = (uint8_t *)wsdisplay.memory +
344		                       wsdisplay.memory_offset +
345		                       (size_t)y * wsdisplay.pitch;
346		for (x = 0; x < fb->width; ++x) {
347			store_pixel(destination + (size_t)x * bytes, source[x]);
348		}
349	}
350	return true;
351}
352
353const char *
354framebuffer_name(void)
355{
356	return "wsdisplay-0";
357}