这一周都在研究 Kindle 屏幕操作。可能 Kindle 这东西太小众了,玩的人不多,但是感觉可玩性挺高的。

越狱之后获取 Root 权限,可以访问 /dev/fb0 屏幕缓存数据,但是写入屏幕缓存并不是马上刷新上去,墨水屏是需要手动更新屏幕的,这是和 LED 显示器不一样的地方。

网上爬资料

  1. 找到一个 这个 ,使用 ioctl 发送控制命令 0x46db 零或是一刷新屏幕,目测应该是旧版的设备,我这边测试不可以。作者对应给了相应的代码
  2. 新的设备也是使用 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

刷新标志

flagnote
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_modehist_gray_waveform_mode,表示黑白图像(2bit)和灰度图像(8bit)各自的刷新模式。当前面的波形模式设定为 WAVEFORM_MODE_AUTO的时候,这两个参数会生效,如果是其他模式不会生效。

用户空间调用

  1. 查询 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 界面,还有输入设备驱动的是他手写的。

https://github.com/baskerville/plato

另外一个项目,操作常见阅读器的屏幕的工具,如果想要自己写工具,相关代码都可以从这里找到。

https://github.com/NiLuJe/FBInk

Kindle 用的是飞思卡尔芯片,屏幕驱动部分可以查看芯片方的开发手册:i.MX Linux Reference Manual