uclinux内核的console(2):early console

本文详细介绍了在 BF561 DSP 上使用 earlyconsole 的过程,包括配置早期打印功能、全局变量设置、硬件初始化步骤等内容。适用于 uclinux-2008r1.5-rc3 系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

rev 0.2

 

快乐虾

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)

 

欢迎转载,但请保留作者信息

 

在内核启动初期,为了尽可能早地输出一些调试信息,可以在配置时选择使用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

当内核调用此函数时,仅仅做了一些最基本的硬件初始化工作,如CCLKEBIU等。下面看看它的实现:

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_portcflag两个数值,然后调用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中传递过来的optionNULL,因此将直接调用bfin_serial_console_get_options,而这个函数用于直接从硬件寄存器中读取当前的串口配置,但是它仅适用于boot-loader已经对串口初始化的情况,对于没用boot-loader的情况,它将什么也不做。

因此,对于不用boot-loader的情况,early console的波特率将只能使用57600这一固定值。如果要使earlyprintk中的波特率这一参数生效,必须修改此处的逻辑。

 

1       参考资料

uclinux-2008r1-rc8(bf561)内核的console(1):数据结构2008/5/29

uclinux-2008r1-rc8(bf561)内核的console(2):驱动初始化(2008/5/29)

uclinux-2008r1-rc8(bf561)内核的console(3):通过console输出信息(2008/5/29)

uclinux内核的console(1):数据结构(2009-1-31)

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌云阁主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值