[Linux 驱动] -- Linux 驱动之串口(UART)

一、UART 驱动程序概述

在嵌入式 Linux 系统中,串口被看成终端设备,终端设备(tty)的驱动程序分为三个部分:

  1. tty_core
  2. tty_disicipline
  3. tty_driver

包括3个结构体:

  1. uart_driver
  2. uart_port
  3. uart_ops( include/serial_core.h)

因此实现一个平台的 UART 驱动程序只需要实现这3个结构体即可。

二、uart_drvier 与 tty_driver 之间的关系

uart_driver 结构体:

uart_driver 结构体包含了串口设备名、串口驱动名、主次设备号、串口控制台(可选)等信息,还封装了 tty_driver(底层串口驱动,无需关心 tty_driver)。

struct uart_driver
{
    struct module *owner; //拥有该uart_driver的模块,一般为THIS_MODULE
    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;
    struct tty_driver *tty_driver; //uart_driver 封装了 tty_driver,使底层uart驱动不用关心tty_driver。
};
  • 一个 tty 驱动程序必须注册/注销 tty_driver;
  • 一个 uart 驱动则变为注册/注销 uart_driver;

使用如下接口:

int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_drvier *drv);
int tty_register_driver(struct tty_driver *drv);
void tty_unregister_driver(struct tty_driver *drv);

实际上,uart_register_driver() 和 uart_unregister_drvier() 中分别包含了 tty_register_driver() 和 tty_unregister () 的操作,详情如下:

uart_port结构体:

uart_port 用于描述一个 UART 端口(直接对应于一个串口)的 I/O 端口或 I/O内存地址、FIFO大小、端口类型等信息。

struct uart_port
{
    spinlock_t lock; //串口端口锁
    unsigned int iobase; //IO 端口基地址
    unsigned char __iomem *membase; //IO 内存基地址,经映射(如ioremap)后的IO内存虚拟基地址
    unsigned int irq; //中断号
    unsigend int uartlock; //串口时钟
    unsigend int fifosize; //串口FIFO缓冲大小
    unsigned char x_char; //xon/xoff 字符
    unsigned char regshift; //寄存器位移
    unsigned char iotype; //IO访问方式
    unsigned char unused1; 
    
    #define UPIO_PORT (0) //IO端口
    #deifne UPIO_HUB6 (1)
    #define UPIO_MEM  (2) //IO内存
    #define UPIO_MEM32(3)
    #define UPIO_AU (4) //Aulx00 type IO
    #define UPIO_TSI (5) //Tsi108/109 type IO
    #define UPIO_DWAPB (6) //DesignWare APB UART
    #define UPIO_RM9000 (7) //RM9000 type IO
    
    unsigned int read_status_mask; //关心的Rx error status
    unsigned int ignore_status_mask; //忽略的Rx error status
    struct uart_info *info; //重要,见下面
    struct uart_icount icount; //计数器 uart_icount 为串口信息计数器,包含了发送字符计数、接收字符计数等。在串口的发送中断处理函数和接收处理函数中,我们需要管理这些计数。

    struct console *cons; //console 结构体
    #ifdef CONFIG_SERIAL_CORE_CONSOLE
        unsigned long sysrq; //sysrq timeout
    #endif

    upf_t flags;
    
    #define UPF_FOURPORT ((__forceupf_t)(1 << 1))
    #define UPF_SAK ((__forceupf_t)(1 << 2))
    #define UPF_SPD_MASK ((__forceupf_t)(0x1030))
    #define UPF_SPD_HI ((__forceupf_t)(0x0010))
    #define UPF_SPD_VHI ((__forceupf_t)(0x0020))
    #define UPF_SPD_CUST ((__forceupf_t)(0x0030))
    #define UPF_SPD_SHI ((__forceupf_t)(0x1000))
    #define UPF_SPD_WARP ((__forceupf_t)(0x1010))
    #define UPF_SKIP_TEST ((__forceupf_t)(1 << 6))
    #define UPF_AUTO_IRQ ((__forceupf_t)(1 << 7))
    #define UPF_HARDPPS_CD ((__forceupf_t)(1 << 11))
    #define UPF_LOW_LATENCY ((__forceupf_t)(1 << 13))
    #define UPF_BUGGY_UART ((__forceupf_t)(1 << 14))
    #define UPF_MAGIC_MULTIPLIER((__force upf_t)(1 << 16))
    #define UPF_CONS_FLOW ((__forceupf_t)(1 << 23))
    #define UPF_SHARE_IRQ ((__forceupf_t)(1 << 24))
    #define UPF_BOOT_AUTOCONF ((__forceupf_t)(1 << 28))
    #define UPF_FIXED_PORT ((__forceupf_t)(1 << 29))
    #define UPF_DEAD ((__forceupf_t)(1 << 30))
    #define UPF_IOREMAP ((__forceupf_t)(1 << 31))

    #define UPF_CHANGE_MASK((__forceupf_t)(0x17fff))
    #define UPF_USR_MASK ((__forceupf_t)(UPF_SPD_MASK | UPF_LOW_LATENCY))

    unsigned int mctrl; //当前的 moden 设置
    unsigned int timeout; //character-based timeout
    unsigned int type; //端口类型
    const struct uart_ops *ops; //串口端口操作函数集
    unsigned int custom_divisor;
    unsigned int line; //端口索引
    resource_size_t mapbase; //IO内存物理基地址,可用于ioremap
    struct device *dev; //父设备
    unsigned char hub6; //this should be in the 8250 driver
    unsigned char suspended;
    unsigend char unused[2];
    void *private_data; //端口私有数据,一般为platform数据指针
};

串口核心层提供如下函数来添加一个端口:

  • int uart_add_one_port(struct uart_driver *drv, struct uart_port *port);

对上述函数的调用应该发生在 uart_register_driver() 之后, uart_add_one_port() 的一个最重要的作用是封装了 tty_register_device()。

uart_add_one_port() 的“反函数”是 uart_remove_one_port(),其中会调用 tty_unregister_device(), 原型为:

int uart_remove_one_port(struct driver *drv, struct uart_port *port);

 

uart_info结构体:

uart_info 有两个成员在底层串口驱动会用到: xmit 和 tty。用户空间程序通过串口发送数据时,上层驱动将用户数据保存在xmit;而串口发送中断处理函数就是通过xmit获取到用户数据并将它们发送出去。串口接收中断处理函数需要通过tty将接收到的数据传递给行规则层。

在使用串口核心层这个通用串口tty驱动层的接口后,一个串口驱动要完成的主要工作将包括:

  1. 定义uart_drvier、uart_ops、uart_port等结构体的实例,并在适当的地方根据具体硬件和驱动的情况初始化它们。(当然具体设备xxx的驱动可以将这些结构套在新定义的 xxx_uart_driver、xxx_uart_ops、xxx_uart_port之内)。
  2. 在模块初始化时调用uart_register_driver() 和 uart_add_one_port()以注册UART驱动并添加端口,在模块卸载时调用uart_unregister_driver 和 uart_remove_one_port() 以注销UART驱动并移除端口。
  3. 根据具体硬件的datasheet实现uart_ops中的成员函数,这些函数的实现成为UART驱动的主体工作。

串口驱动之tty

概念介绍:

在Linux中,终端是一类字符设备,他包括多种类型,通常使用tty来简称各种中断设备串口终端(/dev/ttyS*):串口终端是使用串口连接的终端设备,Linux中将每一个串口设备都看作一个字符设备,这些串行端口对应的设备名称是/dev/ttySAC0 和 /dev/ttySAC1。

控制台终端(/dev/console):

在Linux中,计算中的输出设备通常被称为控制台终端(console)。这里特指printk()信息输出的。注意:/dev/console 是一个虚拟的设备,它需要映射到真正的tty上。比如通过内核启动参数“console=ttySAC0”就是把console映射到串口0,经常被内核所使用。

注意:这里的终端是一个虚拟设备,虚拟设备必须和实际的设备联系起来,console=ttySAC0系统启动的时候就关联起来了。

虚拟终端(/dev/tty*):

当用户登录的使用使用的是虚拟终端,使用快捷键组合:ctrl+alt+[F1-F6]组合键就可以切换到tty1,tty2,tty3等上面去。tty1-tty6等成为虚拟终端,而tty0是当前使用的终端的一个别名。主要是提供给应用程序使用。

tty架构

tty核心:

tty核心是对整个tty设备的抽象,并提供单一的接口。

tty线路规划:

tty线路规程是对数据的传输的格式化,比如需要实现某种协议,就需要将协议的实现代码放在该位置。

tty驱动:

是面向tty设备的硬件驱动

注意:Linux中的获取回溯信息使用函数dump_stack()用来显示各种函数的调用信息。

串口驱动程序的结构

分析:串口驱动程序需要提供给用户读数据的功能,写数据,打开串口和关闭串口的功能。打开之前需要对肯定需要对串口进行初始化的工作。

重要数据结构:

  1. struct uart_driver :一个串口对应一个串口驱动,用于描述串口结构
  2. struct uart_port:    有几个串口就对应几个port
  3. struct uart_ops:  UART相关操作函数结构体,对应相关串口所支持的操作函数集
  4. struct uart_state:UART状态结构
  5. struct uart_info: UART信息结构

串口初始化:

  1. 定义并描述串口:struct uart_driver;
  2. 注册串口驱动程序:uart_register_driver;
  3. 取出相应的串口,并初始化该取出的串口。

串口驱动之打开驱动:

系统调用过程:用户使用open()函数打开设备文件

注意:

  1. 打开设备文件肯定有对应的设备驱动文件打开函数:file_operations;
  2. 在使用uart_register_driver()注册串口驱动的时候,该函数里面会调用函数tty_register_driver(),该函数会调用 cdev_init()函数和cdev_add()。
  3. 从这里可以看出tty设备是属于字符设备。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值