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中會設置
- Jul 14 Tue 2015 15:12
linux的console驅動
全站熱搜
留言列表