tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)

发布时间:
来源: 电子工程世界

开发板:tiny4412ADK+S700 4GB Flash

主机:Wind7 64位

虚拟机:Vmware+Ubuntu12_04

u-boot:U-Boot 2010.12

Linux内核版本:linux-3.0.31

Android版本:android-4.1.2

 

在arch/arm/mach-exynos/mach-tiny4412.c中:

MACHINE_START(TINY4412, "TINY4412")

    .boot_params    = S5P_PA_SDRAM + 0x100,

    .init_irq    = exynos4_init_irq,

    .map_io        = smdk4x12_map_io,

    .init_machine    = smdk4x12_machine_init,

    .timer        = &exynos4_timer,

    .reserve    = &exynos4_reserve,

MACHINE_END


在文件arch/arm/kernel/setup.c中:


static int __init customize_machine(void)

{

    if (machine_desc->init_machine)

        machine_desc->init_machine();

    return 0;

}

arch_initcall(customize_machine);

在文件arch/arm/plat-samsung/init.c中:


static int __init s3c_arch_init(void)

{

    int ret;

    ret = (cpu->init)();

    ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);

    return ret;

}


arch_initcall(s3c_arch_init);


这几个函数跟uart有关,我们先看一下内核启动的时候是如何调用这个函数:


start_kernel   (init/main.c)

   --- setup_arch  (arch/arm/kernel/setup.c)

        --- paging_init  (arch/arm/mm/mmu-cma.c)

             --- devicemaps_init (arch/arm/mm/mmu-cma.c)

                  --- mdesc->map_io() ----- smdk4x12_map_io

   --- rest_init

             ---kernel_init 

                      --- do_pre_smp_initcalls (init/main.c)

                      --- do_basic_setup

                                  --- do_initcalls


static void __init do_pre_smp_initcalls(void)

{

    initcall_t *fn;


    for (fn = __initcall_start; fn < __early_initcall_end; fn++)

        do_one_initcall(*fn);

}


static void __init do_initcalls(void)

{

    initcall_t *fn;


    for (fn = __early_initcall_end; fn < __initcall_end; fn++)

        do_one_initcall(*fn);

// 在这里会调用customize_machine和s3c_arch_init

}


在文件include/linux/init.h中:


#define __define_initcall(level,fn,id)

    static initcall_t __initcall_##fn##id __used

    __attribute__((__section__(".initcall" level ".init"))) = fn


#define arch_initcall(fn)        __define_initcall("3",fn,3)


对于arch_initcall(customize_machine)展开后就是:


    static initcall_t __initcall_ customize_machine3 __used

    __attribute__((__section__(".initcall3.init"))) = customize_machine

在arch/arm/kernel/vmlinux.lds中:


  __initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcallbresume.init) *(.initcallresume.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;

通过上面的分析,大致知道这几个函数的调用过程了。



下面继续分析:


static void __init smdk4x12_map_io(void)

{

    clk_xusbxti.rate = 24000000;

// 宏S5P_VA_CHIPID的值是S3C_ADDR(0x02000000)

    s5p_init_io(NULL, 0, S5P_VA_CHIPID);  // 获取cpu id

    s3c24xx_init_clocks(24000000);       // 初始化时钟资源

    s3c24xx_init_uarts(smdk4x12_uartcfgs, ARRAY_SIZE(smdk4x12_uartcfgs)); 

/* 

初始化uart,此时并没有进行注册,只是填充了一些结构体,提供给注册时用,数组smdk4x12_uartcfgs中是关于每一个uart控制器的寄存器默认参数,如:

static struct s3c2410_uartcfg smdk4x12_uartcfgs[] __initdata = {

    [0] = {

        .hwport        = 0,

        .flags        = 0,

        .ucon        = SMDK4X12_UCON_DEFAULT,

        .ulcon        = SMDK4X12_ULCON_DEFAULT,

        .ufcon        = SMDK4X12_UFCON_DEFAULT,

    },

...

}

*/

    exynos4_reserve_mem();  // 预留内存

}


下面我们一一分析上面的每个函数


 s5p_init_io 函数


void __init s5p_init_io(struct map_desc *mach_desc,

            int size, void __iomem *cpuid_addr)

{

    /* initialize the io descriptors we need for initialization */

/*

 数组s5p_iodesc中记录了一些物理地址和虚拟地址的对应关系,如:

static struct map_desc s5p_iodesc[] __initdata = {

    {

        .virtual    = (unsigned long)S5P_VA_CHIPID,

        .pfn        = __phys_to_pfn(S5P_PA_CHIPID),

        .length        = SZ_4K,

        .type        = MT_DEVICE,

    }, 

……

}


*/

    iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc)); // 静态映射内存资源

    if (mach_desc)

        iotable_init(mach_desc, size);


    /* detect cpu id and rev. 

这里cpuid_addr的值是S5P_VA_CHIPID,从上面的代码可以知道S5P_VA_CHIPID对应的物理地址是S5P_PA_CHIPID,即0x10000000,这个是exynos4412的PRO_ID寄存器,通过在u-boot读取到寄存器0x10000000的值,注意:由于在u-boot中开启了mmu,需要判断这个物理地址0x10000000对应的虚拟机地址是多少,还好,我们的u-boot中将物理地址0x0000_0000 -- 0x1FFF_FFFF 映射到了0x0000_0000 -- 0x1FFF_FFFF,所以我们直接使用命令 md 0x10000000 即可,我试了一下,结果如下:

TINY4412 # md 0x10000000 0x4

10000000: e4412011 08051008 00000009 00000010    . A............

*/

    s5p_init_cpu(cpuid_addr);  // 读取cpu_id

/* 

下面是函数s5p_init_cpu的实现

void __init s5p_init_cpu(void __iomem *cpuid_addr)

{

    samsung_cpu_id = __raw_readl(cpuid_addr);

    samsung_cpu_rev = samsung_cpu_id & 0xFF;

}

所以samsung_cpu_id的值是 0xe4412011,samsung_cpu_id的值是0x11

*/


    s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));

/*

数组cpu_ids中列出了一些类samsung的soc芯片的信息,关于exynos4412的信息如下:

static struct cpu_table cpu_ids[] __initdata = {

    ...

    {

        .idcode        = EXYNOS4412_CPU_ID,  // 0xE4412200

        .idmask        = EXYNOS_CPU_MASK,   // 0xFFFE0000

        .map_io        = exynos4_map_io,

        .init_clocks    = exynos4_init_clocks,

        .init_uarts    = exynos4_init_uarts,

        .init        = exynos4_init,

        .name        = name_exynos4412,    // "EXYNOS4412"


    },

    ...

}

*/


}


下面的函数的目的是从cpu_ids中找到与samsung_cpu_id匹配的数组元素


void __init s3c_init_cpu(unsigned long idcode,

             struct cpu_table *cputab, unsigned int cputab_size)

{

    cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);

/* 下面是函数s3c_lookup_cpu的实现,目的就是从上面的cpu_ids中找到了跟刚才读到的cpu id相等的数组元素

static struct cpu_table * __init s3c_lookup_cpu(unsigned long idcode,

                        struct cpu_table *tab,

                        unsigned int count)

{

    for (; count != 0; count--, tab++) {

        if ((idcode & tab->idmask) == (tab->idcode & tab->idmask))

            return tab;

    }


    return NULL;

}

通过上面的循环就可以找到exynos4412对应的cpu_ids

*/

    cpu->map_io();  // 调用的是exynos4_map_io

}


s3c24xx_init_clocks 函数


void __init s3c24xx_init_clocks(int xtal)

{

        (cpu->init_clocks)(xtal); // 调用的是exynos4_init_clocks,初始化系统时钟资源

}


s3c24xx_init_uarts

cfg指向了一个数组,no是数组的元素个数,cfg数组中每一项对应了一个uart控制器的寄存器默认配置参数


void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)

{

        (cpu->init_uarts)(cfg, no);  // 调用函数exynos4_init_uarts

}


在文件arch/arm/plat-s5p/include/plat/exynos4.h中:


#define exynos4_init_uarts exynos_common_init_uarts

void __init exynos_common_init_uarts(struct s3c2410_uartcfg *cfg, int no)

{

    struct s3c2410_uartcfg *tcfg = cfg;

    u32 ucnt;

// 下面这个循环的目的是为每一个uart指定之中源

    for (ucnt = 0; ucnt < no; ucnt++, tcfg++) {

        if (!tcfg->clocks) {

            tcfg->has_fracval = 1;

            tcfg->clocks = exynos_serial_clocks;

            tcfg->clocks_size = ARRAY_SIZE(exynos_serial_clocks);

        }

        tcfg->flags |= NO_NEED_CHECK_CLKSRC;

    }

// s5p_uart_resources数组中存放的是每一个uart控制器的使用的寄存器资源、中断资源

    s3c24xx_init_uartdevs("s5pv210-uart", s5p_uart_resources, cfg, no);

}


下面这个函数的目的是填充每一个uart控制器对应的platform_device


void __init s3c24xx_init_uartdevs(char *name,

                  struct s3c24xx_uart_resources *res,

                  struct s3c2410_uartcfg *cfg, int no)

{

    struct platform_device *platdev;

    struct s3c2410_uartcfg *cfgptr = uart_cfgs;

    struct s3c24xx_uart_resources *resp;

    int uart;


    memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);


    for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {

        platdev = s3c24xx_uart_src[cfgptr->hwport]; // 每一个uart对应一个platform_device


        resp = res + cfgptr->hwport;  // 找到编号为cfgptr->hwport的uart对应的res资源地址


        s3c24xx_uart_devs[uart] = platdev; // 将来会注册其中的每一个platform_device

文章来源于: 电子工程世界 原文链接

本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。