pmon 学习笔记
如果生命有终点
我就陪你到终点
如果生命没终点
我就陪你到永远 ……
本笔记零散写于 2009 年 2 月~5 月,最初基于 2009 年 1 月 20 日
dev.lemote.com 上的 pmon 版本。后期使用的是更新版本,部分代
码和当前版本有误差,不过应该影响不大。
2
目录
扯 3
start.S 4
配置空间的访问 10
superio_init 11
start.S 之内存 15
start.S 之 cache 20
从汇编到 c 25
initmips 27
dbginit 29
_pci_businit 37
cs5536_pci_fixup 45
Init_net 46
Init_net 之 tgt_devconfig 53
tgt_devconfig 之显卡 55
tgt_devconfig 之 config_init 58
tgt_devconfig()之 configure 61
USB 71
回到 init_net 80
DevicesInit 82
open 函数 88
load 内核 98
Termio 105
printf 和 write 108
键盘和键盘事件的响应 114
Ioctl 122
环境变量和 flash 125
GPIO 135
你怎么出来了 -- 图片显示 137
3
扯
一开始大家都不认识,先胡乱抛出几句话扯扯,熟悉熟悉。就是开场白,也有人
说叫序。
pmon 是 cpu 上电后执行的代码,相当于 x86PC 机中的 BIOS,兼有 bootloader
的功能,代码来源于早期 BSD 的内核,到如今已旧貌换新颜,糟蹋得差不多了。
pmon 的二进制代码存放于主板上的一块 512KB 的 flash 芯片上,选择这个容量
是因为够用了,龙芯 2F 允许的最大 boot rom 容量是 1MB。
这块 flash 芯片的地址是确定的,虚拟地址 0xbfc00000,物理地址 0x1fc00000。
cpu 上电后,会在第一时刻从虚拟地址为 0xbfc00000 的读取指令执行,这个和
x86 的 cpu 是一样的,差别的只是地址。
地址差别的一方面是地址值,另一方面是地址类型。
x86 有实模式和保护模式的区别,上电初期为实模式,地址就是物理地址。MIPS
没有这两种模式的区别,而且一开始就是虚拟地址。虚拟地址是虚的,所以必定
需要映射到物理地址。
下面介绍一下 pmon 文件相关的地址问题。
cpu 眼中的地址是虚拟地址,cpu 取指和取数据的地址是物理地址,经过北桥解
释后的地址是总线地址,编译器产生的地址(包括解析了所有引用和重定位的符
号后)为程序地址,也就是程序它自己理解的地址。不同的地址概念间需要有映
射函数来关联。
mips 的虚拟地址到物理地址的映射比 x86 要复杂一些(也更有用些),既有可供
页表之类动态建立映射函数的机制,也有 unmapped cacheed/uncached 这种固
定映射关系的转换方式。bios 代码由于其执行时机的限制,开始阶段只能使用
unmapped uncached 这个段。地址范围是 0xa0000000~0xc0000000,也就是
2.5G 到 3G 的 512MB 空间。这个范围的映射函数为物理地址=程序地址-2.5G。
所有的 32 位 mips 架构的 cpu 都遵循这个约定。
pmon的代码绝大部分是用C写的,只有个别的体系结构相关的代码使用了汇编。
好,先到这儿吧。
如果你对 pmon 感兴趣,想了解它的框架,
如果你对 pmon 感兴趣,想了解各个设备在 bios 中是如何工作的,
如果你对 pmon 感兴趣,想找一个理解 pmon 的 N 天搞定法。
以及不满足上述条件的其他人
就不要在这浪费时间了。本笔记只讲述流程。
好,想看流程或者愿意浪费时间的,Let‘s go。
4
start.S
start.S 在 pmon 中的作用?
核心是把 pmon 的二进制文件复制到内存。并初始化 cache,内存控制器,内
存和南桥的部分信号。这个代码执行之后会执行 c 代码,解压在二进制中压缩
的 bin 文件,跳到解压后的代码继续执行。
由于一上电的时候,内存和内存控制器都处于不确定的状态,所以 cpu 一开始执
行的代码不能在内存中存放,只能把 bios 的代码放在非易失性的介质中(实际
都是 nor 型的 flash)。由于在这类介质中的执行速度比较慢,所以尽可能的,要
把其中的 bios 代码载入到内存执行,这需要先初始化内存控制器。要初始化内
存控制器必须获得内存的 spd 信息,而内存的 spd 信息需要通过 i2c 总线读取,
而 i2c 模块在南桥上,访问要在初始化 smbus 之后,要访问南桥又必须先初始
化北桥,要访问北桥又需要先初始化cpu 本身。这是个一环扣一环的过程。start.S
基本就是围绕这个流程展开的。当然在初始化南桥的 SMB 总线以外,顺便把南
桥相关的其他一些信号也初始了,这些具体的可能要看电路图了。
start.S 是在 cpu 上电之后立即执行的代码。
为什么?就因为它叫 start.S?
MIPS cpu 约定 cpu 执行的第一条指令位于虚拟地址 0xbfc00000,而 pmon 的二
进制代码是以 load -r -f bfc00000 gzrom.bin这个命令烧入 bfc00000这个地址开
始的 flash,硬件布线会使得虚拟地址 0xbfc00000映射到这块flash上。而start.S
就占据了 gzrom.bin 的开头部分。
为什么 gzrom.bin 的开头就是 start.S?
是./zloader/Makefine.inc 中的 L29:
mips-elf-ld -T ld.script -e start -o gzrom ${START} zloader.o 决定的。
start.S 文件是用汇编语言写的,关于龙芯汇编语言的内容请参阅 mips 指令手册
(龙芯没有这方面的公开手册)。
cpu 上电后,内部的寄存器一些可写的寄存器的值是不可预知的,所以代码先设
定 cpu 的部分内部寄存器。在第一条汇编指令前有一行注释如下:
/* NOTE!! Not more that 16 instructions here!!! Right now it's FULL! */
就是说这里已经是 16 条指令了,不能再多了云云。如果你实在想不明白这怎么
有 16 条指令,那么反汇编一把,看到的确是 16 条。
80010000 <_ftext>:
80010000: 40806000 mtc0 zero,c0_sr
80010004: 40806800 mtc0 zero,c0_cause
80010008: 3c080040 lui t0,0x40
8001000c: 40886000 mtc0 t0,c0_sr
80010010: 3c1d8001 lui sp,0x8001
80010014: 67bdc000 daddiu sp,sp,-16384
5
80010018: 3c1c800f lui gp,0x800f
8001001c: 679ce0e0 daddiu gp,gp,-7968
80010020: 04110003 bal 80010030 <uncached>
80010024: 00000000 nop
80010028: 04110183 bal 80010638 <locate>
8001002c: 00000000 nop
80010030 <uncached>:
80010030: 3c01a000 lui at,0xa000
80010034: 03e1f825 or ra,ra,at
80010038: 03e00008 jr ra
8001003c: 00000000 nop
如果你没想明白,还要问那么为什么不能超过 16 条呢?。。。。。。。。。。
我也不知道了。呵呵。
如果你非要铤而走险,在中间多加些指令非让它超过 16 条,那么哥们你太聪明
了,你将发现这只是个玩笑而已。是吧,这帮写代码的有时没正经。
好,言归正传,这个 start.S 共有 2656 行,的确不算太短。
现在我们按照 cpu 的执行顺序对 start.S 代码进行逐一分析。
cpu 执行的第一条指令在 L 213,
mtc0 zero, COP_0_STATUS_REG
mtc0 zero, COP_0_CAUSE_REG
zero 就是寄存器 0($0),和下面的 t0($8)之类相似,都由 asm.h 中 incldue 的
refdef.h 定义的,由预处理去解析。
一开始将状态寄存器和原因寄存器清零主要是禁用所有的中断和异常检测,并使
当前处于内核模式。之后执行:
li t0, SR_BOOT_EXC_VEC /* Exception to Boostrap Location */
mtc0 t0, COP_0_STATUS_REG
以上两句只是设定当前的异常处理模式处于启动模式,启动模式(BEV=1)和正常
模式(BEV=0)的区别是异常处理的入口地址不同,具体如下:
此表来自龙芯 2F 的用户手册,更详细的 status 位描述见手册第五章。