上面这张图是U-Boot中串口设备驱动的流程,从寄存器级别的设置到最后终端信息的输出。下面我们详细讲解每一个步骤。
该函数设置了gd->bd->bi_baudrate。
static int init_baudrate (void) { char tmp[64]; /* long enough for environment variables */ int i = getenv_r ("baudrate", tmp, sizeof (tmp)); gd->bd->bi_baudrate = gd->baudrate = (i > 0) ? (int) simple_strtoul (tmp, NULL, 10) : CONFIG_BAUDRATE; //#define CONFIG_BAUDRATE 115200 定义在/include/configs/smdk2410.c中 //如果环境中没有保存,则使用宏定义的参数 return (0); }
UART控制器的初始化。
void serial_setbrg (void) { S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR); int i; unsigned int reg = 0; /* value is calculated so : (int)(PCLK/16./baudrate) -1 */ reg = get_PCLK() / (16 * gd->baudrate) - 1; /* FIFO enable, Tx/Rx FIFO clear */ uart->UFCON = 0x07; uart->UMCON = 0x0; /* Normal,No parity,1 stop,8 bit */ uart->ULCON = 0x3; /* * tx=level,rx=edge,disable timeout int.,enable rx error int., * normal,interrupt or polling */ uart->UCON = 0x245; uart->UBRDIV = reg; #ifdef CONFIG_HWFLOW uart->UMCON = 0x1; /* RTS up */ #endif for (i = 0; i < 100; i++); } /* * Initialise the serial port with the given baudrate. The settings * are always 8 data bits, no parity, 1 stop bit, no start bits. * */ int serial_init (void) { serial_setbrg ();//UART寄存器设置 return (0); }
控制台的前期初始化。
int console_init_f (void) { gd->have_console = 1; #ifdef CONFIG_SILENT_CONSOLE if (getenv("silent") != NULL) gd->flags |= GD_FLG_SILENT; #endif return (0); }
http://blog.csdn.net/ce123/article/details/7357045
控制台后期初始化。查看环境参数stdin,stdout,stderr中对标准IO的指定的设备名称,如果没有知道再按照环境指定的名称搜索devlist,将搜到的设备指针赋给标准IO数组stdio_devices[]。
int console_init_r (void) { char *stdinname, *stdoutname, *stderrname; device_t *inputdev = NULL, *outputdev = NULL, *errdev = NULL; #ifdef CFG_CONSOLE_ENV_OVERWRITE int i; #endif /* CFG_CONSOLE_ENV_OVERWRITE */ /* set default handlers at first 设置跳转表*/ gd->jt[XF_getc] = serial_getc; gd->jt[XF_tstc] = serial_tstc; gd->jt[XF_putc] = serial_putc; gd->jt[XF_puts] = serial_puts; gd->jt[XF_printf] = serial_printf; /* stdin stdout and stderr are in environment 查看环境参数stdin,stdout,stderr中对标准IO的指定的设备名称*/ /* scan for it */ stdinname = getenv ("stdin"); stdoutname = getenv ("stdout"); stderrname = getenv ("stderr"); if (OVERWRITE_CONSOLE == 0) { /* if not overwritten by config switch */ inputdev = search_device (DEV_FLAGS_INPUT, stdinname); outputdev = search_device (DEV_FLAGS_OUTPUT, stdoutname); errdev = search_device (DEV_FLAGS_OUTPUT, stderrname); } /* 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"); } /* Initializes output console first将搜到的设备指针赋给标准IO数组stdio_devices[],在下面会将为什么要这样做 */ if (outputdev != NULL) { console_setfile (stdout, outputdev); } if (errdev != NULL) { console_setfile (stderr, errdev); } if (inputdev != NULL) { console_setfile (stdin, inputdev); } gd->flags |= GD_FLG_DEVINIT; /* device initialization completed,到此串口设备才初始化完成,这个标志会影响getc等函数 */ #ifndef CFG_CONSOLE_INFO_QUIET /* Print information 打印信息*/ puts ("In: "); if (stdio_devices[stdin] == NULL) { puts ("No input devices available!\n"); } else { printf ("%s\n", stdio_devices[stdin]->name); } puts ("Out: "); if (stdio_devices[stdout] == NULL) { puts ("No output devices available!\n"); } else { printf ("%s\n", stdio_devices[stdout]->name); } puts ("Err: "); if (stdio_devices[stderr] == NULL) { puts ("No error devices available!\n"); } else { printf ("%s\n", stdio_devices[stderr]->name); } #endif /* CFG_CONSOLE_INFO_QUIET */ #ifdef CFG_CONSOLE_ENV_OVERWRITE /* set the environment variables (will overwrite previous env settings) */ for (i = 0; i < 3; i++) { setenv (stdio_names[i], stdio_devices[i]->name); } #endif /* CFG_CONSOLE_ENV_OVERWRITE */ #if 0 /* If nothing usable installed, use only the initial console */ if ((stdio_devices[stdin] == NULL) && (stdio_devices[stdout] == NULL)) return (0); #endif return (0); }
在最后会打印出如下信息:
In: serial
Out: serial
Err: serial
这说明串口初始化完成。
int console_assign (int file, char *devname) { int flag, i; /* 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 */ for (i = 1; i <= ListNumItems (devlist); i++) { device_t *dev = ListGetPtrToItem (devlist, i); if (strcmp (devname, dev->name) == 0) { if (dev->flags & flag) return console_setfile (file, dev); return -1; } } return -1; }
该函数是调用console_setfile设置stdo_device[]完成重定向输入输出。
static int console_setfile (int file, device_t * 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 (); /* 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[XF_getc] = dev->getc; gd->jt[XF_tstc] = dev->tstc; break; case stdout: gd->jt[XF_putc] = dev->putc; gd->jt[XF_puts] = dev->puts; gd->jt[XF_printf] = printf; break; } break; default: /* Invalid file ID */ error = -1; } return error; }------------------------------------------------------------------------------------------------------------------------
u-boot中控制台的初始化 |
在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数组中。 |
u-boot 中bss段未清0带来的问题 |
最近为s3c44b0编译了一个u-boot,在用AXD软件调试时出现一个很棘手的问题,串口有输出但不能进入命令行状态。 U-Boot 2009.08-rc1 (Oct 20 2009 – 15:51:46) I2C: ready 于是在board.c中flash初始化后调用的每个初始化函数后面加了puts调试语句,在console_init_r后面的puts语句都没有显示出来,而是在问题定位在console_init_r中。进入到该函数同样在几个关键的地方加入puts进行调试,结果发现在语句 gd->flags |= GD_FLG_DEVINIT; 后面的调试信息都没有显示出来。这条语句影响putc,getc函数的实现,未定义此标志时直接由串口serial_getc和serial_putc实现,定义以后通过标准设备数组中的putc和 getc来实现IO。在我写的一篇关于控制台初始化的文章中有对这个部分的分析。在置标志位之前所做的工作就是从设备列表中搜寻可用的I/O设备,并将设备指针赋给标准I/O数组。因此我们加入判断搜寻到得设备的调试信息。 669 list_for_each(pos, list) { 671 dev = list_entry(pos, struct stdio_dev, list); 672 printf("dev 0x%x\n",dev); //debug ...... 685 if (outputdev != NULL) { 686 console_setfile(stdout, outputdev); 687 console_setfile(stderr, outputdev); ...... 692 } 693 printf("function pointer, puts 0x%x\n",&(outputdev->puts)); //debug 串口输出为: U-Boot 2009.08-rc1 (Oct 20 2009 – 15:51:46) I2C: ready Flash: 2 MB dev=0 function pointer, puts 0×24 dev=0,说明没有可用的I/O设备注册到设备列表中,而且puts 0×24只是一个相对偏移量,不是一个可用的32位地址,问题出在没有将设备注册到设备列表中,默认情况下设备列表中注册的只有串口设备。在I/O设备的创建和初始化stdio_init中调用drv_system_init ()函数注册串口设备到设备列表中。drv_system_init又调用stdio_register() 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 } 在初始化好的devs中添加_dev设备,于是根据stdio_clone()函数跟踪_dev的由来: 141 _dev = calloc(1, sizeof(struct stdio_dev)); 147 memcpy(_dev, dev, sizeof(struct stdio_dev)); _dev申请的是一块动态内存,然后将dev中的内容拷贝到_dev中,在141行后此加入调试信息: 143 printf("dev=%x\n",_dev); 结果从串口得到_dev的值为零。到此算是有点眉目了,于是在cpu/s3c44b0/start.s中查看堆分配的情况: 157 stack_setup: 158 ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ 159 sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */ 160 sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo 在u-boot启动的第一阶段已经在代码段起始地址往下分配了CONFIG_SYS_MALLOC_LEN大小的堆空间,该宏在 在uptech44b0中定义,将其适当增大为132KB的空间,下载到ram中调试,问题依然没有解决。 排除堆空间的问题后那就只能是堆分配函数本身的问题了。在google里搜索了一下,看到有人遇到同样的问题。 根本原因在于bss段没有清零,结果便会导致所以依赖于bss段赋初值为0的函数发生问题,而在dmalloc.c中有一个变量 top_pad默认情况下为0,由于未清除bss段,导致top_pad值为一随机数,最终malloc分配堆失败。于是在start.s函 数中跳转到start_armboot语句前加入将bss段清0的操作,问题解决。 |