快乐虾
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
即2的14次方。输入的值必须比这个值大才有效果。而且由于使用了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);