DSP

uclinux内核的console

2019-07-13 20:22发布

  快乐虾 http://blog.csdn.net/lights_joy/ lights@hb165.com 本文适用于 ADI bf561 DSP 优视BF561EVB开发板 uclinux-2008r1.5-rc3 (smp patch) Visual DSP++ 5.0 (update 5)     欢迎转载,但请保留作者信息
内核中与console相关的结构体可以分为通用定义与不同体系结构的定义两部分,通用定义与具体的硬件无关,它只是定义了一类硬件的通用参数与接口,不同的体系结构下还需要加上一些特有的东西。 1.1    串口通用定义
1.1.1   uart_ops
这个结构体的定义位于include/linux/serial_core.h,它定义了UART要实现的操作: /*  * This structure describes all the operations that can be  * done on the physical hardware.  */ struct uart_ops {      unsigned int  (*tx_empty)(struct uart_port *);      void     (*set_mctrl)(struct uart_port *, unsigned int mctrl);      unsigned int  (*get_mctrl)(struct uart_port *);      void     (*stop_tx)(struct uart_port *);      void     (*start_tx)(struct uart_port *);      void     (*send_xchar)(struct uart_port *, char ch);      void     (*stop_rx)(struct uart_port *);      void     (*enable_ms)(struct uart_port *);      void     (*break_ctl)(struct uart_port *, int ctl);      int      (*startup)(struct uart_port *);      void     (*shutdown)(struct uart_port *);      void     (*set_termios)(struct uart_port *, struct ktermios *new,                           struct ktermios *old);      void     (*pm)(struct uart_port *, unsigned int state,                     unsigned int oldstate);      int      (*set_wake)(struct uart_port *, unsigned int state);        /*       * Return a string describing the type of the port       */      const char *(*type)(struct uart_port *);        /*       * Release IO and memory resources used by the port.       * This includes iounmap if necessary.       */      void     (*release_port)(struct uart_port *);        /*       * Request IO and memory resources used by the port.       * This includes iomapping the port if necessary.       */      int      (*request_port)(struct uart_port *);      void     (*config_port)(struct uart_port *, int);      int      (*verify_port)(struct uart_port *, struct serial_struct *);      int      (*ioctl)(struct uart_port *, unsigned int, unsigned long); };   1.1.2   uart_port
这个结构体的定义位于include/linux/serial_core.h,从它的位置可以看出,这是一个与具体硬件无关的结构体,它提供了对UART的描述信息,对于某个具体的UART,可能只使用其中的某些字段。 struct uart_port {      spinlock_t         lock;              /* port lock */      unsigned int       iobase;            /* in/out[bwl] */      unsigned char __iomem  *membase;     /* read/write[bwl] */      unsigned int       irq;          /* irq number */      unsigned int       uartclk;      /* base uart clock */      unsigned int       fifosize;     /* tx fifo size */      unsigned char      x_char;            /* xon/xoff char */      unsigned char      regshift;     /* reg offset shift */      unsigned char      iotype;            /* io access style */      unsigned char      unused1;        unsigned int       read_status_mask;  /* driver specific */      unsigned int       ignore_status_mask;    /* driver specific */      struct uart_info   *info;             /* pointer to parent info */      struct uart_icount icount;            /* statistics */        struct console         *cons;             /* struct console, if any */        upf_t              flags;        unsigned int       mctrl;             /* current modem ctrl settings */      unsigned int       timeout;      /* character-based timeout */      unsigned int       type;              /* port type */      const struct uart_ops  *ops;      unsigned int       custom_divisor;      unsigned int       line;              /* port index */      unsigned long      mapbase;      /* for ioremap */      struct device      *dev;              /* parent device */      unsigned char      hub6;              /* this should be in the 8250 driver */      unsigned char      unused[3];      void          *private_data;         /* generic platform data pointer */ }; 在内核中,没有为uart_port定义独立的变量,它将从属于某个具体的serial_port,对于bf561,它将从属于bfin_serial_port这一结构体。 实际上内核只使用了以下几个成员: l         uartclk:这个值将设置为BF561的系统时钟频率,在我的系统中,它将为99M。 l         ops:定义对UART的操作函数,指向bfin_serial_pops这一变量。 l         line:串口序号,只有一个串口,恒为0。 l         iotype:取UPIO_MEM,即直接寄存器访问方式。 l         membase:指向UART_THR(0xFFC0 0400),即UART的发送寄存器。 l         mapbase:与membase相同。 l         irq:内核中中断描述数组(irq_desc)的序号,指UART接收中断(IRQ_UART_RX)。 l         flags:配置为UPF_BOOT_AUTOCONF。     1.2    不同体系结构下的串口定义
1.2.1   bfin_serial_port
这个结构体的定义在include/asm/mach/bfin-serial-5xx.h中: struct bfin_serial_port {         struct uart_port        port;         unsigned int            old_status;          unsigned int lsr; #ifdef CONFIG_SERIAL_BFIN_DMA      int           tx_done;      int           tx_count;      struct circ_buf        rx_dma_buf;      struct timer_list       rx_dma_timer;      int           rx_dma_nrows;      unsigned int       tx_dma_channel;      unsigned int       rx_dma_channel;      struct work_struct tx_dma_workqueue; #endif }; 此结构体在uart_port的基础上,扩充了几个成员。 在内核中,有这样的定义: struct bfin_serial_port bfin_serial_ports[NR_PORTS]; 在这里,NR_PORTS的值为1,对UART的所有操作都将通过这一变量来完成。 需要注意的是rx_dma_timer这个成员,在串口初始化的时候,它将调用          init_timer(&(bfin_serial_ports[i].rx_dma_timer)); 向内核注册一个时钟源。 1.3    console
这个结构体的定义在include/linux/console.h中,它定义了一个console驱动需要提供的信息及其必须实现的一些操作: /*  * The interface for a console, or any other device that wants to capture  * console messages (printer driver?)  *  * If a console driver is marked CON_BOOT then it will be auto-unregistered  * when the first real console is registered.  This is for early-printk drivers.  */   struct console {      char name[16];      void (*write)(struct console *, const char *, unsigned);      int  (*read)(struct console *, char *, unsigned);      struct tty_driver *(*device)(struct console *, int *);      void (*unblank)(void);      int  (*setup)(struct console *, char *);      short    flags;      short    index;      int  cflag;      void *data;      struct   console *next; }; 在内核中,对此结构体初始化为: static struct console bfin_serial_console = {      .name         = BFIN_SERIAL_NAME,   // 即”ttyBF”      .write        = bfin_serial_console_write,      .device       = uart_console_device,      .setup        = bfin_serial_console_setup,      .flags        = CON_PRINTBUFFER,      .index        = -1,      .data         = &bfin_serial_reg, }; 而console中的cflag则保存了串口配置,如CREAD | HUPCL | CLOCAL | B57600 | CS8,使用它即可知道当前的串口配置。 flags的值在初始化完成后则变成了CON_PRINTBUFFER | CON_ENABLED | CON_CONSDEV。 index的值在初始化完成后变成0,因为只使用serial console。 这里值得注意的是setup回调函数,当console初始化时,它需要初始化与此console相关的硬件,此时它将调用setup这一回调函数来完成此工作。 1.4    ktermios
这个结构体用于定义一个终端需要使用的参数: struct ktermios {      tcflag_t c_iflag;               /* input mode flags */      tcflag_t c_oflag;               /* output mode flags */      tcflag_t c_cflag;               /* control mode flags */      tcflag_t c_lflag;               /* local mode flags */      cc_t c_line;                    /* line discipline */      cc_t c_cc[NCCS];                /* control characters */      speed_t c_ispeed;               /* input speed */      speed_t c_ospeed;               /* output speed */ }; 它并没有定义相关的结构体,仅仅用于向bfin_serial_set_termios传递参数。 几个比较重要的值: c_cflag:串口的波特率、校验位、字长这几个参数都通过这个标志来传递。   1       参考资料
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lights_joy/archive/2009/01/31/3855503.aspx   在内核启动初期,为了尽可能早地输出一些调试信息,可以在配置时选择使用early console,其选项为CONFIG_EARLY_PRINTK,然后通过earlyprintk=??传递一个参数进去。当内核检测到earlyprintk这一参数时,它将调用setup_early_printk函数初始化BF561内部的UART,然后注册一个console,这样printk的信息就可以通过串口输出。earlyprintk参数的分析参见《uclinux内核参数处理(5):earlyprintk》。 1.1    全局变量
1.1.1   bfin_serial_pops
这个全局变量用以指定对bf561内部串口进行操作的一些回调函数,其定义为: static struct uart_ops bfin_serial_pops = {      .tx_empty = bfin_serial_tx_empty,      .set_mctrl    = bfin_serial_set_mctrl,      .get_mctrl    = bfin_serial_get_mctrl,      .stop_tx = bfin_serial_stop_tx,      .start_tx = bfin_serial_start_tx,      .stop_rx = bfin_serial_stop_rx,      .enable_ms    = bfin_serial_enable_ms,      .break_ctl    = bfin_serial_break_ctl,      .startup = bfin_serial_startup,      .shutdown = bfin_serial_shutdown,      .set_termios  = bfin_serial_set_termios,      .type         = bfin_serial_type,      .release_port = bfin_serial_release_port,      .request_port = bfin_serial_request_port,      .config_port  = bfin_serial_config_port,      .verify_port  = bfin_serial_verify_port, };     1.1.2   bfin_serial_ports
这个全局变量的定义为: struct bfin_serial_port bfin_serial_ports[NR_PORTS]; 在这里NR_PORTS的值为1。 这个全局变量的初始化由bfin_serial_init_ports函数完成,经过此函数的初始化后,此变量的值为: struct bfin_serial_port {         struct uart_port        port; struct uart_port {      spinlock_t         lock;              /* port lock */      unsigned int       iobase;            /* in/out[bwl] */      unsigned char __iomem  *membase;     /*指向UART_THR(0xFFC0 0400),即UART的发送寄存器。 */      unsigned int       irq;          /* 取IRQ_UART_RX */      unsigned int       uartclk;      /* 取SCLK */      unsigned int       fifosize;     /* tx fifo size */      unsigned char      x_char;            /* xon/xoff char */      unsigned char      regshift;     /* reg offset shift */      unsigned char      iotype;            /* UPIO_MEM,以直接寄存器访问方式进行操作 */      unsigned char      unused1;        unsigned int       read_status_mask;  /* driver specific */      unsigned int       ignore_status_mask;    /* driver specific */      struct uart_info   *info;             /* pointer to parent info */      struct uart_icount icount;            /* statistics */        struct console         *cons;             /* struct console, if any */        upf_t              flags;     /* 取UPF_BOOT_AUTOCONF */        unsigned int       mctrl;             /* current modem ctrl settings */      unsigned int       timeout;      /* character-based timeout */      unsigned int       type;              /* port type */      const struct uart_ops  *ops;         /* 指向bfin_serial_pops */      unsigned int       custom_divisor;      unsigned int       line;              /* 串口序号,取0 */      unsigned long      mapbase;      /*指向UART_THR(0xFFC0 0400),即UART的发送寄存器。 */      struct device      *dev;              /* parent device */      unsigned char      hub6;              /* this should be in the 8250 driver */      unsigned char      unused[3];      void          *private_data;         /* generic platform data pointer */ };           unsigned int            old_status;          unsigned int lsr; #ifdef CONFIG_SERIAL_BFIN_DMA      int           tx_done;           /* 初始化为1 */      int           tx_count;          /* 初始化为0 */      struct circ_buf        rx_dma_buf;      struct timer_list       rx_dma_timer;      int           rx_dma_nrows;      unsigned int       tx_dma_channel;  /* 取18,即CH_UART_TX */      unsigned int       rx_dma_channel;  /* 取17,即CH_UART_RX */      struct work_struct tx_dma_workqueue; #endif };   1.1.3   bfin_early_serial_console
这个就是得以使用的early console的全局变量,其定义为: static struct __init console bfin_early_serial_console = {      .name = "early_BFuart",      .write = early_serial_write,      .device = uart_console_device,      .flags = CON_PRINTBUFFER,      .setup = bfin_serial_console_setup,      .index = -1,      .data  = &bfin_serial_reg, }; 经过初始化后,flags的值将变为 CON_PRINTBUFFER | CON_BOOT 。   1.1.4   bfin_serial_reg
这个全局变量的定义为: static struct uart_driver bfin_serial_reg = {      .owner             = THIS_MODULE,      .driver_name       = "bfin-uart",      .dev_name     = BFIN_SERIAL_NAME,      .major             = BFIN_SERIAL_MAJOR,      .minor             = BFIN_SERIAL_MINOR,      .nr           = NR_PORTS,      .cons              = BFIN_SERIAL_CONSOLE, };       1.2    初始化过程
1.2.1   setup_early_printk
当内核调用此函数时,仅仅做了一些最基本的硬件初始化工作,如CCLK,EBIU等。下面看看它的实现: int __init setup_early_printk(char *buf) {        /* Crashing in here would be really bad, so check both the var         and the pointer before we start using it       */      if (!buf)          return 0;        if (!*buf)          return 0;        if (early_console != NULL)          return 0;   #ifdef CONFIG_SERIAL_BFIN      /* Check for Blackfin Serial */      if (!strncmp(buf, "serial,uart", 11)) {          buf += 11;          early_console = earlyserial_init(buf);      } #endif #ifdef CONFIG_FB          /* TODO: add framebuffer console support */ #endif        if (likely(early_console)) {          early_console->flags |= CON_BOOT;            register_console(early_console);          printk(KERN_INFO "early printk enabled on %s%d/n",               early_console->name,               early_console->index);      }        return 0; } 这段代码在简单判断后,有两个关键的调用。第一个是earlyserial_init,用以初始化串口的硬件参数。第二个调用是register_console,用以注册一个console结构体,同时输出printk缓冲区中的已有数据。 1.2.2   earlyserial_init
这一函数的实现为: static struct console * __init earlyserial_init(char *buf) {      int baud, bit;      char parity;      unsigned int serial_port = DEFAULT_PORT;      unsigned int cflag = DEFAULT_CFLAG;        serial_port = simple_strtoul(buf, &buf, 10);      buf++;        cflag = 0;      baud = simple_strtoul(buf, &buf, 10);      switch (baud) {      case 1200:          cflag |= B1200;          break;      case 2400:          cflag |= B2400;          break;      case 4800:          cflag |= B4800;          break;      case 9600:          cflag |= B9600;          break;      case 19200:          cflag |= B19200;          break;      case 38400:          cflag |= B38400;          break;      case 115200:          cflag |= B115200;          break;      default:          cflag |= B57600;      }        parity = buf[0];      buf++;      switch (parity) {      case 'e':          cflag |= PARENB;          break;      case 'o':          cflag |= PARODD;          break;      }        bit = simple_strtoul(buf, &buf, 10);      switch (bit) {      case 5:          cflag |= CS5;          break;      case 6:          cflag |= CS5;          break;      case 7:          cflag |= CS5;          break;      default:          cflag |= CS8;      }   #ifdef CONFIG_SERIAL_BFIN      return bfin_earlyserial_init(serial_port, cflag); #else      return NULL; #endif   } 很简单,实际上就是提取earlyprintk中的参数,将之转换为serial_port和cflag两个数值,然后调用bfin_earlyserial_init函数来初始化串口。 1.2.3   bfin_earlyserial_init
此函数如下所示: struct console __init *bfin_earlyserial_init(unsigned int port,                             unsigned int cflag) {      struct bfin_serial_port *uart;      struct ktermios t;        if (port == -1 || port >= nr_ports)          port = 0;      bfin_serial_init_ports();      bfin_early_serial_console.index = port;      uart = &bfin_serial_ports[port];      t.c_cflag = cflag;      t.c_iflag = 0;      t.c_oflag = 0;      t.c_lflag = ICANON;      t.c_line = port;      bfin_serial_set_termios(&uart->port, &t, &t);      return &bfin_early_serial_console; } 这个函数除了初始化硬件参数之外,还要构造一个console结构体。 1.2.3.1             bfin_serial_init_ports
这个函数由bfin_earlyserial_init调用,其实现为: static void __init bfin_serial_init_ports(void) {      static int first = 1;      int i;        if (!first)          return;      first = 0;        for (i = 0; i < nr_ports; i++) {          bfin_serial_ports[i].port.uartclk   = get_sclk();          bfin_serial_ports[i].port.ops       = &bfin_serial_pops;          bfin_serial_ports[i].port.line      = i;          bfin_serial_ports[i].port.iotype    = UPIO_MEM;          bfin_serial_ports[i].port.membase   =               (void __iomem *)bfin_serial_resource[i].uart_base_addr;          bfin_serial_ports[i].port.mapbase   =               bfin_serial_resource[i].uart_base_addr;          bfin_serial_ports[i].port.irq       =               bfin_serial_resource[i].uart_irq;          bfin_serial_ports[i].port.flags     = UPF_BOOT_AUTOCONF; #ifdef CONFIG_SERIAL_BFIN_DMA          bfin_serial_ports[i].tx_done         = 1;          bfin_serial_ports[i].tx_count        = 0;          bfin_serial_ports[i].tx_dma_channel =               bfin_serial_resource[i].uart_tx_dma_channel;          bfin_serial_ports[i].rx_dma_channel =               bfin_serial_resource[i].uart_rx_dma_channel;          init_timer(&(bfin_serial_ports[i].rx_dma_timer)); #endif #ifdef CONFIG_SERIAL_BFIN_CTSRTS          init_timer(&(bfin_serial_ports[i].cts_timer));          bfin_serial_ports[i].cts_pin         =               bfin_serial_resource[i].uart_cts_pin;          bfin_serial_ports[i].rts_pin         =               bfin_serial_resource[i].uart_rts_pin; #endif          bfin_serial_hw_init(&bfin_serial_ports[i]);      } } 在这里nr_ports的值为1,即只有一个串口。从上述代码可以看出,它只是设置了uart结构体的内容,但是并没有初始化硬件。最后一个函数调用bfin_serial_hw_init,看起来好像要初始化硬件的样子,实际上什么也没做。 static void bfin_serial_hw_init(struct bfin_serial_port *uart) {   #ifdef CONFIG_SERIAL_BFIN_UART0      peripheral_request(P_UART0_TX, DRIVER_NAME);      peripheral_request(P_UART0_RX, DRIVER_NAME); #endif   #ifdef CONFIG_SERIAL_BFIN_CTSRTS      if (uart->cts_pin >= 0) {          gpio_request(uart->cts_pin, DRIVER_NAME);          gpio_direction_input(uart->cts_pin);      }      if (uart->rts_pin >= 0) {          gpio_request(uart->rts_pin, DRIVER_NAME);          gpio_direction_input(uart->rts_pin, 0);      } #endif } 1.2.3.2             bfin_serial_set_termios
这个函数将实际配置硬件参数,与下文所示的普通console功能相同,此时传递进来的将是bfin_serial_ports[0]这个全局变量中的port的指针,实际上也是bfin_serial_ports[0]的首地址,因此在此函数一开头就将之转换为bfin_serial_port类型的指针。 static void bfin_serial_set_termios(struct uart_port *port, struct ktermios *termios,             struct ktermios *old) {      struct bfin_serial_port *uart = (struct bfin_serial_port *)port;      unsigned long flags;      unsigned int baud, quot;      unsigned short val, ier, lcr = 0;        switch (termios->c_cflag & CSIZE) {      case CS8:          lcr = WLS(8);          break;      case CS7:          lcr = WLS(7);          break;      case CS6:          lcr = WLS(6);          break;      case CS5:          lcr = WLS(5);          break;      default:          printk(KERN_ERR "%s: word lengh not supported/n",               __FUNCTION__);      }        if (termios->c_cflag & CSTOPB)          lcr |= STB;      if (termios->c_cflag & PARENB)          lcr |= PEN;      if (!(termios->c_cflag & PARODD))          lcr |= EPS;      if (termios->c_cflag & CMSPAR)          lcr |= STP;        port->read_status_mask = OE;      if (termios->c_iflag & INPCK)          port->read_status_mask |= (FE | PE);      if (termios->c_iflag & (BRKINT | PARMRK))          port->read_status_mask |= BI;        /*       * Characters to ignore       */      port->ignore_status_mask = 0;      if (termios->c_iflag & IGNPAR)          port->ignore_status_mask |= FE | PE;      if (termios->c_iflag & IGNBRK) {          port->ignore_status_mask |= BI;          /*           * If we're ignoring parity and break indicators,           * ignore overruns too (for real raw support).           */          if (termios->c_iflag & IGNPAR)               port->ignore_status_mask |= OE;      }        baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);      quot = uart_get_divisor(port, baud);      spin_lock_irqsave(&uart->port.lock, flags);        UART_SET_ANOMALY_THRESHOLD(uart, USEC_PER_SEC / baud * 15);        /* Disable UART */      ier = UART_GET_IER(uart); #ifdef CONFIG_BF54x      UART_CLEAR_IER(uart, 0xF); #else      UART_PUT_IER(uart, 0); #endif   #ifndef CONFIG_BF54x      /* Set DLAB in LCR to Access DLL and DLH */      val = UART_GET_LCR(uart);      val |= DLAB;      UART_PUT_LCR(uart, val);      SSYNC(); #endif        UART_PUT_DLL(uart, quot & 0xFF);      SSYNC();      UART_PUT_DLH(uart, (quot >> 8) & 0xFF);      SSYNC();   #ifndef CONFIG_BF54x      /* Clear DLAB in LCR to Access THR RBR IER */      val = UART_GET_LCR(uart);      val &= ~DLAB;      UART_PUT_LCR(uart, val);      SSYNC(); #endif        UART_PUT_LCR(uart, lcr);        /* Enable UART */ #ifdef CONFIG_BF54x      UART_SET_IER(uart, ier); #else      UART_PUT_IER(uart, ier); #endif        val = UART_GET_GCTL(uart);      val |= UCEN;      UART_PUT_GCTL(uart, val);        spin_unlock_irqrestore(&uart->port.lock, flags); } 此时传递进来的termios变量中的几个值: c_cflag:此成员中保存了串口的硬件参数。 c_lflag:此成员的值为ICANON。 其它值均为0。 还有一点需要注意,在使用u-boot之类的引导程序时,通常会打开接收中断或者发送中断,而在不使用引导程序时,UART_IER复位后的值为0,为了模拟引导程序,可以在最后还原UART_IER时直接打开接收中断。类似于下面的语句:      /* Enable UART */ #ifdef CONFIG_BF54x      UART_SET_IER(uart, ier); #else      ier |= (ERBFI | ETBEI);      UART_PUT_IER(uart, ier); #endif       1.2.4   register_console
这个和普通console的功能相同,其实现为: /*  * The console driver calls this routine during kernel initialization  * to register the console printing procedure with printk() and to  * print any messages that were printed by the kernel before the  * console driver was initialized.  */ void register_console(struct console *console) {      int i;      unsigned long flags;      struct console *bootconsole = NULL;        if (console_drivers) {          if (console->flags & CON_BOOT)               return;          if (console_drivers->flags & CON_BOOT)               bootconsole = console_drivers;      }        if (preferred_console < 0 || bootconsole || !console_drivers)          preferred_console = selected_console;        /*       *   See if we want to use this console driver. If we       *   didn't select a console we take the first one       *   that registers here.       */      if (preferred_console < 0) {          if (console->index < 0)               console->index = 0;          if (console->setup == NULL ||              console->setup(console, NULL) == 0) {               console->flags |= CON_ENABLED | CON_CONSDEV;               preferred_console = 0;          }      }        /*       *   See if this console matches one we selected on       *   the command line.       */      for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];               i++) {          if (strcmp(console_cmdline[i].name, console->name) != 0)               continue;          if (console->index >= 0 &&              console->index != console_cmdline[i].index)               continue;          if (console->index < 0)               console->index = console_cmdline[i].index;          if (console->setup &&              console->setup(console, console_cmdline[i].options) != 0)               break;          console->flags |= CON_ENABLED;          console->index = console_cmdline[i].index;          if (i == selected_console) {               console->flags |= CON_CONSDEV;               preferred_console = selected_console;          }          break;      }        if (!(console->flags & CON_ENABLED))          return;        if (bootconsole) {          printk(KERN_INFO "console handover: boot [%s%d] -> real [%s%d]/n",                 bootconsole->name, bootconsole->index,                 console->name, console->index);          unregister_console(bootconsole);          console->flags &= ~CON_PRINTBUFFER;      }        /*       *   Put this console in the list - keep the       *   preferred driver at the head of the list.       */      acquire_console_sem();      if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {          console->next = console_drivers;          console_drivers = console;          if (console->next)               console->next->flags &= ~CON_CONSDEV;      } else {          console->next = console_drivers->next;          console_drivers->next = console;      }      if (console->flags & CON_PRINTBUFFER) {          /*           * release_console_sem() will print out the buffered messages           * for us.           */          spin_lock_irqsave(&logbuf_lock, flags);          con_start = log_start;          spin_unlock_irqrestore(&logbuf_lock, flags);      }      release_console_sem(); } 当运行到这里时,参数console将指向全局变量bfin_early_serial_console。而console_drivers这一全局变量则为空。 当此函数执行完成后,将输出printk缓冲区中的内容: Linux version 2.6.22.19-ADI-2008R1.5-svn (wmz@localhost.localdomain) (gcc versio n 4.1.2 (ADI svn)) #4 SMP Sat Jan 10 22:24:10 CST 2009 early printk enabled on early_BFuart0 1.2.4.1             串口硬件初始化
在register_console函数中要进行串口硬件的初始化工作,这个工作是由console结构体中的setup回调函数来完成的:      int  (*setup)(struct console *, char *); 在register_console函数中有这样一段代码:      /*       *   See if we want to use this console driver. If we       *   didn't select a console we take the first one       *   that registers here.       */      if (preferred_console < 0) {          if (console->index < 0)               console->index = 0;          if (console->setup == NULL ||              console->setup(console, NULL) == 0) {               console->flags |= CON_ENABLED | CON_CONSDEV;               preferred_console = 0;          }      } 在此调用了setup回调函数。 在bf561的内核中,此回调函数指向bfin_serial_console_setup,它位于drivers/serial/bfin-5xx.c: static int __init bfin_serial_console_setup(struct console *co, char *options) {      struct bfin_serial_port *uart;      int baud = 57600;      int bits = 8;      int parity = 'n';      int flow = 'n';        /*       * Check whether an invalid uart number has been specified, and       * if so, search for the first available port that does have       * console support.       */                                                          if (co->index == -1 || co->index >= nr_ports)          co->index = 0;      uart = &bfin_serial_ports[co->index];        if (options)          uart_parse_options(options, &baud, &parity, &bits, &flow);      else          bfin_serial_console_get_options(uart, &baud, &parity, &bits);        return uart_set_options(&uart->port, co, baud, parity, bits, flow); } 在这里,由于在register_console中传递过来的option为NULL,因此将直接调用bfin_serial_console_get_options,而这个函数用于直接从硬件寄存器中读取当前的串口配置,但是它仅适用于boot-loader已经对串口初始化的情况,对于没用boot-loader的情况,它将什么也不做。 因此,对于不用boot-loader的情况,early console的波特率将只能使用57600这一固定值。如果要使earlyprintk中的波特率这一参数生效,必须修改此处的逻辑。   1.3    通过console输出信息
在内核中,向console输出信息是通过release_console_sem函数来完成的:
/**
 * release_console_sem - unlock the console system
 *
 * Releases the semaphore which the caller holds on the console system
 * and the console driver list.
 *
 * While the semaphore was held, console output may have been buffered
 * by printk(). If this is the case, release_console_sem() emits
 * the output prior to releasing the semaphore.
 *
 * If there is output waiting for klogd, we wake it up.
 *
 * release_console_sem() may be called from any context.
 */
void release_console_sem(void)
{
     unsigned long flags;
     unsigned long _con_start, _log_end;
     unsigned long wake_klogd = 0;
 
     if (console_suspended) {
         up(&secondary_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);
         call_console_drivers(_con_start, _log_end);
         local_irq_restore(flags);
     }
     console_locked = 0;
     up(&console_sem);
     spin_unlock_irqrestore(&logbuf_lock, flags);
     if (wake_klogd)
         wake_up_klogd();
}
在这里,实际输出通过call_console_drivers函数完成:
/*
 * Call the console drivers, asking them to write out
 * log_buf[start] to log_buf[end - 1].
 * The console_sem must be held.
 */
static void call_console_drivers(unsigned long start, unsigned long end)
{
     unsigned long cur_index, start_print;
     static int msg_level = -1;
 
     BUG_ON(((long)(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) {
                       /*
                        * printk() has already given us loglevel tags in
                        * the buffer. This code is here in case the
                        * log buffer has wrapped right round and scribbled
                        * on those tags
                        */
                       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:
/*
 * Write out chars from start to end - 1 inclusive
 */
static void _call_console_drivers(unsigned long start,
                   unsigned long end, int msg_log_level)
{
     if ((msg_log_level < console_loglevel || ignore_loglevel) &&
              console_drivers && start != end) {
         if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {
              /* wrapped write */
              __call_console_drivers(start & LOG_BUF_MASK,
                            log_buf_len);
              __call_console_drivers(0, end & LOG_BUF_MASK);
         } else {
              __call_console_drivers(start, end);
         }
     }
}
继续跟踪__call_console_drivers:
/*
 * Call the console drivers on a range of log_buf
 */
static void __call_console_drivers(unsigned long start, unsigned long end)
{
     struct console *con;
 
     for (con = console_drivers; con; 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结构体中的write函数!记得我们在内核中是使用了bfin_serial_console做为我们的console,而这个结构体中的write回调函数则初始化为bfin_serial_console_write,这个函数在drivers/serial/bfin_5xx.c:
/*
 * Interrupts are disabled on entering
 */
static void
bfin_serial_console_write(struct console *co, const char *s, unsigned int count)
{
     struct bfin_serial_port *uart = &bfin_serial_ports[co->index];
     int flags = 0;
 
     spin_lock_irqsave(&uart->port.lock, flags);
     uart_console_write(&uart->port, s, count, bfin_serial_console_putchar);
     spin_unlock_irqrestore(&uart->port.lock, flags);
}
再跟踪uart_console_write,此函数位于drivers/serial/serial_core.c。
/*
 *   uart_console_write - write a console message to a serial port
 *   @port: the port to write the message
 *   @s: array of characters
 *   @count: number of characters in string to write
 *   @write: function to write character to port
 */
void uart_console_write(struct uart_port *port, const char *s,
              unsigned int count,
              void (*putchar)(struct uart_port *, int))
{
     unsigned int i;
 
     for (i = 0; i < count; i++, s++) {
         if (*s == '/n')
              putchar(port, '/r');
         putchar(port, *s);
     }
}
因为uart是一个通用的抽象接口,它需要指定与具体硬件相关的函数来进行输出,在我们的调用中使用了bfin_serial_console_putchar做为回调函数,因此实际输出是通过bfin_serial_console_putchar来完成的,此函数在drivers/serial/bfin_5xx.c:
static void bfin_serial_console_putchar(struct uart_port *port, int ch)
{
     struct bfin_serial_port *uart = (struct bfin_serial_port *)port;
     while (!(UART_GET_LSR(uart) & THRE))
         barrier();
     UART_PUT_CHAR(uart, ch);
     SSYNC();
}   细看printk.c文件,可以发现几个与printk函数相关的参数:

1.1 log_buf_len

__setup("log_buf_len=", log_buf_len_setup); 即这个内核参数由log_buf_len_setup函数进行处理: static char __log_buf[__LOG_BUF_LEN]; static char *log_buf = __log_buf; static int log_buf_len = __LOG_BUF_LEN; static unsigned long logged_chars; /* Number of chars produced since last read+clear operation */   static int __init log_buf_len_setup(char *str) { unsigned long size = memparse(str, &str); unsigned long flags;   if (size) size = roundup_pow_of_two(size); if (size > log_buf_len) { unsigned long start, dest_idx, offset; char *new_log_buf;   new_log_buf = alloc_bootmem(size); if (!new_log_buf) { printk(KERN_WARNING "log_buf_len: allocation failed/n"); goto out; }   spin_lock_irqsave(&logbuf_lock, flags); log_buf_len = size; log_buf = new_log_buf;   offset = start = min(con_start, log_start); dest_idx = 0; while (start != log_end) { log_buf[dest_idx] = __log_buf[start & (__LOG_BUF_LEN - 1)]; start++; dest_idx++; } log_start -= offset; con_start -= offset; log_end -= offset; spin_unlock_irqrestore(&logbuf_lock, flags);   printk(KERN_NOTICE "log_buf_len: %d/n", log_buf_len); } out: return 1; } 在默认情况下,printk缓冲区的大小由__LOG_BUF_LEN指定 #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) #define CONFIG_LOG_BUF_SHIFT 14 214次方。输入的值必须比这个值大才有效果。而且由于使用了memparse进行数值的分析,因此它可以K, M, G这三个值。如: log_buf_len=64k

1.2 ignore_loglevel

static int __read_mostly ignore_loglevel;   static int __init ignore_loglevel_setup(char *str) { ignore_loglevel = 1; printk(KERN_INFO "debug: ignoring loglevel setting./n");   return 1; }   __setup("ignore_loglevel", ignore_loglevel_setup); 这个内核参数不需要设置值。这个参数仅用在_call_console_drivers函数中 static void _call_console_drivers(unsigned long start, unsigned long end, int msg_log_level) { if ((msg_log_level < console_loglevel || ignore_loglevel) && console_drivers && start != end) { if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) { /* wrapped write */ __call_console_drivers(start & LOG_BUF_MASK, log_buf_len); __call_console_drivers(0, end & LOG_BUF_MASK); } else { __call_console_drivers(start, end); } } } 在使用了这个内核参数后,printk将忽略输出的级别,直接将传递进来的所有信息输出。

1.3 KERN_*

在使用printk输出的时候,可以使用KERN_*宏来指定输出级别。 #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant condition */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ _call_console_drivers函数中可以看到,当指定的输出级别大于等于console_loglevel时,信息将不会输出。console_loglevel的定义为: #define console_loglevel (console_printk[0]) 当使用printk而不指定输出级别时,printk取默认级别default_message_loglevel,其定义为: #define default_message_loglevel (console_printk[1]) 这里涉及的console_printk是一个全局变量: int console_printk[4] = { DEFAULT_CONSOLE_LOGLEVEL, /* console_loglevel */ DEFAULT_MESSAGE_LOGLEVEL, /* default_message_loglevel */ MINIMUM_CONSOLE_LOGLEVEL, /* minimum_console_loglevel */ DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */ }; #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */ #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ 因此,只要不是在输出时指定KERN_DEBUG,其信息都将被printk输出。

1.4 printk_time

这个参数定义为: #if defined(CONFIG_PRINTK_TIME) static int printk_time = 1; #else static int printk_time = 0; #endif module_param(printk_time, int, S_IRUGO | S_IWUSR);   static int __init printk_time_setup(char *str) { if (*str) return 0; printk_time = 1; return 1; }   __setup("time", printk_time_setup); 在指定这个参数之后,printk将在每条信息之前加上时间。 asmlinkage int vprintk(const char *fmt, va_list args) { ……………………………. for (p = printk_buf; *p; p++) { if (log_level_unknown) { /* log_level_unknown signals the start of a new line */ if (printk_time) { int loglev_char; char tbuf[50], *tp; unsigned tlen; unsigned long long t; unsigned long nanosec_rem;   /* * force the log level token to be * before the time output. */ if (p[0] == '<' && p[1] >='0' && p[1] <= '7' && p[2] == '>') { loglev_char = p[1]; p += 3; printed_len -= 3; } else { loglev_char = default_message_loglevel + '0'; } t = printk_clock(); nanosec_rem = do_div(t, 1000000000); tlen = sprintf(tbuf, "<%c>[%5lu.%06lu] ", loglev_char, (unsigned long)t, nanosec_rem/1000);   for (tp = tbuf; tp < tbuf + tlen; tp++) emit_log_char(*tp); printed_len += tlen; } else { if (p[0] != '<' || p[1] < '0' || p[1] > '7' || p[2] != '>') { emit_log_char('<'); emit_log_char(default_message_loglevel + '0'); emit_log_char('>'); printed_len += 3; } } log_level_unknown = 0; if (!*p) break; } emit_log_char(*p);