| 
 
 
 Windows系统:Windows10 x64  vmware:VMware Workstation 15 Pro  Linux系统:Ubuntu16.04 x64  BootLoader:u-boot-2010.03  Linux内核:Linux2.6  编译链:gcc-3.4.5-glibc-2.3.6  板子介绍:与SMDK2416使用同样的ARM926内核,其它硬件资源都不一样。我们就基于smdk2416移植。  
一、uboot跳转到Linux 
需进行的工作如下:  
1、设置好Linux内核的机器码bi_arch_number,必须与Linux内核支持的机器码相等才能正常启动Linux;  假设想在2440板子上启动内核,则需在board/xxx/mini2440.c的int board_init (void)函数中修改  gd->bd->bi_arch_number = MACH_TYPE_SMDK2440; //机器码定义在mach-types.h。  gd->bd->bi_boot_params = xxx; //bi_boot_params是启动参数的地址  
2、设置好传递给Linux的启动参数bootargs,bootargs解析:  root:    根据文件系统存储的位置(flash、网络)不同,结合实际存储情况进行设置。例如:    root=/dev/mtdblockx rw(x=0,1,2…)    root=/dev/nfs rw nfsroot=10.103.4.216:/nfsroot/rootfs ip=10.103.4.211    root=/dev/ram0 rw      console:    console=ttySAC0,115200 控制台使用串口0,波特率115200.  串行端口终端(/dev/ttySn )  控制终端(/dev/tty )  控制台终端(/dev/ttyn, /dev/console )  
init:  init 指定的是内核启起来后,进入系统中运行的第一个脚本,一般为init=/linuxrc, /linuxrc指的是/目录下面的linuxrc脚本,一般是一个连接。  
initrd, noinitrd:    当使用ramdisk启动系统的时候,需要指定initrd=r_addr,size。 r_addr表示initrd在内存中的位置,size表示initrd的大小。否则使用noinitrd。  
根据实际情况在include/configs/mini2440.h中设置默认的bootargs宏。  
3、移植好网络驱动,使开发板和电脑可以进行tftp文件传输;方便调试;  
4、用tftp把制作好的内核镜像uImage加载到RAM非首地址;  
5、使用bootm XXX指令,加载并执行XXX处的Linux内核镜像uImage;如果校验失败uboot打印失败信息,不会跳转。  
二、 Linux内核启动之解压阶段 
内核加载位置设置需要修改arch\arm\mach-s3c2410\Makefile.boot (虽然用的是基于SMDK2416的内核代码,但是加载地址修改都是在mach-s3c2410\Makefile.boot)  zreladdr-xxx := 0xX0008000 //linux内核加载地址  params_phys-xxx := 0xX0000100 //uboot传输过来的参数地址  
为什么要偏移0x8000呢?因为0xX0000000到0xX0008000被用来存放uboot传递的参数和内核MMU Table。  
Uboot成功跳转到内核部分后,Linux首先运行arch\arm\boot\compressed\head.S文件,然后跳转到arm\boot\compressed\misc.c中运行decompress_kernel()函数,进行内核校验和解压:  
makecrc();
putstr("Uncompressing Linux...");
gunzip();
putstr(" done, booting the kernel.\n");
  
putstr打印函数,会调用include\asm-arm\arch-s3c2410\uncompress.h中的static void putc(int ch)函数,如果uboot阶段已经移植好了串口,那这里我们可以直接将putc函数改写为直接往TX FIFO填充数据,即可实现最早期的打印。  
static void putc(int ch)
{
	
}
  
注意:putc的原有实现函数与我们开发板不符,必须注释掉或者改为自己的打印函数。由于还没有MMU映射,该函数实现使用寄存器的物理地址。  
三、 Linux内核启动之汇编阶段 
arch\arm\boot\compressed\head.S解压操作执行成功后跳转到 arch\arm\kernel\head.S  在arch\arm\kernel\head.S中,会判断cpu和机器码是否支持。  
3.1 __lookup_processor_type,核对CPU:  每个型号的CPU有个参数结构体存在于汇编文件例如arch/arm/mm/proc-arm926.S中,包括CPU ID和掩码等众多信息。该汇编函数读取CPU ID,进行掩码比对,如果内核中有CPU ID与之相等则判断为内核支持该CPU,否则停止运行。  
3.2 __lookup_machine_type,,核对机器码;  内核中对于每种支持的开发板都会使用宏MACHINE_START、MACHINE_END来定义一个machine_desc结构,它定义开发板相关的一些属性及函数,比如机器类型ID、起始I/O物理地址、Bootloader传入的参数的地址、中断初始化函数、I/O映射函数等。  我们可以在arch\arm\tools\mach-types.h中仿照smdk2416建立一个自己的开发板型号XXX:  
smdk2416		MACH_SMDK2416		SMDK2416			1685
  
然后在arch\arm\mach-myboard\mach-myboard.c文件中,我们进行如下开发板信息定义,本节只讲解型号名的由来和使用,内部函数后面详细讲解:  
MACHINE_START(XXX, "XXXX")
	
	
	.boot_params	= 0xX0000100, 
	.init_irq	    = xxx_init_irq, 
	.map_io		    = xxx_map_io, 
	
	.init_machine	= xxx_machine_init,
	.timer		    = &s3c24xx_timer, 
MACHINE_END
  
所有machine_desc结构体都处于“.arch.info.init”段中,在连接内核时,它们被组织在一起,开始地址为__arch_info_begin,结束地址为__arch_info_end。  
做好以上工作后__lookup_machine_type汇编函数就能将uboot传来的机器码与内核支持的机器码比对,一致才能正常运行,否则停止运行。  
3.3__create_page_tables:  因为汇编阶段会执行__turn_mmu_on打开MMU,所以必须进行部分地址映射。建立一个临时的page table(将来这个table会被清除,重新建立)。  
在该函数后加入串口寄存器映射,这样开启MMU后用虚拟地址也能进行串口打印信息:  
add	r0, r4, #0xXX000000 >> 18
orr	r3, r7, #0xXX000000
str	r3, [r0]
  
3.4 arch\arm\kernel\head-common.S  b start_kernel  跳转到C语言启动函数start_kernel();  
四、 Linux内核启动之C语言阶段 
汇编语言跳到init\main.c中的asmlinkage void __init start_kernel(void)函数,asmlinkage是一个宏定义,主要是声明这个函数是给汇编代码调用的。  该阶段执行众多初始化函数,根据CPU架构和板级型号不同,又会调用众多板级相关文件,直至完成文件系统启动前的所有工作。  start_kernel–>rest_init–>kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND)–>init–>  init_post–>run_init_process(execute_command) //execute_command=/linuxrc,由uboot指定。  修改include/asm-arm/arch-s3c2410/system.h的s3c24xx_default_idle():  void cpu_idle(void)–>default_idle()–>arch_idle()–>s3c24xx_default_idle(),因为板子不同,这里需要把该函数注释掉,不然内核运行崩溃。  
五、板级重要函数修改 
arch\arm\mach-xxx\mach-xxx.c  板级相关.c文件,把裸机代码中通用的c文件尽量都移植到此处。  include/asm-arm/plat-s3c24xx/xxx.h  板级相关.h文件,其它文件需要引用板级资源时,需要包含该头文件,注意和实际的绝对路径名不同(Linux特殊性):  #include <asm/plat-s3c24xx/xxx.h>  
mach-xxx.c文件包含以下重要的板级相关宏定义:  
MACHINE_START(XXX, "XXX")
...
MACHINE_END
  
MACHINE_START、MACHINE_END都是定义的宏,代码如下  
#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,
 
#define MACHINE_END				\
};
  
简化展开一下,就是定义了一个如下结构体:  
	static const struct machine_desc = {
		.nr		= MACH_TYPE_ xXX,
		.name	= xXX,
		.boot_params	= 0xx0000100,
		.init_irq	    = xxx_init_irq, 
		.map_io		    = xxx_map_io,
		.init_machine	= xxx_machine_init,
		.timer		    = &s3c24xx_timer, 
	}
  
5.1 MMU映射函数.map_io  这是该结构体中最先执行的成员函数。作用主要是MMU映射;部分硬件初始化,例如串口,也可以放在该成员函数中。  
static void __init xxx_map_io(void)
{
    xxx_init_io();
}
  
arch\arm\plat-s3c24xx\cpu.c:  
void xxx_init_io(void)
{
    iotable_init(xxx_iodesc, ARRAY_SIZE(xxx_iodesc));
}
static struct map_desc xxx_iodesc[] __initdata = {
	{
		    .virtual	= (0xF0200000),
		    .pfn		= __phys_to_pfn(0xXX000000),
		    .length		= 0x00100000,
		    .type		= MT_DEVICE,
    },
	{
		    .virtual	= (0xF0600000),
		    .pfn		= __phys_to_pfn(0xXX000000),
		    .length		= 0x00100000,
		    .type		= MT_DEVICE,
    },
};
  
asm-arm\arch-s3c2410\memory.h  
#define PHYS_OFFSET	UL(0xX0000000)  
  
Linux内核汇编结束后就已经开启了MMU功能,后面使用的所有地址都必须是映射过的。内核会自动完成两部分的映射,一是RAM地址PHYS_OFFSET,即将0xX0000000映射到0xC0000000,大小我们可以在bootargs的mem=XX M指定。第二个是中断向量表0xX0001000映射到0xFFFF0000,大小为0x00001000。因为ARM中断向量表可以存放于两个地址:0和0xFFFF0000。可通过设置ARM寄存器设置用哪个地址的中断向量表。这两部分映射是内核隐式完成,我们只需指定RAM起始地址和要使用的大小。  
内核中映射有两种方式。  
静态映射:将指定虚拟地址映射到物理地址,虚拟地址是已知的、固定的;  
动态映射:ioremap等函数实现,映射的虚拟地址是随机的。因为我们操作寄存器时有一些宏定义操作,地址已知更方便编程,所以采用静态映射。  
而struct map_desc xxx_iodesc[]里就是我们指定的静态映射数组,主要映射了串口寄存器等。  .virtual是要映射的虚拟地址,地址范围涉及到内核空间分布知识,IO寄存器映射一般映射到0xF0000000到0xFF000000。  .pfn是要映射的物理地址。  .length是要映射的地址长度。  .type是要映射的地址类型,不同类型的权限是不同的,需要按照地址的属性正确设置,否则会带来意料之外的bug。  
arch\arm\mm\mmu.c :该文件不用改,但调试的时候可能需要用到如下两个函数。  
static inline void prepare_page_table(struct meminfo *mi)
{
	
	for (addr = 0; addr < MODULE_START; addr += PGDIR_SIZE)
		pmd_clear(pmd_off_k(addr));
}
#define PAGE_OFFSET		UL(0xc0000000)
#define MODULE_END		(PAGE_OFFSET)
#define MODULE_START	(MODULE_END - 16*1048576)
  
该函数可能会清除我们在汇编语言建立好的映射,所以在该函数之后、xxx_map_io之前,注意虚拟地址的使用问题。  
void __init create_mapping(struct map_desc *md)
{
printk(KERN_INFO "map PHYS:0x%08llx ,VIRT:0x%08lx ,length:0x%08lx\n",
		       __pfn_to_phys((u64)md->pfn), md->virtual, md->length);
}
  
所有的映射都会走该程序,所以我们加个如上打印,显示映射的物理地址、虚拟地址、映射长度,防止映射有冲突。  
后续有空再写。 
                
                
                
        
        
    
 
 |