硬件平台: QCA9531
软件平台: Openwrt-trunk Kernel-4.1.15
2.硬件资源:
对9531而言,其只有一个串口uart0,其为16550兼容系列,支持最大波特率为115200,不支持硬件流控。
官方datasheet中描述如下:

3.软件调试:
从上述的硬件资源我们大致可以猜测到该uart的驱动需要使用16550的驱动,具体情况下面慢慢分析:
查看openwrt下的内核的默认相关配置:
# vim build_dir/target-mips_34kc_musl-1.1.11/linux-ar71xx_generic/linux-4.1.15/.config (NOTE: 上述.config,由target/linux/ar71xx/config-4.1、target/linux/generic/config-4.1 和Openwrt框架产生的.config 共同作用所得,其产生的具体过程可参考include/*.h, 这里不加以介绍) CONFIG_EARLY_PRINTK=y CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_DMA=y CONFIG_SERIAL_CORE=y CONFIG_SERIAL_CORE_CONSOLE=y CONFIG_SERIAL_EARLYCON=y CONFIG_SERIAL_8250_NR_UARTS=1 CONFIG_SERIAL_8250_RUNTIME_UARTS=1 CONFIG_SERIAL_AR933X=y CONFIG_SERIAL_AR933X_CONSOLE=y CONFIG_SERIAL_AR933X_NR_UARTS=2 |
从上述相关的配置可以看出,貌似使用了两个串口驱动,它们之间又是什么关系?如何协同合作的呢?
这里我们首先思考一个问题: 在Linux启动过程中,我们一直能看到串口输出的启动信息,仔细思考一下,在Linux系统还未加载对应串口驱动之前这些信息是如何打印出来的?
调试过内核启动的知道,在内核串口未初始化之前,我们无法使用printk进行输出,但我们可以调用一个叫early_printk()的函数在串口上进行相应的输出。对应到此平台上来,
在串口驱动(9531 driver)没有初始化之前,系统使用(8250 driver)进行early print输出,因此系统启动的时候会首先初始化8250系列的驱动,然后等9531驱动初始化完成
以后关闭8250驱动输出,使能9531驱动的输出,以此完成启动过程中串口输出的交接工作。
这里先找一个能正常启动的目标板卡,观看其启动信息,看看这个串口输出交接的过程:
Starting kernel ... [ 0.000000] Linux version 4.1.15 (gcc version 4.8.3 (OpenWrt/Linaro GCC 4.8-2014.04 r46919) ) #1 Tue Jan 26 17:07:14 CST 2016 [ 0.000000] bootconsole [early0] enabled #NOTE: 进入内核后立即打开early print端口 [ 0.000000] Kernel command line: board=PIONEER-9531 console=ttyS0,115200 rootfstype=squashfs,jffs2 noinitrd #NOTE: 注意串口名称为ttyS0 .... .... [ 0.640000] Serial: 8250/16550 driver, 1 ports, IRQ sharing disabled [ 0.650000] console [ttyS0] disabled [ 0.670000] serial8250.0: ttyS0 at MMIO 0x18020000 (irq = 11, base_baud = 1562500) is a 16550A [ 0.680000] console [ttyS0] enabled [ 0.680000] console [ttyS0] enabled [ 0.690000] bootconsole [early0] disabled [ 0.690000] bootconsole [early0] disabled |
下面来追踪以下early串口是何时?如何注册到内核中的呢?
--/arch/mips/kernel/early_printk.c extern void prom_putchar(char); static void early_console_write(struct console *con, const char *s, unsigned n) { while (n-- && *s) { if (*s == '\n') prom_putchar('\r'); prom_putchar(*s); s++; } } static struct console early_console_prom = { #NOTE:此处定义一个console设备 .name = "early", .write = early_console_write, .flags = CON_PRINTBUFFER | CON_BOOT, .index = -1 }; void __init setup_early_printk(void) #NOTE: early console初始化入口 { if (early_console) return; early_console = &early_console_prom; register_console(&early_console_prom); #NOTE: early console 注册,启动信息中的bootconsole [early0] enabled 来自kernel/printk/printk.c中注册的该函数。 } --/kernel/printk/prink.c ifdef CONFIG_EARLY_PRINTK struct console *early_console; asmlinkage __visible void early_printk(const char *fmt, ...) { va_list ap; char buf[512]; int n; if (!early_console) return; va_start(ap, fmt); n = vscnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); early_console->write(early_console, buf, n); #NOTE: early_printk函数的最终调用的是 early_console->write. #endif |
我们先往上追踪,看看哪里调用了该入口函数.
--/arch/mips/kernel/setup.c void __init setup_arch(char **cmdline_p) { cpu_probe(); prom_init(); setup_early_fdc_console(); #ifdef CONFIG_EARLY_PRINTK setup_early_printk(); #NOTE: 此处调用了/arch/mips/kernel/early_printk.c中的early console的注册接口,该接口包含一个配置控制选项CONFIG_EARLY_PRINTK。 #endif ...... } |
继续往上追踪,看看哪里调用了setup_arch()函数
--/init/main.c asmlinkage __visible void __init start_kernel(void) { ....... pr_notice("%s", linux_banner); #NOTE:此语句貌似是一个关键,该打印输出位于early print之前,且也能正常的打出内核的banner,即上面打印信息的“Linux version ..."语句, 此处是直接输出到串口,还是输出到缓冲区待early print初始化完成后再从缓冲区输出来呢? 要想直到这个结果,有一个简单的方法,在 两个语句之间加上一个死循环,看输出信息是否包含banner即可大致确定。 实际上,pr_notice即printk(KERN_NOTICE ....) ,据说其内核实现是由一个环形缓冲区构成,在输出状态没有初始化好了之前,调用的 输出将缓存在环形缓冲区中,待初始化完成以后,再把环形缓冲区的输出信息一并输出,关于环形缓冲区,哪天空了再研究。 setup_arch(&command_line); #NOTE:此处调用了setup_arch()函数 ...... } |
现在回过头来,我们直到early_printk()函数的的输出为调用early_console->write 继而调用prom_putchar().也就是说,最终的发送都是函数prom_putchar()
函数来完成的。该函数根据平台来各自进行实现,9531的最终调用接口实现如下:
--/arch/mips/ath79/early_printk.c prom_putchar ------> plarform-match ---> static void prom_putchar_ar933x(unsigned char ch) { void __iomem *base = (void __iomem *)(KSEG1ADDR(AR933X_UART_BASE)); prom_putchar_wait(base + AR933X_UART_DATA_REG, AR933X_UART_DATA_TX_CSR, AR933X_UART_DATA_TX_CSR); __raw_writel(AR933X_UART_DATA_TX_CSR | ch, base + AR933X_UART_DATA_REG); prom_putchar_wait(base + AR933X_UART_DATA_REG, AR933X_UART_DATA_TX_CSR, AR933X_UART_DATA_TX_CSR); } |
在uboot的bootargs中,console变量指定的端口为ttyS0, 如果你添加过target/linux/ar71xx/中profile,你可能会发现类似如下信息:
define Device/pioneer-qca9531-64m-16m $(Device/tplink-16mlzma) BOARDNAME := PIONEER-9531 DEVICE_PROFILE := PIONEER-QCA9531-64M-16M TPLINK_HWID := 0x3C000101 CONSOLE := ttyATH0,115200 #NOTE:注意这里的console名称为ttyATH0 endef |
如果你把uboot中的bootargs中的console直接设置成ttyATH0,你会发现,系统启动的时候,当输出信息输出到如下语句后就没有输出了:
......... [ 0.690000] bootconsole [early0] disabled |
------未完待续