Linux内核(一) [ IMX RK ] TTY-UART驱动框架解析_内核tty框架-程序员宅基地

技术标签: Linux内核  c语言  linux  nxp  arm  驱动开发  

平台:NXP imx6ull 内核版本:4.1.15

一、Linux TTY驱动框架

Linux TTY驱动程序代码位于/drivers/tty下面。TTY整体框架大致分为TTY应用层、TTY文件层、TTY线路规程层、TTY驱动层、TTY设备驱动层。
TTY应用层负责应用逻辑
TTY文件层(操作函数集file_operations)负责文件接口
TTY线路规程(操作函数集tty_operations)负责串行通信协议处理,包括特定协议的封装与解封
TTY驱动层对各种TTY设备进行分类和抽象(struct uart_driver)
TTY设备驱动层(操作函数集struct uart_ops)实现具体的TTY设备(芯片或者控制器)驱动,即设备配置与数据收发。
(具体的TTY设备有串口、USB串口、VT设备、PTY设备等,最常见的是串口,即UART。)

二、Linux Uart驱动框架

UART驱动没有主机端和设备端之分,只有控制器驱动。imx6ull的UART驱动已由厂家写好,位于/drivers/tty/serial/imx.c文件中。剩下的工作主要是在设备树中配置串口节点信息,当UART驱动和设备树匹配成功后,相应的UART被驱动起来,在/dev/目录下生成ttymxcX(x=0…n)文件。

三、UART相关结构体uart_driver(UART驱动结构体) 、uart_port(UART端口) 、uart_ops(UART操作函数集)

(1)struct uart_driver结构体本身并不包含底层UART硬件的操作方法,其是所有串口设备驱动的抽象和封装。起到了连接硬件设备驱动和TTY驱动的作用。注册了struct uart_driver后还不能使用UART设备,还需要关联具体的UART设备。

// include/linux/serial_core.h
struct uart_driver {
    
    struct module       *owner;             /* 模块所属者 */
    const char      *driver_name;           /* 驱动名字 */ 
    const char      *dev_name;              /* 设备名字 */ 
    int          major;                     /* 主设备号 */
    int          minor;                     /* 次设备号 */
    int          nr;                        /* 该uart_driver支持的串口个数(最大) */
    struct console      *cons;              /* 控制台  其对应的console.若该uart_driver支持serial console,否则为NULL */ */

    struct uart_state   *state;            // 下层,串口驱动层 uart_state则代表下层,uart_state会在register_uart_driver 的过程中分配空间
    struct tty_driver   *tty_driver;       /* tty相关 */ tty_driver是tty驱动的结构体,它会在register_uart_driver中的过程中赋值。
};

// 每个串口驱动都需要定义一个 uart_driver,加载驱动的时候通过 uart_register_driver 函数向系统注册这个 uart_driver
int uart_register_driver(struct uart_driver *drv)              drv: 要注册的 uart_driver
// 注销驱动的时候也需要注销掉前面注册的 uart_driver,需要用到 uart_unregister_driver 函数
void uart_unregister_driver(struct uart_driver *drv)         drv: 要注册的 uart_driver

// 在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息。
struct uart_state {
    
    struct tty_port     port;
    enum uart_pm_state  pm_state;
    struct circ_buf     xmit;       // 缓冲区,发送数据和接收数据的存储空间
    struct uart_port    *uart_port; // 对应于一个实际的串口设备
};

(2)一个串口芯片上往往有多个串行端口(serial ports,对应于一个物理上的串口),这些串行端口具备相同的操作机制。Linux内核将这些串行端口用struct uart_port结构体描述。struct uart_port用于描述一个UART端口的中断、I/O内存地址、FIFO大小、端口类型等信息。

//  include/linux/serial_core.h
struct uart_port {
    
    spinlock_t      lock;          /* 自旋锁 */
    unsigned long       iobase;         /* io端口基地址(物理) */
    unsigned char __iomem   *membase;       /* io内存基地址(虚拟) */
    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,
                               struct ktermios *old);
    void            (*set_mctrl)(struct uart_port *, unsigned int);
    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 serial_rs485 *rs485);
    unsigned int        irq;            /* 中断号 */
    unsigned long       irqflags;      /* 中断标志  */
    unsigned int        uartclk;        /* 串口时钟 */
    unsigned int        fifosize;       /* 串口缓冲区大小 */
    unsigned char       x_char;         /* xon/xoff握手字节 */
    unsigned char       regshift;       /* 寄存器位移 */
    unsigned char       iotype;         /* IO访问方式 */
    unsigned char       unused1;
#define UPIO_PORT       (SERIAL_IO_PORT)    /* 8b I/O port access */
#define UPIO_HUB6       (SERIAL_IO_HUB6)    /* Hub6 ISA card */
#define UPIO_MEM        (SERIAL_IO_MEM)     /* 8b MMIO access */
#define UPIO_MEM32      (SERIAL_IO_MEM32)   /* 32b little endian */
#define UPIO_AU         (SERIAL_IO_AU)      /* Au1x00 and RT288x type IO */
#define UPIO_TSI        (SERIAL_IO_TSI)     /* Tsi108/109 type IO */
#define UPIO_MEM32BE        (SERIAL_IO_MEM32BE) /* 32b big endian */
    unsigned int        read_status_mask;   /* 读状态验码 */
    unsigned int        ignore_status_mask; /* 忽略状态掩码 */
    struct uart_state   *state;         /* 指向父设备的状态 */
    struct uart_icount  icount;         /* 串口信息计数器 */
    struct console      *cons;          /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
    unsigned long       sysrq;          /* sysrq timeout */
#endif
    /* flags must be updated while holding port mutex */
    upf_t           flags;
#define UPF_FOURPORT        ((__force upf_t) ASYNC_FOURPORT       /* 1  */ )
#define UPF_SAK         ((__force upf_t) ASYNC_SAK            /* 2  */ )
#define UPF_SPD_HI      ((__force upf_t) ASYNC_SPD_HI         /* 4  */ )
#define UPF_SPD_VHI     ((__force upf_t) ASYNC_SPD_VHI        /* 5  */ )
#define UPF_SPD_CUST        ((__force upf_t) ASYNC_SPD_CUST   /* 0x0030 */ )
#define UPF_SPD_WARP        ((__force upf_t) ASYNC_SPD_WARP   /* 0x1010 */ )
#define UPF_SPD_MASK        ((__force upf_t) ASYNC_SPD_MASK   /* 0x1030 */ )
#define UPF_SKIP_TEST       ((__force upf_t) ASYNC_SKIP_TEST      /* 6  */ )
#define UPF_AUTO_IRQ        ((__force upf_t) ASYNC_AUTO_IRQ       /* 7  */ )
#define UPF_HARDPPS_CD      ((__force upf_t) ASYNC_HARDPPS_CD     /* 11 */ )
#define UPF_SPD_SHI     ((__force upf_t) ASYNC_SPD_SHI        /* 12 */ )
#define UPF_LOW_LATENCY     ((__force upf_t) ASYNC_LOW_LATENCY    /* 13 */ )
#define UPF_BUGGY_UART      ((__force upf_t) ASYNC_BUGGY_UART     /* 14 */ )
#define UPF_NO_TXEN_TEST    ((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER    ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ )
/* Port has hardware-assisted h/w flow control */
#define UPF_AUTO_CTS        ((__force upf_t) (1 << 20))
#define UPF_AUTO_RTS        ((__force upf_t) (1 << 21))
#define UPF_HARD_FLOW       ((__force upf_t) (UPF_AUTO_CTS | UPF_AUTO_RTS))
/* Port has hardware-assisted s/w flow control */
#define UPF_SOFT_FLOW       ((__force upf_t) (1 << 22))
#define UPF_CONS_FLOW       ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ       ((__force upf_t) (1 << 24))
#define UPF_EXAR_EFR        ((__force upf_t) (1 << 25))
#define UPF_BUG_THRE        ((__force upf_t) (1 << 26))
/* The exact UART type is known and should not be probed.  */
#define UPF_FIXED_TYPE      ((__force upf_t) (1 << 27))
#define UPF_BOOT_AUTOCONF   ((__force upf_t) (1 << 28))
#define UPF_FIXED_PORT      ((__force upf_t) (1 << 29))
#define UPF_DEAD        ((__force upf_t) (1 << 30))
#define UPF_IOREMAP     ((__force upf_t) (1 << 31))
#define __UPF_CHANGE_MASK   0x17fff
#define UPF_CHANGE_MASK     ((__force upf_t) __UPF_CHANGE_MASK)
#define UPF_USR_MASK        ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))
#if __UPF_CHANGE_MASK > ASYNC_FLAGS
#error Change mask not equivalent to userspace-visible bit defines
#endif
    /*
     * Must hold termios_rwsem, port mutex and port lock to change;
     * can hold any one lock to read.
     */
    upstat_t        status;
#define UPSTAT_CTS_ENABLE   ((__force upstat_t) (1 << 0))
#define UPSTAT_DCD_ENABLE   ((__force upstat_t) (1 << 1))
#define UPSTAT_AUTORTS      ((__force upstat_t) (1 << 2))
#define UPSTAT_AUTOCTS      ((__force upstat_t) (1 << 3))
#define UPSTAT_AUTOXOFF     ((__force upstat_t) (1 << 4))
    int         hw_stopped;     /* sw-assisted CTS flow state */
    unsigned int        mctrl;          /* 当前的Moden 设置 */
    unsigned int        timeout;        /* character-based timeout */
    unsigned int        type;           /* 端口类型 */
    const struct uart_ops   *ops;       /* 此结构体很重要,uart硬件的操作函数 */
    unsigned int        custom_divisor;
    unsigned int        line;           /* 端口索引 */
    unsigned int        minor;
    resource_size_t     mapbase;        /* io内存物理基地址 */
    resource_size_t     mapsize;
    struct device       *dev;           /* 父设备 */
    unsigned char       hub6;           /* this should be in the 8250 driver */
    unsigned char       suspended;
    unsigned char       irq_wake;
    unsigned char       unused[2];
    struct attribute_group  *attr_group;        /* port specific attributes */
    const struct attribute_group **tty_groups;  /* all attributes (serial core use only) */
    struct serial_rs485     rs485;
    void            *private_data;      /* generic platform data pointer */
};

每个 UART 都有一个 uart_port,uart_port 和 uart_driver 结合起来是通过uart_add_one_port 函数
uart_port通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)       
drv:此 port 对应的 uart_driver    
uport: 要添加到 uart_driver 中的 port     
返回值: 0,成功;负值,失败
卸载 UART 驱动的时候也需要将 uart_port 从相应的 uart_driver 中移除,需要用到uart_remove_one_port 函数
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
drv:要卸载的 port 所对应的 uart_driver
uport: 要卸载的 uart_port
返回值: 0,成功;负值,失败

(3)Linux 系统收发数据最终调用的都是 ops 中的函数。 ops 是 uart_ops类型的结构体指针变量。uart硬件操作函数集合,底层硬件驱动必须实现这个结构体

// include/linux/serial_core.h
struct uart_ops {
    
    unsigned int    (*tx_empty)(struct uart_port *);                    // 串口的tx_fifo是否为空
    void        (*set_mctrl)(struct uart_port *, unsigned int mctrl);   // 设置串口的modem控制,xyz
    unsigned int    (*get_mctrl)(struct uart_port *);                   // 获取modem设置
    void        (*stop_tx)(struct uart_port *);                         // 停止传输
    void        (*start_tx)(struct uart_port *);                        // 开始传输
    void        (*throttle)(struct uart_port *);
    void        (*unthrottle)(struct uart_port *);
    void        (*send_xchar)(struct uart_port *, char ch);
    void        (*stop_rx)(struct uart_port *);                        // 停止接收
    void        (*enable_ms)(struct uart_port *);                      // 使能modem的状态信号
    void        (*break_ctl)(struct uart_port *, int ctl);             // 设置break信号
    int     (*startup)(struct uart_port *);                            // 使能串口,用户调用open使最终会调用此函数
    void        (*shutdown)(struct uart_port *);                       // 关闭串口,应用程序关闭串口设备文件时,该函数会被调用
    void        (*flush_buffer)(struct uart_port *);
    void        (*set_termios)(struct uart_port *, struct ktermios *new,
                       struct ktermios *old);                         // 设置串口属性,包括波特率等
    void        (*set_ldisc)(struct uart_port *, struct ktermios *);
    void        (*pm)(struct uart_port *, unsigned int state,
                  unsigned int oldstate);
                  
    const char  *(*type)(struct uart_port *);                         // 判断串口类型是否为amba
    void        (*release_port)(struct uart_port *);                  // 释放端口使用的内存

    int     (*request_port)(struct uart_port *);                     // 请求端口使用的内存
    void        (*config_port)(struct uart_port *, int);             // 设置端口类型并申请端口使用的内存
    int     (*verify_port)(struct uart_port *, struct serial_struct *);  // 检验串口属性,包括总线类型和波特率
    int     (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
    int     (*poll_init)(struct uart_port *);
    void        (*poll_put_char)(struct uart_port *, unsigned char);
    int     (*poll_get_char)(struct uart_port *);
#endif
};

四、设备树配置

uart3: serial@021ec000 {
    
    compatible = "fsl,imx6ul-uart",
             "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x021ec000 0x4000>;
    interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_UART3_IPG>,
         <&clks IMX6UL_CLK_UART3_SERIAL>;
    clock-names = "ipg", "per";
    dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
    dma-names = "rx", "tx";
    status = "disabled";
};

pinctrl_uart3: uart3grp {
    
    fsl,pins = <
        MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0x1b0b1
        MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0x1b0b1
    >;
};

&uart3 {
    
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart3>;
    status = "okay";
};

五、串口驱动分析

// 用于和设备树匹配的表,和设备树中的兼容属性一致
static const struct of_device_id imx_uart_dt_ids[] = {
    
    {
     .compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },
    {
     .compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },
    {
     .compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },
    {
     /* sentinel */ }
};

// 平台驱动结构体
static struct platform_driver serial_imx_driver = {
    
    .probe      = serial_imx_probe,                
    .remove     = serial_imx_remove,

    .suspend    = serial_imx_suspend,
    .resume     = serial_imx_resume,
    .id_table   = imx_uart_devtype,
    .driver     = {
    
        .name   = "imx-uart",
        .of_match_table = imx_uart_dt_ids,
    },
};

// 驱动具体定义的uart_driver结构体,成员state指向的内存在uart_register_driver函数中根据uart port数量进行分配
static struct uart_driver imx_reg = {
    
    .owner          = THIS_MODULE,
    .driver_name    = DRIVER_NAME,                #define DRIVER_NAME "IMX-uart"
    .dev_name       = DEV_NAME,                  #define DEV_NAME        "ttymxc"   
    .major          = SERIAL_IMX_MAJOR,            #define SERIAL_IMX_MAJOR    207
    .minor          = MINOR_START,               #define MINOR_START     16
    .nr             = ARRAY_SIZE(imx_ports),
    .cons           = IMX_CONSOLE,
};
#define UART_NR 8
static struct imx_port *imx_ports[UART_NR];

// 模块初始化函数
static int __init imx_serial_init(void)   
{
    
    int ret = uart_register_driver(&imx_reg);     // 调用uart_register_driver函数向TTY层注册uart_driver

    if (ret)
        return ret;

    ret = platform_driver_register(&serial_imx_driver);    // UART本质上是一个platform 驱动,注册,自动匹配和调用probe函数
    if (ret != 0)
        uart_unregister_driver(&imx_reg);

    return ret;
}
// 模块退出函数
static void __exit imx_serial_exit(void)  // 驱动出口函数
{
    
    // 注销平台驱动
    platform_driver_unregister(&serial_imx_driver);  //调用 uart_unregister_driver 函数注销掉前面注册的 uart_driver
     // 注销uart_driver结构体
    uart_unregister_driver(&imx_reg);
}

module_init(imx_serial_init);
module_exit(imx_serial_exit);

tty核心层(上层)底层驱动层和tty层之间的联系需要从register_uart_driver中连接,tty_driver是在uart_driver注册过程中构建的。

// drivers/tty/serial/serial_core.c
uart_register_driver(struct uart_driver *drv)
    -> struct tty_driver *normal;
    // 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个uart_port,支持多少个串口,就开辟多少块空间 
    -> drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
    /* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */
    -> normal = alloc_tty_driver(drv->nr);
    -> drv->tty_driver = normal;                                  // tty_driver 赋值给uart_driver结构体中的tty_driver成员
    /* 对 tty_driver 进行设置 */
    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 = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
    normal->flags       = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    normal->driver_state    = drv;                                   // uart_driver 赋值给tty_driver结构体中的driver_state成员
    -> tty_set_operations(normal, &uart_ops);     // 设置tty驱动层的处理函数  将tty驱动的操作函数和uart的操作函数关联起来  将tty_driver的操作集统一设为了uart_ops.这样就使得从用户空间下来的操作可以找到正确的serial_core的操作函数
    -> struct uart_state *state = drv->state + i;
    -> struct tty_port *port = &state->port;      // driver->state->tty_port
    -> tty_port_init     // 初始化tty_port
    -> port->ops = &uart_port_ops;               // 操作tty上层函数
    -> tty_register_driver(normal)			      // 注册tty驱动
    
    static const struct tty_operations uart_ops = {
    
    .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,           // 当termios设置被改变时又tty核心调用
    .set_ldisc  = uart_set_ldisc,                 // 设置线路规程函数
    .stop       = uart_stop,
    .start      = uart_start,
    .hangup     = uart_hangup,                    // 挂起函数,当驱动挂起tty设备时调用
    .break_ctl  = uart_break_ctl,                 // 线路中断控制函数
    .wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
    .proc_fops  = &uart_proc_fops,
#endif
    .tiocmget   = uart_tiocmget,                  // 获得当前tty的线路规程的设置
    .tiocmset   = uart_tiocmset,                  // 设置当前tty线路规程的设置
    .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
};
// 设备层可以调用控制tty层
static const struct tty_port_operations uart_port_ops = {
    
    .activate   = uart_port_activate,
    .shutdown   = uart_port_shutdown,
    .carrier_raised = uart_carrier_raised,
    .dtr_rts    = uart_dtr_rts,
};

用户空间函数分析

// drivers/tty/tty_io.c
// 在uart_register_driver()函数中我们调用了每个tty类型的驱动注册时都会调用的tty_register_driver函数
tty_register_driver(struct tty_driver *driver)
    -> if (!driver->major)        //设备号未知的情况
    -> error = alloc_chrdev_region(&dev, driver->minor_start, driver->num, driver->name);
    -> else                      //设备号已知的情况
       dev = MKDEV(driver->major, driver->minor_start);
       error = register_chrdev_region(dev, driver->num, driver->name);
    -> tty_cdev_add(driver, dev, 0, driver->num);
    	-> cdev_init(&driver->cdevs[index], &tty_fops);          /* 创建字符设备,使用 tty_fops  index=0为传入得参数*/
     	-> driver->cdevs[index].owner = driver->owner;
     	-> cdev_add(&driver->cdevs[index], dev, count);
    -> list_add(&driver->tty_drivers, &tty_drivers);              /* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
    -> tty_register_device(driver, i, NULL);
    -> proc_tty_register_driver(driver);                          /* proc 文件系统注册driver */
    
static const struct file_operations tty_fops = {
    
    .llseek     = no_llseek,
    .read       = tty_read,
    .write      = tty_write,
    .poll       = tty_poll,
    .unlocked_ioctl = tty_ioctl,
    .compat_ioctl   = tty_compat_ioctl,
    .open       = tty_open,
    .release    = tty_release,
    .fasync     = tty_fasync,
};

自动匹配调用probe函数(serial_imx_probe):初始化 uart_port,然后将其添加到对应的 uart_driver 中

// drivers/tty/serial/imx.c
serial_imx_probe
    -> struct imx_port *sport;          // 定义一个imx_port类型的结构体指针变量sport  (后面有定义)
    -> devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);          // 为sport申请内存
    -> serial_imx_probe_dt(sport, pdev)                               // 获取设备树信息
    	-> of_match_device(imx_uart_dt_ids, &pdev->dev);           // 将设备树匹配表与设备进行匹配,获取设备对应的设备树匹配表
     	-> ret = of_alias_get_id(np, "serial");                    // 获取uart别名id,与aliases设备树节点有关
           -> sport->port.line = ret;                                 // 将uart别名id保存到uart_port结构体line成员中
           -> of_get_property(np, "fsl,uart-has-rtscts", NULL)        // 获取fsl,uart-has-rtscts属性,有就设置sport->have_rtscts = 1;
           -> of_get_property(np, "fsl,dte-mode", NULL)               // 获取fsl,dte-mode属性,有就设置sport->dte_mode = 1;
           -> sport->devdata = of_id->data;                           // 设置设备结构体的驱动数据成员,指向设备树匹配表中的date成员
    -> platform_get_resource(pdev, IORESOURCE_MEM, 0);                // 从设备树中获取uart外设寄存器首地址,即设备树中的reg属性   #define IORESOURCE_MEM      0x00000200
    -> devm_ioremap_resource(&pdev->dev, res);        		   // 得到寄存器首地址以后对其进行内存映射,得到对应的虚拟地址
    -> rxirq = platform_get_irq(pdev, 0);               		   // 获取接收中断(中断索引号为0),设备树中配置了此中断
        txirq = platform_get_irq(pdev, 1);  			   // 获取发送中断(中断索引号为1),设备树中没有配置此中断
        rtsirq = platform_get_irq(pdev, 2);   			   // 获取rts中断(中断索引号为2),设备树中没有配置此中断
   ------------------- 设置uart_port结构体 --------------------------
    sport->port.dev = &pdev->dev;
    sport->port.mapbase = res->start;				   // 设置映射前的基地址
    sport->port.membase = base;					   // 设置映射后的基地址
    sport->port.type = PORT_IMX,
    sport->port.iotype = UPIO_MEM;					   // IO类型为IO内存
    sport->port.irq = rxirq;					   // 设置接收中断
    sport->port.fifosize = 32; 					   // 设置fifo大小
    sport->port.ops = &imx_pops;					   // 设置uart硬件操作函数集合
    sport->port.rs485_config = imx_rs485_config;			   // 设置rs485配置函数
    sport->port.rs485.flags =
        SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
    sport->port.flags = UPF_BOOT_AUTOCONF;
     ------------------------------------------------------------------------------------------
    init_timer(&sport->timer);					   // 初始化定时器
    sport->timer.function = imx_timeout;				   // 设置定时器超时函数
    sport->timer.data     = (unsigned long)sport;			   // 设置定时器超时函数的参数
    sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");		   // 获取ipg时钟
    sport->clk_per = devm_clk_get(&pdev->dev, "per");		   // 获取per时钟
    sport->port.uartclk = clk_get_rate(sport->clk_per);		   // uart的时钟频率
   
    -> devm_request_irq(&pdev->dev, rxirq, imx_int, 0, dev_name(&pdev->dev), sport);    // 请求接收中断,中断服务函数为imx_int
    -> imx_ports[sport->port.line] = sport;                          // 保存设备结构体指针,数组索引为uart别名id
    // uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
    -> uart_add_one_port(&imx_reg, &sport->port)                     // 向 uart_driver 添加 uart_port         drivers/tty/serial/serial_core.c
    	-> if (uport->line >= drv->nr)                            // 检查uart端口数量是否超过了uart_driver中规定的数量 
     	-> state = drv->state + uport->line;			  // 根据串口编号找到具体串口的state地址
    	-> state->uart_port = uport;				  // 关联state中的port和具体的port
           -> uport->state = state;				  // 关联具体port中的state和uart_driver中的state
    	-> uart_configure_port(drv, state, uport);		  // 配置串口
    		-> port->ops->config_port(port, flags);		  // 调用imx_config_port配置串口
    			-> port.type = PORT_IMX    		  // 设置port类型为Motorola i.MX SoC
    		-> uart_report_port(drv, port);  		  // 打印串口配置信息
      		-> port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR);   // 调用imx_set_mctrl函数设置串口 设置串口模式
        // 注册tty设备 device *tty_port_register_device_attr(struct tty_port *port, struct tty_driver *driver, unsigned index, struct device *device, void *drvdata, const struct attribute_group **attr_grp)
          -> struct tty_port *port;         port = &state->port;
          -> tty_port_register_device_attr(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups);   
          		-> tty_port_link_device(port, driver, index);
    			-> driver->ports[index] = port;

// 封装设备结构体,包含uart_port
struct imx_port {
    
    struct uart_port    port;
    struct timer_list   timer;
    unsigned int        old_status;
    unsigned int        have_rtscts:1;
    unsigned int        dte_mode:1;
    unsigned int        irda_inv_rx:1;
    unsigned int        irda_inv_tx:1;
    unsigned short      trcv_delay; /* transceiver delay */
    struct clk      *clk_ipg;
    struct clk      *clk_per;
    const struct imx_uart_data *devdata;

    /* DMA fields */
    unsigned int        dma_is_inited:1;
    unsigned int        dma_is_enabled:1;
    unsigned int        dma_is_rxing:1;
    unsigned int        dma_is_txing:1;
    struct dma_chan     *dma_chan_rx, *dma_chan_tx;
    struct scatterlist  tx_sgl[2];
    struct imx_dma_rxbuf    rx_buf;
    unsigned int        tx_bytes;
    unsigned int        dma_tx_nents;
    struct delayed_work tsk_dma_tx;
    wait_queue_head_t   dma_wait;
    unsigned int            saved_reg[10];
#define DMA_TX_IS_WORKING 1
    unsigned long       flags;
};

// 最底层的驱动函数集合
static struct uart_ops imx_pops = {
    
    .tx_empty   = imx_tx_empty,
    .set_mctrl  = imx_set_mctrl,
    .get_mctrl  = imx_get_mctrl,
    .stop_tx    = imx_stop_tx,
    .start_tx   = imx_start_tx,
    .stop_rx    = imx_stop_rx,
    .enable_ms  = imx_enable_ms,
    .break_ctl  = imx_break_ctl,
    .startup    = imx_startup,
    .shutdown   = imx_shutdown,
    .flush_buffer   = imx_flush_buffer,
    .set_termios    = imx_set_termios,
    .type       = imx_type,
    .config_port    = imx_config_port,
    .verify_port    = imx_verify_port,
#if defined(CONFIG_CONSOLE_POLL)
    .poll_init      = imx_poll_init,
    .poll_get_char  = imx_poll_get_char,
    .poll_put_char  = imx_poll_put_char,
#endif
};

小结
可参考整理的TTY-UART结构体关系图

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43564241/article/details/122569859

智能推荐

让你的开发效率提高1400%的IntelliJ IDEA插件汇总_鲁班大叔 maven插件-程序员宅基地

文章浏览阅读1k次,点赞6次,收藏28次。哈喽,大家好,我是爱学习的莫提。从大二就开始接触并使用IDEA这款软件,现在它同样是我工作中的主力开发工具。对于Java开发人员来说,掌握这款软件的基本使用也已经是必备的工作技能。这款软件相比于Eclipse来说也是有非常多的优点,更加的高效,也更加的稳定。IDEA的插件市场也是非常的丰富!吸引了非常多的开发者从Eclipse来过渡到IDEA,那么正所谓「工欲善其事,必先利其器」,经过我这几年的使用,以及工作之后和同事的一些交流分享。我总结出了很多可以让我们的使用体验和开发效率得到很大程度提升的。_鲁班大叔 maven插件

Oracle SQL Loader的详细语法_sqlldr941-程序员宅基地

文章浏览阅读623次。Oracle SQL Loader 的详细语法 SQL*LOADER 是 ORACLE 的数据加载工具,通常用来将操作系统文件迁移到 ORACLE 数据库中。 S_sqlldr941

嵌入式和机械哪个好?机械转嵌入式好转吗?_机械转嵌入式那个方向好-程序员宅基地

文章浏览阅读2.9k次,点赞6次,收藏10次。大家好,我是无际。最近有一些做机械的朋友找到我,说嵌入式好不好学。深度聊完以后,我抓到了他们的几个吐槽点,行业过于传统,工资低,没前途,工作环境恶劣。看到身边那些做了10年的才1万块出头,心都凉了。如果不是看不到希望,谁也不会轻易冒险转行,毕竟付出的代价太大啦。我做了嵌入式单片机开发10年,除了技术以外,我还领悟到了很多经验。一、嵌入式和机械哪个好?首先要告诉你的是,不管任何技术都好,本身只是工具,有没有前途其实最大的因素是你怎么用这个工具去创造价值。做嵌入式开发也一样,_机械转嵌入式那个方向好

React native和原生之间的通信_react native promise 原生通信-程序员宅基地

文章浏览阅读7.3k次,点赞3次,收藏5次。RN中文网关于原生模块(Android)的介绍可以看到,RN前端与原生模块之间通信,主要有三种方法:1)使用回调函数Callback,它提供了一个函数来把返回值传回给JavaScript。2)使用Promise来实现。3)原生模块向JavaScript发送事件。关于使用回调,这是最简单的一种通信,这里可以看看官网的实现,今天要讲的是滴三种由原生模块向JavaScript发送事件。(1)首先,你需要_react native promise 原生通信

centos7 - 解决Python.h No such file or directory_centos7 fatal error: python.h: no such file or dir-程序员宅基地

文章浏览阅读1.5w次。解决Python.h No such file or directory 1.可以先查看一下含python-devel的包 yum search python | grep python-devel2.64位安装python-devel.x86_64,32位安装python-devel.i686,我这里安装: sudo yum install python-deve..._centos7 fatal error: python.h: no such file or directory

python中开根号函数_用二分法定义平方根函数(Bisection method Square Root Python)-程序员宅基地

文章浏览阅读905次。Python里面有内置(Built-in)的平方根函数:sqrt(),可以方便计算正数的平方根。那么,如果要自己定义一个sqrt函数,该怎么解决呢?解决思路:1. 大于等于1的正数n的方根,范围肯定在0~n之间;小于1的正数n的方根,范围肯定在0~1之间2. 用二分法(Bisection method, Binary search)从中间开始找n的方根。3. 对于大于等于1的正数n,先假设n/2是..._python怎么定义平方根筛选函数

随便推点

BUUCTF逆向刷题——[GKCTF2020]BabyDriver_do { *(_dword *)(((unsigned int)v7 & 0xfffffffc) +-程序员宅基地

文章浏览阅读471次。IDA打开在sub_140001380发现主函数然后在字符串里发现有东西双击跟进发现a0有东西,估计是个迷宫题。看代码` char v9; // dlCHAR *v10; // rcxv2 = a2;if ( *(_DWORD *)(a2 + ‘0’) >= 0 ){v3 = *(_QWORD *)(a2 + 24);v4 = *(_QWORD *)(a2 + 56) >> 3;if ( (_DWORD)v4 ){v5 = dword_1400030E4;_do { *(_dword *)(((unsigned int)v7 & 0xfffffffc) + v4) = 0; v4 += 4; } while

MFC简单的登入界面设计_mfc制作密码登录页面-程序员宅基地

文章浏览阅读5.8k次,点赞4次,收藏33次。原文地址:https://blog.csdn.net/to_baidu/article/details/53649796步骤一:登录界面设计首先在VS中设计一个简单的登录界面,点击【资源视图】,然后右键【Dialog】,选择【添加资源】——【Dialog】——【新建】,这样就创建了一个新的空白对话框。可以按自己的需要更改窗口的ID,并根据自己需要将窗口设计成登录的方式,例如我的设计如下: ..._mfc制作密码登录页面

mysql数据库ACID实现原理_acid原理-程序员宅基地

文章浏览阅读243次。说到事物的四大特性原子性、一致性、隔离性、持久性,懂的人很多,但是稍微涉及细节,四大特性在数据库中的实现原理是怎么实现的?几乎很少有人能够答上来。所以这里着重讨论下mysql中的实现原理。问题一:Mysql怎么保证一致性的?OK,这个问题分为两个层面来说。从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性_acid原理

docker-compose搭建skywalking服务并且与SpringBoot集成_springboot集成skywalking docker-程序员宅基地

文章浏览阅读1.5k次。服务搭建:基于docker-compose搭建单机版skywalking:docker-compose.yml文件内容version: '3.5'services: elasticsearch: image: elasticsearch:${ES_TAG} container_name: elasticsearch restart: always ports: - 9200:9200 environment: dis_springboot集成skywalking docker

叉乘和平行四边形面积 的关系_为什么叉乘是平行四边形面积-程序员宅基地

文章浏览阅读1.4w次,点赞8次,收藏10次。、其中的 过程解释如下_为什么叉乘是平行四边形面积

Ubuntu下安装QQ的方法_ubuntuqq安葬添加信任-程序员宅基地

文章浏览阅读849次。Ubuntu安装QQ方法1下载:链 接:http://pan.baidu.com/s/1pJ9D1OZ提取密码: lbq02解压:7zxwineTM.7z解压出来了,有一个安装脚本tm2013_install.sh,和两个隐藏目录.wine和.local,你只需要运行tm2013_install.sh脚本就可以完成安装wine和配置tm2013了。_ubuntuqq安葬添加信任

推荐文章

热门文章

相关标签