tiny4412 串口驱动分析七 --- log打印的几个阶段之内核启动阶段(earlyprintk)

发布时间:2023-06-25  

开发板: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

 

下面要分析的是内核Log打印的几个阶段

  1. 自解压阶段

  2. 内核启动阶段

  3. 内核启动完全以后

  4. shell终端下

在这个阶段内核log打印可以调用printk和printascii,同时printk又分为两个阶段,从刚才的分析中知道,printk最终调用的是有register_console注册的console_drivers的write函数,在tiny4412平台上调用register_console的地方有两处,第一处是在arch/arm/kernel/early_printk.c中,另一处就是在串口驱动注册中,具体是在driver/tty/serial/samsung.c中,下面我们开始分析。

printascii的实现:

首先printascii需要配置内核才能使用:

make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig

 

Kernel hacking

     --- Kernel low-level debugging functions


 

这样就可以使用printascii了:

在printk中会调用printascii,

#ifdef        CONFIG_DEBUG_LL

         printascii(printk_buf);

#endif

print_asciii使用汇编实现的,在文件arch/arm/kernel/debug.S中:


.macro     addruart_current, rx, tmp1, tmp2

addruart   tmp1, tmp2

mrc        p15, 0, rx, c1, c0

tst        rx, #1

moveq      rx, tmp1

movne      rx, tmp2

.endm


ENTRY(printascii)

                   addruart_current r3, r1, r2

                   b       2f

1:                 waituart r2, r3

                   senduart r1, r3

                   busyuart r2, r3

                   teq   r1, #'n'

                   moveq      r1, #'r'

                   beq  1b


2:                 teq   r0, #0

                   ldrneb       r1, [r0], #1

                   teqne        r1, #0

                   bne  1b


                   mov pc, lr

ENDPROC(printascii)


其中 addruart 是在文件arch/arm/mach-exynos/include/mach/debug-macro.S中实现的,waituart、senduart以及busyuart是在arch/arm/plat-samsung/include/plat/debug-macro.S中实现的,大家可以参考这两个文件理解具体实现过程。

early_printk中调用register_console

tiny4412使用的内核默认是没有开启early_printk的,即log_buf中内容只有等driver/tty/serial下的串口驱动注册完成后才能输出到串口终端,在此之前调用printk的内容都缓存到log_buf中了,如果想提前使用的话,需要使能early_printk,下面说明一下如何使能early_printk。

make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig

Kernel hacking

        -- Kernel low-level debugging functions

        --Early printk

 

 

要使能early_printk首先必须使能Kernel low-level debugging functions,因为early_printk最终也是使用printascii实现的,这样arch/arm/kernel/early_printk.c就会参加编译

在early_printk.c中:

static int __init setup_early_printk(char *buf)

{

    printk("%s entern", __func__);

    register_console(&early_console);

    return 0;

}

early_param("earlyprintk", setup_early_printk);


虽然内核配置了,但是要让内核调用setup_early_printk还必须在u-boot给内核传参的时候给bootargs在加上一个参数”earlyprintk”,如下:


set bootargs ‘console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70 earlyprintk’

那么内核是如何处理的呢?


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


#define __setup_param(str, unique_id, fn, early)           

    static const char __setup_str_##unique_id[] __initconst   

        __aligned(1) = str;

    static struct obs_kernel_param __setup_##unique_id   

        __used __section(.init.setup)           

        __attribute__((aligned((sizeof(long)))))   

        = { __setup_str_##unique_id, fn, early }


#define early_param(str, fn)                   

    __setup_param(str, fn, fn, 1)


将early_param("earlyprintk", setup_early_printk);展开后:


static const char __setup_str_setup_early_printk[] __initconst __aligned(1) = "earlyprintk";

static struct obs_kernel_param __setup_setup_early_printk __used __section(.init.setup)

__attribute__((aligned((sizeof(long))))) =

{ __setup_str_setup_early_printk, setup_early_printk, 1 }

即上面的这个结构体被链接到了”.init.setup”段,在arch/arm/kernel/vmlinux.lds中:


  . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;

将所有的.init.setup都链接到了__setup_start和__setup_end之间


在内核启动的时候:


start_kernel

  --- setup_arch(&command_line); 

  //这个函数的目的是获得u-boot传给内核的参数(bootargs),并将参数存放在command_line中,然后解析command_line,并调用相关的函数处理--- parse_early_param()   (arch/arm/kernel/setup.c)

                    --- strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);

                    --- parse_early_options(tmp_cmdline);


parse_early_options用于解析tmp_cmdline


void __init parse_early_options(char *cmdline)

{

    parse_args("early options", cmdline, NULL, 0, do_early_param);

}

在文件kernel/params.c中:


int parse_args(const char *name,

           char *args,

           const struct kernel_param *params,

           unsigned num,

           int (*unknown)(char *param, char *val))

{

    char *param, *val;


    /* Chew leading spaces */

    args = skip_spaces(args); // 跳过args开头的空格


    while (*args) {

        int ret;

        int irq_was_disabled;

/*

    如:cmdline是“console=ttySAC0,115200n8 androidboot.console=ttySAC0”

    执行next_arg后:

    params=”console”, val=” ttySAC0,115200n8” args=” androidboot.console=ttySAC0”

*/

        args = next_arg(args, &param, &val); // 获得下一个参数的位置,

        irq_was_disabled = irqs_disabled();

/*

    parse_one所做的主要就是将params和val传递给unknown处理,这里unknown就是do_early_param,所以下面分析do_early_param

*/

        ret = parse_one(param, val, params, num, unknown);

        …

        }

    }


    /* All parsed OK. */

    return 0;

}


static int __init do_early_param(char *param, char *val)

{

    const struct obs_kernel_param *p;

/*

   还记得early_param("earlyprintk", setup_early_printk)展开后的结果吗?

   其中early为1,str是 “earlyprintk”,setup_func就是setup_early_printk

*/

    for (p = __setup_start; p < __setup_end; p++) {

        if ((p->early && strcmp(param, p->str) == 0) ||

            (strcmp(param, "console") == 0 &&

             strcmp(p->str, "earlycon") == 0)

        ) {

            if (p->setup_func(val) != 0)

        }

    }

    return 0;

}


下面我们就分析setup_early_printk:


static int __init setup_early_printk(char *buf)

{

    register_console(&early_console);

    return 0;

}


结构体early_console 的定义如下:


static struct console early_console = {

    .name =        "earlycon",

    .write =    early_console_write,

    .flags =    CON_PRINTBUFFER | CON_BOOT,

    .index =    -1,

};


看一下early_console_write干了什么:


static void early_console_write(struct console *con, const char *s, unsigned n)

{

    early_write(s, n);

}


static void early_write(const char *s, unsigned n)

{

    while (n-- > 0) {

        if (*s == 'n')

            printch('r');

        printch(*s);

        s++;

    }

}


可以看到,它调用的是printch,它在文件arch/arm/kernel/debug.S中实现:


ENTRY(printascii)

        addruart_current r3, r1, r2

        b    2f

1:      waituart r2, r3

        senduart r1, r3

        busyuart r2, r3

        teq    r1, #'n'

        moveq    r1, #'r'

        beq    1b

2:      teq    r0, #0

        ldrneb    r1, [r0], #1

        teqne    r1, #0

        bne    1b

        mov    pc, lr

ENDPROC(printascii)


ENTRY(printch)

        addruart_current r3, r1, r2

        mov    r1, r0

        mov    r0, #0

        b    1b

ENDPROC(printch)


这样当driver/tty/serial下的驱动尚未注册时,printk就已经可以使用了,它最终调用的是early_console_write输出到串口终端的


下面我们分析一个函数register_console


/*

   这段话最好看一下

 * The console driver calls this routine during kernel initialization

 * to register the console printing procedure with printk() and to

 * print any messages that were printed by the kernel before the

 * console driver was initialized.

 *

 * This can happen pretty early during the boot process (because of

 * early_printk) - sometimes before setup_arch() completes - be careful

 * of what kernel features are used - they may not be initialised yet.

 *

 * There are two types of consoles - bootconsoles (early_printk) and

 * "real" consoles (everything which is not a bootconsole) which are

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

相关文章

    送过来的数据直接送回给电脑。 1.STM32串口简介 串口设置的一般步骤可以总结为如下几个步骤: 1)串口时钟使能,GPIO时钟使能 2)串口复位 3) GPIO端口模式设置 4)串口参数初始化 5)开启中断并且初始化......
    流程我们便不多说了,大体分为以下几个步骤串口时钟使能,GPIO时钟使能 GPIO端口模式设置 串口参数初始化:USART_Init(); 开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤) 使能串口......
    模式设置   4、串口参数初始化   5、开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)   6、使能串口   7、编写中断处理函数   下面, 我们就简单介绍下这几个与串口......
    STM32串口的理解;串口设置的一般步骤可以总结为如下几个步骤: 1) 串口时钟使能,GPIO 时钟使能 2) 串口复位 3) GPIO 端口模式设置 4) 串口参数初始化 5) 开启中断并且初始化......
    位等等参数。在设置完成后就是使能串口。同时,如果开启了串口的中断,当然要初始化 NVIC 设置中断优先级别,最后编写中断服务函数。   串口设置的一般步骤可以总结为如下几个步骤:   1) 串口......
    到了 serial_fin_console_or_panic 这个函数,这里就感觉优点不对了,我们分析的串口初始化的跳转应该是跳转到s3c24xx_serial_initialize() 这个函数中,去执......
    函数定义:   void USART_Configuration(void); //定义串口初始化函数   c) 初始化函数调用:   void UART_Configuration(void......
    函数定义:   void USART_Configuration(void); //定义串口初始化函数   c) 初始化函数调用:   void UART_Configuration(void......
           NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级   uart_init(115200);   //串口初始化......
    */ delay_init(72); /*延时初始化 */ usart_init(72, 115200); /*串口初始化为115200 */ usmart_dev.init(72); /*初始化......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>