转载:http://www.eefocus.com/lishutong/blog/13-06/295362_c25c3.html#articletop
之前简单实现过s5pv210的串口驱动,能正常使用,不过只能支持一个串口。串口驱动的实现还是比较简单的。
UBoot的Serial驱动主要功能有如下几点:
不过,现在新出的电脑很多都不再提供串口,所以需要自己配一个USB转RS232的转接线。质量建议选好的,相应的驱动也要装个稳定点的,否则一不小心电脑就容易蓝屏死机,我是遇到过好多次这种情况。
从uboot官网上下载的最新uboot源码中已经提供了s5pv210的serial驱动,所以不需要自己写,简单的改了改uboot配置文件就可以在tiny210开发板上用。所以下面的内容不谈移植,主要了解uboot对串口是如何处理的。
所有涉及的文件为driver/serial/{serial.c、serial_s5p.c}。
UBoot的串口设备抽像结构及操作接口(serial.c)
UBoot将串口设备抽像为struct serial_device,这个结构包含了所有串口的基本操作接口。接口实现比较简单,无非是初始化、反初始化、接收发送字符/字符串、设置波特率。而且由于uboot本身就是一个前后台程序,所以在实现这些操作接口时跟我们平时写前后台的串口驱动代码没有任何区别。
struct serial_device {
char name[16];int (*start)(void);
int (*stop)(void);
void (*setbrg)(void);
int (*getc)(void);
int (*tstc)(void);
void (*putc)(const char c);
void (*puts)(const char *s);
#if CONFIG_POST & CONFIG_SYS_POST_UART
void (*loop)(int);
#endif
struct serial_device *next;
};
然后,UBoot定义了两个指针和一个注册函数:
void serial_register(struct serial_device *dev)
static struct serial_device *serial_devices;
static struct serial_device *serial_current;
这样,如果系统中如果有多个设备的话,就会逐个通过serial_register(dev)加入到一个串口设备链表中。 每个设备都有一个名称,当需要切换当前的串口设备时,则调用serial_assign(name)。
所有的设备都在serial_initialize()中注册到该链表中。
这个链表没有提供删除操作,对于一个bootloader而言,这个功能没必要。个人甚至认为在绝大数情况下,多串口的支持也不是必要的。
在选定当前使用的设备后,就可以调用下面的操作接口来完成通信操作。这些接口调用了struct serial_device结构中的底层接口:
s5pv210的串口驱动实现(serial_s5p.c)
uboot的串口初始化机制有些奇怪,除了使用了很多GCC扩展特性外,代码写的比较糟糕。
一般来说,无论是支持什么设备,driver/serial.c这个文件是不应该修改的,它应当被设计成跟具体的设备无关。但是它采用了下面的实现代码:
void serial_initialize(void)
{
mpc8xx_serial_initialize();
ns16550_serial_initialize();
pxa_serial_initialize();
s3c24xx_serial_initialize(); // s3c2400的串口初始化
s5p_serial_initialize(); // s5pv210的串口初始化………………………………………………..
serial_assign(default_serial_console()->name);
}
也就是说,serial_initialize包含了uboot能支持的各种mcu串口初始化函数。如果你想支持一种新的MCU,则必须修改serial.c文件,将初始化函数加入到serial_initialize中。
serial_initialize中虽然包含了很多初始化函数,但不是每个函数都需要加入到编译中。在针对s5pv210的编译中,其它的诸如mpc8xx_serial_initialize()之类调用,会被替换成调用空的serial_null函数。
#define serial_initfunc(name) \
void name(void) \
__attribute__((weak, alias("serial_null")));serial_initfunc(mpc8xx_serial_initialize);
s5pv210有多个串口,所以s5p_serial_initialize()主要完成的操作是注册多个struct serial_device结构。当然,可以只注册需要使用的其中一个。
void s5p_serial_initialize(void)
{
serial_register(&s5p_serial0_device);
serial_register(&s5p_serial1_device);
serial_register(&s5p_serial2_device);
serial_register(&s5p_serial3_device);
}
因为各个串口的操作方式都差不多,只不过寄存器的地址不同,所以使用同样的底层接口函数,通过dev_index来区分当前所使用的设备。
之后便是用这些函数来填充多个s5p_serialx_device结构,源码中使用了C语言的宏特性,避免反复写很类似的代码。不过看起来比较费劲,可读性不强。之后就是调用前面提到的s5p_serial_initialize来完成设备结构注册。
/* Multi serial device functions */
#define DECLARE_S5P_SERIAL_FUNCTIONS(port) \
int s5p_serial##port##_init(void) { return serial_init_dev(port); } \
void s5p_serial##port##_setbrg(void) { serial_setbrg_dev(port); } \
int s5p_serial##port##_getc(void) { return serial_getc_dev(port); } \
int s5p_serial##port##_tstc(void) { return serial_tstc_dev(port); } \
void s5p_serial##port##_putc(const char c) { serial_putc_dev(c, port); } \
void s5p_serial##port##_puts(const char *s) { serial_puts_dev(s, port); }#define INIT_S5P_SERIAL_STRUCTURE(port, __name) { \
.name = __name, \
.start = s5p_serial##port##_init, \
.stop = NULL, \
.setbrg = s5p_serial##port##_setbrg, \
.getc = s5p_serial##port##_getc, \
.tstc = s5p_serial##port##_tstc, \
.putc = s5p_serial##port##_putc, \
.puts = s5p_serial##port##_puts, \
}DECLARE_S5P_SERIAL_FUNCTIONS(0);
struct serial_device s5p_serial0_device =
INIT_S5P_SERIAL_STRUCTURE(0, "s5pser0");
DECLARE_S5P_SERIAL_FUNCTIONS(1);
struct serial_device s5p_serial1_device =
INIT_S5P_SERIAL_STRUCTURE(1, "s5pser1");
DECLARE_S5P_SERIAL_FUNCTIONS(2);
struct serial_device s5p_serial2_device =
INIT_S5P_SERIAL_STRUCTURE(2, "s5pser2");
DECLARE_S5P_SERIAL_FUNCTIONS(3);
struct serial_device s5p_serial3_device =
INIT_S5P_SERIAL_STRUCTURE(3, "s5pser3");
这些源码中,我认为比较有价值的是UBoot将串口设备抽像为struct serial_device,以及对多串口设备的支持。之前在别的地方也看到不过类似的思路,值得借鉴。