在DM8168的EVM板中,DM8168使用UART2作为控制台串口,但在新研FXX板中,DM8168改用UART0,这就需要修改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支持多种设备实现输入输出,只要有相应的外设,在编译时进行一些配置就可以了。