Uboot中控制台的前期初始化

来源:互联网 发布:邱泽 知乎 编辑:程序博客网 时间:2024/06/04 23:40

在u-boot完成第一阶段基本的硬件初始化、重定位代码、建立堆栈,清除bss段后,将会跳转到start_armboot中执行.这是u-boot

执行的第一段C语言代码,完成系统的初始化工作,进入主循环,处理用户输入的命令。

在这个初始化过程中,start_armboot首先会根据结构体变量init_sequence[]定义的顺序执行初始化的工作,下面以U-Boot

2009.08-rc1中S3C44B0为例分析与控制台相关的初始化工作。

 

1 控制台的前期初始化

在start_armboot中执行serial_init初始化串口后,将会调用console_init_f进行控制台前期的初始化操作,这时候标准设备还没有

初始化,于是使用串口作为控制台的输入输出。console_init_f执行如下语句:

   gd->have_console = 1;

gd是全局结构体变量,成员have_console=1表明将串口作为控制台的输入输出设备。

 

2 设备初始化

在完成控制台的前期初始化工作后,将会初始化设备表,并按编译选项注册特定的设备到设备列表中,这里用到一个重

要的结构体变量:

struct stdio_dev {

        int     flags;                  /* Device flags:input/output/system    */

        int     ext;                    /* Supported extensions                 */

        char    name[16];               /* Device name                          */

        int (*start) (void);            /* To start the device                  */

        int (*stop) (void);             /* To stop the device                   */

        void (*putc) (const char c);    /* To put a char                        */

        void (*puts) (const char *s);   /* To put a string (accelerator)        */

        void *priv;                     /* Private extensions                   */

        struct list_head list;

};

struct list_head {

        struct list_head *next, *prev;

};

结构体stdio_dev为创建的设备表的类型定义,其成员函数将在下面用到的地方加以说明。这个过程会进入common/stdio.c中调用

stdio_init()函数。

39 static struct stdio_dev devs;

 

204 int stdio_init (void)

205 {

         ......

217         /* Initialize the list */

218         INIT_LIST_HEAD(&(devs.list));

219

         ......

238         drv_system_init ();

239 #ifdef CONFIG_SERIAL_MULTI

240         serial_stdio_init ();

241 #endif

         ......

252         return (0);

253 }

在218行以devs.list为参数,初始化设备列表。这里的devs.list是一个struct list_head 类型的双向链表,初始化

的操作就是将(devs.list)->next和(devs.list)->prev都指向devs.list本身。初始化设备列表后,将会根据所定义

宏变量将指定的设备作为输入输出设备注册到设备列表中。 默认情况下会调用drv_system_init()函数将串口设备

到devs中。

 71 static void drv_system_init (void)

 72 {

 73         struct stdio_dev dev;

 74

 75         memset (&dev, 0, sizeof (dev));

 76

 77         strcpy (dev.name, "serial");

 78         dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;

            ......

 85         dev.putc = serial_putc;

 86         dev.puts = serial_puts;

 87         dev.getc = serial_getc;

 88         dev.tstc = serial_tstc;

 

 90         stdio_register (&dev);

            ......

105 }

将串口名,串口的输入输出函数的指针都传递给dev相应的成员变量,并置设备标志dev.flags。接着调用

stdio_register (&dev)函数进行注册。

153 int stdio_register (struct stdio_dev * dev)

154 {

155         struct stdio_dev *_dev;

156

157         _dev = stdio_clone(dev);

158         if(!_dev)

159                 return -1;

160         list_add_tail(&(_dev->list), &(devs.list));

161         return 0;

162 }

stdio_clone(dev)函数首先申请sizeof(stdio_dev)大小的动态内存空间并返回起始地址_dev,然后将dev中的

内容拷贝到_dev对应的成员变量中,注册的最后一步激将调用list_add_tail函数,这里所做所做的插入操作是通

过dev的list的prev和next将_dev和初始化的devs链接起来,形成一个双向的循环链表。如下图所示:左上为初始

化后的状态,右上为注册的串口设备的结构体_dev,下面为加入注册后设备列表的情况。至此完成设备的初始化和

设备的注册。

 

 

3 控制台的后期初始化

这个过程调用console_init_r()函数,主要完成的工作将扫描设备表中注册的设备,并将扫描到得设备和控制台绑定,

以从特定的设备完成输入输出。下面分析其具体过程:

651 int console_init_r(void)

652 {

            ......

655         struct list_head *list = stdio_get_list();

 

            ......

667

668         /* Scan devices looking for input and output devices */

669         list_for_each(pos, list) {

670                 dev = list_entry(pos, struct stdio_dev, list);

671

672                 if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {

673                         inputdev = dev;

674                 }

675                 if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {

676                         outputdev = dev;

677                 }

678                 if(inputdev && outputdev)

679                         break;

680         }

682         /* Initializes output console first */

683         if (outputdev != NULL) {

684                 console_setfile(stdout, outputdev);

685                 console_setfile(stderr, outputdev);

690         }

691

692         /* Initializes input console */

693         if (inputdev != NULL) {

694                 console_setfile(stdin, inputdev);

698         }

700         gd->flags |= GD_FLG_DEVINIT; 

首先调用stdio_get_list()取得设备列表,返回devs.list的指针。接着调用宏list_for_each(pos, list),它是

一个for循环

193 #define list_for_each(pos, head) \

194         for (pos = (head)->next, prefetch(pos->next); pos != (head); \

195                 pos = pos->next, prefetch(pos->next))

   prefetch(x)为1不用考虑,这里就是根据devs.list扫描设备表,因为前面在注册设备时也是通过devs.list的prev

和next添加设备到设备列表的。进入循环后调用list_entry()将pos当前地址减去list字段在devs中的偏移量得到dev

的起始地址,接着判断扫描到的设备的flags字段,如果置了输入输出标志则将扫描到的设备作为输入输出设备。输入输

出设备找到后退出循环,否则将扫描整个设备列表都没有找到可用的设备为止。

当找到可用的输入输出设备后,程序将会执行console_setfile()函数,将搜到的设备指针赋给标准I/O数组,完成控制

台的初始化。

 77                 switch (file) {

 78                 case stdin:

 79                         gd->jt[XF_getc] = dev->getc;

 80                         gd->jt[XF_tstc] = dev->tstc;

 81                         break;

 82                 case stdout:

 83                         gd->jt[XF_putc] = dev->putc;

 84                         gd->jt[XF_puts] = dev->puts;

 85                         gd->jt[XF_printf] = printf;

 86                         break;

 87                 }

 88                 break;

 89

最后置gd->flag标志GD_FLG_DEVINIT。这个标志影响putc,getc函数的实现,未定义此标志时直接由串口

serial_getc,serial_putc实现,定义以后通过标准设备数组中的 putc和getc来实现IO。

因此使用devlist,标准I/O可以更灵活地实现标准IO重定向,任何可以作为标准IO的设备, 都可以对应一个stdio_dev的结构体变

量,只需要实现getc和putc等函数,就能加入到设备列表中去, 也就可以被assign为标准IO设备数组中去。

如函数 int console_assign (int file, char *devname); 这个函数功能就是把名为devname的设备重定向为标准IO文件file,其执行

过程是在设备列表中查找devname的设备,返回这个设备的stdio_dev指针,并把指针值赋给标准I/O数组中。

         

 

原创粉丝点击