build_embed_linux_system

串口tty管理框架

在接触嵌入式Linux以来,从上电那一刻开始,我们接触的就是从串口命令行打印的信息。作为U-Boot和Kernel调试打印和交互的接口,串口的重要性不言而喻。从硬件上来说,串口并不复杂,最简单的通讯只需要RX,TX,GND三根线即可;复杂的也就是增加RTS,CTS实现流控,或者增加方向引脚实现RS485通讯。但是从软件的角度来看,串口的管理框架是非常复杂的;涉及到tty设备注册,console终端管理,理解起来就相当复杂了。这也是如此靠后去讲解tty serial框架的原因。

本文中将从UART常用硬件结构、UART驱动实现、串口命令调试和应用层访问以及Console终端管理四部分讲解tty serial框架相关说明;具体目录如下所示。

uart_hw

UART是支持全双工和半双工、异步收发的通讯模块。在嵌入式系统中,UART可以说是最常用的通讯模块之一,例如用于打印调试信息和命令控制的调试口,用于和蓝牙,wifi模块连接的通讯口,板级芯片通讯的连接口,都是基于UART功能实现。另外UART也支持外扩芯片支持不同的物理接口,如RS232,RS485和RS422等,可以满足多场景多设备的需求。

UART对于芯片I/O输出的是TTL电平,一般用于短距离通讯。为了增加可靠性和远距离通讯,可以通过外部扩展支持RS232,RS485和RS422等多种通讯接口,这些接口连接时注意不能混用,否则有损坏器件的风险。

通常调试串口一般使用UART-TTL或者UART-RS232来实时打印;在工业中,通常使用RS232或者RS485来实现稳定可靠的远程通讯。RS422因为成本问题,则常用于对通讯速率要求高,要求全双工,且传输距离远的场景。UART的常用引脚和功能说明如下所示。

引脚名称 功能描述
TXD 串口发送端,用于发送数据
RXD 串口接收端,用于接收数据
RTS 串口请求发送,用于控制发送数据
CTS 串口清除发送,用于控制接收数据
DIR 串口方向控制,用于控制发送和接收方向(用于连接RS485模块,控制收发方向)
GND 串口地,用于连接地线

在产品开发中,一般使用UART-TXD和UART-RXD进行通信。对于部分低功耗或者高低频搭配的产品,通过流控线来限制高速产品的数据发送,避免处理来不及导致数据包丢失。方向控制则用于RS485连接通讯,控制收发方向。对于大部分应用,我们只需要考虑TXD和RXD的连接即可。

tty_driver

上面讲解了UART的硬件知识,这样就对UART硬件接口有了一定了解。在本节中,我们将从tty框架的角度,讲解tty驱动实现。因为tty驱动的复杂度,单独实现十分困难,因此本节中以解析i.MX6ULL的UART驱动为例,讲解tty驱动实现。关于驱动的代码,详细见路径

- drivers/tty/serial/imx.c                  //uart驱动硬件代码
- drivers/tty/serial/imx_earlycon.c         //uart驱动earlycon代码(用于实现提前打印调试信息)

不过在理解驱动之前,先了解下本节涉及的tty相关接口。

// 向系统中添加uart设备端口
// drv: uart设备节点信息
// uport: uart端口信息
// 返回值: 0-成功,-1-失败
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)

其中比较关键的就是struct uart_driver和struct uart_port结构,对应格式如下。

// uart驱动信息
struct uart_driver {
    struct module     *owner;           // 驱动拥有者,一般是THIS_MODULE
    const char        *driver_name;     // 驱动名称
    const char        *dev_name;        // 设备名称,最终设备节点/dev/ttymxc0中的ttymxc依赖此项定义
    int             major;              // 主设备号  
    int             minor;              // 次设备号
    int             nr;                 // 设备数量
    struct console  *cons;              // 串口属于的终端信息,打印窗口实现依赖于此项

    // 私有状态数据
    struct uart_state    *state;        // uart状态信息
    struct tty_driver    *tty_driver;   // tty驱动信息
};

// uart端口信息
struct uart_port {
    spinlock_t      lock;          // 端口锁,用于保护对UART端口的并发访问
    unsigned long   iobase;        // I/O基地址,用于in/out系列函数进行I/O操作
    unsigned char __iomem *membase;  // 内存映射基地址,用于read/write系列函数进行内存映射I/O操作
    unsigned int    (*serial_in)(struct uart_port *, int);      // 从UART端口读取数据的函数指针
    void            (*serial_out)(struct uart_port *, int, int);// 向UART端口写入数据的函数指针
    void            (*set_termios)(struct uart_port *,          // 设置终端属性的函数指针
                        struct ktermios *new,
                        const struct ktermios *old);
    void            (*set_ldisc)(struct uart_port *,            // 设置线路规程的函数指针
                        struct ktermios *);
    unsigned int    (*get_mctrl)(struct uart_port *);               // 获取调制解调器控制寄存器的函数指针
    void            (*set_mctrl)(struct uart_port *, unsigned int); // 设置调制解调器控制寄存器的函数指针
    unsigned int        (*get_divisor)(struct uart_port *,          // 获取波特率除数的函数指针
                           unsigned int baud,
                           unsigned int *frac);
    void            (*set_divisor)(struct uart_port *,              // 设置波特率除数的函数指针
                           unsigned int baud,
                           unsigned int quot,
                           unsigned int quot_frac);
    int            (*startup)(struct uart_port *port);              // 启动UART端口的函数指针
    void            (*shutdown)(struct uart_port *port);            // 关闭UART端口的函数指针  
    void            (*throttle)(struct uart_port *port);            // 暂停UART发送
    void            (*unthrottle)(struct uart_port *port);          // 恢复UART发送
    int            (*handle_irq)(struct uart_port *);               // 处理中断的函数指针
    void            (*pm)(struct uart_port *, unsigned int state,   // 电源管理函数指针
                      unsigned int old);
    void            (*handle_break)(struct uart_port *);            // 处理BREAK中断的函数指针
    int            (*rs485_config)(struct uart_port *,              // RS485配置函数指针
                        struct ktermios *termios,
                        struct serial_rs485 *rs485);
    int            (*iso7816_config)(struct uart_port *,            // ISO7816配置函数指针
                        struct serial_iso7816 *iso7816);
    unsigned int        irq;                // 中断号,UART端口使用的中断号
    unsigned long       irqflags;           // 中断标志 
    unsigned int        uartclk;            // UART时钟频率,单位为Hz
    unsigned int        fifosize;           // tx fifo大小
    unsigned char        x_char;            // 待发送的字符(用于发送XON/XOFF字符)
    unsigned char        regshift;          // I/O地址偏移量
    unsigned char        iotype;            // I/O访问方式  
    unsigned char        quirks;            // 硬件quirks

    unsigned int        read_status_mask;   // 状态掩码
    unsigned int        ignore_status_mask; // 忽略状态掩码
    struct uart_state   *state;             // uart状态信息 
    struct uart_icount  icount;             // 串口统计信息 

    struct console      *cons;              // 串口属于的终端信息,打印窗口实现依赖于此项
    upf_t               flags;              // 串口标志,用于控制串口的行为

    upstat_t            status;             // 串口状态,用于记录串口的状态信息

    int            hw_stopped;              // 串口是否暂停
    unsigned int        mctrl;              // 串口控制寄存器
    unsigned int        frame_time;         // 帧时间,单位为纳秒
    unsigned int        type;               // 串口类型
    const struct uart_ops    *ops;          // 串口操作函数
    unsigned int        custom_divisor;     // 自定义波特率除数
    unsigned int        line;               // 串口编号
    unsigned int        minor;              // 串口次设备号
    resource_size_t     mapbase;            // 串口映射基址
    resource_size_t     mapsize;            // 串口映射大小
    struct device       *dev;               // 串口设备

    unsigned long       sysrq;              // sysrq值
    unsigned int        sysrq_ch;           // sysrq字符
    unsigned char       has_sysrq;          // sysrq开关
    unsigned char       sysrq_seq;          // sysrq序列

    unsigned char       hub6;               // hub6, 8250 driver
    unsigned char       suspended;          // 串口是否被挂起
    unsigned char       console_reinit;     // console reinit
    const char          *name;              // 串口名称
    struct attribute_group    *attr_group;  // 串口属性组
    const struct attribute_group **tty_groups;    // tty属性组
    struct serial_rs485     rs485;                // RS485配置
    struct serial_rs485    rs485_supported;       // RS485支持的配置
    struct gpio_desc    *rs485_term_gpio;         // RS485终端控制引脚
    struct serial_iso7816   iso7816;              // ISO7816配置
    void            *private_data;          // 串口私有数据
};

上述配置项很多,不过和前面Linux内核中的其它驱动模块类似;大部分都是用于串口框架管理和执行时关联的配置项,我们并不需要了解每一个配置项。其中比较关键的如下所示。

tty_app

console_terminal

串口模块

tty的操作主要涉及文件:

//获取tty相关的gpio,分别对应cts-gpios, dsr-gpios, dcd-gpios, rng-gpios, rts-gpios, dtr-gpios
drivers/tty/serial/serial_mctrl_gpio.c  
drivers/tty/serial/serial_core.c
drivers/tty/serial/imx.c
drivers/tty/serial/imx_earlycon.c

//include/linux/circ_buf.h
struct circ_buf {
    char *buf;
    int head;
    int tail;
};

//include/linux/serial_core.h
struct uart_state {
    struct tty_port         port;

    enum uart_pm_state      pm_state;
    struct circ_buf         xmit;

    atomic_t                refcount;
    wait_queue_head_t       remove_wait;
    struct uart_port        *uart_port;
};

struct uart_port {
    spinlock_t              lock;            /* port lock */
    unsigned long           obase;            /* in/out[bwl] */
    unsigned char __iomem   *membase;        /* read/write[bwl] */
    unsigned int            (*serial_in)(struct uart_port *, int);
    void                    (*serial_out)(struct uart_port *, int, int);
    void                    (*set_termios)(struct uart_port *,
                               struct ktermios *new,
                               const struct ktermios *old);
    void                    (*set_ldisc)(struct uart_port *,
                                struct ktermios *);
    unsigned int            (*get_mctrl)(struct uart_port *);
    void                    (*set_mctrl)(struct uart_port *, unsigned int);
    unsigned int            (*get_divisor)(struct uart_port *,
                                unsigned int baud,
                                unsigned int *frac);
    void                    (*set_divisor)(struct uart_port *,
                                unsigned int baud,
                                unsigned int quot,
                                unsigned int quot_frac);
    int                     (*startup)(struct uart_port *port);
    void                    (*shutdown)(struct uart_port *port);
    void                    (*throttle)(struct uart_port *port);
    void                    (*unthrottle)(struct uart_port *port);
    int                     (*handle_irq)(struct uart_port *);
    void                    (*pm)(struct uart_port *, unsigned int state,
                                unsigned int old);
    void                    (*handle_break)(struct uart_port *);
    int                     (*rs485_config)(struct uart_port *,
                                struct ktermios *termios,
                                struct serial_rs485 *rs485);
    int                     (*iso7816_config)(struct uart_port *,
                                struct serial_iso7816 *iso7816);
    unsigned int            irq;            /* irq number */
    unsigned long           irqflags;        /* irq flags  */
    unsigned int            uartclk;        /* base uart clock */
    unsigned int            fifosize;        /* tx fifo size */
    unsigned char           x_char;            /* xon/xoff char */
    unsigned char           regshift;        /* reg offset shift */
    unsigned char           iotype;            /* io access style */
    unsigned char           quirks;            /* internal quirks */

    unsigned int            read_status_mask;    /* driver specific */
    unsigned int            ignore_status_mask;    /* driver specific */
    struct uart_state       *state;            /* pointer to parent state */
    struct uart_icount      icount;            /* statistics */

    struct console          *cons;            /* struct console, if any */
    /* flags must be updated while holding port mutex */
    upf_t                   flags;

    /*
     * Must hold termios_rwsem, port mutex and port lock to change;
     * can hold any one lock to read.
     */
    upstat_t                status;

    int                     hw_stopped;        /* sw-assisted CTS flow state */
    unsigned int            mctrl;            /* current modem ctrl settings */
    unsigned int            frame_time;        /* frame timing in ns */
    unsigned int            type;            /* port type */
    const struct uart_ops    *ops;
    unsigned int            custom_divisor;
    unsigned int            line;            /* port index */
    unsigned int            minor;
    resource_size_t         mapbase;        /* for ioremap */
    resource_size_t         mapsize;
    struct device           *dev;            /* parent device */

    unsigned long           sysrq;            /* sysrq timeout */
    unsigned int            sysrq_ch;        /* char for sysrq */
    unsigned char           has_sysrq;
    unsigned char           sysrq_seq;        /* index in sysrq_toggle_seq */

    unsigned char           hub6;            /* this should be in the 8250 driver */
    unsigned char           suspended;
    unsigned char           console_reinit;
    const char              *name;            /* port name */
    struct attribute_group    *attr_group;        /* port specific attributes */
    const struct attribute_group **tty_groups;    /* all attributes (serial core use only) */
    struct serial_rs485     rs485;
    struct serial_rs485    rs485_supported;    /* Supported mask for serial_rs485 */
    struct gpio_desc    *rs485_term_gpio;    /* enable RS485 bus termination */
    struct serial_iso7816   iso7816;
    void            *private_data;        /* generic platform data pointer */
};

next_chapter

返回目录

直接开始下一节说明: nvmem子系统管理框架