显示是这套模型里最复杂的一块。QEMU 完全没有 Atmel LCDC 的模型,从寄存器到渲染都得自己写。
先说这东西长什么样,再说踩的坑。
控制器长什么样
XLCDC 的寄存器分两层。
全局寄存器管时序和使能:
| 0x00–0x14 | CFG0–CFG5 | 像素时钟、同步极性、时序参数 |
| 0x20 / 0x24 | EN / DIS | 使能 / 关闭,写哪个位置位或清位 |
| 0x28 | SR | 状态,反映当前的使能状态 |
| 0x2c / 0x38 | IER / ISR | 中断使能与状态 |
图层寄存器每层一块,regs_offset 不同:
| base | 0x60 | 基础层,RGB |
| overlay1 | 0x160 | 叠加层 |
| HEO | 0x360 | 高端叠加,支持 YUV 和缩放 |
每层块里前 7 个是控制寄存器(CHER/CHDR/CHSR/IER/IDR/IMR/ISR),后面是配置字。最关键的一个是 base 层的帧缓冲地址,在 0x78——驱动把 framebuffer 的物理地址写到这里,硬件就该从那儿取像素。
模型怎么实现的
197 行,四块。
第一块,寄存器文件。 一个 uint32_t regs[0x2000/4] 数组兜住整个窗口,读写走默认路径。只有几个寄存器需要特殊处理:
EN(0x20):把写进来的位置进 SR
DIS(0x24):把写进来的位从 SR 清掉
SR(0x28):读的时候直接返回 SR 的值
0x78:记下帧缓冲地址,打印一行日志
ISR(0x38):读即清除,同时撤中断
第二块,渲染。 每次 gfx_update 回调触发时:
if (s->fb_addr) {
size_t row_bytes = s->width * 3;
for (y = 0; y < s->height; y++) {
address_space_read(&address_space_memory,
s->fb_addr + y * row_bytes,
MEMTXATTRS_UNSPECIFIED, s->linebuf, row_bytes);
/* 逐像素转成 surface 的格式 */
}
dpy_gfx_update(s->con, 0, 0, s->width, s->height);
}核心就一句 address_space_read——直接从客户机内存把帧缓冲读出来,按行处理,再写进 QEMU 的 DisplaySurface。
第三块,帧中断。 一个 16 ms 的 QEMUTimer,到点检查 IER 里 SOF 位有没有开,开了就置 ISR、拉中断线。
第四块,控制台注册。 graphic_console_init() 挂一个 QemuConsole,qemu_console_resize() 设成 480×272。
四块拼起来,链路是:驱动配寄存器 → 写帧缓冲地址 → 客户机画像素到内存 → 模型读内存 → 画进 surface → VNC / 截屏看到。
坑一:MachineState 不是 DeviceState
graphic_console_init() 要一个 DeviceState *。我图省事传了 DEVICE(machine),直接崩:
DEVICE: Object 0x… is not an instance of type device
MachineState 继承的是 Object,不是 Device。后来借用已有的 GIC 对象才把控制台注册上。
这个做法不干净,正规做法是把显示控制器做成独立的 QOM 设备。当时先让它跑起来。
坑二:缺时钟
驱动 probe 到一半失败:
atmel-hlcdc e1400000.lcd-controller: failed to get peripheral clock atmel-hlcdc: probe of e1400000.lcd-controller failed with error -2
MFD 层要三个时钟:periph_clk(必需)、sys_clk、slow_clk。设备树里一个都没给,补三个 fixed-clock 上去就好。
坑三:2900 万次轮询
接上之后启动卡死,日志刷到几千万行。统计寄存器访问:
29008845 off=0x0028 2 off=0x040c 2 off=0x0408
全压在 0x28 一个偏移上。 驱动使能之后要等 SR & SYNC 置位,而模型永远返回 0,于是无限等。
修法就是前面说的:让 SR 跟着 EN/DIS 走。
【11_01_寄存器轮询】
调试教训:一开始没给日志限流,2900 万行把 QEMU 拖到超时。改成"前 80 条全打、之后每 200 次打一条计数"之后,问题一眼就看出来——所有访问集中在同一个地址,明显是轮询。
坑四:fbcon 把系统拖死
修完轮询,DRM 起来了:
[drm] Initialized atmel-hlcdc 1.0.0 [drm] fb0: atmel-hlcdcdrmf frame buffer device DRM device successfully registered
然后停在这,98 行,不动了。
中断是好的——35 秒里 ISR 被处理 2000 次,帧中断一直在来。但 info registers 看 PC:
PC -> cpu_v7_do_idle + 0x8 LR -> default_idle_call + 0x20
在睡觉,不是死循环。
最后查出来是 CONFIG_FRAMEBUFFER_CONSOLE=y。fbcon 接管控制台之后跟 fbdev 初始化撞上了。关掉它,只留 CONFIG_FB 和 CONFIG_FB_DEVICE,问题消失。
代价是开机文字不上屏,但 /dev/fb0 有了,这才是我们要的。
坑五:24bpp 不是 32bpp
/dev/fb0 出来之后,参数是:
virtual_size: 480,272 bits_per_pixel: 24 stride: 1440
24bpp,步长 1440(480 × 3)。我一开始按 32bpp 读,画面全错。改成按 3 字节读才对。
画面
往 /dev/fb0 写八条色带:
~ # printf '\\377\\000\\000%.0s' $(seq 1 16320) > /tmp/c.bin ~ # dd if=/tmp/c.bin of=/dev/fb0 bs=1440 seek=0
踩了个小坑:busybox 的 dd 不支持 conv=notrunc,而且 count 只是上限、不会重复输入——输入文件必须是完整的 34 行。
【11_02_客户机帧缓冲】
八条色带,红黄绿青蓝品红白灰,一个不差。
我要赚赏金
