U-Boot中改用DM8168的UART0

背景

在DM8168的EVM板中,DM8168使用UART2作为控制台串口,但在新研FXX板中,DM8168改用UART0,这就需要修改U-Boot源码。

U-Boot控制台串口驱动分析

U-Boot的启动过程中控制台和UART初始化分为两个阶段,相关的几个函数的调用关系分别为:
第一阶段:start_armboot()–>init_sequence[]初始化函数数组–>board_init()&init_baudrate()&serial_init()
第二阶段:start_armboot()–>stdio_init()&console_init_r()

第一阶段。

board_init()中主要是复位串口,使用了UART_SYSCFG寄存器。

========= arch/arm/include/asm/arch-ti81xx/cpu.h 306 317 ==============
/* UART2 registers */
#ifdef CONFIG_TI816X
#define DEFAULT_UART_BASE UART2_BASE
#endif

#ifdef CONFIG_TI814X
#define DEFAULT_UART_BASE UART0_BASE
#endif

/* UART registers */
/*TODO:Move to a new file */
#define UART_SYSCFG (DEFAULT_UART_BASE + 0x54)

把UART2_BASE修改为UART0_BASE即可改为初始化UART0。

serial_init()函数中有这样的一段配置:

============ drivers/serial/serial.c 68 73 ==================
static NS16550_t serial_ports[4] = {
#ifdef CONFIG_SYS_NS16550_COM1
    (NS16550_t)CONFIG_SYS_NS16550_COM1,
#else
    NULL,
#endif

============= drivers/serial/serial.c 171 174 ================
#ifdef CONFIG_SYS_NS16550_COM1
    clock_divisor = calc_divisor(serial_ports[0]);
    NS16550_init(serial_ports[0], clock_divisor);
#endif

CONFIG_SYS_NS16550_COM1宏在include/configs/ti8168_evm.h中定义:

========= include/configs/ti8168_evm.h 168 168 ================== #define CONFIG_SYS_NS16550_COM1     0x48024000  /* Base EVM has UART2 */

所以这里也要把这个宏修改为UART0的基址。

第二阶段

第二阶段的stdio_init()函数中首先初始化了全局链表devs.list,然后根据配置,初始化各种输入输出设备如LCD、VIDEO、KEYBOARD、NETCONSOLE、USB_TTY等,并通过stdio_register()把设备注册到stdio设备链表中。
根据我们的配置,这些设备都不存在,只有一个drv_system_init()函数会得到执行,它初始化一个系统默认设备并注册到stdio设备链表中。该函数片段为:

========= common/stdio.c 71 91 ==================
static void drv_system_init (void)
{
    struct stdio_dev dev;

    memset (&dev, 0, sizeof (dev));

    strcpy (dev.name, "serial");
    dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;
#ifdef CONFIG_SERIAL_SOFTWARE_FIFO
    dev.putc = serial_buffered_putc;
    dev.puts = serial_buffered_puts;
    dev.getc = serial_buffered_getc;
    dev.tstc = serial_buffered_tstc;
#else
    dev.putc = serial_putc;
    dev.puts = serial_puts;
    dev.getc = serial_getc;
    dev.tstc = serial_tstc;
#endif

    stdio_register (&dev);

我们没有定义CONFIG_SERIAL_SOFTWARE_FIFO,可见该系统默认设备的输入输出方法是serial_puts()等。

最后start_armboot()调用console_init_r(),它遍历stdio设备链表,找到一组输入输出设备,将stdout、stderr、stdin定向到该设备上,然后设置GD_FLG_DEVINIT标志。
我们的设备链表只有系统默认设备,于是标准输入输出等就被定向到serial_puts()等函数上了。

控制台输入输出

第一阶段的输出

在U-Boot中第一次调用输出发生在init_sequence[]初始化函数数组中的display_banner()函数,它在控制台第一阶段完成后执行,这是理所当然的,否则过早执行没有任何意义。
我们来看这里打印函数调用过程:printf()–>puts()。来看puts()函数片段:

============ common/console.c 361 367 ==================
    if (gd->flags & GD_FLG_DEVINIT) {
        /* Send to the standard output */
        fputs(stdout, s);
    } else {
        /* Send directly to the handler */
        serial_puts(s);
    }

第一阶段gd标志的GD_FLG_DEVINIT未置位,调用关系为:
–>serial_puts()–>_serial_puts()–>_serial_putc()–>NS16550_putc()

============ drivers/serial/ns16550.c 23 24 ==================
#define serial_out(x,y) writeb(x,y)
#define serial_in(y) readb(y)
============ drivers/serial/ns16550.c 69 73 ==================
void NS16550_putc (NS16550_t com_port, char c)
{
    while ((serial_in(&com_port->lsr) & UART_LSR_THRE) == 0);
    serial_out(c, &com_port->thr);
}

可见最终是将内容写入到串口的发送寄存器中实现输出。具体写到哪个寄存器取决于com_port,该变量在serial_puts()函数中给定为CONFIG_CONS_INDEX,这个宏在include/configs/ti8168_evm.h中定义为1,代表drivers/serial/serial.c中serial_ports[4]构体数据中的首元素。前面已经将其修改为了UART0的基址了,因此这里会输出到UART0上。

第二阶段的输出

第二阶段gd标志的GD_FLG_DEVINIT已置位,则运行puts()函数另一分支,调用关系为:
–>fputs()–>console_puts()–>stdio_devices[file]->puts()
最后一个函数指针就是serial_puts(),可见在没有LCD等其他设备的情况下,这里和第一阶段是一样的结果。

输入

只有第二阶段才有输入的情况。控制台的输入也是类似的,这里不再分析。

总结

根据以上分析,只需要做少量修改后就可以实现改变控制台使用的串口。经过重新编译U-Boot并烧入Nandflash启动,上位机控制台上就出现了打印字符,证明修改成功。
从上面的分析还可以看出,U-Boot支持多种设备实现输入输出,只要有相应的外设,在编译时进行一些配置就可以了。

你可能感兴趣的:(控制台,u-boot)