现在的位置: 首页 > 综合 > 正文

U-Boot串口初始化详解

2013年10月15日 ⁄ 综合 ⁄ 共 12665字 ⁄ 字号 评论关闭

零、概述


上面这张图是U-Boot中串口设备驱动的流程,从寄存器级别的设置到最后终端信息的输出。下面我们详细讲解每一个步骤。

一、init_baudrate

该函数设置了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);
}

二、serial_init

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);
}

三、console_init_f

控制台的前期初始化。

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);
}

四、devices_init或stdio_init

http://blog.csdn.net/ce123/article/details/7357045

五、console_init_r

控制台后期初始化。查看环境参数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

这说明串口初始化完成。

七、为什么要使用devlist,std_device[]?

为了更灵活地实现标准IO重定向,任何可以作为标准IO的设备,如USB键盘,LCD屏,串口等都可以对应一个device_t的结构体变量,只需要实现getc和putc等函数,就能加入到devlist列表中去,也就可以被assign为标准IO设备stdo_device中去。如函数 int console_assign (int file, char *devname); /* Assign the console 重定向标准输入输出*/这个函数功能就是把名为devname的设备重定向为标准IO文件file(stdin,stdout,stderr)。其执行过程是在devlist中查找devname的设备,返回这个设备的device_t指针,并把指针值赋给stdo_device

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 = 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
    DRAM:   8 MB
    Flash:  2 MB

于是在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

    DRAM:   8 MB

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的操作,问题解决。

抱歉!评论已关闭.