uart驱动框架及编程方法
一、UART介绍
UART(Universal Asynchronous Receiver/Transmitter),中文全称为通用异步收发传输器,是一种异步收发传输器,它将要传输的数据通过并行到串行转换后再进行传输。该总线双向通信,可以实现全双工传输和接收。在嵌入式设备中,UART 用于主机与辅助设备通信。
1. 通信协议
UART通信协议的工作原理是将传输数据的每个比特位一位接一位地传输。其中各比特的意义如下:
起始位:在时钟线为高电平时,数据线发出一个逻辑”0”的信号,表示传输字符的开始。
数据位:紧接着起始位之后。数据位的个数可以是5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。
奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性 。
停止位:在时钟线为高电平时,数据线发出一个逻辑”1”的信号。可以是1位、1.5位、2位的高电平。
由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
- 空闲位:当时钟线和数据新都处于逻辑“1”状态,表示当前线路上没有数据传送。
2. 波特率
波特率是衡量数据传送快慢的指标。表示每秒钟传送的符号数(symbol)。一个符号代表的信息量(比特数)与符号的阶数有关。例如传输使用256阶符号,每8bit代表一个符号,数据传送速率为120字符/秒,则波特率就是120 baud,比特率是120*8=960bit/s。这两者的概念很容易搞错。
UART 的接收和发送是按照相同的波特率进行收发的。波特率发生器产生的时钟频率不是波特率时钟频率,而是波特率时钟频率的16倍,目的是为在接收时进行精确的采样,以提取出异步的串行数据。根据给定的晶振时钟和要求的波特率,可以算出波特率分频计数值。
3. 工作原理
发送数据过程:空闲状态,线路处于高电位;当收到发送数据指令后,拉低线路一个数据位的时间T,接着数据位按低位到高位依次发送,数据发送完毕后,接着发送奇偶检验位和停止位(停止位为高电位),一帧数据发送结束。
接收数据过程: 空闲状态,线路处于高电位;当检测到线路的下降沿(线路电位由高电位变为低电位)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶检验位是否正确,如果正确则通知则通知后续设备准备接收数据或存入缓存。
采用率:UART是异步传输,没有传输同步时钟。为了能保证数据传输的正确性,UART采用16倍数据波特率的时钟进行采样。每个数据有16个时钟采样,取中间的采样值,以保证采样不会滑码或误码。一般UART一帧的数据位为8,这样即使每一个数据有一个时钟的误差,接收端也能正确地采样到数据。
接收数据时序为:当检测到数据下降沿时,表明线路上有数据进行传输,这时计数器CNT开始计数,当计数器为8时,采样的值为“0”表示开始位;当计数器为
24=16*1+8
时,采样的值为bit0数据;当计数器的值为40=16*2+8
时,采样的值为bit1数据;依次类推,进行后面6个数据的采样。如果需要进行奇偶校验位,则当计数器的值为152=16*9+8
时,采样的值为奇偶位;当计数器的值为168=16*10+8
时,采样的值为“1”表示停止位,一帧数据收发完成。
4. 流控
数据在两个串口传输时,常常会出现丢失数据的现象,或者两台计算机的处理速度不同,如台式机与单片机之间的通讯,接收端数据缓冲区以满,此时继续发送的数据就会丢失,流控制能解决这个问题,当接收端数据处理不过来时,就发出“不再接收”的信号,发送端就停止发送,直到收到“可以继续发送”的信号再发送数据。因此流控制可以控制数据传输的进程,防止数据丢失。
PC机中常用的两种流控为:硬件流控(包括RTS/CTS、DTR/CTS等)和软件流控制XON/XOFF(继续/停止)。
(1)硬件流控
硬件流控制常用的有RTS/CTS流控制和DTR/DSR流控制两种。
- **DTR–数据终端就绪(Data Terminal Ready)**低有效,当为低时,表示本设备自身准备就绪。此信号输出对端设备,使用对端设备决定能否与本设备通信。
- **DSR-数据装置就绪(Data Set Ready)**低有效,此信号由本设备相连接的对端设备提供,当为低时,本设备才能与设备端进行通信。
- **RTS - 请求发送(数据)(Request To Send)**低有效,此信号由本设备在需要发送数据给对端设备时设置。当为低时,表示本设备有数据需要向对端设备发送。对端设备能否接收到本方的发送数据,则通过CTS信号来应答。
- **CTS - 接收发送(请求)(Clear To Send)**低有效,对端设备能否接收本方所发送的数据,由CTS决定。若CTS为低,则表示对端的以准备好,可以接收本端发送数据。
以RTS/CTS流控制分析,分析主机发送/接收流程:
物理连接
- 主机的RTS(输出信号),连接到从机的CTS(输入信号)。主机是CTS(输入信号),连接到从机的RTS(输入信号)。
主机的发送过程:主机查询主机的CTS脚信号,此信号连接到从机的RTS信号,受从机控制。如果主机CTS信号有效(为低),表示从机的接收FIFO未满,从机可以接收,此时主机可以向从机发送数据,并且在发送过程中要一直查询CTS信号是否为有效状态。主机查询到CTS无效时,则中止发送。主机的CTS信号什么时候会无效呢?从机在接收到主机发送的数据时,从机的接收模块的FIFO如果满了,则会使从机RTS无效,也即主机的CTS信号无效。主机查询到CTS无效时,主机发送中止。
主机接收模式:如果主机接收FIFO未满,那么使主机RTS信号有效(为低),即从机的CTS信号有效。此时如果从机要发送,发送前会查询从机的CTS信号,如果有效,则开始发送。并且在发送过程中要一直查询从机CTS信号的有效状态,如果无效则终止发送。是否有效由主机的RTS信号决定。如果主机FIFO满了,则使主机的RTS信号无效,也即从机CTS信号无效,主机接收中止。
(2)软件流控
由于电缆的限制,在普通的控制通讯中一般不采用硬件流控制,而是使用软件流控制。一般通过XON/XOFF来实现软件流控制。
常用方法是:
- 当接收端的输入缓冲区内数据量超过设定的高位时,就向数据发送端发送XOFF字符后就立即停止发送数据。
- 当接收端的输入缓冲区内数据量低于设定的低位时,就向数据发送端发送XON字符(十进制的17或Control-Q),发送端收到XON字符后就立即开始发送数据。
一般可从设备配套源程序中找到发送端收到XON字符后就立即发送数据。一般可以从设备配套源程序中找到发送的是什么字节。应注意,若传输的是二进制的数据,标志字符也可能在数据流中出现而引起误操作,这是软件流控的缺陷,而硬件流控不会出现这样的问题。
二、UART驱动编程
首先,带大家简单回顾一下tty架构情况,详情可参考一文彻底讲清Linux tty子系统架构及编程实例
- 整个 tty架构大概的样子如上图所示,简单来分的话可以说成两层:
- 一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体;
- 另一层是 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 Ops 结构,用户空通过间是 tty 注册的字符设备节点来访问。
- 发送数据的流程为:tty核心从一个用户获取将要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传到tty驱动,tty驱动将数据转换为可以发给硬件的格式。
- 接收数据的流程为:从tty硬件接收到的数据向上交给tty驱动,接着进入tty线路规程驱动,再进入tty核心,在这里它被一个用户获取。
1. UART驱动编写
【分析】以最常见的S3C2410平台为例叙述:
- 在使用串口核心层这个通用串口tty驱动层的接口后,一个串口驱动要完成的主要工作将包括:定义uart_driver、uart_ops、uart_port等结构体的实例,并在适当的地方根据具体硬件和驱动的情况初始化它们。(当然具体设备xxx的驱动可以将这些结构套在新定义的xxx_uart_driver、xxx_uart_ops,xxx_uart_port之内)
- 在模块初始化时调用
uart_register_driver()
和uart_add_one_port()
以注册UART驱动并添加端口;在模块卸载时调用uart_unregister_driver()
和uart_remove_one_port()
以注销UART驱动并移除端口。- 以S3C2410串口驱动的模块加载函数中会调用uart_register_driver()注册s3c24xx_uart_drv这个uart_driver。初始化过程如下:
s3c2410_serial_init()
;platform_driver_register()
;- 匹配后,
s3c24xx_serial_probe()
被执行,它包含下面的函数; s3c24xx_serial_init_port()
被执行以初始化UART端口,它包含下面的函数;uart_add_one_port()
被执行以添加端口。
- 以S3C2410串口驱动的模块加载函数中会调用uart_register_driver()注册s3c24xx_uart_drv这个uart_driver。初始化过程如下:
- 根据具体硬件的datasheet实现uart_ops中的成员函数,这些函数的实现是UART驱动的主体工作。S3C2410串口驱动
uart_ops
结构体中的startup ()成员函数s3c24xx_serial_startup()
用于启动端口,申请端口的发送、接收中断,使能端口的发送和接收。
(1) 注册uart_driver
- 在uart driver的初始阶段(module_init()),需要将我们的
struct uart_driver
结构变量注册到内核,其注册流程大致为:- uart_register_driver
- 申请n个uart_state结构的空间(根据driver支持的最大设备数,申请n个uart_state空间,每一个uart_state都有一个uart_port。)
- 分配及初始化tty_driver结构(分配一个tty_driver,设置默认波特率、检验方式等,并将uart_driver->tty_driver指向它)
- 设置tty_driver的操作集——tty_operations(它是tty核心与串口驱动通信的接口)
- 设置tty_port的操作集——tty_port_operations(初始化每一个uart_state的tty_port)
- 注册tty_driver(注册uart_driver实际上就是注册tty_driver,与用户空间打交道的工作完全交给tty_driver,这一部分是内核实现好的不需要修改)
- 完毕
(2) 添加/移除uart_port
uart_add_one_port
接口用于注册一个uart port 到uart driver上,此后uart driver就可以访问对应的uart port进行数据收发。对该函数的调用应该发生在uart_register_driver()之后,它的一个最重要作用是封装了tty_register_device()。uart_remove_one_port
接口是上述函数的反函数,其最重要作用是会调用tty_unregister_device()。/* 将uart_port添加到相应的uart_driver中,成功返回0 */ int uart_add_one_port(struct uart_driver *drv,struct uart_port *uport);/*将uart_port从相应的uart_driver中移除,成功返回0 */ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport);
该接口在uart driver中的probe函数中调用,所以必须保证晚于uart_register_driver的注册过程。
uart driver在调用接口前,要手动设置uart_port的操作集uart_ops,使得通过调用
uart_add_one_port
接口后驱动完成硬件的操作接口注册。uart添加port流程如下图所示:
2. 数据收发流程分析
(1)打开设备
(2)数据发送流程(write)
(3) 数据接收流程(read)
(4)关闭设备(close)
3. 注销流程
(1) 移除uart_port
此接口用于从uart driver上注销一个uart port,该接口在uart driver中的remove函数中调用。uart移除port的流程如图3-9所示:
(2) 注销uart_driver
此接口在uart driver中调用,用来从kernel中注销uart_driver,调用阶段在uart driver的退出阶段,例如:module_exit(),uart driver的注销流程如图3.10所示
三、UART驱动中的数据结构
- 串口驱动数据结构总图
- 以s3c2440开发板为例,讲述其中UART驱动的数据结构构成:
- 因为我们和开发板的人机交互的接口是Windows下的串口控制台。这就是上面所说的控制台终端。但是我们用了console = ttySAC0。即把串口终端当做控制台终端。所以我们要研究具体的代码需要cd到serial子目录下。即串口终端目录。ls显示serial下的文件结点。如图所示:
- 我们主要关心的是两类文件:
- 一类是与体系结构和板载资源无关的通用串口操作文件(samsung.c)。
- 一类是与体系结构相关的硬件操作文件(s3c2440.c s3c2410.c s5pv210.c等)。我们为了得到具体的调用链。在具体的发送函数中加入回溯。如图所示。
- 我们得到的函数调用链是这样的(以发送函数。即文件的写操作为例)
write-> sys_write-> vfs_write-> redirected_tty_write-> tty_write-> n_tty_write-> uart_write-> uart_start-> s3c24xx_serial_start_tx
- 从具体代码上来看。这些函数基本上都是通过结构体中的函数指针调用。我们可以把这个调用链分为三个部分。即tty子系统核心、tty链路规程、tty驱动:
tty核心:是对整个tty设备的抽象,对用户提供统一的接口,包括sys_write->vfs_write
tty线路规程:是对传输数据的格式化,在tty_ldisc_N_TTY变量中描述,包括redirected_tty_write-> tty_write->n_tty_write
tty驱动:是面向tty设备的硬件驱动,这里面真正的对硬件进行操作,包括uart_write-> uart_start-> s3c24xx_serial_start_tx
这是从具体函数的角度来看的调用链。下面为了从数据结构的角度来分析调用链,介绍linux内核中针对于这一个串口硬件的主要数据结构。
(1) uart_driver
就是uart驱动程序结构,封装了tty_driver,使得底层的UART驱动无需关心tty_driver,其定义在 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;struct console *cons;/* these are private;the low level driver should not* touch these; they should be initialised to NULL*/struct uart_state *state;struct tty_driver *tty_driver;
}
(2) uart_port
uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或者IO内存地址、FIFO大小、端口类型等信息。
struct uart_port {spinlock_t lock; /* 串口端口锁 */unsignedint iobase; /* IO端口基地址 */unsignedchar __iomem *membase; /* IO内存基地址,经映射(如ioremap)后的IO内存虚拟基地址 */unsignedint irq; /* 中断号 */unsignedint uartclk; /* 串口时钟 */unsignedint fifosize; /* 串口FIFO缓冲大小 */unsignedchar x_char; /* xon/xoff字符 */unsignedchar regshift; /* 寄存器位移 */unsignedchar iotype; /* IO访问方式 */unsignedchar unused1;#define UPIO_PORT (0) /* IO端口 */#define UPIO_HUB6 (1)#define UPIO_MEM (2) /* IO内存 */#define UPIO_MEM32 (3)#define UPIO_AU (4) /* Au1x00 type IO */#define UPIO_TSI (5) /* Tsi108/109 type IO */#define UPIO_DWAPB (6) /* DesignWare APB UART */#define UPIO_RM9000 (7) /* RM9000 type IO */unsignedint read_status_mask; /* 关心的Rx error status */unsignedint ignore_status_mask; /* 忽略的Rx error status */struct uart_info *info; //重要,见下面struct uart_icount icount; /* 串口信息计数器,包含了发送和接收字符计数等。分别在发送和接收中断处理函数中管理这些计数。*/ struct console *cons; /* console结构体 */
#ifdef CONFIG_SERIAL_CORE_CONSOLEunsignedlong sysrq; /* sysrq timeout */
#endifupf_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;unsigned char unused[2];void*private_data; /* 端口私有数据,一般为platform数据指针 */
};
(3) uart_ops
uart_ops定义了针对UART的一系列操作。注意这里不要把uart_ops结构和uart_ops变量混淆。uart_ops结构是我们这里的数据结构。而uart_ops变量则是一个tty_operations的变量。
在serial_core.c中定义了tty_operations的实例。即uart_ops变量,包含uart_open();uart_close();uart_send_xchar()等成员函数,这些函数借助uart_ops结构体中的成员函数来完成具体的操作:
struct uart_ops {unsignedint(*tx_empty)(struct uart_port *); /* 串口的Tx FIFO缓存是否为空 */
void(*set_mctrl)(struct uart_port *,unsignedint mctrl); /* 设置串口modem控制 */
unsignedint(*get_mctrl)(struct uart_port *); /* 获取串口modem控制 */
void(*stop_tx)(struct uart_port *); /* 禁止串口发送数据 */
void(*start_tx)(struct uart_port *); /* 使能串口发送数据 */
void(*send_xchar)(struct uart_port *,char ch); /* 发送xChar */
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 *); /* 启动串口,应用程序打开串口设备文件时,该函数会被调用 */
void(*shutdown)(struct uart_port *);/* 关闭串口,应用程序关闭串口设备文件时,该函数会被调用 */
void(*set_termios)(struct uart_port *,struct ktermios *new,struct ktermios *old); /* 设置串口参数 */
void(*pm)(struct uart_port *,unsignedint state,
unsignedint oldstate); /* 串口电源管理 */
int(*set_wake)(struct uart_port *,unsignedint state);/* */
constchar*(*type)(struct uart_port *); /* 返回一描述串口类型的字符串 */
void(*release_port)(struct uart_port *);/* 释放串口已申请的IO端口/IO内存资源,必要时还需iounmap */
int(*request_port)(struct uart_port *); /* 申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口 */
void(*config_port)(struct uart_port *,int); /* 执行串口所需的自动配置 */
int(*verify_port)(struct uart_port *,struct serial_struct *); /* 核实新串口的信息 */
int(*ioctl)(struct uart_port *,unsignedint,unsignedlong); /* IO控制 */#ifdef CONFIG_CONSOLE_POLLint (*poll_init)(struct uart_port *);void (*poll_put_char)(struct uart_port *, unsigned char);int (*poll_get_char)(struct uart_port *);
#endif
};
而uart_ops变量是tty_operations型的一个变量。如下图所示:
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,
}
(4) uart_state
uart_state是uart的状态结构
struct uart_state{struct tty_port port;int pm_state;struct circ_buf xmit;struct tasklet_struct tlet;struct uart_port *uart_port;
};
#define UART_XMIT_SIZE PAGE_SIZE
(5) uart_info
uart_info是uart的信息结构,它有两个成员在底层串口驱动会用到:xmit和tty:
- 用户空间程序通过串口发送数据时,上层驱动将用户数据保存在xmit;而串口发送中断处理函数就是通过xmit获取到用户数据并将它们发送出去。
- 串口接收中断处理函数需要通过tty将接收到的数据传递给行规则层。
struct uart_info {struct tty_struct *tty; //处理接受数据struct circ_buf xmit; //上层需要发送uif_t flags;/** Definitions for info->flags. These are _private_ to serial_core,and* are specific to this structure. They may be queried by low leveldrivers.*/#define UIF_CHECK_CD ((__force uif_t)(1 << 25))#define UIF_CTS_FLOW ((__force uif_t)(1 << 26))#define UIF_NORMAL_ACTIVE ((__force uif_t)(1 << 29))#define UIF_INITIALIZED ((__force uif_t)(1 << 31))#define UIF_SUSPENDED ((__force uif_t)(1 << 30))int blocked_open;struct tasklet_struct tlet; //上层驱动任务等待队列的wait_queue_head_t open_wait;wait_queue_head_t delta_msr_wait;
};
在这个体系结构下定义为s3c24xx_uart_info:
struct s3c24xx_uart_info{char *name;unsigned int type;unsigned int fifosize;unsigned long rx_fifomask;unsigned long rx_fifoshift;unsigned long rx_fifofull;
}
- 所以很显然,用数据结构来描述函数调用链就是:
uart_driver -> uart_state-> uart_port-> uart_ops-> 特定的函数指针。
(6) 发送函数及中断服务例程
使能发送并没有真正的发送,而只是使能发送中断(enable_irq(ourport->tx_irq);
)而已。
static void s3c24xx_serial_start_tx(struct uart_port *port)
{struct s3c24xx_uart_port *ourport = to_ourport(port);if(!tx_enabled(port)){if(port->flags & UPF_CONS_FLOW)s3c24xx_serial_rx_disable(port);enable_irq(ourport->tx_irq);tx_enabled(port) = 1;}
}
这是因为ARM9处理器上有一个循环缓冲。用户从write系统调用传下来的数据就会写入这个UTXH0寄存器。发送完事之后处理器会产生一个内部中断。我们通过这个内部中断就可以实现流控过程,我们打开芯片手册可以看到如下字样:
如下才是发送中断的ISR(Interrupt Service Routine)中断服务例程。一个irqreturn_t类型的handler。
static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{struct s3c24xx_uart_port *ourport = id;struct uart_port *port = &ourport->port;struct cirt_buf *xmit = &port->state->xmit;int count = 256;if(port->x_char){wr_regb(port,S3C2410_UTXH, port->x_char);port->icount.tx++;port->x_char = 0;goto out;}...
}
这个wr_regb(port, S3C2410_UTXH, port->x_char);
就是往特定寄存器写的过程。
至此我们的分析已经结束。相信读者对于Linux下的tty子系统已经有一个概观了。下面是这个uart驱动的总图。结合数据结构的调用链。Linux内核完成了驱动模型和特定硬件的分离:
在下一篇文章中,将继续讲解如何实际编写一个串口模块,欢迎点赞加关注!
四、内核代码分析(s3c2410平台)
(1)平台资源
static struct resource s3c2410_uart0_resource[] = {...
};static struct resource s3c2410_uart1_resource[] = {...
};static struct resource s3c2410_uart2_resource[] = {...
};
- 在文件linux/arch/arm/plat-samsung/dev-uart.c中定义了每个串口对应的平台设备。
static struct platform_device s3c24xx_uart_device0 = {.id = 0,
};static struct platform_device s3c24xx_uart_device1 = {.id = 1,
};static struct platform_device s3c24xx_uart_device2 = {.id = 2,
};
- 在文件linux/arch/arm/mach-s3c2440/mach-smdk2440.c中有一些串口寄存器的初始化配置。
static struct s3c2410_uartcfg smdk2440_uartcfgs[] __initdata = {[0] = {...},[1] = {...},/* IR port */[2] = {...},
};
- 在文件linux/arch/arm/mach-s3c2440/mach-smdk2440.c中将调用函数**s3c24xx_init_uarts()**将上面的硬件资源,初始化配置,平台设备整合到一起。
在文件 linux/arch/arm/plat-s3c/init.c中有:
static int __init s3c_arch_init(void)
{.../*这个函数将串口所对应的平台设备添加到了内核*/ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);return ret;
}
(2)串口设备驱动原理浅析
我认为任何设备在linux中的实现就“两条线”:一是设备模型的建立,二是读写数据流。串口驱动也是这样。
- 串口设备模型建立:
串口设备驱动的核心结构体在文件linux/drivers/serial/samsuing.c中如下
static struct uart_driver s3c24xx_uart_drv = {.owner = THIS_MODULE,.dev_name = "s3c2410_serial", .nr = CONFIG_SERIAL_SAMSUNG_UARTS,.cons = S3C24XX_SERIAL_CONSOLE,.driver_name = S3C24XX_SERIAL_NAME,.major = S3C24XX_SERIAL_MAJOR,.minor = S3C24XX_SERIAL_MINOR,
};
- 串口驱动的注册
static int __init s3c24xx_serial_modinit(void)
{...ret = uart_register_driver(&s3c24xx_uart_drv);...
}int uart_register_driver(struct uart_driver *drv)
{...//每一个端口对应一个statedrv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);...normal = alloc_tty_driver(drv->nr); //分配该串口驱动对应的tty_driver ...drv->tty_driver = normal; //让drv->tty_driver字段指向这个tty_driver...normal->driver_name = drv->driver_name;normal->name = drv->dev_name;normal->major = drv->major;normal->minor_start = drv->minor;...//设置该tty驱动对应的操作函数集tty_operations (linux/drivers/char/core.c)tty_set_operations(normal, &uart_ops); ...retval = tty_register_driver(normal); //将tty驱动注册到内核...
}
(3)串口本身是一个字符设备
其实tty驱动的本质是一个字符设备,在文件 linux/drivers/char/tty_io.c中,这样才能通过操作设备文件操作硬件
int tty_register_driver(struct tty_driver *driver)
{...cdev_init(&driver->cdev, &tty_fops);driver->cdev.owner = driver->owner;error = cdev_add(&driver->cdev, dev, driver->num);...
}
它所关联的操作函数集tty_fops在文件linux/drivers/char/tty_io.c中实现
static const struct file_operations tty_fops = {.llseek = no_llseek,.read = tty_read,.write = tty_write,....open = tty_open,...
};
到此串口的驱动作为tty_driver被注册到了内核。前面提到串口的每一个端口都是作为平台设备被添加到内核的。那么这些平台设备就对应着有它们的平台设备驱动。在文件linux/drivers/serial/s3c2440.c中有:
static struct platform_driver s3c2440_serial_driver = {.probe = s3c2440_serial_probe,.remove = __devexit_p(s3c24xx_serial_remove),.driver = {.name = "s3c2440-uart",.owner = THIS_MODULE,},
};
当其驱动与设备匹配时就会调用他的探测函数:
static int s3c2440_serial_probe(struct platform_device *dev)
{return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
}
每一个端口都有一个描述它的结构体s3c24xx_uart_port ,在文件linux/drivers/serial/samsuing.c中
static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {[0] = {.port = {.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),.iotype = UPIO_MEM,.irq = IRQ_S3CUART_RX0, //该端口的中断号.uartclk = 0,.fifosize = 16,.ops = &s3c24xx_serial_ops, //该端口的操作函数集.flags = UPF_BOOT_AUTOCONF,.line = 0, //端口编号}},...
}
上面探测函数的具体工作是函数s3c24xx_serial_probe()来完成的
int s3c24xx_serial_probe(struct platform_device *dev,struct s3c24xx_uart_info *info)
{...//根据平台设备提供的硬件资源等信息初始化端口描述结构体中的一些字段ret = s3c24xx_serial_init_port(ourport, info, dev);//前面注册了串口驱动,这里便要注册串口设备uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);...
}int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{...//前面说串口驱动是tty_driver,这里可以看到串口设备其实是tty_devtty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);...
}
(4)串口数据流分析
在串口设备模型建立中提到了三个操作函数集,uart_ops ,tty_fops,s3c24xx_serial_ops数据的流动便是这些操作函数间的调用,这些调用关系如下:
在对一个设备进行其他操作之前必须先打开它,linux/drivers/char/tty_io.c
static const struct file_operations tty_fops = {....open = tty_open,...
};static int tty_open(struct inode *inode, struct file *filp)
{...dev_t device = inode->i_rdev;...driver = get_tty_driver(device, &index); //根据端口设备号获取它的索引号...if (tty) {...} elsetty = tty_init_dev(driver, index, 0); //创建一个tty_struct 并初始化...
}struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,int first_ok)
{...tty = alloc_tty_struct(); //分配一个tty_struct结构initialize_tty_struct(tty, driver, idx); //一些字段的初始化,retval = tty_driver_install_tty(driver, tty); //完成的主要工作是driver->ttys[idx] = tty;.../*下面函数主要做的就是调用线路规程的打开函数ld->ops->open(tty)。在这个打开函数中分配了一个重要的数据缓存tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);*/retval = tty_ldisc_setup(tty, tty->link);
}void initialize_tty_struct(struct tty_struct *tty,struct tty_driver *driver, int idx)
{...//获取线路规程操作函数集tty_ldisc_N_TTY,并做这样的工作tty->ldisc = ld;tty_ldisc_init(tty);.../*下面函数的主要工作是INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);初始化一个延时tty->buf.work 并关联一个处理函数flush_to_ldisc(),这个函数将在数据读取的时候用到。*/tty_buffer_init(tty);...tty->driver = driver; tty->ops = driver->ops; //这里的ops就是struct tty_operations uart_opstty->index = idx; //idx就是该tty_struct对应端口的索引号tty_line_name(driver, idx, tty->name);
}
端口设备打开之后就可以进行读写操作了,这里只讨论数据的读取,在文件 linux/drivers/char/tty_io.c中,
static const struct file_operations tty_fops = {....read = tty_read,...
};static ssize_t tty_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{...ld = tty_ldisc_ref_wait(tty); //获取线路规程结构体if (ld->ops->read) //调用线路规程操作函数集中的n_tty_read()函数i = (ld->ops->read)(tty, file, buf, count);else...
}
在linux/drivers/char/N_tty.c中:
struct tty_ldisc_ops tty_ldisc_N_TTY = {....open = n_tty_open,....read = n_tty_read,...
};static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr)
{...while (nr) {...if (tty->icanon && !L_EXTPROC(tty)) {//如果设置了tty->icanon 就从缓存tty->read_buf[]中逐个数据读取,并判断读出的每一个数//据的正确性或是其他数据类型等。eol = test_and_clear_bit(tty->read_tail,tty->read_flags);c = tty->read_buf[tty->read_tail];...} else {...//如果没有设置tty->icanon就从缓存tty->read_buf[]中批量读取数据,之所以要进行两次读//取是因为缓存tty->read_buf[]是个环形缓存uncopied = copy_from_read_buf(tty, &b, &nr);uncopied += copy_from_read_buf(tty, &b, &nr);...}}...
}
用户空间是从缓存tty->read_buf[]中读取数据的,那么缓存tty->read_buf[]中的数据是从那里来的呢?分析如下:
回到文件 linux/drivers/serial/samsuing.c中,串口数据接收中断处理函数实现如下:(这是串口最原始的数据流入的地方)
static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id)
{...while (max_count-- > 0) {...ch = rd_regb(port, S3C2410_URXH); //从数据接收缓存中读取一个数据...flag = TTY_NORMAL; //普通数据,还可能是其他数据类型在此不做讨论.../*下面函数做的最主要工作是这样struct tty_buffer *tb = tty->buf.tail;tb->flag_buf_ptr[tb->used] = flag;tb->char_buf_ptr[tb->used++] = ch;将读取的数据和该数据对应标志插入 tty->buf。*/uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);}tty_flip_buffer_push(tty); //将读取到的max_count个数据向上层传递。
out:return IRQ_HANDLED;
}void tty_flip_buffer_push(struct tty_struct *tty)
{...if (tty->low_latency)flush_to_ldisc(&tty->buf.work.work);elseschedule_delayed_work(&tty->buf.work, 1); //这里这个延时work在上面串口设备打开中提到过,该work的处理函数也是flush_to_ldisc。
}static void flush_to_ldisc(struct work_struct *work)
{...while ((head = tty->buf.head) != NULL) {...char_buf = head->char_buf_ptr + head->read;flag_buf = head->flag_buf_ptr + head->read;...//刚才在串口接收中断处理函数中,将接收到的数据和数据标志存到tty->buf中,现在将//这些数据和标志用char_buf 和flag_buf指向进一步向上传递。disc->ops->receive_buf(tty, char_buf,flag_buf, count);spin_lock_irqsave(&tty->buf.lock, flags);}
}
上面调用的函数disc->ops->receive_buf在文件linux/drivers/char/N_tty.c中实现
struct tty_ldisc_ops tty_ldisc_N_TTY = {....receive_buf = n_tty_receive_buf,...
};static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
{...//现在可以看到缓冲区tty->read_buf 中数据的由来了。if (tty->real_raw) {//如果设置了tty->real_raw将上面讲到的些传入数据批量拷贝到tty->read_head中。//对环形缓存区的数据拷贝需要进行两次,第一次拷贝从当前位置考到缓存的末尾,如果还//有没考完的数据而且缓存区开始出处还有剩余空间,就把没考完的数据考到开始的剩余空//间中。spin_lock_irqsave(&tty->read_lock, cpuflags);i = min(N_TTY_BUF_SIZE - tty->read_cnt,N_TTY_BUF_SIZE - tty->read_head);i = min(count, i);memcpy(tty->read_buf + tty->read_head, cp, i);tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);tty->read_cnt += i;cp += i;count -= i;i = min(N_TTY_BUF_SIZE - tty->read_cnt, N_TTY_BUF_SIZE - tty->read_head);i = min(count, i);memcpy(tty->read_buf + tty->read_head, cp, i);tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);tty->read_cnt += i;spin_unlock_irqrestore(&tty->read_lock, cpuflags);} else {for (i = count, p = cp, f = fp; i; i--, p++) {//如果没有设置tty->real_raw,就根据传入数据标志分类获取数据。...}...}...
}
到此,数据读取的整个过程就结束了。可以看出数据读取可以分为两个阶段:一个阶段是上层函数从环形缓存区tty->read_buf 读取数据,第二阶段是底层函数将接收的数据考到环形缓存区tty->read_buf 中。
(5)串口驱动程序
串口驱动之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*):当用户登录的时候使用的是虚拟终端,使用快捷键组合:ctcl+alt+[F1-F6]组合键就可以切换到tty1,tty2,tty3等上面去。tty1-tty6等称为虚拟终端,而tty0 是当前使用的终端的一个别名。主要是提供给应用程序使用。
tty架构
- tty核心:tty核心是对整个tty设备的抽象,并提供单一的接口
- tty线路规划:tty线路规程是对数据的传输的格式化,比如需要实现某种协议,就需要将协议的实现代码放在该位置
- tty驱动:是面向tty设备的硬件驱动
注意:Linux中的获取回溯信息使用函数 dump_stack()用来显示各种函数的调用信息。
串口驱动程序的结构
分析:串口驱动需要提供给用户读数据的功能,写数据,打开串口和关闭串口的功能。打开之前需要对肯定需要对串口进行初始化的工作。
重要数据结构:
- UART 驱动程序结构:struct uart_driver//一个串口对应一个串口驱动,用于描述串口结构
- UART 端口结构:struct uart_port//有几个串口就对应几个port
- UART 相关操作函数结构struct uart_ops//对应相关串口所支持的操作函集
- UART 状态结构:struct uart_state
- UART 信息结构:struct uart_info
串口初始化:
- 定义并描述串口:struct uart_driver
- 注册串口驱动程序:uart_register_driver
- 取出相应的串口
- 初始化该取出的串口
串口驱动之打开驱动:
系统调用过程:用户使用open()函数打开设备文件
- 打开设备文件肯定有对应的设备驱动文件打开函数:file_operations.
- 在使用uart_register_driver ()注册串口驱动的时候,该函数里面会调用函数tty_register_driver(),该函数会调用cdev_init()函数和cdev_add()。
- 从这里可以看出tty设备是属于字符设备
——>内核调用cdev结构中的file-operations指针所指向结构里的tty-open()函数——>tty-open()函数紧接着调用tty_struct 结构中的tty_operations指针所指向的结构里的uart_open() 函数——>该函数接着调用uart_satarup()函数——>该函数会调用uart_port 结构中的ops 指针所指向是操作函数集合中文件操作函数。
打开步骤:使能串口接收功能——>为数据的接收注册中断处理程序——>使能发送功能——>为发送数据注册中断处理程序
串口驱动之发送数据:
系统调用过程:用户通过使用write()函数实现发送数据
write()函数调用cdev结构中的file_operations 指针所指向的结构里tty_write()函数——>该函数会调用tty_ldisc_ops 结构中的write指针所指向的函数n_tty_write()函数——>该函数会调用uart_tty 结构中的指针write所指向的函数uart_write()函数——>该函数会调用函数uart_startup()函数——>该函数会调用uart_port结构中的ops指针所指的操作函数集合中的文件发送函数。
注意:当使能中断后,驱动程序发现FIFO 中数据量小于某一个值就会触发中断,在中断处理程序中实现相应的数据发送
循环缓冲:在使用函数write()的时候,会经过tty核的处理,在处理的过程中,会将要发现的数据放到一个地方,这个地方就叫做循环缓冲
注意:FIFO 中的数据是先入先出。对于循环缓冲中的数据,在没有数据的时候 ,tail 和head 是在缓冲数据的同一个位置,当往其中装入数据的时候,head 跟随写入数据的变化而变化,但是发送数据的起始位置是在tail
数据发送过程:
- 判断是否有需要发送的x_char,如果用,通过UTXH寄存器发送。注意:x_char用于表示接收数据端的状态用,是否满足接收数据的条件
- 判断循环缓冲或串口状态不允许发送数据则需要停止发送数据
- 使用while()循环来发数据,发送规则<1>循环换缓冲有数据<2>发送的数据量不到256。注意:
- 发送FIFO中数据为满时停止发送
- 从循环缓冲中的尾部(tail)中取数据,并将数据送入到UTXH寄存中
- 使用自加运算来调整循环缓冲中的数据的位置
- 如果循环缓冲中的数据量低于256 则唤醒在发送时阻塞的进程(在之前有进程需要王循环缓冲中发送数据,但是发现循环缓冲中的没有足够的空间进行来存放要发送的数据,则进程进入休眠状态)
- 如果循环缓冲中数据为空则需要关闭中断,否则将不断产生中断影响系统的稳定运行
串口驱动程序之数据接收:
系统调用过程:应用程序要接收数据肯定要使用read() 函数
流程:
用户程序调用函数read()——>read() 函数会调用结构file_operations中read指针所指向的tty_read()函数——>该函数会调用结构tty_ldisc_tty结构中的指针read所指向的n_tty_read()函数——>
n_tty_read()函数分析:
- 将应用程序执行进程状态设置成(TASK_INTERRUPTIBLE)。注意:设置成该状态但是程序并不会直接进入阻塞态,需要在进程调度中才会进入相应的状态
- 如果没有数据可读,这通过 调度程序实现将进程进入阻塞态度
- 如果readbuf中有数据则从中读取数据
驱动程序处理流程:
- 读取UFCON寄存器
- 读取UFSTAT寄存器
- UFSTAT中的fifocnt 的大小为零退出处理
- UFSTAT 中的UERSAT判断错误类型
- 从URXH中取出接收到的数据
- 进行流控处理
- 根据UERSTAT的值记录具体的错误类型
- 如果接受到的是sysrq字符,则调用特殊处理函数-uart_handle_sysrq_chgr()
- 把字符送入串口驱动的buffer,调用函数uart_insert_char
- 将串口驱动buffer中的数据送进线路规程的read_buf ,调用函数tty_flip_buffer_push
流控:当发送数据给接收方,接收方的数据缓冲区里面没有空余空间可以使用的时候就需要通知发送方停止发送数据,否则发送方的数据就不能被接收到。
- 流控方式:
- 软件流控:接收端通过串行线向发送端发送数据x_off表示不再接收,当可以再接收时再向发送端发送x_on。
- 硬件流控:通过使用硬件联系连接起来,当不能接收数据的时候就会使 相应连线产生高电平,发送端每发送一次说都会检测该引脚
- 流控方式:
五、实例
该代码摘取自LDD3一书中,它创建了一个简单的串口驱动,为了便于大家进行学习,它没有使用实际的串口硬件,而是使用了一个定时器来模拟从其它硬件接收到数据。相信通过前面4个章节的讲解,大家应该能够看懂下面的代码
/** This driver shows how to create a minimal serial driver. It does not rely on* any backing hardware, but creates a timer that emulates data being received* from some kind of hardware.*/#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/module.h>#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>"
#define DRIVER_DESC "Tiny serial driver"/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");#define DELAY_TIME HZ * 2 /* 每发送1个字符延迟2秒 */
#define TINY_DATA_CHARACTER 't'#define TINY_SERIAL_MAJOR 240 /* 主设备号 */
#define TINY_SERIAL_MINORS 1 /* 次设备个数 */
#define UART_NR 1 /* 端口数 */#define TINY_SERIAL_NAME "ttytiny" /* 自定义串口驱动名 */#define MY_NAME TINY_SERIAL_NAMEstatic struct timer_list *timer;static void tiny_stop_tx(struct uart_port *port, unsigned int tty_stop)
{}static void tiny_stop_rx(struct uart_port *port)
{}static void tiny_enable_ms(struct uart_port *port)
{}static void tiny_tx_chars(struct uart_port *port)
{struct circ_buf *xmit = &port->info->xmit;int count;if (port->x_char) {pr_debug("wrote %2x", port->x_char);port->icount.tx++;port->x_char = 0;return;}if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {tiny_stop_tx(port, 0);return;}count = port->fifosize >> 1;do {pr_debug("wrote %2x", xmit->buf[xmit->tail]);xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);port->icount.tx++;if (uart_circ_empty(xmit))break;} while (--count > 0);if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)uart_write_wakeup(port);if (uart_circ_empty(xmit))tiny_stop_tx(port, 0);
}static void tiny_start_tx(struct uart_port *port, unsigned int tty_start)
{}static void tiny_timer(unsigned long data)
{struct uart_port *port;struct tty_struct *tty;port = (struct uart_port *)data;if (!port)return;if (!port->info)return;tty = port->info->tty;if (!tty)return;/* add one character to the tty port *//* this doesn't actually push the data through unless tty->low_latency is set */tty_insert_flip_char(tty, TINY_DATA_CHARACTER, 0);tty_flip_buffer_push(tty);/* resubmit the timer again */timer->expires = jiffies + DELAY_TIME;add_timer(timer);/* see if we have any data to transmit */tiny_tx_chars(port);
}static unsigned int tiny_tx_empty(struct uart_port *port)
{return 0;
}static unsigned int tiny_get_mctrl(struct uart_port *port)
{return 0;
}static void tiny_set_mctrl(struct uart_port *port, unsigned int mctrl)
{}static void tiny_break_ctl(struct uart_port *port, int break_state)
{}static void tiny_set_termios(struct uart_port *port,struct termios *new, struct termios *old)
{int baud, quot, cflag = new->c_cflag;/* get the byte size */switch (cflag & CSIZE) {case CS5:printk(KERN_DEBUG " - data bits = 5\n");break;case CS6:printk(KERN_DEBUG " - data bits = 6\n");break;case CS7:printk(KERN_DEBUG " - data bits = 7\n");break;default: // CS8printk(KERN_DEBUG " - data bits = 8\n");break;}/* determine the parity */if (cflag & PARENB)if (cflag & PARODD)pr_debug(" - parity = odd\n");elsepr_debug(" - parity = even\n");elsepr_debug(" - parity = none\n");/* figure out the stop bits requested */if (cflag & CSTOPB)pr_debug(" - stop bits = 2\n");elsepr_debug(" - stop bits = 1\n");/* figure out the flow control settings */if (cflag & CRTSCTS)pr_debug(" - RTS/CTS is enabled\n");elsepr_debug(" - RTS/CTS is disabled\n");/* Set baud rate */baud = uart_get_baud_rate(port, new, old, 0, port->uartclk/16);quot = uart_get_divisor(port, baud);//UART_PUT_DIV_LO(port, (quot & 0xff));//UART_PUT_DIV_HI(port, ((quot & 0xf00) >> 8));
}/* 启动端口 */
static int tiny_startup(struct uart_port *port)
{/* this is the first time this port is opened *//* do any hardware initialization needed here *//* create our timer and submit it */if (!timer) {timer = kmalloc(sizeof(*timer), GFP_KERNEL);if (!timer)return -ENOMEM;}timer->data = (unsigned long)port;timer->expires = jiffies + DELAY_TIME;timer->function = tiny_timer; //超时回调函数add_timer(timer);return 0;
}/* 关闭端口 */
static void tiny_shutdown(struct uart_port *port)
{/* The port is being closed by the last user. *//* Do any hardware specific stuff here *//* shut down our timer */del_timer(timer);
}static const char *tiny_type(struct uart_port *port)
{return "tinytty";
}static void tiny_release_port(struct uart_port *port)
{}static int tiny_request_port(struct uart_port *port)
{return 0;
}static void tiny_config_port(struct uart_port *port, int flags)
{}static int tiny_verify_port(struct uart_port *port, struct serial_struct *ser)
{return 0;
}static struct uart_ops tiny_ops = { //5.初始化UART端口操作方法.tx_empty = tiny_tx_empty, .set_mctrl = tiny_set_mctrl,.get_mctrl = tiny_get_mctrl,.stop_tx = tiny_stop_tx,.start_tx = tiny_start_tx,.stop_rx = tiny_stop_rx,.enable_ms = tiny_enable_ms,.break_ctl = tiny_break_ctl,.startup = tiny_startup,.shutdown = tiny_shutdown,.set_termios = tiny_set_termios,.type = tiny_type,.release_port = tiny_release_port,.request_port = tiny_request_port,.config_port = tiny_config_port,.verify_port = tiny_verify_port,
};static struct uart_port tiny_port = { //4.初始化UART端口.ops = &tiny_ops,
};static struct uart_driver tiny_reg = { //3.初始化UART驱动.owner = THIS_MODULE,.driver_name= TINY_SERIAL_NAME,.dev_name = TINY_SERIAL_NAME,.major = TINY_SERIAL_MAJOR,.minor = TINY_SERIAL_MINORS,.nr = UART_NR,
};static int __init tiny_init(void)
{int result;printk(KERN_INFO "Tiny serial driver loaded\n");result = uart_register_driver(&tiny_reg); //1.注册UART驱动if (result)return result;result = uart_add_one_port(&tiny_reg, &tiny_port); //2.为UART添加端口if (result)uart_unregister_driver(&tiny_reg);return result;
}module_init(tiny_init);
【参考】
嵌入式大杂烩.原文
基于Linux的tty架构及UART驱动详解 (qq.com)
.break_ctl = tiny_break_ctl,
.startup = tiny_startup,
.shutdown = tiny_shutdown,
.set_termios = tiny_set_termios,
.type = tiny_type,
.release_port = tiny_release_port,
.request_port = tiny_request_port,
.config_port = tiny_config_port,
.verify_port = tiny_verify_port,
};
static struct uart_port tiny_port = { //4.初始化UART端口
.ops = &tiny_ops,
};
static struct uart_driver tiny_reg = { //3.初始化UART驱动
.owner = THIS_MODULE,
.driver_name= TINY_SERIAL_NAME,
.dev_name = TINY_SERIAL_NAME,
.major = TINY_SERIAL_MAJOR,
.minor = TINY_SERIAL_MINORS,
.nr = UART_NR,
};
static int __init tiny_init(void)
{
int result;
printk(KERN_INFO “Tiny serial driver loaded\n”);
result = uart_register_driver(&tiny_reg); //1.注册UART驱动
if (result)
return result;
result = uart_add_one_port(&tiny_reg, &tiny_port); //2.为UART添加端口
if (result)uart_unregister_driver(&tiny_reg);return result;
}
module_init(tiny_init);
**【参考】**[嵌入式大杂烩.原文](https://www.jianshu.com/p/3a9013b9569c)[基于Linux的tty架构及UART驱动详解 (qq.com)](https://mp.weixin.qq.com/s?__biz=MzUxMjEyNDgyNw==&mid=2247491957&idx=1&sn=a5bdd1d224465b167bf239dae2131fff&chksm=f96b9381ce1c1a9792157647b644d2e41f67bd3050ee8fa0ff3f32ef601930cb8ec2c53c90f0&scene=178&cur_album_id=1502410824114569216#rd)[Linux驱动之串口(UART)](https://www.cnblogs.com/big-devil/p/8590050.html)
uart驱动框架及编程方法相关推荐
- 超详细Uart驱动框架及编程方法
一.UART介绍 UART(Universal Asynchronous Receiver/Transmitter),中文全称为通用异步收发传输器,是一种异步收发传输器,它将要传输的数据通过并行到串行 ...
- Linux uart驱动框架
Linux uart驱动框架 串口驱动框架包括两部分 struct uart_driver int uart_register_driver(struct uart_driver *uart); vo ...
- 基于Linux的UART驱动框架源码分析笔记
文章目录 前言 一.I.MX6ULL串口接收和发送方式 1.非DMA方式 1.1.接收方式 1.2 发送方式 2.DMA方式 2.1.接收方式 2.2 发送方式 二.UART驱动注册 1.uart_r ...
- i.MX6ULL驱动开发 | 15 - Linux UART 驱动框架
Linux UART 驱动框架比较简单,不需要设备驱动,只需要UART设备驱动即可. 一.Linux UART驱动框架 1. uart_driver结构体 Linux将 UART 驱动抽象为 uart ...
- usb serial port 驱动_tty初探 — uart驱动框架分析
写在前面: 我们没有讲UART驱动,不过我们认为,只要系统学习了第2期,应该具备分析UART驱动的能力,小编做答疑几年以来,陆陆续续有不少人问到UART驱动怎么写,所以今天就分享一篇深度长文(1700 ...
- platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架
platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...
- tty初探 — uart驱动框架分析
写在前面: 我们没有讲UART驱动,不过我们认为,只要系统学习了第2期,应该具备分析UART驱动的能力,小编做答疑几年以来,陆陆续续有不少人问到UART驱动怎么写,所以今天就分享一篇深度长文(1700 ...
- linux UART驱动_应用程序例程
系列文章 I.MX6ULL 手册查找使用方法 实战点亮LED(寄存器版) I.MX6ULL 手册查找使用方法 实战点亮LED(固件库版本) linux 字符设备驱动实战 linux LED设备驱动文件 ...
- 驱动框架入门之LED-linux驱动开发第4部分-朱有鹏-专题视频课程
驱动框架入门之LED-linux驱动开发第4部分-5199人已学习 课程介绍 本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入.通过led驱动框架和gpiolib的这两 ...
最新文章
- UVALive5379 UVA270 Lining Up
- MIT Molecular Biology 笔记5 转录机制
- 哈夫曼编码压缩率计算_程序员的算法课(8)-贪心算法:理解霍夫曼编码
- centos php7.0 mysql_CentOS 7.3 下 安装LNMP(Nginx1.10+MySQL5.7+PHP7.0.20)
- 无状态Spring安全性第2部分:无状态身份验证
- koa2异常处理_读 koa2 源码后的一些思考与实践
- 增加数据_咱晋城人口又增加了?最新数据来了
- KMP 算法 学习 整理
- JavaScript 设计模式核⼼原理与应⽤实践 之 结构型设计模式
- c#ref和out的相同点_C#中使用ref和out一点认识!
- nRF5 SDK for Mesh(四) 源码编译
- java 实现多重继承
- 医疗保健行业未来发展的5大趋势预测
- 2021.9.8 华为笔试题第三题
- Huffman-哈夫曼编码算法详解
- 关于“程序员996”,大能发话了,能顶用吗?
- 一个人九月份开始考北邮的经验
- Java使用正则表达式从字符串中提取 IP 地址
- 普通运维人员是秋后的蚂蚱?
- lisp语言绘制路灯_LISP语言在AD道路设计方案中各种应用