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

 
我们知道,在uclinux初始化过程中,直到console_init调用之前是没有任何输出的,它们的输出都放在 __log_buf这个缓冲内的,在console_init调用时再将这个缓冲区内的数据一次性输出。
那么console_init又做了什么工作呢?这个函数体在 drivers/char/tty_io.c中:
/*
 * Initialize the console device. This is called *early*, so
 * we can't necessarily depend on lots of kernel help here.
 * Just do some early initializations, and do the complex setup
 * later.
 */
void __init console_init(void)
{
     initcall_t *call;
 
     /* Setup the default TTY line discipline. */
     (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
 
     /*
      * set up the console device so that later boot sequences can
      * inform about problems etc..
      */
     call = __con_initcall_start;
     while (call < __con_initcall_end) {
         (*call)();
         call++;
     }
}
从这个函数大致可以猜出,console和串口的初始化操作应该是由 __con_initcall_start__con_initcall_end之间的函数调用来完成的。那么放到这段空间中的函数是怎么来的呢?又是什么函数可以放在这段空间中呢?
让我们看看vmlinux.lds.s文件中对这两个符号的定义吧:
       .con_initcall.init :
       {
              ___con_initcall_start = .;
              *(.con_initcall.init)
              ___con_initcall_end = .;
       }
原来,这段空间中存放的是.con_initcall.init这个段的内容,也就是说只要在数据代码中声明.con_initcall.init就可以了。
在uclinux的头文件中搜索initcall,发现了这样一个定义( include/linux/init.h):
#define console_initcall(fn) /
     static initcall_t __initcall_##fn /
     __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn
在此函数中的所有操作都是与硬件无关的,据此可以猜测,应该还有一个与硬件相关的文件,且在此文件中应该使用console_initcall这个宏。
在内核源码中搜索console_initcall,发现了一大堆结果:
查找全部"console_initcall", 大小写匹配, 全字匹配, 子文件夹, 查找结果1, "D:/uc2008/linux-2.6.x", "*.c"
 D:/uc2008/linux-2.6.x/drivers/char/amiserial.c(2132)://console_initcall(amiserial_console_init);
 D:/uc2008/linux-2.6.x/drivers/char/decserial.c(66)://console_initcall(decserial_console_init);
 D:/uc2008/linux-2.6.x/drivers/char/hvc_beat.c(135)://console_initcall(hvc_beat_console_init);
 D:/uc2008/linux-2.6.x/drivers/char/hvc_console.c(242)://console_initcall(hvc_console_init);
 D:/uc2008/linux-2.6.x/drivers/char/hvc_iseries.c(594)://console_initcall(hvc_find_vtys);
 D:/uc2008/linux-2.6.x/drivers/char/hvc_rtas.c(134)://console_initcall(hvc_rtas_console_init);
 D:/uc2008/linux-2.6.x/drivers/char/hvc_vio.c(169)://console_initcall(hvc_find_vtys);
 D:/uc2008/linux-2.6.x/drivers/char/hvsi.c(1317)://console_initcall(hvsi_console_init);
 D:/uc2008/linux-2.6.x/drivers/char/serial167.c(2602)://console_initcall(serial167_console_init);
 D:/uc2008/linux-2.6.x/drivers/char/viocons.c(1167)://console_initcall(viocons_init);
 D:/uc2008/linux-2.6.x/drivers/char/vme_scc.c(1044)://console_initcall(vme_scc_console_init);
 D:/uc2008/linux-2.6.x/drivers/char/vt.c(2823)://console_initcall(con_init);
 D:/uc2008/linux-2.6.x/drivers/s390/char/con3215.c(890)://console_initcall(con3215_init);
 D:/uc2008/linux-2.6.x/drivers/s390/char/con3270.c(632)://console_initcall(con3270_init);
 D:/uc2008/linux-2.6.x/drivers/s390/char/sclp_con.c(252)://console_initcall(sclp_console_init);
 D:/uc2008/linux-2.6.x/drivers/s390/char/sclp_vt220.c(774)://console_initcall(sclp_vt220_con_init);
 D:/uc2008/linux-2.6.x/drivers/serial/21285.c(471)://console_initcall(rs285_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/68328serial.c(1577)://console_initcall(m68328_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/8250.c(2539)://console_initcall(serial8250_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/8250_early.c(210)://console_initcall(early_uart_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/atmel_serial.c(863)://console_initcall(atmel_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/bfin_5xx.c(1103)://console_initcall(bfin_serial_rs_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/clps711x.c(534)://console_initcall(clps711xuart_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/dz.c(765)://console_initcall(dz_serial_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/imx.c(1022)://console_initcall(imx_rs_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/m32r_sio.c(1121)://console_initcall(m32r_sio_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/mcfserial.c(1974)://console_initcall(mcfrs_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/mpc52xx_uart.c(800)://console_initcall(mpc52xx_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/netx-serial.c(644)://console_initcall(netx_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/pmac_zilog.c(2024)://console_initcall(pmz_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/pnx8xxx_uart.c(747)://console_initcall(pnx8xxx_rs_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/pxa.c(691)://console_initcall(serial_pxa_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/s3c2410.c(1932)://console_initcall(s3c24xx_serial_initconsole);
 D:/uc2008/linux-2.6.x/drivers/serial/sa1100.c(808)://console_initcall(sa1100_rs_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/serial_ks8695.c(611)://console_initcall(ks8695_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/serial_lh7a40x.c(631)://console_initcall (lh7a40xuart_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/serial_txx9.c(922)://console_initcall(serial_txx9_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/sh-sci.c(1280)://console_initcall(sci_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/sh-sci.c(1361)://console_initcall(kgdb_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/sn_console.c(1086)://console_initcall(sn_sal_serial_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/uartlite.c(400)://console_initcall(ulite_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/v850e_uart.c(234)://console_initcall(v850e_uart_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/vr41xx_siu.c(903)://console_initcall(siu_console_init);
 D:/uc2008/linux-2.6.x/drivers/serial/cpm_uart/cpm_uart_core.c(1242)://console_initcall(cpm_uart_console_init);
  匹配行: 44    匹配文件: 43    合计搜索文件: 6311
果然不出所料,从目录和文件名中可以看出内核为许多不同的设备提供了硬件驱动。因为我用的是BF561,因此使用上述文件中的drivers/serial/bfin_5xx.c文件。
在drivers/serial/bfin_5xx.c中有这样一行:
console_initcall(bfin_serial_rs_console_init);
展开这个宏:
     static initcall_t __initcall_bfin_serial_rs_console_init
     __attribute_used__ __attribute__((__section__(".con_initcall.init")))= bfin_serial_rs_console_init
上式中 initcall_t定义为:
typedef int (*initcall_t)(void);
可以看出它声明了一个函数指针变量,这个变量就放在 .con_initcall.init这个数据段中,且这个指针的值指向 bfin_serial_rs_console_init函数。这样在console_init函数中就可以通过这个函数指针调用 bfin_serial_rs_console_init函数。
bf561串口的初始化就是在 bfin_serial_rs_console_init函数中完成的,看看:
static int __init bfin_serial_rs_console_init(void)
{
     bfin_serial_init_ports();
     register_console(&bfin_serial_console);
     return 0;
}
实际上做了两件事情,首先调用 bfin_serial_init_ports 函数初始化了uart结构体的部分成员,但是它并不初始化硬件,看起来似乎有点奇怪。然后注册一个struct console结构体变量,并将printk缓冲区中的字符输出。
此函数的实现在kernel/printk.c中:
/*
 * 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 (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;
 
     /*
      *   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();
}
 
在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的情况,它将什么也不做。
uart_set_options的代码如下,它位于drivers/serial/serial_core.c,将进行硬件配置:
/**
 *   uart_set_options - setup the serial console parameters
 *   @port: pointer to the serial ports uart_port structure
 *   @co: console pointer
 *   @baud: baud rate
 *   @parity: parity character - 'n' (none), 'o' (odd), 'e' (even)
 *   @bits: number of data bits
 *   @flow: flow control character - 'r' (rts)
 */
int __init
uart_set_options(struct uart_port *port, struct console *co,
          int baud, int parity, int bits, int flow)
{
     struct ktermios termios;
     int i;
 
     /*
      * Ensure that the serial console lock is initialised
      * early.
      */
     spin_lock_init(&port->lock);
     lockdep_set_class(&port->lock, &port_lock_key);
 
     memset(&termios, 0, sizeof(struct ktermios));
 
     termios.c_cflag = CREAD | HUPCL | CLOCAL;
 
     /*
      * Construct a cflag setting.
      */
     for (i = 0; baud_rates[i].rate; i++)
         if (baud_rates[i].rate <= baud)
              break;
 
     termios.c_cflag |= baud_rates[i].cflag;
 
     if (bits == 7)
         termios.c_cflag |= CS7;
     else
         termios.c_cflag |= CS8;
 
     switch (parity) {
     case 'o': case 'O':
         termios.c_cflag |= PARODD;
         /*fall through*/
     case 'e': case 'E':
         termios.c_cflag |= PARENB;
         break;
     }
 
     if (flow == 'r')
         termios.c_cflag |= CRTSCTS;
 
     port->ops->set_termios(port, &termios, NULL);
     co->cflag = termios.c_cflag;
 
     return 0;
}
在这里,使用了一个叫ktermios的结构体,但是实际上只使用了c_cflag这样一个成员,因此我们实际可以认为它就是一个整数。
在此调用了port->ops->set_termios来进行参数配置,在bf561的内核中,此回调函数指向 bfin_serial_set_termios,位于bfin-5xx.c。
此函数位于drivers/serial/bfin-5xx.c中:
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, lsr, 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;
     port->ignore_status_mask = 0;
 
     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);
 
     do {
         lsr = UART_GET_LSR(uart);
     } while (!(lsr & TEMT));
 
     /* Disable UART */
     ier = UART_GET_IER(uart);
     UART_PUT_IER(uart, 0);
 
     /* Set DLAB in LCR to Access DLL and DLH */
     val = UART_GET_LCR(uart);
     val |= DLAB;
     UART_PUT_LCR(uart, val);
     SSYNC();
 
     UART_PUT_DLL(uart, quot & 0xFF);
     SSYNC();
     UART_PUT_DLH(uart, (quot >> 8) & 0xFF);
     SSYNC();
 
     /* Clear DLAB in LCR to Access THR RBR IER */
     val = UART_GET_LCR(uart);
     val &= ~DLAB;
     UART_PUT_LCR(uart, val);
     SSYNC();
 
     UART_PUT_LCR(uart, lcr);
 
     /* Enable UART */
     UART_PUT_IER(uart, ier);
 
     val = UART_GET_GCTL(uart);
     val |= UCEN;
     UART_PUT_GCTL(uart, val);
 
     spin_unlock_irqrestore(&uart->port.lock, flags);
}
在移植这部分代码到VDSP5的时候碰到了一个问题,在上述代码中:
     do {
         lsr = UART_GET_LSR(uart);
     } while (!(lsr & TEMT));
即它要示UART_LSR寄存器中的TEMT这个位为1,但是实际运行到这里的时候,这一位为0,因此进入死循环。
跟踪发现,在head.s中有一段设置UART的代码:
 
       /* Initialise UART*/
       p0.h = hi(UART_LCR);
       p0.l = lo(UART_LCR);
       r0 = 0x80(Z); /* 未修改前为 0x0(Z)*/
       w[p0] = r0.L; /* To enable DLL writes */
       ssync;
 
       p0.h = hi(UART_DLL);
       p0.l = lo(UART_DLL);
       r0 = 0x0(Z);
       w[p0] = r0.L;
       ssync;
 
       p0.h = hi(UART_DLH);
       p0.l = lo(UART_DLH);
       r0 = 0x00(Z);
       w[p0] = r0.L;
       ssync;
 
       p0.h = hi(UART_GCTL);
       p0.l = lo(UART_GCTL);
       r0 = 0x0(Z);
       w[p0] = r0.L; /* To enable UART clock */
       ssync;
我们知道在设置UART_DLL和UART_DLH时必须将UART_LCR的最高位设置为1,但是原始代码中却未设置,这样就造成了设置UART_DLL的失败,此时UART_LCR的TEMT标志将被设置为0,从而导致了死循环问题的发生。将上述红色标志的代码修改后解决此问题。
1.2.6   通过boot-loader向内核传递串口参数
我们知道,可以在u-boot中向内核传递串口参数,如
console=ttyBF0,57600
在内核初始化的时候,会将这个字符串分解出来放在
static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
中,在register_console函数中要对此数组进行分析,并根据其中的参数进行串口的重新配置:
 
     /*
      *   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;
     }
由于在移植时没有使用boot-loader,因此本部分不做进一步分析,相当于本部分代码什么事也不做。
在内核中,将所有的console放在一个单链表中,当内核调用register_console时,它将新的console指针链接到链表中:
     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;
     }
通常在内核中只有一个console,因而直接将console指针赋给console_drivers这个链表头,此链表将只有一个节点。
在register_console函数的末尾,有这样一个调用:
     release_console_sem();
正是在此函数中,内核将缓冲区中的已有数据输出到console。后面有详细分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

嵌云阁主

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

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

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

打赏作者

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

抵扣说明:

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

余额充值