1. head.s -> start_kernel -> setup_arch
setup_arch 会获取平台文件中定义的struct machine_desc,所有关于平台的初始化操作都依据machine_desc来进行。具体的编译条件又是依据arch/arm/configs中的xxx_defconfig
2. 根据上述判断,HAL(BSP)的编写完全是以machine_desc为入口
需要编写的内容包括plat/xx以及mach_xx/下的 内核节拍、中断管理、时钟、GPIO、DMA、IO内存映射、管脚复用、外部设备注册和资源提供 相关的代码。 外部设备和设备资源在板子初始化中注册,设备驱动则放在/drivers目录中。Linux BSP(这里不考虑bootloader 以及 文件系统 )开发的层次有3个级别:1.体系结构级别的开发。2.SOC级别的移植。3.设备驱动的移植。体系结构级别的开发一般是由Linux内核社区或体系结构厂商完成后加入到Linux内核源码树中,这一级别的开发主要完成内存管理、进程调度、异常、陷阱等,开发过程中需要参考体系结构的datasheet,比如arm920TDMI/Cortex A8甚至还有armV6/armV7指令集架构。实际上在Linux的arch目录下几乎已经支持了现今所有的体系结构,所以一般不会去进行体系结构级别的BSP开发。最常见的还是SOC级别的BSP开发和驱动开发,SOC级别的BSP开发只需要参考SOC的datasheet,比如S3C2410/MX53。驱动开发不仅要参考SOC的datasheet还要参考PCB设计原理图。本文关注的就是SOC级别的开发或移植。 通俗来讲,就是要让一套官方标准的linux源码在自己的板子上跑起来。
3. struct machine_desc成员以及调用时机
在setup_arch()中init_irq, timer & init_machine分别被赋值给下列变量:
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
而这三个函数指针是在下列场景中被调用的:
start_kernel()-> init_IRQ() [irq.c] ->init_arch_irq();
start_kernel()->time_init () [time.c] ->system_time->init();
customize_machine()-> init_machine();
customize_machine是被放在arch_initcall段的,按照顺序被调用。xxx_initcall段内的函数是按下列顺序被调用的:start_kernel()-> rest_init() [启动内核线程]-> kernel_init() –> do_basic_setup()-> do_initcalls();
map_io是在下列顺序中被调用
start_kernel()-> setup_arch ()-> paging_init()-> devicemaps_init()-> map_io()
fixup是在下列顺序中被调用
start_kernel()-> setup_arch ()->setup_machine_tags()
init_early是在下列顺序中被调用
start_kernel()-> setup_arch ()->init_early
它们在start_kernel()中被调用的顺序,可知它们执行的先后为:fixup -> map_io -> init_early -> init_irq -> timer -> init_machine ,所以一般就应该按照各个顺序依次来实现。
4. fixup在mx53中虽然设置了,但是是一个空函数
5. map_io完成的工作
void __init mx6_map_io(void)
{
iotable_init(mx6_io_desc, ARRAY_SIZE(mx6_io_desc));
mxc_iomux_v3_init(IO_ADDRESS(MX6Q_IOMUXC_BASE_ADDR));
mxc_arch_reset_init(IO_ADDRESS(MX6Q_WDOG1_BASE_ADDR));
mx6_set_cpu_type();
mxc_cpu_lp_set(WAIT_CLOCKED);
}
iotable_init就是进行静态内存映射,把一些物理地址永久的映射到固定的虚拟地址上去;这些虚拟地址必须是处于内核空间的;为什么要进行映射呢?在驱动中可以使用ioremap函数进行动态映射,对于那种在系统运行期间都需要被频繁访问的物理地址就可以使用静态映射;或许被静态映射的区域不宜过大,mx6q上没有超过4M;被映射的物理地址,在以后的访问中直接使用固定的虚拟地址就可以访问到;或许使用静态映射就可以把分散的物理地址集中映射到一片连续虚拟地址区域,这样可以减少虚拟地址空间的碎片?
mxc_iomux_v3_init设置IOMUXC寄存器块对应的虚拟地址,因为这块物理地址区域已经被静态映射了;
mxc_arch_reset_init设置watchdog寄存器块对应的虚拟地址,因为这块物理地址区域已经被静态映射了;
mx6_set_cpu_type读取寄存器,获取处理器的型号mx6sl/mx6q/mx6dl以及型号的版本1/2/3
mxc_cpu_lp_set把CPU设置为指定的低功耗状态;这个函数会在系统功耗管理代码中被使用;
6. init_early在mx6中没有被设置
7. init_irq完成的工作
void mx6_init_irq(void)
{
void __iomem *gpc_base = IO_ADDRESS(GPC_BASE_ADDR);
struct irq_desc *desc;
unsigned int i;
gic_init(0, 29, IO_ADDRESS(IC_DISTRIBUTOR_BASE_ADDR),IO_ADDRESS(IC_INTERFACES_BASE_ADDR));
if (enable_wait_mode) {
/* Mask the always pending interrupts - HW bug. */
__raw_writel(0x00400000, gpc_base + 0x0c);
__raw_writel(0x20000000, gpc_base + 0x10);
}
for (i = MXC_INT_START; i <= MXC_INT_END; i++) {
desc = irq_to_desc(i);
desc->irq_data.chip->irq_set_wake = mx6_gic_irq_set_wake;
}
mx6q_register_gpios();
#ifdef CONFIG_CPU_FREQ_GOV_INTERACTIVE
for (i = 0; i < ARRAY_SIZE(mxc_irq_tuner); i++)
cpufreq_gov_irq_tuner_register(mxc_irq_tuner[i]);
#endif
#ifdef CONFIG_PCI_MSI
imx_msi_init();
#endif
}
gic_init暂时不知道是什么
mx6q_register_gpios()初始化GPIO,或者可以把这里看做是GPIO驱动的入口,GPIO作为一种系统必备资源,需要在arch初始化的时候被加入系统;
8. timer完成的工作
static void __init mx6_sabresd_timer_init(void)
{
struct clk *uart_clk;
#ifdef CONFIG_LOCAL_TIMERS
twd_base = ioremap(LOCAL_TWD_ADDR, SZ_256);
BUG_ON(!twd_base);
#endif
mx6_clocks_init(32768, 24000000, 0, 0);
uart_clk = clk_get_sys("imx-uart.0", NULL);
early_console_setup(UART1_BASE_ADDR, uart_clk);
}
mx6_clocks_init可以认为是时钟驱动,这里完成了所有外设控制器的时钟初始化;
early_console_setup在系统启动期间也是需要串口输出的,这里赶紧设置好串口UART的时钟?really?
9. init_machine完成的工作
无论在mx53还是mx6中,或者是在s3cc24xx系列中,init_machine所做的事情非常的一致,那就是向内核注册控制器device;主要包括uart、i2c、spi、GPU、IPU、fb等等;此外还要添加一些板子上的设备,比如i2c设备、固化的MMC、网络的Phy芯片等等;其实这个函数完全就和linux驱动密切相关了,我就不把它归入BSP开发了;