Linux驱动开发笔记(十一)tty子系统及其驱动

文章目录

  • 前言
  • 一、串口驱动框架
    • 1.1 核心数据结构
    • 1.2 数据处理流程
  • 二、驱动编写
    • 1. 设备树的修改
    • 2. 相关API函数
    • 3. 驱动框架
    • 4. 具体功能的实现
      • 4.1 出入口函数的编写
      • 4.2 读写函数


前言

  之前已经讲过应用层的应用,接下来我们继续进行驱动的学习。其实实际上我们很少主动进行串口的驱动编写,通常情况下只需要进行应用层的应用就可以了,网络上相关的驱动内容介绍也较少,这里仅作了解并简单了解一下架构即可。


一、串口驱动框架

  串口驱动没有什么主机端和设备端之分,就只有一个串口驱动,而且这个驱动也已经由厂商已经编写好了,我们真正要做的就是在设备树中添加所要使用的串口节点信息。当系统启动以后串口驱动和设备匹配成功,相应的串口就会被驱动起来,生成/dev/ttymxcX(X=0….n)文件。
  这部分内容还是比较多的,笔者发现这个文章讲述的还是很详细的,感兴趣可以自行查阅,这里不再赘述:框架详细介绍

1.1 核心数据结构

  对于串口驱动来说,实际上重要的结构体只有uart_drive和uart_port,这里可能根据不同的厂商所采用的名称不大一样,但内容差不多,比如在rk3568中,官方提供的uart驱动程序位8250通用串口程序,其为uart_port结构体提供了扩展后的uart_8250_port,包含8250 UART特有的属性和操作。
  这部分内容保存于内核中的下列文件中,感兴趣可以自行查阅:

  •  serial_core.c:包含UART核心层的实现。
  •  8250.c:包含8250 UART特定操作的实现。
  •  8250_port.c:包含8250 UART端口的具体实现。

  uart_driver结构体用于描述一个UART驱动程序,它通常包含一些基础的信息和操作函数。

struct uart_driver {
    struct module *owner;               // 设备驱动模块的所有者
    const char *driver_name;            // 驱动程序的名称
    const char *dev_name;               // 设备名称
    int major;                          // 设备主设备号
    int minor;                          // 设备次设备号
    int nr;                             // 该驱动程序支持的设备数量
    struct console *cons;               // 控制台相关信息
    struct uart_ops *ops;               // UART操作函数集合
    
	struct uart_state *state;
	struct tty_driver *tty_driver;
};

  uart_8250_port结构体用于描述8250 UART端口的具体信息和操作方法。

struct uart_8250_port {
    struct uart_port port;              // 通用UART端口结构体
    struct timer_list timer;            // 定时器用于处理超时
    unsigned int capabilities;          // 硬件能力
    unsigned int mcr;                   // 调制解调器控制寄存器
    unsigned int lsr;                   // 线路状态寄存器
    unsigned int msr;                   // 调制解调器状态寄存器
    unsigned int icr;                   // 中断控制寄存器
    unsigned int lcr;                   // 线路控制寄存器
    unsigned int fcr;                   // FIFO控制寄存器
    unsigned int ier;                   // 中断使能寄存器
    unsigned char mcr_mask;             // 调制解调器控制寄存器掩码
    unsigned char mcr_force;            // 调制解调器控制寄存器强制值
    unsigned char lsr_break_flag;       // 断开标志
    unsigned char bugs;                 // 硬件错误标志
    unsigned int tx_loadsz;             // 发送FIFO加载大小
    unsigned int acr;                   // 额外控制寄存器
    unsigned int ier_mask;              // 中断使能寄存器掩码
    unsigned int ier_force;             // 中断使能寄存器强制值
    unsigned int lsr_mask;              // 线路状态寄存器掩码
    unsigned int lsr_break_flag_mask;   // 断开标志掩码
};

//通用结构体,但比较鸡肋
struct uart_port {
 spinlock_t lock; /* port lock */
 unsigned long iobase; /* in/out[bwl] */
 unsigned char __iomem *membase; /* read/write[bwl] */
......
 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 */

  这里补充一下在uart_driver提到的实现控制台打印功能必须要注册的结构体console和将uart_port与对应的circ_buf联系起来的uart_state结构体。

struct console {
      char name[16];
      void(*write)(struct console *const char *, unsigined);
      int (*read)(struct console *, char *, unsigned);
      struct tty_driver *(struct console *,int*);
      void (*unblank)(void);
      int  (*setup)(struct console *, char *);
      int  (*early_setup)(void);
      short  flags;
      short  index; /*用来指定该console使用哪一个uart port (对应的uart_port中的line),如果为-1,kernel会自动选择第一个uart port*/
      int   cflag;
      void  *data;
      struct   console *next;
};

/*uart_state有两个成员在底层串口驱动会用到,即xmit和port。
用户空间程序通过串口发送数据时,上层驱动将用户数据保存在xmit;而串口发送中断处理函数就是通过xmit获取到用户数据并将它们发送出去。
串口接收中断处理函数需要通过port将接收到的数据传递给线路规程层。
*/
struct uart_state {
       struct  tty_port  port;
       
       enum uart_pm_state   pm_state;
       struct circ_buf     xmit;
       
       struct uart_port     *uart_port; /*对应于一个串口设备*/
}

  uart_ops结构体几乎涵盖了驱动可对串口的所有操作:

 struct uart_ops {
        unsigned int    (*tx_empty)(struct uart_port *);
        void            (*set_mctrl)(struct uart_port *, unsigned int mctrl);
        unsigned int    (*get_mctrl)(struct uart_port *);
        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 *);
        void            (*break_ctl)(struct uart_port *, int ctl);
        int             (*startup)(struct uart_port *);
        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 *, int new);
        void            (*pm)(struct uart_port *, unsigned int state,
                              unsigned int oldstate);
        int             (*set_wake)(struct uart_port *, unsigned int state);
 
        /*
         * Return a string describing the type of the port
         */
        const char      *(*type)(struct uart_port *);
 
        /*
         * Release IO and memory resources used by the port.
         * This includes iounmap if necessary.
         */
        void            (*release_port)(struct uart_port *);
 
        /*
         * Request IO and memory resources used by the port.
         * This includes iomapping the port if necessary.
         */
        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
};

1.2 数据处理流程

  相较于前面学习的spi子系统和iic子系统,uart驱动通常情况不需要根据协议的时序额外编写新的传输函数,这里可以直接提供write函数和read函数实现数据的互传。
在这里插入图片描述
  这部分的内容我们在应用层实验的时候已经详细介绍过了,感兴趣可以回顾一下。

二、驱动编写

  我们这里采用的8250通用驱动,对于不同驱动其函数名称和入口参数可能有所不同,这里我们简单学习梳理一下框架即可,通常来说我们是只需要进行应用层的编写。

1. 设备树的修改

代码如下(示例):

uart3: serial@fe670000 {
         compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
         reg = <0x0 0xfe670000 0x0 0x100>;
         interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
         clocks = <&cru SCLK_UART3>, <&cru PCLK_UART3>;
         clock-names = "baudclk", "apb_pclk";
         reg-shift = <2>;
         reg-io-width = <4>;
         dmas = <&dmac0 6>, <&dmac0 7>;
         pinctrl-names = "default";
         pinctrl-0 = <&uart3m0_xfer>;
         status = "disabled";
     };

//uart3的复用
uart3 {
         /omit-if-no-ref/
        uart3m0_xfer: uart3m0-xfer {
             rockchip,pins =
                 /* uart3_rxm0 */
                 <1 RK_PA0 2 &pcfg_pull_up>,
                 /* uart3_txm0 */
                 <1 RK_PA1 2 &pcfg_pull_up>;
        };
 
         /omit-if-no-ref/
         uart3m0_ctsn: uart3m0-ctsn {
             rockchip,pins =
                /* uart3m0_ctsn */
                <1 RK_PA3 2 &pcfg_pull_none>;
         };

         /omit-if-no-ref/
         uart3m0_rtsn: uart3m0-rtsn {
             rockchip,pins =
                 /* uart3m0_rtsn */
                  <1 RK_PA2 2 &pcfg_pull_none>;
		};
 
         /omit-if-no-ref/
         uart3m1_xfer: uart3m1-xfer {
             rockchip,pins =
                 /* uart3_rxm1 */
                 <3 RK_PC0 4 &pcfg_pull_up>,
                 /* uart3_txm1 */
                 <3 RK_PB7 4 &pcfg_pull_up>;
         };
     };

  接着根据我们的需要继续编写即可,泰山派这里已经默认将uart3配置好了,这里可以直接调用。

//用户串口3
&uart3 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart3m1_xfer>;
     ...
 };

2. 相关API函数

//注册 uart_driver
int uart_register_driver(struct uart_driver *drv)
  • 参数
    • drv :要注册的 uart_driver
  • 返回值
    • 成功:0
    • 失败:负值
//注销uart_driver
void uart_unregister_driver(struct uart_driver *drv)
  • 参数
    • drv :要注销的 uart_driver
  • 返回值:无
//
int serial8250_register_8250_port(struct uart_8250_port *up);
  • 参数
    • up:指向 uart_8250_port 结构体的指针,该结构体包含了要注册的 8250 UART 端口的所有必要信息。
  • 返回值
    • 成功:端口的线路号(line)
    • 失败:错误码
void serial8250_unregister_port(int line);
  • 参数
    • line:要注销的 UART 端口的线路号(line),通常是在调用 serial8250_register_8250_port 时返回的值。
  • 返回值:无

3. 驱动框架

  实际上,我们可以简单地将uart驱动理解为是一个 platform 驱动在驱动入口函数中调用uart_register_driver 函数向 Linux 内核注册 uart_driver,在驱动出口函数中调用uart_unregister_driver 函数注销掉前面注册的 uart_driver,当设备和驱动匹配成功以后进入probe 函数,此函数的重点工作就是初始化uart_8250_port(uart_port),然后将其添加到对应的uart_driver 中。在初始化uart_port过程中,设置 uart_ops 为对应的 XXX_ops即可。

4. 具体功能的实现

4.1 出入口函数的编写

static int __init my_uart_init(void)
{
    int ret;

    // 初始化uart_driver结构体
    my_uart_driver = (struct uart_driver) {
        .owner      = THIS_MODULE,
        .driver_name= DRIVER_NAME,
        .dev_name   = "ttyMY",
        .major      = TTY_MAJOR,
        .minor      = 64,
        .nr         = 1,
    };

    // 注册UART驱动
    ret = uart_register_driver(&my_uart_driver);
    if (ret)
        return ret;

    // 初始化uart_8250_port结构体
    my_uart_port.port = (struct uart_port) {
        .iotype     = UPIO_PORT,
        .mapbase    = UART_BASE,
        .irq        = UART_IRQ,
        .uartclk    = UART_CLOCK,
        .fifosize   = 16,
        .ops        = &my_uart_ops,
        .line       = 0,
    };

    // 注册8250 UART端口
    line = serial8250_register_8250_port(&my_uart_port);
    if (line)
        uart_unregister_driver(&my_uart_driver);

    return line;
}

static void __exit my_uart_exit(void)
{
    serial8250_unregister_port(line);
    uart_unregister_driver(&my_uart_driver);
}

module_init(my_uart_init);
module_exit(my_uart_exit);

4.2 读写函数

static void my_uart_start_tx(struct uart_port *port)
{
    struct uart_8250_port *up = (struct uart_8250_port *)port;
    unsigned int ier;

    spin_lock_irq(&up->port.lock);

    // 启用发送中断
    ier = serial_in(up, UART_IER);
    serial_out(up, UART_IER, ier | UART_IER_THRI);

    spin_unlock_irq(&up->port.lock);
}

static void my_uart_stop_tx(struct uart_port *port)
{
    struct uart_8250_port *up = (struct uart_8250_port *)port;
    unsigned int ier;

    spin_lock_irq(&up->port.lock);

    // 禁用发送中断
    ier = serial_in(up, UART_IER);
    serial_out(up, UART_IER, ier & ~UART_IER_THRI);

    spin_unlock_irq(&up->port.lock);
}

static irqreturn_t my_uart_interrupt(int irq, void *dev_id)
{
    struct uart_8250_port *up = dev_id;
    unsigned int iir = serial_in(up, UART_IIR);

    // 检查中断类型并处理
    if (!(iir & UART_IIR_NO_INT)) {
        if (iir & UART_IIR_RDI)
            serial8250_rx_chars(up);
        if (iir & UART_IIR_THRI)
            serial8250_tx_chars(up);
        return IRQ_HANDLED;
    }

    return IRQ_NONE;
}

免责声明:本内容部分参考野火科技及其他相关公开资料,若有侵权或者勘误请联系作者。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/742980.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux显示服务器Wayland切换到X11

1、临时切换 &#xff1a; 注销当前用户账户&#xff0c;返回到登录屏幕。 在登录屏幕上&#xff0c;选择您要登录的用户账户。 在输入密码之前&#xff0c;在登录屏幕的右下角可能有一个齿轮图标&#xff0c;点击它以展开更多选项。 在选项中选择“Ubuntu on Xorg”或“Ubu…

从0开始学做质量工程师,只需6个月成为专业的质量管理者

欢迎来到优思学院的特别讲座——从零开始学质量工程师&#xff0c;只需6个月&#xff01;在这篇博客中&#xff0c;我们将分享满满的干货&#xff0c;帮助你在短时间内掌握成为质量工程师所需的知识和技能。无论你是刚踏入职场的新人&#xff0c;还是希望提升自身竞争力的在职人…

【ACM出版】第13届亚洲膜计算会议(ACMC2024)暨 2024年机器学习、模式识别与自动化工程国际学术会议(MLPRAE 2024,8月7日-9)

第13届亚洲膜计算会议&#xff08;ACMC2024&#xff09;暨2024年机器学习、模式识别与自动化工程国际学术会议(MLPRAE 2024) 将于2024年8月7日-9日在新加坡举行。它致力于为机器学习、模式识别与自动化工程领域的专家和学者之间的学术交流创造一个平台。 会议的理念是让来自世…

寄存器组(堆栈指针寄存器小解)

寄存器组&#xff08;堆栈指针寄存器小解&#xff09; 寄存器组栈是向下伸长的出入栈操作时候的SP寄存器 例子 寄存器组 主堆栈指针&#xff08;MSP&#xff09;&#xff1a;这是缺省的堆栈指针&#xff0c;它由 OS 内核、异常服务例程以及所有需要特权访问的 应用程序代码来使…

kubernetes pod 最小可部署计算单元

1 工作负载&#xff08;workloads&#xff09; 工作负载&#xff08;workload&#xff09;是在kubernetes集群中运行的应用程序。无论你的工作负载是单一服务还是多个一同工作的服务构成&#xff0c;在kubernetes中都可以使用pod来运行它。 workloads分为pod与controllers p…

Mysql中varchar类型数字排序不对踩坑记录

场景 在进行表设计时将版本号字段设计了为varchar类型&#xff0c;尽量从表设计阶段将数字类型列设计为int型。 再进行排序时如果版本号累计到了10及以上&#xff0c;那么再进行排序时则会出现问题。 比如下面执行排序时发现10被排在了第一位。 这是因为 varchar类型对数字…

数据增强 data augmentation(在PyTorch中,data_transforms通常用于数据集处理,并且经常用于数据增强)

数据增强是一种在训练深度学习模型时&#xff0c;通过对输入数据进行变换和修改的方法&#xff0c;以增加训练数据集的大小和多样性&#xff0c;从而提高模型的泛化能力和性能。 PS: PyTorch中&#xff0c;data_transforms具体使用代码可以查看这篇文章&#xff1a; pytor…

2024年全国VUE考试中心大全!

大家好&#xff0c;华为HCIA、HCIP、HCIE的笔试部分&#xff0c;都需要在VUE考试中心进行预约。但是很多同学都不知道当地VUE考试中心在哪里&#xff01; 为了解决大家的问题&#xff0c;这边整理了全国各大城市的VUE考试中心名称和详细地址。需要的小伙伴们可以来看看&#x…

Python联动Mysql

首先配置pip源(不然在安装库的时候会很慢!!!) pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/安装必要库: mysql.connector MySQL 连接器/ODBC 是 MySQL ODBC 驱动程序&#xff08;以前称为 MyODBC 驱动程序&#xff09;系列的名称&#xff0c;它使…

Nuxt3 的生命周期和钩子函数(一)

title: Nuxt3 的生命周期和钩子函数&#xff08;一&#xff09; date: 2024/6/25 updated: 2024/6/25 author: cmdragon excerpt: 摘要&#xff1a;本文是关于Nuxt3的系列文章之一&#xff0c;主要探讨Nuxt3的生命周期和钩子函数&#xff0c;引导读者深入了解其在前端开发中…

食品供应链管理商城系统的设计、实现和代码

上线食品供应链管理商城系统的设计与实现是一项复杂且重要的任务&#xff0c;它不仅涉及到技术层面的具体实现&#xff0c;还包括业务流程的优化和用户体验的提升。本文将从系统设计、功能模块、技术选型以及实现步骤等方面进行详细探讨。 ### 系统设计 在设计食品供应链管理…

JVM虚拟机的组成

一、为什么要学习 JVM &#xff1f; 1. “ ⾯试造⽕箭&#xff0c;⼯作拧螺丝” &#xff0c; JVM 属于⾯试官特别喜欢提问的知识点&#xff1b; 2. 未来在⼯作场景中&#xff0c;也许你会遇到以下场景&#xff1a; 线上系统突然宕机&#xff0c;系统⽆法访问&#xff0c;甚⾄直…

【CT】LeetCode手撕—46. 全排列

目录 题目1- 思路2- 实现⭐46. 全排列——题解思路 3- ACM实现 题目 原题连接&#xff1a;46. 全排列 1- 思路 模式识别 模式1&#xff1a;不含重复数字的数组 nums ——> 任意顺序 可能的全排列 ——> 回溯模式2&#xff1a;全排列 ——> 排列问题&#xff0c;不同…

Verilog HDL语法入门系列(四):Verilog的语言操作符规则(下)

目录 7.移位操作符8.关系操作符9.相等操作符9.1逻辑等与case等9.2逻辑等与逻辑不等9.3 case等与case不等 10.条件操作符11.级联操作符12.复制 微信公众号获取更多FPGA相关源码&#xff1a; 7.移位操作符 符号含义>>逻辑右移<<逻辑左移 移位操作符对其左边的操作…

达梦(DM8)数据库表空间的备份与还原(联机备份) 四

一、表空间的备份 1、备份表空间的命令操作 backup tablespace main backupset /home/dmdba/dmdata/DAMENG/bak/full_back_01 ; 2、检查表空间的备份文件 select sf_bakset_check(disk,/home/dmdba/dmdata/DAMENG/bak/full_back_01); 二、表空间的还原 1、修改表空间位脱机…

qt pro文件常用配置

概述 记录一下常用的项目pro文件的一些常用配置 常用配置 QT core gui concurrent#添加concurrent并行处理模块 CONFIG windeployqt#打包部署&#xff0c;项目->构建步骤->Make参数 添加windeployqt&#xff0c;编译自动打包greaterThan(QT_MAJOR_VERSION, 4):…

计网入门还没到放弃

TCP报文段格式 源端口&#xff1a;标识报文的返回地址 目的端口&#xff1a;指明计算机上的应用程序接口 序号&#xff1a;通过SYN包传给接收端主机&#xff0c;每传送一次就1&#xff0c;用来解决网络包乱序的问题。 确认号&#xff1a;期望下一次收到的数据的序列号&#xff…

WMS可以为制造企业解决什么问题?

在快速变化、高度竞争的制造业环境中&#xff0c;仓库不仅是储存物料的地方&#xff0c;更是企业运营的“心脏”。然而&#xff0c;随着业务的扩展和产品种类的增多&#xff0c;仓库管理变得越来越复杂&#xff0c;传统的管理方式已经难以满足现代企业的需求。这时&#xff0c;…

舆论中心的《黑神话:悟空》:人们总希望,这只猴子能打破些什么

距离《黑神话&#xff1a;悟空》上线还有60天。外界关于游戏的争议有很多&#xff0c;但游戏科学却很少出来回应什么。 6月9日&#xff0c;博主兲虎发文称&#xff0c;《黑神话&#xff1a;悟空》之所以在发布宣传视频后&#xff0c;一直遭受到所谓性别歧视的攻击与污蔑&#…

Java项目毕业设计:基于springboot+vue的幼儿园管理系统

数据库:MYSQL5.7 **应用服务:Tomcat7/Tomcat8 使用框架springbootvue** 项目介绍 管理员&#xff1b;首页、个人中心、用户管理、教师管理、幼儿信息管理、班级信息管理、工作日志管理、会议记录管理、待办事项管理、职工考核管理、请假信息管理、缴费信息管理、幼儿请假管理…