main shrub/neuswc / libswc / fb_fbdev.c
  1#include "fb.h"
  2#include "launch.h"
  3#include "util.h"
  4
  5#include <fcntl.h>
  6#include <linux/fb.h>
  7#include <stdint.h>
  8#include <string.h>
  9#include <sys/ioctl.h>
 10#include <sys/mman.h>
 11#include <unistd.h>
 12
 13struct fb_channel {
 14	uint8_t offset;
 15	uint8_t length;
 16};
 17
 18static struct {
 19	int fd;
 20	void *memory;
 21	size_t memory_length;
 22	size_t memory_offset;
 23	uint32_t pitch;
 24	uint8_t bits_per_pixel;
 25	struct fb_channel red;
 26	struct fb_channel green;
 27	struct fb_channel blue;
 28} fbdev;
 29
 30static struct fb_channel
 31channel_from_fbdev(struct fb_bitfield field)
 32{
 33	return (struct fb_channel) {
 34	    .offset = field.offset,
 35	    .length = field.length,
 36	};
 37}
 38
 39bool
 40framebuffer_initialize(struct swc_fb *fb)
 41{
 42	struct fb_fix_screeninfo fixed;
 43	struct fb_var_screeninfo variable;
 44	size_t visible_offset;
 45
 46	memset(&fbdev, 0, sizeof(fbdev));
 47	fbdev.fd = launch_open_device(FBDEV_DEVICE, O_RDWR | O_CLOEXEC);
 48	if (fbdev.fd < 0) {
 49		ERROR("Could not open framebuffer device %s\n", FBDEV_DEVICE);
 50		return false;
 51	}
 52	if (ioctl(fbdev.fd, FBIOGET_FSCREENINFO, &fixed) < 0 ||
 53	    ioctl(fbdev.fd, FBIOGET_VSCREENINFO, &variable) < 0) {
 54		ERROR("Could not query framebuffer device %s\n", FBDEV_DEVICE);
 55		goto error;
 56	}
 57	if (variable.bits_per_pixel != 16 && variable.bits_per_pixel != 24 &&
 58	    variable.bits_per_pixel != 32) {
 59		ERROR("Unsupported fbdev depth: %u bits per pixel\n",
 60		      variable.bits_per_pixel);
 61		goto error;
 62	}
 63	if (fixed.type != FB_TYPE_PACKED_PIXELS ||
 64	    (fixed.visual != FB_VISUAL_TRUECOLOR &&
 65	     fixed.visual != FB_VISUAL_DIRECTCOLOR)) {
 66		ERROR("Unsupported fbdev framebuffer type or visual\n");
 67		goto error;
 68	}
 69
 70	if (!variable.xres || !variable.yres || variable.xres > UINT16_MAX ||
 71	    variable.yres > UINT16_MAX ||
 72	    variable.red.msb_right || variable.green.msb_right ||
 73	    variable.blue.msb_right ||
 74	    variable.red.length > 16 || variable.green.length > 16 ||
 75	    variable.blue.length > 16 ||
 76	    variable.red.offset + variable.red.length > variable.bits_per_pixel ||
 77	    variable.green.offset + variable.green.length > variable.bits_per_pixel ||
 78	    variable.blue.offset + variable.blue.length > variable.bits_per_pixel) {
 79		ERROR("Unsupported fbdev geometry or channel layout\n");
 80		goto error;
 81	}
 82	fbdev.memory_length = fixed.smem_len;
 83	fbdev.memory = mmap(NULL, fbdev.memory_length, PROT_READ | PROT_WRITE,
 84	                  MAP_SHARED, fbdev.fd, 0);
 85	if (fbdev.memory == MAP_FAILED) {
 86		fbdev.memory = NULL;
 87		ERROR("Could not map framebuffer device %s\n", FBDEV_DEVICE);
 88		goto error;
 89	}
 90
 91	fb->width = variable.xres;
 92	fb->height = variable.yres;
 93	fbdev.pitch = fixed.line_length;
 94	fbdev.bits_per_pixel = variable.bits_per_pixel;
 95	fbdev.red = channel_from_fbdev(variable.red);
 96	fbdev.green = channel_from_fbdev(variable.green);
 97	fbdev.blue = channel_from_fbdev(variable.blue);
 98	visible_offset = (size_t)variable.yoffset * fbdev.pitch +
 99	                 (size_t)variable.xoffset * variable.bits_per_pixel / 8;
100	if (fixed.line_length < (size_t)variable.xres * variable.bits_per_pixel / 8 ||
101	    visible_offset >= fbdev.memory_length ||
102	    (size_t)(variable.yres - 1) * fixed.line_length >
103	        fbdev.memory_length - visible_offset ||
104	    (size_t)variable.xres * variable.bits_per_pixel / 8 >
105	        fbdev.memory_length - visible_offset -
106	            (size_t)(variable.yres - 1) * fixed.line_length) {
107		ERROR("fbdev visible buffer lies outside its mapping\n");
108		goto error;
109	}
110	fbdev.memory_offset = visible_offset;
111	return true;
112
113error:
114	if (fbdev.memory) {
115		munmap(fbdev.memory, fbdev.memory_length);
116	}
117	close(fbdev.fd);
118	fbdev.fd = -1;
119	return false;
120}
121
122void
123framebuffer_finalize(struct swc_fb *fb)
124{
125	(void)fb;
126	munmap(fbdev.memory, fbdev.memory_length);
127	close(fbdev.fd);
128}
129
130static uint32_t
131channel(uint8_t value, struct fb_channel field)
132{
133	uint32_t maximum;
134
135	if (!field.length) {
136		return 0;
137	}
138	maximum = field.length >= 32 ? UINT32_MAX : (1u << field.length) - 1;
139	return (((uint32_t)value * maximum + 127) / 255) << field.offset;
140}
141
142static uint32_t
143native_pixel(uint32_t pixel)
144{
145	return channel(pixel >> 16, fbdev.red) |
146	       channel(pixel >> 8, fbdev.green) |
147	       channel(pixel, fbdev.blue);
148}
149
150static void
151store_pixel(uint8_t *destination, uint32_t pixel)
152{
153	uint32_t native = native_pixel(pixel);
154
155	if (fbdev.bits_per_pixel == 24) {
156#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
157		destination[0] = native >> 16;
158		destination[1] = native >> 8;
159		destination[2] = native;
160#else
161		destination[0] = native;
162		destination[1] = native >> 8;
163		destination[2] = native >> 16;
164#endif
165	} else {
166		memcpy(destination, &native, fbdev.bits_per_pixel / 8);
167	}
168}
169
170bool
171framebuffer_present(struct swc_fb *fb)
172{
173	uint32_t bytes = fbdev.bits_per_pixel / 8;
174	uint32_t x, y;
175
176	for (y = 0; y < fb->height; ++y) {
177		uint32_t *source = (uint32_t *)((uint8_t *)fb->pixels + y * fb->pitch);
178		uint8_t *destination = (uint8_t *)fbdev.memory + fbdev.memory_offset +
179		                       (size_t)y * fbdev.pitch;
180		for (x = 0; x < fb->width; ++x) {
181			store_pixel(destination + (size_t)x * bytes, source[x]);
182		}
183	}
184	return true;
185}
186
187const char *
188framebuffer_name(void)
189{
190	return "fbdev-0";
191}