Chinaunix首页 | 论坛 | 博客
  • 博客访问: 234817
  • 博文数量: 54
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 431
  • 用 户 组: 普通用户
  • 注册时间: 2014-07-26 09:36
文章分类

全部博文(54)

分类: LINUX

2016-01-27 11:55:51

1.平台简介:
    硬件平台: 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

                                                                                                                                                                                                                                                 ------未完待续

                            



阅读(5350) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~