tiny4412 串口驅動分析七 --- log打印的幾個階段之內核啟動階段(earlyprintk)


作者:彭東林

郵箱:pengdonglin137@163.com

 

開發板:tiny4412ADK+S700 4GB Flash

主機:Wind7 64位

虛擬機:Vmware+Ubuntu12_04

u-boot:U-Boot 2010.12

Linux內核版本:linux-3.0.31

Android版本:android-4.1.2

 

下面要分析的是內核Log打印的幾個階段

  1. 自解壓階段
  2. 內核啟動階段
  3. 內核啟動完全以后
  4. shell終端下

在這個階段內核log打印可以調用printk和printascii,同時printk又分為兩個階段,從剛才的分析中知道,printk最終調用的是有register_console注冊的console_drivers的write函數,在tiny4412平台上調用register_console的地方有兩處,第一處是在arch/arm/kernel/early_printk.c中,另一處就是在串口驅動注冊中,具體是在driver/tty/serial/samsung.c中,下面我們開始分析。

printascii的實現:

首先printascii需要配置內核才能使用:

make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig

 

Kernel hacking

     --- Kernel low-level debugging functions

 

這樣就可以使用printascii了:

在printk中會調用printascii,

#ifdef        CONFIG_DEBUG_LL

         printascii(printk_buf);

#endif

print_asciii使用匯編實現的,在文件arch/arm/kernel/debug.S中:

.macro addruart_current, rx, tmp1, tmp2 addruart \tmp1, \tmp2 mrc p15, 0, \rx, c1, c0 tst \rx, #1 moveq \rx, \tmp1 movne \rx, \tmp2 .endm ENTRY(printascii) addruart_current r3, r1, r2 b 2f 1: waituart r2, r3 senduart r1, r3 busyuart r2, r3 teq r1, #'\n' moveq r1, #'\r' beq 1b 2:                 teq   r0, #0 ldrneb r1, [r0], #1 teqne r1, #0 bne 1b mov pc, lr ENDPROC(printascii)

其中 addruart 是在文件arch/arm/mach-exynos/include/mach/debug-macro.S中實現的,waituart、senduart以及busyuart是在arch/arm/plat-samsung/include/plat/debug-macro.S中實現的,大家可以參考這兩個文件理解具體實現過程。

early_printk中調用register_console

tiny4412使用的內核默認是沒有開啟early_printk的,即log_buf中內容只有等driver/tty/serial下的串口驅動注冊完成后才能輸出到串口終端,在此之前調用printk的內容都緩存到log_buf中了,如果想提前使用的話,需要使能early_printk,下面說明一下如何使能early_printk。

make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig

Kernel hacking

        -- Kernel low-level debugging functions

        --Early printk

 

 

要使能early_printk首先必須使能Kernel low-level debugging functions,因為early_printk最終也是使用printascii實現的,這樣arch/arm/kernel/early_printk.c就會參加編譯

在early_printk.c中:

static int __init setup_early_printk(char *buf) { printk("%s enter\n", __func__);     register_console(&early_console);     return 0; } early_param("earlyprintk", setup_early_printk);

雖然內核配置了,但是要讓內核調用setup_early_printk還必須在u-boot給內核傳參的時候給bootargs在加上一個參數”earlyprintk”,如下:

set bootargs ‘console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70 earlyprintk

那么內核是如何處理的呢?

在文件include/linux/init.h中:

#define __setup_param(str, unique_id, fn, early)            \
    static const char __setup_str_##unique_id[] __initconst \ __aligned(1) = str; \ static struct obs_kernel_param __setup_##unique_id \ __used __section(.init.setup) \ __attribute__((aligned((sizeof(long))))) \ = { __setup_str_##unique_id, fn, early } #define early_param(str, fn)                    \ __setup_param(str, fn, fn, 1)

將early_param("earlyprintk", setup_early_printk);展開后:

static const char __setup_str_setup_early_printk[] __initconst __aligned(1) = "earlyprintk"; static struct obs_kernel_param __setup_setup_early_printk __used __section(.init.setup) \ __attribute__((aligned((sizeof(long))))) = \ { __setup_str_setup_early_printk, setup_early_printk, 1 }

即上面的這個結構體被鏈接到了”.init.setup”段,在arch/arm/kernel/vmlinux.lds中:

  . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;

將所有的.init.setup都鏈接到了__setup_start和__setup_end之間

在內核啟動的時候:

start_kernel --- setup_arch(&command_line); //這個函數的目的是獲得u-boot傳給內核的參數(bootargs),並將參數存放在command_line中,然后解析command_line,並調用相關的函數處理--- parse_early_param()   (arch/arm/kernel/setup.c) --- strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); --- parse_early_options(tmp_cmdline);

parse_early_options用於解析tmp_cmdline

void __init parse_early_options(char *cmdline) { parse_args("early options", cmdline, NULL, 0, do_early_param); }

在文件kernel/params.c中:

int parse_args(const char *name, char *args, const struct kernel_param *params, unsigned num, int (*unknown)(char *param, char *val)) { char *param, *val; /* Chew leading spaces */ args = skip_spaces(args); // 跳過args開頭的空格

    while (*args) { int ret; int irq_was_disabled; /* 如:cmdline是“console=ttySAC0,115200n8 androidboot.console=ttySAC0” 執行next_arg后: params=”console”, val=” ttySAC0,115200n8” args=” androidboot.console=ttySAC0” */ args = next_arg(args, &param, &val); // 獲得下一個參數的位置,
        irq_was_disabled = irqs_disabled(); /* parse_one所做的主要就是將params和val傳遞給unknown處理,這里unknown就是do_early_param,所以下面分析do_early_param */ ret = parse_one(param, val, params, num, unknown); … } } /* All parsed OK. */
    return 0; }

 

static int __init do_early_param(char *param, char *val) { const struct obs_kernel_param *p; /* 還記得early_param("earlyprintk", setup_early_printk)展開后的結果嗎? 其中early為1,str是 “earlyprintk”,setup_func就是setup_early_printk */
    for (p = __setup_start; p < __setup_end; p++) { if ((p->early && strcmp(param, p->str) == 0) || (strcmp(param, "console") == 0 && strcmp(p->str, "earlycon") == 0) ) { if (p->setup_func(val) != 0) … } } return 0; }

下面我們就分析setup_early_printk:

static int __init setup_early_printk(char *buf) { register_console(&early_console); return 0; }

結構體early_console 的定義如下:

static struct console early_console = { .name =        "earlycon", .write = early_console_write, .flags =    CON_PRINTBUFFER | CON_BOOT, .index =    -1, };

看一下early_console_write干了什么:

static void early_console_write(struct console *con, const char *s, unsigned n) { early_write(s, n); }

 

static void early_write(const char *s, unsigned n) { while (n-- > 0) { if (*s == '\n') printch('\r'); printch(*s); s++; } }

可以看到,它調用的是printch,它在文件arch/arm/kernel/debug.S中實現:

ENTRY(printascii) addruart_current r3, r1, r2 b 2f 1: waituart r2, r3 senduart r1, r3 busyuart r2, r3 teq r1, #'\n' moveq r1, #'\r' beq 1b 2:      teq    r0, #0 ldrneb r1, [r0], #1 teqne r1, #0 bne 1b mov pc, lr ENDPROC(printascii) ENTRY(printch) addruart_current r3, r1, r2 mov r1, r0 mov    r0, #0 b 1b ENDPROC(printch)

這樣當driver/tty/serial下的驅動尚未注冊時,printk就已經可以使用了,它最終調用的是early_console_write輸出到串口終端的

下面我們分析一個函數register_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. * * This can happen pretty early during the boot process (because of * early_printk) - sometimes before setup_arch() completes - be careful * of what kernel features are used - they may not be initialised yet. * * There are two types of consoles - bootconsoles (early_printk) and * "real" consoles (everything which is not a bootconsole) which are * handled differently. * - Any number of bootconsoles can be registered at any time. * - As soon as a "real" console is registered, all bootconsoles * will be unregistered automatically. * - Once a "real" console is registered, any attempt to register a * bootconsoles will be rejected */
void register_console(struct console *newcon) { int i; unsigned long flags; struct console *bcon = NULL; /* * before we register a new CON_BOOT console, make sure we don't * already have a valid console 這個判斷的目的是:看當前系統是否有”real”類型的console注冊,如果有的話,直接返回,即”real”類型和”bootconsoles”類型的console不能共存,
如果注冊了”real”類型的console的話,則會對”bootconsoles”類型的console進行unregister 如果已經有”real”類型的console,則注冊”bootconsoles”類型的console時會失敗,”bootconsoles”類型的console的flags設置CON_BOOT位。 這里在內核剛啟動,還沒有任何console注冊,console_drivers是NULL
*/ if (console_drivers && newcon->flags & CON_BOOT) { /* find the last or real console */ for_each_console(bcon) { 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; // perferred_console和selected_console的初始值都是-1,但是如果在bootargs中設置了類似“console=ttySAC0,115200n8”時,在解析console參數時會設置selected_console,這個一會分析 if (preferred_console < 0 || bcon || !console_drivers) preferred_console = selected_console; if (newcon->early_setup) // 如果有的話,則調用 newcon->early_setup(); /* * 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 (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; } } } /* * See if this console matches one we selected on * the command line. // console_cmdline會在解析bootargs的console參數時設置 */ for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) { if (strcmp(console_cmdline[i].name, newcon->name) != 0) continue; if (newcon->index >= 0 && newcon->index != console_cmdline[i].index) continue; if (newcon->index < 0) newcon->index = console_cmdline[i].index; if (newcon->setup && newcon->setup(newcon, console_cmdline[i].options) != 0) break; newcon->flags |= CON_ENABLED; newcon->index = console_cmdline[i].index; if (i == selected_console) { // selected_console是從bootargs中解析出來的 newcon->flags |= CON_CONSDEV; preferred_console = selected_console; } break; } if (!(newcon->flags & CON_ENABLED)) return; /* * If we have a bootconsole, and are switching to a real console, * don't print everything out again, since when the boot console, and * the real console are the same physical device, it's annoying to * see the beginning boot messages twice */ if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) newcon->flags &= ~CON_PRINTBUFFER; /* * Put this console in the list - keep the * preferred driver at the head of the list. */ console_lock(); // 這里會把跟selected_console一樣的ttySAC盡量往前放 if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) { newcon->next = console_drivers; console_drivers = newcon; if (newcon->next) newcon->next->flags &= ~CON_CONSDEV; } else { newcon->next = console_drivers->next; console_drivers->next = newcon; } if (newcon->flags & CON_PRINTBUFFER) { spin_lock_irqsave(&logbuf_lock, flags); con_start = log_start; spin_unlock_irqrestore(&logbuf_lock, flags); exclusive_console = newcon; } console_unlock(); console_sysfs_notify(); /* * By unregistering the bootconsoles after we enable the real console * we get the "console xxx enabled" message on all the consoles - * boot consoles, real consoles, etc - this is to ensure that end * users know there might be something in the kernel's log buffer that * went to the bootconsole (that they do not see on the real console) */ if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) && !keep_bootcon) { // 在命令行中可以設置參數,將keep_bootcon置1,就不會將bootconsole注銷了 /* we need to iterate through twice, to make sure we print * everything out, before we unregister the console(s) 如果使能了early_printk的話,下面的這條log會打印兩次,因為一次是從real console,另一次是從boot consoles。原因是 當real已經注冊時(上面更新了console_drivers),
但是此時boot consoles還尚未unregister。
*/ 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); // unregister boot consoles } else { printk(KERN_INFO "%sconsole [%s%d] enabled\n", (newcon->flags & CON_BOOT) ? "boot" : "" , newcon->name, newcon->index); } }

上面我們分析了early_printk,下面我們分析當內核啟動之后的printk打印的實現。

當Linux內核啟動之后,或者更確切的說是串口驅動注冊后,使用的是real console,會把boot consoles都disable。

內核起來后,使用串口終端可以登錄,在用戶空間(對於android)是/system/bin/sh跟用戶交互,它接收用戶通過串口輸入的命令,然后執行,它雖然在最底層都會操作串口硬件寄存器,但是跟內核的printk還不一樣,前者走的是tty驅動架構,他們在底層是通過不同的機制來操作uart控制器的。下面一塊分析。

在此之前,我們還要看一下u-boot給內核傳參:

console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70

由於tiny4412一共有4個串口,我們使用了com0和com3,在參數中的console參數console=ttySAC0,115200n8告訴Linux,將來sh要運行在ttySAC0上,它的波特率是115200,每幀8位。也就是我們要通過com0與Linux交互。

在此可以做一個實驗,如果我們執行

“hello world”會通過串口ttySAC0打印到終端

但是執行

什么反應也沒有,因為我們接在com0上而不是com3上。后面我們會嘗試修改默認的串口,從com0改成com3.

那么系統是如何解析console=ttySAC0,115200n8的呢?

在kernel/printk.c中:

__setup("console=", console_setup);

在include/linux/init.h中:

#define __setup(str, fn)                    \ __setup_param(str, fn, fn, 0)

而__setup_param我們在上面已經分析了,最終__setup("console=", console_setup);

的展開結果是:

static const char __setup_str_console_setup[] __initconst \ __aligned(1) = "console="; static struct obs_kernel_param __setup_console_setup \ __used __section(.init.setup) \ __attribute__((aligned((sizeof(long))))) \ = { __setup_str_console_setup, console_setup, 0 }

可以看到它也鏈接到了”.init.setup”段。在內核啟動的時候:

asmlinkage void __init start_kernel(void) { char * command_line; extern const struct kernel_param __start___param[], __stop___param[]; ... parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); ...... }

 

int parse_args(const char *name, char *args, const struct kernel_param *params, unsigned num, int (*unknown)(char *param, char *val)) { char *param, *val; DEBUGP("Parsing ARGS: %s\n", args); /* Chew leading spaces */ args = skip_spaces(args); while (*args) { int ret; int irq_was_disabled; args = next_arg(args, &param, &val); … ret = parse_one(param, val, params, num, unknown); // 在parse_one中會調用unknown函數,並將param和val傳給它
 … }

 

static int __init unknown_bootoption(char *param, char *val) { /* Change NUL term back to "=", to make "param" the whole string. */
    if (val) { /* param=val or param="val"? */
        if (val == param+strlen(param)+1) val[-1] = '='; else if (val == param+strlen(param)+2) { val[-2] = '='; memmove(val-1, val, strlen(val)+1); val--; } else BUG(); } /* Handle obsolete-style parameters */
    if (obsolete_checksetup(param)) return 0; /* Unused module parameter. */
    if (strchr(param, '.') && (!val || strchr(param, '.') < val)) return 0; if (panic_later) return 0; if (val) { /* Environment option */ unsigned int i; for (i = 0; envp_init[i]; i++) { if (i == MAX_INIT_ENVS) { panic_later = "Too many boot env vars at `%s'"; panic_param = param; } if (!strncmp(param, envp_init[i], val - param)) break; } envp_init[i] = param; } else { /* Command line option */ unsigned int i; for (i = 0; argv_init[i]; i++) { if (i == MAX_INIT_ARGS) { panic_later = "Too many boot init vars at `%s'"; panic_param = param; } } argv_init[i] = param; } return 0; }

對於“console=ttySAC0,115200n8”符合這個條件

static int __init obsolete_checksetup(char *line) { const struct obs_kernel_param *p; int had_early_param = 0; p = __setup_start; do { int n = strlen(p->str); if (!strncmp(line, p->str, n)) { if (p->early) { /* Already done in parse_early_param? * (Needs exact match on param part). * Keep iterating, as we can have early * params and __setups of same names 8( */
                if (line[n] == '\0' || line[n] == '=') had_early_param = 1; } else if (!p->setup_func) { printk(KERN_WARNING "Parameter %s is obsolete,"
                       " ignored\n", p->str); return 1; } else if (p->setup_func(line + n)) return 1; } p++; } while (p < __setup_end); return had_early_param; }

這樣就會調用console_setup,並把參數:ttySAC0,115200n8 傳給str變量

static int __init console_setup(char *str) { char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
    char *s, *options, *brl_options = NULL; int idx; /* * Decode str into name, index, options. */
    if (str[0] >= '0' && str[0] <= '9') { strcpy(buf, "ttyS"); strncpy(buf + 4, str, sizeof(buf) - 5); } else { strncpy(buf, str, sizeof(buf) - 1); } //這里將ttySAC0,115200n8分成了兩個字符串” ttySAC0”和”115200n8”
    buf[sizeof(buf) - 1] = 0; if ((options = strchr(str, ',')) != NULL) *(options++) = 0;  // 此時options指向字符串”115200n8” //這里會從ttySAC0中將0解析出來,這個循環執行完成后,*s就是’0’
    for (s = buf; *s; s++) if ((*s >= '0' && *s <= '9') || *s == ',') break; idx = simple_strtoul(s, NULL, 10);  // 將字符’0’轉化成10進制類型的0,然后賦值給idx
    *s = 0;  // 將’0’所在的位置為0,那么就將”ttySAC0”變成了”ttySAC”
 __add_preferred_console(buf, idx, options, brl_options); console_set_on_cmdline = 1; return 1; }

 

name=”ttySAC”, idx=0, options=”115200n8”, brl_options=NULL

static int __add_preferred_console(char *name, int idx, char *options, char *brl_options) { struct console_cmdline *c; int i; // 此時console_cmdline還沒有賦值,所以name[0]是空,循環不成立 // 如果在bootargs中傳了兩個console參數,那么在解析第二個console參數的時候name[0]就不是空的了
    for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) if (strcmp(console_cmdline[i].name, name) == 0 && console_cmdline[i].index == idx) { if (!brl_options) selected_console = i; return 0; } if (i == MAX_CMDLINECONSOLES) return -E2BIG; // 從這里可以看出,如果bootargs中傳遞了兩個console參數,如console=ttySAC0,console=ttySAC3,那么最終selected_console就是3
    if (!brl_options) selected_console = i; c = &console_cmdline[i]; strlcpy(c->name, name, sizeof(c->name));   // “ttySAC”
    c->options = options;                   // “115200n8”
    c->index = idx;                         // 0
    return 0; }

這里可以想一想,如果在bootargs中傳遞了兩個console,如”console=ttySAC0,115200n8 console=ttySAC3,115200n8”,那么:

console_cmdline[0].name = “ttySAC”

console_cmdline[0].index = 0

console_cmdline[0].options = "115200n8"

 

console_cmdline[1].name = “ttySAC”

console_cmdline[1].index = 3

console_cmdline[1].options = "115200n8"

 

selected_console = 1

對於tiny4412,有四個串口,所以會調用四次register_console,但是只有一次能成功,那一次呢?如果bootargs中console參數是ttySAC0,那么只有名為ttySAC0的串口才能成功調用register_console。具體過程,我們下面分析。是在register_console中調用newcon->setup時,因為port的mapbase沒有設置而返回錯誤。因為這四個串口在注冊時,會調用probe四次,probe的時候才會為這個port的mapbase賦值,然后才調用register_console的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM