[uboot] (番外篇)uboot串口&console&stdio设备工作流程

来源:互联网 发布:织梦if标签 编辑:程序博客网 时间:2024/06/11 18:14

[uboot] uboot流程系列: 
[project X] tiny210(s5pv210)上电启动流程(BL0-BL2) 
[project X] tiny210(s5pv210)从存储设备加载代码到DDR 
[uboot] (第一章)uboot流程——概述 
[uboot] (第二章)uboot流程——uboot-spl编译流程 
[uboot] (第三章)uboot流程——uboot-spl代码流程 
[uboot] (第四章)uboot流程——uboot编译流程 
[uboot] (第五章)uboot流程——uboot启动流程 
[uboot] (番外篇)global_data介绍 
[uboot] (番外篇)uboot relocation介绍 
[uboot] (番外篇)uboot 驱动模型

建议先看《[uboot] (番外篇)uboot 驱动模型》

=============================================================================================================

一、uboot serial框架

1、serial模块驱动模型

在《[uboot] (番外篇)uboot 驱动模型》中我们已经介绍uboot的驱动模型,uboot DM。 
在uboot中,serial模块也使用了对应的驱动模型。 
其框架图如下: 
这里写图片描述 
我们在《[uboot] (番外篇)uboot 驱动模型》已经说明过了,这里再简单解释一下:

  • serial core为serial模块向外提供接口,但是也是在serial-uclass中实现
  • serial uclass是serial设备的集合抽象,为serial设备提供统一的操作接口,serial uclass driver则是其对应的驱动
  • serial udevice是serial设备的具体抽象,代表了一个serial设备对象,serial driver则是其对应的驱动

2、serial DM实现

在《[uboot] (番外篇)uboot 驱动模型》中,我们已经知道了uclass和udevice由uboot动态生成,但是我们需要在dtsi中添加相应的设备信息,以及添加相应的uclass driver和udevice driver. 
以tiny210为例,如下:

  • dts中的设备信息
/{    aliases {        console = "/serial@e2900000";    };    serial@e2900000 {        compatible = "samsung,exynos4210-uart";        reg = <0xe2900000 0x100>;        interrupts = <0 51 0>;        id = <0>;    };};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这里,有些人或许会有疑问,在relocate之前就需要打印串口数据,就需要使用到这个节点了,为什么不需要加上“u-boot,dm-pre-reloc”属性? 
确实是可以加,但是不加也没事,因为console中已经指定了串口节点的路径,在relocate之前的串口初始化过程中,在设备链表上找不到对应串口设备的话,会强制绑定console指定的串口节点的设备。

  • uclass driver 
    driver/serial/serial-uclass.c
UCLASS_DRIVER(serial) = {    .id     = UCLASS_SERIAL,    .name       = "serial",    .flags      = DM_UC_FLAG_SEQ_ALIAS,    .post_probe = serial_post_probe,    .pre_remove = serial_pre_remove,    .per_device_auto_alloc_size = sizeof(struct serial_dev_priv),};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • udevice driver 
    driver/serial/serial_s5p.c
static const struct udevice_id s5p_serial_ids[] = {    { .compatible = "samsung,exynos4210-uart" },  // 必须和dts匹配    { }};// s5p serial driver 提供了如下操作集 static const struct dm_serial_ops s5p_serial_ops = {    .putc = s5p_serial_putc,    .pending = s5p_serial_pending,    .getc = s5p_serial_getc,    .setbrg = s5p_serial_setbrg,};U_BOOT_DRIVER(serial_s5p) = {    .name   = "serial_s5p",    .id = UCLASS_SERIAL,    .of_match = s5p_serial_ids,    .ofdata_to_platdata = s5p_serial_ofdata_to_platdata,    .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),    .probe = s5p_serial_probe,    .ops    = &s5p_serial_ops,    .flags = DM_FLAG_PRE_RELOC,};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

注意,serial uclass driver的id和serial driver的id是一致的,都是UCLASS_SERIAL。 
具体也在《[uboot] (番外篇)uboot 驱动模型》分析过了,这里也不多说了。 
关于tiny210的串口驱动的实现,在第四节中再学习。

3、serial core提供的接口

serial core会利用serial uclass找到对应的设备及其操作集,向上层提供接口。 
如下: 
driver/serial/serial-uclass.c

  • void serial_putc(char ch) 
    往gd->cur_serial_dev指定的串口设备输出一个字符。
void serial_putc(char ch){    if (gd->cur_serial_dev)        _serial_putc(gd->cur_serial_dev, ch); // 将gd->cur_serial_dev指定的udevice作为参数传入}static void _serial_putc(struct udevice *dev, char ch){    struct dm_serial_ops *ops = serial_get_ops(dev); // 获取对应udevice的操作集(driver->ops)    int err;    if (ch == '\n')        _serial_putc(dev, '\r');    do {        err = ops->putc(dev, ch); // 调用udevice的操作集(driver->ops)中的putc函数,这里真正向硬件串口设备进行输出。    } while (err == -EAGAIN);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • void serial_puts(const char *str) 
    往gd->cur_serial_dev指定的串口设备输出字符串。其方法与serial_putc类似,自己参考代码。

  • int serial_getc(void) 
    从gd->cur_serial_dev指定的串口设备获取一个字符。其方法与serial_putc类似,自己参考代码。

  • int serial_tstc(void) 
    判断gd->cur_serial_dev指定的串口设备是否有数据在等待。其方法与serial_putc类似,自己参考代码。

  • void serial_setbrg(void) 
    设置gd->cur_serial_dev指定的串口设备的波特率。其方法与serial_putc类似,自己参考代码。

  • void serial_initialize(void) & int serial_init(void) 
    串口初始化。这里重点说明

void serial_initialize(void){    serial_init();}// 可以看出serial_initialize和serial_init是一样的。int serial_init(void){    serial_find_console_or_panic(); //调用serial_find_console_or_panic来查找console指定的设备    gd->flags |= GD_FLG_SERIAL_READY;    return 0;}static void serial_find_console_or_panic(void){    const void *blob = gd->fdt_blob;    struct udevice *dev;    int node;    if (CONFIG_IS_ENABLED(OF_CONTROL) && blob) {        node = fdtdec_get_chosen_node(blob, "stdout-path"); // 尝试在chosen节点中获取"stdout-path"节点        if (node < 0) {            const char *str, *p, *name;            str = fdtdec_get_chosen_prop(blob, "stdout-path"); // 尝试在chosen节点中获取"stdout-path"属性            if (str) {                p = strchr(str, ':');                name = fdt_get_alias_namelen(blob, str,                        p ? p - str : strlen(str));                if (name)                    node = fdt_path_offset(blob, name);            }        }        if (node < 0)            node = fdt_path_offset(blob, "console"); // 上述都找不到的话,最终去获取"console"指定的节点路径对应的偏移node        if (!uclass_get_device_by_of_offset(UCLASS_SERIAL, node,                            &dev)) {                 // 根据节点node从serial uclass的设备链表中获取对应的udevice设备,在获取之后,会进行probe。                // 注意,tiny210的serial节点并没有设置“u-boot,dm-pre-reloc”属性,所以在relocate之前dm_init中并不会去解析这个节点,                // 所以在relocate之前,这里会返回失败,也就是找不到对应的udevice            gd->cur_serial_dev = dev;            return;        }        /*         * If the console is not marked to be bound before relocation,         * bind it anyway.         */                // 如果console的串口设备节点在relocate之前没有被绑定,那么这里就会强制调用lists_bind_fdt和device_probe进行绑定和probe                // 这里就是为什么可以不需要加“u-boot,dm-pre-reloc”属性的原因。        if (node > 0 &&            !lists_bind_fdt(gd->dm_root, blob, node, &dev)) {            if (!device_probe(dev)) {                gd->cur_serial_dev = dev;                return;            }        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

在串口初始化完成之后,如果指定了console并且对应serial节点正常,那么serial_putc等等API都可以正常使用了。 
同时,gd->flags中的GD_FLG_SERIAL_READY的标志也被设置了 
后续就可以通过serial_putc、serial_puts、serial_getc向串口输出数据或者获取数据了。

4、默认波特率的设置

有两个地方可以设置默认波特率,并且存储到gd->baudrate中

  • 环境变量”baudrate”
  • 如果环境变量”baudrate”不存在,则使用CONFIG_BAUDRATE宏

具体参考代码: 
common/board_f.c

static int init_baud_rate(void){    gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);// 从环境变量中获取"baudrate"的值,存储到gd->baudrate中,如果如果环境变量"baudrate"不存在,则使用CONFIG_BAUDRATE宏的值作为默认波特率    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在include/configs/tiny210.h中配置默认波特率如下:

#define CONFIG_BAUDRATE         115200
  • 1

5、serial初始化时机

在relocate之前的board_f中和relocate之后的board_r各会执行一次串口初始操作

  • 在relocate之前的serial初始化 
    common/board_f.c
static init_fnc_t init_sequence_f[] = {#ifdef CONFIG_OF_CONTROL    fdtdec_setup,#endif    initf_dm,                    // DM的初始化,解析带有“u-boot,dm-pre-reloc”属性的节点    init_baud_rate,     // 设置默认波特率    serial_init,        // 串口初始化}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 在relocate之后的serial初始化 
    common/board_r.c
init_fnc_t init_sequence_r[] = {    initr_dm,            // DM的初始化,解析所有设备节点    initr_serial,        // 又一次进行串口初始化}/* initr_serial实现如下 */static int initr_serial(void){    serial_initialize();    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

二、debug、printf的输出流程

我们知道,在uboot中,可以通过debug和printf两个函数来进行串口log输出。

1、简单流程图

这里写图片描述

简单说明如下

  • debug会转化为printf
  • printf会调用到console的puts接口进行输出
  • console的puts的输出主要分成如下三个阶段 
    • 在serial初始化之前,会存储到pre console buffer中
    • 在serial初始化之后,console完全初始化(console_init_r)之前,会直接调用serial的serial_puts接口进行输出
    • 在serial初始化之后,console完全初始化(console_init_r)之后,会使用标准输入输出设备进行输出,但最终也是会调用到串口设备进行输出

2、debug()流程

  • 要使能debug()的输出功能,需要先打开DEBUG宏 
    include/common.h
++#define DEBUG#ifdef DEBUG#define _DEBUG 1#else
  • 1
  • 2
  • 3
  • 4
  • debug()使用方法如下:
    debug("U-Boot code: %08lX -> %08lX  BSS: -> %08lX\n",        text_base, bss_start, bss_end);
  • 1
  • 2
  • debug()定义如下: 
    include/common.h
#define debug_cond(cond, fmt, args...)          \    do {                        \        if (cond)               \   // 会根据_DEBUG的值来判断是否要进行输出            printf(pr_fmt(fmt), ##args);    \  //调用printf进行输出        } while (0)#define debug(fmt, args...)         \    debug_cond(_DEBUG, fmt, ##args) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以观察到最终也是调用到printf进行输出。

3、printf()流程

printf代码流程如下: 
lib/vsprintf.c

int printf(const char *fmt, ...){    va_list args;    uint i;    char printbuffer[CONFIG_SYS_PBSIZE];    va_start(args, fmt);    /*     * For this to work, printbuffer must be larger than     * anything we ever want to print.     */    i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args); // 对参数进行格式化    va_end(args);    /* Print the string */    puts(printbuffer); // 调用puts进行输出    return i;}// common/console.cvoid puts(const char *s){ // 在serial初始化之前,会调用pre_console_puts将字符串存储到pre console buffer中 // 具体参考下面第4小节     if (!gd->have_console)            return pre_console_puts(s);    if (gd->flags & GD_FLG_DEVINIT) {// 在serial初始化之后,console完全初始化(console_init_r)之后,// 会使用标准输入输出设备进行输出,也就是调用fputs(stdout, s)进行输出,但最终也是会调用到串口设备进行输出// 具体参考下面第5小节        /* Send to the standard output */        fputs(stdout, s);    } else {        /* Send directly to the handler *///在serial初始化之后,console完全初始化(console_init_r)之前,会直接调用serial的serial_puts接口进行输出// serial_puts我们已经在前面说过了        pre_console_puts(s);        serial_puts(s);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

几个重点标志位说明一下:

  • gd->have_console 
    用于判断是否有console,在relocate之前、serial串口初始化之后的console_init_f进行设置, 
    这里可以简单的理解为作为console的串口初始化的标识

  • gd->flags & GD_FLG_DEVINIT 
    标准输入输出设备初始化完成的标识。 
    在console_init_r中设置,当这个标识被设置,就表示console已经完全初始化了。

4、pre console buffer的使用

根据上面第2小节说明,在serial初始化之前,发到串口的数据会通过pre_console_puts存储到pre console buffer中。 
当串口初始化之后,在console_init_f中会对pre console buffer里面的内容进行输出

  • 需要打开的宏 
    以tiny210为例 
    include/configs/tiny210.h
#define CONFIG_PRE_CONSOLE_BUFFER // 用于使能pre console buffer的功能#define CONFIG_PRE_CON_BUF_SZ           4096 // buffer的size#define CONFIG_PRE_CON_BUF_ADDR         0x30000000 // 需要从内存中指定一块区域给buffer用,必须小心的判断哪部分区域在relocate之前是不会被使用的
  • 1
  • 2
  • 3
  • 存储流程 
    在puts中通过调用pre_console_puts将字符串存储到pre console buffer中,代码流程如下: 
    common/console.c
#define CIRC_BUF_IDX(idx) ((idx) % (unsigned long)CONFIG_PRE_CON_BUF_SZ)static void pre_console_putc(const char c){    char *buffer = (char *)CONFIG_PRE_CON_BUF_ADDR; // 设置buffer地址为CONFIG_PRE_CON_BUF_ADDR    buffer[CIRC_BUF_IDX(gd->precon_buf_idx++)] = c; // 以gd->precon_buf_idx为当前buffer的指针,写入对应位置上}static void pre_console_puts(const char *s){    while (*s)        pre_console_putc(*s++);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 输出流程 
    uboot会在relocate之前的串口初始化之后,通过调用console_init_f对pre console buffer的内容进行输出。代码如下: 
    common/console.c
int console_init_f(void){    gd->have_console = 1; // 打开这个标志之后,printf的输出信息就不会在存储到pre console buffer中了。    print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL); //  调用print_pre_console_buffer对pre console buffer的数据进行输出。    return 0;}static void print_pre_console_buffer(int flushpoint){    unsigned long in = 0, out = 0;    char *buf_in = (char *)CONFIG_PRE_CON_BUF_ADDR;    char buf_out[CONFIG_PRE_CON_BUF_SZ + 1];    if (gd->precon_buf_idx > CONFIG_PRE_CON_BUF_SZ)        in = gd->precon_buf_idx - CONFIG_PRE_CON_BUF_SZ;    while (in < gd->precon_buf_idx)        buf_out[out++] = buf_in[CIRC_BUF_IDX(in++)];    buf_out[out] = 0; // 将pre console buffer数据复制到buf_out中    switch (flushpoint) {    case PRE_CONSOLE_FLUSHPOINT1_SERIAL:        puts(buf_out); // 重新调用puts,此时的puts不会再存储到pre console buffer,而是会走serial_puts进行输出。        break;    case PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL:        console_puts_noserial(stdout, buf_out);        break;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

三、标准输入输出(stdio)

前面也说了,当console完全初始化之后,会生成标准输入输出设备。 
后续会在puts中调用fputs(stdout, s)对串口数据进行输出。

1、标准输入输出设备的结构体

include/stdio_dev.h

/* Device information */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 */    void (*putc)(struct stdio_dev *dev, const char c);  /* To put a char */ // 输出一个字符    void (*puts)(struct stdio_dev *dev, const char *s);  /* To put a string (accelerator) */ // 输出字符串/* INPUT functions */    int (*tstc)(struct stdio_dev *dev);/* To test if a char is ready... */ // 测试是否有数据可以获取    int (*getc)(struct stdio_dev *dev); /* To get that char */ // 获取一个字符/* Other functions */    void *priv;         /* Private extensions           */    struct list_head list;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

可以看到包含了标准输入输出设备的一些操作集。

2、标准输入输出设备的存储位置

uboot创建了一个虚拟stdio设备devs作为所有stdio设备的链表头,所有其他stdio设备都会挂载到devs.list链表上。 
并且提供了如下操作链表的方法: 
common/stdio.c

static struct stdio_dev devs;struct list_head* stdio_get_list(void); // 获取stdio链表struct stdio_dev* stdio_get_by_name(const char *name); // 通过name获取stdio链表中的stdio设备struct stdio_dev* stdio_clone(struct stdio_dev *dev); // 复制一个stdio设备int stdio_register_dev(struct stdio_dev *dev, struct stdio_dev **devp); // 注册一个stdio设备,会连接到stdio链表中int stdio_register(struct stdio_dev *dev); // 调用stdio_register_devint stdio_init_tables(void); // 初始化stdio链表
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

并且默认提供了stdin、stdout、stderr三个标准输入输出设备

struct stdio_dev *stdio_devices[] = { NULL, NULL, NULL };char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };
  • 1
  • 2

3、stdio模块初始化

stdio模块的初始化是在relocate之后的board_r中实现的

init_fnc_t init_sequence_r[] = {    stdio_init_tables,    stdio_add_devices,    console_init_r, }
  • 1
  • 2
  • 3
  • 4
  • 5

stdio_init_tables用于stdio链表的初始化 
stdio_add_devices用于创建一些标准输入输出设备。 
console_init_r用于将console和标准输入输出设备关联

4、stdio_add_devices

int stdio_add_devices(void){    drv_system_init ();  // 在drv_system_init 中创建一些必须的标准输入输出设备,比如串口的标准输入输出设备。    return 0;}static void drv_system_init (void){    struct stdio_dev dev;    memset (&dev, 0, sizeof (dev)); /* 以下创建串口的标准输入输出设备,设置其方法,并注册到stdio链表中 */    strcpy (dev.name, "serial");    dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;    dev.putc = stdio_serial_putc; //可以观察到这几个方法都是直接调用serial的接口进行输出    dev.puts = stdio_serial_puts;    dev.getc = stdio_serial_getc;    dev.tstc = stdio_serial_tstc;    stdio_register (&dev);}static void stdio_serial_putc(struct stdio_dev *dev, const char c){    serial_putc(c);}。。。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

经过上述步骤之后就注册了serial的标准输入输出设备了。

5、console_init_r

console_init_r主要用将console和标准输入输出设备关联。 
其会获取stdio链表中的第一个满足条件的标准输入输出设备,进行关联。 
代码如下

int console_init_r(void){    struct stdio_dev *inputdev = NULL, *outputdev = NULL;    int i;    struct list_head *list = stdio_get_list(); // 获取stdio链表    struct list_head *pos;    struct stdio_dev *dev;    /* Scan devices looking for input and output devices */    list_for_each(pos, list) { //遍历stdio链表        dev = list_entry(pos, struct stdio_dev, list);        if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {            inputdev = dev; // 设置输入设备为第一个搜索到的支持DEV_FLAGS_INPUT的stdio设备        }        if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {            outputdev = dev;// 设置输出设备为第一个搜索到的支持DEV_FLAGS_OUTPUT的stdio设备        }        if(inputdev && outputdev)            break;    }// 此时,我们的stdio链表上只有serial一个设备,并且既支持DEV_FLAGS_INPUT又支持DEV_FLAGS_OUTPUT,// 所以inputdev = serail_stdio_dev, output = serail_stdio_dev    /* Initializes output console first */    if (outputdev != NULL) {        console_setfile(stdout, outputdev); //设置系统的标准输出设备stdio_devices[0]为outputdev,对于tiny210来说就是serail_stdio_dev        console_setfile(stderr, outputdev); //设置系统的标准错误设备stdio_devices[2]为outputdev,对于tiny210来说就是serail_stdio_dev    }    /* Initializes input console */    if (inputdev != NULL) {        console_setfile(stdin, inputdev);//设置系统的标准输入设备stdio_devices[1]为inputdev,对于tiny210来说就是serail_stdio_dev    }#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET    stdio_print_current_devices();#endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */        // 打印出三个系统标准输入输出设备的名称,在log中可以看到如下:        // In:    serial@e2900000                        Out:   serial@e2900000                     Err:   serial@e2900000    /* Setting environment variables */    for (i = 0; i < 3; i++) {        setenv(stdio_names[i], stdio_devices[i]->name);    }        // 设置环境变量stdio_names    gd->flags |= GD_FLG_DEVINIT;    /* device initialization completed */        // 设置GD_FLG_DEVINIT标识,表示当前系统标准输入输出设备已经初始化完成了,    print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL);    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

后续就可以通过fputs(stdout, s)从串口输出字符,或者通过fgets(string, sizeof(string), stdin);从串口中获取字符。

6、fputs流程

fputs也是有console实现,较为简单,简单地看一下代码 
fputs(stdout, s)从串口输出的流程如下:

void fputs(int file, const char *s){    if (file < MAX_FILES)        console_puts(file, s);}static inline void console_puts(int file, const char *s){    stdio_devices[file]->puts(stdio_devices[file], s);        // 在console_init_r中stdio_devices已经和serial的标准输入输出绑定了        // 所以这里会调用到stdio_serial_puts函数        // 最终就调用到serial_puts从串口输出数据了}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

到这里,框架层的串口输出流程就分析完成了,也就是板级无关的部分。 
后面我们分析一下和板级相关的串口驱动的部分。

四、s5pv210 seriial driver分析

和tiny210(s5pv210)相关性较强,这里仅仅说明一下思路。 
代码具体参考driver/serial/serial_s5p.c

1、先定义驱动中需要使用到的私有数据

/* Information about a serial port */struct s5p_serial_platdata {    struct s5p_uart *reg;  /* address of registers in physical memory */ // 和uart相关的寄存器的物理基地址    u8 port_id;     /* uart port number */ // 串口id号};
  • 1
  • 2
  • 3
  • 4
  • 5

2、定义DM模型中的driver结构体

U_BOOT_DRIVER(serial_s5p) = {    .name   = "serial_s5p",    .id = UCLASS_SERIAL,  // 这个是固定的!!!    .of_match = s5p_serial_ids,    .ofdata_to_platdata = s5p_serial_ofdata_to_platdata,    .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata), // 私有数据的大小    .probe = s5p_serial_probe,    .ops    = &s5p_serial_ops,    .flags = DM_FLAG_PRE_RELOC,};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3、设置match_id:s5p_serial_ids

static const struct udevice_id s5p_serial_ids[] = {    { .compatible = "samsung,exynos4210-uart" },    { }};
  • 1
  • 2
  • 3
  • 4

4、设置dts节点的解析函数:s5p_serial_ofdata_to_platdata

static int s5p_serial_ofdata_to_platdata(struct udevice *dev){    struct s5p_serial_platdata *plat = dev->platdata;    fdt_addr_t addr;    addr = dev_get_addr(dev); // 获取dts节点中的寄存器地址    plat->reg = (struct s5p_uart *)addr; // 将地址存储在plat->reg中    plat->port_id = fdtdec_get_int(gd->fdt_blob, dev->of_offset, "id", -1); // 获取dtsi中的id属性,并且存放到plat->port_id中    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

5、实现probe函数:s5p_serial_probe

probe就相当于是激活这个串口,就需要在probe中对这个串口进行初始话

static int s5p_serial_probe(struct udevice *dev){    struct s5p_serial_platdata *plat = dev->platdata;    struct s5p_uart *const uart = plat->reg;    s5p_serial_init(uart);    return 0;}static void __maybe_unused s5p_serial_init(struct s5p_uart *uart){    /* enable FIFOs, auto clear Rx FIFO */    writel(0x3, &uart->ufcon);    writel(0, &uart->umcon);    /* 8N1 */    writel(0x3, &uart->ulcon);    /* No interrupts, no DMA, pure polling */    writel(0x245, &uart->ucon);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

6、实现操作集:s5p_serial_ops

注意,因为这个driver对应的udevice所属的serial uclass类型,因此其操作集的类型必须定义为dm_serial_ops类型。

static const struct dm_serial_ops s5p_serial_ops = {    .putc = s5p_serial_putc,    .pending = s5p_serial_pending,    .getc = s5p_serial_getc,    .setbrg = s5p_serial_setbrg,};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

具体实现这里不多说了。

7、在dtsi中添加设备信息

/{    aliases {        console = "/serial@e2900000";    };    serial@e2900000 {        compatible = "samsung,exynos4210-uart";        reg = <0xe2900000 0x100>;        interrupts = <0 51 0>;        id = <0>;    };};
阅读全文
0 0
原创粉丝点击