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}