一、UART 驱动程序概述
在嵌入式 Linux 系统中,串口被看成终端设备,终端设备(tty)的驱动程序分为三个部分:
包括3个结构体:
因此实现一个平台的 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。
};
使用如下接口:
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数据指针
};
串口核心层提供如下函数来添加一个端口:
对上述函数的调用应该发生在 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驱动层的接口后,一个串口驱动要完成的主要工作将包括:
串口驱动之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()用来显示各种函数的调用信息。
串口驱动程序的结构
分析:串口驱动程序需要提供给用户读数据的功能,写数据,打开串口和关闭串口的功能。打开之前需要对肯定需要对串口进行初始化的工作。
重要数据结构:
串口初始化:
串口驱动之打开驱动:
系统调用过程:用户使用open()函数打开设备文件
注意: