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}