printk列印到LCD

來源:互聯網
上載者:User

先從ppcboot傳過來的啟動命令參數說起,ppcboot把它放在內的存固定地址,參數如下
char linux_cmd[] = "initrd=0x30800000,0x440000 root=/dev/ram init=/linuxrc console=ttyS0";
在核心start_kernel函數中調用了setup_arch(&command_line)取到命令參數並儲存到
saved_command_line字元數組裡,同時command_line字元指標指向一個全域的字元數組
command_line(同名,不過這是全域的),其中存放的同樣是命令列參數。
為了方便,我們可以在此就另給command_line賦值,而不需要去改ppcboot了。
command_line="initrd=0x30800000,0x440000 root=/dev/ram init=/linuxrc console=tty0";
這樣就可以改變系統的控制台了. 還是在setup_arch函數中找到了以下代碼,以後會用到的.
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
 conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
 conswitchp = &dummy_con;
#endif
#endif
CONFIG_VT,在menuconfig的Character devices中選上Virtual terminal
CONFIG_VGA_CONSOLE,在menuconfig的Console drivers中選上VGA text console
CONFIG_DUMMY_CONSOLE,在menuconfig的Console drivers的Frame-buffer support中
選上Support for frame buffer devices,參看driver/video中的Config.in檔案
意思是如果選擇VGA文本控制台,那麼預設的顯示裝置介面是vga_con,如果是LCD之類的
Frame-buffer裝置,則預設的顯示裝置介面是dummy_con.
我這邊是用的LCD,所以重點關注dummy_con.
const struct consw *conswitchp;
const struct consw dummy_con = {
    con_startup: dummycon_startup,
    con_init:  dummycon_init,
    con_deinit:  DUMMY,
    con_clear:  DUMMY,
    con_putc:  DUMMY,
    con_putcs:  DUMMY,
    con_cursor:  DUMMY,
    con_scroll:  DUMMY,
    con_bmove:  DUMMY,
    con_switch:  DUMMY,
    con_blank:  DUMMY,
    con_font_op: DUMMY,
    con_set_palette: DUMMY,
    con_scrolldelta: DUMMY,
};
還是回到start_kernel中,接下來調用了parse_options(command_line)來處理命令列
它會以空格對命令列進行分解,比如分解後使得line="console=ttyS0",然後調用
checksetup(line),下面分析一下這個函數
static int __init checksetup(char *line)
{
 struct kernel_param *p;
 p = &__setup_start; //setup段開始地址
 do {
  int n = strlen(p->str);
  if (!strncmp(line,p->str,n)) //比較line和p->str的n個字元
  {
   if (p->setup_func(line+n))//相等則調用函數
    return 1;
  }
  p++;
 } while (p < &__setup_end);
 return 0;
}
需要理解的是這個setup段,這和驅動的初始化init_call段類似,在include/linux/init.h
中可以找到
#define __setup(str, fn)        /
 static char __setup_str_##fn[] __initdata = str;    /
 static struct kernel_param __setup_##fn __attribute__((unused)) __initsetup = { __setup_str_##fn, fn }
#define __initsetup __attribute__ ((unused,__section__ (".setup.init")))
另外我在kernel/printk.c中找到了__setup("console=", console_setup);
通過這個函數,在連結時就會在段.setup.init中建立一個kernel_param結構,其中一個成員
是字串"console="的指標,另一個則是console_setup這個函數指標,於是這樣在checksetup
中就必然會調用到console_setup這個函數了.
該函數的作用是把傳入的字串進行處理並儲存在全域結構中.我們以console=ttyS0 console=tty0
為例,這樣就會調用console_setup兩次,第一次是console_setup("ttyS0"),第二次是console_setup("tty0").
全域結構struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
struct console_cmdline
{
 char name[8];   /* Name of the driver     */
 int index;    /* Minor dev. to use     */
 char *options;   /* Options for the driver   */
};
第一次就會使console_cmdline[0]的name="ttyS",index=0,options=NULL
第二次就會使console_cmdline[1]的name="tty",index=0,options=NULL
並且最後preferred_console = 1 ,這些結構會在註冊控制台時用到.
接著下來還是回到start_kernel函數中,該函數調用了console_init()來初始化console
在該函數中調用了con_init()函數來初始化tty控制台,uart_console_init()初始化ttyS控制台
在con_init()函數中有
#ifdef CONFIG_VT_CONSOLE  //menuconfig的Character devices中選上Support for console...
 register_console(&vt_console_driver);
#endif
struct console vt_console_driver = {
 name:  "tty",
 write:  vt_console_print,
 device:  vt_console_device,
 wait_key: keyboard_wait_for_keypress,
 flags:  CON_PRINTBUFFER,
 index:  -1,
};
在register_console函數中會把name和前面賦值的console_cmdline[1]中的name比較,
 console->flags |= CON_ENABLED;
 console->index = console_cmdline[i].index;
 if (i == preferred_console)  console->flags |= CON_CONSDEV;
這樣其實在命令列最後的console的i才會等於preferred_console,所以它會是主控制台
 if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
  console->next = console_drivers;
  console_drivers = console;
 } else {
  console->next = console_drivers->next;
  console_drivers->next = console;
 }
會把控制台驅動組織成單鏈表格式,頭結點是全域指標struct console *console_drivers
它指向命令列參數最後的console,以後註冊的回插在其後
比如在我這裡就是console_drivers == vt_console_driver --> s3c2410_cons --> NULL
前面已經說過printk和register_console這兩個函數最後都會調用release_console_sem函數,
該函數最終調用了printk.c中的__call_console_drivers函數來列印字元。
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)
   con->write(con, &LOG_BUF(start), end - start);
 }
} 這個函數又調用了console_drivers中各個成員的write方法
對於vt_console_driver,其write方法就是vt_console_print,到這裡我們還需要回去看幾個
特別重要的結構,以便更好的理解這個函數。
在console.c的con_init函數中有如下代碼 
for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {
  vc_cons[currcons].d = (struct vc_data *)
    alloc_bootmem(sizeof(struct vc_data));
  vt_cons[currcons] = (struct vt_struct *)
    alloc_bootmem(sizeof(struct vt_struct));
  visual_init(currcons, 1);
  screenbuf = (unsigned short *) alloc_bootmem(screenbuf_size);
  kmalloced = 0;
  vc_init(currcons, video_num_lines, video_num_columns,
   currcons || !sw->con_save_screen); //sw=dummy_con
 }
 currcons = fg_console = 0;
 master_display_fg = vc_cons[currcons].d;

看看定義
#define MIN_NR_CONSOLES 1  
struct vc vc_cons [MAX_NR_CONSOLES];
struct vt_struct *vt_cons[MAX_NR_CONSOLES];
int fg_console;   fg_console is the current virtual console
static struct vc_data *master_display_fg;
這樣我們可以知道這段代碼首先分配了vc_cons[0].d的記憶體,vt_cons[0]的記憶體,
然後調用了visual_init來填充vc_cons[0].d結構
static void visual_init(int currcons, int init)
{
    /* ++Geert: sw->con_init determines console size */
    sw = conswitchp; //#define sw (vc_cons[currcons].d->vc_sw)
#ifndef VT_SINGLE_DRIVER //這裡sw就被賦值為上面介紹過的dummy_con了
    if (con_driver_map[currcons]) //第一次調用時是NULL
 sw = con_driver_map[currcons];
#endif
    cons_num = currcons;
    display_fg = &master_display_fg;
    vc_cons[currcons].d->vc_uni_pagedir_loc = &vc_cons[currcons].d->vc_uni_pagedir;
    vc_cons[currcons].d->vc_uni_pagedir = 0;
    hi_font_mask = 0;
    complement_mask = 0;
    can_do_color = 0;
    sw->con_init(vc_cons[currcons].d, init); //調用了dummycon_init
    if (!complement_mask) //can_do_color=1
        complement_mask = can_do_color ? 0x7700 : 0x0800;
    s_complement_mask = complement_mask;
    video_size_row = video_num_columns<<1; 
    screenbuf_size = video_num_lines*video_size_row;
}這裡的video_num_lines=80  video_num_columns=30是在dummycon_init調用中賦值的。
接下來分配一個80*30*2的記憶體screenbuf,是用來存放需要顯示到螢幕上的資料的,
當然由於剛開始我們的sw指向的是dummy_con,是丟棄控制台,其結構方法基本都是空操作,
所以並不會向螢幕上列印什麼。只有在後來初始化了lcd的驅動,sw會指向fb_con結構,那
才能真正在lcd屏上顯示。具體的在下面接著介紹。

回到vt_console_print函數中看,其中一段代碼
 while (count--) {
  c = *b++;
  if (c == 10 || c == 13 || c == 8 || need_wrap) {
   if (cnt > 0) {
    if (IS_VISIBLE)
     sw->con_putcs(vc_cons[currcons].d, start, cnt, y, x);
    x += cnt;
    if (need_wrap)
     x--;
    cnt = 0;
   }
   if (c == 8) {  /* backspace */
    bs(currcons);
    start = (ushort *)pos;
    myx = x;
    continue;
   }
   if (c != 13)
    lf(currcons);
   cr(currcons);
   start = (ushort *)pos;
   myx = x;
   if (c == 10 || c == 13)
    continue;
  }
  scr_writew((attr << 8) + c, (unsigned short *) pos);
  cnt++;
  if (myx == video_num_columns - 1) {
   need_wrap = 1;
   continue;
  }
  pos+=2;
  myx++;
 }
仔細分析一下可以知道,要顯示的字元會通過scr_writew儲存到vc_cons[0].d->vc_screenbuf
中pos指向的位置,當然儲存的是(attr << 8) + c的值,而如果字元是10 13 8或者一行滿的時候
則會調用sw->con_putcs方法把該行顯示至螢幕,對於dummy_con當然是什麼也不做了,而對於
fb_con則會在lcd上顯示。看到有個條件是 IS_VISIBLE
#define IS_VISIBLE CON_IS_VISIBLE(vc_cons[currcons].d)
#define CON_IS_VISIBLE(conp) (*conp->vc_display_fg == conp)
是要求*(vc_cons[currcons].d->vc_display_fg) == vc_cons[currcons].d
而上面在visual_init中有display_fg = &master_display_fg;
出來在con_init中又有master_display_fg = vc_cons[currcons].d;
於是其實master_display_fg = vc_cons[0].d
這樣對於currcons=0時 IS_VISIBLE 為真。

在沒有註冊vt_console_driver前調用printk函數,要列印的字元只會儲存到全域數組log_buf中,
而在註冊vt_console_driver時同樣會把以前儲存在log_buf中的列印出來.看代碼就知道了

接下來看看真正的lcd驅動是如何與控制台接軌的.
在drivers/video/s3c2410fb.c中找到lcd初始化的代碼
int __init s3c2410fb_init(void)
{
    struct s3c2410fb_info *fbi;
    int ret;
    fbi = s3c2410fb_init_fbinfo();
    ret = -ENOMEM;
    if (!fbi)
 goto failed;
    ret = s3c2410fb_map_video_memory(fbi);
    if (ret)
 goto failed;
    s3c2410_lcd_init();
    s3c2410fb_set_var(&fbi->fb.var, -1, &fbi->fb);
    ret = register_framebuffer(&fbi->fb);
    if (ret < 0)
        goto failed;
    printk("Installed S3C2410 frame buffer/n");
    MOD_INC_USE_COUNT ;
    return 0;
failed:
    if (fbi)
 kfree(fbi);
    return ret;
}
主要是分配和填充struct s3c2410fb_info結構體,其中細節的地方下一次再分析,
這裡主要看一下lcd驅動和控制台的關係.
在register_framebuffer(&fbi->fb)中調用了
take_over_console(&fb_con, first_fb_vc, last_fb_vc, fbcon_is_default);
這個函數會用fb_con替換dummy_con,具體看其中的代碼
void take_over_console(const struct consw *csw, int first, int last, int deflt)
{
 int i, j = -1;
 const char *desc;
 desc = csw->con_startup();
 if (!desc) return;
 if (deflt)
  conswitchp = csw; //這裡把fb_con賦給全域變數conswitchp
 for (i = first; i <= last; i++) {
  int old_was_color;
  int currcons = i;
  con_driver_map[i] = csw;
  if (!vc_cons[i].d || !vc_cons[i].d->vc_sw)
   continue;
  j = i;
  if (IS_VISIBLE)
   save_screen(i);
  old_was_color = vc_cons[i].d->vc_can_do_color;
  vc_cons[i].d->vc_sw->con_deinit(vc_cons[i].d);
  visual_init(i, 0); //這裡又調用visual_init
  update_attr(i);
  if (old_was_color != vc_cons[i].d->vc_can_do_color)
   clear_buffer_attributes(i);
  if (IS_VISIBLE)
   update_screen(i);
 }
 printk("Console: switching ");
 if (!deflt)
  printk("consoles %d-%d ", first+1, last+1);
 if (j >= 0)
  printk("to %s %s %dx%d/n",
         vc_cons[j].d->vc_can_do_color ? "colour" : "mono",
         desc, vc_cons[j].d->vc_cols, vc_cons[j].d->vc_rows);
 else
  printk("to %s/n", desc);
}

再次調用visual_init的時候把vc_cons[0].d->vc_sw賦值為fb_con,同時通過
sw->con_init(vc_cons[currcons].d, init)調用了fbcon_init函數
該函數又調用了fbcon_setup函數fbcon_setup(unit, init, !init)
在fbcon_setup中如果logo=1的話就會把screenbuf空間中的上面一塊
留給logo就是那個蜻蜓,又調用了vc_resize_con(nr_rows, nr_cols, con),
vc_resize_con會重新調整vc_cons[0].d->vc_screenbuf並把原來screenbuf中
對應的資料拷過來,原來是80*30,現在成了40*30,而上面的10*30留給了logo,
這樣只把原來最下面的30*30複製過來,通過調用update_screen(currcons)來
更新螢幕,update_screen調用fbcon_switch顯示logo,並顯示screen_buf中的資料.
take_over_console以後再調用printk函數就會調用fbcon_putcs真正實現把
螢幕顯示.

接下來看看幾個顯示裝置的區別吧,分別是tty,tty0,console,vc/0
在drivers/char/mem.c函數中找到chr_dev_init函數,其調用的tty_init在
tty_io.c中,該函數註冊了/dev/tty,/dev/console,/dev/vc/0這幾個裝置,
而/dev/tty0是通過mknod建立的節點,並不是devfs裝置.
他們都是通過tty_register_driver註冊的,而且其裝置方法集都是tty_fops
tty      5  0
console    5  1
tty0    4  0
vc/0    4  0
major和minor如上所示,這樣看來關鍵還是看tty_fops中的方法,先看tty_open
 device = inode->i_rdev;
 if (device == TTY_DEV) {
  if (!current->tty)
   return -ENXIO;
  device = current->tty->device; //開啟tty時用current->tty->device
  filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
  /* noctty = 1; */
 }
#ifdef CONFIG_VT
 if (device == CONSOLE_DEV) { //開啟tty0和vc/0時用device=0x401
  extern int fg_console;
  device = MKDEV(TTY_MAJOR, fg_console + 1);
  noctty = 1;
 }
#endif
 if (device == SYSCONS_DEV) {
  struct console *c = console_drivers;
  while(c && !c->device)
   c = c->next;
  if (!c)
                        return -ENODEV;
                device = c->device(c);
  filp->f_flags |= O_NONBLOCK; /* Don't let /dev/console block */
  noctty = 1;
 }
而開啟console時,調用了vt_console_device,使用串口控制台的話當然返回的是ttyS0的裝置號,這裡只介紹LCD控制台。
static kdev_t vt_console_device(struct console *c)
{
 return MKDEV(TTY_MAJOR, c->index ? c->index : fg_console + 1);
}
在我這裡返回的也是0x401
這樣看來tty0和vc/0和console甚至vc/1其device都是0x401
接下來調用了init_dev(device, &tty)分配填充tty結構,在init_dev函數中
driver = get_tty_driver(device)擷取對應裝置的驅動,0x401擷取的並不是
在tty_init中註冊的dev_tty_driver或dev_syscons_driver,而是在console.c
con_init中註冊的console_driver,可以看到這裡還同時要註冊vc/1-63裝置,不過
由於開始沒有支援devfs,其實是在tty_init中調用con_init_devfs來註冊的.
於是driver=&console_driver,這樣tty->driver=&console_driver,在tty_open
中有filp->private_data = tty,並通過tty->driver.open(tty, filp)調用了con_open函數.
重點就是要知道filp->private_data = tty,tty這個結構,在tty_read和tty_write中都用到
再來看tty_write函數
tty = (struct tty_struct *)file->private_data;
do_tty_write(tty->ldisc.write, tty, file,(const unsigned char *)buf, count);
而tty結構中的ldisc其實是在console_init函數中註冊的
tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY),就是tty_ldisc_N_TTY
其write方法就是write_chan,在drivers/char/N_tty.c中,事實上
在write_chan中還是通過tty->driver.write(tty, 1, b, nr)調用了console_driver的
write方法con_write,其又調用了do_con_write(tty, from_user, buf, count)
其中很關鍵的兩句代碼
struct vt_struct *vt = (struct vt_struct *)tty->driver_data;
currcons = vt->vc_num;
這樣就必須回去看看tty->driver_data是怎麼賦值的,這是在con_open中給出的
 currcons = MINOR(tty->device) - tty->driver.minor_start;
 i = vc_allocate(currcons);
 if (i)
  return i;
 vt_cons[currcons]->vc_num = currcons;
 tty->driver_data = vt_cons[currcons];
而console_driver.minor_start = 1
於是乎對於0x401裝置來說currcons=0,vt_cons[0]->vc_num = 0
tty->driver_data = vt_cons[0]
回到do_con_write中定義了
#define FLUSH if (draw_x >= 0) { /
 sw->con_putcs(vc_cons[currcons].d, (u16 *)draw_from, (u16 *)draw_to-(u16 *)draw_from, y, draw_x); /
 draw_x = -1; /
 }
通過FLUSH調用了fb_con的con_putcs方法把資料寫到lcd上去.
當然注意了這裡
 if (DO_UPDATE && draw_x < 0) {
  draw_x = x;
  draw_from = pos;
 }
#define DO_UPDATE IS_VISIBLE
如果IS_VISIBLE為0的話就沒法調用con_putcs更新螢幕了,
這也是為什麼寫vc/2等不能顯示的原因了. 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.