2024年1月1日发(作者:)

第二章 PCI设备初始化

系统刚上电时,CPU从0xbfc0.0000开始执行。这个地址在Rom空间中,在完成TLB,Cache,UART等初始化后,CPU就将代码拷到0x8010.0000开始的RAM空间(这个地址是编译Pmon时分配符号_start的),然后跳转到initmips(),开始在内存空间的执行。

执行initmips之前,CPU做的初始化只是初步的,其作用只是为CPU在内存中运行做一些必要的准备。主要的初始化工作:PCI设备的扫描、空间映射、资源分配都是initmips()函数所完成的。

下面我们跟踪initmips的执行来观察系统初始化的过程。

void

initmips(unsigned int memsz)

{

/*

* Set up memory address decoders to map entire memory.

* But first move away bootrom map to high memory.

*/

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 -

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()。

3) 可执行文件类型初始化。在pmon/loader目录下。函数名称叫init_exec()

Pmon中定义了大量的命令,每个命令都对应一个Cmd类型的结构。该结构的含义如下。

typedef struct Cmd {

const char *name; //命令的名称

const char *opts; //参数

const Optdesc *optdesc; //命令参数的option

const char *desc; //命令描述

int (*func) __P((int, char *[])); //处理函数

int minac; //最小参数个数

int maxac; //最大参数个数

int flag;

#define CMD_REPEAT 1 /* Command is repeatable */

#define CMD_HIDE 2 /* Command is hidden */

#define CMD_ALIAS 4 /* Alias for another command name */

} Cmd;

CmdTable是一个指针数组。对每一个命令,CmdTable中都有一个指针指向它对应的Cmd结构。Init_cmd()所作的就是将各个Cmd结构的地址填入CmdTable中。

Pmon中所支持的每一个文件系统都有一个相应的数据结构来表示。对于磁盘文件系统,这个结构是DiskFileSystem;对于其他文件系统,这个结构叫做FileSystem。这两个结构的成员主要是一些函数指针,分别指向文件系统的open,read,write,ioctl,lseek及close函数。

2

文件系统初始化就是代表各个文件系统的数据结构插入到相应链表。对于磁盘文件系统,链表的头指针式DiskFileSystems。对于其他的文件系统,头指针是FileSystems。这样当需要对某个文件操作(包括虚拟文件,如与用户交互的终端termio)。通过这两个链表可以找到相应的结构,在通过里面的函数指针就可以对文件进行具体操作了。

Pmon可以载入执行几种格式的文件。其中包括elf可执行文件,二进制文件(称为 Raw binary file)。对每一个支持的文件类型,有一个ExecType类型的的结构。这个结构定义如下:

typedef struct ExecType {

char *execname; /*文件类型,如bin ,elf 等*/

long (*loader) __P((int , char *, int *, int ));/*载入文件类型,并设置执行条

件*/

#define EXECFLAGS_NONE 0x0000

#define EXECFLAGS_NOAUTO 0x0001 /* Don't auto load */

int flags;

SLIST_ENTRY(ExecType) i_next;

} ExecType;

由于每种执行文件的格式不一样,因此对它们的载入执行也不一样,函数指针loader就是文件的转载寒暑。对于二进制文件(bin),这个函数是load_bin。二进制文件是最简单的,load_bin所作的只不过就是将执行文件读入到指定地址。Elf文件比较复杂,它的载入函数是load_elf()。这个函数比较复杂,但过程还是很简单的,就是依据elf文件头的内容载入各个程序段的内容,并返回可执行文件开始执行的地址。

 环境初始化

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结构;

struct pci_device {

struct pci_attach_args pa; //设备的一些信息,如id,class,中断线

//

unsigned char min_gnt;

unsigned char max_lat;

unsigned char int_line;

pcireg_t stat;

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指向该设备的父设备。

struct pci_bus {

struct pci_bus *next; /* next bus pointer */

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; /* PCI allocation min i/o for bus */

paddr_t nextpciioaddr; /* PCI allocation max i/o for bus */

paddr_t pci_mem_base;

paddr_t pci_io_base;

};

可以看到pci_bus中很多成员和pci_device相同,这是因为pci_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的地址映射。

int

_pci_hwinit(initialise, iot, memt)

int initialise;

bus_space_tag_t iot;

bus_space_tag_t memt;

{

/*pcireg_t stat;*/

struct pci_device *pd;

struct pci_bus *pb;

if (!initialise) {

return(0);

}

pci_local_mem_pci_base = PCI_LOCAL_MEM_PCI_BASE;

/*

* Allocate and initialize PCI bus heads.

*/

/*

* PCI Bus 0

*/

pd = pmalloc(sizeof(struct pci_device));

pb = pmalloc(sizeof(struct pci_bus));

if(pd == NULL || pb == NULL) {

printf("pci: can't alloc memory. pci not initializedn");

return(-1);

}

pd->_flags = PCI_FLAGS_IO_ENABLED |

PCI_FLAGS_MEM_ENABLED;

pd->_iot = pmalloc(sizeof(bus_space_tag_t));

pd->_iot->bus_reverse = 1;

pd->_iot->bus_base = BONITO_PCIIO_BASE_VA;

//printf("pd->_iot=%p,bus_base=0x%xn",pd->_iot,pd->_iot->bus_base);

5

pd->_memt = pmalloc(sizeof(bus_space_tag_t));

pd->_memt->bus_reverse = 1;

pd->_memt->bus_base = PCI_LOCAL_MEM_PCI_BASE;

pd->_dmat = &bus_dmamap_tag;

pd-> = pb;

_pci_head = pd;

从上面可以看到,北桥的_flags被设置成IO_ENABLED和MEM_ENABLED,这就表示可以进行IO和内存访问。

_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->赋值为pb。

pb->minpcimemaddr = PCI_MEM_SPACE_PCI_BASE+0x01000000;

pb->nextpcimemaddr=

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,

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;

return(1);

}

为了说明上面代码,先要讲一下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 Pci_lo1 Pci_lo0

PCIMAP寄存器

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设备访问地址在时这个地址会先减掉0x80000000变成要访问的内存地址。如果设备访问地址在,经过地址转换后,会变成内存的低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_scan_dev(pb, i, 0, init);

_pci_scan_dev(pb, i, 8, init);//from 8+11, hu mingchang

}

前面已经说过_pci_head是指向北桥的。Pci_roots在初始化北桥后被置成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

因此主要的函数就是_pci_query_dev。为了解释这个函数,先说一下PCI设备的相关知识。

每个PCI设备都有一个256字节的配置头。这个配置头在任何时候都是可以被访问的。配置头的前16个字节对所有设备来说都是一样的。

PCI设备配置头的前16个字节

Vendor ID:厂家标识,有标准组织分配。

Device ID:设备标识,由厂家自己分配。

Class code: 设备类别,class code=0x060400表示pci桥。

Header Type:定义了配置头中其他部分的内容。有些PCI设备是单功能的,还有些设备是多功能的。如果Header Type的bit7=1则表示该设备是多功能设备,否则是单功能设备。如果是普通pci设备则bit6-0=00,如果是pci桥则bit6-0=01;

前面说过,CPU通过访问物理地址0x1fe8.0000 可以访问PCI设备的配置空间。一共有两种配置周期:Type 0和Type 1。配置周期的地址信息有特殊的格式。下面表示了Type0 和Type1配置周期的地址格式。

配置周期的地址

为了读写设备的配置头,程序还需事先设置pcimap_cfg寄存器的值。下面是它的格式:

Pcimap_cfg的格式

Type1指示配置周期的类别。AD16UP是高16位地址。结果是当CPU读写物理地址0x1FE8.0000到的区间时,在PCI地址总线会出现如下的地址;

9

有了上述知识,再来看_pci_conf_readn;这个函数的作用就是读设备的配置头。虽然很简单,但是它在_pci_query_dev等几个主要的函数里都要用到。

pcireg_t _pci_conf_readn(pcitag_t tag, int reg, int width)

{

u_int32_t addr, type;

pcireg_t data;

int bus, device, function;

if ((reg & (width-1)) || reg < 0 || reg >= 0x100) {

if (_pciverbose >= 1)

_pci_tagprintf (tag, "_pci_conf_read: bad reg 0x%xn", reg);

return ~0;

}

_pci_break_tag (tag, &bus, &device, &function);

if (bus == 0) {

/* Type 0 configuration on onboard PCI bus */

if (device > 20 || function > 7)

return ~0; /* device out of range */

addr = (1 << (device+11)) | (function << 8) | reg;

type = 0x00000;

}

else {

/* Type 1 configuration on offboard PCI bus */

if (bus > 255 || device > 31 || function > 7)

return ~0; /* device out of range */

addr = (bus << 16) | (device << 11) | (function << 8) | reg;

type = 0x10000;

}

/* clear aborts */

BONITO_PCICMD |= PCI_STATUS_MASTER_ABORT |

PCI_STATUS_MASTER_TARGET_ABORT;

BONITO_PCIMAP_CFG = (addr >> 16) | type;

data = *(volatile pcireg_t *)

PHYS_TO_UNCACHED (BONITO_PCICFG_BASE | (addr & 0xfffc));

if (BONITO_PCICMD & PCI_STATUS_MASTER_ABORT) {

BONITO_PCICMD |= PCI_STATUS_MASTER_ABORT;

#if 0

10

if (_pciverbose >= 1)

_pci_tagprintf (tag, "_pci_conf_read: reg=%x master abortn", reg);

#endif

return ~0;

}

if (BONITO_PCICMD & PCI_STATUS_MASTER_TARGET_ABORT) {

BONITO_PCICMD |= PCI_STATUS_MASTER_TARGET_ABORT;

if (_pciverbose >= 1)

_pci_tagprintf (tag, "_pci_conf_read: target abortn");

return ~0;

}

return data;

}

可以看到该函数首先从参数tag中分离出bus no,device no和function no,然后根据总线号决定配置周期的类型,形成总线的高16位地址写入pcimap_cfg寄存器。最后就可以访问设配的配置块了。由于访问设备可能会有各种错误,这里主要是master abort和target abort,因此读前要清掉错误信息,读之后还要检查是否出错。

另外,_pci_conf_write是用来设置pci配置头部的,它的过程和_pci_conf_read相类似。这里就不多说了。

_pci_query_dev。

static void

_pci_query_dev (struct pci_device *dev, int bus, int device, int initialise)

{

pcitag_t tag;

pcireg_t id;

pcireg_t misc;

tag = _pci_make_tag(bus, device, 0);

if (!_pci_canscan (tag))

return;

if (_pciverbose >= 2)

_pci_bdfprintf (bus, device, -1, "");

/*读取pci设备的Vendor ID和Device ID。如果放回0或者全1,则说明插槽中没有设备

*/

id = _pci_conf_read(tag, PCI_ID_REG);

if (_pciverbose >= 2) {

11

}

PRINTF ("completedn");

if (id == 0 || id == 0xffffffff) {

return;

}

/*现在确定找到一个设备了,读取它的Header Type,如果Header Type的bit7

*=1,则说明是多功能设备(功能位有3位,最多有8个功能)。

*/

misc = _pci_conf_read(tag, PCI_BHLC_REG);

if (PCI_HDRTYPE_MULTIFN(misc)) {

int function;

for (function = 0; function < 8; function++) {

tag = _pci_make_tag(bus, device, function);

id = _pci_conf_read(tag, PCI_ID_REG);

if (id == 0 || id == 0xffffffff) {

return;

}

_pci_query_dev_func (dev, tag, initialise);

}

}

else {

_pci_query_dev_func (dev, tag, initialise);

}

}

这里主要的函数就是_pci_query_dev_func了。这个函数比较长。因此我们先讲一讲他的主要过程。

1) 分配一个pci_device的数据结构由pd指向它,并初始化(设置bus no,device

no,function no 设备ID等)

2) Pd-〉parent设置成dev(这是调用_query_pci_dev_func的参数,这个dev代表的设备是一个pci桥,设备pd所在总线就挂在它上面)。然后将pd插入pci桥dev的孩子链表中。

3) 获取设备所在总线的pci_bus结构的指针pb(就是pci桥dev的从总线)

4) 设置设备的中断控制信息(函数_pci_setupIntRouting,后面会讲到)

5) 通过清除设备配置头中的Command寄存器的Master_enable, IO_enable,

Mem_Enable位将设备暂时禁用。

6) 根据状态寄存器设置总线pb的相关域(是否支持back to back transaction,是否支持66M时钟等)

7) 读PCI_MINGNT和PCI_MAXLAT,设置pd-〉min_gnt和pd-〉max_lat。

8) 更新pb的min_gnt和max_lat(总线pci_bus结构中min_gnt是它所连设备的min_gnt的最大值,总线的max_lat是所有设备的max_lat的最小值)。前面 12

已经讲过,他们和总线的带宽直接相关。

9) 如果设备的class code=0x060400,则说明该设备是PCI桥。否则执行15)

10) 设置该设备pci_device结构的bridge成员的相关分量,主要就是设置主总线号(primary bus no),从总线号(Secondary bus no),下级总线号(Subordinate bus

no),在pci桥的配置头中还有三个寄存器分别表示这三个总线号,因此还要设置它们。

11) 更新该PCI桥所有祖先PCI桥的subordinate bus no(将它们设置成pd->secbus)

12) 分配一个pci_bus 的数据结构,由pd->指向它。初始化这个数据结构。初始化的方法和_pci_hwinit中类似。

13) 递归调用_pci_scan_dev,搜集该pci桥所有设备的资源请求信息(io空间大小,memory空间大小)。每个pci设备的IO请求信息和mem请求信息都有一个_pci_win来表示。从_pci_scan_dev返回后,所有子设备IO资源请求都链入了pd->e指向的有序单链表。所有子设备的Mem资源请求信息都联入了pd->ce指向的有序单链表。

14) 遍历iospace链表汇总所有的IO请求形成一个pci_win结构。遍历memspace链表汇总所有的memory请求也形成一个pci_win结构。将这两个结构插入当前pci桥的父设备的iospace和memspace链表。自此,当前pci桥及他的所有子设备的信息都已经搜集完毕。

15) 如果设备是IDE存储设备并且ISA的IO空间可用则不需要分配资源。函数_pci_query_dev_func返回。否则往下执行。

16) 现在确定设备是一个普通PCI设备。在PCI设备的配置头中从偏移地址0x10到0x24是6个基地址寄存器。他们代表了设备的IO/Memory资源请求信息。具体是IO请求还是Memory请求取决于寄存器德最低位。0x30处的寄存器代表了设备的ROM空间请求信息。确定地址空间范围的方法是向基地址寄存器中写入全1,然后读出来,无关的位会返回0,有用的位会返回1。比如基地址寄存器返回的值是0xFFF00000.则说明所需要的空间是1M。

内存基地址

Bit0=0表示是内存基地址,bit3是否预取

bit2-1=00:表示32位地址空间

01: 应在1M以下空间分配

10: 表示64位地址空间

11: 保留

13

IO基地址

扩展Rom基地址寄存器

Bit0是地址译码使能位

设置好资源请求信息的pci_win结构并链入父设备PCI桥的memspace和iospace链表后,_pci_query_dev_func就结束返回到_pci_query_dev,最后返回到_pci_businit()。至此PCI设备的信息搜集完毕。现在内存中存在如下数据结构

 Pci设备链表_pci_head,节点类型是pci_device。链表中第一个设备是北桥Bonito。

 Pci总线链表_pci_bushead,节点类型是pci_bus。第一个节点是PCIbus0。

 每个pci桥的子设备形成一个链表。可以通过pci桥的pci_device的bridge成员的child指针访问这个链表。每个pci设备的pci_device的parent指针指向父设备pci桥。

 每个pci桥的对应两个资源请求链表:memspace和iospace。链表中每个节点是桥的子设备memory请求和IO请求。

下一步就是真正的为设备分配资源。这_setup_pcibuses()完成的。

_setup_pcibuses()

static void

_setup_pcibuses(int initialise)

{

struct pci_bus *pb;

struct pci_device *pd;

unsigned int def_ltim, max_ltim;

int i;

SBD_DISPLAY ("PCIS", CHKPNT_PCIS);

for(pb = _pci_bushead; pb != NULL; pb = pb->next) {

if (pb->ndev == 0)

return;

if (initialise) {

/* convert largest minimum grant time to cycle count */

/*XXX 66/33 Mhz */

max_ltim = pb->min_gnt * 33 / 4;

/* now see how much bandwidth is left to distribute */

if (pb->bandwidth <= 0) {

14

if (_pciverbose) {

_pci_bdfprintf (pb->bus, -1, -1,

"WARN: total bandwidth exceededn");

}

def_ltim = 1;

}

else {

/* calculate a fair share for each device */

def_ltim = pb->bandwidth / pb->ndev;

if (def_ltim > pb->max_lat) {

/* would exceed critical time for some device */

def_ltim = pb->max_lat;

}

/* convert to cycle count */

def_ltim = def_ltim * 33 / 4;

}

/* most devices don't implement bottom three bits */

def_ltim = (def_ltim + 7) & ~7;

max_ltim = (max_ltim + 7) & ~7;

pb->def_ltim = MIN (def_ltim, 255);

pb->max_ltim = MIN (MAX (max_ltim, def_ltim), 255);

}

}

SBD_DISPLAY ("PCIR", CHKPNT_PCIR);

_pci_hwreinit ();

/* setup the individual device windows */

SBD_DISPLAY ("PCIW", CHKPNT_PCIW);

for(i = 0, pd = _pci_head; i < pci_roots; i++, pd = pd->next) {

_pci_setup_windows (pd);

}

}

该函数前面部分主要检查总线所接设备的带宽是否超过总线允许带宽(pb->bandwidth是总线允许带宽剪掉它所连设备消耗后的剩余带宽)。至于min_gnt,max_lat前面已经提过,是与设备总线周期和访问总线频度相关的参数。

函数中最主要的部分时后面的循环,它负责为总线上的所有设备分配IO和memory空间。

static void

_pci_setup_windows (struct pci_device *dev)

{

struct pci_win *pm;

15

struct pci_win *next;

struct pci_device *pd;

/*memspace所指的的链表代表的是PCI桥的子设备的Mem空间请求

和下面的iospace链表一样,它们是由_pci_scan_dev搜集的pci设备信息

*/

for(pm = dev->ce; pm != NULL; pm = next) {

pd = pm->device;

next = pm->next;

pm->address = _pci_allocate_mem (dev, pm->size);

if (pm->address == -1) {

_pci_tagprintf (pd->_tag,

"not enough PCI mem space (%d requested)n",

pm->size);

continue;

}

if (_pciverbose >= 2)

_pci_tagprintf (pd->_tag, "mem @%p, %d bytesn",

pm->address, pm->size);

/*如果是设备类型是PCI桥,则它的资源请求实际上是他的子设备的请求,

因此要再下一层为子设备分配资源

*/

if (PCI_ISCLASS(pd->_class,

PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_PCI) &&

(pm->reg == PCI_MEMBASE_1)) {

pcireg_t memory;

/*PCI桥下的所有设备的mem地址空间都要在minpcimemaddr和

Nextpcimemaddr之间*/

pd->->minpcimemaddr = pm->address;

pd->->nextpcimemaddr = pm->address + pm->size;

/*将内存基地址值和上限值写到PCI桥的membase和limit寄存器中*/

memory = (((pm->address+pm->size) >> 16) << 16) |

(pm->address >> 16);

_pci_conf_write(pd->_tag, pm->reg, memory);

} else if (pm->reg != PCI_MAPREG_ROM) {

/* normal memory - expansion rom done below */

/*设置PCI设备的基地址寄存器

*/

pcireg_t base = _pci_conf_read(pd->_tag, pm->reg);

base = pm->address | (base &

~PCI_MAPREG_MEM_ADDR_MASK);

_pci_conf_write(pd->_tag, pm->reg, base);

16

}

}

/* Program expansion rom address base after normal memory base,

to keep DEC ethernet chip happy */

for (pm = dev->ce; pm != NULL; pm = next) {

pd = pm->device;

if (PCI_ISCLASS(pd->_class, PCI_CLASS_DISPLAY,

PCI_SUBCLASS_DISPLAY_VGA))

vga_dev = pd;

#if 0

/*

* If this is the first VGA card we find, set the BIOS rom

* at address c0000 if PCI base address is 0x00000000.

*/

if (pm->reg == PCI_MAPREG_ROM && !have_vga &&

dev->->minpcimemaddr == 0 &&

(PCI_ISCLASS(pd->_class,

PCI_CLASS_PREHISTORIC,

PCI_SUBCLASS_PREHISTORIC_VGA) ||

PCI_ISCLASS(pd->_class,

PCI_CLASS_DISPLAY, PCI_SUBCLASS_DISPLAY_VGA))) {

have_vga = pd->_tag;

pm->address = 0x000c0000; /* XXX PCI MEM @ 0x000 */

}

#endif

if (pm->reg == PCI_MAPREG_ROM) {

/* expansion rom */

if (_pciverbose >= 2)

_pci_tagprintf (pd->_tag, "exp @%p, %d bytesn",

pm->address, pm->size);

_pci_conf_write(pd->_tag, pm->reg, pm->address

PCI_MAPREG_TYPE_ROM);

}

next = pm->next;

dev->ce = next;

pfree(pm);

}

/*iospace所指的链表是pci设备的IO请求信息*/

for(pm = dev->e; pm != NULL; pm = next) {

pd = pm->device;

17

|

next = pm->next;

pm->address = _pci_allocate_io (dev, pm->size);

if (pm->address == -1) {

_pci_tagprintf (pd->_tag,

"not enough PCI io space (%d requested)n",

pm->size);

pfree(pm);

continue;

}

if (_pciverbose >= 2)

_pci_tagprintf (pd->_tag, "i/o @%p, %d bytesn",

pm->address, pm->size);

if (PCI_ISCLASS(pd->_class,

PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_PCI) &&

(pm->reg == PCI_IOBASEL_1)) {

pcireg_t tmp;

pd->->minpciioaddr = pm->address;

pd->->nextpciioaddr = pm->address + pm->size;

/*设置PCI桥配置空间里的iobase和iolimit寄存器*/

tmp = _pci_conf_read(pd->_tag,PCI_IOBASEL_1);

tmp &= 0xffff0000;

tmp |= (pm->address >> 8) & 0xf0;

tmp |= ((pm->address + pm->size) & 0xf000);

_pci_conf_write(pd->_tag,PCI_IOBASEL_1, tmp);

tmp = (pm->address >> 16) & 0xffff;

tmp |= ((pm->address + pm->size) & 0xffff0000);

_pci_conf_write(pd->_tag,PCI_IOBASEH_1, tmp);

}

else {

/*设置PCI设备配置空间里的iobase寄存器*/

_pci_conf_write(pd->_tag, pm->reg, pm->address |

PCI_MAPREG_TYPE_IO);

}

dev->e = next;

pfree(pm);

}

/* Recursive allocate memory for secondary buses */

/*遍历当前pci桥的字设备,如果他们也是一个pci桥,则递归地调用

*_pci_setup_windows来分配资源

18

*/

for(pd = dev->; pd != NULL; pd = pd->next) {

if (PCI_ISCLASS(pd->_class,

PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_PCI)) {

_pci_setup_windows(pd);

}

}

}

_pci_setup_windowns()调用了_pci_allocate_mem和_pci_allocate_io来分配mem和io空间,下面来看这两个函数:

static pcireg_t

_pci_allocate_io(dev, size)

struct pci_device *dev;

vm_size_t size;

{

pcireg_t address;

#ifndef PCI_ALLOC_UPWARDS

/* allocate downwards, then round to size boundary */

address = (dev->->nextpciioaddr - size) &

~(size - 1);

if (address > dev->->nextpciioaddr ||

address < dev->->minpciioaddr) {

return -1;

}

dev->->nextpciioaddr = address;

#else

/* allocate downwards, then round to size boundary */

address = (dev->->minpciioaddr + size) &

~(size - 1);

if (address > dev->->nextpciioaddr ||

address < dev->->minpciioaddr) {

return -1;

}

dev->->minpciioaddr = address;

#endif

return(address);

}

由于程序中没有定义PCI_ALLOC_UPWARDS这个宏,因此编译执行的是前半部分的代码。也就是说空间的分配是从高到低。每次分配都是从地址nextpciioaddr开始,分配完要将nextpciioaddr设成下次分配时的开始地址。

至于_pci_allocate_mem,它的过程和_pci_allocate_io类似,也是从高地址开始分配。

19

初始化完pci设备后,函数pci_businit返回到tgt_devinit(),最后回到dbginit()。在往下就是执行initnet()。下面列出这个函数的主要代码。

void

init_net (int hwok)

{

paraminit ();

/*

* Initialise "virtual memory" maps

*/

vminit();

/*

* Initialise memory allocator

*/

kmeminit();

/*

* Initialize callouts

*/

callout = malloc(sizeof(struct callout) * ncallout, M_TEMP,

M_NOWAIT);

callfree = callout;

for (i = 1; i < ncallout; i++) {

callout[i-1].c_next = &callout[i];

}

if (hwok) {

startrtclock(hz);

}

/*

* Initialise mbufs

*/

mclrefcnt=(char*)malloc(VM_KMEM_SIZE/MCLBYTES,M_MBUF,

M_NOWAIT);

bzero(mclrefcnt, NMBCLUSTERS+CLBYTES/MCLBYTES);

mb_map = kmem_suballoc(kernel_map, (vm_offset_t *)&mbutl, &maxaddr,

NMBCLUSTERS*MCLBYTES, FALSE);

mbinit();

/*

20

}

Paraminit()主要是初始化一些系统参数,比如说允许打开的最大文件数。

函数vminit()则是初始化虚拟内存

void

vminit ()

{

extern int memorysize;

if (!kmem) {

/* grab a chunk at the top of memory */

if (memorysize < VM_KMEM_SIZE * 2) {

panic ("not enough memory for network");

}

memorysize = (memorysize - VM_KMEM_SIZE) & ~PGOFSET;

#ifdef __mips__

if ((u_int32_t)&kmem < (u_int32_t)UNCACHED_MEMORY_ADDR) {

/* if linked for data in kseg0, keep kmem there too */

kmem = (u_char *) PHYS_TO_CACHED (memorysize);

}

else {

kmem = (u_char *) PHYS_TO_UNCACHED (memorysize);

}

#else

kmem = (u_char *)memorysize;

#endif

21

* Initialise network devices and protocols

*/

if (hwok) {

s = splhigh();

tgt_devconfig();

for (pdev = pdevinit; pdev->pdev_attach != NULL; pdev++)

{

if (pdev->pdev_count > 0) {

(*pdev->pdev_attach)(pdev->pdev_count);

}

}

ifinit();

printf("ifinit done.n");

domaininit();

printf("domaininit done.n");

splx(s);

}

}

}

该函数将内存的高512k作为虚拟内存(VM_KMEM_SIZE定义为512*1024,每页的大小是4k)。初始化后kmem指向这块内存的内存起始地址。

虚拟内存中的每一个页面都有一个kmemusage的结构与之对应:

struct kmemusage {

short ku_indx; /* bucket index */

union {

u_short freecnt;/* for small allocations, free pieces in page

*/

u_short pagecnt;/* for large allocations, pages alloced */

} ku_un;

};

对kmemusage的解释如下:每次内存分配时都是按页取整的。但是实际上使用的内存并不需这么多,解决的办法是将内存分成一些2的整次幂的块,称为一个bucket。每个内存bucket最小为16个字节(MINBUCKET=4,2^4=16),最大为2^20字节,也就是1M。同样大小的bucket形成一个链表。这样bucket的定义如下。

struct kmembuckets {

caddr_t kb_next; /* list of free blocks */

caddr_t kb_last; /* last free block */

long kb_calls; /* total calls to allocate this size */

long kb_total; /* total number of blocks allocated */

long kb_totalfree; /* # of free elements in this bucket */

long kb_elmpercl; /* # of elements in this sized allocation */

long kb_highwat; /* high water mark */

long kb_couldfree; /* over high water mark and could free */

};

struct kmembuckets bucket[MINBUCKET + 16];

Kmemusage中的ku_index就表示它所代表的内存区在bucket中的序号。

Kmemusage的分配是在kmeminit中完成的。

void

kmeminit()

{

int npg;

npg = VM_KMEM_SIZE/ NBPG;

kmemusage = (struct kmemusage *) kmem_alloc(kernel_map,

(vsize_t)(npg * sizeof(struct kmemusage)));

kmem_map = kmem_suballoc(kernel_map, (vaddr_t *)&kmembase,

(vaddr_t *)&kmemlimit, (vsize_t)(npg * NBPG), FALSE);

}

这里内存分配的方法是非常简单的。前面讲过kmem指向了可供分配虚存的首地址,而变量kmem_offs则记录了已经分配了多少空间。这样每次分配从 22

&kmem[kmem_offs]开始就可以了,另外每次分配空间的大小都按也大小4k取整。

函数kmem_suballoc()实际上只是将kmembase设置成等于kmem,将kmemlimit设置成等于kmem+ VM_KMEM_SIZE。因此这两个变量代表了可分配虚存空间。

有时候需要在系统到达某个时刻就自动运行某个函数,这就是所谓的callout。Callout是一个用数组实现的静态链表,其大小是在paraminit中设定的,因此也要为它们分配空间。从上面的代码可以看到,这里的内存分配函数是malloc()。这其实是一个宏,展开后,malloc就变成了kern_malloc()。

由于kern_malloc比较大。这里就只以一个例子说明它分配内存的方法。

假设现在要通过malloc分配200个字节的内存空间。由于128<200<256,因此要实际分配的空间是2^8=8B。它在bucket中的序号是8-4=4,现在假设bucket[4]队列中没有空余的块可供使用,因此要通过kmem_alloc来分配一页也就是4KB的空间。这4k的空间可以分为4096/256=16个块。分配时是从地址较高的快开始的。这样kern_malloc会返回第16个块的首地址作为被分配的内存首地址。而bucket[4]的kb_next会指向第15个块,kb_last指向业的首地址也就是第一个块。在每一个块的开头几个字节是一个freelist结构,用来将bucket中的块连成一个链表。这样下次在这个bucket中分配时,分配的地址就是va=bucket->kb_next。同时修正kb_next即可(执行bucket->kb_next=(( struct freelist *)va)->next 即可)。

Kb_next

被分配 。。。。。

的mem

Kb_last

与kern_malloc相对应的是kern_free。

void

free(addr, type) /*free 被定义成kern_free*/

void *addr;

int type;

{

register struct kmembuckets *kbp;

register struct kmemusage *kup;

register struct freelist *freep;

long size;

int s;

23

kup = btokup(addr);

size = 1 << kup->ku_indx;

kbp = &bucket[kup->ku_indx];

s = splimp();

if (size > MAXALLOCSAVE) {

kmem_free(kmem_map, (vaddr_t)addr, ctob(kup->ku_pagecnt));

splx(s);

return;

}

freep = (struct freelist *)addr;

if (kbp->kb_next == NULL)

kbp->kb_next = addr;

else

((struct freelist *)kbp->kb_last)->next = addr;

freep->next = NULL;

kbp->kb_last = addr;

splx(s);

}

kup = btokup(addr),将addr-kmembase再右移12位,取得页面号。这个页面号也是kernelusage数组的下标。前面已经说过kernelusage数组中有一个分量叫做ku_index,它表示了内存块在bucket中位置,同时也表示了该内存块的大小。如果kbp->kb_next为空,则说明该bucket中已经没有空闲内存块,现在被释放的内存块就成为第一个空闲块,因此将kbp->kb_next指向它。如果kbp->kb_next不为空,则把被释放的内存块插入到kbp->kb_last所指内存块后面(kb_last指向的是bucket中最后面的一个空闲块)。最后使kbp->kb_last指向addr所对应的块。

以上说的内存分配和释放都是针对一些核心的数据结构,也就是当文件中定义了_KERNEL才使用它们。在其他场合分配和释放内存都是不定义_KERNEL的。这时使用的就是定义在lib/libc/malloc.c中的malloc和free了。

这里的malloc和free就是通常的堆空间分配和释放。堆的起点是end。而end由gcc定义为bss数据段的结束地址。堆的顶点是heaptop,其初始值为end+65536。从而堆的初始大小为64k。用户可以通过设置环境变量heaptop来改变堆的大小以满足程序的需要。变量allocp1是当前堆顶位置(allocp1以上的空间市空闲的)。

同kern_malloc一样,malloc每次分配内存时并不是刚好所申请的大小。分配是以一个HEADER结构的大小(8个字节)为单位的。

union header {

struct {

union header *ptr;

unsigned size;

24

} s;

ALIGN x;

};

typedef union header HEADER;

一个HEADER结构大小成为一个unit。结构中的size分量表示该空闲块包unit的个数。另外,所有的空闲内存块通过HEADER结构中的ptr指针按地址增加的顺序连接成一个链表。每个内存块前面HEADER结构大小的部分就是做这个用途的。

void *

malloc(size_t nbytes)

{

HEADER *p, *q; /* K&R called q, prevp */

unsigned nunits;

nunits = (nbytes + sizeof (HEADER) - 1) / sizeof (HEADER) + 1;

if ((q = allocp) == NULL) { /* no free list yet */

= allocp = q = &base;

= 0;

}

/*扫描空闲链表,直到找到一个大小满足要求的空闲块*/

for (p = q->;; q = p, p = p->) {

if (p-> >= nunits) { /* big enough */

/*空闲块p的大小刚好满足要求,将p所指的内存块从空闲链表脱链 */

if (p-> == nunits) /* exactly */

q-> = p->;

else { /* allocate tail end */

/*空闲区比索要求的内存大,则从后面分配内存,而空闲链表的结构不

*需要改变*/

p-> -= nunits;

p += p->;

p-> = nunits;

}

/*q指向刚才所分配空闲区前面的那个空闲块,让allocp指向它,下一次分配时,从该位置开始扫描*/

allocp = q;

return ((char *)(p + 1));

}

if (p == allocp)

if ((p = morecore (nunits)) == NULL)

return (NULL);

}

}

空闲区链表有一个头结点base,这是一个静态变量,它本身的不能代表一个空闲区,因此在上面将它的size分量置成0。静态变量allocp指向上一次内存分 25

配的位置。每次内存分配都是从allocp开始扫描空闲队列。

但是如果扫描完空闲队列中都没有发现合适的空闲块,这时候就要从堆空间中分配了。这是由morecore(uint_32 nu)完成的。

static HEADER *

morecore(u_int32_t nu)

{

char *cp;

HEADER *up;

int rnu;

/*分配空间向上取整成1k的整数倍*/

rnu = NALLOC * ((nu + NALLOC - 1) / NALLOC);

/*sbrk返回所分配内存的首址,并调整堆顶值*/

cp = sbrk(rnu * sizeof (HEADER));

if ((int)cp == NULL)

return (NULL);

up = (HEADER *) cp;

up-> = rnu;

/*将up插入到空闲链表*/

free ((char *)(up + 1));

return (allocp);

}

函数free(ap):释放ap所指的内存块,并把它插入空闲链表的合适位置。如果ap所在内存块和他前后的内存块在地址上相邻,则还需要将它们合并成一个快。

void

free(void *ap)

{

HEADER *p, *q;

p = (HEADER *) ap - 1;

/*空闲链表是按地址升序排列的。为p查找合适的插入位置*/

for (q = allocp; !(p > q && p < q->); q = q->)

if (q >= q-> && (p > q || p < q->))

break;

if (p + p-> == q->) {

/*和后面一个空闲块相邻*/

p-> += q->->;

p-> = q->->;

} else

p-> = q->;

if (q + q-> == p) {

/*和前一个空闲块相邻*/

q-> += p->;

26

q-> = p->;

} else

q-> = p;

allocp = q;

}

终端建立和通信

在Pmon的调试过程中,串口是一个重要的工具。因此在系统启动后,便对串口ns16550进行了初始化。

 串口初始化

LEAF(initserial)

# la v0, COM1_BASE_ADDR

la v0, COM3_BASE_ADDR

1:

li v1,

FIFO_ENABLE|FIFO_RCV_RST|FIFO_XMT_RST|FIFO_TRIGGER_4

sb v1, NSREG(NS16550_FIFO)(v0)

li v1, CFCR_DLAB

sb v1, NSREG(NS16550_CFCR)(v0)

li v1, NS16550HZ/(16*CONS_BAUD)

sb v1, NSREG(NS16550_DATA)(v0)

srl v1, 8

sb v1, NSREG(NS16550_IER)(v0)

li v1, CFCR_8BITS

sb v1, NSREG(NS16550_CFCR)(v0)

li v1, MCR_DTR|MCR_RTS

sb v1, NSREG(NS16550_MCR)(v0)

li v1, 0x0

sb v1, NSREG(NS16550_IER)(v0)

# move v1, v0

# la v0, COM2_BASE_ADDR

# bne v0, v1, 1b

# nop

j ra

nop

END(initserial)

COM3_BASE_ADDR是串口的基地址0xBFF003F8。这段代码的主要功能就是设置发送和接收数据的波特率。设置桢格式:字符长度为8bits,1个停止位,无奇偶校验。波特率寄存器是两个8位的寄存器,复用了数据寄存器(Data Register)和中断使能寄存器(IER, Interrupt Enable)。在设置波特率之 27

前先要将CFCR(Line control Register)的bit7置1,表示下面要设置波特率。然后就可以往CFCR和IER中分别写入波特率的低8位和高8位。

 终端通信

在__init函数中执行了所有的constructor函数。终端就是在那个时候建立的。

static void

init_fs()

{

//SBD_DISPLAY ("DEVI", CHKPNT_DEVI);

SBD_DISPLAY ("TTYI", CHKPNT_DEVI);

devinit ();

/*

* Install terminal based file system.

*/

filefs_init(&termfs);

/*

* Create the standard i/o files the proper way

*/

_file[0].valid = 1;

_file[0].fs = &termfs;

_file[1].valid = 1;

_file[1].fs = &termfs;

_file[2].valid = 1;

_file[2].fs = &termfs;

_file[3].valid = 1;

_file[3].fs = &termfs;

_file[4].valid = 1;

_file[4].fs = &termfs;

term_open(0, "/dev/tty0", 0, 0); /* stdin */

term_open(1, "/dev/tty0", 0, 0); /* stdout */

term_open(2, "/dev/tty0", 0, 0); /* stderr */

term_open(3, "/dev/tty2", 0, 0); /* kbdin */

term_open(4, "/dev/tty2", 0, 0); /* vgaout */

}

该函数首先调用devinit()来初始化终端系统的数据结构DevTable。初始化过程是依据ConfigTable数组中的内容进行的。在Bonito版本中Config数组中只有一项内容,就是串口ns16550。

DevTable是DevEntry结构数组。其定义如下:

struct DevEntry {

int txoff; /*是否允许发送*/

int qsize; /*发送及接收队列的大小*/

Queue *rxq; /*接收队列*/

28

Queue *txq; /*发送队列*/

char *sio; /*设备信息,初始化后设为com口的基地址*/

int chan;

int rxoff; /*是否允许接收*/

int(*handler) (int, struct DevEntry *, unsigned long, int);/*

通用函数指针,对com口被置成ns16550,进行串口读写及控制。

struct jmp_buf *intr;

char *tname;

int(*tfunc) (int, int, int, int);

struct termio t;

unsigned long freq;

int nopen; /*使用计数*/

} DevEntry;

函数devinit执行结束后,DevTable的第1项被设置成串口设备,各个域成员的含义如注释。Rxq和txq是两个循环队列,这两个队列的大小均为256个字节。rxq用来缓存从串口读来的数据,txq缓存要发送的数据。Intr为jmp_buf类型的指针。在main()函数开始处有一个setjmp()调用,它保存了调用处的机器状态信息。这样当接收到的字符是中断键(Ctrl-c)时,便可以恢复机器状态,回到main中执行。如果编译PMON时定义了INET,则使用的的是类似于Unix中的信号机制。当接收到一个信号时,也可以使Pmon的控制转移到main中,这时intr实际没有被使用。

Pmon支持了多种文件系统,每个文件系统由一个FileSystem类型的数据结构来表示。结构中的主要成员是一些函数指针。该数据结构的定义如下:

typedef struct FileSystem {

char *devname;

int fstype;

int (*open) (int, const char *, int, int);

int (*read) (int , void *, size_t);

int (*write) (int , const void *, size_t);

off_t (*lseek) (int , off_t, int);

int (*close) (int);

int (*ioctl) (int , unsigned long , ...);

SLIST_ENTRY(FileSystem) i_next;

} FileSystem;

所有的文件系统结构通过结构中的i_next指针连接形成一个单链表。FileSystems指向这个链表的第一个元素。函数filefs_init(FileSystem *fs)的所做的就是将fs插入到FileSystems中去,插入的方法是逆序插入,也就是后插入的节点在链表前面。

最后,调用term_open()打开标准IO。至此终端就建立起来了。

 终端(tty)读写

终端读写其实是读中有写,写中有读。当出现终端读写时,最终会调用term_read()和term_write()这两个函数。首先来看term_read()

int

29

term_read (fd, buf, n)

int fd;

size_t n;

void *buf;

{

int i, used;

DevEntry *p;

char ch;

struct TermDev *devp;

char *buf2 = buf;

devp = (struct TermDev *)_file[fd].data;

p = &DevTable[devp->dev];

for (i = 0; i < n;) {

/*扫描串口的读写队列,并对串口读写*/

scandevs ();

/* << LOCK >> */

while(!tgt_smplock());

/*Qused(p->rxq) 返回的是rx queue中元素的个数*/

if ((used = Qused (p->rxq)) == 0) { /* rx queue为空*/

tgt_smpunlock();

continue;

}

if (used < 20 && p->rxoff) {

/*流量控制,串口rx queue的大小是256。当rx queue 中元素个数少

于20时,重新允许接受数据

*/

(*p->handler) (OP_RXSTOP, p, NULL, p->rxoff = 0);

if (p->t.c_iflag & IXOFF)

chwrite (p, CNTRL ('Q'));

}

ch = Qget (p->rxq);/*取出 rx queue 第一个数据*/

tgt_smpunlock();

/* << UNLOCK >> */

/*将回车符转成换行符 */

if (p->t.c_iflag & ICRNL && ch == 'r')

ch = 'n';

if (p->t.c_lflag & ICANON) {

if (ch == p->t.c_cc[VERASE]) { /*是退格键*/

if (i > 0) {

i--;

30

}

if (p->t.c_lflag & ECHOE) /*删除前面输入

字符*/

write (fd, "b b", 3);

else if (p->t.c_lflag & ECHO)

write (fd, "b", 1);

}

continue;

}

if (p->t.c_lflag & ECHO) /*回显输入字符*/

write (fd, &ch, 1);

buf2[i++] = ch;

if (ch == p->t.c_cc[VEOL] || ch == p->t.c_cc[VEOL2])

break;

} else {

buf2[i++] = ch;

}

}

return i;

31