Bootstrap

uboot中串口(控制台)初始化详解

1、串口初始化函数调用关系

start.S
	_start
		reset
			lowlevel_init 
				uart_asm_init 	

1.1、uart_asm_init汇编函数

//根据配置文件中CONFIG_SERIALn[0:3]宏定义,决定初始化哪一个串口作为打印输出串口
#if defined(CONFIG_SERIAL1)
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART0_OFFSET)
#elif defined(CONFIG_SERIAL2)
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART1_OFFSET)
#elif defined(CONFIG_SERIAL3)
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART2_OFFSET) //X210开发板和S5PV210芯片都默认使用串口2
#elif defined(CONFIG_SERIAL4)
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART3_OFFSET)
#else
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART0_OFFSET)
#endif

uart_asm_init:

	/* 设置GPIO模式成UART模式 */
	ldr	r0, =ELFIN_GPIO_BASE
	ldr	r1, =0x22222222
	str   	r1, [r0, #GPA0CON_OFFSET]

	ldr     r1, =0x2222
	str     r1, [r0, #GPA1CON_OFFSET]

	//ELFIN_UART_CONSOLE_BASE = 要初始化的串口寄存器组的基地址
	ldr	r0, =ELFIN_UART_CONSOLE_BASE		
	mov	r1, #0x0
	str	r1, [r0, #UFCON_OFFSET]
	str	r1, [r0, #UMCON_OFFSET]

	/*接下来就是设置串口模式、波特率等,查看具体的寄存器说明*/
	mov	r1, #0x3
	str	r1, [r0, #ULCON_OFFSET]

	ldr	r1, =0x3c5
	str	r1, [r0, #UCON_OFFSET]

	ldr	r1, =UART_UBRDIV_VAL
	str	r1, [r0, #UBRDIV_OFFSET]

	ldr	r1, =UART_UDIVSLOT_VAL
	str	r1, [r0, #UDIVSLOT_OFFSET]

	ldr	r1, =0x4f4f4f4f
	str	r1, [r0, #UTXH_OFFSET]		@'O'

	mov	pc, lr //函数返回

2、将串口设备注册到设备链表

2.1、函数调用关系

start.S
	start_armboot()
		devices_init ()
			drv_system_init ()
				device_register(&dev)

2.2、全局变量devlist

devlist是一个链表节点,将来uboot中新增的设备都注册该链表;devlist是在devices_init()函数里初始化的。

2.3、设备结构体

/* Device information */
typedef struct {
	int	flags;			/* Device flags: input/output/system	*/
	int	ext;			/* Supported extensions			*/
	char	name[16];		/* Device name				*/

/* GENERAL functions */

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

/* OUTPUT functions */

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

/* INPUT functions */

	int (*tstc) (void);		/* To test if a char is ready...	*/
	int (*getc) (void);		/* To get that char			*/

/* Other functions */

	void *priv;			/* Private extensions			*/
} device_t;

这个结构体是用来描述一个输入输出设备的,putc、getc······指针将来要指向实际完成输入输出功能的函数,在构建设备结构体时会赋值。

2.4、drv_system_init函数

static void drv_system_init (void)
{
	device_t dev;

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

	strcpy (dev.name, "serial");

	//标志位标明该设备的用途,在初始化stdio_devices[]时会用到
	dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;

	//构建设备结构体
	dev.putc = serial_putc;
	dev.puts = serial_puts;
	dev.getc = serial_getc;
	dev.tstc = serial_tstc;

	//注册串口设备到设备链表
	device_register (&dev);	
}

2.5、device_register()函数

int device_register (device_t * dev)
{
	ListInsertItem (devlist, dev, LIST_END);
	return 0;
}

将新的设备结构体注册到devlist链表中。

3、全局变量stdio_devices[]

3.1、函数调用关系

start.S
	start_armboot()
		console_init_f()	//控制台第一阶段初始化
		console_init_r()	//控制台第二阶段初始化
			ListGetPtrToItem (devlist, i) //遍历已经注册的设备结构体
			console_setfile()	//将设备注册到stdio_devices[ ]

3.2、全局变量stdio_devices[]介绍

device_t *stdio_devices[] = { NULL, NULL, NULL };

是一个结构体数组,三个成员变量都是device_t结构体,对应stdin、stdout、stderr。

3.3、console_init_r()函数

int console_init_r (void) //实际使用
{
	device_t *inputdev = NULL, *outputdev = NULL;
	int i, items = ListNumItems (devlist);

	/* 在设备链表中根据标志位查找标准输入、标准输出设备 */
	for (i = 1;
	     (i <= items) && ((inputdev == NULL) || (outputdev == NULL));
	     i++
	    ) {
		device_t *dev = ListGetPtrToItem (devlist, i);

		if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
			inputdev = dev;
		}
		if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
			outputdev = dev;
		}
	}

	/* 将找到的标准输出设备结构体注册到stdio_devices[]全局变量中,其中标准输出和标准出错是同一个设备 */
	if (outputdev != NULL) {
		console_setfile (stdout, outputdev);
		console_setfile (stderr, outputdev);
	}

	/* 将找到的标准输入设备结构体注册到stdio_devices[]全局变量中 */
	if (inputdev != NULL) {
		console_setfile (stdin, inputdev);
	}

	/*修改gd全局变量的标准,表示设备初始化完成*/
	gd->flags |= GD_FLG_DEVINIT;	/* device initialization completed */

	/* 打印输入、输出、出错都是什么类型的设备 */
	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);
	}

	return (0);
}

3.4、console_setfile()函数

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

		/* 将设备结构体注册到stdio_devices[] */
		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;
}

初始化全局变量stdio_devices,stdin、stdout、stderr都有对应的设备结构体(一般三者都是指向同一个串口),调用printf()函数时会用到。

4、printf()函数用串口输出打印信息

参考博客:《uboot中printf( )函数实现分析》

5、修改打印输出串口

#if defined(CONFIG_SERIAL1)
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART0_OFFSET)
#elif defined(CONFIG_SERIAL2)
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART1_OFFSET)
#elif defined(CONFIG_SERIAL3)//X210开发板和S5PV210芯片都默认使用串口2
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART2_OFFSET) 
#elif defined(CONFIG_SERIAL4)
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART3_OFFSET)
#else
#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART0_OFFSET)
#endif

#define CONFIG_SERIAL3          1	/* we use UART2 on SMDKC110 */

修改配置文件中的CONFIG_SERIALn[0:3]宏,比如定义CONFIG_SERIAL3就是代表使用串口2作输出。思路就是通过宏定义控制作为打印信息输出串口的寄存器组基地址。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;