linux console驱动详解

console驱动:

一、基本概念

终端是一种字符型设备,通常使用tty简称各种类型的终端。linux的终端类型:

/dev/ttySn,串行口终端

/dev/pty,伪终端

/dev/tty,当前进程的控制终端,可以是介绍的其它任何一种终端

/dev/ttyn,tty1~tty6是虚拟终端,tty0当前虚拟终端的别名。

/dev/console,控制台终端(显示器)

二、uboot传参数的处理

linux启动时uboot传递进console=ttyS2,115200n8的参数

内核中用__setup()宏声明参数处理的方法:__setup("console=", console_setup); 

1.console_cmdline结构体

struct console_cmdline 

    char name[8];    //驱动名  

    int  index;      //次设备号  

    char *options;   //选项  

#ifdef CONFIG_A11Y_BRAILLE_CONSOLE  

    char    *brl_options;    

#endif  

}; 

2.内核调用console_setup()函数处理uboot传进的console参数

static int __init console_setup(char *str) 

    char buf[sizeof(console_cmdline[0].name) + 4]; //分配驱动名+index的缓冲区,分配12个字节 

    char *s, *options, *brl_options = NULL; 

    int idx; 

   

#ifdef CONFIG_A11Y_BRAILLE_CONSOLE  

    if (!memcmp(str, "brl,", 4)) { 

        brl_options = ""

        str += 4; 

    } else if (!memcmp(str, "brl=", 4)) { 

        brl_options = str + 4; 

        str = strchr(brl_options, ','); 

        if (!str) { 

            printk(KERN_ERR "need port name after brl=\n"); 

            return 1; 

        

        *(str++) = 0; 

    

#endif  

    if (str[0] >= '0' && str[0] <= '9') { //第一个参数属于[0,9]  

        strcpy(buf, "ttyS");    //则将其驱动名设为ttyS  

        strncpy(buf + 4, str, sizeof(buf) - 5);//将次设备号放其后面  

    } else

        strncpy(buf, str, sizeof(buf) - 1); //否则直接将驱动名+设备号拷贝到buf中

    

    buf[sizeof(buf) - 1] = 0; 

    if ((options = strchr(str, ',')) != NULL) //获取options,即“115200n8”  

        *(options++) = 0; 

         

#ifdef __sparc__  

    if (!strcmp(str, "ttya")) 

        strcpy(buf, "ttyS0"); 

    if (!strcmp(str, "ttyb")) 

        strcpy(buf, "ttyS1"); 

#endif 

  

    for (s = buf; *s; s++) 

        if ((*s >= '0' && *s <= '9') || *s == ',')//移动指针s到次设备号处 

            break

    idx = simple_strtoul(s, NULL, 10); //获取次设备号,字符串转换成unsigend long long型数据,s表示字符串的开始,NULL表示字符串的结束,10表示进制

                                                                        //这里返回的是次设备号=2

    *s = 0; 

   

    __add_preferred_console(buf, idx, options, brl_options); 

    console_set_on_cmdline = 1; 

    return 1; 

3.__add_preferred_console()函数

//整体的作用是根据uboot传递的参数设置全局console_cmdline数组

//该数组及全局selected_console,在register_console中会使用到

static int __add_preferred_console(char *name, int idx, char *options,char *brl_options) 

    struct console_cmdline *c; 

    int i; 

    for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)//可以最多8个console  

        if (strcmp(console_cmdline[i].name, name) == 0 && console_cmdline[i].index == idx) { 

            //比较已注册的console_cmdline数组中的项的名字及次设备号,若console_cmdline已经存在  

                if (!brl_options) 

                    selected_console = i;//设置全局selected_console索引号  

                return 0;//则返回  

        

      

    if (i == MAX_CMDLINECONSOLES)//判断console_cmdline数组是否满了  

        return -E2BIG; 

    if (!brl_options) 

        selected_console = i; //设置全局selected_console索引号  

     

    c = &console_cmdline[i];//获取全局console_cmdline数组的第i项地址  

    strlcpy(c->name, name, sizeof(c->name));  //填充全局console_cmdline的驱动名“ttyS2”  

    c->options = options;    //填充配置选项115200n8  

#ifdef CONFIG_A11Y_BRAILLE_CONSOLE  

    c->brl_options = brl_options; 

#endif  

    c->index = idx;  //填充索引号2,即次设备号  

    return 0; 

三、在console初始化之前能使用printk,使用内核提供的early printk支持。

//在调用console_init之前调用printk也能打印出信息,这是為什麼呢?在start_kernel函数中很早就调用了 parse_early_param函数,

//该函数会调用到链接脚本中.init.setup段的函数。其中就有 setup_early_serial8250_console函数。

//该函数通过 register_console(&early_serial8250_console);

//注册了一个比较简单的串口设备。可以用来打印内核启 动早期的信息。

//对于early printk的console注册往往通过内核的early_param完成。

early_param(“earlycon”,setup_early_serial8250_console);

//定义一个earlycon的内核参数,内核解析这个参数时调用setup_early_serial8250_console()函数

1.setup_early_serial8250_console()函数

//earlycon = uart8250,mmio,0xff5e0000,115200n8

int __init setup_early_serial8250_console(char *cmdline)

{

    char *options;

    int err;

    options = strstr(cmdline, "uart8250,");//找到“uart8250,”字符串,返回此字符串的起始位置

    if (!options) {

        options = strstr(cmdline, "uart,");

        if (!options)

            return 0;

    }

    options = strchr(cmdline, ',') + 1;//options指针指向第一个逗号后边的字符串地址

    err = early_serial8250_setup(options);//进行配置

    if (err < 0)

        return err;

     

    /*

    static struct console early_serial8250_console __initdata = {

        .name   = "uart",

        .write  = early_serial8250_write,

        .flags  = CON_PRINTBUFFER | CON_BOOT,//所用具有CON_BOOT属性的console都会在内核初始化到late initcall阶段被注销,相互消他们的函数是

        .index  = -1,

    };

    */

    //注册一个早期的console,到真正的console_init时,此console会被注销,因为设置了CON_BOOT标志

    register_console(&early_serial8250_console);

    return 0;

}

static int __init early_serial8250_setup(char *options)

{

    struct early_serial8250_device *device = &early_device;

    int err;

    if (device->port.membase || device->port.iobase)//early_device设备的端口地址若配置过则返回

        return 0;

    err = parse_options(device, options);//解析参数并配置early_device设备对应的uart_port结构

    if (err < 0)

        return err;

    init_port(device);//early_device设备对应的初始化uart_port结构

    return 0;

}

static int __init parse_options(struct early_serial8250_device *device,char *options)

{

    struct uart_port *port = &device->port;//找到early_device设备对应的uart_port结构

    int mmio, mmio32, length;

    if (!options)

        return -ENODEV;

    port->uartclk = BASE_BAUD * 16;//串口时钟

    mmio = !strncmp(options, "mmio,", 5);//查找"mmio,"字符串,找到mmio=1

    mmio32 = !strncmp(options, "mmio32,", 7);//mmio32=0

    if (mmio || mmio32) {

        port->iotype = (mmio ? UPIO_MEM : UPIO_MEM32);//串口类型设为UPIO_MEM=2

        port->mapbase = simple_strtoul(options + (mmio ? 5 : 7),&options, 0);//获得串口的配置寄存器基础地址(物理地址),这里是得到0xff5e0000

        if (mmio32)

            port->regshift = 2;

#ifdef CONFIG_FIX_EARLYCON_MEM

        set_fixmap_nocache(FIX_EARLYCON_MEM_BASE,port->mapbase & PAGE_MASK);

        port->membase =(void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);

        port->membase += port->mapbase & ~PAGE_MASK;

#else

        port->membase = ioremap_nocache(port->mapbase, 64);//映射到内存的配置寄存器基础地址

        if (!port->membase) {

            printk(KERN_ERR "%s: Couldn't ioremap 0x%llx\n",    __func__,(unsigned long long) port->mapbase);

            return -ENOMEM;

        }

#endif

    } else if (!strncmp(options, "io,", 3)) {

        port->iotype = UPIO_PORT;

        port->iobase = simple_strtoul(options + 3, &options, 0);

        mmio = 0;

    } else

        return -EINVAL;

    options = strchr(options, ',');//指针移到“115200n8”字符串处

    if (options) { //存在

        options++;

        device->baud = simple_strtoul(options, NULL, 0);//取得波特率115200

        length = min(strcspn(options, " "), sizeof(device->options));

        strncpy(device->options, options, length);//将字符串115200n8拷贝到设备的device->options字段中

    } else {

        device->baud = probe_baud(port);

        snprintf(device->options, sizeof(device->options), "%u",device->baud);

    }

    if (mmio || mmio32)

        printk(KERN_INFO "Early serial console at MMIO%s 0x%llx (options '%s')\n",mmio32 ? "32" : "",(unsigned long long)port->mapbase,device->options);

    else

        printk(KERN_INFO

              "Early serial console at I/O port 0x%lx (options '%s')\n",port->iobase,device->options);

    return 0;

}

static void __init init_port(struct early_serial8250_device *device)

{

    struct uart_port *port = &device->port;

    unsigned int divisor;

    unsigned char c;

    serial_out(port, UART_LCR, 0x3);    /* 8n1 */

    serial_out(port, UART_IER, 0);      /* no interrupt */

    serial_out(port, UART_FCR, 0);      /* no fifo */

    serial_out(port, UART_MCR, 0x3);    /* DTR + RTS */

    divisor = port->uartclk / (16 * device->baud);//根据波特率设置分频

    c = serial_in(port, UART_LCR);

    serial_out(port, UART_LCR, c | UART_LCR_DLAB);

    serial_out(port, UART_DLL, divisor & 0xff);

    serial_out(port, UART_DLM, (divisor >> 8) & 0xff);

    serial_out(port, UART_LCR, c & ~UART_LCR_DLAB);

}

void register_console(struct console *newcon)

{

    int i;

    unsigned long flags;

    struct console *bcon = NULL;

    /*

    现在是注册一个early console,即

    static struct console early_serial8250_console __initdata = {

        .name   = "uart",

        .write  = early_serial8250_write,

        .flags  = CON_PRINTBUFFER | CON_BOOT,//所用具有CON_BOOT属性的console都会在内核初始化到late initcall阶段被注销,相互消他们的函数是

        .index  = -1,

    };

    */

    if (console_drivers && newcon->flags & CON_BOOT) { //注册的是否是引导控制台。early console的CON_BOOT置位,表示只是一个引导控制台,以后会被注销

        for_each_console(bcon) { 遍历全局console_drivers数组  

            if (!(bcon->flags & CON_BOOT)) { //判断是否已经有引导控制台了,有了的话就直接退出

                printk(KERN_INFO "Too late to register bootconsole %s%d\n",newcon->name, newcon->index);

                return;

            }

        }

    }

     

    if (console_drivers && console_drivers->flags & CON_BOOT)//如果注册的是引导控制台 

        bcon = console_drivers;//让bcon指向全局console_drivers  

    if (preferred_console < 0 || bcon || !console_drivers)

        preferred_console = selected_console;//设置preferred_console为uboot命令选择的selected_console(即索引)  

    if (newcon->early_setup)//early console没有初始化early_setup字段,以下这个函数不执行

        newcon->early_setup();//调用serial8250_console_early_setup()

    if (preferred_console < 0) {

        if (newcon->index < 0)

            newcon->index = 0;

        if (newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {

            newcon->flags |= CON_ENABLED;

            if (newcon->device) {

                newcon->flags |= CON_CONSDEV;

                preferred_console = 0;

            }

        }

    }

     //传给内核参数:

     //Kernel command line: console=ttyS2,115200n8 rw root=/dev/ram0 initrd=0xc2000000,20M mem=128M ip=192.168.1.220::192.168.1.1:255.255.255.0::eth0:off

     //所以这里将根据传参console=ttyS2,115200来配置作为console的ttyS2串口

    for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) { //遍历全局console_cmdline找到匹配的

        if (strcmp(console_cmdline[i].name, newcon->name) != 0)//比较终端名称“ttyS”

            continue;

        if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)//console_cmdline[i].index=2。//比较次设备号 

            continue;

        if (newcon->index < 0)

            newcon->index = console_cmdline[i].index;//将终端号赋值给serial8250_console->index

             

#ifdef CONFIG_A11Y_BRAILLE_CONSOLE//没有定义,下边不执行

        if (console_cmdline[i].brl_options) {

            newcon->flags |= CON_BRL;

            braille_register_console(newcon,console_cmdline[i].index,console_cmdline[i].options,console_cmdline[i].brl_options);

            return;

        }

#endif

        //console_cmdline[i].options = "115200n8",对于early console而言setup字段未被初始化,故下边的函数不执行

        if (newcon->setup &&newcon->setup(newcon, console_cmdline[i].options) != 0)//调用serial8250_console_setup()对终端进行配置

            break;

        newcon->flags |= CON_ENABLED; //设置标志为CON_ENABLE(这个在printk调用中使用到)

        newcon->index = console_cmdline[i].index;//设置索引号  

        if (i == selected_console) { //索引号和uboot指定的console的一样

            newcon->flags |= CON_CONSDEV;//设置标志CON_CONSDEV(全局console_drivers链表中靠前)

            preferred_console = selected_console;

        }

        break;

    }//for循环作用大致是查看注册的console是否是uboot知道的引导console,是则设置相关标志和preferred_console

    if (!(newcon->flags & CON_ENABLED))

        return;

    if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))//防止重复打印  

        newcon->flags &= ~CON_PRINTBUFFER;

    acquire_console_sem();

    if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) { //如果是preferred控制台

        newcon->next = console_drivers;

        console_drivers = newcon;//添加进全局console_drivers链表前面位置(printk中会遍历该表调用合适的console的write方法打印信息)

        if (newcon->next)

            newcon->next->flags &= ~CON_CONSDEV;

    } else { //如果不是preferred控制台

        newcon->next = console_drivers->next;

        console_drivers->next = newcon; //添加进全局console_drivers链表后面位置

    }

     

    //主册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,

    //console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来

    if (newcon->flags & CON_PRINTBUFFER) {

        spin_lock_irqsave(&logbuf_lock, flags);

        con_start = log_start;

        spin_unlock_irqrestore(&logbuf_lock, flags);

    }

    release_console_sem();

    if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {

        printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",newcon->name, newcon->index);

        for_each_console(bcon)

            if (bcon->flags & CON_BOOT)

                unregister_console(bcon);

    } else { //调用这里

        printk(KERN_INFO "%sconsole [%s%d] enabled\n",(newcon->flags & CON_BOOT) ? "boot" : "" ,newcon->name, newcon->index);

    }

}

四、在未对console进行初始化之前,内核使用early console进行打印。之后内核进行真正的console初始化

//console_init()在start_kernel()中调用,用来对控制台初始化,这个函数执行完成后,串口可以看到内核用printk()函数打印的信息

void __init console_init(void)

{

 initcall_t *call;

 /* Setup the default TTY line discipline. */

 //此函数调用tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY)

 //#define N_TTY 0

 /*struct tty_ldisc_ops tty_ldisc_N_TTY = {

         .magic           = TTY_LDISC_MAGIC,

         .name            = "n_tty",

         .open            = n_tty_open,

         .close           = n_tty_close,

         .flush_buffer    = n_tty_flush_buffer,

         .chars_in_buffer = n_tty_chars_in_buffer,

         .read            = n_tty_read,

         .write           = n_tty_write,

         .ioctl           = n_tty_ioctl,

         .set_termios     = n_tty_set_termios,

         .poll            = n_tty_poll,

         .receive_buf     = n_tty_receive_buf,

         .write_wakeup    = n_tty_write_wakeup

    };

    内核定义一个tty_ldiscs数组,然后根据数组下标来存放对应的线路规程的操作集,而这里的数组下标表示的就是具体的协议,在头文件中已经通过宏定义好了。例如N_TTY 0。

    所以可以发现:ldisc[0] 存放的是N_TTY对应的线路规程操作集

    ldisc[1]存放的是N_SLIP对应的线路规程操作集

    ldisc[2]存放的就是N_MOUSE对应的线路规程操作集

    依次类推。此处就是ldisc[N_TTY] = tty_ldisc_N_TTY。

 int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)

    {

         unsigned long flags;

         int ret = 0;

         if (disc < N_TTY || disc >= NR_LDISCS)

                 return -EINVAL;

         spin_lock_irqsave(&tty_ldisc_lock, flags);

         tty_ldiscs[disc] = new_ldisc;//tty_ldiscs[0]存放的是N_TTY对应的线路规程操作集

         new_ldisc->num = disc;//0

         new_ldisc->refcount = 0;

         spin_unlock_irqrestore(&tty_ldisc_lock, flags);

         return ret;

     }

*/

 tty_ldisc_begin();//这段代码前面是注册了第0个(逻辑上1)线路规程

  //依次调用从__con_initcall_start到__con_initcall_end之间的函数指针

  //会调用两个函数就是con_init()和serial8250_console_init()

 call = __con_initcall_start;

 while (call < __con_initcall_end) {

  (*call)();

  call++;

 }

}

static int __init serial8250_console_init(void)

{

    if (nr_uarts > UART_NR)//串口数量不能大于3个

        nr_uarts = UART_NR;

    serial8250_isa_init_ports();//对三个串口的uart_8250_port结构静态常量serial8250_ports结构进行初始化,主要是将up->port.ops = &serial8250_pops

    /*

    static struct console serial8250_console = {

        .name       = "ttyS",

        .write      = serial8250_console_write,//写方法

        .device     = uart_console_device,//tty驱动

        .setup      = serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。

        .early_setup    = serial8250_console_early_setup,

        .flags      = CON_PRINTBUFFER | CON_ANYTIME,

        .index      = -1,

        .data       = &serial8250_reg,

    };

    */

    register_console(&serial8250_console);//在这里注册serial8250_console真正的console终端

    return 0;

}

console_initcall(serial8250_console_init);

/*

serial8250_console_init()函数会比serial8250_probe()先调用,所以调用register_console的时候,port还没有初始化,所以当

register_console调用serial8250_console_setup()设置buad,parity bits的时候,

serial8250_console_setup()会检测port->iobase和port->membase是否是有效值,如果不是就返回,

放弃初始化console,所以实际上,console不是在serial8250_console_init()里边初始化,

如果要在serial8250_console_init初始化,需要将port静态初始化.

当serial8250_probe()调用uart_add_one_port->uart_configure_port:

if (port->cons && !(port->cons->flags & CON_ENABLED)){

    printk("%s retister console\n", __FUNCTION__);

    register_console(port->cons);

}

该函数会检查console有没有初始化,如果没有初始化,则调用register_console来初始化.

所以console放在这里初始化也是比较好一些,可以将console_initcall(serial8250_console_init) comment.

*/

//对三个串口的uart_8250_port结构静态常量serial8250_ports结构进行初始化,主要是将up->port.ops = &serial8250_pops

static void __init serial8250_isa_init_ports(void)

{

    struct uart_8250_port *up;

    static int first = 1;

    int i, irqflag = 0;

    if (!first)//静态变量,serial8250_console_init()第一次进入这个函数,之后serial8250_init()再进入这个函数就会直接返回

        return;

    first = 0;

     

    //对三个串口的uart_8250_port结构serial8250_ports结构体进行初始化

    for (i = 0; i < nr_uarts; i++) {

        struct uart_8250_port *up = &serial8250_ports[i];

        up->port.line = i;//0代表串口0,1代表串口1

        spin_lock_init(&up->port.lock);

        init_timer(&up->timer);//初始化定时器

        up->timer.function = serial8250_timeout;//初始化定时器的超时函数

        //ALPHA_KLUDGE_MCR needs to be killed.

        up->mcr_mask = ~ALPHA_KLUDGE_MCR;

        up->mcr_force = ALPHA_KLUDGE_MCR;

         

        //初始化uart_8250_port指向的uart_port字段port的操作

        up->port.ops = &serial8250_pops;

        /*

        static struct uart_ops serial8250_pops = {

            .tx_empty   = serial8250_tx_empty,

            .set_mctrl  = serial8250_set_mctrl,

            .get_mctrl  = serial8250_get_mctrl,

            .stop_tx    = serial8250_stop_tx,

            .start_tx   = serial8250_start_tx,

            .stop_rx    = serial8250_stop_rx,

            .enable_ms  = serial8250_enable_ms,

            .break_ctl  = serial8250_break_ctl,

            .startup    = serial8250_startup,

            .shutdown   = serial8250_shutdown,

            .set_termios    = serial8250_set_termios,

            .set_ldisc  = serial8250_set_ldisc,

            .pm     = serial8250_pm,

            .type       = serial8250_type,

            .release_port   = serial8250_release_port,

            .request_port   = serial8250_request_port,

            .config_port    = serial8250_config_port,

            .verify_port    = serial8250_verify_port,

        #ifdef CONFIG_CONSOLE_POLL

            .poll_get_char = serial8250_get_poll_char,

            .poll_put_char = serial8250_put_poll_char,

        #endif

        };

        */

    }

    if (share_irqs)//中断是否共享(这里设置成不共享)

        irqflag = IRQF_SHARED;

     

    //条件不满足,不会进来初始化

    for (i = 0, up = serial8250_ports;i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;i++, up++) {

/*  up->port.iobase   = old_serial_port[i].port;

        up->port.irq      = irq_canonicalize(old_serial_port[i].irq);

        up->port.irqflags = old_serial_port[i].irqflags;

        up->port.uartclk  = old_serial_port[i].baud_base * 16;

        up->port.flags    = old_serial_port[i].flags;

        up->port.hub6     = old_serial_port[i].hub6;

        up->port.membase  = old_serial_port[i].iomem_base;

        up->port.iotype   = old_serial_port[i].io_type;

        up->port.regshift = old_serial_port[i].iomem_reg_shift;

        set_io_from_upio(&up->port);

        up->port.irqflags |= irqflag;

        if (serial8250_isa_config != NULL)

            serial8250_isa_config(i, &up->port, &up->capabilities);

*/

    }

}

//下边再次调用register_console()注册serial8250_console真正的console终端

void register_console(struct console *newcon)

{

    int i;

    unsigned long flags;

    struct console *bcon = NULL;

    /*

    现在是注册一个serial8250_console,即

    static struct console serial8250_console = {

        .name       = "ttyS",

        .write      = serial8250_console_write,//写方法

        .device     = uart_console_device,//tty驱动

        .setup      = serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。

        .early_setup    = serial8250_console_early_setup,

        .flags      = CON_PRINTBUFFER | CON_ANYTIME,

        .index      = -1,

        .data       = &serial8250_reg,

    };

    */

    if (console_drivers && newcon->flags & CON_BOOT) { //注册的是serial8250_console,CON_BOOT没有置位,不是引导控制台。下边不会进去遍历

        for_each_console(bcon) { 遍历全局console_drivers数组  

            if (!(bcon->flags & CON_BOOT)) { //判断是否已经有引导控制台了,有了的话就直接退出

                printk(KERN_INFO "Too late to register bootconsole %s%d\n",newcon->name, newcon->index);

                return;

            }

        }

    }

     

    if (console_drivers && console_drivers->flags & CON_BOOT)//如果注册的是引导控制台,serial8250_console不是引导控制台

        bcon = console_drivers;//这里不执行

    if (preferred_console < 0 || bcon || !console_drivers)

        preferred_console = selected_console;//设置preferred_console为uboot命令选择的selected_console(即在Uboot传入的参数“console=ttyS2,115200n8”在console_cmdline[]数组中的索引)  

                                                                                 //这里preferred_console =0

    if (newcon->early_setup)//serial8250_console初始化early_setup字段

        newcon->early_setup();//调用serial8250_console_early_setup()

    if (preferred_console < 0) { //由于preferred_console =0,不会进入下边

        if (newcon->index < 0)

            newcon->index = 0;

        if (newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {

            newcon->flags |= CON_ENABLED;

            if (newcon->device) {

                newcon->flags |= CON_CONSDEV;

                preferred_console = 0;

            }

        }

    }

     //传给内核参数:

     //Kernel command line: console=ttyS2,115200n8 rw root=/dev/ram0 initrd=0xc2000000,20M mem=128M ip=192.168.1.220::192.168.1.1:255.255.255.0::eth0:off

     //所以这里将根据传参console=ttyS2,115200来配置作为console的ttyS2串口

    for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) { //遍历全局console_cmdline找到匹配的,i=0就是匹配的“ttyS2”

        if (strcmp(console_cmdline[i].name, newcon->name) != 0)//比较终端名称“ttyS”

            continue;

        if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)//console_cmdline[i].index=2。//比较次设备号 

            continue;

        if (newcon->index < 0)

            newcon->index = console_cmdline[i].index;//将终端号赋值给serial8250_console->index,这里是2

             

        //console_cmdline[i].options = "115200n8",对于serial8250_console而言setup字段已初始化

        if (newcon->setup && newcon->setup(newcon, console_cmdline[i].options) != 0)//调用serial8250_console_setup()对终端进行配置,调用不成功

            break;

        //在这里注册serial8250_console时,调用serial8250_console_setup()由于port->iobase和port->membase不是有效值,

        //故返回错误,这样下边的操作不会执行,直接break跳出,从flag1出跳出函数。即在这里serial8250_console没有注册成功

        //由于内核在下边的操作队串口进行初始化时,还会调用register_console()来注册serial8250_console,在那时注册就会成功

         

        newcon->flags |= CON_ENABLED; //设置标志为CON_ENABLE,表示console使能(这个在printk调用中使用到)

        newcon->index = console_cmdline[i].index;//设置索引号  

        if (i == selected_console) { //索引号和uboot指定的console的一样

            newcon->flags |= CON_CONSDEV;//设置标志CON_CONSDEV(全局console_drivers链表中靠前)

            preferred_console = selected_console;

        }

        break;

    }//for循环作用大致是查看注册的console是否是uboot知道的引导console,是则设置相关标志和preferred_console

  //flag1:

    if (!(newcon->flags & CON_ENABLED))//若前边没有设置CON_ENABLED标志,就退出

        return;

    if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))//防止重复打印  

        newcon->flags &= ~CON_PRINTBUFFER;

    acquire_console_sem();

    if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) { //如果是preferred控制台

        newcon->next = console_drivers;

        console_drivers = newcon;//添加进全局console_drivers链表前面位置(printk中会遍历该表调用合适的console的write方法打印信息)

        if (newcon->next)

            newcon->next->flags &= ~CON_CONSDEV;

    } else { //如果不是preferred控制台

        newcon->next = console_drivers->next;

        console_drivers->next = newcon; //添加进全局console_drivers链表后面位置

    }

     

    //主册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,

    //console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来

    if (newcon->flags & CON_PRINTBUFFER) {

        spin_lock_irqsave(&logbuf_lock, flags);

        con_start = log_start;

        spin_unlock_irqrestore(&logbuf_lock, flags);

    }

    release_console_sem();

    if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {

        printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",newcon->name, newcon->index);

        for_each_console(bcon)

            if (bcon->flags & CON_BOOT)

                unregister_console(bcon);

    } else { //调用这里

        printk(KERN_INFO "%sconsole [%s%d] enabled\n",(newcon->flags & CON_BOOT) ? "boot" : "" ,newcon->name, newcon->index);

    }

}

//serial8250_console_early_setup()-->serial8250_find_port_for_earlycon()

int serial8250_find_port_for_earlycon(void)

{

    struct early_serial8250_device *device = &early_device;//early console初始化时对early_device结构的初始化

    struct uart_port *port = &device->port;

    int line;

    int ret;

    if (!device->port.membase && !device->port.iobase)//early_device结构初始化时已经配置好

        return -ENODEV;

    //early console注册时不会调用此函数。

    //当真正的console初始化时,会调用此函数。

    //真正的console初始化时,会查找early console注册时用的是哪一个串口号,从serial8250_ports[]中根据uart_port->mapbase地址来比对

    line = serial8250_find_port(port);//根据uart_port结构找到串口号,比对没有找到串口号,line返回负值

    if (line < 0)

        return -ENODEV;//从这里返回,下边的不再执行

     

    //若找到early console用的串口号,更新当初传入内核参数使用的console_cmdline[i],名称改成ttyS。。。。

    ret = update_console_cmdline("uart", 8250, "ttyS", line, device->options);

    if (ret < 0)

        ret = update_console_cmdline("uart", 0,"ttyS", line, device->options);

    return ret;

}

static int __init serial8250_console_setup(struct console *co, char *options)

{

    struct uart_port *port;

    int baud = 9600;

    int bits = 8;

    int parity = 'n';

    int flow = 'n';

    if (co->index >= nr_uarts)//console的索引,这里是2,即ttyS2

        co->index = 0;

    port = &serial8250_ports[co->index].port;//找到对应的ttyS2的uart_port结构

     

    //由于console_init在注册serial8250_console时调用的register_console()函数调用serial8250_console_setup()

    //进入这个函数时,由于ttyS2的uart_port结构没有初始化,port->iobase 和port->membase值都未设置,所以直接从下边返回

    //当进行串口初始化时,还会回来注册serial8250_console,再调用到这里,由于设置了ttyS2的uart_port结构,所以下边的配置就会成功

    if (!port->iobase && !port->membase)//第一次注册时,由于未设置,从这里直接返回

        return -ENODEV;

    if (options)//如果options不为空,就将options里的数值写给baud, &parity, &bits, &flow

        uart_parse_options(options, &baud, &parity, &bits, &flow);

    //没有配置options,则使用缺省值,否则使用传下来的的参数options里的串口配置

    return uart_set_options(port, co, baud, parity, bits, flow);

}

五、通过四知道,在对console注册时,没有成功,由于串口还没有配置。当对串口配置时再对console注册就能成功。

serial8250_console就能注册到内核全局变量console_drivers中。这样终端打印时就通过注册的serial8250_console就能将信息打印到终端上。

  

//内核的打印函数

asmlinkage int printk(const char *fmt, ...)

{

    va_list args;   //可变参数链表

    int r;

#ifdef CONFIG_KGDB_KDB

    if (unlikely(kdb_trap_printk)) {

        va_start(args, fmt);

        r = vkdb_printf(fmt, args);

        va_end(args);

        return r;

    }

#endif

    va_start(args, fmt);    //获取第一个可变参数

    r = vprintk(fmt, args); //调用vprintk函数

    va_end(args);   //释放可变参数链表指针

    return r;

}

//vprintk函数

asmlinkage int vprintk(const char *fmt, va_list args)

{

    int printed_len = 0;

    int current_log_level = default_message_loglevel;

    unsigned long flags;

    int this_cpu;

    char *p;

    boot_delay_msec();

    printk_delay();

    preempt_disable();

    raw_local_irq_save(flags);

    this_cpu = smp_processor_id();

    if (unlikely(printk_cpu == this_cpu)) {

        if (!oops_in_progress) {

            recursion_bug = 1;

            goto out_restore_irqs;

        }

        zap_locks();

    }

    lockdep_off();

    spin_lock(&logbuf_lock);

    printk_cpu = this_cpu;

    if (recursion_bug) {

        recursion_bug = 0;

        strcpy(printk_buf, recursion_bug_msg);

        printed_len = strlen(recursion_bug_msg);

    }

    printed_len += vscnprintf(printk_buf + printed_len,sizeof(printk_buf) - printed_len, fmt, args);

    p = printk_buf;

    if (p[0] == '<') { //处理打印级别字段

        unsigned char c = p[1];

        if (c && p[2] == '>') {

            switch (c) {

            case '0' ... '7': /* loglevel */

                current_log_level = c - '0';

            case 'd': /* KERN_DEFAULT */

                if (!new_text_line) {

                    emit_log_char('\n');

                    new_text_line = 1;

                }

            case 'c': /* KERN_CONT */

                p += 3;

                break;

            }

        }

    }

    for ( ; *p; p++) {

        if (new_text_line) {

            /* Always output the token */

            emit_log_char('<');

            emit_log_char(current_log_level + '0');

            emit_log_char('>');

            printed_len += 3;

            new_text_line = 0;

            if (printk_time) {      //打印时间信息

                /* Follow the token with the time */

                char tbuf[50], *tp;

                unsigned tlen;

                unsigned long long t;

                unsigned long nanosec_rem;

                t = cpu_clock(printk_cpu);

                nanosec_rem = do_div(t, 1000000000);

                tlen = sprintf(tbuf, "[%5lu.%06lu] ",(unsigned long) t,nanosec_rem / 1000);

                for (tp = tbuf; tp < tbuf + tlen; tp++)

                    emit_log_char(*tp);

                printed_len += tlen;

            }

            if (!*p)

                break;

        }

        emit_log_char(*p);

        if (*p == '\n')

            new_text_line = 1;

    }

    if (acquire_console_semaphore_for_printk(this_cpu))

        release_console_sem();

    lockdep_on();

out_restore_irqs:

    raw_local_irq_restore(flags);

    preempt_enable();

    return printed_len;

}

//接着调用release_console_sem函数

void release_console_sem(void)

{

    unsigned long flags;

    unsigned _con_start, _log_end;

    unsigned wake_klogd = 0;

    if (console_suspended) {

        up(&console_sem);

        return;

    }

    console_may_schedule = 0;

    for ( ; ; ) {

        spin_lock_irqsave(&logbuf_lock, flags);

        wake_klogd |= log_start - log_end;

        if (con_start == log_end)

            break;          /* Nothing to print */

        _con_start = con_start;

        _log_end = log_end;

        con_start = log_end;        /* Flush */

        spin_unlock(&logbuf_lock);

        stop_critical_timings();    /* don't trace print latency */

        call_console_drivers(_con_start, _log_end);

        start_critical_timings();

        local_irq_restore(flags);

    }

    console_locked = 0;

    up(&console_sem);

    spin_unlock_irqrestore(&logbuf_lock, flags);

    if (wake_klogd)

        wake_up_klogd();

}

EXPORT_SYMBOL(release_console_sem);

//调用call_console_drivers函数

static void call_console_drivers(unsigned start, unsigned end)

{

    unsigned cur_index, start_print;

    static int msg_level = -1;

    BUG_ON(((int)(start - end)) > 0);

    cur_index = start;

    start_print = start;

    while (cur_index != end) {

        if (msg_level < 0 && ((end - cur_index) > 2) &&LOG_BUF(cur_index + 0) == '<' &&LOG_BUF(cur_index + 1) >= '0' &&LOG_BUF(cur_index + 1) <= '7' &&LOG_BUF(cur_index + 2) == '>') {

            msg_level = LOG_BUF(cur_index + 1) - '0';

            cur_index += 3;

            start_print = cur_index;

        }

        while (cur_index != end) {

            char c = LOG_BUF(cur_index);

            cur_index++;

            if (c == '\n') {

                if (msg_level < 0) {

                    msg_level = default_message_loglevel;

                }

                _call_console_drivers(start_print, cur_index, msg_level);

                msg_level = -1;

                start_print = cur_index;

                break;

            }

        }

    }

    _call_console_drivers(start_print, end, msg_level);

}_call_console_drivers函数

//调用console的写方法

static void __call_console_drivers(unsigned start, unsigned end) 

    struct console *con; 

   

    for_each_console(con) { //遍历console_drivers数组 #define for_each_console(con) for (con = console_drivers; con != NULL; con = con->next) 

        if ((con->flags & CON_ENABLED) && con->write &&(cpu_online(smp_processor_id()) ||(con->flags & CON_ANYTIME))) 

            con->write(con, &LOG_BUF(start), end - start);   //调用console的写方法  

    

//由于已经注册的终端是serial8250_console,这个终端的写方法是调用serial8250_console_write()函数--->uart_console_write()--->serial8250_console_putchar()

//--->serial_out()最终打印在串口2终端上

/*

    static struct console serial8250_console = {

        .name       = "ttyS",

        .write      = serial8250_console_write,//写方法

        .device     = uart_console_device,//tty驱动

        .setup      = serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。

        .early_setup    = serial8250_console_early_setup,

        .flags      = CON_PRINTBUFFER | CON_ANYTIME,

        .index      = -1,

        .data       = &serial8250_reg,

    };

    */

console_drivers链表在register_console中会设置

猜你喜欢

转载自blog.csdn.net/pan0755/article/details/108472847