PMON PCI裝置初始化
第二章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)
{
/*
* Setup memory address decoders to map entire memory.
* Butfirst move away bootrom map to high memory.
*/
memorysize=(memsz&0x0000ffff)<< 20;//recover to original size:256M
memorysize_high=((memsz&0xffff0000)>>16)<< 20;//0
/*
* Probeclock frequencys so delays will work properly.
*/
tgt_cpufreq();
SBD_DISPLAY("DONE",0);
/*
* InitPMON and debug
*/
cpuinfotab[0]= &DBGREG;
dbginit(NULL);
/*
* Setup 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! */
main();
}
首先是獲取CPU的時鐘頻率,這是tgt_cpufreq()完成的。它定義在tgt_machdep.c中。主要的方法就是先讀取COP0中的count暫存器,然後延時一段時間,再讀取count暫存器。兩次的差值乘以2就是這段時間內cpu的時鐘週期數。另外,CMOS中有個實時鐘,在延時前後讀取當前時間該相減,就可以知道延時的準確時間。從而計算出cpu的時鐘頻率。全域性變數md_cpufreq記錄了cpu頻率值,md_pipefreq是流水線的頻率,它們的值分別是500MHZ和1000MHZ;
接著呼叫的是dbginit(),這是最要的一個函式,幾乎所有的初始化程式碼都由他直接或間接呼叫。
l 建構函式(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 {
constchar *name; //命令的名稱
constchar *opts; //引數
constOptdesc *optdesc; //命令引數的option
constchar *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函式。
檔案系統初始化就是代表各個檔案系統的資料結構插入到相應連結串列。對於磁碟檔案系統,連結串列的頭指標式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檔案頭的內容載入各個程式段的內容,並返回可執行檔案開始執行的地址。
l 環境初始化
envint()設定所有的環境變數。最多可以設定64個環境變數。每個環境變數對應一個envpair結構。Envvar是一個envpair型別的陣列。envinit()就是根據標準環境變數的值(陣列stdenvtab)來初始化envvar陣列。更改環境變數的值可以改變pmon中命令的行為以及一些系統引數。
l 裝置初始化
裝置初始化主要就是PCI裝置的初始化。這是由tgt_devinit()完成的。tgt_devinit又呼叫了_pci_businit()。
PCI裝置初始化分為兩步,第一步是北橋初始化。第二步是裝置初始化。在Pmon中,每個PCI裝置都對應一個pci_device結構(包括pci-pci橋);每個pci匯流排都對應一個pci_bus結構;
struct pci_device {
structpci_attach_args pa; //裝置的一些資訊,如id,class,中斷線
//
unsignedchar min_gnt;
unsignedchar max_lat;
unsignedchar int_line;
pcireg_t stat;
u_int8_t intr_routing[4];
structpci_bridge bridge;
structpci_bus *pcibus;
structpci_device *next;
structpci_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 {
structpci_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空間同樣是從nextpciioaddr開始往下分配。分配的時候要注意不能超過最低可用的地址。
1. 北橋初始化
北橋提供了CPU和PCI裝置相互訪問的通道,實現了CPU空間和PCI空間的對映。_pci_hwinit() 具體地完成了這個工作。
所有的pci裝置通過匯流排和pci橋聯成一個樹狀結構。北橋Bonito是這棵樹的根,與北橋直接相連的匯流排就是pcibus0。_pci_hwinit()的工作就是建立北橋和pcibus0的資料結構,同時進行CPU和PCI的地址對映。
int
_pci_hwinit(initialise, iot, memt)
intinitialise;
bus_space_tag_tiot;
bus_space_tag_tmemt;
{
/*pcireg_tstat;*/
structpci_device *pd;
structpci_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 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->bus_base);
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)和下級匯流排(Subordinatebus)。上游匯流排是和PCI橋相連的離CPU較近的匯流排。下游匯流排是離CPU較遠的匯流排。下級匯流排則是和PCI相連的匯流排號最大的匯流排。
對於北橋來說,是沒有上游匯流排的,而他的下游匯流排就是pcibus0,因此pd->bridge.secbus賦值為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。
bus_dmamap_tag._dmamap_offs= 0;
/*setBonito 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 1211 6 5 0
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地址。這是通過設定BaseAddress Register。Bonito中一共有三個這樣的暫存器pcibas0-2。Pcibas0,pcibase1都可以對映多達256M的空間。具體對映的大小還取決於pcimembasecfg的設定。Pcibase2是對映Bonito的內部暫存器,對映區間為64k大小。
暫存器pcimembasecfg在start.S中設定。我們先看它各個域的含義。
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_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, intbus, int device, int initialise)
{
for(;device < 19; device++) //to 19+11, hu mingchang
{
_pci_query_dev (dev, bus, device,initialise);
}
}
因此主要的函式就是_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到0x1FE.FFFC的區間時,在PCI地址匯流排會出現如下的地址;
有了上述知識,再來看_pci_conf_readn;這個函式的作用就是讀裝置的配置頭。雖然很簡單,但是它在_pci_query_dev等幾個主要的函式裡都要用到。
pcireg_t _pci_conf_readn(pcitag_t tag, int reg, intwidth)
{
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%x\n", reg);
return ~0;
}
_pci_break_tag (tag, &bus, &device,&function);
if (bus == 0) {
/* Type 0 configuration on onboard PCIbus */
if (device > 20 || function > 7)
return ~0; /* device outof range */
addr = (1 << (device+11)) |(function << 8) | reg;
type = 0x00000;
}
else {
/* Type 1 configuration on offboard PCIbus */
if (bus > 255 || device > 31 ||function > 7)
return ~0; /* device out ofrange */
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
if (_pciverbose >= 1)
_pci_tagprintf (tag, "_pci_conf_read: reg=%x master abort\n",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 abort\n");
return ~0;
}
return data;
}
可以看到該函式首先從引數tag中分離出bus no,device no和functionno,然後根據匯流排號決定配置週期的型別,形成匯流排的高16位地址寫入pcimap_cfg暫存器。最後就可以訪問設配的配置塊了。由於訪問裝置可能會有各種錯誤,這裡主要是master abort和targetabort,因此讀前要清掉錯誤資訊,讀之後還要檢查是否出錯。
另外,_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,"probe...");
/*讀取pci裝置的Vendor ID和Device ID。如果放回0或者全1,則說明插槽中沒有裝置
*/
id = _pci_conf_read(tag, PCI_ID_REG);
if (_pciverbose >= 2) {
PRINTF ("completed\n");
}
if (id == 0 || id == 0xffffffff) {
return;
}
/*現在確定找到一個裝置了,讀取它的Header Type,如果HeaderType的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的最小值)。前面已經講過,他們和匯流排的頻寬直接相關。
9) 如果裝置的class code=0x060400,則說明該裝置是PCI橋。否則執行15)
10) 設定該裝置pci_device結構的bridge成員的相關分量,主要就是設定主匯流排號(primary bus no),從匯流排號(Secondary bus no),下級匯流排號(Subordinatebus no),在pci橋的配置頭中還有三個暫存器分別表示這三個匯流排號,因此還要設定它們。
11) 更新該PCI橋所有祖先PCI橋的subordinate bus no(將它們設定成pd->secbus)
12) 分配一個pci_bus 的資料結構,由pd->bridge.secbus指向它。初始化這個資料結構。初始化的方法和_pci_hwinit中類似。
13) 遞迴呼叫_pci_scan_dev,蒐集該pci橋所有裝置的資源請求資訊(io空間大小,memory空間大小)。每個pci裝置的IO請求資訊和mem請求資訊都有一個_pci_win來表示。從_pci_scan_dev返回後,所有子裝置IO資源請求都鏈入了pd->bridge.iospace指向的有序單鏈表。所有子裝置的Mem資源請求資訊都聯入了pd->bridge.memspace指向的有序單鏈表。
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: 保留
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 largestminimum grant time to cycle count */
/*XXX 66/33 Mhz*/
max_ltim = pb->min_gnt* 33 / 4;
/* now see how muchbandwidth is left to distribute */
if (pb->bandwidth<= 0) {
if (_pciverbose){
_pci_bdfprintf (pb->bus, -1, -1,
"WARN: total bandwidth exceeded\n");
}
def_ltim = 1;
}
else {
/* calculate afair share for each device */
def_ltim =pb->bandwidth / pb->ndev;
if (def_ltim >pb->max_lat) {
/* would exceedcritical time for some device */
def_ltim =pb->max_lat;
}
/* convert tocycle count */
def_ltim =def_ltim * 33 / 4;
}
/* most devices don'timplement 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 individualdevice 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;
struct pci_win *next;
struct pci_device *pd;
/*memspace所指的的連結串列代表的是PCI橋的子裝置的Mem空間請求
和下面的iospace連結串列一樣,它們是由_pci_scan_dev蒐集的pci裝置資訊
*/
for(pm = dev->bridge.memspace; 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->pa.pa_tag,
"not enough PCImem space (%d requested)\n",
pm->size);
continue;
}
if (_pciverbose >= 2)
_pci_tagprintf (pd->pa.pa_tag, "mem @%p, %d bytes\n",pm->address, pm->size);
/*如果是裝置型別是PCI橋,則它的資源請求實際上是他的子裝置的請求,
因此要再下一層為子裝置分配資源
*/
if (PCI_ISCLASS(pd->pa.pa_class,
PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_PCI) &&
(pm->reg == PCI_MEMBASE_1)) {
pcireg_t memory;
/*PCI橋下的所有裝置的mem地址空間都要在minpcimemaddr和
Nextpcimemaddr之間*/
pd->bridge.secbus->minpcimemaddr = pm->address;
pd->bridge.secbus->nextpcimemaddr = pm->address + pm->size;
/*將記憶體基地址值和上限值寫到PCI橋的membase和limit暫存器中*/
memory = (((pm->address+pm->size) >> 16) << 16) | (pm->address>> 16);
_pci_conf_write(pd->pa.pa_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->pa.pa_tag, pm->reg);
base = pm->address | (base & ~PCI_MAPREG_MEM_ADDR_MASK);
_pci_conf_write(pd->pa.pa_tag, pm->reg, base);
}
}
/* Program expansion rom address base after normal memory base,
to keep DEC ethernet chip happy */
for (pm = dev->bridge.memspace; pm != NULL; pm = next) {
pd = pm->device;
if (PCI_ISCLASS(pd->pa.pa_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->bridge.secbus->minpcimemaddr == 0 &&
(PCI_ISCLASS(pd->pa.pa_class,
PCI_CLASS_PREHISTORIC, PCI_SUBCLASS_PREHISTORIC_VGA) ||
PCI_ISCLASS(pd->pa.pa_class,
PCI_CLASS_DISPLAY, PCI_SUBCLASS_DISPLAY_VGA))) {
have_vga = pd->pa.pa_tag;
pm->address = 0x000c0000; /* XXX PCI MEM @ 0x000!!! */
}
#endif
if (pm->reg == PCI_MAPREG_ROM) {
/* expansion rom */
if (_pciverbose >= 2)
_pci_tagprintf (pd->pa.pa_tag, "exp @%p, %d bytes\n",
pm->address, pm->size);
_pci_conf_write(pd->pa.pa_tag, pm->reg, pm->address | PCI_MAPREG_TYPE_ROM);
}
next = pm->next;
dev->bridge.memspace = next;
pfree(pm);
}
/*iospace所指的連結串列是pci裝置的IO請求資訊*/
for(pm = dev->bridge.iospace; pm != NULL; pm = next) {
pd = pm->device;
next = pm->next;
pm->address = _pci_allocate_io (dev, pm->size);
if (pm->address == -1) {
_pci_tagprintf (pd->pa.pa_tag,
"not enoughPCI io space (%d requested)\n",
pm->size);
pfree(pm);
continue;
}
if (_pciverbose >= 2)
_pci_tagprintf (pd->pa.pa_tag, "i/o @%p, %d bytes\n",pm->address, pm->size);
if (PCI_ISCLASS(pd->pa.pa_class,
PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_PCI) &&
(pm->reg == PCI_IOBASEL_1)) {
pcireg_t tmp;
pd->bridge.secbus->minpciioaddr = pm->address;
pd->bridge.secbus->nextpciioaddr = pm->address + pm->size;
/*設定PCI橋配置空間裡的iobase和iolimit暫存器*/
tmp = _pci_conf_read(pd->pa.pa_tag,PCI_IOBASEL_1);
tmp &= 0xffff0000;
tmp |= (pm->address >> 8) & 0xf0;
tmp |= ((pm->address + pm->size) & 0xf000);
_pci_conf_write(pd->pa.pa_tag,PCI_IOBASEL_1, tmp);
tmp = (pm->address >> 16) & 0xffff;
tmp |= ((pm->address + pm->size) & 0xffff0000);
_pci_conf_write(pd->pa.pa_tag,PCI_IOBASEH_1, tmp);
}
else {
/*設定PCI裝置配置空間裡的iobase暫存器*/
_pci_conf_write(pd->pa.pa_tag, pm->reg, pm->address | PCI_MAPREG_TYPE_IO);
}
dev->bridge.iospace = next;
pfree(pm);
}
/* Recursive allocate memory for secondary buses */
/*遍歷當前pci橋的字裝置,如果他們也是一個pci橋,則遞迴地呼叫
*_pci_setup_windows來分配資源
*/
for(pd = dev->bridge.child; pd != NULL; pd = pd->next) {
if (PCI_ISCLASS(pd->pa.pa_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_taddress;
#ifndef PCI_ALLOC_UPWARDS
/*allocate downwards, then round to size boundary */
address= (dev->bridge.secbus->nextpciioaddr - size) &
~(size - 1);
if(address > dev->bridge.secbus->nextpciioaddr ||
address < dev->bridge.secbus->minpciioaddr) {
return -1;
}
dev->bridge.secbus->nextpciioaddr= address;
#else
/*allocate downwards, then round to size boundary */
address= (dev->bridge.secbus->minpciioaddr + size) &
~(size - 1);
if(address > dev->bridge.secbus->nextpciioaddr ||
address <dev->bridge.secbus->minpciioaddr) {
return-1;
}
dev->bridge.secbus->minpciioaddr= address;
#endif
return(address);
}
由於程式中沒有定義PCI_ALLOC_UPWARDS這個巨集,因此編譯執行的是前半部分的程式碼。也就是說空間的分配是從高到低。每次分配都是從地址nextpciioaddr開始,分配完要將nextpciioaddr設成下次分配時的開始地址。
至於_pci_allocate_mem,它的過程和_pci_allocate_io類似,也是從高地址開始分配。
初始化完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();
/*
* 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("ifinitdone.\n");
domaininit();
printf("domaininitdone.\n");
splx(s);
}
}
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
}
}
該函式將記憶體的高512k作為虛擬記憶體(VM_KMEM_SIZE定義為512*1024,每頁的大小是4k)。初始化後kmem指向這塊記憶體的記憶體起始地址。
虛擬記憶體中的每一個頁面都有一個kmemusage的結構與之對應:
struct kmemusage {
shortku_indx; /* bucket index */
union{
u_shortfreecnt;/* for small allocations, free pieces in page */
u_shortpagecnt;/* 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 freeblocks */
caddr_t kb_last; /* last freeblock */
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(structkmemusage)));
kmem_map = kmem_suballoc(kernel_map, (vaddr_t *)&kmembase,
(vaddr_t *)&kmemlimit, (vsize_t)(npg * NBPG), FALSE);
}
這裡記憶體分配的方法是非常簡單的。前面講過kmem指向了可供分配虛存的首地址,而變數kmem_offs則記錄了已經分配了多少空間。這樣每次分配從&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 即可)。
與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;
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;
}s;
ALIGN x;
};
typedef union header HEADER;
一個HEADER結構大小成為一個unit。結構中的size分量表示該空閒塊包unit的個數。另外,所有的空閒記憶體塊通過HEADER結構中的ptr指標按地址增加的順序連線成一個連結串列。每個記憶體塊前面HEADER結構大小的部分就是做這個用途的。
void *
malloc(size_t nbytes)
{
HEADER *p, *q; /* K&Rcalled q, prevp */
unsigned nunits;
nunits = (nbytes + sizeof (HEADER) - 1) / sizeof (HEADER) + 1;
if ((q = allocp) == NULL) { /*no free list yet */
base.s.ptr = allocp = q = &base;
base.s.size = 0;
}
/*掃描空閒連結串列,直到找到一個大小滿足要求的空閒塊*/
for (p = q->s.ptr;; q = p, p = p->s.ptr) {
if (p->s.size >= nunits) { /* big enough */
/*空閒塊p的大小剛好滿足要求,將p所指的記憶體塊從空閒連結串列脫鏈*/
if (p->s.size == nunits) /*exactly */
q->s.ptr = p->s.ptr;
else { /* allocate tail end*/
/*空閒區比索要求的記憶體大,則從後面分配記憶體,而空閒連結串列的結構不
*需要改變*/
p->s.size -= nunits;
p += p->s.size;
p->s.size = nunits;
}
/*q指向剛才所分配空閒區前面的那個空閒塊,讓allocp指向它,下一次分配時,從該位置開始掃描*/
allocp = q;
return ((char *)(p + 1));
}
if (p == allocp)
if ((p = morecore (nunits)) == NULL)
return (NULL);
}
}
空閒區連結串列有一個頭結點base,這是一個靜態變數,它本身的不能代表一個空閒區,因此在上面將它的size分量置成0。靜態變數allocp指向上一次記憶體分配的位置。每次記憶體分配都是從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->s.size = 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->s.ptr); q =q->s.ptr)
if (q >= q->s.ptr && (p > q || p < q->s.ptr))
break;
if(p + p->s.size == q->s.ptr) {
/*和後面一個空閒塊相鄰*/
p->s.size += q->s.ptr->s.size;
p->s.ptr = q->s.ptr->s.ptr;
}else
p->s.ptr = q->s.ptr;
if(q + q->s.size == p) {
/*和前一個空閒塊相鄰*/
q->s.size += p->s.size;
q->s.ptr = p->s.ptr;
}else
q->s.ptr = p;
allocp = q;
}
終端建立和通訊
在Pmon的除錯過程中,串列埠是一個重要的工具。因此在系統啟動後,便對串列埠ns16550進行了初始化。
l 串列埠初始化
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位的暫存器,複用了資料暫存器(DataRegister)和中斷使能暫存器(IER, Interrupt Enable)。在設定波特率之前先要將CFCR(Line control Register)的bit7置1,表示下面要設定波特率。然後就可以往CFCR和IER中分別寫入波特率的低8位和高8位。
l 終端通訊
在__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
*/
_