3.6 NAND初始化

dbginit(NULL)执行完成后,说明重要的核心设备初始化过程已经告一段落。接下来设置把BEV清零。

BEV1
BEV0
BEV in SR set to zero.

(内存,cache等)初始化完成后,就可以使用cache了,通过把协处理器0的SR寄存器中的BEV清零,使所有异常入口从ROM入口点(0xBFC0 0000)改为RAM入口点(BASE + 0x180),其中BASE为寄存器EBase的值。

接下来进行NAND初始化。整个函数如下:

#if NNAND                                                                //来源配置文件select   nand参数   这里生效
#ifdef CONFIG_LS2K_NAND                             //如果配置文件定义了CONFIG_LS2K_NAND参数,则时使能NAND管脚,然后进行NAND初始化*(volatile int *)0xbfe10420 |= (1<<9) ;ls2k_nand_init();
#else                                                                          //否则配置NAND的管脚为普通GPIO/*nand pin as gpio*/*(volatile int *)0xbfe10420 &= ~(1<<9) ;
#endif
#if NSPINAND_MT29F || NSPINAND_LLD  //如果定义了select spinand_lld或者select spinand_mt29f则生效    这个主要是针对spi接口的nandls2h_spi_nand_probe();
#endif
#if NM25P80                                                         //M25P80  这个主要是spi型的nor flashls2h_m25p_probe();
#endif

这里我们主要能用到的是普通接口型的CONFIG_LS2K_NAND和NM25P80,下面我们分别来进行解析。

3.6.1 NAND(29F16G08)

ls2k_nand_init() 该函数是NAND的初始化函数,位于sys/dev/nand/ls2k-nand.c:679

要使能该函数的调用,需要在文件Targets/LS2K/conf/ls2k里添加支持,如下所示:

select  nand
option CONFIG_LS2K_NAND

以上配置添加后,Nand Flash的驱动程序会在pmon启动过程中进行调用,Nand Flash的驱动初始化主要完成以下几个部分:

1)、mtd结构的初始化
2)、Nand控制器的初始化
3)、Nand Flash 的识别
4)、建立分区

3.6.1.1 mtd结构的初始化

ls2k_nand_init_mtd()

mtd结构初始化驱动初始化调用 ls2k_nand_init_mtd(),该函数名称虽然是init mtd,但主要是对mtd->priv进行初始化,该成员承载mtd层和Nand控制器的联系,使mtd层可以访问并使用Nand 控制器。

struct nand_chip *this = &info->nand_chip;     //Info->nand_chip对应的就是mtd->privthis->options        = 8;this->waitfunc        = ls2k_nand_waitfunc;this->select_chip    = ls2k_nand_select_chip;...this->ecc.hwctl        = ls2k_nand_ecc_hwctl;this->ecc.calculate    = ls2k_nand_ecc_calculate;this->ecc.correct    = ls2k_nand_ecc_correct;

在该函数里除了对需要的接口进行初始化外,最关键的是对ECC的代码选择。

2K的Nand ECC代码有两种: NAND_ECC_SOFT和NAND_ECC_SOFT_BCH两种,BCH校验相比普通ECC校验有更强的纠错能力,默认bch=4,表示每512字节,生成7 字节校验码,可以最多纠错4bit 错误,而普通的ECC ,每256字节,需要生成3字节的校验码,只能纠错1bit 错误。相对应的,高校验能力需要更多的校验码以使出错数据可以被检测到,这就需要更多代码复杂度和计算,需要的耗时相对普通ECC来说,时间更久。
如果要选择BCH校验,需要在文件Targets/LS2K/conf/ls2k里添加支持如下图所示,如果不用BCH算法,需要将该选项注释掉,注释掉后,代码使用普通ECC校验,同样的,如果不想使用ECC校验,需要将代码中的NAND_ECC_SOFT改为NAND_ECC_NONE。

select  nand
option CONFIG_LS2K_NAND
select nand_bch         #support bch ecc

3.6.1.2 Nand控制器的初始化

ls2k_nand_init_info(info)

初始化完mtd结构体之后,驱动初始化调用ls2k_nand_init_info(),该函数主要是对Nand控制器进行初始化,代码如下所示:

static void ls2k_nand_init_info(struct ls2k_nand_info *info)
{info->buf_start = 0;info->buf_count = 0;info->seqin_column = 0;info->seqin_page_addr = 0;spin_lock_init(&info->nand_lock);writel(0x412, REG(NAND_TIM_REG));writel(0x00440000, REG(NAND_CS_RDY_REG));
}

其中info结构体成员代表了数据读写时的起始位置, 所有起始位置都从0开始,该函数另外一个关键地方,就是对Nand控制器的寄存器NAND_TIM_REG和NAND_CS_RDY_REG寄存器的初始化。
NAND_TIM_REG寄存器

可以控制数据传输的和命令有效的时钟周期,这在一定程度上代表了数据传输的速度,但基于硬件环境和稳定性的考虑,不建议修改该寄存器的值,造成数据传输的不稳定。
NAND_CS_RDY_REG寄存

器表示Nand Flash 片选信号的选择,一般情况下根据硬件设计规范,Flash的片选会接到cs0上,因此该寄存器也没必要修改,除非特殊设计cs接到其他引脚。

3.6.1.4 DMA初始化

dma_desc_init(info);

龙芯 2K1000 中 DMA 用来实现内存与 APB 设备之间数据搬移,可以节省资源提高系统数据传输的效率。 DMA 控制器限定为以字(4Byte)为单位的数据搬运。

2K1000 中包含 5 个 DMA 控制器,使用 DMA 的 APB 设备包括 NAND、I2S、SDIO 以及加解密模块。其中 NAND 和 SDIO 各需要一个 DMA 控制器,而 I2S、加密模块、解密模块各需要两个 DMA 控制器分别控制 DMA 写操作和 DMA 读操作。根据应用场景不同,需要通过芯片配置寄存器来设置 APB 设备使用哪个 DMA 控制.

从内存中读数据,保存在 DMA 控制器的缓存中,由 APB 发请求来访问 DMA缓存中的数据,该寄存器指定了写 APB 设备的地址;

从 APB 设备读数据保存在 DMA 缓存中,当 DMA 缓存中的字超过一定数目,就往内存中写,该寄存器指定了读 APB 设备的地址。

使用 DMA 的 APB 设备都有对应的数据 buffer 地址,比如 NAND 控制器的数据 buffer偏移地址为 0x40,默认的设备地址就可以配置成 0x1fe06040。

分析下代码,代码如下:

void dma_desc_init(struct ls2k_nand_info *info)
{volatile struct ls2k_nand_dma_desc *dma_base =                //打印值为:0x1fe06040(volatile struct ls2k_nand_dma_desc *)(info->desc_addr);  //打印值为:0xFFFFFFFF8b043000dma_base->orderad = 0;                                        //存储下一个 DMA 描述符的地址,bit0 是下个 DMA 描述符的使能位,如果该位为 1 表示下个描述符有效,该位为 0 表示下个描述符无效,不执行操作,地址 16字节对齐。dma_base->saddr = info->data_buff_phys;                       //指定了读 ddr3 的地址        data_buff_phys在此之前执行ls2k_nand_init_buff动态申请的buf内存空间dma_base->daddr = info->apb_data_addr;                        //指定了写 APB 设备的地址      apb_data_addr也就是DMA_ACCESS_ADDR(0x1fe06040)对应的是NAND 控制器的数据 bufferdma_base->step_length = 0;                                   //代表一块被搬运内容的长度,单位是字dma_base->step_times = 0x1;                                  //循环次数说明在一次 DMA 操作中需要搬运的块的数目。如果只想搬运一个连续的数据块,循环次数寄存器的值可以赋值为 1dma_base->length = 0;                                        //间隔长度说明两块被搬运内存数据块之间的长度,前一个 step 的结束地址与后一个 step 的开始地址之间的间隔dma_base->cmd = 0;                                           //操作命令
}

主要是对NAND控制器的DMA进行初始化,首先获得到dma_base。我们通过打印看到地址值是0x1fe06040。 问题来了,为什么是这个地址呢?查询手册,不好意思,你是直接找不到的。需要转换,我们通过之前的讲解知道了,0x1fe00000是APB设备地址空间。下挂的控制器是通过偏移地址来寻址的。我们看到,nand控制器的寻址bit12~bit16的值固定为6,所以0x1fe06000对应的是NAND控制器的地址。剩下的寄存器的访问是通过偏移地址来实现的。好了,那偏移0x40是什么寄存器呢?没错,正是读写数据寄存器 DMA_ADDRESS。这个只是DMA的基址,那么它对应的寄存器描述在哪呢?还得跳到DMA那一章节,根据偏移地址来看。

注意:很多人肯定就好奇了,为什么就只有5个DMA,那我高速设备(比如网卡)怎么办?2K1000的高速设备全走的PCI,直接映射到内存映射到设备空间上了。这些DMA的地址随对应设备进编址。

3.6.1.4 Nand Flash 的识别

当mtd的数据结构和Nand控制器初始化完成之后,驱动就可以对硬件上的Flash进行访问,但在进行实际数据的读写之前,驱动还要对Flash做进一步的识别,以了解其容量、页大小、块大小和OOB区大小等信息。

nand_scan()

完成对Flash的识别,程序调用关系如下:

nand_scan
|-- nand_scan_ident;|-- nand_get_flash_type

nand_scan() 执行过程中会调用nand_get_flash_type(),该函数会读取Flash芯片的ID号,并于nand_flash_ids数组项做匹配,匹配成功即可获取Flash信息。

nand_flash_ids位于sys/dev/nand/nand_ids.c,部分内容如下:

Name. ID code , pagesize , chipsize in MegaByte , eraseblock size , options...    ...    .../* 8 Gigabit */{"NAND 1GiB 1,8V 8-bit",    0xA3, 0, 1024, 0, LP_OPTIONS},{"NAND 1GiB 3,3V 8-bit",    0xD3, 0, 1024, 0, LP_OPTIONS},{"NAND 1GiB 1,8V 16-bit",    0xB3, 0, 1024, 0, LP_OPTIONS16},{"NAND 1GiB 3,3V 16-bit",    0xC3, 0, 1024, 0, LP_OPTIONS16},/* 16 Gigabit */{"NAND 2GiB 1,8V 8-bit",    0xA5, 0, 2048, 0, LP_OPTIONS},{"NAND 2GiB 3,3V 8-bit",    0xD5, 0, 2048, 0, LP_OPTIONS},{"NAND 2GiB 1,8V 16-bit",    0xB5, 0, 2048, 0, LP_OPTIONS16},{"NAND 2GiB 3,3V 16-bit",    0xC5, 0, 2048, 0, LP_OPTIONS16},{"NAND 2GiB 3,3V 16-bit",    0x48, 4096, 2048, 128*4096, LP_OPTIONS},    //我们目前使用的就是29F16G08...    ...    ...

匹配成功够打印log如下:

NAND device: Manufacturer ID: 0x2c, Chip ID: 0x48 (Micron NAND 2GiB 3,3V 16-bit)

该数组结构定义为:

struct nand_flash_dev {char *name;int id;unsigned long pagesize;unsigned long chipsize;unsigned long erasesize;unsigned long options;
};

当有新的Flash需要适配时,要根据设备手册,在该数组里添加ID信息和容量相关信息。

nand_scan_tail(mtd)

  |-- chip->scan_bbt(mtd)

根据识别到的NAND特征,根据特征值来填充所有未初始化的函数指针,并扫描一个坏的块表(如果合适的话)。

chip->scan_bbt(mtd) 函数指针在之前赋值为nand_default_bbt(sys/dev/nand/nand_bbt.c:1146),实际至执行的是这个函数。

3.6.1.5 建立分区

Nand 初始化的最后会建立分区信息,如下所示:

mtd->name="nand-flash";
if(!nand_flash_add_parts(mtd,0)){add_mtd_device(mtd,0,0,"total");add_mtd_device(mtd,0,0x01400000,"kernel");add_mtd_device(mtd,0x01400000,0x0,"os");
}

建立分区的函数是add_mtd_device(),但是驱动初始化函数会先调用nand_flash_add_parts()函数对环境变量mtdparts 进行判断,若设置了该环境变量则根据该变量设置分区,否则调用add_mtd_device()来指定分区。

环境变量mtdparts的赋值:

a、代码里
Targets/LS2K/include/pmon_target.h

#if NNAND
#define TGT_DEFENV  {"mtdparts","nand-flash:30M@0(kernel),-(rootfs);spinand_flash:30M@0(kernel),-(rootfs)",0,&mtd_rescan},   \{"bootdelay","3",0,0}
#else
#define TGT_DEFENV  {"bootdelay","3",0,0}
#endif

b、pmon命令行下
请在PMON常用命令里查看,mtdparts的传参格式为:

mtdparts //查看分区
set mtdparts "mtdname:offset@size(partname)[option]" //修改分区

其中mtdname表示mtd设备名称,offset表示分区的起始地址,size表示分区大小,partname表示分区名称,option是可选项,表示分区的读写权限,可以为rw,ro。
例如:

set mtdparts nand-flash:40M@0(kernel)ro,-(rootfs)

3.6.1.6 pmon下nandflash相关操作

一、查看nandflash分区和容量使用情况

PMON> mtdparts
device <nand-flash>,#parts = 2#: name        size            offset          mask_flags0: kernel      0x00a00000(10m) 0x00000000(0m)  31: rootfs      0x07600000(118m)        0x00a00000(10m) 0mtdparts INFO:now-active:   mtdparts=nand-flash:10M@0(kernel)ro,-(rootfs)<NOTE>:you can use command(CMD:  unset mtdparts ) to become the default !!!

二、擦除第一分区(其他分区类似)

PMON> mtd_erase /dev/mtd0ERASE the device:"/dev/mtd0",SKIP bad-blocksmtd_erase working:
009c0000
mtd_erase work done!

三、把u盘里的二进制拷贝到nandflash里

PMON> devcp /dev/fs/fat@usb0/vmlinuz /dev/mtd0

四、从nandflash加载内核

PMON> load /dev/mtd0

五、从串口启动ramdisk内核

PMON> grdinit=/sbin/init console=ttyS0,115200

六、若pmon下加入了yaffs2文件系统驱动,可以yaffs2加载

Supported filesystems [mtd, net, fs/yaffs2, fat, fs, disk, iso9660, socket, tty, ram]

PMON> load /dev/fs/yaffs2@mtd0/vmlinuz

七、查看nandflash分区里的目录文件

PMON> load /dev/fs/yaffs2@mtd0/

八、命令行设置nandflash分区容量,下面设置后,第一个分区40M,第二个分区40~最大容量;

PMON> set mtdparts nand-flash:40M@0(kernel)ro,-(rootfs)

3.6.2 NOR FLASH(winbond 25Q32)

ls2h_m25p_probe();   //Targets/LS2K/dev/spi_w.c:885int ls2h_m25p_probe()
{spi_initw();m25p_probe(&spi_nand1, "w25q32");spi_initr();
}

这个我们就不继续追代码了,我们看一下两个比较重要的地方。

1、spi_nand1

struct spi_device spi_nand1 =
{.dev = &ls1x_spi0,                           //ls1x_spi0 = { 0xbfff0220}  对其成员*base赋值         手册10.1上说明spi配置空间是0x1FFF_0220~0x1FFF_022F
.chip_select = 0,                              //片选选择
.max_speed_hz = 40000000,    //设置频率
};

2、“w25q32”

这个传递给probe函数,然后在 m25p_id(sys/dev/nand/m25p80.c:737)查表法,找到相应型号的nor flash,得到其相应的参数进行初始化。

最后也还会进行执行一次分区划分。要注意避免重复。

这里我们暂且没有对nor flash分区的需求,可先去掉,后续若有可通过修改Targets/LS2K/include/pmon_target.h来进行分区。

3.6 dtb校验

verify_dtb();

dtbram = (char *)(tgt_flashmap()->fl_map_base);

首先执行一个赋值,看到fl_map_base这个是不是很熟悉,我们在龙芯PMON(2K1000)启动流程(三、C语言部分①)中的envinit初始化找存储地址时,找到地址为0xbfc00000。

dtbram += DTB_OFFS;

基于这个地址然后偏移一个DTB_OFFS,也就是(NVRAM_OFFS - DTB_SIZE),这个NVRAM_OFFS的定义为0x000ff000,DTB_SIZE为0x4000(16KB)。也就是在0xbfcbf000地址处开始存储设备述dtb文件。

3.6.1 校验

dtb文件的结构可参考:设备树_dtb文件分析

我们从设备树结构中看到,没有校验和之类的标识。那么PMON是如何做校验的呢?

working_fdt = (struct fdt_header *)(dtbram + 4);

首先是如上一段代码,为什么这里要偏移4个字节?我们联想到env存储空间,开始4位存储的是余下数据和的相反数。没错,这里低4位存的就是ftd文件的累加和。所以要偏移4位。在PMON命令行模式下,我们用命令查看下:

PMON> d4 0xbfcfb000
ffffffffbfcfb000: cd4e8626
PMON> d4 0xbfcfb004
ffffffffbfcfb004: edfe0dd0     //dtb的幻数头

注意:幻数头是0xd00dfeed(dtb默认按大端顺序排列),但是我们的CPU是小端模式,所以dtb的高位实际在存储空间在低位,如上。

dtb_cksum(dtbram, DTB_SIZE - 4, 0) //这个没什么好说的就是算累加和,就是要注意一下*sp != *(sp + sz),没错,校验和在末尾的四个字节也存了一份,我们命令看一下

PMON> d4 0xbfcfb000
ffffffffbfcfb000: cd4e8626
PMON> d4 0xbfcfeffc
ffffffffbfcfeffc: cd4e8626

校验完之后,输出log:dtb verify ok!!!

3.6.2 从设备树种获取信息

这段代码只在烧写固件后,第一次执行,之后就不再执行,因为if条件不再满足,主要是check_mac_ok函数之后都返回1了

先来普及设备述操作常见操作函数:参考链接:uboot之fdt介绍

fdt_path_offset      获得dtb下某个节点的路径path的偏移。这个偏移就代表了这个节点。
fdt_getprop          获得节点node的某个字符串属性值
fdtdec_get_int_array/fdtdec_get_byte_array   获得节点node的某个整形数组属性值
fdtdec_get_addr      获得节点node的地址属性值
fdtdec_get_config_int、fdtdec_get_config_bool、fdtdec_get_config_string   获得config节点下的整形属性、bool属性、字符串等等
fdtdec_get_chosen_node 获得chosen下的name节点的偏移
fdtdec_get_chosen_prop 获得chosen下name属性的值
fdtdec_get_int         获得节点node的某个整形属性值
fdtdec_get_uint        获得节点node的某个无符号整形属性值

一、check_mac_ok

这个函数主要是从设备述网卡节点获取MAC值,tgt_ethaddr(mac_addr)获取环境变量中的MAC,然后判断是否与环境变量中的MAC地址相等。否则接下来在update_mac函数中,重置更新设备树网卡节点里的mac地址值。

update_mac中的tgt_ethaddr(mac_addr)会从hwethad数组中拷贝数据到mac_addr,hwethad这个数组在之前的初始化过程中会从环境变量中获取mac地址,然后更新设备树中网卡的mac地址。第一次烧写后初始值为全0。

(此段20220526添加)为什么初始值是0呢?在envinit ()初始化环境变量的时候,调用函数tgt_mapenv (_setenv),其中执行bcopy(&nvram[ETHER_OFFS], hwethadr, 6),对hwethadr进行赋值,通过将0xbfcff000+ETHER_OFFS处的地址值打印确实都是0。

修改mac地址通过执行:

PMON> set ethaddr 01:02:03:04:05:06

(此段20220526添加)需要注意的是,自己设置的MAC地址并不是都是合法的,我们在内核程序里看到其会对MAC地址进行校验,校验规则就是:
1、判断MAC地址是否为0
2、判断最位的字节最低位是否为1
否则认为不合规,内核会自动随机生成MAC。

int is_zero_ether_addr(const u8 *addr)
{return !(addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]);
}int is_multicast_ether_addr(const u8 *addr)
{return (0x01 & addr[0]);
}int is_valid_ether_addr_linux(const u8 *addr)
{/* FF:FF:FF:FF:FF:FF is a multicast address so we don't need to* explicitly check for it here. */return !is_multicast_ether_addr(addr) && !is_zero_ether_addr(addr);
}

(此段20220526添加)也可以参照MAC随机生成函数,意思差不多

void eth_random_addr(u8 *addr)
{get_random_bytes(addr, ETH_ALEN);addr[0] &= 0xfe;        /* clear multicast bit */addr[0] |= 0x02;        /* set local assignment bit (IEEE802) */
}

(此段20220526添加)目前手上代码并没有对环境变量中的进行校验,所以需要自己添加一下:if (!is_valid_ether_addr_linux(mac_addr))。

重启之后:

PMON> print_dtb /
...interrupt-names = "macirq", "eth_wake_irq";mac-address = [01 02 03 04 05 06];phy-mode = "rgmii";
...

(此段20220526修改)check_mac_ok在第一次执行,从设备树中获取MAC地址,然后从环境变量或者E2PROM(由宏USE_ENVMAC控制,目前是用环境变量)中获取MAC地址,然后比对两个值是否一致,不一致返回0,导致条件满足,所以后续判断则不再执行。
之后每次执行时会每次判断两个地方获取的MAC值是否一致(通过log打印,每次都比对两处获取的MAC都一样,然后返回1,导致不再执行接下来的更新MAC操作)。

二、check_prop_ok(“dma-coherent”, ls2k_version())

主要是判断设备树中是否有/soc下是否有dma-coherent。

三、check_prop_ok(“pci-probe-only”, pci_probe_only == 1)

pci_probe_only这个变量一直为2。因为设备树里没有这个参数,环境变量中也没有这个参数,这个函数一直返回1。

四、check_pci_bridge_ok

pci_probe_only这个变量一直为2。函数开始就判断pci_probe_only非0,直接返回1。

五、check_mem_args(working_fdt)

libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND

从第一次上电log来看应该是没找到"/memory"然后返回1。

好了,接下来的函数就是更新以上提到的参数,然后写入到设备树存储空间的相应位置。

3.7 main函数( pmon/common/main.c:329)

3.7.1 save_board_ddrparam(0) //pmon/cmds/save_ddrparam.c:302

如果定义了ARB_LEVEL才执行,主要作用就是从ddr控制寄存器里读参数然后存到FLASH中。地址:0xbfc0a000(值为0x1)

Now Read out DDR parameter from DDR MC0 controler after DDR training
Programming flash 8f00b9c0:5d0 into bfc0a000

3.7.2 启动

根据 ShowBootMenu环境变量的设置情况,PMON 启动流程稍有不同。

如果 ShowBootMenu 的值为 yes,则默认首先从硬盘上搜索/boot.cfg 或者/boot/ boot.cfg,如果存在则读取内容后显示相应的菜单等待用户交互后来选择要启动的内核。如果不存在,则查找是否设了 al 环境变量,如设置了,则从 al 设置的指示来启动内核,如果没设置 al,则进入 PMON console 界面。当然,在读取 boot.cfg 之前可以按 Del 来中断直接进入 PMON console 界面。

如果 ShowBootMenu 不存在或者其值不为为 yes,则 BootMenu 不起作用,PMON 将自动搜索/boot.cfg 或者/boot/boot.cfg,若搜索到,则直接按读出内容中指示的默认启动索引来启动相应内核,如果找不到,则查找是否设了 al 环境变量,如设置,则从 al 设置的指示来启动内核,如果没设置 al,则进入 PMON console 界面。当然,同样的,在这之前可以按 Del 来中断直接进入 PMON console 界面。

龙芯派的PMON没有ShowBootMenu参数,所以直接执行了:

bios_available = 1;//support usb_kbd in bios
load_menu_list();    //依次从USB  CD-ROM  sata  寻找boot.cfg
bios_available = 0;

而教育派也没有ShowBootMenu参数,执行了:

bios_available = 1;//support usb_kbd in bios
tty_flush();
do_cmd("bootdev_sel");
bios_available = 0;

我们以龙芯派执行的load_menu_list()来分析:

load_menu_list() //pmon/common/main.c:138

一、搜索硬盘
这里wd和sd都分别代表的是硬盘,只不过在不同的pmon中可能显示的盘符不一样,以龙芯派举例:

PMON> devls
Device name  Type
loopdev0     DISK
syn0         IFNET
syn1         IFNET
usb0         DISK
wd0          DISK

这里找到了是wd,所以rootdev = "/dev/fs/ext2@wd0,这里只是赋值,相关动作在最后。

二、搜索U盘

strncmp(dev->dv_xname, “usb”, 3)找到U盘后,分别依次从以下几个类别寻找/boot.cfg或者/boot/boot.cfg,以下都以/boot.cfg示例。

  1. bl -d ide /dev/fs/ext2@usb0/boot.cfg //从 U 盘或者移动硬盘加载,格式为ext2
  2. bl -d ide /dev/fs/fat@usb0/boot.cfg //从 U 盘或者移动硬盘加载,格式为fat
  3. bl -d ide /dev/fs/iso9660@usb0/boot.cfg //从 USB 光驱加载

我以fat格式的u盘做启动盘,所以在启动log可以看到

usage: bl -d cdrom/ide boot_config_file
/we can't locate root directory in super block!
usage: bl -d cdrom/ide boot_config_file
get partition error: 0
usage: bl -d cdrom/ide boot_config_file
get partition error: 0
usage: bl -d cdrom/ide boot_config_file
\

第四种情况才没报错而停下,对应的是 sprintf(load, “bl -d ide /dev/fs/fat@%s/boot.cfg”, dev->dv_xname)所以接下来do_cmd(load)之际上就是执行命令bl -d ide /dev/fs/fat@usb0/boot.cfg 。

三、搜索CD-ROM

盘符为cd0,如果找到执行:bl -d ide /dev/fs/iso9660@cd0/boot.cfg

四、硬盘加载

上面提到硬盘搜索只是找到盘符,这里如果检查无误执行:bl -d ide wd0/boot.cfg

五:bl -d ide /dev/fs/ext2@wd0/boot/boot.cfg

显示:

加载位于硬盘第一个分区的(pmon 只认第一个分区)。 调用cmd_menu_list处理函数

cmd_menu_list     (pmon/cmds/menulist2f.c:388)-----show_main     // (pmon/cmds/menulist2f.c:271)    ------load_list_menu       获取boot.cfg文件内容------draw_main            绘制基本界面------while(1)             主要是获取按键信息调整显示界面,如果是c直接return 0,导致最终命令执行失败------do_cmd_boot_load  如果获取到的是回车键,则以选中的系统参数启动     pmon/cmds/boot_cfg.c:814---boot_load ----boot_load_from_menu

====>show_main函数解析命令获取path值,path:/dev/fs/ext2@wd0/boot/boot.cfg,调用load_list_menu
====>在load_list_menu函数中,通过路径打开传入的cfg文件,并在menu_list_read函数中,将cfg文件中的信息保存
1)将title、内核、参数、initrd以及root信息保存在全局结构体数组menu_items中。
2)将boot.cfg中的全局参数(如下面显示的menu_options中的参数)保存到全局变量menu_option中。

MenuOptions menu_options[] = {{"showmenu", 0, 0, "1"},      //是否给用户显示菜单{"default", 0, 0, "0"},              //默认选择的启动项  可以在boot.cfg中更改{"timeout", 0, 0, "5"},            //超市时间{"password", 0, 1, ""},{"md5_enable", 0, 0, "0"},      /* 1-md5 */
};

====>show_main函数继续执行,从全局选项(menu_options)中获取默认启动的菜单条目以及菜单超时的默认时间。清屏并执行draw_main函数。

====>draw_main函数在屏幕上显示可选择的启动菜单以及提示信息。

====>以后等待用户输入。当输入c时,退出菜单回到pmon命令行。当获取到上下键选择菜单,则根据选择得到启动的内核对应的条目。

====>最后调用do_cmd_boot_load函数加载启动内核。

启动:

------do_cmd_boot_load----boot_load                                    //过度函数,只是判断索引可超限----boot_load_from_menu                          //加载内核以及initrd----load_kernel_from_menu----load_initrd_from_menu----boot_run_from_menu ---->  boot_kernel    //真正启动函数do_cmd(cmd)

====>boot_load_from_menu分别通过load_kernel_from_menu以及load_initrd_from_menu加载内核以及initrd。最后通过boot_run_from_menu函数运行内核。

1、load_kernel_from_menu
====>在load_kernel_from_menu函数中,根据菜单选择的内核条目,传入对应的menu_items结构体数组成员。
====>在boot_kernel函数中,根据传入的内核路径打开内核文件描述符。通过函数exec函数调用ep = (*p->loader) (fd, buf, n, flags))。这里的函数指针为load_elf()函数在pmon/loaders/exec_elf.c:396中。内核入口Entry address is 817b0000
2、load_initrd_from_menu
调用boot_initrd函数通过exec将initrd加载地址为0x84000000的内存中。

3、boot_run_from_menu
构造启动引导命令:g root=UUID=1ad4bb53-257d-4036-87a0-75db95327687 ro rhgb quiet loglevel=0 LANG=zh_CN.UTF-8。执行do_cmd,跳转到相应注册到pmon的命令处理函数,这里跳转到cmd_go(pmon/cmds/cmd_go.c:101)函数中来引导内核。

主要输出log如下

-Now booting the 'Loongnix GNU/Linux'
\Loading file: (wd0,0)/vmlinuz-3.10.0-693.fc21.loongson.2k.12.mips64el (elf)
(elf)
|0x817b0000/8859698 + 0x82023032/4202542(z) +
Entry address is 817b0000
Loading initrd image (wd0,0)/initramfs-3.10.0-693.fc21.loongson.2k.12.mips64el.im-(bin)
-
Loaded 16957524 bytes
Boot with parameters: root=UUID=1ad4bb53-257d-4036-87a0-75db95327687 ro rhgb quiet loglevel=0 LANG=zh_CN.UTF-8

=========================cmd_go(pmon/cmds/cmd_go.c:101)

clientav指向命令行参数。调用initstack函数,进行一些重要的初始化。

==>initstack(pmon/common/main.c:1032) //计算传参长度以及bp长度设置栈大小。

输出log:

ac = 0000000b, nsp @ 8f00fe00, env @ ffffffff, en @ 8f00ff28
vsp = 08xffffffff8f00fe30, ssp @ 08xffffffff8f00ff28

==>envbuild (vsp, ssp)

输出log:

memory_offset = 0x2428b8;cpu_offset = 0x2434c4; system_offset = 0x243520; irq_offset = 0x245894; interface_offset = 0x2458e8;

==>bp = (struct boot_params *) ssp;

输出log:

board_name:Loongson-2K-SOC-1w-V0.6-demo ---0x8f255880 10
Shutdown:0x20006963 reset:0x8f06b890

==>setup_dtb(Targets/LS2K/dev/load_dtb.c:718)

解析dtb,检查DTB的地址以及checksum是否正常,调整a2寄存器的值。将DTB数据拷贝到前面设置好的堆栈中。更新设备树中关于/chosen节点的数据,将命令行参数将其覆盖。

重要!!!

这里做了一件神奇的事情就是,将设备加载到内存然后将这个内存首地址赋值位a2寄存器传给内核使用。下面的打印可以看到其值是0x8f800000。这个值是怎么来的?

char * ssp = heaptop;    //用于下面将设备树拷贝到这个地址memcpy(ssp, (char *)working_fdt, DTB_SIZE - 8);        working_fdt前面已经说过0xbfcfb000
tf->a2 = heaptop;            //这个就是将寄存器a2赋值为0x8f800000

heaptop为什么是heaptop。主要是init_heaptop函数的作用,没错是__ctors 函数,早早的执行了。在这个函数里有个heaptop = 0x8f800000;赋值的操作。

static void init_heaptop __P((void)) attribute ((constructor)); pmon/common/sbrk.c:93

==>initstack
函数返回,打印32个通用寄存器的值。设置状态寄存器以及硬件相关设置。更新配置pcie。执行goclient函数将控制权交给内核。至此pmon执行完毕。

  zero      at       v0       v1       a0       a1       a2       a3   00000000 00000000 00000000 00000000 0000000a 8f00fe28 8f800000 8f142160t0       t1       t2       t3       t4       t5       t6       t7   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000s0       s1       s2       s3       s4       s5       s6       s7   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000t8       t9       k0       k1       gp       sp       s8       ra   00000000 00000000 00000000 00000000 00000000 8f00fe08 00000000 8f0b2878

3.7.3 命令行

如果异常或者超时时间内按下"c"键,启动过程会被打断,则回到main函数。

接下来依次检查环境变量中的FR、al、al1是否为空,如果为空则更新其值,然后根据al和al1参数进行autoload。如果失败,则进入while(1)命令行模式。

我们通过命令行可以看到FR、al、al1都是有值的,如下:

PMON> env
FR = 1
al = (usb0,0)/boot/vmlinuz
append = “‘console=ttyS0,115200 console=tty initcall_debug=1 loglevel=20’”
al1 = (wd0,0)/boot/vmlinuz
uuid = 9C93CB76-F36B-1400-0963-000000000000
activecom = 0x00

那这值是从哪里来的呢?来至 zloader/Makefile.inc:148

[ -f gzrom.bin ] && cp gzrom.bin gzrom-dtb.bin && python ../tools/pmonenv.py -f gzrom-dtb.bin -d ${TARGET}.dtb -w  al=\(usb0,0\)/boot/vmlinuz    al1=\(wd0,0\)/boot/vmlinuz     append="'console=ttyS0,115200 console=tty initcall_debug=1 loglevel=20'"    FR=1

我们以U盘启动失败的条件来看log:

AUTO
Press <Enter> to execute loading image:(usb0,0)/boot/vmlinuz      //al
Press any other key to abort.
00
Find device failed NULL==pdev.
(usb0,0)/boot/vmlinuz: No such file or directory
PMON>

我们以硬盘启动失败的条件来看log:

-AUTO
Press <Enter> to execute loading image:(wd0,0)/boot/vmlinuz
Press any other key to abort.
02
PMON>

3.8 附录

pmon下常用命令

龙芯PMON(2K1000)启动流程(三、C语言部分③)相关推荐

  1. 龙芯PMON(2K1000)启动流程(三、C语言部分②)

    3.3 tgt_devinit(Targets/LS2K/ls2k/tgt_machdep.c:699) tgt_devinit函数完成pmon阶段PCI设备的初始化,tgt_devinit调用了_p ...

  2. 龙芯PMON(2K1000)启动流程(三、C语言部分①)

    1.程序框架 ├──initmips(raw_memsz) //Targets/LS2K/ls2k/tgt_machdep.c:231 │ ├────ejtag_init(); //由DEBUG_BY ...

  3. 龙芯PMON(2K1000)启动流程(二、汇编部分)

    1.pmon 文件相关的地址问题   cpu眼中的地址是虚拟地址,cpu 取指和取数据的地址是物理地址,经过北桥解释后的地址是总线地址,编译器产生的地址(包括解析了所有引用和重定位的符号后)为程序地址 ...

  4. 龙芯PMON(2K1000)启动流程(一、总述)

    一.总流程 1.ls2k1000 cpu开始执行start.S(Targets/LS2K/ls2k/start.S)中的代码 2.然后跳转到 initmips(-)(zloader.ls2k/init ...

  5. 深入理解Activity启动流程(三)–Activity启动的详细流程2

    本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--A ...

  6. U-boot启动流程[三]

    U-boot启动流程[三] 文章目录 U-boot启动流程[三] 1 Linux启动基础镜像 1.1 内核镜像 1.1.1 vmlinux镜像 1.1.2 Image和zImage镜像 1.2 设备树 ...

  7. 龙芯多核处理器启动概要

    0.约定 为了便于理解,根据最新版的<龙芯3A处理器用户手册>约定以下术语: 处理器节点:包含4个GS464核的1个3A处理器称为1个处理器节点 处理器核: 3A中的每个GS464核就是一 ...

  8. 在x86_64平台上编译龙芯pmon

    编译环境 系统:deepin15.11 平台:x86_64 交叉编译器:gcc-4.4.7-7215-n64-loongson 搭建交叉编译环境 见在x86_64平台上搭建龙芯MIPS64交叉编译环境 ...

  9. bootload启动流程(三)--Eboot每个函数的详细说明

    由于eboot虽小但是各个功能都具有,所以也是一个比较复杂的过程,下面将对它下面的主要函数进行说明,这里面好多函数都是与nk共用的,所以大多代码并不在eboot下面,而是在public下面. 1)Ke ...

最新文章

  1. python的turtle怎么设置rgb颜色_Python : turtle色彩控制实例详解
  2. PL/SQL Developer如何修改表数据
  3. android杀掉进程顺序
  4. python gpu编程_Python笔记_第四篇_高阶编程_进程、线程、协程_5.GPU加速
  5. Ubuntu18.04LTS+Anaconda+Tensorflow-GPU安装记录
  6. JS Cookies
  7. 刘海屏的MacBook Pro还值得入手吗?
  8. 原理图端口符号_电气原理图与接线图的区别和联系
  9. 非华为电脑安装华为电脑管家|多屏协同|非华为电脑与matepad11多屏协同|matepad11连接联想yoga14s|老版华为电脑管家
  10. python柱状图显示数值_Python实现绘制双柱状图并显示数值功能示例
  11. 计算机基础知识测试1,计算机基础知识测试试题及答案(网络)1
  12. win10注册表WOW6432Node
  13. 计算机毕设存档袋子,关于做好2018届毕业论文(设计)材料整理存档的通知
  14. Webpack 如何抽离、压缩 CSS 文件?
  15. 京东上位2018年财富中国500强民企第一席 首次实现全年盈利
  16. 一个简单到令人发指的 ADRCI 工具操作方法
  17. 贝叶斯推断应用:垃圾邮件过滤
  18. Django邮件应用--QQ邮箱、网易邮箱(一)
  19. Meta Cambria手柄曝光,主动追踪+多触觉回馈方案
  20. 编程王 kingofcoders.com

热门文章

  1. 豌豆荚手机助手电脑版 v2.75.0.6063 官方pc版
  2. python的drop duplicates_pandas.DataFrame.drop_duplicates 用法介绍
  3. centos7下yum安装php mysql数据库_centos7下yum方式安装mysql5.7
  4. 问题空间让理工男也敢追求自己心仪的女孩
  5. Vue.js + Restful + PageHelper + Thymeleaf + Springboot 前后端分离 增删改查 CRUD 教程
  6. 券商提供的程序化交易接口可以做高频交易吗?
  7. 科技爱好者周刊(第 203 期):英国的名校签证,伯克利的计算机教育
  8. Android翻页效果原理实现之翻页的尝试
  9. java实现12306接口查询_记录一次调用12306查询。
  10. Vuex简介(带操作实例)