/*
* Set up memory address decoders to map entire memory.
* But first move away bootrom map to high memory.
*/
下面我们跟踪 initmips 的执行来观察系统初始化的过程。
void
initmips(unsigned int memsz)
{
memorysize=(memsz&0x0000ffff) << 20;//recover to original size:256M
memorysize_high=((memsz&0xffff0000)>>16) << 20;//0
/*
* Probe clock frequencys so delays will work properly.
*/
tgt_cpufreq();
SBD_DISPLAY("DONE",0);
/*
* Init PMON and debug
*/
cpuinfotab[0] = &DBGREG;
dbginit(NULL);
/*
* Set up exception vectors.
*/
SBD_DISPLAY("BEV1",0);
bcopy(MipsException, (char *)TLB_MISS_EXC_VEC, MipsExceptionEnd -
MipsException);
bcopy(MipsException,
(char *)GEN_EXC_VEC, MipsExceptionEnd
-
第二章 PCI 设备初始化
系统刚上电时,CPU 从 0xbfc0.0000 开始执行。这个地址在 Rom 空间中,
在完成 TLB,Cache,UART 等初始化后,CPU 就将代码拷到 0x8010.0000 开始
的 RAM 空间(这个地址是编译 Pmon 时分配符号_start 的),然后跳转到 initmips(),
开始在内存空间的执行。
执行 initmips 之前,CPU 做的初始化只是初步的,其作用只是为 CPU 在内
存中运行做一些必要的准备。主要的初始化工作:PCI 设备的扫描、空间映射、
资源分配都是 initmips()函数所完成的。
MipsException);
CPU_FlushCache();
CPU_SetSR(0, SR_BOOT_EXC_VEC);
SBD_DISPLAY("BEV0",0);
/* Launch! */
1
main();
}
首 先 是 获 取 CPU 的 时 钟 频 率 , 这 是 tgt_cpufreq() 完 成 的 。 它 定 义 在
tgt_machdep.c 中。主要的方法就是先读取 COP0 中的 count 寄存器,然后延时一
段时间,再读取 count 寄存器。两次的差值乘以 2 就是这段时间内 cpu 的时钟周
期数。另外,CMOS 中有个实时钟,在延时前后读取当前时间该相减,就可以知
道延时的准确时间。从而计算出 cpu 的时钟频率。全局变量 md_cpufreq 记录了
cpu 频率值,md_pipefreq 是流水线的频率,它们的值分别是 500MHZ 和 1000MHZ;
接着调用的是 dbginit(),这是最要的一个函数,几乎所有的初始化代码都
由他直接或间接调用。
构造函数(constructor)的执行。
Dbginit()调用的第一个函数是__init()。这个函数的过程很简单,它就是将所
有的 constructor 的函数执行一遍,建立一些基本的数据结构。在 pmon 中有三类
constructor 函数,它们都是静态函数。
1) 命令处理初始化函数,位于 pmon/cmds 目录下,其名称都叫 init_cmd()。
2) 文 件 系 统 初 始 化 函 数 。pmon/fs 目 录 下 。 函 数 名 称 叫 init_fs() 或 者
init_xxxfs()。
const char *name; //命令的名称
const char *opts; //参数
const Optdesc *optdesc; //命令参数的 option
const char *desc; //命令描述
int (*func) __P((int, char *[])); //处理函数
int minac; //最小参数个数
int maxac; //最大参数个数
int flag;
3) 可执行文件类型初始化。在 pmon/loader 目录下。函数名称叫 init_exec()
Pmon 中定义了大量的命令,每个命令都对应一个 Cmd 类型的结构。该结构
的含义如下。
typedef struct Cmd {
#define CMD_REPEAT 1 /* Command is repeatable */
#define CMD_HIDE 2
#define CMD_ALIAS
} Cmd;
CmdTable 是一个指针数组。对每一个命令,CmdTable 中都有一个指针指向
它对应的 Cmd 结构。Init_cmd()所作的就是将各个 Cmd 结构的地址填入 CmdTable
中。
/* Command is hidden */
4
/* Alias for another command name */
Pmon 中所支持的每一个文件系统都有一个相应的数据结构来表示。对于磁
盘文件系统,这个结构是 DiskFileSystem;对于其他文件系统,这个结构叫做
FileSystem。这两个结构的成员主要是一些函数指针,分别指向文件系统的 open,
read,write,ioctl,lseek 及 close 函数。
2
文件系统初始化就是代表各个文件系统的数据结构插入到相应链表。对于磁
盘文件系统,链表的头指针式 DiskFileSystems。对于其他的文件系统,头指针是
FileSystems。这样当需要对某个文件操作(包括虚拟文件,如与用户交互的终端
termio)。通过这两个链表可以找到相应的结构,在通过里面的函数指针就可以对
文件进行具体操作了。
char *execname; /*文件类型,如 bin ,elf 等*/
long (*loader) __P((int , char *, int *, int ));/*载入文件类型,并设置执行条
Pmon 可以载入执行几种格式的文件。其中包括 elf 可执行文件,二进制文
件(称为 Raw binary file)。对每一个支持的文件类型,有一个 ExecType 类型的的
结构。这个结构定义如下:
typedef struct ExecType {
件*/
#define EXECFLAGS_NONE
0x0000
#define EXECFLAGS_NOAUTO 0x0001
} ExecType;
由于每种执行文件的格式不一样,因此对它们的载入执行也不一样,函数指
针 loader 就是文件的转载寒暑。对于二进制文件(bin),这个函数是 load_bin。二进
制文件是最简单的,load_bin 所作的只不过就是将执行文件读入到指定地址。Elf
文件比较复杂,它的载入函数是 load_elf()。这个函数比较复杂,但过程还是很
简单的,就是依据 elf 文件头的内容载入各个程序段的内容,并返回可执行文件
开始执行的地址。
int flags;
SLIST_ENTRY(ExecType)
/* Don't auto load */
i_next;
环境初始化
envint()设置所有的环境变量。最多可以设置 64 个环境变量。每个环境变
量对应一个 envpair 结构。Envvar 是一个 envpair 类型的数组。envinit()就是根据
标准环境变量的值(数组 stdenvtab)来初始化 envvar 数组。更改环境变量的值可以
改变 pmon 中命令的行为以及一些系统参数。
设备初始化
设备初始化主要就是 PCI 设备的初始化。这是由 tgt_devinit()完成的。
tgt_devinit 又调用了_pci_businit()。
PCI 设备初始化分为两步,第一步是北桥初始化。第二步是设备初始化。在
Pmon 中,每个 PCI 设备都对应一个 pci_device 结构(包括 pci-pci 桥);每个 pci
总线都对应一个 pci_bus 结构;
pa; //设备的一些信息,如 id,class,中断线
struct pci_attach_args
struct pci_device {
//
min_gnt;
max_lat;
int_line;
stat;
unsigned char
unsigned char
unsigned char
pcireg_t
3
u_int8_t
intr_routing[4];
struct pci_bridge bridge;
struct pci_bus
*pcibus;
struct pci_device *next;
struct pci_device *parent;
};
Min_gnt,Max_lat 是 PCI 设备和时间相关的参数。Min_gnt 说明在 33Mhz 时
钟频率下,一个 burst period 所要的时间。而 Max_lat 说明了设备访问 PCI 总线
的频率。从它们的定义可以看出这两个参数和设备的带宽密切相关。
Stat 则说明了设备所处的状态(是否可用,是否可以作为主设备等)。
由于所有的设备都连接在总线上,而总线又总是通过 pci 桥联在系统中,因
此结构中还有一个 pci_bridge 结构 bridge。如果该设备本身是一个 pci 桥,则 bridge
代表了它自身。
Pcibus 指向设备所在的总线。所有的 pci 设备通过 next 指针形成一个链表。
最后,parent 指向该设备的父设备。
/* next bus pointer */
struct pci_bus {
};
可以看到 pci_bus 中很多成员和 pci_device 相同,这是因为 pci_bus 结构的信
struct pci_bus *next;
u_int8_t min_gnt; /* largest min grant */
u_int8_t max_lat; /* smallest max latency */
u_int8_t devsel; /* slowest devsel */
u_int8_t fast_b2b; /* support fast b2b */
u_int8_t prefetch; /* support prefetch */
u_int8_t freq66; /* support 66MHz */
u_int8_t width64; /* 64 bit bus */
u_int8_t bus;
u_int8_t ndev; /* # devices on bus */
u_int8_t def_ltim; /* default ltim counter */
u_int8_t max_ltim; /* maximum ltim counter */
int32_t
bandwidth; /* # of .25us ticks/sec @ 33MHz */
paddr_t minpcimemaddr; /* PCI allocation min mem for bus */
paddr_t
nextpcimemaddr;/* PCI allocation max mem for bus */
paddr_t minpciioaddr;
nextpciioaddr;
paddr_t
pci_mem_base;
paddr_t
paddr_t
pci_io_base;
/* PCI allocation min i/o for bus */
/* PCI allocation max i/o for bus */
息是和它所连接的设备相关的。
对 pci_bus 结 构 要 说 明 的 地 方 是 minpcimemadd , nextpcimemaddr ,
minpciioadd,nextpciioaddr。Pci 设备所要的 memory 空间和 IO 空间都是有限的。
Minpcimemaddr 是 PCI 总线所能分配的最小 memory 地址;minpciioaddr 是能分
配的最小 IO 地址。Pmon 才用的空间分配方法是从高地址到低地址。每次为设
备分配 mem 空间都是从 nextpcimemaddr 开始往下分配,分配 io 空间同样是从
4
nextpciioaddr 开始往下分配。分配的时候要注意不能超过最低可用的地址。
1. 北桥初始化
北桥提供了 CPU 和 PCI 设备相互访问的通道,实现了 CPU 空间和 PCI 空间
的映射。_pci_hwinit() 具体地完成了这个工作。
所有的 pci 设备通过总线和 pci 桥联成一个树状结构。北桥 Bonito 是这棵树
的根,与北桥直接相连的总线就是 pcibus0。_pci_hwinit()的工作就是建立北
桥和 pcibus0 的数据结构,同时进行 CPU 和 PCI 的地址映射。
if (!initialise) {
return(0);
}
/*pcireg_t stat;*/
struct pci_device *pd;
struct pci_bus *pb;
int initialise;
bus_space_tag_t iot;
bus_space_tag_t memt;
int
_pci_hwinit(initialise, iot, memt)
{
/*
* PCI Bus 0
*/
pd = pmalloc(sizeof(struct pci_device));
pb = pmalloc(sizeof(struct pci_bus));
if(pd == NULL || pb == NULL) {
}
>bus_base);
5
pci_local_mem_pci_base = PCI_LOCAL_MEM_PCI_BASE;
/*
* Allocate and initialize PCI bus heads.
*/
printf("pci: can't alloc memory. pci not initialized\n");
return(-1);
pd->pa.pa_flags
=
PCI_FLAGS_IO_ENABLED
|
PCI_FLAGS_MEM_ENABLED;
pd->pa.pa_iot = pmalloc(sizeof(bus_space_tag_t));
pd->pa.pa_iot->bus_reverse = 1;
pd->pa.pa_iot->bus_base = BONITO_PCIIO_BASE_VA;
//printf("pd->pa.pa_iot=%p,bus_base=0x%x\n",pd->pa.pa_iot,pd->pa.pa_iot-
pd->pa.pa_memt = pmalloc(sizeof(bus_space_tag_t));
pd->pa.pa_memt->bus_reverse = 1;
pd->pa.pa_memt->bus_base = PCI_LOCAL_MEM_PCI_BASE;
pd->pa.pa_dmat = &bus_dmamap_tag;
pd->bridge.secbus = pb;
_pci_head = pd;
从 上 面 可 以 看 到 , 北 桥 的 pa.pa_flags 被 设 置 成 IO_ENABLED 和
MEM_ENABLED,这就表示可以进行 IO 和内存访问。
Pa.pa_iot->bus_base 被设置成 BONITO_PCI_IO_BASE_VA,这个值被定义为
0xbfd0.0000,它在 CPU 的 Kseg1 区间,因此是不经过 TLB 转换的。它的物理地
址是 0x1fd0.0000。Bontio 规范中规定这个地址开始的1M 空间是 PCI 的 IO 空间。
一个 pci 桥的配置体内有三个域和总线相关。分别是上游总线(Primary bus),
下游总线(Secondary bus)和下级总线(Subordinate bus)。上游总线是和 PCI 桥相连
的离CPU较近的总线。下游总线是离 CPU 较远的总线。下级总线则是和 PCI
相连的总线号最大的总线。
对于北桥来说,是没有上游总线的,而他的下游总线就是 pcibus0,因此
pd->bridge.secbus 赋值为 pb。
pb->nextpcimemaddr=
pb->minpcimemaddr = PCI_MEM_SPACE_PCI_BASE+0x01000000;
PCI_MEM_SPACE_PCI_BASE+BONITO_PCILO_SIZE;
pb->minpciioaddr = PCI_IO_SPACE_BASE+0x000a000;
pb->nextpciioaddr =PCI_IO_SPACE_BASE+ BONITO_PCIIO_SIZE;
pb->pci_mem_base = BONITO_PCILO_BASE_VA; //对应 256M
pb->pci_io_base = BONITO_PCIIO_BASE_VA;
pb->max_lat = 255;
pb->fast_b2b = 1;
pb->prefetch = 1;
pb->bandwidth = 4000000;
pb->ndev = 1;
_pci_bushead = pb;
_pci_bus[_max_pci_bus++] = pd;
Pcibus 的 minimemaddr 被设置 PCI_MEM_SPACE_PCI_BASE+0x01000000。
而 PCI_MEM_SPACE_PCI_BASE 被定义成0,因此这个值就是 16M.之所以这样
做,是因为 ISA 设备只能使用最低 16M内存空间。为了保持兼容性,这里预留
最低 16M 的空间给 ISA。
PCIbus0 的 nextpcimemadr 被设置 PCI_MEM_SPACE_PCI_BASE +
BONITO_PCILO_SIZE。这个值是 192M。因此 pci 设备可用的空间就是 16M 到
192M。
Pciioaddr 的值是 PCI_IO_SPACE_BASE+0XA000, Nextioaddr 的值是
PCI_IO_SPACE_BASE+ BONITO_PCIIO_SIZE,也就是 64k。因此 pci 设备可用
的 IO 空间就是从地址 40k 到 64k。
6
bus_dmamap_tag._dmamap_offs = 0;
/*set Bonito register*/
BONITO_PCIMAP =
BONITO_PCIMAP_WIN(0,
PCI_MEM_SPACE_PCI_BASE+0x00000000) |
BONITO_PCIMAP_WIN(1,
PCI_MEM_SPACE_PCI_BASE+0x04000000) |
BONITO_PCIMAP_WIN(2,
return(1);
PCI_MEM_SPACE_PCI_BASE+0x08000000) |
BONITO_PCIMAP_PCIMAP_2;
BONITO_PCIBASE0 = PCI_LOCAL_MEM_PCI_BASE;
BONITO_PCIBASE1 = PCI_LOCAL_MEM_ISA_BASE;
BONITO_PCIBASE2 = PCI_LOCAL_MEM_PCI_BASE + 0x10000000;
}
为了说明上面代码,先要讲一下 Bonito 中几个寄存器的含义。
为了让 CPU 访问 PCI 空间,需要将 CPU 空间映射到 PCI 空间。在内存空间
256M 上 方 有 三 个 连 续 的 大 小 均 为 64M 的 区 间 , 分 别 称 为 PCI_Lo0,
PCI_Lo1,PCI_Lo2。这三个区间可以被北桥映射到 PCI 以64M对齐的任意位置。
映射的关系通过设置 Bonito 的 PCIMAP 寄存器。该寄存器的格式如下图。
17 12 11 6 5 0
Pci_map2
Pci_lo2
PCIMAP 寄存器
Pci_lo1
Pci_lo0
Pci_lo0,pci_lo1,Pci_lo2 分别是上面所说三个区间的高6位地址(bit31-26),而
Pci_map2 是说明映射到2G以上的空间还是2G 以下的空间。因此上面给
BONITO_PCIMAP 赋值就将 PCI_lo0,PCI_lo1,PCI_lo2 分别映射到了 PCI 空间的
从 0,64M,128M 开始的地址。
另外设备进行 DMA 时,还要提供一种机制将 PCI 地址转换成 CPU 地址。
这 是 通 过 设 置 Base Address Register 。Bonito 中 一 共 有 三 个这 样 的 寄 存 器
pcibas0-2。Pcibas0,pcibase1 都可以映射多达 256M 的空间。具体映射的大小还取
决于 pcimembasecfg 的设置。Pcibase2 是映射 Bonito 的内部寄存器,映射区间为
64k 大小。
寄存器 pcimembasecfg 在 start.S 中设置。我们先看它各个域的含义。
7
Pcimembasecfg 寄存器中的各个域
各个域的含义解释如下:
a)
Io:说明示映射到 IO 空间还是内存空间。IO=0,则是映射到内存空间,
IO=1,则映射的是 IO 空间。
b) Cached:如果设置成1则使用 IOBC,这样可以提高性能
c) Trans/mask 各 5 位。用于决定 pci 地址的 27-23 位。一个 pci 地址,先要
拿掉高三位,然后 mask 取反后和 27-23 位相与,再或上 trans。
在 start.S 中,pcimembasecfg 的 io 位都为 0,cached 为都为 1,因此 pcibase
寄存器映射的是内存空间,并且启用高速缓存。对于 pcibase1,它的
trans=00000b,mask=00000b,这样 pcibase0 映射的空间是 256M,而 pcibase0,
它的 trans=00000b,mask=11111b,这样 pcibase0 映射的空间是 8M,以节省
PCI 空间。
现在再来看上面给 PCIBASE 寄存器赋值的三个语句。PCIBASE0 被赋
值为 PCI_LOCAL_MEM_PCI_BASE(被定义为 0x8000.0000),PCIBASE1
被赋值 PCI_LOCAL_MEM_ISA_BASE(定义为 0x00800000)。这样当 PCI
设备访问地址在 0x8000.0000-0x8FFF.FFFF 时这个地址会先减掉 0x80000000
变成要访问的内存地址。如果设备访问地址在 0x0080.0000-0x008F.FFFF,
经 过 地 址 转 换 后 , 会 变 成 内存 的 低 8M 地 址 。PCIBASE2 被 赋 值 为
PCI_LOCAL_MEM_PCI_BASE + 0x10000000(值为 0x9000.0000)。因此根
据 pci 地址值,能正确的决定要访问的空间。
完成基本的地址映射后,就是初始化 pci 设备了:这包括设备的搜索和
资源的分配。具体的工作在_pci_scan_dev 和_setup_pcibuses 完成的。
_pci_scan_dev 在_pci_businit 中调用。下面是它的调用方式。
for(i = 0, pb = _pci_head; i < pci_roots; i++, pb = pb->next) {
前面已经说过_pci_head 是指向北桥的。Pci_roots 在初始化北桥后被置
//_pci_scan_dev(pb, i, 0, init);
_pci_scan_dev(pb, i, 8, init);//from 8+11, hu mingchang
}
成 1,因此_pci_scan_dev 在_pci_businit 中只调用一次。
再来看_pci_scan_dev
static void
_pci_scan_dev(struct pci_device *dev, int bus, int device, int initialise)
{
{
}
for(; device < 19; device++) //to 19+11, hu mingchang
_pci_query_dev (dev, bus, device, initialise);
}
8