uboot串口与标准输入输出代码详解

这里我们来分析下uboot串口设备初始化和串口stdio标准输入输出设备的初始化过程。

一、display_banner与print_cpuinfo

uboot从启动开始,第一句打印就是调用display_banner,打印出我们期待已久的“U-Boot 2012.10-xxxx”信息。接着调用print_cpuinfo打印出cpu信息。

const char version_string[] = U_BOOT_VERSION" (" U_BOOT_DATE " - " U_BOOT_TIME ")"CONFIG_IDENT_STRING;
static int display_banner (void)
{
    printf ("\n\n%s\n\n", version_string);
    debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
           _armboot_start, _bss_start, _bss_end);
……
    return (0);
}

二、Uboot是如何初始化串口的呢?

Uboot第二阶段入口函数board_init_f通过init_sequence初始化数组调用了5个串口相关的函数:

    init_baudrate,      /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,     /* stage 1 init of console */
    display_banner,     /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,      /* display cpu info (and speed) */

这几个函数分别作了以下工作:
1.首先获取波特率设置。这里把获取到的波特率值存入gd->baudrate在串口初始化时候会用到。

static int init_baudrate(void)
{
    int ret;
    ret = early_access_env_vars();

    /* If the above call succeeded, gd's baudrate gets initialized within the
     * call
     */
    if (ret == -1) {
        gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
    }
    return 0;
}

2.串口初始化,serial_init函数定义在drivers/serial目录下,此目录下有各种各样的串口驱动,makefile会通过之前uboot的配置去编译指定的串口驱动。串口驱动中包含了
serial_init, serial_getc, serial_putc, serial_puts, serial_tstc, serial_setbrg的底层API函数。串口打印和标准输入输出的功能都依赖于这些底层函数实现。这一步完成之后,我们的串口就可以用了。可以用putc,puts,getc了。这里我们不在分析具体CPU平台的串口驱动代码。详细可参见具体处理器的串口数据手册。
3.console_init_f这个函数的作用是把在串口还没有初始化之前的puts或putc的打印内容给打印出来。换句话说在串口没初始化之前也能调用putc,puts函数。只是要等到串口初始化成功之后才会有打印输出。

console_init_f()--> print_pre_console_buffer()
static void print_pre_console_buffer(void)
{
    unsigned long i = 0;
    char *buffer = (char *)CONFIG_PRE_CON_BUF_ADDR;

    if (gd->precon_buf_idx > CONFIG_PRE_CON_BUF_SZ)
        i = gd->precon_buf_idx - CONFIG_PRE_CON_BUF_SZ;

    while (i < gd->precon_buf_idx)
        putc(buffer[CIRC_BUF_IDX(i++)]);
}

这里我们看到在内存地址CONFIG_PRE_CON_BUF_ADDR处有一个长度为CONFIG_PRE_CON_BUF_SZ的内存区域。用于在串口没有使能之前,putc的内容暂存于这个数组内,以便等串口使能后一起打印输出。
我们来看putc源码实现:

void putc(const char c)
{
#ifdef CONFIG_SILENT_CONSOLE
    if (gd->flags & GD_FLG_SILENT)
        return;
#endif

#ifdef CONFIG_DISABLE_CONSOLE
    if (gd->flags & GD_FLG_DISABLE_CONSOLE)
        return;
#endif

    if (!gd->have_console)
        return pre_console_putc(c);

    if (gd->flags & GD_FLG_DEVINIT) {
        /* Send to the standard output */
        fputc(stdout, c);
    } else {
        /* Send directly to the handler */
        serial_putc(c);
    }
}

如果!gd->have_console,即串口还未初始化,则调用pre_console_putc(c)函数,这个函数直接把要打印的字符保存到地址CONFIG_PRE_CON_BUF_ADDR的buf中去。
如果串口已经使能,则又要判断gd->flags & GD_FLG_DEVINIT看标准输入输出设备(stdio)是否初始化,如果stdio初始化成功则会置GD_FLG_DEVINIT标志位,这时候就调用fputc(stdout, c);送给stdio处理,否则,直接调用串口底层API函数serial_putc(c)输出。
其它函数如Puts, fputs,fputc, getc,tstc等函数是实现可参见putc的实现方式。
4.这个时候串口输入输出已经OK。我们来分析下printf的实现。

int printf(const char *fmt, ...)
{
    va_list args;
    uint i;
    char printbuffer[CONFIG_SYS_PBSIZE];

#ifndef CONFIG_PRE_CONSOLE_BUFFER
    if (!gd->have_console)
        return 0;
#endif

    va_start(args, fmt);

    /* For this to work, printbuffer must be larger than
     * anything we ever want to print.
     */
    i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args);
    va_end(args);

    /* Print the string */
    puts(printbuffer);
    return i;
}

我们看到printf是调用puts来输出到串口的。如果在串口初始化完成之前调用printf是可以的。如果配置了CONFIG_PRE_CONSOLE_BUFFER,则会保存到CONFIG_PRE_CON_BUF_ADDR内存中,如果没有配置,则不处理。

三、stdio设备初始化

board_init_r—> serial_initialize, stdio_init, console_init_r
接下来board_init_r会调用这三个函数去创建stdio。
首先serial_initialize会注册串口设备。注册实际上就是把之前串口初始化函数挂载到设备结构体指针。
接着调用stdio_init,它会调用serial_stdio_init来创建一个stdio_dev设备结构体。
stdio_register(&dev);是注册设备到一个链表中。这样,一个串口stdio设备就OK了。
调用过程:Stdio_init—-> serial_stdio_init—-> stdio_register。

void serial_stdio_init(void)
{
    struct stdio_dev dev;
    struct serial_device *s = serial_devices;

    while (s) {
        memset(&dev, 0, sizeof(dev));

        strcpy(dev.name, s->name);
        dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;

        dev.start = s->init;
        dev.stop = s->uninit;
        dev.putc = s->putc;
        dev.puts = s->puts;
        dev.getc = s->getc;
        dev.tstc = s->tstc;

        stdio_register(&dev);

        s = s->next;
    }
}

接下来调用 console_init_r把上面创建好的stdio设备对接文件描述符的标准输入、输出、错误。

int console_init_r(void)
{
    struct stdio_dev *inputdev = NULL, *outputdev = NULL;
    int i;
    struct list_head *list = stdio_get_list();
    struct list_head *pos;
    struct stdio_dev *dev;

#ifdef CONFIG_SPLASH_SCREEN
    /*
     * suppress all output if splash screen is enabled and we have
     * a bmp to display. We redirect the output from frame buffer
     * console to serial console in this case or suppress it if
     * "silent" mode was requested.
     */
    if (getenv("splashimage") != NULL) {
        if (!(gd->flags & GD_FLG_SILENT))
            outputdev = search_device (DEV_FLAGS_OUTPUT, "serial");
    }
#endif

    /* Scan devices looking for input and output devices */
    list_for_each(pos, list) {
        dev = list_entry(pos, struct stdio_dev, list);

        if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
            inputdev = dev;
        }
        if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
            outputdev = dev;
        }
        if(inputdev && outputdev)
            break;
    }

    /* Initializes output console first */
    if (outputdev != NULL) {
        console_setfile(stdout, outputdev);
        console_setfile(stderr, outputdev);
#ifdef CONFIG_CONSOLE_MUX
        console_devices[stdout][0] = outputdev;
        console_devices[stderr][0] = outputdev;
#endif
    }

    /* Initializes input console */
    if (inputdev != NULL) {
        console_setfile(stdin, inputdev);
#ifdef CONFIG_CONSOLE_MUX
        console_devices[stdin][0] = inputdev;
#endif
    }

    gd->flags |= GD_FLG_DEVINIT;    /* device initialization completed */

    stdio_print_current_devices();

    /* Setting environment variables */
    for (i = 0; i < 3; i++) {
        setenv(stdio_names[i], stdio_devices[i]->name);
    }

    return 0;
} 

Stdio初始化完毕之后,通过stdio_print_current_devices函数打印出stdio设备名。

void stdio_print_current_devices(void)
{
#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET
    /* Print information */
    puts("In:    ");
    if (stdio_devices[stdin] == NULL) {
        puts("No input devices available!\n");
    } else {
        printf ("%s\n", stdio_devices[stdin]->name);
    }

    puts("Out:   ");
    if (stdio_devices[stdout] == NULL) {
        puts("No output devices available!\n");
    } else {
        printf ("%s\n", stdio_devices[stdout]->name);
    }

    puts("Err:   ");
    if (stdio_devices[stderr] == NULL) {
        puts("No error devices available!\n");
    } else {
        printf ("%s\n", stdio_devices[stderr]->name);
    }
#endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */
}


void fputc(int file, const char c)
{
    if (file < MAX_FILES)
        console_putc(file, c);
}
static inline void console_putc(int file, const char c)
{
    stdio_devices[file]->putc(c);
}

至此,UBOOT串口串口和stdio初始化完毕。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值