console驱动:
一、基本概念
终端是一种字符型设备,通常使用tty简称各种类型的终端。linux的终端类型:
/dev/ttySn,串行口终端
/dev/pty,伪终端
/dev/tty,当前进程的控制终端,可以是介绍的其它任何一种终端
/dev/ttyn,tty1~tty6是虚拟终端,tty0当前虚拟终端的别名。
/dev/console,控制台终端(显示器)
二、uboot传参数的处理
linux启动时uboot传递进console=ttyS2,115200n8的参数
内核中用__setup()宏声明参数处理的方法:__setup(
"console="
, console_setup);
1.console_cmdline结构体
struct
console_cmdline
{
char
name[8];
//驱动名
int
index;
//次设备号
char
*options;
//选项
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
char
*brl_options;
#endif
};
2.内核调用console_setup()函数处理uboot传进的console参数
static
int
__init console_setup(
char
*str)
{
char
buf[
sizeof
(console_cmdline[0].name) + 4];
//分配驱动名+index的缓冲区,分配12个字节
char
*s, *options, *brl_options = NULL;
int
idx;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
if
(!
memcmp
(str,
"brl,"
, 4)) {
brl_options =
""
;
str += 4;
}
else
if
(!
memcmp
(str,
"brl="
, 4)) {
brl_options = str + 4;
str =
strchr
(brl_options,
','
);
if
(!str) {
printk(KERN_ERR
"need port name after brl=\n"
);
return
1;
}
*(str++) = 0;
}
#endif
if
(str[0] >=
'0'
&& str[0] <=
'9'
) {
//第一个参数属于[0,9]
strcpy
(buf,
"ttyS"
);
//则将其驱动名设为ttyS
strncpy
(buf + 4, str,
sizeof
(buf) - 5);
//将次设备号放其后面
}
else
{
strncpy
(buf, str,
sizeof
(buf) - 1);
//否则直接将驱动名+设备号拷贝到buf中
}
buf[
sizeof
(buf) - 1] = 0;
if
((options =
strchr
(str,
','
)) != NULL)
//获取options,即“115200n8”
*(options++) = 0;
#ifdef __sparc__
if
(!
strcmp
(str,
"ttya"
))
strcpy
(buf,
"ttyS0"
);
if
(!
strcmp
(str,
"ttyb"
))
strcpy
(buf,
"ttyS1"
);
#endif
for
(s = buf; *s; s++)
if
((*s >=
'0'
&& *s <=
'9'
) || *s ==
','
)
//移动指针s到次设备号处
break
;
idx = simple_strtoul(s, NULL, 10);
//获取次设备号,字符串转换成unsigend long long型数据,s表示字符串的开始,NULL表示字符串的结束,10表示进制
//这里返回的是次设备号=2
*s = 0;
__add_preferred_console(buf, idx, options, brl_options);
console_set_on_cmdline = 1;
return
1;
}
3.__add_preferred_console()函数
//整体的作用是根据uboot传递的参数设置全局console_cmdline数组
//该数组及全局selected_console,在register_console中会使用到
static
int
__add_preferred_console(
char
*name,
int
idx,
char
*options,
char
*brl_options)
{
struct
console_cmdline *c;
int
i;
for
(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
//可以最多8个console
if
(
strcmp
(console_cmdline[i].name, name) == 0 && console_cmdline[i].index == idx) {
//比较已注册的console_cmdline数组中的项的名字及次设备号,若console_cmdline已经存在
if
(!brl_options)
selected_console = i;
//设置全局selected_console索引号
return
0;
//则返回
}
if
(i == MAX_CMDLINECONSOLES)
//判断console_cmdline数组是否满了
return
-E2BIG;
if
(!brl_options)
selected_console = i;
//设置全局selected_console索引号
c = &console_cmdline[i];
//获取全局console_cmdline数组的第i项地址
strlcpy(c->name, name,
sizeof
(c->name));
//填充全局console_cmdline的驱动名“ttyS2”
c->options = options;
//填充配置选项115200n8
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
c->brl_options = brl_options;
#endif
c->index = idx;
//填充索引号2,即次设备号
return
0;
}
三、在console初始化之前能使用printk,使用内核提供的early printk支持。
//在调用console_init之前调用printk也能打印出信息,这是為什麼呢?在start_kernel函数中很早就调用了 parse_early_param函数,
//该函数会调用到链接脚本中.init.setup段的函数。其中就有 setup_early_serial8250_console函数。
//该函数通过 register_console(&early_serial8250_console);
//注册了一个比较简单的串口设备。可以用来打印内核启 动早期的信息。
//对于early printk的console注册往往通过内核的early_param完成。
early_param(“earlycon”,setup_early_serial8250_console);
//定义一个earlycon的内核参数,内核解析这个参数时调用setup_early_serial8250_console()函数
1.setup_early_serial8250_console()函数
//earlycon = uart8250,mmio,0xff5e0000,115200n8
int
__init setup_early_serial8250_console(
char
*cmdline)
{
char
*options;
int
err;
options =
strstr
(cmdline,
"uart8250,"
);
//找到“uart8250,”字符串,返回此字符串的起始位置
if
(!options) {
options =
strstr
(cmdline,
"uart,"
);
if
(!options)
return
0;
}
options =
strchr
(cmdline,
','
) + 1;
//options指针指向第一个逗号后边的字符串地址
err = early_serial8250_setup(options);
//进行配置
if
(err < 0)
return
err;
/*
static struct console early_serial8250_console __initdata = {
.name = "uart",
.write = early_serial8250_write,
.flags = CON_PRINTBUFFER | CON_BOOT,//所用具有CON_BOOT属性的console都会在内核初始化到late initcall阶段被注销,相互消他们的函数是
.index = -1,
};
*/
//注册一个早期的console,到真正的console_init时,此console会被注销,因为设置了CON_BOOT标志
register_console(&early_serial8250_console);
return
0;
}
static
int
__init early_serial8250_setup(
char
*options)
{
struct
early_serial8250_device *device = &early_device;
int
err;
if
(device->port.membase || device->port.iobase)
//early_device设备的端口地址若配置过则返回
return
0;
err = parse_options(device, options);
//解析参数并配置early_device设备对应的uart_port结构
if
(err < 0)
return
err;
init_port(device);
//early_device设备对应的初始化uart_port结构
return
0;
}
static
int
__init parse_options(
struct
early_serial8250_device *device,
char
*options)
{
struct
uart_port *port = &device->port;
//找到early_device设备对应的uart_port结构
int
mmio, mmio32, length;
if
(!options)
return
-ENODEV;
port->uartclk = BASE_BAUD * 16;
//串口时钟
mmio = !
strncmp
(options,
"mmio,"
, 5);
//查找"mmio,"字符串,找到mmio=1
mmio32 = !
strncmp
(options,
"mmio32,"
, 7);
//mmio32=0
if
(mmio || mmio32) {
port->iotype = (mmio ? UPIO_MEM : UPIO_MEM32);
//串口类型设为UPIO_MEM=2
port->mapbase = simple_strtoul(options + (mmio ? 5 : 7),&options, 0);
//获得串口的配置寄存器基础地址(物理地址),这里是得到0xff5e0000
if
(mmio32)
port->regshift = 2;
#ifdef CONFIG_FIX_EARLYCON_MEM
set_fixmap_nocache(FIX_EARLYCON_MEM_BASE,port->mapbase & PAGE_MASK);
port->membase =(
void
__iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
port->membase += port->mapbase & ~PAGE_MASK;
#else
port->membase = ioremap_nocache(port->mapbase, 64);
//映射到内存的配置寄存器基础地址
if
(!port->membase) {
printk(KERN_ERR
"%s: Couldn't ioremap 0x%llx\n"
, __func__,(unsigned
long
long
) port->mapbase);
return
-ENOMEM;
}
#endif
}
else
if
(!
strncmp
(options,
"io,"
, 3)) {
port->iotype = UPIO_PORT;
port->iobase = simple_strtoul(options + 3, &options, 0);
mmio = 0;
}
else
return
-EINVAL;
options =
strchr
(options,
','
);
//指针移到“115200n8”字符串处
if
(options) {
//存在
options++;
device->baud = simple_strtoul(options, NULL, 0);
//取得波特率115200
length = min(
strcspn
(options,
" "
),
sizeof
(device->options));
strncpy
(device->options, options, length);
//将字符串115200n8拷贝到设备的device->options字段中
}
else
{
device->baud = probe_baud(port);
snprintf(device->options,
sizeof
(device->options),
"%u"
,device->baud);
}
if
(mmio || mmio32)
printk(KERN_INFO
"Early serial console at MMIO%s 0x%llx (options '%s')\n"
,mmio32 ?
"32"
:
""
,(unsigned
long
long
)port->mapbase,device->options);
else
printk(KERN_INFO
"Early serial console at I/O port 0x%lx (options '%s')\n"
,port->iobase,device->options);
return
0;
}
static
void
__init init_port(
struct
early_serial8250_device *device)
{
struct
uart_port *port = &device->port;
unsigned
int
divisor;
unsigned
char
c;
serial_out(port, UART_LCR, 0x3);
/* 8n1 */
serial_out(port, UART_IER, 0);
/* no interrupt */
serial_out(port, UART_FCR, 0);
/* no fifo */
serial_out(port, UART_MCR, 0x3);
/* DTR + RTS */
divisor = port->uartclk / (16 * device->baud);
//根据波特率设置分频
c = serial_in(port, UART_LCR);
serial_out(port, UART_LCR, c | UART_LCR_DLAB);
serial_out(port, UART_DLL, divisor & 0xff);
serial_out(port, UART_DLM, (divisor >> 8) & 0xff);
serial_out(port, UART_LCR, c & ~UART_LCR_DLAB);
}
void
register_console(
struct
console *newcon)
{
int
i;
unsigned
long
flags;
struct
console *bcon = NULL;
/*
现在是注册一个early console,即
static struct console early_serial8250_console __initdata = {
.name = "uart",
.write = early_serial8250_write,
.flags = CON_PRINTBUFFER | CON_BOOT,//所用具有CON_BOOT属性的console都会在内核初始化到late initcall阶段被注销,相互消他们的函数是
.index = -1,
};
*/
if
(console_drivers && newcon->flags & CON_BOOT) {
//注册的是否是引导控制台。early console的CON_BOOT置位,表示只是一个引导控制台,以后会被注销
for_each_console(bcon) {
////遍历全局console_drivers数组
if
(!(bcon->flags & CON_BOOT)) {
//判断是否已经有引导控制台了,有了的话就直接退出
printk(KERN_INFO
"Too late to register bootconsole %s%d\n"
,newcon->name, newcon->index);
return
;
}
}
}
if
(console_drivers && console_drivers->flags & CON_BOOT)
//如果注册的是引导控制台
bcon = console_drivers;
//让bcon指向全局console_drivers
if
(preferred_console < 0 || bcon || !console_drivers)
preferred_console = selected_console;
//设置preferred_console为uboot命令选择的selected_console(即索引)
if
(newcon->early_setup)
//early console没有初始化early_setup字段,以下这个函数不执行
newcon->early_setup();
//调用serial8250_console_early_setup()
if
(preferred_console < 0) {
if
(newcon->index < 0)
newcon->index = 0;
if
(newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {
newcon->flags |= CON_ENABLED;
if
(newcon->device) {
newcon->flags |= CON_CONSDEV;
preferred_console = 0;
}
}
}
//传给内核参数:
//Kernel command line: console=ttyS2,115200n8 rw root=/dev/ram0 initrd=0xc2000000,20M mem=128M ip=192.168.1.220::192.168.1.1:255.255.255.0::eth0:off
//所以这里将根据传参console=ttyS2,115200来配置作为console的ttyS2串口
for
(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) {
//遍历全局console_cmdline找到匹配的
if
(
strcmp
(console_cmdline[i].name, newcon->name) != 0)
//比较终端名称“ttyS”
continue
;
if
(newcon->index >= 0 &&newcon->index != console_cmdline[i].index)
//console_cmdline[i].index=2。//比较次设备号
continue
;
if
(newcon->index < 0)
newcon->index = console_cmdline[i].index;
//将终端号赋值给serial8250_console->index
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE//没有定义,下边不执行
if
(console_cmdline[i].brl_options) {
newcon->flags |= CON_BRL;
braille_register_console(newcon,console_cmdline[i].index,console_cmdline[i].options,console_cmdline[i].brl_options);
return
;
}
#endif
//console_cmdline[i].options = "115200n8",对于early console而言setup字段未被初始化,故下边的函数不执行
if
(newcon->setup &&newcon->setup(newcon, console_cmdline[i].options) != 0)
//调用serial8250_console_setup()对终端进行配置
break
;
newcon->flags |= CON_ENABLED;
//设置标志为CON_ENABLE(这个在printk调用中使用到)
newcon->index = console_cmdline[i].index;
//设置索引号
if
(i == selected_console) {
//索引号和uboot指定的console的一样
newcon->flags |= CON_CONSDEV;
//设置标志CON_CONSDEV(全局console_drivers链表中靠前)
preferred_console = selected_console;
}
break
;
}
//for循环作用大致是查看注册的console是否是uboot知道的引导console,是则设置相关标志和preferred_console
if
(!(newcon->flags & CON_ENABLED))
return
;
if
(bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
//防止重复打印
newcon->flags &= ~CON_PRINTBUFFER;
acquire_console_sem();
if
((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
//如果是preferred控制台
newcon->next = console_drivers;
console_drivers = newcon;
//添加进全局console_drivers链表前面位置(printk中会遍历该表调用合适的console的write方法打印信息)
if
(newcon->next)
newcon->next->flags &= ~CON_CONSDEV;
}
else
{
//如果不是preferred控制台
newcon->next = console_drivers->next;
console_drivers->next = newcon;
//添加进全局console_drivers链表后面位置
}
//主册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,
//console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来
if
(newcon->flags & CON_PRINTBUFFER) {
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_start;
spin_unlock_irqrestore(&logbuf_lock, flags);
}
release_console_sem();
if
(bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {
printk(KERN_INFO
"console [%s%d] enabled, bootconsole disabled\n"
,newcon->name, newcon->index);
for_each_console(bcon)
if
(bcon->flags & CON_BOOT)
unregister_console(bcon);
}
else
{
//调用这里
printk(KERN_INFO
"%sconsole [%s%d] enabled\n"
,(newcon->flags & CON_BOOT) ?
"boot"
:
""
,newcon->name, newcon->index);
}
}
四、在未对console进行初始化之前,内核使用early console进行打印。之后内核进行真正的console初始化
//console_init()在start_kernel()中调用,用来对控制台初始化,这个函数执行完成后,串口可以看到内核用printk()函数打印的信息
void
__init console_init(
void
)
{
initcall_t *call;
/* Setup the default TTY line discipline. */
//此函数调用tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY)
//#define N_TTY 0
/*struct tty_ldisc_ops tty_ldisc_N_TTY = {
.magic = TTY_LDISC_MAGIC,
.name = "n_tty",
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
.read = n_tty_read,
.write = n_tty_write,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = n_tty_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup
};
内核定义一个tty_ldiscs数组,然后根据数组下标来存放对应的线路规程的操作集,而这里的数组下标表示的就是具体的协议,在头文件中已经通过宏定义好了。例如N_TTY 0。
所以可以发现:ldisc[0] 存放的是N_TTY对应的线路规程操作集
ldisc[1]存放的是N_SLIP对应的线路规程操作集
ldisc[2]存放的就是N_MOUSE对应的线路规程操作集
依次类推。此处就是ldisc[N_TTY] = tty_ldisc_N_TTY。
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
{
unsigned long flags;
int ret = 0;
if (disc < N_TTY || disc >= NR_LDISCS)
return -EINVAL;
spin_lock_irqsave(&tty_ldisc_lock, flags);
tty_ldiscs[disc] = new_ldisc;//tty_ldiscs[0]存放的是N_TTY对应的线路规程操作集
new_ldisc->num = disc;//0
new_ldisc->refcount = 0;
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
return ret;
}
*/
tty_ldisc_begin();
//这段代码前面是注册了第0个(逻辑上1)线路规程
//依次调用从__con_initcall_start到__con_initcall_end之间的函数指针
//会调用两个函数就是con_init()和serial8250_console_init()
call = __con_initcall_start;
while
(call < __con_initcall_end) {
(*call)();
call++;
}
}
static
int
__init serial8250_console_init(
void
)
{
if
(nr_uarts > UART_NR)
//串口数量不能大于3个
nr_uarts = UART_NR;
serial8250_isa_init_ports();
//对三个串口的uart_8250_port结构静态常量serial8250_ports结构进行初始化,主要是将up->port.ops = &serial8250_pops
/*
static struct console serial8250_console = {
.name = "ttyS",
.write = serial8250_console_write,//写方法
.device = uart_console_device,//tty驱动
.setup = serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。
.early_setup = serial8250_console_early_setup,
.flags = CON_PRINTBUFFER | CON_ANYTIME,
.index = -1,
.data = &serial8250_reg,
};
*/
register_console(&serial8250_console);
//在这里注册serial8250_console真正的console终端
return
0;
}
console_initcall(serial8250_console_init);
/*
serial8250_console_init()函数会比serial8250_probe()先调用,所以调用register_console的时候,port还没有初始化,所以当
register_console调用serial8250_console_setup()设置buad,parity bits的时候,
serial8250_console_setup()会检测port->iobase和port->membase是否是有效值,如果不是就返回,
放弃初始化console,所以实际上,console不是在serial8250_console_init()里边初始化,
如果要在serial8250_console_init初始化,需要将port静态初始化.
当serial8250_probe()调用uart_add_one_port->uart_configure_port:
if (port->cons && !(port->cons->flags & CON_ENABLED)){
printk("%s retister console\n", __FUNCTION__);
register_console(port->cons);
}
该函数会检查console有没有初始化,如果没有初始化,则调用register_console来初始化.
所以console放在这里初始化也是比较好一些,可以将console_initcall(serial8250_console_init) comment.
*/
//对三个串口的uart_8250_port结构静态常量serial8250_ports结构进行初始化,主要是将up->port.ops = &serial8250_pops
static
void
__init serial8250_isa_init_ports(
void
)
{
struct
uart_8250_port *up;
static
int
first = 1;
int
i, irqflag = 0;
if
(!first)
//静态变量,serial8250_console_init()第一次进入这个函数,之后serial8250_init()再进入这个函数就会直接返回
return
;
first = 0;
//对三个串口的uart_8250_port结构serial8250_ports结构体进行初始化
for
(i = 0; i < nr_uarts; i++) {
struct
uart_8250_port *up = &serial8250_ports[i];
up->port.line = i;
//0代表串口0,1代表串口1
spin_lock_init(&up->port.lock);
init_timer(&up->timer);
//初始化定时器
up->timer.function = serial8250_timeout;
//初始化定时器的超时函数
//ALPHA_KLUDGE_MCR needs to be killed.
up->mcr_mask = ~ALPHA_KLUDGE_MCR;
up->mcr_force = ALPHA_KLUDGE_MCR;
//初始化uart_8250_port指向的uart_port字段port的操作
up->port.ops = &serial8250_pops;
/*
static struct uart_ops serial8250_pops = {
.tx_empty = serial8250_tx_empty,
.set_mctrl = serial8250_set_mctrl,
.get_mctrl = serial8250_get_mctrl,
.stop_tx = serial8250_stop_tx,
.start_tx = serial8250_start_tx,
.stop_rx = serial8250_stop_rx,
.enable_ms = serial8250_enable_ms,
.break_ctl = serial8250_break_ctl,
.startup = serial8250_startup,
.shutdown = serial8250_shutdown,
.set_termios = serial8250_set_termios,
.set_ldisc = serial8250_set_ldisc,
.pm = serial8250_pm,
.type = serial8250_type,
.release_port = serial8250_release_port,
.request_port = serial8250_request_port,
.config_port = serial8250_config_port,
.verify_port = serial8250_verify_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = serial8250_get_poll_char,
.poll_put_char = serial8250_put_poll_char,
#endif
};
*/
}
if
(share_irqs)
//中断是否共享(这里设置成不共享)
irqflag = IRQF_SHARED;
//条件不满足,不会进来初始化
for
(i = 0, up = serial8250_ports;i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;i++, up++) {
/* up->port.iobase = old_serial_port[i].port;
up->port.irq = irq_canonicalize(old_serial_port[i].irq);
up->port.irqflags = old_serial_port[i].irqflags;
up->port.uartclk = old_serial_port[i].baud_base * 16;
up->port.flags = old_serial_port[i].flags;
up->port.hub6 = old_serial_port[i].hub6;
up->port.membase = old_serial_port[i].iomem_base;
up->port.iotype = old_serial_port[i].io_type;
up->port.regshift = old_serial_port[i].iomem_reg_shift;
set_io_from_upio(&up->port);
up->port.irqflags |= irqflag;
if (serial8250_isa_config != NULL)
serial8250_isa_config(i, &up->port, &up->capabilities);
*/
}
}
//下边再次调用register_console()注册serial8250_console真正的console终端
void
register_console(
struct
console *newcon)
{
int
i;
unsigned
long
flags;
struct
console *bcon = NULL;
/*
现在是注册一个serial8250_console,即
static struct console serial8250_console = {
.name = "ttyS",
.write = serial8250_console_write,//写方法
.device = uart_console_device,//tty驱动
.setup = serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。
.early_setup = serial8250_console_early_setup,
.flags = CON_PRINTBUFFER | CON_ANYTIME,
.index = -1,
.data = &serial8250_reg,
};
*/
if
(console_drivers && newcon->flags & CON_BOOT) {
//注册的是serial8250_console,CON_BOOT没有置位,不是引导控制台。下边不会进去遍历
for_each_console(bcon) {
////遍历全局console_drivers数组
if
(!(bcon->flags & CON_BOOT)) {
//判断是否已经有引导控制台了,有了的话就直接退出
printk(KERN_INFO
"Too late to register bootconsole %s%d\n"
,newcon->name, newcon->index);
return
;
}
}
}
if
(console_drivers && console_drivers->flags & CON_BOOT)
//如果注册的是引导控制台,serial8250_console不是引导控制台
bcon = console_drivers;
//这里不执行
if
(preferred_console < 0 || bcon || !console_drivers)
preferred_console = selected_console;
//设置preferred_console为uboot命令选择的selected_console(即在Uboot传入的参数“console=ttyS2,115200n8”在console_cmdline[]数组中的索引)
//这里preferred_console =0
if
(newcon->early_setup)
//serial8250_console初始化early_setup字段
newcon->early_setup();
//调用serial8250_console_early_setup()
if
(preferred_console < 0) {
//由于preferred_console =0,不会进入下边
if
(newcon->index < 0)
newcon->index = 0;
if
(newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {
newcon->flags |= CON_ENABLED;
if
(newcon->device) {
newcon->flags |= CON_CONSDEV;
preferred_console = 0;
}
}
}
//传给内核参数:
//Kernel command line: console=ttyS2,115200n8 rw root=/dev/ram0 initrd=0xc2000000,20M mem=128M ip=192.168.1.220::192.168.1.1:255.255.255.0::eth0:off
//所以这里将根据传参console=ttyS2,115200来配置作为console的ttyS2串口
for
(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) {
//遍历全局console_cmdline找到匹配的,i=0就是匹配的“ttyS2”
if
(
strcmp
(console_cmdline[i].name, newcon->name) != 0)
//比较终端名称“ttyS”
continue
;
if
(newcon->index >= 0 &&newcon->index != console_cmdline[i].index)
//console_cmdline[i].index=2。//比较次设备号
continue
;
if
(newcon->index < 0)
newcon->index = console_cmdline[i].index;
//将终端号赋值给serial8250_console->index,这里是2
//console_cmdline[i].options = "115200n8",对于serial8250_console而言setup字段已初始化
if
(newcon->setup && newcon->setup(newcon, console_cmdline[i].options) != 0)
//调用serial8250_console_setup()对终端进行配置,调用不成功
break
;
//在这里注册serial8250_console时,调用serial8250_console_setup()由于port->iobase和port->membase不是有效值,
//故返回错误,这样下边的操作不会执行,直接break跳出,从flag1出跳出函数。即在这里serial8250_console没有注册成功
//由于内核在下边的操作队串口进行初始化时,还会调用register_console()来注册serial8250_console,在那时注册就会成功
newcon->flags |= CON_ENABLED;
//设置标志为CON_ENABLE,表示console使能(这个在printk调用中使用到)
newcon->index = console_cmdline[i].index;
//设置索引号
if
(i == selected_console) {
//索引号和uboot指定的console的一样
newcon->flags |= CON_CONSDEV;
//设置标志CON_CONSDEV(全局console_drivers链表中靠前)
preferred_console = selected_console;
}
break
;
}
//for循环作用大致是查看注册的console是否是uboot知道的引导console,是则设置相关标志和preferred_console
//flag1:
if
(!(newcon->flags & CON_ENABLED))
//若前边没有设置CON_ENABLED标志,就退出
return
;
if
(bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
//防止重复打印
newcon->flags &= ~CON_PRINTBUFFER;
acquire_console_sem();
if
((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
//如果是preferred控制台
newcon->next = console_drivers;
console_drivers = newcon;
//添加进全局console_drivers链表前面位置(printk中会遍历该表调用合适的console的write方法打印信息)
if
(newcon->next)
newcon->next->flags &= ~CON_CONSDEV;
}
else
{
//如果不是preferred控制台
newcon->next = console_drivers->next;
console_drivers->next = newcon;
//添加进全局console_drivers链表后面位置
}
//主册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,
//console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来
if
(newcon->flags & CON_PRINTBUFFER) {
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_start;
spin_unlock_irqrestore(&logbuf_lock, flags);
}
release_console_sem();
if
(bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {
printk(KERN_INFO
"console [%s%d] enabled, bootconsole disabled\n"
,newcon->name, newcon->index);
for_each_console(bcon)
if
(bcon->flags & CON_BOOT)
unregister_console(bcon);
}
else
{
//调用这里
printk(KERN_INFO
"%sconsole [%s%d] enabled\n"
,(newcon->flags & CON_BOOT) ?
"boot"
:
""
,newcon->name, newcon->index);
}
}
//serial8250_console_early_setup()-->serial8250_find_port_for_earlycon()
int
serial8250_find_port_for_earlycon(
void
)
{
struct
early_serial8250_device *device = &early_device;
//early console初始化时对early_device结构的初始化
struct
uart_port *port = &device->port;
int
line;
int
ret;
if
(!device->port.membase && !device->port.iobase)
//early_device结构初始化时已经配置好
return
-ENODEV;
//early console注册时不会调用此函数。
//当真正的console初始化时,会调用此函数。
//真正的console初始化时,会查找early console注册时用的是哪一个串口号,从serial8250_ports[]中根据uart_port->mapbase地址来比对
line = serial8250_find_port(port);
//根据uart_port结构找到串口号,比对没有找到串口号,line返回负值
if
(line < 0)
return
-ENODEV;
//从这里返回,下边的不再执行
//若找到early console用的串口号,更新当初传入内核参数使用的console_cmdline[i],名称改成ttyS。。。。
ret = update_console_cmdline(
"uart"
, 8250,
"ttyS"
, line, device->options);
if
(ret < 0)
ret = update_console_cmdline(
"uart"
, 0,
"ttyS"
, line, device->options);
return
ret;
}
static
int
__init serial8250_console_setup(
struct
console *co,
char
*options)
{
struct
uart_port *port;
int
baud = 9600;
int
bits = 8;
int
parity =
'n'
;
int
flow =
'n'
;
if
(co->index >= nr_uarts)
//console的索引,这里是2,即ttyS2
co->index = 0;
port = &serial8250_ports[co->index].port;
//找到对应的ttyS2的uart_port结构
//由于console_init在注册serial8250_console时调用的register_console()函数调用serial8250_console_setup()
//进入这个函数时,由于ttyS2的uart_port结构没有初始化,port->iobase 和port->membase值都未设置,所以直接从下边返回
//当进行串口初始化时,还会回来注册serial8250_console,再调用到这里,由于设置了ttyS2的uart_port结构,所以下边的配置就会成功
if
(!port->iobase && !port->membase)
//第一次注册时,由于未设置,从这里直接返回
return
-ENODEV;
if
(options)
//如果options不为空,就将options里的数值写给baud, &parity, &bits, &flow
uart_parse_options(options, &baud, &parity, &bits, &flow);
//没有配置options,则使用缺省值,否则使用传下来的的参数options里的串口配置
return
uart_set_options(port, co, baud, parity, bits, flow);
}
五、通过四知道,在对console注册时,没有成功,由于串口还没有配置。当对串口配置时再对console注册就能成功。
serial8250_console就能注册到内核全局变量console_drivers中。这样终端打印时就通过注册的serial8250_console就能将信息打印到终端上。
//内核的打印函数
asmlinkage
int
printk(
const
char
*fmt, ...)
{
va_list
args;
//可变参数链表
int
r;
#ifdef CONFIG_KGDB_KDB
if
(unlikely(kdb_trap_printk)) {
va_start
(args, fmt);
r = vkdb_printf(fmt, args);
va_end
(args);
return
r;
}
#endif
va_start
(args, fmt);
//获取第一个可变参数
r = vprintk(fmt, args);
//调用vprintk函数
va_end
(args);
//释放可变参数链表指针
return
r;
}
//vprintk函数
asmlinkage
int
vprintk(
const
char
*fmt,
va_list
args)
{
int
printed_len = 0;
int
current_log_level = default_message_loglevel;
unsigned
long
flags;
int
this_cpu;
char
*p;
boot_delay_msec();
printk_delay();
preempt_disable();
raw_local_irq_save(flags);
this_cpu = smp_processor_id();
if
(unlikely(printk_cpu == this_cpu)) {
if
(!oops_in_progress) {
recursion_bug = 1;
goto
out_restore_irqs;
}
zap_locks();
}
lockdep_off();
spin_lock(&logbuf_lock);
printk_cpu = this_cpu;
if
(recursion_bug) {
recursion_bug = 0;
strcpy
(printk_buf, recursion_bug_msg);
printed_len =
strlen
(recursion_bug_msg);
}
printed_len += vscnprintf(printk_buf + printed_len,
sizeof
(printk_buf) - printed_len, fmt, args);
p = printk_buf;
if
(p[0] ==
'<'
) {
//处理打印级别字段
unsigned
char
c = p[1];
if
(c && p[2] ==
'>'
) {
switch
(c) {
case
'0'
...
'7'
:
/* loglevel */
current_log_level = c -
'0'
;
case
'd'
:
/* KERN_DEFAULT */
if
(!new_text_line) {
emit_log_char(
'\n'
);
new_text_line = 1;
}
case
'c'
:
/* KERN_CONT */
p += 3;
break
;
}
}
}
for
( ; *p; p++) {
if
(new_text_line) {
/* Always output the token */
emit_log_char(
'<'
);
emit_log_char(current_log_level +
'0'
);
emit_log_char(
'>'
);
printed_len += 3;
new_text_line = 0;
if
(printk_time) {
//打印时间信息
/* Follow the token with the time */
char
tbuf[50], *tp;
unsigned tlen;
unsigned
long
long
t;
unsigned
long
nanosec_rem;
t = cpu_clock(printk_cpu);
nanosec_rem = do_div(t, 1000000000);
tlen =
sprintf
(tbuf,
"[%5lu.%06lu] "
,(unsigned
long
) t,nanosec_rem / 1000);
for
(tp = tbuf; tp < tbuf + tlen; tp++)
emit_log_char(*tp);
printed_len += tlen;
}
if
(!*p)
break
;
}
emit_log_char(*p);
if
(*p ==
'\n'
)
new_text_line = 1;
}
if
(acquire_console_semaphore_for_printk(this_cpu))
release_console_sem();
lockdep_on();
out_restore_irqs:
raw_local_irq_restore(flags);
preempt_enable();
return
printed_len;
}
//接着调用release_console_sem函数
void
release_console_sem(
void
)
{
unsigned
long
flags;
unsigned _con_start, _log_end;
unsigned wake_klogd = 0;
if
(console_suspended) {
up(&console_sem);
return
;
}
console_may_schedule = 0;
for
( ; ; ) {
spin_lock_irqsave(&logbuf_lock, flags);
wake_klogd |= log_start - log_end;
if
(con_start == log_end)
break
;
/* Nothing to print */
_con_start = con_start;
_log_end = log_end;
con_start = log_end;
/* Flush */
spin_unlock(&logbuf_lock);
stop_critical_timings();
/* don't trace print latency */
call_console_drivers(_con_start, _log_end);
start_critical_timings();
local_irq_restore(flags);
}
console_locked = 0;
up(&console_sem);
spin_unlock_irqrestore(&logbuf_lock, flags);
if
(wake_klogd)
wake_up_klogd();
}
EXPORT_SYMBOL(release_console_sem);
//调用call_console_drivers函数
static
void
call_console_drivers(unsigned start, unsigned end)
{
unsigned cur_index, start_print;
static
int
msg_level = -1;
BUG_ON(((
int
)(start - end)) > 0);
cur_index = start;
start_print = start;
while
(cur_index != end) {
if
(msg_level < 0 && ((end - cur_index) > 2) &&LOG_BUF(cur_index + 0) ==
'<'
&&LOG_BUF(cur_index + 1) >=
'0'
&&LOG_BUF(cur_index + 1) <=
'7'
&&LOG_BUF(cur_index + 2) ==
'>'
) {
msg_level = LOG_BUF(cur_index + 1) -
'0'
;
cur_index += 3;
start_print = cur_index;
}
while
(cur_index != end) {
char
c = LOG_BUF(cur_index);
cur_index++;
if
(c ==
'\n'
) {
if
(msg_level < 0) {
msg_level = default_message_loglevel;
}
_call_console_drivers(start_print, cur_index, msg_level);
msg_level = -1;
start_print = cur_index;
break
;
}
}
}
_call_console_drivers(start_print, end, msg_level);
}_call_console_drivers函数
//调用console的写方法
static
void
__call_console_drivers(unsigned start, unsigned end)
{
struct
console *con;
for_each_console(con) {
//遍历console_drivers数组 #define for_each_console(con) for (con = console_drivers; con != NULL; con = con->next)
if
((con->flags & CON_ENABLED) && con->write &&(cpu_online(smp_processor_id()) ||(con->flags & CON_ANYTIME)))
con->write(con, &LOG_BUF(start), end - start);
//调用console的写方法
}
}
//由于已经注册的终端是serial8250_console,这个终端的写方法是调用serial8250_console_write()函数--->uart_console_write()--->serial8250_console_putchar()
//--->serial_out()最终打印在串口2终端上
/*
static struct console serial8250_console = {
.name = "ttyS",
.write = serial8250_console_write,//写方法
.device = uart_console_device,//tty驱动
.setup = serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。
.early_setup = serial8250_console_early_setup,
.flags = CON_PRINTBUFFER | CON_ANYTIME,
.index = -1,
.data = &serial8250_reg,
};
*/
console_drivers链表在register_console中会设置
联系客服