Kindle 屏幕刷新控制
这一周都在研究 Kindle 屏幕操作。可能 Kindle 这东西太小众了,玩的人不多,但是感觉可玩性挺高的。
越狱之后获取 Root 权限,可以访问 /dev/fb0
屏幕缓存数据,但是写入屏幕缓存并不是马上刷新上去,墨水屏是需要手动更新屏幕的,这是和 LED 显示器不一样的地方。
网上爬资料
- 找到一个 这个 ,使用 ioctl 发送控制命令 0x46db 零或是一刷新屏幕,目测应该是旧版的设备,我这边测试不可以。作者对应给了相应的代码。
- 新的设备也是使用 ioctl 控制刷新,参数为
mxcfb_update_data
,能更加精细的控制屏幕刷新。
// linux/mxcfb.h
struct mxcfb_alt_buffer_data {
__u32 phys_addr;
__u32 width; /* width of entire buffer */
__u32 height; /* height of entire buffer */
struct mxcfb_rect alt_update_region; /* region within buffer to update */
};
struct mxcfb_update_data {
struct mxcfb_rect update_region;
__u32 waveform_mode;
__u32 update_mode;
__u32 update_marker;
__u32 hist_bw_waveform_mode;
__u32 hist_gray_waveform_mode;
int temp;
unsigned int flags;
struct mxcfb_alt_buffer_data alt_buffer_data;
};
struct mxcfb_rect {
__u32 top;
__u32 left;
__u32 width;
__u32 height;
};
更新温度
刷新屏幕的时候,还有一个温度参数,我不懂为啥刷新屏幕需要一个温度参数,但是这个参数确实就是温度,单位是摄氏度。
#define TEMP_USE_AMBIENT 0x1000
#define TEMP_USE_AUTO 0x1001
更新模式
更新模式有两种,局部刷新、全局刷新。如果是局部更新,这里应该还存在另外一个缓存,然后通过对比判断某个像素要不要更新。
#define UPDATE_MODE_PARTIAL 0x0
#define UPDATE_MODE_FULL 0x1
刷新标志
flag | note |
---|---|
EPDC_FLAG_ENABLE_INVERSION - 0x01 | 颜色反转 |
EPDC_FLAG_FORCE_MONOCHROME - 0x02 | 黑白模式(二值化) |
EPDC_FLAG_USE_ALT_BUFFER - 0x100 | 使用备用缓存更新 |
EPDC_FLAG_TEST_COLLISION - 0x200 | 碰撞测试模式 |
EPDC_FLAG_USE_ALT_BUFFER
添加这个标志的时候,会使用struct mxcfb_alt_buffer_data
这个参数里面的内容更新屏幕,不是从 framebuffer 缓冲区读取,在某些情况下可以省略一次内存拷贝吧。
EPDC_FLAG_FORCE_MONOCHROME
这个在阅读文字的时候启用吧,如果不加入这个标签图片是有灰度变化的,如果加入这个标签屏幕直接刷新成黑白模式(二值化)。
EPDC_FLAG_TEST_COLLISION
碰撞测试我不知道是什么意思,不过上有一个update_marker
参数,在碰撞测试的时候需要用到,平时刷新屏幕维持一个不断累加的计数器就可以。
波形模式
去翻 Kindle 内核代码找到下面波形模式,感觉就是刷新屏幕的过程:
/* Supported waveform modes */
#define WAVEFORM_MODE_INIT 0x0 /* Screen goes to white (clears) */
#define WAVEFORM_MODE_DU 0x1 /* Grey->white/grey->black */
#define WAVEFORM_MODE_GC16 0x2 /* High fidelity (flashing) */
#define WAVEFORM_MODE_GC4 WAVEFORM_MODE_GC16 /* For compatibility */
#define WAVEFORM_MODE_GC16_FAST 0x3 /* Medium fidelity */
#define WAVEFORM_MODE_A2 0x4 /* Faster but even lower fidelity */
#define WAVEFORM_MODE_GL16 0x5 /* High fidelity from white transition */
#define WAVEFORM_MODE_GL16_FAST 0x6 /* Medium fidelity from white transition */
#define WAVEFORM_MODE_DU4 0x7 /* Medium fidelity 4 level of gray direct update */
#define WAVEFORM_MODE_REAGL 0x8 /* Ghost compensation waveform */
#define WAVEFORM_MODE_REAGLD 0x9 /* Ghost compensation waveform with dithering */
#define WAVEFORM_MODE_GL4 0xA /* 2-bit from white transition */
#define WAVEFORM_MODE_GL16_INV 0xB /* High fidelity for black transition */
#define WAVEFORM_MODE_AUTO 257
其中 kindle 内核代码比起 i.MX Linux 手册上的实例多出了两个参数,分别是:hist_bw_waveform_mode
和hist_gray_waveform_mode
,表示黑白图像(2bit)和灰度图像(8bit)各自的刷新模式。当前面的波形模式设定为 WAVEFORM_MODE_AUTO
的时候,这两个参数会生效,如果是其他模式不会生效。
用户空间调用
- 查询 ioctl 用法,这个函数签名为,三个点的不定参数看得我有点出戏,但是 C 语言表示不定参数就只是三个点。
int ioctl(int fd, unsigned long request, ...);
注意 long
类型在PC端64bit平台长度是 8 字节,在Kindle这边是 4 字节,这个在写代码的时候也踩过这个坑。然后这个 request
以小端模式拆分成 4 个字节,如下面表格展示。
31 - 30 29 - 16 15 - 8 7 - 0
+-------------+-----------+-------------+---------+
| Direction | Data Size | Magic | Command |
| (2) | (14) | (8) | (8) |
+-------------+-----------+-------------+---------+
- Magic 参数固定是 0x46
- Command 刷新屏幕是 0x2e
- Size 是
mxcfb_update_data
结构体大小 - Direction 两个比特用来区分读和写
根据上面内容,我简单编写了一个函数用来刷新屏幕,用起来好像没有问题:
const IO_R:libc::c_int = 1 << 31;
const IO_W:libc::c_int = 1 << 30;
unsafe fn kindle_refresh(fd:libc::c_int, data:&MxcfbUpdateData) -> c_int {
let data = data as *const _ as *const libc::c_void;
let size:libc::c_int = (size_of::<MxcfbUpdateData>() as c_int) << 16;
let magic:libc::c_int = (b'F' as c_int )<< 8;
let cmd:libc::c_int = 0x2e;
let q:libc::c_int = IO_W | size | magic | cmd;
libc::ioctl(fd,q.try_into().unwrap(),data)
}
Framebuffer 编码解码
知道怎么解码就知道怎么编码,可以先读取 /dev/fb0 数据,然后拿到这个 rawpixels 查看,这个网站可以在线调试这种图片数据,选择宽度高度,颜色配置,就能查看图片。
屏幕也是 ioctl 读取到 fb_var_screeninfo
,这里注意 xres/yres
是屏幕物理尺寸,xres_virtual/yres_virtual
这两个参数才是对应 fb 里面数据存储时候的尺寸,这两个参数乘上颜色深度就能得到整个 framebuffer 大小。
Rust 交叉编译
根据以往的习惯,直接下载armv7l-linux-musleabihf-cross.tgz 并且配置项目 .cargo/config
,项目基本都能顺利编译,但是有一个问题,编译出来的程序加载不了系统的 .so
动态库。
[target.armv7-unknown-linux-musleabihf]
linker = "armv7l-linux-musleabihf-gcc"
阅读器相关的项目
发现一个第三方的 Kobo 阅读器固件,使用 Rust 编写,看了一下代码,太强了,作者自己编写整个 ui 界面,还有输入设备驱动的是他手写的。
另外一个项目,操作常见阅读器的屏幕的工具,如果想要自己写工具,相关代码都可以从这里找到。
Kindle 用的是飞思卡尔芯片,屏幕驱动部分可以查看芯片方的开发手册:i.MX Linux Reference Manual。