1、initmips
void initmips(unsigned long long raw_memsz) //raw_memz = msize
{
unsigned int hi;
unsigned long long memsz;
unsigned short i;
//core1 run wait_for_smp_call function in ram
asm volatile(".set mips64;sd %1,(%0);.set mips0;"::"r"(0xbfe11120),"r"(&wait_for_smp_call)); //0xbfe11120:1 号处理器核的 IPI_MailBox0 寄存器?等待内核启动后启动核唤醒其他核??
tgt_fpuenable(); //设置协处理器1(浮点协处理器)可用,并设置浮点控制与状态寄存器为默认值
get_memorysize(raw_memsz); //获取内存大小
/*enable float */
tgt_fpuenable();
tgt_cpufreq(); //计算CPU主频(主要的方法就是先读取COP0中的count寄存器,然后延时一段时间,再读取count寄存器。两次的差值乘以2就是这段时间内cpu的时钟周期数。另外,CMOS中有个实时钟,在延时前后读取当前时间相减,就可以知道延时的准确时间。从而计算出cpu的时钟频率。)
SBD_DISPLAY("DONE", 0);
/*
* Init PMON and debug
*/
CPU_ConfigCache();
cpuinfotab[0] = &DBGREG;
SBD_DISPLAY("BEV1", 0);
bcopy(MipsException, (char *)XTLB_MISS_EXC_VEC,
MipsExceptionEnd - MipsException);
bcopy(MipsException, (char *)GEN_EXC_VEC,
MipsExceptionEnd - MipsException);
dbginit(NULL);
/*
* Set up exception vectors.
*/
SBD_DISPLAY("BEV0", 0);
printf("BEV in SR set to zero.\n");
ls2k_nand_init();
#ifdef DTB
verify_dtb();
#endif
/*
* Launch!
*/
main();
}
- dbginit
这个函数几乎完成了第二阶段所有的工作。包括环境变量和基本数据结构的初始化、PCI总线扫描和设备初始化,显卡初始化、网络协议和设备初始化,并对搜索到的PCI总线上的设备进行驱动程序的加载与配置等。
void
dbginit (char *adr)
{
unsigned long long memsize;
char *s;
memsize = memorysize;
__init(); /* Do all constructor initialisation */
SBD_DISPLAY ("ENVI", CHKPNT_ENVI);
envinit ();
#if defined(SMP)
/* Turn on caches unless opted out */
if (!getenv("nocache"))
md_cacheon();
#endif
SBD_DISPLAY ("SBDD", CHKPNT_SBDD);
tgt_devinit();
init_net (1);
SBD_DISPLAY ("SBDE", CHKPNT_SBDE);
initial_sr |= tgt_enable (tgt_getmachtype ());
#ifdef SR_FR
Status = initial_sr & ~SR_FR; /* don't confuse naive clients */
#endif
/* Set up initial console terminal state */
printf ("\n * PMON2000 Professional *");
printf ("\nConfiguration [%s,%s", TARGETNAME,
BYTE_ORDER == BIG_ENDIAN ? "EB" : "EL");
printf (",NET");
printf (",SCSI");
printf (",IDE");
printf ("]\nVersion: %s.\n", vers);
printf ("Supported loaders [%s]\n", getExecString());
printf ("Supported filesystems [%s]\n", getFSString());
printf ("This software may be redistributed under the BSD copyright.\n");
print_cpu_info();
print_mem_freq();
printf ("Memory size %lld MB .\n", memorysize_total);
tgt_memprint();
#if defined(SMP)
tgt_smpstartup();
#endif
printf ("\n");
loongson_smbios_init();
md_clreg(NULL);
md_setpc(NULL, (int32_t) CLIENTPC);
md_setsp(NULL, tgt_clienttos ());
DevicesInit();
}
(1)__init
主要功能是顺序执行初始化。列表上的所有函数,建立必要的数据结构和编译环境。初始化列表上的函数主要可以分为3类:命令初始化函数,文件系统初始化函数,可执行文件初始化函数。
- pmon命令行cmd的实现。
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中。
- 集成支持了最常见的文件系统,包括 ext2、iso9660等。
Pmon中所支持的每一个文件系统都有一个相应的数据结构来表示。对于磁盘文件系统,这个结构是DiskFileSystem;对于其他文件系统,这个结构叫做FileSystem。这两个结构的成员主要是一些函数指针,分别指向文件系统的open,read,write,ioctl,lseek及close函数。
文件系统初始化就是代表各个文件系统的数据结构插入到相应链表。对于磁盘文件系统,链表的头指针式DiskFileSystems。对于其他的文件系统,头指针是FileSystems。这样当需要对某个文件操作(包括虚拟文件,如与用户交互的终端termio)。通过这两个链表可以找到相应的结构,在通过里面的函数指针就可以对文件进行具体操作了。
- 可执行文件的类型支持,分别有 bin、elf、txt 等。
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。就是将执行文件读入到指定地址。Elf文件比较复杂,它的载入函数是load_elf()。
(2)envinit
envint()设置所有的环境变量。最多可以设置64个环境变量。每个环境变量对应一个envpair结构。Envvar是一个envpair类型的数组。envinit()就是根据标准环境变量的值(数组stdenvtab)来初始化envvar数组。更改环境变量的值可以改变pmon中命令的行为以及一些系统参数。
(3)tgt_devinit
tgt_devinit函数完成pmon阶段PCI设备的初始化,tgt_devinit调用了_pci_businit,_pci_businit分为2步执行,第一步是北桥初始化。第二步是设备初始化。所有的pci设备通过总线和pci桥联成一个树状结构。pci主桥是这棵树的根,与主桥直接相连的总线就是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;
int newcfg=0;
char *env;
SBD_DISPLAY ("HW-0", 0);
if(getenv("newcfg"))newcfg=1;
if ((env = getenv("pci_probe_only")))
pci_probe_only = strtoul(env, 0, 0);
if (!initialise) {
return(0);
}
SBD_DISPLAY ("HW-1", 0);
/*
* 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);
}
SBD_DISPLAY ("HW-2", 0);
分配pci_device和pci_bus结构体,分别描述pci设备(主桥)和总线(bus0)。
pd->pa.pa_flags=
PCI_FLAGS_IO_ENABLED|PCI_FLAGS_MEM_ENABLED; //可以进行IO和内存访问
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; //0xbfd00000
pd->pa.pa_memt = pmalloc(sizeof(bus_space_tag_t));
pd->pa.pa_memt->bus_reverse = 1;
pd->pa.pa_memt->bus_base = 0xc0000000;
pd->pa.pa_dmat = &bus_dmamap_tag;
pd->bridge.secbus = pb; //主桥没有上游总线,下游总线为bus0
_pci_head = pd;
SBD_DISPLAY ("HW-3", 0);
设置pci设备附加信息结构体,初始化主桥。
if(pci_probe_only)
{
pb->minpcimemaddr = 0x40100000;
pb->nextpcimemaddr = 0x80000000;
}
else
{
pb->minpcimemaddr = 0x10000000;
pb->nextpcimemaddr = 0x18000000;
}
pb->minpciioaddr = PCI_IO_SPACE_BASE+0x0004000;
pb->nextpciioaddr = PCI_IO_SPACE_BASE+ BONITO_PCIIO_SIZE; // 0 + 0x0200_0000
pb->pci_mem_base = BONITO_PCILO_BASE_VA; //0xb0000000
pb->pci_io_base = BONITO_PCIIO_BASE_VA; //0xbfd00000
pb->max_lat = 255;
pb->fast_b2b = 1;
pb->prefetch = 1;
pb->bandwidth = 4000000;
pb->ndev = 1;
_pci_bushead = pb; //_pci_head和_pci_bushead分别记录了主桥和bus0描述结构体
_pci_bus[_max_pci_bus++] = pd;
SBD_DISPLAY ("HW-5", 0);
bus_dmamap_tag._dmamap_offs = 0;
/*set pci base0 address and window size*/
pci_local_mem_pci_base = 0x0;
return(1);
}
完成基本的地址映射后,开始初始化pci设备:包括设备的扫描和资源的分配。具体的工作在_pci_scan_dev和_setup_pcibuses完成的。
_pci_scan_dev:
static void _pci_scan_dev(struct pci_device *dev, int bus, int device, int initialise)
{
for(; device < 32; device++) {
_pci_query_dev (dev, bus, device, initialise);
}
}
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...");
id = _pci_conf_read(tag, PCI_ID_REG);
if (_pciverbose >= 2) {
PRINTF ("completed\n");
}
if (id == 0 || id == 0xffffffff) {
return;
} //读取pci设备的Vendor ID和Device ID。全0或全1,则说明插槽中没有设备
misc = _pci_conf_read(tag, PCI_BHLC_REG); //读取Header Type.bit7.判断设备是否位多功能设备
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;
continue;
}
_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等), Pd->parent设置成dev(设备pd所在总线挂在pci桥下)。然后将pd插入pci桥dev的子链表中,并获取设备所在总线的pci_bus结构的指针pb(就是pci桥dev的从总线)。
2> 设置设备中断控制信息(配置空间中的IRQ Pin决定设备是否支持中断,1表示支持,0表示不支持。假如支持中断,IRQ Line表记录下中断号)。
3> 读取配置空间command和status位,清除设备配置头中的Command寄存器的Master_enable, IO_enable, Mem_Enable位将设备暂时禁用。并根据status设置pb的相关参数,包括是否支持back to back transaction,是否支持66M时钟等。
4>读取配置空间PCI_MINGNT和PCI_MAXLAT,设置pd->min_gnt和pd->max_lat。更新pb的min_gnt和max_lat。这些和pci访问数据周期和频率相关, 并配置Interrupt Line。
5> 检测是否为pci桥,如果是,则设置pd主、次、sub总线并设置桥配置空间的主、次、sub总线号到相应位。然后更新该PCI桥所有父PCI桥的subordinate bus no(将它们设置成pd->secbus)。分配一个pci_bus 的数据结构,由pd->bridge.secbus指向它。初始化这个数据结构。初始化的方法和_pci_hwinit中类似。递归调用_pci_scan_dev,搜集该pci桥下所有设备的资源请求信息(io空间大小,memory空间大小)。每个pci设备的IO请求信息和mem请求信息都由一个_pci_win来表示。从_pci_scan_dev返回后,所有子设备IO资源请求都链入了pd->bridge.iospace指向的有序单链表。所有子设备的Mem资源请求信息都联入了pd->bridge.memspace指向的有序单链表。遍历iospace链表汇总所有的IO请求形成一个pci_win结构。遍历memspace链表汇总所有的memory请求也形成一个pci_win结构。将这两个结构插入当前pci桥的父设备的iospace和memspace链表。自此,当前pci桥及他的所有子设备的信息都已经搜集完毕。
6> 如果是pci设备,在PCI设备的配置头中从偏移地址0x10到0x24是6个基地址寄存器。他们代表了设备的IO/Memory资源请求信息。具体是IO请求还是Memory请求取决于寄存器德最低位。设置好资源请求信息的pci_win结构并链入父设备PCI桥memspace和iospace链表后,_pci_query_dev_func就结束返回到_pci_query_dev,最后返回到_pci_businit()。至此PCI设备的信息扫描完毕。
然后开始真正的为pci设备分配资源。这_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) {
if (_pciverbose) {
_pci_bdfprintf (pb->bus, -1, -1,
"WARN: total bandwidth exceeded\n");
}
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);
}
//_pci_setup_windows负责为总线上的所有设备分配IO和memory空间。
}
_pci_setup_windows函数主要步骤:
1> 遍历桥下pci设备的mem空间请求(memspace所指的的链表代表的是PCI桥的子设备的Mem空间请求,它是由_pci_scan_dev搜集的pci设备信息),如果是设备类型是PCI桥,则它的资源请求实际上是他的子设备的请求,因此要为下一层子设备分配资源。PCI桥下的所有设备的mem地址空间都要在minpcimemaddr和Nextpcimemaddr之间,并且将内存基地址值和上限值写到PCI桥的mem base和limit寄存器中。
2> io资源的分配则和mem资源分配相似,同样的遍历桥下pci设备的io空间请求(iospace所指的的链表代表的是PCI桥的子设备的io空间请求,它是由_pci_scan_dev搜集的pci设备信息),如果是设备类型是PCI桥,则它的资源请求实际上是他的子设备的请求,为下一层子设备分配资源。PCI桥下的所有设备的io地址空间都要在minpcimemaddr和Nextpcimemaddr之间,并且将内存基地址值和上限值写到PCI桥的io base和limit寄存器中。
3> 最后递归调用_pci_setup_window函数,为桥下所有pci设备io和mem请求分配资源。
(4)init_net
1> 初始化系统全局参数
hz = HZ; /HZ 表示 1 秒钟时钟中断的产生次数
tick = 1000000 / HZ; //两次时钟中断中间的 us 数
tz.tz_minuteswest = TIMEZONE;
tz.tz_dsttime = DST;
ncallout = 16 + NPROC; //进程数
nmbclusters = NMBCLUSTERS;
maxfiles = NDFILE * NPROC;
2> 初始化虚拟内存
void
vminit ()
{
extern unsigned long long 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;
kmem = (u_char *)memorysize;
}
}
3> 初始化内存分配器
4> 初始化callout
5> 初始化相关pci设备-----------tgt_devconfig();
a、设置所有pci设备的配置参数
_pci_devinit(1);
b、初始化显卡和图形处理器
c、config_init()
/* parent vectors */
short pv[13] = {
1, 8, -1, 4, -1, 7, -1, 0, -1, 9, -1, 10, -1,
};
d、configure()函数完成初始化pci框架的层次结构
7> 初始化网络
- loongson_smbios_init
SMBIOS 是主板或系统制造者以标准格式显示产品管理信息所需遵循的统一规范,可以给出序列号、电脑厂商信息、固件、操作系统、主板、内存、扩展槽及扩展接口的详细情况。
static size_t
write_smbios_tables(void *start)
{
unsigned int cpu_num, nr_structs = 0, max_struct_size = 0;
unsigned int dimmnum = 0;
unsigned int maximum_capacity = 0;
unsigned int i;
char *p, *q;
p = (char *)start + sizeof(struct smbios_entry_point);
#define do_struct(fn) do { \
q = (fn); \
nr_structs++; \
if ((q - p) > max_struct_size) \
max_struct_size = q - p; \
p = q; \
} while (0)
do_struct(smbios_type_0_init(p));
do_struct(smbios_type_1_init(p));
do_struct(smbios_type_2_init(p));
do_struct(smbios_type_4_init(p));
do_struct(smbios_type_16_init(p, dimmnum, maximum_capacity));
for(i = 0; i < dimmnum; i++){
do_struct(smbios_type_17_init(p, i));
}
do_struct(smbios_type_28_init(p));
do_struct(smbios_type_127_init(p));
#undef do_struct
smbios_entry_point_init(
start, max_struct_size,
(p - (char *)start) - sizeof(struct smbios_entry_point),
SMBIOS_PHYSICAL_ADDRESS + sizeof(struct smbios_entry_point),
nr_structs);
return (size_t)((char *)p - (char *)start);
}
在write_smbios_tables函数中,主要初始化了type0:bios的信息、type1:系统信息、type2:板卡信息、type4:处理器信息、type16:物理内存信息、type28:温度探测信息。
md_clreg(NULL);
md_setpc(NULL, (int32_t) CLIENTPC); //pc = 0x80100000
md_setsp(NULL, tgt_clienttos ()); //设置sp的,根据内存大小计算获得
- DeviceInit
void DevicesInit(void)
{
DeviceDisk *dev_disk, *p = NULL;
struct device *dev, *next_dev;
int part_open_ret;
if (gDevice != NULL)
{
return ;
}
for (dev = TAILQ_FIRST(&alldevs); dev != NULL; dev = next_dev)
{
next_dev = TAILQ_NEXT(dev, dv_list);
if(dev->dv_class != DV_DISK)
{
continue;
}
dev_disk = (DeviceDisk *)malloc(sizeof(DeviceDisk));
if (dev_disk == NULL)
{
printf("DeviceInit malloc DeviceDisk failed.\n");
continue;
}
memset(dev_disk, 0, sizeof(DeviceDisk));
//strcpy(dev_disk->device_name, &dev->dv_xname);
strcpy(dev_disk->device_name, dev->dv_xname);
dev_disk->dev_fstype = FS_TYPE_UNKNOWN;
if (strncmp(dev_disk->device_name, "loopdev", 7) != 0)
part_open_ret=_DevPartOpen(dev_disk,dev_disk->device_name);
if (gDevice == NULL)
{
gDevice = dev_disk;
p = gDevice;
}
else
{
p->Next = dev_disk;
p = dev_disk;
}
}
}
遍历已经注册的设备,所有已知设备都在 alldevs 这个链表中。如果是 disk 设备,就分配一个 DeviceDisk 结构体,填充名字后插入以gDivice 为首的链表中。
最后,经过上述初始化过程,pmon阶段所有重要设备基本完成初始化,后续开始引导启动内核。