Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> u-boot串口和stdio、console初始化及相關操作詳解(三)

u-boot串口和stdio、console初始化及相關操作詳解(三)

編輯:關於Android編程

console是構建在stdio之上的,console的初始化是board_r中最後收尾的操作。
console的初始化函數console_init_r在common/console.c中實現:
int console_init_r(void)
{
    char *stdinname, *stdoutname, *stderrname;
    struct stdio_dev *inputdev = NULL, *outputdev = NULL, *errdev = NULL;

#ifdef CONFIG_CONSOLE_MUX
    int iomux_err = 0;
#endif
    /* set default handlers at first */
    gd->jt->getc  = serial_getc;
    gd->jt->tstc  = serial_tstc;
    gd->jt->putc  = serial_putc;
    gd->jt->puts  = serial_puts;
    gd->jt->printf = serial_printf;
/*--------------------------以上為代碼段1--------------------------------------------*/

    /* stdin stdout and stderr are in environment */
    /* scan for it */
    stdinname  = getenv("stdin");   
    stdoutname = getenv("stdout");   
    stderrname = getenv("stderr");  //setenv stdout serial,vga標准輸出被重載,如果u-boot中環境變量stdou被設定,那麼stdout就被重定位
    if (OVERWRITE_CONSOLE == 0) {  /* if not overwritten by config switch */  這裡OVERWRITE_CONSOLE值為1
        inputdev  = search_device(DEV_FLAGS_INPUT,  stdinname);
        outputdev = search_device(DEV_FLAGS_OUTPUT, stdoutname);
        errdev    = search_device(DEV_FLAGS_OUTPUT, stderrname);
#ifdef CONFIG_CONSOLE_MUX  //如setenv stdout serial,vga
        iomux_err = iomux_doenv(stdin, stdinname);
        iomux_err += iomux_doenv(stdout, stdoutname);
        iomux_err += iomux_doenv(stderr, stderrname);
        if (!iomux_err)
            /* Successful, so skip all the code below. */
            goto done;
#endif
    }
/*--------------------------以上為代碼段2--------------------------------------------*/

    /* if the devices are overwritten or not found, use default device */
    if (inputdev == NULL) {
        inputdev  = search_device(DEV_FLAGS_INPUT,  "serial");
    }
    if (outputdev == NULL) {
        outputdev = search_device(DEV_FLAGS_OUTPUT, "serial");
    }
    if (errdev == NULL) {
        errdev    = search_device(DEV_FLAGS_OUTPUT, "serial");
    }

/*--------------------------以上為代碼段3--------------------------------------------*/
    
    /* Initializes output console first */
    if (outputdev != NULL) {
        /* need to set a console if not done above. */
        console_doenv(stdout, outputdev);
    }
    if (errdev != NULL) {
        /* need to set a console if not done above. */
        console_doenv(stderr, errdev);
    }
    if (inputdev != NULL) {
        /* need to set a console if not done above. */
        console_doenv(stdin, inputdev);
    }

/*--------------------------以上為代碼段4--------------------------------------------*/

#ifdef CONFIG_CONSOLE_MUX
done:
#endif
#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET  /*defined*/
    stdio_print_current_devices();
#endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */
#ifdef CONFIG_SYS_CONSOLE_ENV_OVERWRITE  /*no defined*/
    /* set the environment variables (will overwrite previous env settings) */
    for (i = 0; i < 3; i++) {
        setenv(stdio_names[i], stdio_devices[i]->name);
    }
#endif /* CONFIG_SYS_CONSOLE_ENV_OVERWRITE */  /*defined*/
    gd->flags |= GD_FLG_DEVINIT;   /* device initialization completed */

    print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL);
/*--------------------------以上為代碼段5--------------------------------------------*/

    return 0;
}
上述程序按其實現的功能可分為5部分,為了便於分析,我們下面僅以stdout設備為例,逐步進行討論:
1. gd->jt初始化------代碼段1
gd->jt->getc = serial_getc;
gd->jt->tstc  = serial_tstc;
....
上述的代碼段設置jt操作的默認函數為串口相關函數。關於gd->jt所包含函數的使用,我們將在後續的章節中討論。
2.stdin, stdout, stderr設備被環境變量中的設定值重定位---代碼段2
取決於OVERWRITE_CONSOLE配置,當其被配置為0時,標准輸入輸出設備將使用環境變量stdin,stdout,stderr中的設定值。
代碼段2包括的代碼段如下:
    stdinname =getenv("stdin");   
    stdoutname = getenv("stdout");   
    stderrname = getenv("stderr");  /*setenv stdout serial,vga標准輸出被重載,如果u-boot中環境變量stdou被設定,那麼stdout就被重定位*/
    if (OVERWRITE_CONSOLE == 0) {  /* if not overwritten by config switch */  /* OVERWRITE_CONSOLE或為宏定義,或為函數返回值,這裡為返回值1*/
        inputdev  = search_device(DEV_FLAGS_INPUT,  stdinname);
        outputdev = search_device(DEV_FLAGS_OUTPUT, stdoutname);
        errdev    = search_device(DEV_FLAGS_OUTPUT, stderrname);
#ifdef CONFIG_CONSOLE_MUX  //如setenv stdout serial,vga
        iomux_err = iomux_doenv(stdin, stdinname);
        iomux_err += iomux_doenv(stdout, stdoutname);
        iomux_err += iomux_doenv(stderr, stderrname);
        if (!iomux_err)
            /* Successful, so skip all the code below. */
            goto done;
#endif
如我們曾在u-boot中執行命令:
=>setenv stdout vga

=>setenv stdout serial, vga
(注意:只有在定義了CONFIG_CONSOLE_MUX了時,才能將多個設備賦值給stdio相關的環境變量,否則執行上述命令u-boot會返回錯誤信息。)
然後執行saevenv命令保存環境變量,重啟u-boot,代碼執行到這裡,如果OVERWRITE_CONSOLE的值為0,代碼繼續向下執行,
針對環境變量的設定值,有兩種情況,我們以stdout設備為例,分析其處理流程。
如環境變量stdout的值為"vga",stdout設定為單一的stdio設備vga,search_device(DEV_FLAGS_OUTPUT, stdoutname)的返回值
outputdev為非空,且指向設備名為"vga"的設備。
如環境變量stdout的值為"serial,vga ",則search_device(DEV_FLAGS_OUTPUT, stdoutname)找不到設備名為"serial,vga"的設備,
返回值outputdev為NULL。
接下來的宏CONFIG_CONSOLE_MUX處理上述某一stdio包含多路設備的情況,如輸出信息同時輸出到多個設備的情況。
上述代碼中search_device語句放在iomux_doenv之後應該更合理。即:
    stdoutname = getenv("stdout");   /*setenv stdout serial,vga標准輸出被重載,如果u-boot中環境變量stdou被設定,那麼stdout就被重定位*/
    ...
    if (OVERWRITE_CONSOLE == 0) {  /* if not overwritten by config switch */  /* OVERWRITE_CONSOLE或為宏定義,或為函數返回值,這裡為返回值1*/
#ifdef CONFIG_CONSOLE_MUX  //如setenv stdout serial,vga
        iomux_err = iomux_doenv(stdin, stdinname);
             ...
        if (!iomux_err)
            /* Successful, so skip all the code below. */
            goto done;
#endif
        inputdev  = search_device(DEV_FLAGS_INPUT,  stdinname);
        ...
這樣,當定義了CONFIG_CONSOLE_MUX時,如果iomux_doenv的執行沒有錯誤,那麼跳過search_device。如果有錯誤,則進一步執行search_device段代碼。
如果沒有定義CONFIG_CONSOLE_MUX,則直接執行search_device。原來的程序流程安排中,當定義了CONFIG_CONSOLE_MUX時,會先執行那些search_device代碼,後續執行iomux_doenv後,大多數情況下iomux_doenv的執行不會有錯誤,這樣直接goto done,search_device的返回值根本用不到,這時先執行的search_device有些多余。對比原程序流程安排,更改後的代碼邏輯性比較強,執行效率提高,但可讀性有點差。
這裡還要強調的是:
標志OVERWRITE_CONSOLE(或是宏,或是函數的返回值)決定著標准輸入輸出設備是否使用非易失性存儲器存儲的環境變量stdin,stdout,stderr中的設定值,即是否被後者重載。如果OVERWRITE_CONSOLE 的值為0,那麼u-boot啟動後,stdio設備將使用重載值。否則,會使用默認的serial設備(見代碼段3)。
當在u-boot中執行命令setenv stdout ...時,其類似於linux中執行了的輸入輸出重定位命令">"。該命令也是立即生效的。但setenv stdout ...並未調用代碼段2。而是調用了某些回調函數,進行了stdio重定位。所以,u-boot命令setenv stdout ...強調的是stdio的重定位到指定的設備。
要注意區分stdio被環境變量重載和stdio重定位的區別。
下面我們重點分析iomux_doenv函數。
iomux_doenv函數包含的代碼比較多,下面刪除了注解和一些返回值的判斷處理,且只保留了stdout分支,且按實現功能將其分為3段,其大致的處理框架如下:
#ifdef CONFIG_CONSOLE_MUX

/* This tries to preserve the old list if an error occurs. */
int iomux_doenv(const int console, const char *arg)
{
    char *console_args, *temp, **start;
    int i, j, k, io_flag, cs_idx, repeat;
    struct stdio_dev *dev;
    struct stdio_dev **cons_set;
    console_args = strdup(arg);
      ...
    i = 0;
    temp = console_args;
    for (;;) {
        temp = strchr(temp, ',');
        if (temp != NULL) {
            i++;
            temp++;
            continue;
        }
        /* There's always one entry more than the number of commas. */
        i++;
        break;
    }
    start = (char **)malloc(i * sizeof(char *));
    ...
    /* setenv stdout serial,vga 幾個用逗號分隔的參數*/    
    i = 0;
    start[0] = console_args;
    for (;;) {
        temp = strchr(start[i++], ',');
        if (temp == NULL)
            break;
        *temp = '\0';
        start[i] = temp + 1;
    }
   /*start是一個指向字符串的指針數組。這裡start[0]指向serial, start[1]指向vga*/

/*--------------------------以上為代碼段2.1 --------------------------------------------*/
    cons_set = (struct stdio_dev **)calloc(i, sizeof(struct stdio_dev *));
    /*...cons_set檢查,出錯返回1*/
    switch (console) {
    case stdout:
        io_flag = DEV_FLAGS_OUTPUT;
        break;
    default:
            /*...釋放資源start,console_args,cons_set*/
        return 1;
    }
    cs_idx = 0;
    for (j = 0; j < i; j++) {
        dev = search_device(io_flag, start[j]);
        if (dev == NULL)
            continue;
         repeat = 0;
         for (k = 0; k < cs_idx; k++) {
            if (dev == cons_set[k]) {
                repeat++;
                break;
            }
         }
         if (repeat)
            continue;
        if (console_assign(console, start[j]) < 0)
            continue;
        cons_set[cs_idx++] = dev;
    }

/*--------------------------以上為代碼段2.2 --------------------------------------------*/
    free(console_args);
    free(start);
    /* failed to set any console */
    if (cs_idx == 0) {
        free(cons_set);
        return 1;
    } else {
        console_devices[console] =
            (struct stdio_dev **)realloc(console_devices[console],
            cs_idx * sizeof(struct stdio_dev *));
        if (console_devices[console] == NULL) {
            free(cons_set);
            return 1;
        }
      
        memcpy(console_devices[console], cons_set, cs_idx *
            sizeof(struct stdio_dev *));
        cd_count[console] = cs_idx;
    }
    free(cons_set);
/*--------------------------以上為代碼段2.3 --------------------------------------------*/
    return 0;
}

#endif /* CONFIG_CONSOLE_MUX */
上述程序中的stdin,stdout,stderr在include/common.h中定義:
#define stdin        0
#define stdout       1
#define stderr       2
#define MAX_FILES    3
討論上述代碼之前,首先要強調的是,只有定義了CONFIG_CONSOLE_MUX,才會有函數iomux_doenv的定義和實現。
否則,不會使用此函數。
代碼段2.1
這裡主要處理stdout包含多個設備的情況,而單個設備可看作多個設備的特例。多個設備的設備名使用逗號分開,就像我們在上面的代碼段2中討論的一樣,當stdio被環境變量的設定值重載時,可能包含的多個設備的設備名用逗號分開,如環境變量stdout的值為“ serial,vga”,該段代碼最終將這些設備名字符串的首址存入start[i]字符串指針數組中,i為用逗號隔開的設備名個數。
代碼段2.2
首先利用上述start[i]字符串指針數組指向的設備名查找在全局設備表devs中查找此前已注冊的stdio設備,並去掉設備重復(如設定stdout環境變量為serial,vga,serial),然後調用console_assign:
int console_assign(int file, const char *devname)
{
    int flag;
    struct stdio_dev *dev;
    /* Check for valid file */
    switch (file) {
    case stdin:
        flag = DEV_FLAGS_INPUT;
        break;
    case stdout:
    case stderr:
        flag = DEV_FLAGS_OUTPUT;
        break;
    default:
        return -1;
    }
    /* Check for valid device name */
    dev = search_device(flag, devname);
    if (dev)
        return console_setfile(file, dev);
    return -1;
}
為了此處的討論盡可能清晰簡單,search_device函數我們放在後面分析。
在執行console_assign之前,已經調用過search_device獲取了設備指針,而後調用的console_assign中又執行了一遍search_device,然後調用了onsole_setfile,查找設備的操作有些冗余,為何不在iomux_doenv中直接調用onsole_setfile,來代替console_assign呢?
對外部應用程序來說,只關心和知道設備名,所以調用console_assign,利用設備設備名查找到對應的設備,然後再調用onsole_setfile。這是比較合理的。console_assign是文件console.c中開放給外部程序的唯一stdio分配操作的函數接口。onsole_setfile則是console.c中的靜態函數。所以針對外部文件中的函數iomux_doenv相關的stdio分配操作,調用了console_assign ,即使有些代碼冗余,就其程序架構上合理性,該冗余還是能容忍的。
下面分析函數console_setfile:
static int console_setfile(int file, struct stdio_dev * dev)
{
    int error = 0;
    if (dev == NULL)
        return -1;
    switch (file) {
    case stdin:
    case stdout:
    case stderr:
        /* Start new device */
        if (dev->start) {
            error = dev->start(dev);
            /* If it's not started dont use it */
            if (error < 0)
                break;
        }
        /* Assign the new device (leaving the existing one started) */
        stdio_devices[file] = dev;
        /*
         * Update monitor functions
         * (to use the console stuff by other applications)
         */
        switch (file) {
        case stdin:
            gd->jt->getc = getc;
            ...
            break;
        case stdout:
                    ...
            gd->jt->printf = printf;
            break;
        }
        break;
    default:       /* Invalid file ID */
        error = -1;
    }
    return error;
}
首先嘗試啟動入口參數中的stdio設備。需要注意的是,在前面"stdio_add_devices"一節stdio設備的注冊中,只是填充了相關的結構體,如果其後沒有被使用(如serial就曾被使用了),就還未真正實際啟動被注冊的設備。而這裡,為console分配stdio設備時,就要啟動它(實際是初始化該硬件設備),因為接下就要使用該硬件完成stdio實際的硬件輸入輸出操作。上述dev->start代碼中,一旦啟動失敗(有可能已啟動,或硬件自身的原因),函數console_setfile就立即返回,返回值為0。console_setfile的上層函數也返回0,這樣就回到代碼段2.2,但接下來還是會填充cons_set,但不會在函數console_setfile中接著填充下面的stdio_devices。
另外還有一種情況,如video設備,在video注冊為stdio設備時,並未填充start函數,那麼,start為NULL,即該設備無需有啟動操作即可使用,那麼這裡,該設備也會填充到stdio_devices中。
接下來函數console_setfile將上述查找到的設備最終存儲在全局變量stdio_devices中。如上所述,此設備是被成功啟動或可用的設備,
stdio_devices在common/stdio.c中定義為:
struct stdio_dev *stdio_devices[] = { NULL, NULL, NULL };
它是一個設備指針數組,該數組有3個成員,即stdin,stdout,stderr,代表當前正在使用的stdio設備。所以,如果是stdout設備,該設備的結構體首址存入到stdio_devices[1]中。其他類此。當被重載後stdio為多個設備,如stdout環境變量設定為serial,vga,多次調用的console_assign也會多次執行console_setfile,如果兩設備都有效,且能被成功啟動,那麼就會對同一個stdio_devices[stdout]進行賦值,可以看到stdio_devices[1]的值最終為設備名為"vga"的設備,第一個serial設備會被覆蓋掉。即這時只使用環境變量設定值的最後一個可被啟動的有效設備。
另外在後續的信息輸出的stdout使用時,我們可以看到printf調用了fputs,fputs又調用了console_puts,這時,根據CONFIG_CONSOLE_MUX的定義,console_puts有兩處實現,當定義了CONFIG_CONSOLE_MUX,即stdout設備可多路輸出, console_puts使用console_devices數組中的設備進行輸入輸出的相關操作;
當沒有定義了CONFIG_CONSOLE_MUX時,直接調用stdio_devices[file]->puts(stdio_devices[file], s)。但顯然,當定義了CONFIG_CONSOLE_MUX時,後續的輸入輸出操作中幾乎不會使用到stdio_devices,此中情況下也被填充,其意義不是很大。
其實stdio_devices變量主要用在CONFIG_CONSOLE_MUX未定義的情況下。此時,最後的輸入輸出操作會使用該變量中存儲的設備來完成。
README.iomux中有:
It should be possible to specify any device which console_assign()
finds acceptable, but the code has only been tested with serial and
nc.

程序最後將更新gd->jt函數列表。

代碼段2.3

該段代碼主要實現:
將上面代碼段查找到的設備存儲到全局變量console_devices[console]中,其設備個數存儲到全局變量cd_count[console]中。
這裡的console即stdin,stdout,stderr常量之一。當這三者之一擁有多個stdio設備時,console_devices[console]會保存這多個設備,且用cd_count[console]來記錄設備個數。如環境變量stdout的值為serial,vga,那麼console_devices[1]指向的struct stdio_dev結構體指針數組中會包含兩個指針,分別指向serial和vga設備對應的結構體地址。
cd_count[1]為console_devices[1]指向的數組的長度,這裡值為2。
我們可以在最終的輸出函數console_puts實現中看到console_devices和cd_count的使用:

static void console_puts(int file, const char *s)
{
    int i;
    struct stdio_dev *dev;
    for (i = 0; i < cd_count[file]; i++) {
        dev = console_devices[file][i];
        if (dev->puts != NULL)
            dev->puts(dev, s);
    }
}
函數iomux_doenv總結:
該函數填充了如下的全局變量:
stdio_devices[3]
console_devices[3]
cd_count[3]
stdio_devices[3]在common/stdio.c中定義。還未被使用過。
console_devices、cd_count在common/console.c中定義為:
static struct stdio_dev *tstcdev;
struct stdio_dev **console_devices[MAX_FILES];
int cd_count[MAX_FILES];
其中的MAX_FILES在 include/common.h中定義為3,即stdin,stdout,stderr。
console_devices包含控制台所用的struct stdio_dev設備,控制台設備包括標准輸入,輸出和錯誤設備。而每項標准設備會有包含多個struct stdio_dev設備的情況。如控制台同時輸出到串口和液晶。cd_count[...]的值這種所多個包含struct stdio_dev設備的計數。如console_devices[1]是指向stdout設備數組的指針,而cd_count[1]是stdout設備所包含的個數。這裡的1即stdout。其他類此。
stdio_devices[...]則包含了當前的標准輸入輸出和出錯設備。如 stdio_devices[0]為當前標准輸入設備,
stdio_devices[1]為當前標准輸出, stdio_devices[2]為當前標准出錯。
當某項標准stdio設備包含多個設備時,只是使用了環境變量相對應設定中的最後一項,
比如我們設定了:
setenv stdout serial,vga
那麼stdio_devices[1]的值是設備名為"vga"的設備。
3. 查找設備---代碼段3
前面的處理包含了以下的情況:
a). OVERWRITE_CONSOLE != 0,即stdio設備沒有被環境變量重載。無路是否定義了CONFIG_CONSOLE_MUX ,
這將直接執行到代碼段3
b). OVERWRITE_CONSOLE == 0, 即stdio設備被環境變量重載,且定義了CONFIG_CONSOLE_MUX,
stdio設備環境變量中包含的設備無效或stdio設備console注冊失敗,也將執行代碼段3。
否則如果注冊成功,跳過代碼段3-4。
所以,只要程序執行到了代碼段3,都將使用串口(serial)作為默認的stdio設備。
下面我們具體分析該代碼段所包含函數的具體實現。以stdout設備為例,簡化後代碼段3如下:
if (outputdev == NULL) {
   outputdev = search_device(DEV_FLAGS_OUTPUT, "serial");
}
search_device函數執行設備查找,其輸入參數DEV_FLAGS_OUTPUT為stdin,stdout,stderr對應的三者之一。
另一個參數為設備名,即根據設備名來查找設備。
search_device函數的在common/console.c中實現如下:
struct stdio_dev *search_device(int flags, const char *name)
{
    struct stdio_dev *dev;
    dev = stdio_get_by_name(name);
    if (dev && (dev->flags & flags))
          return dev;
    return ((void *)0);
}
函數stdio_get_by_name在common/stdio.c中實現:
struct stdio_dev* stdio_get_by_name(const char *name)
{
    struct list_head *pos;
    struct stdio_dev *dev;
    if(!name)
        return NULL;
    list_for_each(pos, &(devs.list)) {
        dev = list_entry(pos, struct stdio_dev, list);
        if(strcmp(dev->name, name) == 0)
            return dev;
    }
    return NULL;
}
在前面"stdio_add_devices"函數討論的一節中,所有注冊的stdio設備使用全局變量devs.list鏈表串接起來,stdio_get_by_name函數就是在此鏈表中查找名字為涵參name的stdio設備。我們繼續跟蹤list_entry,可以看到其定義為:
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)
container_of和linux驅動中用法一致,也是通過結構體成員來查找結構體自身的首址。我們在前面的"stdio_add_devices" 一節中也曾經提及過,devs.list的鏈表成員只是struct stdio_dev結構體的成員變量list,而非struct stdio_dev結構體變量本身。所以要使用container_of查找描述stdio設備的struct stdio_dev變量自身。stdio_get_by_name執行完返回到search_device後,如果找到設備,還會核對查找到的設備屬性標志是否和輸入參數的標志一致,設備屬性標志在該設備注冊時設置。
4. 注冊stdio設備到console中---代碼段4
我們要注意代碼執行到代碼段4的前述情況:
使用串口serial作為唯一的默認stdio設備進行console注冊。
針對CONFIG_CONSOLE_MUX定義與否,函數console_doenv有兩處實現。當沒有定義CONFIG_CONSOLE_MUX時,則console_doenv的實現為:
static inline void console_doenv(int file, struct stdio_dev *dev)
{
    console_setfile(file, dev);
}
函數console_setfile將上述查找到的設備最終存儲在全局變量stdio_devices中。這我們在上面代碼段2.2已經討論過。最終將默認的serial設備賦值到stdio_devices中去。
綜上所述,當沒有定義CONFIG_CONSOLE_MUX,設備是不會注冊到console的設備描述變量console_devices中去的。當定義了CONFIG_CONSOLE_MUX時,函數console_doenv則直接調用common/Iomux.c中的iomux_doenv。後續操作和上述代碼段2.x中的操作一致,只不過這裡的設備為單一設備serial,會將serial注冊到console中去。不知是否是一種代碼過渡,截止到u-boot-2016.3,個人覺得還是將stdio和console混淆處理的模糊不清。
5.函數的尾端程序處理---代碼段5
後續的程序包括:
gd->flags |= GD_FLG_DEVINIT;   /* device initialization completed */
print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL);
由於CONFIG_PRE_CONSOLE_BUFFER沒有定義,print_pre_console_buffer為空函數。
gd->flags是一個非常重要的定義,當執行gd->flags |= GD_FLG_DEVINIT後,代表此時console控制台已經的前戲准備工作已經完成,console控制台已經可用。
gd->flags會被console.c文件中的函數多次判標GD_FLG_DEVINIT使用。 這些函數包括getc,tstc,puts,on_console。
如最常用的信息輸出函數printf調用了這其中的puts。下面以puts函數為例,分析變量gd->flags使用及意義:
void puts(const char *s)
{
   ...
   if(!gd->have_console)
        return pre_console_puts(s);
    if (gd->flags & GD_FLG_DEVINIT) {
        /* Send to the standard output */
        fputs(stdout, s);
    } else {
        /* Send directly to the handler */
        pre_console_puts(s);
        serial_puts(s);
    }
}
上述代碼中,如果gd->flags & GD_FLG_DEVINIT為真時,將使用fputs執行信息輸出,fputs定義為:
void fputs(int file,constchar*s)
{
    if (file < MAX_FILES)
        console_puts(file, s);
}
函數console_puts我們在代碼段2.2中粗略提及過,其具體實現如下:
static void console_puts(int file, const char *s)
{
    int i;
    struct stdio_dev *dev;
    for (i = 0; i < cd_count[file]; i++) {
        dev = console_devices[file][i];
        if (dev->puts != NULL)
            dev->puts(dev, s);
    }
}

可見,gd->flags & GD_FLG_DEVINIT為真時,最終將使用console_devices中注冊過的控制台函數執行相關操作。也即是,執行了代表gd->flags |= GD_FLG_DEVINIT後,gd->flags & GD_FLG_DEVINIT為真 ,代表console控制台中的相關操作函數可用了。否則使用默認的串口輸出函數serial_puts。

考慮到這樣一種情況,我們在上述代碼段5的語句

 

gd->flags |= GD_FLG_DEVINIT;

 

之前的printf輸出信息,將會使用默認的串口輸出函數serial_puts,該函數在board_f階段被注冊且其後續可用。而該代碼段之後的程序,所使用的printf,都將使用該節討論的console控制台輸出函數。

gd->flags |= GD_FLG_DEVINIT語句制造了一個這樣的分水嶺。

 

那麼在stdio和serial結構圖的基礎上,加上console,三者之間的結構總圖如下:

\

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved