u-boot串口和stdio、console初始化及相关操作详解<二>
2017-12-09 11:28
357 查看
二.board_r阶段串口操作和stdio初始化
该阶段包括3个函数:stdio_init_tables、 initr_serial、stdio_add_devices。下面逐一对其进行详细说明。
1. stdio_init_tables
[cpp] view
plain copy
int stdio_init_tables(void)
{
/* Initialize the list */
INIT_LIST_HEAD(&(devs.list));
return 0;
}
INIT_LIST_HEAD为include/linux/List.h中的内联函数:
[cpp] view
plain copy
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
devs在stdio.c中定义:
static struct stdio_dev devs;
其中结构体struct stdio_dev定义在include/Stdio_dev.h中:
[cpp] view
plain copy
struct stdio_dev {
int flags; /* Device flags: input/output/system */
int ext; /* Supported extensions */
char name[32]; /* Device name */
/* GENERAL functions */
int (*start)(struct stdio_dev *dev); /* To start the device */
int (*stop)(struct stdio_dev *dev); /* To stop the device */
/* OUTPUT functions */
/* To put a char */
void (*putc)(struct stdio_dev *dev, const char c);
/* To put a string (accelerator) */
void (*puts)(struct stdio_dev *dev, const char *s);
/* INPUT functions */
/* To test if a char is ready... */
int (*tstc)(struct stdio_dev *dev);
int (*getc)(struct stdio_dev *dev); /* To get that char */
/* Other functions */
void *priv; /* Private extensions */
struct list_head list;
};
include/linux/List.h中有:
[cpp] view
plain copy
struct list_head {
struct list_head *next, *prev;
};
struct stdio_dev代表对stdio设备的描述,当有多个stdio设备时,将使用其成员变量list将这些stdio设备链接起来。这里,函数stdio_init_tables对该链表进行了初始化。注意这里的链表机制是在struct stdio_dev结构体中嵌入一个楔子struct
list_head list,list有两个成员*next和*prev,分别指向上一个和下一个struct list_head变量,而该变量则是struct stdio_dev的成员,*next和*prev并不指向struct stdio_dev结构体变量。
这和struct serial_device中的*next成员不同,后者指向下一个struct serial_device结构体变量,即下一个serial设备。
由struct list_head list组成的链表可称为stdio设备的维护链表。u-boot维持着devs这样一个全局变量,它始终指向stdio设备维护链表struct list_head的头部,u-boot通过这样一个链表维护着所有的stdio设备。
下面我们还会看到另一个全局变量serial_devices,它始终指向serial设备链表的头部。同样,所有注册的serial设备通过这样一个链表串接起来。
2. initr_serial
initr_serial为静态函数,它调用了drivers/serail/serail.c中的serial_initialize,该函数实现如下:
[cpp] view
plain copy
void serial_initialize(void)
{
altera_jtag_serial_initialize();
altera_serial_initialize();
...
mxc_serial_initialize();
...
serial_assign(default_serial_console()->name);
}
serial_initialize调用的函数列表中,末尾serial_assign除外,所有的函数都是weak类型,即其可能在它处实现,但如果没有实现,
会被函数serial_null替换。serial_null是serial.c中的静态函数,该函数中没有任何代码。
如altera_jtag_serial_initialize在它处没有实现,那么它将被alias("serial_null"),即被serial_null替换。
上面的函数列表是针对不同的SOC,有各自的实现。单独针对某一SOC时,只有一个函数得到具体的实现,此处针对I.MX6的CPU,
查看其目录下的Makefiel文件,根据配置可以看出最终使用的是文件drivers/serial/Serial_mxc.c中的mxc_serial_initialize:
[cpp] view
plain copy
void imx_serial_initialize(void)
{
serial_register(&mxc_serial_drv);
}
serial_register在drivers/serial/serial .c中实现:
[cpp] view
plain copy
void serial_register(struct serial_device *dev)
{
dev->next = serial_devices;
serial_devices = dev;
}
serial_devices在serial.c中被定义为静态变量:
static struct serial_device *serial_devices;
static struct serial_device *serial_current;
结构体struct serial_device的最后一个成员是struct serial_device *next。
执行完serial_register后,dev->next被赋值为serial_devices的初始值,即变量定义时默认的初始值0,
serial_devices后被赋值为dev,即传入的参数&mxc_serial_drv。该结构体在上面board_init_f阶段的分析中讨论过,
这里,为了便于分析,再次列出它的定义和初始化:
[cpp] view
plain copy
static struct serial_device mxc_serial_drv = {
.name = "mxc_serial",
.start = mxc_serial_init,
.stop = NULL,
.setbrg = mxc_serial_setbrg,
.putc = mxc_serial_putc,
.puts = default_serial_puts,
.getc = mxc_serial_getc,
.tstc = mxc_serial_tstc,
};
新加入serial的设备总是插入到serial_devices指向的serial设备链表的头部,而后serial_devices指向该新serial设备。
这样,所有注册过的serial设备通过设备结构成员next链接成一个设备链表。最后,全局变量serial_devices指向该链表的表头。
在函数serial_initialize中,执行完所有有效的serial设备的注册后,最后执行serial_assign:
[cpp] view
plain copy
int serial_assign(const char *name)
{
struct serial_device *s;
for (s = serial_devices; s; s = s->next) {
if (strcmp(s->name, name))
continue;
serial_current = s;
return 0;
}
return -EINVAL;
}
当某一SOC包含有多个可被u-boot使用的串口时,只要创建一个针对某串口的initialize函数,并包含对该串口如上类似的注册操作,把它加入到serial_initialize的执行函数列表中,就可被使用。所谓的串口链表也是针对多个串口的上述情况。但只有一个是被当前使用的,serial_assign函数就是指定以其入参为名字的串口为当前设备,该函数中的全局变量serial_current最后指向一个有效的当前串口设备。
3. stdio_add_devices
函数stdio_add_devices执行stdio设备的注册。stdio设备层工作在serail层之上,但stdio设备并不限于serial设备,比如标准输出设备可能包括显示器。但我们这里只讨论stdio中的serail设备。
stdio_add_devices在common/stdio.c中实现:
[cpp] view
plain copy
int stdio_add_devices(void)
{
i2c_init_all();
drv_video_init ();
drv_system_init ();
serial_stdio_init ();
drv_nc_init ();
return 0;
}
上述函数中注册为stdio的设备包括I2c,video,system,serial,以及netcommand设备。
其中serial设备到stdio的注册通过函数serial_stdio_ini来实现,其在drivres/serial/serial.c中定义为:
[cpp] view
plain copy
void serial_stdio_init(void)
{
struct stdio_dev dev;
struct serial_device *s = serial_devices;
while (s) {
memset(&dev, 0, sizeof(dev));
strcpy(dev.name, s->name);
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;
dev.start = serial_stub_start;
dev.stop = serial_stub_stop;
dev.putc = serial_stub_putc;
dev.puts = serial_stub_puts;
dev.getc = serial_stub_getc;
dev.tstc = serial_stub_tstc;
dev.priv = s;
stdio_register(&dev);
s = s->next;
}
}
在上面的initr_serial函数一节中,我们已经讨论过serail设备链表,它是已被注册的SOC层serial设备的集合。
这里的全局变量serial_devices指向serial设备链表的表头, 而后使用while循环用来遍历该设备链表并执行如下操作:
a).将serial设备的设备名拷贝到stdio设备的设备名中
b).我们知道stdio设备包含stdin,stdout,stderr三种设备,dev.flags即为这种设备属性区分标志。
c).添加stdio设备的ops函数。
d). 语句dev.priv = s将serial设备指针赋值给stdio设备结构体的成员变量priv。
e). 注册stdio 设备。
struct stdio_dev成员变量priv(私有的)是访问下层设备---这里是serial层----的媒介。通过priv变量,就可以访问stdio包含
的下层设备。由于stdio设备并不只包含serial设备,所以该变量定义为void *类型。
我们在board_init_f阶段阶段的讨论中曾经提到过,在serial层,提供了诸如serial_puts,serial_getc的外部访问接口。
而针对上层的stdio设备,serial层提供的外部接口则是名为serial_stub_xx的桩函数。
如serial_stub_putc在drivres/serial/serial.c中实现为:
[cpp] view
plain copy
static void serial_stub_putc(struct stdio_dev *sdev, const char ch)
{
struct serial_device *dev = sdev->priv;
dev->putc(ch);
}
如前所述,sdev->priv为serial设备指针,后者在serail初始化(注册)时被赋值为SOC层的设备指针,这样,最终的dev->putc将指向了
SOC层中的相关输出函数。
最后使用stdio_register函数,完成serial设备到stdio的注册:
[cpp] view
plain copy
int stdio_register(struct stdio_dev *dev)
{
return stdio_register_dev(dev, NULL);
}
该函数然后调用stdio_register_dev(在common/stdio.c中实现)
[cpp] view
plain copy
int stdio_register_dev(struct stdio_dev *dev, struct stdio_dev **devp)
{
struct stdio_dev *_dev;
_dev = stdio_clone(dev); //分配struct stdio_dev并将参数dev的内容拷贝到分配的结构体中
if(!_dev)
return -ENODEV;
list_add_tail(&(_dev->list), &(devs.list));
if (devp)
*devp = _dev;
return 0;
}
最后一个涵参devp是保留给udev驱动模式使用,此处为空。
函数stdio_clone首先分配一个struct stdio_dev实例内存空间,然后将输入参数dev指向的结构体内容拷贝到新分配的struct stdio_dev实例空间,其返回值是新分配的struct stdio_dev实例的首址。
list_add_tail(&(_dev->list), &(devs.list))
将新建_dev成员变量中的list链表加入到stdio设备总链表的末尾。注意这里是单纯的list链表,_dev结构体自身并未加入链表中。
在上面"stdio_init_tables"函数分析一节中曾经提到过,全局变量devs指向该链表的表头。
综上所述,serial_stdio_init函数实现了如下功能:
1.为串口设备分配相应的struct stdio_dev实例
2.将struct stdio_dev的ops函数和串口的ops相关联
3.利用struct stdio_dev的成员priv将串口设备和代表stdio实例的struct stdio_dev关联起来。
4.将struct stdio_dev的成员list加入全局变量devs的list链表,完成最终的serial设备到stdio的注册。这样,后续的stdio设备的访问就可以使用全局变量devs来进行。使用的也是一种和linux驱动中类似的机制,使用宏container_of根据结构体成员倒查结构体。这里devs中的list是结构体成员,其被结构体struct
stdio_dev所包含。查找到相应的stdio设备后,就可利用该stdi设备的ops函数进行相关的输入输出操作。
总结:
就分层机制来说,stdio的实现包含3层,即stdio层,serail层和SOC serail层。
stdio层和SOC serail层都是有实体存在的,比如上述讨论中为stdio设备层分配了实体内存空间,
而serail层并无相应的内存实体存在,它使用的还是SOC serail分配的内存空间,只不过对该SOC serail
设备实例进一步执行了serail层特有的操作。所以serail层可称为抽象层。它是对相应的具体SOC层serail设备
的抽象。serail层和SOC serail是一种映射关系。
下图总揽了stdio层,serail层和SOC serail层之间的关系:
该阶段包括3个函数:stdio_init_tables、 initr_serial、stdio_add_devices。下面逐一对其进行详细说明。
1. stdio_init_tables
[cpp] view
plain copy
int stdio_init_tables(void)
{
/* Initialize the list */
INIT_LIST_HEAD(&(devs.list));
return 0;
}
INIT_LIST_HEAD为include/linux/List.h中的内联函数:
[cpp] view
plain copy
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
devs在stdio.c中定义:
static struct stdio_dev devs;
其中结构体struct stdio_dev定义在include/Stdio_dev.h中:
[cpp] view
plain copy
struct stdio_dev {
int flags; /* Device flags: input/output/system */
int ext; /* Supported extensions */
char name[32]; /* Device name */
/* GENERAL functions */
int (*start)(struct stdio_dev *dev); /* To start the device */
int (*stop)(struct stdio_dev *dev); /* To stop the device */
/* OUTPUT functions */
/* To put a char */
void (*putc)(struct stdio_dev *dev, const char c);
/* To put a string (accelerator) */
void (*puts)(struct stdio_dev *dev, const char *s);
/* INPUT functions */
/* To test if a char is ready... */
int (*tstc)(struct stdio_dev *dev);
int (*getc)(struct stdio_dev *dev); /* To get that char */
/* Other functions */
void *priv; /* Private extensions */
struct list_head list;
};
include/linux/List.h中有:
[cpp] view
plain copy
struct list_head {
struct list_head *next, *prev;
};
struct stdio_dev代表对stdio设备的描述,当有多个stdio设备时,将使用其成员变量list将这些stdio设备链接起来。这里,函数stdio_init_tables对该链表进行了初始化。注意这里的链表机制是在struct stdio_dev结构体中嵌入一个楔子struct
list_head list,list有两个成员*next和*prev,分别指向上一个和下一个struct list_head变量,而该变量则是struct stdio_dev的成员,*next和*prev并不指向struct stdio_dev结构体变量。
这和struct serial_device中的*next成员不同,后者指向下一个struct serial_device结构体变量,即下一个serial设备。
由struct list_head list组成的链表可称为stdio设备的维护链表。u-boot维持着devs这样一个全局变量,它始终指向stdio设备维护链表struct list_head的头部,u-boot通过这样一个链表维护着所有的stdio设备。
下面我们还会看到另一个全局变量serial_devices,它始终指向serial设备链表的头部。同样,所有注册的serial设备通过这样一个链表串接起来。
2. initr_serial
initr_serial为静态函数,它调用了drivers/serail/serail.c中的serial_initialize,该函数实现如下:
[cpp] view
plain copy
void serial_initialize(void)
{
altera_jtag_serial_initialize();
altera_serial_initialize();
...
mxc_serial_initialize();
...
serial_assign(default_serial_console()->name);
}
serial_initialize调用的函数列表中,末尾serial_assign除外,所有的函数都是weak类型,即其可能在它处实现,但如果没有实现,
会被函数serial_null替换。serial_null是serial.c中的静态函数,该函数中没有任何代码。
如altera_jtag_serial_initialize在它处没有实现,那么它将被alias("serial_null"),即被serial_null替换。
上面的函数列表是针对不同的SOC,有各自的实现。单独针对某一SOC时,只有一个函数得到具体的实现,此处针对I.MX6的CPU,
查看其目录下的Makefiel文件,根据配置可以看出最终使用的是文件drivers/serial/Serial_mxc.c中的mxc_serial_initialize:
[cpp] view
plain copy
void imx_serial_initialize(void)
{
serial_register(&mxc_serial_drv);
}
serial_register在drivers/serial/serial .c中实现:
[cpp] view
plain copy
void serial_register(struct serial_device *dev)
{
dev->next = serial_devices;
serial_devices = dev;
}
serial_devices在serial.c中被定义为静态变量:
static struct serial_device *serial_devices;
static struct serial_device *serial_current;
结构体struct serial_device的最后一个成员是struct serial_device *next。
执行完serial_register后,dev->next被赋值为serial_devices的初始值,即变量定义时默认的初始值0,
serial_devices后被赋值为dev,即传入的参数&mxc_serial_drv。该结构体在上面board_init_f阶段的分析中讨论过,
这里,为了便于分析,再次列出它的定义和初始化:
[cpp] view
plain copy
static struct serial_device mxc_serial_drv = {
.name = "mxc_serial",
.start = mxc_serial_init,
.stop = NULL,
.setbrg = mxc_serial_setbrg,
.putc = mxc_serial_putc,
.puts = default_serial_puts,
.getc = mxc_serial_getc,
.tstc = mxc_serial_tstc,
};
新加入serial的设备总是插入到serial_devices指向的serial设备链表的头部,而后serial_devices指向该新serial设备。
这样,所有注册过的serial设备通过设备结构成员next链接成一个设备链表。最后,全局变量serial_devices指向该链表的表头。
在函数serial_initialize中,执行完所有有效的serial设备的注册后,最后执行serial_assign:
[cpp] view
plain copy
int serial_assign(const char *name)
{
struct serial_device *s;
for (s = serial_devices; s; s = s->next) {
if (strcmp(s->name, name))
continue;
serial_current = s;
return 0;
}
return -EINVAL;
}
当某一SOC包含有多个可被u-boot使用的串口时,只要创建一个针对某串口的initialize函数,并包含对该串口如上类似的注册操作,把它加入到serial_initialize的执行函数列表中,就可被使用。所谓的串口链表也是针对多个串口的上述情况。但只有一个是被当前使用的,serial_assign函数就是指定以其入参为名字的串口为当前设备,该函数中的全局变量serial_current最后指向一个有效的当前串口设备。
3. stdio_add_devices
函数stdio_add_devices执行stdio设备的注册。stdio设备层工作在serail层之上,但stdio设备并不限于serial设备,比如标准输出设备可能包括显示器。但我们这里只讨论stdio中的serail设备。
stdio_add_devices在common/stdio.c中实现:
[cpp] view
plain copy
int stdio_add_devices(void)
{
i2c_init_all();
drv_video_init ();
drv_system_init ();
serial_stdio_init ();
drv_nc_init ();
return 0;
}
上述函数中注册为stdio的设备包括I2c,video,system,serial,以及netcommand设备。
其中serial设备到stdio的注册通过函数serial_stdio_ini来实现,其在drivres/serial/serial.c中定义为:
[cpp] view
plain copy
void serial_stdio_init(void)
{
struct stdio_dev dev;
struct serial_device *s = serial_devices;
while (s) {
memset(&dev, 0, sizeof(dev));
strcpy(dev.name, s->name);
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;
dev.start = serial_stub_start;
dev.stop = serial_stub_stop;
dev.putc = serial_stub_putc;
dev.puts = serial_stub_puts;
dev.getc = serial_stub_getc;
dev.tstc = serial_stub_tstc;
dev.priv = s;
stdio_register(&dev);
s = s->next;
}
}
在上面的initr_serial函数一节中,我们已经讨论过serail设备链表,它是已被注册的SOC层serial设备的集合。
这里的全局变量serial_devices指向serial设备链表的表头, 而后使用while循环用来遍历该设备链表并执行如下操作:
a).将serial设备的设备名拷贝到stdio设备的设备名中
b).我们知道stdio设备包含stdin,stdout,stderr三种设备,dev.flags即为这种设备属性区分标志。
c).添加stdio设备的ops函数。
d). 语句dev.priv = s将serial设备指针赋值给stdio设备结构体的成员变量priv。
e). 注册stdio 设备。
struct stdio_dev成员变量priv(私有的)是访问下层设备---这里是serial层----的媒介。通过priv变量,就可以访问stdio包含
的下层设备。由于stdio设备并不只包含serial设备,所以该变量定义为void *类型。
我们在board_init_f阶段阶段的讨论中曾经提到过,在serial层,提供了诸如serial_puts,serial_getc的外部访问接口。
而针对上层的stdio设备,serial层提供的外部接口则是名为serial_stub_xx的桩函数。
如serial_stub_putc在drivres/serial/serial.c中实现为:
[cpp] view
plain copy
static void serial_stub_putc(struct stdio_dev *sdev, const char ch)
{
struct serial_device *dev = sdev->priv;
dev->putc(ch);
}
如前所述,sdev->priv为serial设备指针,后者在serail初始化(注册)时被赋值为SOC层的设备指针,这样,最终的dev->putc将指向了
SOC层中的相关输出函数。
最后使用stdio_register函数,完成serial设备到stdio的注册:
[cpp] view
plain copy
int stdio_register(struct stdio_dev *dev)
{
return stdio_register_dev(dev, NULL);
}
该函数然后调用stdio_register_dev(在common/stdio.c中实现)
[cpp] view
plain copy
int stdio_register_dev(struct stdio_dev *dev, struct stdio_dev **devp)
{
struct stdio_dev *_dev;
_dev = stdio_clone(dev); //分配struct stdio_dev并将参数dev的内容拷贝到分配的结构体中
if(!_dev)
return -ENODEV;
list_add_tail(&(_dev->list), &(devs.list));
if (devp)
*devp = _dev;
return 0;
}
最后一个涵参devp是保留给udev驱动模式使用,此处为空。
函数stdio_clone首先分配一个struct stdio_dev实例内存空间,然后将输入参数dev指向的结构体内容拷贝到新分配的struct stdio_dev实例空间,其返回值是新分配的struct stdio_dev实例的首址。
list_add_tail(&(_dev->list), &(devs.list))
将新建_dev成员变量中的list链表加入到stdio设备总链表的末尾。注意这里是单纯的list链表,_dev结构体自身并未加入链表中。
在上面"stdio_init_tables"函数分析一节中曾经提到过,全局变量devs指向该链表的表头。
综上所述,serial_stdio_init函数实现了如下功能:
1.为串口设备分配相应的struct stdio_dev实例
2.将struct stdio_dev的ops函数和串口的ops相关联
3.利用struct stdio_dev的成员priv将串口设备和代表stdio实例的struct stdio_dev关联起来。
4.将struct stdio_dev的成员list加入全局变量devs的list链表,完成最终的serial设备到stdio的注册。这样,后续的stdio设备的访问就可以使用全局变量devs来进行。使用的也是一种和linux驱动中类似的机制,使用宏container_of根据结构体成员倒查结构体。这里devs中的list是结构体成员,其被结构体struct
stdio_dev所包含。查找到相应的stdio设备后,就可利用该stdi设备的ops函数进行相关的输入输出操作。
总结:
就分层机制来说,stdio的实现包含3层,即stdio层,serail层和SOC serail层。
stdio层和SOC serail层都是有实体存在的,比如上述讨论中为stdio设备层分配了实体内存空间,
而serail层并无相应的内存实体存在,它使用的还是SOC serail分配的内存空间,只不过对该SOC serail
设备实例进一步执行了serail层特有的操作。所以serail层可称为抽象层。它是对相应的具体SOC层serail设备
的抽象。serail层和SOC serail是一种映射关系。
下图总揽了stdio层,serail层和SOC serail层之间的关系:
相关文章推荐
- u-boot串口和stdio、console初始化及相关操作详解<二>
- u-boot串口和stdio、console初始化及相关操作详解<一>
- u-boot串口和stdio、console初始化及相关操作详解<三>
- u-boot串口和stdio、console初始化及相关操作详解<三>
- u-boot串口和stdio、console初始化及相关操作详解<一>
- <C语言>结构体的各种定义、初始化、操作代码实例----注释详解
- DataGridView、List<T>相关操作
- 防止页面表单重复提交,JSP页面加入 <s:token/> 以及Struts的相关配置详解
- spring-boot-starter-logging logback配置之<configuration><logger>标签详解
- Shell 编程详解之变量<二>
- 【Qt开发】V4L2 API详解 <二> Camera详细设置
- Json概述以及python对json的相关操作<转>
- 配置文件ehcache.xml详解(2)— <diskStore>配置及相关
- Dojo 的文档操作基础二<9>
- spring-boot-started-logging logback常用配置之<filter>标签详解
- (转)Spring boot——logback.xml 配置详解(四)<filter>
- spring-boot-starter-logging logback常用配置之<appender>标签详解
- <读书笔记>Windows内核安全 ---串口过滤驱动(3) 相关知识总结
- iOS开发中对OC字符串的相关操作<转>
- U-Boot串口初始化详解