二十六【uart】Uart驱动架构分析

一、前言

  在linux中,serial也对应着终端你,通常被称为串口终端。在shell上,我们看到得/dev/ttyS*就是串口终端所对应的设备节点。

  uart(Universal Asynchronous Receicer and transmitter)即为“通用异步收发器”。它是串口设备驱动的封装层。

二、Uart驱动架构概貌

 

   从上图可以看到,uart设备是继tty_drivers的又一层封装。实际上uart_driver就是对应tty_driver.在它的操作函数中,将操作转入uart_port.

  • 在写操作的时候,先将数据放入一个叫做circ_buf的环形缓存区。然后uart_port从缓存区中取数据,将其写入到串口设备中。
  • 当uart_port从serial设备接收到数据时,会将设备放入对应line discipline的缓存区中。这样,用户在编写串口驱动的时候,只先要注册一个uart_driver.它的主要作用是定义设备节点号。然后将对设备的各项操作封装在uart_port.驱动工程师没必要关心上层的流程,只需按硬件规范将uart_port中的接口函数完成就可以了。

三、uart驱动代码分析

1、uart驱动注册函数

drivers/tty/serial/serial_core.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
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
61
62
63
64
65
66
67
68
69
70
/**
 *  uart_register_driver - register a driver with the uart core layer
 *  @drv: low level driver structure
 *
 *  Register a uart driver with the core driver.  We in turn register
 *  with the tty layer, and initialise the core driver per-port state.
 *
 *  We have a proc file in /proc/tty/driver which is named after the
 *  normal driver.
 *
 *  drv->port should be NULL, and the per-port structures should be
 *  registered using uart_add_one_port after this call has succeeded.
 */
int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal;
    int i, retval = -ENOMEM;
 
    BUG_ON(drv->state);
 
    /*
     * Maybe we should be using a slab cache for this, especially if
     * we have a large number of ports to handle.
     */
    drv->state = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);
    if (!drv->state)
        goto out;
 
    normal = alloc_tty_driver(drv->nr);
    if (!normal)
        goto out_kfree;
 
    drv->tty_driver = normal;
        
    normal->driver_name  = drv->driver_name;
    normal->name     = drv->dev_name;
    normal->major        = drv->major;
    normal->minor_start  = drv->minor;
    normal->type     = TTY_DRIVER_TYPE_SERIAL;
    normal->subtype      = SERIAL_TYPE_NORMAL;
    normal->init_termios = tty_std_termios;
    normal->init_termios.c_cflag = B4000000 | CS8 | CREAD | HUPCL | CLOCAL;
    normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 4000000;   //默认的波特率
    normal->flags        = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    normal->driver_state    = drv;
    tty_set_operations(normal, &uart_ops);
 
    /*
     * Initialise the UART state(s).
     */
    for (i = 0; i < drv->nr; i++) {
        struct uart_state *state = drv->state + i;
        struct tty_port *port = &state->port;
 
        tty_port_init(port);   //端口初始化
        port->ops = &uart_port_ops;
    }
           //驱动注册
    retval = tty_register_driver(normal);
    if (retval >= 0)
        return retval;
 
    for (i = 0; i < drv->nr; i++)
        tty_port_destroy(&drv->state[i].port);
    put_tty_driver(normal);
out_kfree:
    kfree(drv->state);
out:
    return retval;
}

2、添加端口函数uart_add_one_port()

在uart_driver增加一个port

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
 *  uart_add_one_port - attach a driver-defined port structure
 *  @drv: pointer to the uart low level driver structure for this port
 *  @uport: uart port structure to use for this port.
 *
 *  This allows the driver to register its own uart_port structure
 *  with the core driver.  The main purpose is to allow the low
 *  level uart drivers to expand uart_port, rather than having yet
 *  more levels of structures.
 */
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
    struct uart_state *state;
    struct tty_port *port;
    int ret = 0;
    struct device *tty_dev;
    int num_groups;
 
    BUG_ON(in_interrupt());
 
    if (uport->line >= drv->nr)
        return -EINVAL;
 
    state = drv->state + uport->line;
    port = &state->port;
 
    mutex_lock(&port_mutex);
    mutex_lock(&port->mutex);
    if (state->uart_port) {
        ret = -EINVAL;
        goto out;
    }
 
    /* Link the port to the driver state table and vice versa */
    atomic_set(&state->refcount, 1);
    init_waitqueue_head(&state->remove_wait);
    state->uart_port = uport;
    uport->state = state;
 
    state->pm_state = UART_PM_STATE_UNDEFINED;
    uport->cons = drv->cons;
    uport->minor = drv->tty_driver->minor_start + uport->line;
    uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
                drv->tty_driver->name_base + uport->line);
    if (!uport->name) {
        ret = -ENOMEM;
        goto out;
    }
 
    /*
     * If this port is a console, then the spinlock is already
     * initialised.
     */
    if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
        spin_lock_init(&uport->lock);
        lockdep_set_class(&uport->lock, &port_lock_key);
    }
    if (uport->cons && uport->dev)
        of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
 
    tty_port_link_device(port, drv->tty_driver, uport->line);
    uart_configure_port(drv, state, uport);
 
    port->console = uart_console(uport);
 
    num_groups = 2;
    if (uport->attr_group)
        num_groups++;
 
    uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
                    GFP_KERNEL);
    if (!uport->tty_groups) {
        ret = -ENOMEM;
        goto out;
    }
    uport->tty_groups[0] = &tty_dev_attr_group;
    if (uport->attr_group)
        uport->tty_groups[1] = uport->attr_group;
 
    /*
     * Register the port whether it's detected or not.  This allows
     * setserial to be used to alter this port's parameters.
     */
    tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
            uport->line, uport->dev, port, uport->tty_groups);
    if (!IS_ERR(tty_dev)) {
        device_set_wakeup_capable(tty_dev, 1);
    } else {
        dev_err(uport->dev, "Cannot register tty device on line %d\n",
               uport->line);
    }
 
    /*
     * Ensure UPF_DEAD is not set.
     */
    uport->flags &= ~UPF_DEAD;
 
 out:
    mutex_unlock(&port->mutex);
    mutex_unlock(&port_mutex);
 
    return ret;
}

首先这个函数不能在中断环境中使用.Uart_port-> line就是对uart设备文件序号.它对应的也就是uart_driver-> state数组中的uart_port->line项.
它主要初始化对应uart_driver-> state项.接着调用uart_configure_port()进行port的自动配置.然后注册 tty_device.如果用户空间运行了udev或者已经配置好了hotplug.就会在/ dev下自动生成设备文件了.

3、串口的文件操作集合

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
static const struct tty_operations uart_ops = {
    .install    = uart_install,
    .open       = uart_open,
    .close      = uart_close,
    .write      = uart_write,
    .put_char   = uart_put_char,  //单字符写入
    .flush_chars    = uart_flush_chars,
    .write_room = uart_write_room,     //检测缓冲区的剩余空间
    .chars_in_buffer= uart_chars_in_buffer,   //检测包含数据缓冲区的数量
    .flush_buffer   = uart_flush_buffer,   //刷新缓冲区并丢弃其中的数据
    .ioctl      = uart_ioctl,
    .throttle   = uart_throttle,
    .unthrottle = uart_unthrottle,
    .send_xchar = uart_send_xchar,
    .set_termios    = uart_set_termios,
    .set_ldisc  = uart_set_ldisc,   //设置线路规程的函数
    .stop       = uart_stop,
    .start      = uart_start,
    .hangup     = uart_hangup,
    .break_ctl  = uart_break_ctl,
    .wait_until_sent= uart_wait_until_sent,  //用来向硬件发送数据
#ifdef CONFIG_PROC_FS
    .proc_show  = uart_proc_show,
#endif
    .tiocmget   = uart_tiocmget,    //获取特定tty设备当前的线路设置
    .tiocmset   = uart_tiocmset,    //设置特定tty设备当前的线路
    .set_serial = uart_set_info_user,
    .get_serial = uart_get_info_user,
    .get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
    .poll_init  = uart_poll_init,
    .poll_get_char  = uart_poll_get_char,
    .poll_put_char  = uart_poll_put_char,
#endif
};

  

 

  

  

 

posted @   轻轻的吻  阅读(895)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示