forked from koreader/koreader-base
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathframebuffer_linux.lua
287 lines (242 loc) · 11.5 KB
/
framebuffer_linux.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
local ffi = require("ffi")
local bit = require("bit")
local BB = require("ffi/blitbuffer")
local C = ffi.C
require("ffi/linux_fb_h")
require("ffi/posix_h")
-- This is common across all mxcfb-like platforms.
local GRAYSCALE_8BIT = 0x1
local GRAYSCALE_8BIT_INVERTED = 0x2
local framebuffer = {
device_node = "/dev/fb0",
fd = -1,
fb_size = nil,
fb_bpp = nil,
fb_rota = nil,
data = nil,
_finfo = nil,
_vinfo = nil,
_forced_rotation = false, -- true if painting, and HW rotation is actually forced now
}
--[[
The raw framebuffer memory is managed through Blitbuffer. When creating the
Blitbuffer, we bind it to a framebuffer memory size of `vinfo.yres *
finfo.line_length` and assuming the FB is laid out in portrait mode by default.
E-ink fb drivers are frequently broken, so we make only minimum assumptions
about correctness of the information reported, namely the following are crucial:
* vinfo.bits_per_pixel: Size of each pixel, for example, 16bits, 32bits, etc.
* finfo.line_length: Size (in bytes) of each row for the framebuffer.
Should be >= `vinfo.xres_virtual * vinfo.bits_per_pixel / 8`.
* vinfo.xres: Number of pixels in one row on physical screen, i.e. physical screen width
* vinfo.yres: Number of rows of the physical screen, i.e. physical screen height
The following don't concern us and we can survive if the values are bogus:
* finfo.smem_len: Size of the actual framebuffer memory provided by the kernel. We'll usually map
less than this (just finfo.line_length * vinfo.yres) to keep things on the safer side.
* vinfo.xres_virtual: Number of pixels in one row on scrollable virtual screen, for fb_pan_display.
Should be `vinfo.xres_virtual` >= `vinfo.xres`.
* vinfo.yres_virtual: Number of pixels in one column on scrollable virtual screen, for fb_pan_display.
Should be `vinfo.yres_virtual` >= `vinfo.yres`.
--]]
function framebuffer:init()
self._finfo = ffi.new("struct fb_fix_screeninfo")
self._vinfo = ffi.new("struct fb_var_screeninfo")
self.fd = C.open(self.device_node, bit.bor(C.O_RDWR, C.O_CLOEXEC))
assert(self.fd ~= -1, "cannot open framebuffer")
self:reinit()
framebuffer.parent.init(self)
-- if force rotation is on with no default, the value from parent is a bogus preset, so ask the OS directly.
if self.forced_rotation and not self.forced_rotation.default then
local r = self:getHWRotation()
local v = self:getCanonicalRotationMode(r)
self.debug("Initializing 'native' rotation mode - OS reported ", r, "which maps to canonical", v)
self.native_rotation_mode = v
self.cur_rotation_mode = v
end
end
-- Frontend driver should override this if they need to apply kludges on vinfo/finfo
function framebuffer:fbinfoOverride(finfo, vinfo)
end
-- Align FB size up to 4KB boundary, as device driver may provide direct mmio handler and not standard physmem mmap that does align kernel side.
-- We always track fb.fb_size unaligned, so as to have correct account of where the screen *really* ends.
local function PAGE_ALIGN(size)
return bit.band(size + 4095, -4096)
end
function framebuffer:reinit()
local finfo = self._finfo
local vinfo = self._vinfo
-- Unmap early, before fb_size gets overriden
self:close(true)
-- Get screen information
assert(C.ioctl(self.fd, C.FBIOGET_FSCREENINFO, finfo) == 0, "cannot get fixed screen info")
assert(C.ioctl(self.fd, C.FBIOGET_VSCREENINFO, vinfo) == 0, "cannot get variable screen info")
-- Apply frontend kludges (color lux, very old eink...)
self:fbinfoOverride(finfo, vinfo)
assert(finfo.type == C.FB_TYPE_PACKED_PIXELS, "video type not supported")
assert(vinfo.xres > 0 and vinfo.yres > 0, "invalid framebuffer resolution")
-- We can forgo all messy fb detection logic by simply assuming only f.line_length and v.yres are valid,
-- because if they aren't, the world would be on fire no matter what sooner or later.
self.fb_size = finfo.line_length * vinfo.yres
local bpp = vinfo.bits_per_pixel
local stride_pixels = bit.lshift(finfo.line_length, 3)
assert(stride_pixels % bpp == 0, "line_length doesn't end at pixel boundary")
stride_pixels = stride_pixels / bpp
self.debug("FB info (post fixup)", {
fb_size = self.fb_size,
xres = vinfo.xres,
yres = vinfo.yres,
xoffset = vinfo.xoffset,
yoffset = vinfo.yoffset,
bpp = bpp,
xres_virtual = vinfo.xres,
yres_virtual = vinfo.yres,
line_length = finfo.line_length,
stride_pixels = stride_pixels,
smem_len = finfo.smem_len,
type = finfo.type,
mmio_len = finfo.mmio_len,
rotate = vinfo.rotate,
width_mm = vinfo.width,
height_mm = vinfo.height,
})
-- Make sure we never try to map a larger memory region than the fb reports
-- @warning Feel free to remove this check if it burns. There are chinese things out there that even happily report smem as 0x1000 and such.
assert(self.fb_size <= finfo.smem_len or finfo.smem <= 0x1000, "computed fb memory region too large")
-- @warning The assumption here is that mapping less than whatever reported (but aligned up to a page size) is always ok to do.
self.data = C.mmap(nil,
PAGE_ALIGN(self.fb_size),
bit.bor(C.PROT_READ, C.PROT_WRITE),
C.MAP_SHARED,
self.fd,
0)
assert(tonumber(ffi.cast("intptr_t", self.data)) ~= C.MAP_FAILED,
"can not mmap() framebuffer, yres or line_length are probably wrong")
self.debug("FB mapped at", self.data, "of", PAGE_ALIGN(self.fb_size), "bytes")
-- @warning Don't ever cache self.bb, as we may replace it at any time later due to HW rotation causing fb reinit.
self.bb = BB.new(vinfo.xres, vinfo.yres, BB["TYPE_BB"..bpp] or BB["TYPE_BBRGB"..bpp], self.data, finfo.line_length, stride_pixels)
-- Make accessing the bitdepth easier, because we might want to know we're running on Kobo's quirky 16bpp mode later...
self.fb_bpp = bpp
-- Same for the current hardware rotation, it's potentially useful info on the Kobo Forma
self.fb_rota = vinfo.rotate
if ffi.string(finfo.id, 7) == "eink_fb" then
-- classic eink framebuffer driver has grayscale values inverted (i.e. 0xF = black, 0 = white)
-- technically a device quirk, but hopefuly generic enough to warrant being here
self.bb:invert()
end
self.screen_size = self:getRawSize()
self.bb:fill(BB.COLOR_WHITE)
end
function framebuffer:setHWNightmode(toggle)
-- On some devices, the fb driver does some funky post-processing with the values passed by userland...
-- This is catastrophically bad when this affects the rotate flag, so, don't do anything on those ;).
if not self.device:canModifyFBInfo() then
return
end
-- Only makes sense @ 8bpp
if self.fb_bpp ~= 8 then
return
end
-- And on devices with the actual capability.
if not self.device:canHWInvert() then
return
end
local vinfo = self._vinfo
-- Just flip the grayscale flag.
-- This shouldn't affect *anything* (layout-wise), which is why we don't touch the mmap or anything else, really.
vinfo.grayscale = toggle and GRAYSCALE_8BIT_INVERTED or GRAYSCALE_8BIT
assert(C.ioctl(self.fd, C.FBIOPUT_VSCREENINFO, vinfo) == 0,
"cannot set variable screen info")
end
function framebuffer:getHWNightmode()
if not self.device:canModifyFBInfo() then
return false
end
-- Only makes sense @ 8bpp
if self.fb_bpp ~= 8 then
return false
end
-- And on devices with the actual capability.
if not self.device:canHWInvert() then
return false
end
local vinfo = self._vinfo
return vinfo.grayscale == GRAYSCALE_8BIT_INVERTED
end
function framebuffer:setHWRotation(mode)
local vinfo = self._vinfo
vinfo.rotate = self.forced_rotation and self.forced_rotation[mode+1] or mode
assert(C.ioctl(self.fd, C.FBIOPUT_VSCREENINFO, vinfo) == 0,
"cannot set variable screen info")
end
function framebuffer:getHWRotation()
local vinfo = self._vinfo
assert(C.ioctl(self.fd, C.FBIOGET_VSCREENINFO, vinfo) == 0,
"cannot get variable screen info")
return vinfo.rotate
end
-- If the device can stomach it (this feature is frontend opt-in) perform hardware rotations
-- FIXME: What about viewports? Do we need to revert back to SW rotation mode?
function framebuffer:setRotationMode(mode)
if not self.forced_rotation then
-- Use SW rotation modes instead
return framebuffer.parent.setRotationMode(self, mode)
end
assert(not self._forced_rotation, "do not flip rotation modes mid-paint")
self.debug("setRotationMode:", mode, "old:", self.cur_rotation_mode)
if mode ~= self.cur_rotation_mode then
-- Requested rotation has changed. Set the HW to it and then reinit FB to update dimensions, line width,
-- as well as prod the driver via new mmap() as some do tie down rotation modes to each mapping.
self.cur_rotation_mode = mode
self:setHWRotation(mode)
-- Remember the screen bb's invert flag, too
local inverse = self.bb:getInverse()
self:reinit()
self.bb:setInverse(inverse)
assert(self.forced_rotation, "reinit/fb hooks shouldn't flip hw rotation flags")
if self.forced_rotation.restore then
self:setHWRotation(self.native_rotation_mode)
end
end
end
-- (before paint hook)
-- If enabled, force desired FB rotation mode in hardware.
-- This is to be done right before we're going to paint into the framebuffer. Because some other process can set
-- different rotation at any time without us knowing, we have to assert our own rot mode every time we're about to paint.
-- Yes this is silly and prone to race conditions, but thats's just how linux FB is - rotation is an OS wide flag. Don't ask.
function framebuffer:beforePaint()
if (not self._forced_rotation) and self.forced_rotation then
self._forced_rotation = true
self:setHWRotation(self.cur_rotation_mode)
end
framebuffer.parent.beforePaint(self)
end
-- (after paint hook)
-- If enabled, restore hardware rotation mode to what OS expects.
-- This must be called after paints and refresh are finished - we don't need to have the HW rotation set now, and other process wishing
-- to steal focus (can happen at any time) from us may assume rotation didn't change. For this occasion we should reset rotation back to
-- "OS wide" one we've seen initially so as to not confuse outside FB users. This isn't always necessary, hence the bool opt-in.
function framebuffer:afterPaint()
if self._forced_rotation and self.forced_rotation and self.forced_rotation.every_paint and self.forced_rotation.restore then
self:setHWRotation(self.native_rotation_mode)
end
self._forced_rotation = false
framebuffer.parent.afterPaint(self)
end
function framebuffer:close(reinit)
if self.bb ~= nil then
self.bb:free()
self.bb = nil
end
if self.data then
C.munmap(self.data, PAGE_ALIGN(self.fb_size))
self.data = nil
end
if not reinit and (self.fd ~= -1) then
if self.forced_rotation then
-- Always restore the OS one so that koreader sees it if restarting
self:setHWRotation(self.native_rotation_mode)
end
C.close(self.fd)
self.fd = -1
end
end
return require("ffi/framebuffer"):extend(framebuffer)