2013-04-03 11:34:37 taozi343805436 阅读数 1418
  • 串口通信详解-1.7.ARM裸机第七部分

    本期课程主要讲述S5PV210的串口通信。本课程共分为3个部分:首先用3节课时间系统讲解了通信的基础知识、串行通信的重要概念和知识等;然后用5节课时间详细分析了S5PV210的串口控制器的关键部分及代码编写;后用3节课时间讲述了如何基于S5PV210的基本串口通信函数实现stdio的移植。

    8375 人正在学习 去看看 朱有鹏
一、核心数据结构
串口驱动有3个核心数据结构,它们都定义在<#include linux/serial_core.h>

1、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 */
/*
* 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用于描述串口端口的I/O端口或I/O内存地址、FIFO大小、端口类型、串口时钟等信息。
实际上,一个uart_port实例对应一个串口设备
struct uart_port {
spinlock_t lock; /* 串口端口锁 */
unsigned int iobase; /* IO端口基地址 */
unsigned char __iomem *membase; /* IO内存基地址,经映射(如ioremap)后的IO内存虚拟基地址 */
unsigned int irq; /* 中断号 */
unsigned int uartclk; /* 串口时钟 */
unsigned int fifosize; /* 串口FIFO缓冲大小 */
unsigned char x_char; /* xon/xoff字符 */
unsigned char regshift; /* 寄存器位移 */
unsigned char iotype; /* IO访问方式 */
unsigned char 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 */
unsigned int read_status_mask; /* 关心的Rx error status */
unsigned int ignore_status_mask;/* 忽略的Rx error status */
struct uart_info *info; /* pointer to parent info */
struct uart_icount icount; /* 计数器 */
struct console *cons; /* console结构体 */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
unsigned long sysrq; /* sysrq timeout */
#endif
upf_t flags;
#define UPF_FOURPORT ((__force upf_t) (1 << 1))
#define UPF_SAK ((__force upf_t) (1 << 2))
#define UPF_SPD_MASK ((__force upf_t) (0x1030))
#define UPF_SPD_HI ((__force upf_t) (0x0010))
#define UPF_SPD_VHI ((__force upf_t) (0x0020))
#define UPF_SPD_CUST ((__force upf_t) (0x0030))
#define UPF_SPD_SHI ((__force upf_t) (0x1000))
#define UPF_SPD_WARP ((__force upf_t) (0x1010))
#define UPF_SKIP_TEST ((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ ((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD ((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY ((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART ((__force upf_t) (1 << 14))
#define UPF_MAGIC_MULTIPLIER ((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ ((__force upf_t) (1 << 24))
#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 ((__force upf_t) (0x17fff))
#define UPF_USR_MASK ((__force upf_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数据指针 */
};
uart_info有两个成员在底层串口驱动会用到:xmit和tty。用户空间程序通过串口发送数据时,
上层驱动将用户数据保存在xmit;而串口发送中断处理函数就是通过xmit获取到用户数据并将它
们发送出去。串口接收中断处理函数需要通过tty将接收到的数据传递给行规则层。
/* uart_info实例仅在串口端口打开时有效,它可能在串口关闭时被串口核心层释放。因此,在使用uart_port的uart_info成员时必须保证串口已打开。底层驱动和核心层驱动都可以修改uart_info实例。
* This is the state information which is only valid when the port
* is open; it may be freed by the core driver once the device has
* been closed. Either the low level driver or the core can modify
* stuff here.
*/
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 level drivers.
*/
#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;
};
uart_iconut为串口信息计数器,包含了发送字符计数、接收字符计数等。在串口的发送中断处理函数
和接收中断处理函数中,我们需要管理这些计数。
struct uart_icount {
__u32 cts;
__u32 dsr;
__u32 rng;
__u32 dcd;
__u32 rx; /* 发送字符计数 */
__u32 tx; /* 接受字符计数 */
__u32 frame; /* 帧错误计数 */
__u32 overrun; /* Rx FIFO溢出计数 */
__u32 parity; /* 帧校验错误计数 */
__u32 brk; /* break计数 */
__u32 buf_overrun;
};


3、uart_ops
uart_ops涵盖了串口驱动可对串口设备进行的所有操作。
/*
* This structure describes all the operations that can be
* done on the physical hardware.
*/
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); /* 串口的Tx FIFO缓存是否为空 */
void (*set_mctrl)(struct uart_port *, unsigned int mctrl); /* 设置串口modem控制 */
unsigned int (*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 *, unsigned int state,
unsigned int oldstate); /* 串口电源管理 */
int (*set_wake)(struct uart_port *, unsigned int state); /* */
const char *(*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 *, unsigned int, unsigned long); /* IO控制 */
};


二、串口驱动API
1、uart_register_driver
/* 功能: uart_register_driver用于将串口驱动uart_driver注册到内核(串口核心层)中,通常在模块初始化函数调用该函数。
* 参数 drv:要注册的uart_driver
* 返回值: 成功,返回0;否则返回错误码
*/
int uart_register_driver(struct uart_driver *drv)

2、uart_unregister_driver
/* 功能: uart_unregister_driver用于注销我们已注册的uart_driver,通常在模块卸载函数调用该函数
* 参数 drv:要注销的uart_driver
* 返回值: 成功,返回0;否则返回错误码
*/
void uart_unregister_driver(struct uart_driver *drv)

3、uart_add_one_port
/* 功能: uart_add_one_port用于为串口驱动添加一个串口端口,通常在探测到设备后(驱动的设备probe方法)调用该函数
* 参数 drv:串口驱动
* port:要添加的串口端口
* 返回值: 成功,返回0;否则返回错误码
*/
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)

4、uart_remove_one_port
/* 功能: uart_remove_one_port用于删除一个已添加到串口驱动中的串口端口,通常在驱动卸载时调用该函数
* 参数 drv: 串口驱动
* port: 要删除的串口端口
* 返回值: 成功,返回0;否则返回错误码
*/
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)

5、uart_write_wakeup
/* 功能: uart_write_wakeup唤醒上层因向串口端口写数据而阻塞的进程,通常在串口发送中断处理函数中调用该函数
* 参数 port:需要唤醒写阻塞进程的串口端口
*/
void uart_write_wakeup(struct uart_port *port)

6、uart_suspend_port
/* 功能: uart_suspend_port用于挂起特定的串口端口
* 参数 drv: 要挂起的串口端口所属的串口驱动
* port:要挂起的串口端口
* 返回值: 成功返回0;否则返回错误码
*/
int uart_suspend_port(struct uart_driver *drv, struct uart_port *port)

7、uart_resume_port
/* 功能: uart_resume_port用于恢复某一已挂起的串口
* 参数 drv: 要恢复的串口端口所属的串口驱动
* port:要恢复的串口端口
* 返回值: 成功返回0;否则返回错误码
*/
int uart_resume_port(struct uart_driver *drv, struct uart_port *port)

8、uart_get_baud_rate
/* 功能: uart_get_baud_rate通过解码termios结构体来获取指定串口的波特率
* 参数 port: 要获取波特率的串口端口
* termios:当前期望的termios配置(包含串口波特率)
* old: 以前的termios配置,可以为NULL
* min: 可接受的最小波特率
* max: 可接受的最大波特率
* 返回值: 串口的波特率
*/
unsigned int
uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
struct ktermios *old, unsigned int min, unsigned int max)

9、uart_get_divisor
/* 功能: uart_get_divisor用于计算某一波特率的串口时钟分频数(串口波特率除数)
* 参数 port:要计算时钟分频数的串口端口
* baud:期望的波特率
*返回值: 串口时钟分频数
*/
unsigned int uart_get_divisor(struct uart_port *port, unsigned int baud)

10、uart_update_timeout
/* 功能: uart_update_timeout用于更新(设置)串口FIFO超时时间
* 参数 port: 要更新超时时间的串口端口
* cflag:termios结构体的cflag值
* baud: 串口的波特率
*/
void uart_update_timeout(struct uart_port *port, unsigned int cflag, unsigned int baud)

11、uart_match_port
/* 功能:uart_match_port用于判断两串口端口是否为同一端口
* 参数 port1、port2:要判断的串口端口
* 返回值:不同返回0;否则返回非0
*/
int uart_match_port(struct uart_port *port1, struct uart_port *port2)

12、uart_console_write
/* 功能: uart_console_write用于向串口端口写一控制台信息
* 参数 port: 要写信息的串口端口
* s: 要写的信息
* count: 信息的大小
* putchar: 用于向串口端口写字符的函数,该函数函数有两个参数:串口端口和要写的字符
*/
void uart_console_write(struct uart_port *port, const char *s,
unsigned int count,
void (*putchar)(struct uart_port *, int))

三、串口驱动例子
该串口驱动例子是我针对s3c2410处理器的串口2(uart2)独立开发的。因为我通过博创2410s开发板的GRPS扩展板来测试该驱动(已通过测试),所以我叫该串口为gprs_uart。
 
该驱动将串口看作平台(platform)设备。platform可以看作一伪总线,用于将集成于片上系统的轻量级设备与Linux设备驱动模型联系到一起,它包含以下两部分(有关platform的声明都在#include <linux/platform_device.h>,具体实现在drivers/base/platform.c):
1、platform设备。我们需要为每个设备定义一个platform_device实例
struct platform_device {
const char *name; /* 设备名 */
int id; /* 设备的id号 */
struct device dev; /* 其对应的device */
u32 num_resources;/* 该设备用有的资源数 */
struct resource *resource; /* 资源数组 */
};
发表于2012-07-17 10:52 2#
回复:Linux串口驱动程序设计
 
二、串口驱动API
1、uart_register_driver
/* 功能: uart_register_driver用于将串口驱动uart_driver注册到内核(串口核心层)中,通常在模块初始化函数调用该函数。
* 参数 drv:要注册的uart_driver
* 返回值: 成功,返回0;否则返回错误码
*/
int uart_register_driver(struct uart_driver *drv)
2、uart_unregister_driver
/* 功能: uart_unregister_driver用于注销我们已注册的uart_driver,通常在模块卸载函数调用该函数
* 参数 drv:要注销的uart_driver
* 返回值: 成功,返回0;否则返回错误码
*/
void uart_unregister_driver(struct uart_driver *drv)
3、uart_add_one_port
/* 功能: uart_add_one_port用于为串口驱动添加一个串口端口,通常在探测到设备后(驱动的设备probe方法)调用该函数
* 参数 drv:串口驱动
* port:要添加的串口端口
* 返回值: 成功,返回0;否则返回错误码
*/
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
4、uart_remove_one_port
/* 功能: uart_remove_one_port用于删除一个已添加到串口驱动中的串口端口,通常在驱动卸载时调用该函数
* 参数 drv: 串口驱动
* port: 要删除的串口端口
* 返回值: 成功,返回0;否则返回错误码
*/
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
5、uart_write_wakeup
/* 功能: uart_write_wakeup唤醒上层因向串口端口写数据而阻塞的进程,通常在串口发送中断处理函数中调用该函数
* 参数 port:需要唤醒写阻塞进程的串口端口
*/
void uart_write_wakeup(struct uart_port *port)
6、uart_suspend_port
/* 功能: uart_suspend_port用于挂起特定的串口端口
* 参数 drv: 要挂起的串口端口所属的串口驱动
* port:要挂起的串口端口
* 返回值: 成功返回0;否则返回错误码
*/
int uart_suspend_port(struct uart_driver *drv, struct uart_port *port)
7、uart_resume_port
/* 功能: uart_resume_port用于恢复某一已挂起的串口
* 参数 drv: 要恢复的串口端口所属的串口驱动
* port:要恢复的串口端口
* 返回值: 成功返回0;否则返回错误码
*/
int uart_resume_port(struct uart_driver *drv, struct uart_port *port)
8、uart_get_baud_rate
/* 功能: uart_get_baud_rate通过解码termios结构体来获取指定串口的波特率
* 参数 port: 要获取波特率的串口端口
* termios:当前期望的termios配置(包含串口波特率)
* old: 以前的termios配置,可以为NULL
* min: 可接受的最小波特率
* max: 可接受的最大波特率
* 返回值: 串口的波特率
*/
unsigned int
uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
struct ktermios *old, unsigned int min, unsigned int max)
9、uart_get_divisor
/* 功能: uart_get_divisor用于计算某一波特率的串口时钟分频数(串口波特率除数)
* 参数 port:要计算时钟分频数的串口端口
* baud:期望的波特率
*返回值: 串口时钟分频数
*/
unsigned int uart_get_divisor(struct uart_port *port, unsigned int baud)
10、uart_update_timeout
/* 功能: uart_update_timeout用于更新(设置)串口FIFO超时时间
* 参数 port: 要更新超时时间的串口端口
* cflag:termios结构体的cflag值
* baud: 串口的波特率
*/
void uart_update_timeout(struct uart_port *port, unsigned int cflag, unsigned int baud)
11、uart_match_port
/* 功能:uart_match_port用于判断两串口端口是否为同一端口
* 参数 port1、port2:要判断的串口端口
* 返回值:不同返回0;否则返回非0
*/
int uart_match_port(struct uart_port *port1, struct uart_port *port2)
12、uart_console_write
/* 功能: uart_console_write用于向串口端口写一控制台信息
* 参数 port: 要写信息的串口端口
* s: 要写的信息
* count: 信息的大小
* putchar: 用于向串口端口写字符的函数,该函数函数有两个参数:串口端口和要写的字符
*/
void uart_console_write(struct uart_port *port, const char *s,
unsigned int count,
void (*putchar)(struct uart_port *, int))
 
三、串口驱动例子
该串口驱动例子是我针对s3c2410处理器的串口2(uart2)独立开发的。
因为我通过博创2410s开发板的GRPS扩展板来测试该驱动(已通过测试),所以我叫该串口为gprs_uart。
 
该驱动将串口看作平台(platform)设备。platform可以看作一伪总线,用于将集成于片上系统的轻量级设备
与Linux设备驱动模型联系到一起,它包含以下两部分(有关platform的声明都在
#include <linux/platform_device.h>,具体实现在drivers/base/platform.c):
1、platform设备。我们需要为每个设备定义一个platform_device实例
struct platform_device {
const char *name; /* 设备名 */
int id; /* 设备的id号 */
struct device dev; /* 其对应的device */
u32 num_resources;/* 该设备用有的资源数 */
struct resource *resource; /* 资源数组 */
};
为我们的设备创建platform_device实例有两种方法:填充一个platform_device结构体后用
platform_device_register(一次注册一个)或platform_add_devices(一次可以注册多个platform设备)
将platform_device注册到内核;更简单的是使用platform_device_register_simple来
建立并注册我们的platform_device。

2、 platform驱动。platform设备由platform驱动进行管理。当设备加入到系统中时,platform_driver的
probe方法会 被调用来见对应的设备添加或者注册到内核;当设备从系统中移除时,platform_driver的
remove方法会被调用来做一些清理工作,如移除该 设备的一些实例、注销一些已注册到系统中去的东西。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};


更详细platform资料可参考网上相关文章。
例子驱动中申请和释放IO内存区的整个过程如下:
insmod gprs_uart.ko→gprs_init_module()→uart_register_driver()→gprs_uart_probe()→ uart_add_one_port()→gprs_uart_config_port()→gprs_uart_request_port()→request_mem_region()
rmmod gprs_uart.ko→gprs_exit_module()→uart_unregister_driver()→gprs_uart_remove()→uart_remove_one_port()→gprs_uart_release_port()→release_mem_region()
例子驱动中申请和释放IRQ资源的整个过程如下:
open /dev/gprs_uart→gprs_uart_startup()→request_irq()
close /dev/gprs_uart→gprs_uart_shutdown()→free_irq()
想了解更详细的调用过程可以在驱动模块各函数头插入printk(KERN_DEBUG "%s\n", __FUNCTION__);并在函数尾插入printk(KERN_DEBUG "%s done\n", __FUNCTION__);


下面是串口驱动例子和其GPRS测试程序源码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_*_user */
#include <linux/ioctl.h>
#include <linux/device.h>

#include <linux/platform_device.h>
#include <linux/sysrq.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/console.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/hardware.h>
#include <asm/plat-s3c/regs-serial.h>
#include <asm/arch/regs-gpio.h>


#define DEV_NAME "gprs_uart" /* 设备名 */
/* 这里将串口的主设备号设为0,则串口设备编号由内核动态分配;你也可指定串口的设备编号 */
#define GPRS_UART_MAJOR 0 /* 主设备号 */
#define GPRS_UART_MINOR 0 /* 次设备号 */
#define GPRS_UART_FIFO_SIZE 16 /* 串口FIFO的大小 */
#define RXSTAT_DUMMY_READ (0x10000000)
#define MAP_SIZE (0x100) /* 要映射的串口IO内存区大小 */

/* 串口发送中断号 */
#define TX_IRQ(port) ((port)->irq + 1)
/* 串口接收中断号 */
#define RX_IRQ(port) ((port)->irq)

/* 允许串口接收字符的标志 */
#define tx_enabled(port) ((port)->unused[0])
/* 允许串口发送字符的标志 */
#define rx_enabled(port) ((port)->unused[1])

/* 获取寄存器地址 */
#define portaddr(port, reg) ((port)->membase + (reg))

/* 读8位宽的寄存器 */
#define rd_regb(port, reg) (ioread8(portaddr(port, reg)))
/* 读32位宽的寄存器 */
#define rd_regl(port, reg) (ioread32(portaddr(port, reg)))
/* 写8位宽的寄存器 */
#define wr_regb(port, reg, val) \
do { iowrite8(val, portaddr(port, reg)); } while(0)
/* 写32位宽的寄存器 */
#define wr_regl(port, reg, val) \
do { iowrite32(val, portaddr(port, reg)); } while(0)


/* 禁止串口发送数据 */
static void gprs_uart_stop_tx(struct uart_port *port)
{
if (tx_enabled(port)) /* 若串口已启动发送 */
{
disable_irq(TX_IRQ(port)); /* 禁止发送中断 */
tx_enabled(port) = 0; /* 设置串口为未启动发送 */
}
}

/* 使能串口发送数据 */
static void gprs_uart_start_tx(struct uart_port *port)
{
if (!tx_enabled(port)) /* 若串口未启动发送 */
{
enable_irq(TX_IRQ(port)); /* 使能发送中断 */
tx_enabled(port) = 1; /* 设置串口为已启动发送 */
}
}

/* 禁止串口接收数据 */
static void gprs_uart_stop_rx(struct uart_port *port)
{
if (rx_enabled(port)) /* 若串口已启动接收 */
{
disable_irq(RX_IRQ(port)); /* 禁止接收中断 */
rx_enabled(port) = 0; /* 设置串口为未启动接收 */
}
}

/* 使能modem的状态信号 */
static void gprs_uart_enable_ms(struct uart_port *port)
{
}

/* 串口的Tx FIFO缓存是否为空 */
static unsigned int gprs_uart_tx_empty(struct uart_port *port)
{
int ret = 1;
unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
unsigned long ufcon = rd_regl(port, S3C2410_UFCON);

if (ufcon & S3C2410_UFCON_FIFOMODE) /* 若使能了FIFO */
{
if ((ufstat & S3C2410_UFSTAT_TXMASK) != 0 || /* 0 <FIFO <=15 */
(ufstat & S3C2410_UFSTAT_TXFULL)) /* FIFO满 */
ret = 0;
}
else /* 若未使能了FIFO,则判断发送缓存和发送移位寄存器是否均为空 */
{
ret = rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE;
}

return ret;
}

/* 获取串口modem控制,因为uart2无modem控制,所以CTS、DSR直接返回有效 */
static unsigned int gprs_uart_get_mctrl(struct uart_port *port)
{
return (TIOCM_CTS | TIOCM_DSR | TIOCM_CAR);
}

/* 设置串口modem控制 */
static void gprs_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
{

}

/* 设置break信号 */
static void gprs_uart_break_ctl(struct uart_port *port, int break_state)
{
unsigned long flags;
unsigned int ucon;
spin_lock_irqsave(&port->lock, flags);
ucon = rd_regl(port, S3C2410_UCON);
if (break_state)
ucon |= S3C2410_UCON_SBREAK;
else
ucon &= ~S3C2410_UCON_SBREAK;
wr_regl(port, S3C2410_UCON, ucon);
spin_unlock_irqrestore(&port->lock, flags);
}

/* 返回Rx FIFO已存多少数据 */
static int gprs_uart_rx_fifocnt(unsigned long ufstat)
{
/* 若Rx FIFO已满,返回FIFO的大小 */
if (ufstat & S3C2410_UFSTAT_RXFULL)
return GPRS_UART_FIFO_SIZE;
/* 若FIFO未满,返回Rx FIFO已存了多少字节数据 */
return (ufstat & S3C2410_UFSTAT_RXMASK) >> S3C2410_UFSTAT_RXSHIFT;
}

#define S3C2410_UERSTAT_PARITY (0x1000)

/* 串口接收中断处理函数,获取串口接收到的数据,并将这些数据递交给行规则层 */
static irqreturn_t gprs_uart_rx_chars(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
struct tty_struct *tty = port->info->tty;
unsigned int ufcon, ch, flag, ufstat, uerstat;
int max_count = 64;
/* 循环接收数据,最多一次中断接收64字节数据 */
while (max_count-- > 0)
{
ufcon = rd_regl(port, S3C2410_UFCON);
ufstat = rd_regl(port, S3C2410_UFSTAT);
/* 若Rx FIFO无数据了,跳出循环 */
if (gprs_uart_rx_fifocnt(ufstat) == 0)
break;
/* 读取Rx error状态寄存器 */
uerstat = rd_regl(port, S3C2410_UERSTAT);
/* 读取已接受到的数据 */
ch = rd_regb(port, S3C2410_URXH);
/* insert the character into the buffer */
/* 先将tty标志设为正常 */
flag = TTY_NORMAL;
/* 递增接收字符计数器 */
port->icount.rx++;
/* 判断是否存在Rx error
* if (unlikely(uerstat & S3C2410_UERSTAT_ANY))等同于
* if (uerstat & S3C2410_UERSTAT_ANY)
* 只是unlikely表示uerstat & S3C2410_UERSTAT_ANY的值为假的可能性大一些
* 另外还有一个likely(value)表示value的值为真的可能性更大一些
*/
if (unlikely(uerstat & S3C2410_UERSTAT_ANY))
{
/* 若break错误,递增icount.brk计算器 */
if (uerstat & S3C2410_UERSTAT_BREAK)
{
port->icount.brk++;
if (uart_handle_break(port))
goto ignore_char;
}
/* 若frame错误,递增icount.frame计算器 */
if (uerstat & S3C2410_UERSTAT_FRAME)
port->icount.frame++;
/* 若overrun错误,递增icount.overrun计算器 */
if (uerstat & S3C2410_UERSTAT_OVERRUN)
port->icount.overrun++;
/* 查看我们是否关心该Rx error
* port->read_status_mask保存着我们感兴趣的Rx error status
*/
uerstat &= port->read_status_mask;
/* 若我们关心该Rx error,则将flag设置为对应的error flag */
if (uerstat & S3C2410_UERSTAT_BREAK)
flag = TTY_BREAK;
else if (uerstat & S3C2410_UERSTAT_PARITY)
flag = TTY_PARITY;
else if (uerstat & ( S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_OVERRUN))
flag = TTY_FRAME;
}
/* 处理sys字符 */
if (uart_handle_sysrq_char(port, ch))
goto ignore_char;
/* 将接收到的字符插入到tty设备的flip缓冲 */
uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
ignore_char:
continue;
}

/* 刷新tty设备的flip缓冲,将接受到的数据传给行规则层 */
tty_flip_buffer_push(tty);
return IRQ_HANDLED;
}

/* 串口发送中断处理函数,将用户空间的数据(保存在环形缓冲xmit里)发送出去 */
static irqreturn_t gprs_uart_tx_chars(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
struct circ_buf *xmit = &port->info->xmit; /* 获取环线缓冲 */
int count = 256;
/* 若设置了xChar字符 */
if (port->x_char)
{
/* 将该xChar发送出去 */
wr_regb(port, S3C2410_UTXH, port->x_char);
/* 递增发送计数 */
port->icount.tx++;
/* 清除xChar */
port->x_char = 0;
/* 退出中断处理 */
goto out;
}
/* 如果没有更多的字符需要发送(环形缓冲为空),
* 或者uart Tx已停止,
* 则停止uart并退出中断处理函数
*/
if (uart_circ_empty(xmit) || uart_tx_stopped(port))
{
gprs_uart_stop_tx(port);
goto out;
}
/* 循环发送数据,直到环形缓冲为空,最多一次中断发送256字节数据 */
while (!uart_circ_empty(xmit) && count-- > 0)
{
/* 若Tx FIFO已满,退出循环 */
if (rd_regl(port, S3C2410_UFSTAT) & S3C2410_UFSTAT_TXFULL)
break;
/* 将要发送的数据写入Tx FIFO */
wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
/* 移向循环缓冲中下一要发送的数据 */
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
}
/* 如果环形缓冲区中剩余的字符少于WAKEUP_CHARS,唤醒上层 */
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(port);
/* 如果环形缓冲为空,则停止发送 */
if (uart_circ_empty(xmit))
gprs_uart_stop_tx(port);
out:
return IRQ_HANDLED;
}

/* 启动串口端口,在打开该驱动的设备文件时会调用该函数来申请串口中断,并设置串口为可接受,也可发送 */
static int gprs_uart_startup(struct uart_port *port)
{
unsigned long flags;
int ret;
const char *portname = to_platform_device(port->dev)->name;
/* 设置串口为不可接受,也不可发送 */
rx_enabled(port) = 0;
tx_enabled(port) = 0;
spin_lock_irqsave(&port->lock, flags);
/* 申请接收中断 */
ret = request_irq(RX_IRQ(port), gprs_uart_rx_chars, 0, portname, port);
if (ret != 0)
{
printk(KERN_ERR "cannot get irq %d\n", RX_IRQ(port));
return ret;
}
/* 设置串口为允许接收 */
rx_enabled(port) = 1;
/* 申请发送中断 */
ret = request_irq(TX_IRQ(port), gprs_uart_tx_chars, 0, portname, port);
if (ret)
{
printk(KERN_ERR "cannot get irq %d\n", TX_IRQ(port));
rx_enabled(port) = 0;
free_irq(RX_IRQ(port), port);
goto err;
}
/* 设置串口为允许发送 */
tx_enabled(port) = 1;
err:
spin_unlock_irqrestore(&port->lock, flags);
return ret;
}

/* 关闭串口,在关闭驱动的设备文件时会调用该函数,释放串口中断 */



static void gprs_uart_shutdown(struct uart_port *port)
{
rx_enabled(port) = 0; /* 设置串口为不允许接收 */
free_irq(RX_IRQ(port), port); /* 释放接收中断 */
tx_enabled(port) = 0; /* 设置串口为不允许发送 */
free_irq(TX_IRQ(port), port); /* 释放发送中断 */
}

/* 设置串口参数 */
static void gprs_uart_set_termios(struct uart_port *port,
struct ktermios *termios,
struct ktermios *old)
{
unsigned long flags;
unsigned int baud, quot;
unsigned int ulcon, ufcon = 0;
/* 不支持moden控制信号线
* HUPCL: 关闭时挂断moden
* CMSPAR: mark or space (stick) parity
* CLOCAL: 忽略任何moden控制线
*/
termios->c_cflag &= ~(HUPCL | CMSPAR);
termios->c_cflag |= CLOCAL;
/* 获取用户设置的串口波特率,并计算分频数(串口波特率除数quot) */
baud = uart_get_baud_rate(port, termios, old, 0, 115200*8);
if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
quot = port->custom_divisor;
else
quot = port->uartclk / baud / 16 - 1;
/* 设置数据字长 */
switch (termios->c_cflag & CSIZE)
{
case CS5:
ulcon = S3C2410_LCON_CS5;
break;
case CS6:
ulcon = S3C2410_LCON_CS6;
break;
case CS7:
ulcon = S3C2410_LCON_CS7;
break;
case CS8:
default:
ulcon = S3C2410_LCON_CS8;
break;
}
/* 是否要求设置两个停止位(CSTOPB) */
if (termios->c_cflag & CSTOPB)
ulcon |= S3C2410_LCON_STOPB;
/* 是否使用奇偶检验 */
if (termios->c_cflag & PARENB)
{
if (termios->c_cflag & PARODD) /* 奇校验 */
ulcon |= S3C2410_LCON_PODD;
else /* 偶校验 */
ulcon |= S3C2410_LCON_PEVEN;
}
else /* 无校验 */
{
ulcon |= S3C2410_LCON_PNONE;
}
if (port->fifosize > 1)
ufcon |= S3C2410_UFCON_FIFOMODE | S3C2410_UFCON_RXTRIG8;
spin_lock_irqsave(&port->lock, flags);
/* 设置FIFO控制寄存器、线控制寄存器和波特率除数寄存器 */
wr_regl(port, S3C2410_UFCON, ufcon);
wr_regl(port, S3C2410_ULCON, ulcon);
wr_regl(port, S3C2410_UBRDIV, quot);
/* 更新串口FIFO的超时时限 */
uart_update_timeout(port, termios->c_cflag, baud);
/* 设置我们感兴趣的Rx error */
port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
if (termios->c_iflag & INPCK)
port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY;
/* 设置我们忽略的Rx error */
port->ignore_status_mask = 0;
if (termios->c_iflag & IGNPAR)
port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;
/* 若未设置CREAD(使用接收器),则忽略所有Rx error*/
if ((termios->c_cflag & CREAD) == 0)
port->ignore_status_mask |= RXSTAT_DUMMY_READ;
spin_unlock_irqrestore(&port->lock, flags);
}
/* 获取串口类型 */
static const char *gprs_uart_type(struct uart_port *port)
{/* 返回描述串口类型的字符串指针 */
return port->type == PORT_S3C2410 ? "gprs_uart:s3c2410_uart2" : NULL;
}

/* 申请串口一些必要的资源,如IO端口/IO内存资源,必要时还可以重新映射串口端口 */
static int gprs_uart_request_port(struct uart_port *port)
{
struct resource *res;
const char *name = to_platform_device(port->dev)->name;
/* request_mem_region请求分配IO内存,从开始port->mapbase,大小MAP_SIZE
* port->mapbase保存当前串口的寄存器基地址(物理)
* uart2: 0x50008000
*/
res = request_mem_region(port->mapbase, MAP_SIZE, name);
if (res == NULL)
{
printk(KERN_ERR"request_mem_region error: %p\n", res);
return -EBUSY;
}
return 0;
}

/* 释放串口已申请的IO端口/IO内存资源,必要时还需iounmap */
static void gprs_uart_release_port(struct uart_port *port)
{
/* 释放已分配IO内存 */
release_mem_region(port->mapbase, MAP_SIZE);
}

/* 执行串口所需的自动配置 */
static void gprs_uart_config_port(struct uart_port *port, int flags)
{
  int retval;
/* 请求串口 */
retval = gprs_uart_request_port(port);
/* 设置串口类型 */
if (flags & UART_CONFIG_TYPE && retval == 0)
port->type = PORT_S3C2410;
}

/* The UART operations structure */
static struct uart_ops gprs_uart_ops = {
.start_tx = gprs_uart_start_tx, /* Start transmitting */
.stop_tx = gprs_uart_stop_tx, /* Stop transmission */
.stop_rx = gprs_uart_stop_rx, /* Stop reception */
.enable_ms = gprs_uart_enable_ms, /* Enable modem status signals */
.tx_empty = gprs_uart_tx_empty, /* Transmitter busy? */
.get_mctrl = gprs_uart_get_mctrl, /* Get modem control */
.set_mctrl = gprs_uart_set_mctrl, /* Set modem control */
.break_ctl = gprs_uart_break_ctl, /* Set break signal */
.startup = gprs_uart_startup, /* App opens GPRS_UART */
.shutdown = gprs_uart_shutdown, /* App closes GPRS_UART */
.set_termios = gprs_uart_set_termios, /* Set termios */
.type = gprs_uart_type, /* Get UART type */
.request_port = gprs_uart_request_port, /* Claim resources associated with a GPRS_UART port */
.release_port = gprs_uart_release_port, /* Release resources associated with a GPRS_UART port */
.config_port = gprs_uart_config_port, /* Configure when driver adds a GPRS_UART port */
};

/* Uart driver for GPRS_UART */
static struct uart_driver gprs_uart_driver = {
.owner = THIS_MODULE, /* Owner */
.driver_name = DEV_NAME, /* Driver name */
.dev_name = DEV_NAME, /* Device node name */
.major = GPRS_UART_MAJOR, /* Major number */
.minor = GPRS_UART_MINOR, /* Minor number start */
.nr = 1, /* Number of UART ports */
};

/* Uart port for GPRS_UART port */
static struct uart_port gprs_uart_port = {
.irq = IRQ_S3CUART_RX2, /* IRQ */
.fifosize = GPRS_UART_FIFO_SIZE, /* Size of the FIFO */
.iotype = UPIO_MEM, /* IO memory */
.flags = UPF_BOOT_AUTOCONF, /* UART port flag */
.ops = &gprs_uart_ops, /* UART operations */
.line = 0, /* UART port number */
.lock = __SPIN_LOCK_UNLOCKED(gprs_uart_port.lock),
};

/* 初始化指定串口端口 */
static int gprs_uart_init_port(struct uart_port *port, struct platform_device *platdev)
{
unsigned long flags;
unsigned int gphcon;
if (platdev == NULL)
return -ENODEV;
port->dev = &platdev->dev;
/* 设置串口波特率时钟频率 */
port->uartclk = clk_get_rate(clk_get(&platdev->dev, "pclk"));
/* 设置串口的寄存器基地址(物理): 0x50008000 */
port->mapbase = S3C2410_PA_UART2;
/* 设置当前串口的寄存器基地址(虚拟): 0xF5008000 */
port->membase = S3C24XX_VA_UART + (S3C2410_PA_UART2 - S3C24XX_PA_UART);
spin_lock_irqsave(&port->lock, flags);
wr_regl(port, S3C2410_UCON, S3C2410_UCON_DEFAULT);
wr_regl(port, S3C2410_ULCON, S3C2410_LCON_CS8 | S3C2410_LCON_PNONE);
wr_regl(port, S3C2410_UFCON, S3C2410_UFCON_FIFOMODE
| S3C2410_UFCON_RXTRIG8 | S3C2410_UFCON_RESETBOTH);
/* 将I/O port H的gph6和gph7设置为TXD2和RXD2 */
gphcon = readl(S3C2410_GPHCON);
gphcon &= ~((0x5) << 12);
writel(gphcon, S3C2410_GPHCON);
spin_unlock_irqrestore(&port->lock, flags);
return 0;
}

/* Platform driver probe */
static int __init gprs_uart_probe(struct platform_device *dev)
{
int ret;
/* 初始化串口 */
ret = gprs_uart_init_port(&gprs_uart_port, dev);
if (ret < 0)
{
printk(KERN_ERR"gprs_uart_probe: gprs_uart_init_port error: %d\n", ret);
return ret;
}
/* 添加串口 */
ret = uart_add_one_port(&gprs_uart_driver, &gprs_uart_port);
if (ret < 0)
{
printk(KERN_ERR"gprs_uart_probe: uart_add_one_port error: %d\n", ret);
return ret;
}
/* 将串口uart_port结构体保存在platform_device->dev->driver_data中 */
platform_set_drvdata(dev, &gprs_uart_port);
return 0;
}

/* Called when the platform driver is unregistered */
static int gprs_uart_remove(struct platform_device *dev)
{
platform_set_drvdata(dev, NULL);
/* 移除串口 */
uart_remove_one_port(&gprs_uart_driver, &gprs_uart_port);
return 0;
}

/* Suspend power management event */
static int gprs_uart_suspend(struct platform_device *dev, pm_message_t state)
{
uart_suspend_port(&gprs_uart_driver, &gprs_uart_port);
return 0;
}

/* Resume after a previous suspend */
static int gprs_uart_resume(struct platform_device *dev)
{
uart_resume_port(&gprs_uart_driver, &gprs_uart_port);
return 0;
}

/* Platform driver for GPRS_UART */
static struct platform_driver gprs_plat_driver = {
.probe = gprs_uart_probe, /* Probe method */
.remove = __exit_p(gprs_uart_remove), /* Detach method */
.suspend = gprs_uart_suspend, /* Power suspend */
.resume = gprs_uart_resume, /* Resume after a suspend */
.driver = {
.owner = THIS_MODULE,
.name = DEV_NAME, /* Driver name */
},
};

/* Platform device for GPRS_UART */
struct platform_device *gprs_plat_device;

static int __init gprs_init_module(void)
{
int retval;
/* Register uart_driver for GPRS_UART */
retval = uart_register_driver(&gprs_uart_driver);
if (0 != retval)
{
printk(KERN_ERR "gprs_init_module: can't register the GPRS_UART driver %d\n",retval);
return retval;
}
/* Register platform device for GPRS_UART. Usually called
during architecture-specific setup */
gprs_plat_device = platform_device_register_simple(DEV_NAME, 0, NULL, 0);
if (IS_ERR(gprs_plat_device))
{
retval = PTR_ERR(gprs_plat_device);
printk(KERN_ERR "gprs_init_module: can't register platform device %d\n", retval);
goto fail_reg_plat_dev;
}
/* Announce a matching driver for the platform
devices registered above */
retval = platform_driver_register(&gprs_plat_driver);
if (0 != retval)
{
printk(KERN_ERR "gprs_init_module: can't register platform driver %d\n", retval);
goto fail_reg_plat_drv;
}
return 0; /* succeed */
fail_reg_plat_drv:
platform_device_unregister(gprs_plat_device);
fail_reg_plat_dev:
uart_unregister_driver(&gprs_uart_driver);
return retval;
}

static void __exit gprs_exit_module(void)
{
/* The order of unregistration is important. Unregistering the
UART driver before the platform driver will crash the system */
/* Unregister the platform driver */
platform_driver_unregister(&gprs_plat_driver);
/* Unregister the platform devices */
platform_device_unregister(gprs_plat_device);
/* Unregister the GPRS_UART driver */
uart_unregister_driver(&gprs_uart_driver);
}

module_init(gprs_init_module);
module_exit(gprs_exit_module);

2018-10-31 19:39:28 vv_cool 阅读数 657
  • 串口通信详解-1.7.ARM裸机第七部分

    本期课程主要讲述S5PV210的串口通信。本课程共分为3个部分:首先用3节课时间系统讲解了通信的基础知识、串行通信的重要概念和知识等;然后用5节课时间详细分析了S5PV210的串口控制器的关键部分及代码编写;后用3节课时间讲述了如何基于S5PV210的基本串口通信函数实现stdio的移植。

    8375 人正在学习 去看看 朱有鹏

Linux串口驱动相关主要涉及3个重要的结构体,uart_driver,uart_port,uart_ops。本文主要以msm8917平台分析, 先贴dts相关代码

	blsp1_uart2: serial@78b0000 {
		compatible = "qcom,msm-lsuart-v14";
		reg = <0x78b0000 0x200>;
		interrupts = <0 108 0>;
		status = "disabled";
		clocks = <&clock_gcc clk_gcc_blsp1_uart2_apps_clk>,
		<&clock_gcc clk_gcc_blsp1_ahb_clk>;
		clock-names = "core_clk", "iface_clk";
	};

几个重要结构体定义

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_driver *drv);

实际上,uart_register_driver()和uart_unregister_driver()中分别包含了tty_register_driver()和tty_unregister_driver()的操作。
uart_driver的定义

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;
};

对应高通平台的代码

static struct uart_driver msm_hsl_uart_driver = {
	.owner = THIS_MODULE,
	.driver_name = "msm_serial_hsl",
	.dev_name = "ttyHSL",
	.nr = UART_NR,
	.cons = MSM_HSL_CONSOLE,
};

其中uart_driver中的uart_state比较重要,在uart_register_driver中为state分配了内存

/*
 * This is the state information which is persistent across opens.
 */
struct uart_state {
	struct tty_port		port;

	enum uart_pm_state	pm_state;
	struct circ_buf		xmit;

	struct uart_port	*uart_port;
};

uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。串口核心层提供如下函数来添加1个端口:

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 uart_driver *drv, struct uart_port *port);

uart_port定义

struct uart_port {
	spinlock_t		lock;			/* port lock */
	unsigned long		iobase;			/* in/out[bwl] */
	unsigned char __iomem	*membase;		/* read/write[bwl] */
	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);
	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 *);
	unsigned int		irq;			/* irq number */
	unsigned long		irqflags;		/* irq flags  */
	unsigned int		uartclk;		/* base uart clock */
	unsigned int		fifosize;		/* tx fifo size */
	unsigned char		x_char;			/* xon/xoff char */
	unsigned char		regshift;		/* reg offset shift */
	unsigned char		iotype;			/* io access style */
	unsigned char		unused1;

#define UPIO_PORT		(0)
#define UPIO_HUB6		(1)
#define UPIO_MEM		(2)
#define UPIO_MEM32		(3)
#define UPIO_AU			(4)			/* Au1x00 and RT288x type IO */
#define UPIO_TSI		(5)			/* Tsi108/109 type IO */

	unsigned int		read_status_mask;	/* driver specific */
	unsigned int		ignore_status_mask;	/* driver specific */
	struct uart_state	*state;			/* pointer to parent state */
	struct uart_icount	icount;			/* statistics */

	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) (1 << 1))
#define UPF_SAK			((__force upf_t) (1 << 2))
#define UPF_SPD_MASK		((__force upf_t) (0x1030))
#define UPF_SPD_HI		((__force upf_t) (0x0010))
#define UPF_SPD_VHI		((__force upf_t) (0x0020))
#define UPF_SPD_CUST		((__force upf_t) (0x0030))
#define UPF_SPD_SHI		((__force upf_t) (0x1000))
#define UPF_SPD_WARP		((__force upf_t) (0x1010))
#define UPF_SKIP_TEST		((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ		((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD		((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY		((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART		((__force upf_t) (1 << 14))
#define UPF_NO_TXEN_TEST	((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER	((__force upf_t) (1 << 16))
/* Port has hardware-assisted h/w flow control (iow, auto-RTS *not* auto-CTS) */
#define UPF_HARD_FLOW		((__force upf_t) (1 << 21))
/* 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		((__force upf_t) (0x17fff))
#define UPF_USR_MASK		((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

	/* status must be updated while holding port lock */
	upstat_t		status;

#define UPSTAT_CTS_ENABLE	((__force upstat_t) (1 << 0))
#define UPSTAT_DCD_ENABLE	((__force upstat_t) (1 << 1))

	int			hw_stopped;		/* sw-assisted CTS flow state */
	unsigned int		mctrl;			/* current modem ctrl settings */
	unsigned int		timeout;		/* character-based timeout */
	unsigned int		type;			/* port type */
	const struct uart_ops	*ops;
	unsigned int		custom_divisor;
	unsigned int		line;			/* port index */
	resource_size_t		mapbase;		/* for ioremap */
	struct device		*dev;			/* parent device */
	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) */
	void			*private_data;		/* generic platform data pointer */
};

对应高通平台的代码

struct msm_hsl_port {
	struct uart_port	uart;
	char			name[16];
	struct clk		*clk;
	struct clk		*pclk;
	struct dentry		*loopback_dir;
	unsigned int		imr;
	unsigned int		*uart_csr_code;
	unsigned int            *gsbi_mapbase;
	unsigned int            *mapped_gsbi;
	unsigned int            old_snap_state;
	unsigned long		ver_id;
	int			tx_timeout;
	struct mutex		clk_mutex;
	enum uart_core_type	uart_type;
	enum uart_func_mode	func_mode;
	struct wakeup_source	port_open_ws;
	int			clk_enable_count;
	u32			bus_perf_client;
	/* BLSP UART required BUS Scaling data */
	struct msm_bus_scale_pdata *bus_scale_table;
};

msm_hsl_port 对uart_port做了一个封装

static struct msm_hsl_port msm_hsl_uart_ports[] = {
	{
		.uart = {
			.iotype = UPIO_MEM,
			.ops = &msm_hsl_uart_pops,
			.flags = UPF_BOOT_AUTOCONF,
			.fifosize = 64,
			.line = 0,
		},
	},
	{
		.uart = {
			.iotype = UPIO_MEM,
			.ops = &msm_hsl_uart_pops,
			.flags = UPF_BOOT_AUTOCONF,
			.fifosize = 64,
			.line = 1,
		},
	},
	{
		.uart = {
			.iotype = UPIO_MEM,
			.ops = &msm_hsl_uart_pops,
			.flags = UPF_BOOT_AUTOCONF,
			.fifosize = 64,
			.line = 2,
		},
	},
};

uart_ops

/*
 * This structure describes all the operations that can be done on the
 * physical hardware.  See Documentation/serial/driver for details.
 */
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);
	void		(*wake_peer)(struct uart_port *);

	/*
	 * 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
};

高通平台对应代码

static struct uart_ops msm_hsl_uart_pops = {
	.tx_empty = msm_hsl_tx_empty,
	.set_mctrl = msm_hsl_set_mctrl,
	.get_mctrl = msm_hsl_get_mctrl,
	.stop_tx = msm_hsl_stop_tx,
	.start_tx = msm_hsl_start_tx,
	.stop_rx = msm_hsl_stop_rx,
	.enable_ms = msm_hsl_enable_ms,
	.break_ctl = msm_hsl_break_ctl,
	.startup = msm_hsl_startup,
	.shutdown = msm_hsl_shutdown,
	.set_termios = msm_hsl_set_termios,
	.type = msm_hsl_type,
	.release_port = msm_hsl_release_port,
	.request_port = msm_hsl_request_port,
	.config_port = msm_hsl_config_port,
	.verify_port = msm_hsl_verify_port,
	.pm = msm_hsl_power,
};

驱动注册流程

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

  1. 定义uart_driver、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驱动的主体工作。

串口驱动初始化过程

在高通串口驱动的模块加载函数(msm_serial_hsl_init)中会调用uart_register_driver()注册msm_hsl_uart_driver这个uart_driver(对应driver)
同时经过msm_serial_hsl_init()–>platform_driver_register(&msm_hsl_platform_driver);–>msm_serial_hsl_probe–>初始化UART端口并调用uart_add_one_port()添加端口。(对应device)

下面开始具体分析,从uart_register_driver开始,对应代码在serial_core.c中

/**                                                                                         
 *  uart_register_driver - register a driver with the uart core layer                       
 *  @drv: low level driver structure                                                        
 *                                                                                          
 *  Register a uart driver with the core driver.  We in turn register                       
 *  with the tty layer, and initialise the core driver per-port state.                      
 *                                                                                          
 *  We have a proc file in /proc/tty/driver which is named after the                        
 *  normal driver.                                                                          
 *                                                                                          
 *  drv->port should be NULL, and the per-port structures should be                         
 *  registered using uart_add_one_port after this call has succeeded.                       
 */                                                                                         
int uart_register_driver(struct uart_driver *drv)                                           
{                                                                                           
    struct tty_driver *normal;                                                              
    int i, retval;                                                                          
                                                                                            
    BUG_ON(drv->state);                                                                     
                                                                                            
    /*                                                                                      
     * Maybe we should be using a slab cache for this, especially if                        
     * we have a large number of ports to handle.                                           
     */                                                                                     
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);                  
    if (!drv->state)                                                                        
        goto out;                                                                           
                                                                                            
    normal = alloc_tty_driver(drv->nr);                                                     
    if (!normal)                                                                            
        goto out_kfree;                                                                     
                                                                                            
    drv->tty_driver = normal;                                                               
                                                                                            
    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;                                                          
    tty_set_operations(normal, &uart_ops);    //会调用到此ops中有uart_open等
                                                          
    /*                                                    
     * Initialise the UART state(s).                      
     */                                                   
    for (i = 0; i < drv->nr; i++) {                       
        struct uart_state *state = drv->state + i;        
        struct tty_port *port = &state->port;             
                                                          
        tty_port_init(port);                              
        port->ops = &uart_port_ops;                       
        port->close_delay     = HZ / 2; /* .5 seconds */  
        port->closing_wait    = 30 * HZ;/* 30 seconds */  
    }                                                     
                                                          
    retval = tty_register_driver(normal);                 
    if (retval >= 0)                                      
        return retval;                                    
                                                          
    for (i = 0; i < drv->nr; i++)                         
        tty_port_destroy(&drv->state[i].port);            
    put_tty_driver(normal);                               
out_kfree:                                                
    kfree(drv->state);                                    
out:                                                      
    return -ENOMEM;                                       
}                                                                                                       

贴两个ops

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,
    .set_ldisc  = uart_set_ldisc,
    .stop       = uart_stop,
    .start      = uart_start,
    .hangup     = uart_hangup,
    .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,
    .tiocmset   = uart_tiocmset,
    .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
};

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,
};

tty_port_init代码位于tty_port.c中,在tty_port初始化中又调用到了tty_buffer_init。tty_buffer_init的代码在tty_buffer.c中
便于理解把tty_buffer_init代码贴一下

/**                                                  
 *  tty_buffer_init     -   prepare a tty buffer stru
 *  @tty: tty to initialise                          
 *                                                   
 *  Set up the initial state of the buffer management
 *  Must be called before the other tty buffer functi
 */                                                  
                                                     
void tty_buffer_init(struct tty_port *port)          
{                                                    
    struct tty_bufhead *buf = &port->buf;            
                                                     
    mutex_init(&buf->lock);                          
    tty_buffer_reset(&buf->sentinel, 0);             
    buf->head = &buf->sentinel;                      
    buf->tail = &buf->sentinel;                      
    init_llist_head(&buf->free);                     
    atomic_set(&buf->mem_used, 0);                   
    atomic_set(&buf->priority, 0);                   
    INIT_WORK(&buf->work, flush_to_ldisc);  //work比较重要         
    buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;         
}                                                    

看一下上面tty_port结构体

struct tty_port {                                                      
    struct tty_bufhead  buf;        /* Locked internally */            
    struct tty_struct   *tty;       /* Back pointer */                 
    struct tty_struct   *itty;      /* internal back ptr */            
    const struct tty_port_operations *ops;  /* Port operations */      
    spinlock_t      lock;       /* Lock protecting tty field */        
    int         blocked_open;   /* Waiting to open */                  
    int         count;      /* Usage count */                          
    wait_queue_head_t   open_wait;  /* Open waiters */                 
    wait_queue_head_t   close_wait; /* Close waiters */                
    wait_queue_head_t   delta_msr_wait; /* Modem status change */      
    unsigned long       flags;      /* TTY flags ASY_*/                
    unsigned char       console:1,  /* port is a console */            
                low_latency:1;  /* optional: tune for latency */       
    struct mutex        mutex;      /* Locking */                      
    struct mutex        buf_mutex;  /* Buffer alloc lock */            
    unsigned char       *xmit_buf;  /* Optional buffer */              
    unsigned int        close_delay;    /* Close port delay */         
    unsigned int        closing_wait;   /* Delay for output */         
    int         drain_delay;    /* Set to zero if no pure time         
                           based drain is needed else                  
                           set to size of fifo */                      
    struct kref     kref;       /* Ref counter */                      
};                                                                     

再贴下tty_bufhead

struct tty_bufhead {
    struct tty_buffer *head;    /* Queue head */
    struct work_struct work;
    struct mutex       lock;
    atomic_t       priority;
    struct tty_buffer sentinel;
    struct llist_head free;     /* Free queue head */
    atomic_t       mem_used;    /* In-use buffers excluding free list */
    int        mem_limit;
    struct tty_buffer *tail;    /* Active buffer */
};

再回到tty_register_driver(normal)在tty_io.c中。
tty_cdev_add(driver, dev, 0, driver->num);会调用

static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
        unsigned int index, unsigned int count)
{
    /* init here, since reused cdevs cause crashes */
    cdev_init(&driver->cdevs[index], &tty_fops);
    driver->cdevs[index].owner = driver->owner;
    return cdev_add(&driver->cdevs[index], dev, count);
}

此处关联tty_fops。用户空间会open,read,write会调用对应的tty_open,tty_read,tty_write

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,
};

读写流程

串口打开

用户open -> tty_open -> tty_init_dev(driver, index) -> tty_ldisc_setup -> tty_ldisc_open ->ld->ops->open(tty)-> n_tty_open(n_tty.c)
当tty_init_dev走完后又会走下面的
retval = tty->ops->open(tty, filp);
tty对应tty_struct。ops->open对应driver->open对应uart_ops中的uart_open->uart_startup(tty, state, 0);->uart_port_startup->uport->ops->startup(uport)(对应msm_hsl_uart_pops)->msm_hsl_startup。至此整个流程已经走完。read,write同理分析。
其中tty_init_dev比较重要

/**                                                                                      
 *  tty_init_dev        -   initialise a tty device                                      
 *  @driver: tty driver we are opening a device on                                       
 *  @idx: device index                                                                   
 *  @ret_tty: returned tty structure                                                     
 *                                                                                       
 *  Prepare a tty device. This may not be a "new" clean device but                       
 *  could also be an active device. The pty drivers require special                      
 *  handling because of this.                                                            
 *                                                                                       
 *  Locking:                                                                             
 *      The function is called under the tty_mutex, which                                
 *  protects us from the tty struct or driver itself going away.                         
 *                                                                                       
 *  On exit the tty device has the line discipline attached and                          
 *  a reference count of 1. If a pair was created for pty/tty use                        
 *  and the other was a pty master then it too has a reference count of 1.               
 *                                                                                       
 * WSH 06/09/97: Rewritten to remove races and properly clean up after a                 
 * failed open.  The new code protects the open with a mutex, so it's                    
 * really quite straightforward.  The mutex locking can probably be                      
 * relaxed for the (most common) case of reopening a tty.                                
 */                                                                                      
                                                                                         
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)                      
{                                                                                        
    struct tty_struct *tty;                                                              
    int retval;                                                                          
                                                                                         
    /*                                                                                   
     * First time open is complex, especially for PTY devices.                           
     * This code guarantees that either everything succeeds and the                      
     * TTY is ready for operation, or else the table slots are vacated                   
     * and the allocated memory released.  (Except that the termios                      
     * and locked termios may be retained.)                                              
     */                                                                                  
                                                                                         
    if (!try_module_get(driver->owner))                                                  
        return ERR_PTR(-ENODEV);                                                         
                                                                                         
    tty = alloc_tty_struct(driver, idx);                                                 
    if (!tty) {                                                                          
        retval = -ENOMEM;                                                                
        goto err_module_put;                                                             
    }                                                                                    
                                                                                         
    tty_lock(tty);                                                                       
    retval = tty_driver_install_tty(driver, tty);                                        
    if (retval < 0)                                                                      
        goto err_deinit_tty;       

    if (!tty->port)
        tty->port = driver->ports[idx];

    WARN_RATELIMIT(!tty->port,
            "%s: %s driver does not set tty->port. This will crash the kernel later. Fix the driver!\n",
            __func__, tty->driver->name);

    tty->port->itty = tty;

    /*
     * Structures all installed ... call the ldisc open routines.
     * If we fail here just call release_tty to clean up.  No need
     * to decrement the use counts, as release_tty doesn't care.
     */
    retval = tty_ldisc_setup(tty, tty->link);
    if (retval)
        goto err_release_tty;
    /* Return the tty locked so that it cannot vanish under the caller */
    return tty;

err_deinit_tty:
    tty_unlock(tty);
    deinitialize_tty_struct(tty);
    free_tty_struct(tty);
err_module_put:
    module_put(driver->owner);
    return ERR_PTR(retval);

    /* call the tty release_tty routine to clean out this slot */
err_release_tty:
    tty_unlock(tty);
    printk_ratelimited(KERN_INFO "tty_init_dev: ldisc open failed, "
                 "clearing slot %d\n", idx);
    release_tty(tty, idx);
    return ERR_PTR(retval);
}                                                      

alloc_tty_struct对tty_struct结构体进行初始化

/**
 *  alloc_tty_struct
 *
 *  This subroutine allocates and initializes a tty structure.
 *
 *  Locking: none - tty in question is not exposed at this point
 */

struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx)
{
    struct tty_struct *tty;

    tty = kzalloc(sizeof(*tty), GFP_KERNEL);
    if (!tty)
        return NULL;

    kref_init(&tty->kref);
    tty->magic = TTY_MAGIC;
    tty_ldisc_init(tty);//设置ldisc
    tty->session = NULL;
    tty->pgrp = NULL;
    mutex_init(&tty->legacy_mutex);
    mutex_init(&tty->throttle_mutex);
    init_rwsem(&tty->termios_rwsem);
    mutex_init(&tty->winsize_mutex);
    init_ldsem(&tty->ldisc_sem);
    init_waitqueue_head(&tty->write_wait);
    init_waitqueue_head(&tty->read_wait);
    INIT_WORK(&tty->hangup_work, do_tty_hangup);
    mutex_init(&tty->atomic_write_lock);
    spin_lock_init(&tty->ctrl_lock);
    spin_lock_init(&tty->flow_lock);
    INIT_LIST_HEAD(&tty->tty_files);
    INIT_WORK(&tty->SAK_work, do_SAK_work);

    tty->driver = driver;
    tty->ops = driver->ops;//关联driver->ops
    tty->index = idx;
    tty_line_name(driver, idx, tty->name);
    tty->dev = tty_get_device(tty);

    return tty;
}

在来看下n_tty_open

/**
 *	n_tty_open		-	open an ldisc
 *	@tty: terminal to open
 *
 *	Called when this line discipline is being attached to the
 *	terminal device. Can sleep. Called serialized so that no
 *	other events will occur in parallel. No further open will occur
 *	until a close.
 */

static int n_tty_open(struct tty_struct *tty)
{
	struct n_tty_data *ldata;

	/* Currently a malloc failure here can panic */
	ldata = vmalloc(sizeof(*ldata));
	if (!ldata)
		goto err;

	ldata->overrun_time = jiffies;
	mutex_init(&ldata->atomic_read_lock);
	mutex_init(&ldata->output_lock);

	tty->disc_data = ldata;
	reset_buffer_flags(tty->disc_data);
	ldata->column = 0;
	ldata->canon_column = 0;
	ldata->minimum_to_wake = 1;
	ldata->num_overrun = 0;
	ldata->no_room = 0;
	ldata->lnext = 0;
	tty->closing = 0;
	/* indicate buffer work may resume */
	clear_bit(TTY_LDISC_HALTED, &tty->flags);
	n_tty_set_termios(tty, NULL);
	tty_unthrottle(tty);

	return 0;
err:
	return -ENOMEM;

贴下跟buf相关结构体

struct n_tty_data {
	/* producer-published */
	size_t read_head;
	size_t commit_head;
	size_t canon_head;
	size_t echo_head;
	size_t echo_commit;
	size_t echo_mark;
	DECLARE_BITMAP(char_map, 256);

	/* private to n_tty_receive_overrun (single-threaded) */
	unsigned long overrun_time;
	int num_overrun;

	/* non-atomic */
	bool no_room;

	/* must hold exclusive termios_rwsem to reset these */
	unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
	unsigned char push:1;

	/* shared by producer and consumer */
	char read_buf[N_TTY_BUF_SIZE];
	DECLARE_BITMAP(read_flags, N_TTY_BUF_SIZE);
	unsigned char echo_buf[N_TTY_BUF_SIZE];

	int minimum_to_wake;

	/* consumer-published */
	size_t read_tail;
	size_t line_start;

	/* protected by output lock */
	unsigned int column;
	unsigned int canon_column;
	size_t echo_tail;

	struct mutex atomic_read_lock;
	struct mutex output_lock;
};

uart_port_startup中涉及到state->xmit.buf的初始化,贴下代码简单分析下

/*
 * Startup the port.  This will be called once per open.  All calls
 * will be serialised by the per-port mutex.
 */
static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,
        int init_hw)
{
    struct uart_port *uport = state->uart_port;
    unsigned long page;
    int retval = 0;

    if (uport->type == PORT_UNKNOWN)
        return 1;

    /*
     * Make sure the device is in D0 state.
     */
    uart_change_pm(state, UART_PM_STATE_ON);

    /*
     * Initialise and allocate the transmit and temporary
     * buffer.
     */
    if (!state->xmit.buf) {
        /* This is protected by the per port mutex */
        page = get_zeroed_page(GFP_KERNEL);
        if (!page)
            return -ENOMEM;

        state->xmit.buf = (unsigned char *) page;
        uart_circ_clear(&state->xmit);
    }

    retval = uport->ops->startup(uport);
    if (retval == 0) {
        if (uart_console(uport) && uport->cons->cflag) {
            tty->termios.c_cflag = uport->cons->cflag;
            uport->cons->cflag = 0;
        }
        /*
         * Initialise the hardware port settings.
         */
        uart_change_speed(tty, state, NULL);

        if (init_hw) {
            /*
             * Setup the RTS and DTR signals once the
             * port is open and ready to respond.
             */
            if (tty->termios.c_cflag & CBAUD)
                uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR);
        }

        spin_lock_irq(&uport->lock);
        if (uart_cts_enabled(uport) &&
            !(uport->ops->get_mctrl(uport) & TIOCM_CTS))
            uport->hw_stopped = 1;
        else
            uport->hw_stopped = 0;
        spin_unlock_irq(&uport->lock);
    }

    /*
     * This is to allow setserial on this port. People may want to set
     * port/irq/type and then reconfigure the port properly if it failed
     * now.
     */
    if (retval && capable(CAP_SYS_ADMIN))
        return 1;

    return retval;
}

tty_open 的工作:

  1. 获取到 tty_driver
  2. 根据 tty_driver 初始化一个 tty_struct
    2.1 设置 tty_struct 的线路规程为 N_TTY (不同类型的线路规程有不同的 ops)
    2.2 初始化一个延时工作队列,唤醒时调用flush_to_ldisc ,读函数时我们需要分析它。
    2.3 初始化 tty_struct 里的两个等待队列头。
    2.4 设置 tty_struct->ops == tty_driver->ops 。
  3. 在 tty_ldisc_setup 函数中调用到线路规程的open函数,对于 N_TTY 来说是 n_tty_open 。
  4. 如果 tty_struct->ops 也就是 tty_driver->ops 定义了 open 函数则调用,显然是有的 uart_open 。

串口写流程

tty_write–>do_tty_write(ld->ops->write, tty, file, buf, count)(把用户空间的buf拷贝到tty->write_buf)–>n_tty_write–>tty->ops->write(tty, b, nr)–>uart_write(把数据拷贝到state->xmit环形缓冲区中)–>uart_start(tty)–>__uart_start(tty)–>port->ops->start_tx(port)–>msm_hs_start_tx
写流程:

  1. 将当前进程加入到等待队列
  2. 设置当前进程为可打断的
  3. 层层调用最终调用到底层的 start_tx 函数,将要发送的数据存入 DATA 寄存器,由硬件自动发送。
  4. 进程调度,当前进程进入休眠。
  5. 硬件发送完成,进入中断处理函数,唤醒对面队列。

串口读流程

tty_read–>ld->ops->read(tty, file, buf, count)–>n_tty_read–>copy_from_read_buf(tty, &b, &nr)–>copy_to_user(*b, read_buf_add(ldata, tail), n)(把ldata=tty->disc_data中的read_buf拷贝到用户空间)
再来看下ldata从哪里来
原始数据肯定是硬件读出来的。
msm_hsl_irq–>handle_rx(port, misr)–>tty_flip_buffer_push–>tty_schedule_flip–>schedle_work(&buf->work)–>flush_to_ldisc(操作port->buf)–>receive_buf(tty, head, count)–>disc->ops-receive_buf2–>n_tty_receive_buf2–>n_tty_receive_buf_common–>__receive_buf(tty, cp, fp, n)–>n_tty_receive_buf_raw–>put_tty_queue(*cp++, ldata)–>*read_buf_addr(ldata, ldata->read_head) = c–>&ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]

主要工作

  1. 将当前进程加入等待队列
  2. 设置当前进程可中断
  3. 进程调度,当前进程进入休眠
  4. 在某处被唤醒
  5. 从 tty->read_buf 取出数据,通过 tty_put_user 拷贝到用户空间。
    唤醒是在中断处理函数中,当DATA寄存器满,触发中断,中断处理函数中调用 tty_flip_buffer_push 。

几个重点问题

环形缓冲区

线路规程
注册在console_init(tty_io.c)–>tty_ldisc_begin()–> (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

2016-01-11 22:15:52 wsclinux 阅读数 1461
  • 串口通信详解-1.7.ARM裸机第七部分

    本期课程主要讲述S5PV210的串口通信。本课程共分为3个部分:首先用3节课时间系统讲解了通信的基础知识、串行通信的重要概念和知识等;然后用5节课时间详细分析了S5PV210的串口控制器的关键部分及代码编写;后用3节课时间讲述了如何基于S5PV210的基本串口通信函数实现stdio的移植。

    8375 人正在学习 去看看 朱有鹏

一、对于串口驱动Linux系统中UART驱动属于终端设备驱动,应该说是实现串口驱动和终端驱动来实现串口终端设备的驱动。要了解串口终端的驱动在Linux系统的结构就先要了解终端设备驱动在Linux系统中的结构体系,一方面自己了解的不够,另一发面关于终端设备的体系结构网上有很多很好很详细的资料参考,这里我主要是鉴于终端设备体系的复杂性,我大概总结一下自己了解到的一些方面和一个整体的框架。

二、终端设备的结构


 Linux内核中 tty的层次结构图所示,包含tty核心、tty线路规程和tty驱动tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的驱动,这是对设备的驱动,应该要我们来实现的,但是后面我们可以看到,对于串口驱动Linux还要进行抽象把共性提取出来封装,使得整个驱动层次化简单化。使得驱动的修改只要设计设备硬件的差异来,但是这样使得设备驱动的结构体系比较复杂,过程层次太多,难于理解。

tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个 tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。

接收数据的流程为: 从tty硬件接收到的数据向上交给tty驱动,进入tty线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。尽管大多数时候tty核心和tty之间的数据传输会经历tty线路规程的转换,但是tty驱动与tty核心之间也可以直接传输数据。



三、tty设备驱动结构



图显示了与tty相关的主要源文件及数据的流向。tty_io.c定义了tty 设备通用的file_operations结构体并实现了接口函数tty_register_driver()用于注册tty设备,它会利fs/char_dev.c提供的接口函数注册字符设备,与具体设备对应的tty驱动将实现tty_driver结构体中的成员函数。同时 tty_io.c也提供了tty_register_ldisc()接口函数用于注册线路规程,n_tty.c文件则实现了tty_disc结构体中的成员。特定tty设备驱动的主体工作是填充tty_driver结构体中的成员,实现其中的成员函数。

四、串口驱动核心层



上图的数据流向应该和上面tty设备驱动一样,同样需要经过tty线路规则层,不过上图箭头标的不是很准确,说明一下,因为图是从网上下的。

很明显的看得到由tty设备驱动到串口驱动中间经过了一层serial_core ,从tty设备驱动中需要填充的是tty_driver结构,经过串口核心层后就转变成了实现xxx_uart.c 。到现在Linux系统已经封装了终端设备(tty)的驱动,www.linuxidc.com而我们只需要实现串口驱动就能实现整个串口终端驱动。

2019-05-09 10:52:34 mikedadong 阅读数 111
  • 串口通信详解-1.7.ARM裸机第七部分

    本期课程主要讲述S5PV210的串口通信。本课程共分为3个部分:首先用3节课时间系统讲解了通信的基础知识、串行通信的重要概念和知识等;然后用5节课时间详细分析了S5PV210的串口控制器的关键部分及代码编写;后用3节课时间讲述了如何基于S5PV210的基本串口通信函数实现stdio的移植。

    8375 人正在学习 去看看 朱有鹏

sccv串口驱动学习–8250.c

本文主要记录学习CGEL3(基于Linux version 2.6.21.7) 中关于8250串口驱动代码的心得。代码位于Z:\CGEL3\archto\mips\sccv\drivers\serial
该驱动的代码采用最传统的方式来实现对串口的支持,在最新的CGEL6内核中,已经采用设备树的方法实现,将设备和驱动分离。

为了阅读的方便,打乱了常规驱动写法的顺序。按照先后顺序对代码进行了排序。可能错误的地方会非常多,且看且完善吧。如果产生误导,请见谅并请指正。谢谢。

module_init(serial8250_init);

static int __init serial8250_init(void)
{
	int ret;
	#ifndef CONFIG_SERIAL_POLL_API
	int i;

	if (nr_uarts > UART_NR)
		nr_uarts = UART_NR;
	#endif


if(isIpmux2())
{
printk("8250: module not loaded for IPMUX2\n");
return -1;
}

	printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
		"%d ports, IRQ sharing %sabled\n", nr_uarts,
		share_irqs ? "en" : "dis");

	/* GPIO Direction, GPIO1:input */
		*(int *) 0xBF002884 &= 0xFFFFFFFD;
	
	/* GPIO Interrupt Trigger Mode, GPIO1: triggered by level */   
*(int *) 0xBF002898 |= 0x2;    //这几个地址不明白,哪里来的,功能是设置GPIO的中断触发为水平触发(有数据即触发)?

  /* GPIO Interrupt Level, triggered by active high */    //GPIO中断级,高电平触发。
*(int *) 0xBF00289C |= 0x2;   

#ifndef CONFIG_SERIAL_POLL_API
	for (i = 0; i < NR_IRQS; i++)
		spin_lock_init(&irq_lists[i].lock);
#else
	serial8250_polled_init();
#endif

	ret = uart_register_driver(&serial8250_reg);  
//注册进入内核,serial8250_reg是一个uart_driver类型的结构体,初始化uart_driver中的
子结构体tty_driver的信息,如驱动名称、主次设备号、模块号及其对应的console等等。另外重要
的是初始化了tty_driver中的open、close等回调函数(tty_set_operations(normal, &uart_ops)),将这些函数初始化了static const struct tty_operations uart_ops中的函数。

	if (ret)
		goto out;

	serial8250_isa_devs = platform_device_alloc("serial8250",
						PLAT8250_DEV_LEGACY);//创建一个平台设备对象。设备的名字是serial8250。该名字是串口设备和串口驱动匹配的判断条件。
	if (!serial8250_isa_devs) {
		ret = -ENOMEM;
		goto unreg_uart_drv;
	}

	ret = platform_device_add(serial8250_isa_devs);//注册平台设备,最后对加入到虚拟的平台总线platform_bus_type中。
	if (ret)
		goto put_dev;

	serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);//下面详细分析下。

	ret = platform_driver_register(&serial8250_isa_driver);//注册该串口设备对应的驱动程序。
	if (ret == 0)
		goto out;

	platform_device_del(serial8250_isa_devs);
 put_dev:
	platform_device_put(serial8250_isa_devs);
 unreg_uart_drv:
	uart_unregister_driver(&serial8250_reg);
 out:
	return ret;
}
module_exit(serial8250_exit);

初始化的代码主要如上面,除了几个固定的流程之外,有两个函数serial8250_register_ports和platform_driver_register比较复杂,单独分析下。
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

static struct uart_driver serial8250_reg = {
.owner			= THIS_MODULE,
.driver_name		= "serial", //驱动名称 cat proc/tty/driver/serial查看。
.dev_name		= "ttyS",     //设备名称--cat proc/devices可以查看
.major			= TTY_MAJOR,  //主设备号
.minor			= 64,        //次设备号
.nr			= UART_NR,     //支持最大的串口个数
.cons			= SERIAL8250_CONSOLE,
};


static void __init
serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
	int i;

	serial8250_isa_init_ports();

	for (i = 0; i < nr_uarts; i++) {
		struct uart_8250_port *up = &serial8250_ports[i];

		up->port.dev = dev;
		
		if(i == 2)
			EXTERN_PORT_FLAG = 3;
		
		uart_add_one_port(drv, &up->port); //将驱动static struct uart_driver serial8250_reg 和具体的物理端口联系在一起。
	}
}



static void __init serial8250_isa_init_ports(void)
{
	struct uart_8250_port *up;
	static int first = 1;
	int i;

	if (!first)
		return;
	first = 0;
	//nr_uarts:static unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS; 
	该宏在sccv中等于3,详见CGEL3/boards/9806h/SCCV/kernel.config

	//for循环,主要是初始化了静态结构体数组serial8250_ports,该结构体类型为
	uart_8250_port,有几个串口就有几个这样的结构体,该结构体包含了串口的物理
	信息,波特率、中断号等等,另外结构体中元素uart_ops结构体赋值为了
	serial8250_pops,这个serial8250_pops对应的是串口芯片的驱动。

	for (i = 0; i < nr_uarts; i++) {
		struct uart_8250_port *up = &serial8250_ports[i];

		up->port.line = i;
		spin_lock_init(&up->port.lock);

		init_timer(&up->timer);
		up->timer.function = serial8250_timeout;

		/*
		 * ALPHA_KLUDGE_MCR needs to be killed.
		 */
		up->mcr_mask = ~ALPHA_KLUDGE_MCR;
		up->mcr_force = ALPHA_KLUDGE_MCR;

		up->port.ops = &serial8250_pops; // serial8250_pops定义了许多的回调函数,这些函数实现了对串口的硬件IO端口的读写、寄存器的配置等等。
	}


	//第二个for循环,和上面一个类似,同样是对uart_8250_port 类型的结构体serial8250_ports进行了填充,
	填充的内容主要来自于old_serial_port结构体,通过查看几个serial.h,发现来自于./linux/include/asm-mips/serial.h中。
	关于这个结构体的具体信息见下文。而该for循环的主要作用就是用old_serial_port结构体中前3个元素对serial8250_ports结构体进行初始化。
	结合serial8250_pops中多个回调函数的代码发现,这几个回调函数会使用serial8250_ports中的硬件信息。

	for (i = 0, up = serial8250_ports;
	     i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
	     i++, up++) {
		up->port.iobase   = old_serial_port[i].port;
#ifdef CONFIG_MIPS_MALTA
		up->port.irq      = (old_serial_port[i].irq);
#else
		up->port.irq      = irq_canonicalize(old_serial_port[i].irq);
#endif
		up->port.uartclk  = old_serial_port[i].baud_base * 16;
		
		if(bspGetSccbType() && i==2)/*SCCBK的扩展串口波特率为16384000*/
	  {
	  		up->port.uartclk  = (16384000 / 16) * 16;
	  }
		
		up->port.flags    = old_serial_port[i].flags;
		up->port.hub6     = old_serial_port[i].hub6;
		up->port.membase  = old_serial_port[i].iomem_base;
		up->port.iotype   = old_serial_port[i].io_type;
		up->port.regshift = old_serial_port[i].iomem_reg_shift;
		if (share_irqs)
			up->port.flags |= UPF_SHARE_IRQ;
	}
}

//old_serial_port,定义了一些硬件信息,如波特率、中断、iomem_base、io_type等信息。
static const struct old_serial_port old_serial_port[] = {
SERIAL_PORT_DFNS /* defined in asm/serial.h */
};

#define SERIAL_PORT_DFNS				\
	DDB5477_SERIAL_PORT_DEFNS			\
	EV64120_SERIAL_PORT_DEFNS			\
	IP32_SERIAL_PORT_DEFNS                          \
	JAZZ_SERIAL_PORT_DEFNS				\
	STD_SERIAL_PORT_DEFNS				\
	MOMENCO_OCELOT_G_SERIAL_PORT_DEFNS		\
	MOMENCO_OCELOT_C_SERIAL_PORT_DEFNS		\
	MOMENCO_OCELOT_SERIAL_PORT_DEFNS		\
	MOMENCO_OCELOT_3_SERIAL_PORT_DEFNS		\
	BCM947XX_SERIAL_PORT_DEFNS			\
	BCM56218_SERIAL_PORT_DEFNS

#endif




static void __init
serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
	int i;

	serial8250_isa_init_ports();

	for (i = 0; i < nr_uarts; i++) {
		struct uart_8250_port *up = &serial8250_ports[i];

		up->port.dev = dev;
		
		if(i == 2)
			EXTERN_PORT_FLAG = 3;
		
		uart_add_one_port(drv, &up->port);  //该函数主要就是这一步,将三个物理的串口 uart_port和驱动serial8250_reg联系在一起。
	}
}

注册9250对应的驱动

以上分析的,基本上都是总线驱动模型中的设备信息,下面看下驱动信息。回到初始化函数serial8250_init,有如下的一行代码:

ret = platform_driver_register(&serial8250_isa_driver);

该行代码主要是注册8250对应的驱动程序。
platform_driver_register是虚拟平台总线的统一注册函数,而参数如下。该参数主要定义了probe函数以及.driver。.driver中的成员name是平台总线匹配驱动和设备的条件之一。

static struct platform_driver serial8250_isa_driver = {
	.probe		= serial8250_probe,
	.remove		= __devexit_p(serial8250_remove),
	.suspend	= serial8250_suspend,
	.resume		= serial8250_resume,
	.driver		= {
		.name	= "serial8250",
		.owner	= THIS_MODULE,
	},
};

当虚拟平台总线发现平台设备和平台驱动匹配时,那么serial8250_probe函数将会被调用,该函数主要做了什么呢?下面一起来看看。
static int __devinit serial8250_probe(struct platform_device *dev)
{
struct plat_serial8250_port *p = dev->dev.platform_data; //从平台设备中读取出硬件信息
struct uart_port port;
int ret, i;

	memset(&port, 0, sizeof(struct uart_port));

	for (i = 0; p && p->flags != 0; p++, i++) {
		port.iobase	= p->iobase;
		port.membase	= p->membase;            //将平台设备的各个信息读取出来,存放到结构体uart_port中。
		port.irq	= p->irq;
		port.uartclk	= p->uartclk;
		port.regshift	= p->regshift;
		port.iotype	= p->iotype;
		port.flags	= p->flags;
		port.mapbase	= p->mapbase;
		port.hub6	= p->hub6;
		port.dev	= &dev->dev;
		if (share_irqs)
			port.flags |= UPF_SHARE_IRQ;
		ret = serial8250_register_port(&port);	      //注册串口,比较重要,下面详细分析
		if (ret < 0) {
			dev_err(&dev->dev, "unable to register port at index %d "
				"(IO%lx MEM%lx IRQ%d): %d\n", i,
				p->iobase, p->mapbase, p->irq, ret);
		}
	}
	return 0;
}

serial8250_register_port:注册一个串口设备

int serial8250_register_port(struct uart_port *port)
{
	struct uart_8250_port *uart;
	int ret = -ENOSPC;

	if (port->uartclk == 0)
		return -EINVAL;

	mutex_lock(&serial_mutex);

	uart = serial8250_find_match_or_unused(port);   //查看串口信息是否存在。
	if (uart) {
		uart_remove_one_port(&serial8250_reg, &uart->port);  //如果存在,则清除,后面重新添加

		uart->port.iobase   = port->iobase;
		uart->port.membase  = port->membase;
		uart->port.irq      = port->irq;
		uart->port.uartclk  = port->uartclk;
		uart->port.fifosize = port->fifosize;
		uart->port.regshift = port->regshift;
		uart->port.iotype   = port->iotype;
		uart->port.flags    = port->flags | UPF_BOOT_AUTOCONF;
		uart->port.mapbase  = port->mapbase;
		if (port->dev)
			uart->port.dev = port->dev;

		ret = uart_add_one_port(&serial8250_reg, &uart->port); //这个子函数应该是比较重要的,细看下。
		if (ret == 0)
			ret = uart->port.line;
	}
	mutex_unlock(&serial_mutex);

	return ret;
}


int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
{
	struct uart_state *state;
	int ret = 0;

	BUG_ON(in_interrupt());

	if (port->line >= drv->nr)
		return -EINVAL;

	state = drv->state + port->line;

	mutex_lock(&port_mutex);
	mutex_lock(&state->mutex);
	if (state->port) {
		ret = -EINVAL;
		goto out;
	}

	state->port = port;
tty_fops
	port->cons = drv->cons;
#ifndef CONFIG_SERIAL_POLL_API
	 port->info = state->info;
#else
	/*
	 * Don't assign info if it has already been assigned.
	 *
	 * FIXME - is this assignment really necessary?  port->info
	 * doesn't seem to be used until after an open, and that
	 * assigns the value.
	 */
	if (!port->info)
		port->info = state->info;
#endif

	/*
	 * If this port is a console, then the spinlock is already
	 * initialised.
	 */
	if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) {
		spin_lock_init(&port->lock);
		lockdep_set_class(&port->lock, &port_lock_key);
	}

	uart_configure_port(drv, state, port);
	/*配置端口,会调用8250对应的配置函数 serial8250_config_port、设置modem控制serial8250_set_mctrl等等*/

	/*
	 * Register the port whether it's detected or not.  This allows
	 * setserial to be used to alter this ports parameters.
	 */
	tty_register_device(drv->tty_driver, port->line, port->dev);

	/*
	 * If this driver supports console, and it hasn't been
	 * successfully registered yet, try to re-register it.
	 * It may be that the port was not available.
	 */
	if (port->type != PORT_UNKNOWN &&
	    port->cons && !(port->cons->flags & CON_ENABLED))
		register_console(port->cons);

	/*
	 * Ensure UPF_DEAD is not set.
	 */
	port->flags &= ~UPF_DEAD;

#if defined(CONFIG_KGDB_8250)
	/* Add any 8250-like ports we find later. */
	if (port->type <= PORT_MAX_8250)
		kgdb8250_add_port(port->line, port);
#endif

 out:
	mutex_unlock(&state->mutex);
	mutex_unlock(&port_mutex);

	return ret;
}

##串口驱动关键的结构体解读

struct uart_driver
 {

    struct module  *owner; //拥有该uart_driver的模块,一般为THIS_MODULE 
    constchar *driver_name; // 串口驱动名,串口设备文件名以驱动名为基础 
    constchar *dev_name; // 串口设备名 ,cat /proc/devices查看
    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驱动不用关心ttr_driver。

};

uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。有几个串口就需要几个这样的结构体,在本例子中,有3个串口,对应3个这样的结构体。

struct uart_port
{
	spinlock_t		lock;			/* port lock */
	unsigned int		iobase;			/* in/out[bwl] 端口基地址*/
	unsigned char __iomem	*membase;		/* read/write[bwl] IO内存基地址(经过ioremap后的虚拟地址)*/
	unsigned int		irq;			/* irq number 中断号*/
	unsigned int		uartclk;		/* base uart clock 串口时钟 */
	unsigned int		fifosize;		/* tx fifo size  串口FIFO缓冲大小*/
	unsigned char		x_char;			/* xon/xoff char */
	unsigned char		regshift;		/* reg offset shift 寄存器移位,外设访问方式分为8 16 32 位(分别对应regshift的值为0 1 2 ),因此需要移位,如果不懂,可以韦东山的嵌入式开发手册。*/
	unsigned char		iotype;			/* io access style :io访问方式*/
	unsigned char		unused1;

#define UPIO_PORT		(0)
#define UPIO_HUB6		(1)
#define UPIO_MEM		(2)
#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 */

	unsigned int		read_status_mask;	/* driver specific */
	unsigned int		ignore_status_mask;	/* driver specific */
	struct uart_info	*info;			/* pointer to parent info */
	struct uart_icount	icount;			/* statistics *//* 计数器 uart_icount为串口信息计数器,包含了发送字符计数、接收字符计数等。在串口的发送中断处理函数和接收中断处理函数中,我们需要管理这些计数。*/ 

	struct console		*cons;			/* struct console, if any */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
	unsigned long		sysrq;			/* sysrq timeout */
#endif

	upf_t			flags;

/*
 * The port lock protects UPF_INUSE_DIRECT and UPF_INUSE_NORMAL.  One
 * of those two bits must be set before any other bits can be changed
 * in port->flags.
 */
#define UPF_FOURPORT		((__force upf_t) (1 << 1))
#define UPF_SAK			((__force upf_t) (1 << 2))
#define UPF_SPD_MASK		((__force upf_t) (0x1030))
#define UPF_SPD_HI		((__force upf_t) (0x0010))
#define UPF_SPD_VHI		((__force upf_t) (0x0020))
#define UPF_SPD_CUST		((__force upf_t) (0x0030))
#define UPF_SPD_SHI		((__force upf_t) (0x1000))
#define UPF_SPD_WARP		((__force upf_t) (0x1010))
#define UPF_SKIP_TEST		((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ		((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD		((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY		((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART		((__force upf_t) (1 << 14))
#define UPF_MAGIC_MULTIPLIER	((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW		((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ		((__force upf_t) (1 << 24))
#ifdef CONFIG_SERIAL_POLL_API
#define UPF_INUSE_NORMAL	((__force uif_t) (1 << 27))
#endif
#define UPF_BOOT_AUTOCONF	((__force upf_t) (1 << 28))
#ifdef CONFIG_SERIAL_POLL_API
#define UPF_INUSE_DIRECT	((__force uif_t) (1 << 29))
#endif
#define UPF_DEAD		((__force upf_t) (1 << 30))
#define UPF_IOREMAP		((__force upf_t) (1 << 31))

#define UPF_CHANGE_MASK		((__force upf_t) (0x17fff))
#define UPF_USR_MASK		((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

	unsigned int		mctrl;			/* current modem ctrl settings */
	unsigned int		timeout;		/* character-based timeout */
	unsigned int		type;			/* port type 端口类型 */
	const struct uart_ops	*ops;       /*串口端口回调函数集合,比较重要,后面单独看下*/
	unsigned int		custom_divisor;
	unsigned int		line;			/* port index */
	unsigned long		mapbase;		/* for ioremap IO内存物理基地址,用于ioremap*/
	struct device		*dev;			/* parent device 指向父设备 */
	unsigned char		hub6;			/* this should be in the 8250 driver */
	unsigned char		unused[3];
	#ifdef CONFIG_ARCH_M823XX
	void			*private_data;		/* generic platform data pointer 一般指向platform数据指针 */
	#endif
}

uart_ops这个结构体是一个回调函数集,这里面的函数和串口硬件进行交互,为上层应用提供读写接口。可以理解为裸机程序中的配置、读写串口控制器的各个寄存器。内核对这些方法的格式进行了统一封装,一般该结构体的函数指针芯片厂商会写好。

struct uart_ops {
	unsigned int	(*tx_empty)(struct uart_port *); //判断串口的tx Fifo缓存是否为空
	void		(*set_mctrl)(struct uart_port *, unsigned int mctrl);
	unsigned int	(*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 *);
	void		(*break_ctl)(struct uart_port *, int ctl);
	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 *, unsigned int state,
			      unsigned int oldstate);  //电源管理
	int		(*set_wake)(struct uart_port *, unsigned int state);


#ifdef CONFIG_SERIAL_POLL_API
	/*
	 * Note: Poll routines must be called with the port lock held and
	 * interrupts off.
	 */

	/*
	 * The startup and shutdown routines must be called before
	 * poll is used and after done calling poll.  You cannot allow
	 * the driver code to be run by interrupts (or anything else)
	 * between this.  A state value is returned by the startup
	 * routine in pstate, you must pass that to the shutdown
	 * routine.
	 */
	int		(*poll_startup)(struct uart_port *,
					unsigned long *pstate);
	void		(*poll_shutdown)(struct uart_port *,
					 unsigned long pstate);

	/*
	 * Check the serial chip for I/O.  Flags is used to specify
	 * what to check, see UART_POLL_FLAGS_xxx above.
	 */
	void		(*poll)(struct uart_port *, unsigned int flags);

	/*
	 * Is the port in flow-control (CTS is not asserted).  It is
	 * optional an may be NULL and is only called if UPF_CONS_FLOW
	 * is set in port->flags.
	 */
	int             (*in_flow_control)(struct uart_port *);

	/*
	 * Return reasonable settings for the port; it is primarily
	 * there so firmware can pass console settings for the
	 * console.
	 */
	int		(*port_defaults)(struct uart_port *,
					 int *baud, int *parity, int *bits,
					 int *flow);
#endif	/* CONFIG_SERIAL_POLL_API */

	/*
	 * 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_SERIAL_POLL
	void	(*poll_put_char)(struct uart_port *, unsigned char);
	int		(*poll_get_char)(struct uart_port *);
#endif
}

###用户态对tty设备进行读写的分层次函数处理

对于普通的字符设备驱动,我们用户态open、read、write时会直接调用到驱动对应的open、read、write函数。
如韦东山的传统的字符设备驱动写法,用户态open时,直接调用了led_open.
static struct file_operations myled_oprs = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};

但是对于串口比较特别,由于终端种类较多,如键盘、显示器、网络实现的伪终端等,他们的驱动程序不仅仅是简单的初始化硬件、收发数据。在基本的硬件操作基础上,增加了许多的软件功能,是一个多层次的驱动程序。
一般都将串口驱动分层处理,详细可查阅网上资料或者韦东山的书籍也有简单的介绍。

那么,结合代码,简要的总结下打开一个tty设备时的处理步骤。

用户态open、read,收到走查如下的函数操作集。该函数集注册的地方是在 tty_register_driver 函数中调用了tty_cdev_add函数(driver->cdevs[index]->ops = &tty_fops;)这个回调函数集合是统一的,对于所有的tty设备,都是可以用的。

// linux/drivers/char/tty_io.c
static const struct file_operations tty_fops = {
	.llseek		= no_llseek,
	.read		= tty_read,
	.write		= tty_write,
	.poll		= tty_poll,
	.ioctl		= tty_ioctl,
	.open		= tty_open,
	.release	= tty_release,
	.fasync		= tty_fasync,
};

以上述回退函数集合中的tty_read为例,tty_ldisc_ref_wait(tty)找到该tty设备对应的线路规程操作函数集,并调用对应的read函数。

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
		loff_t *ppos)
{
	int i;
	struct inode *inode = file_inode(file);
	struct tty_struct *tty = file_tty(file);
	struct tty_ldisc *ld;

	if (tty_paranoia_check(tty, inode, "tty_read"))
		return -EIO;
	if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
		return -EIO;

	/* We want to wait for the line discipline to sort out in this
	   situation */
	ld = tty_ldisc_ref_wait(tty);
	if (ld->ops->read)
		i = ld->ops->read(tty, file, buf, count);
	else
		i = -EIO;
	tty_ldisc_deref(ld);

	if (i > 0)
		tty_update_time(&inode->i_atime);

	return i;
}

// linux/drivers/serial/serial_core.c --串口核心层。这一部分其实无需关心,uart_register_driver函数内部实现会对这个回调函数集合进行注册。

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,
	.stop		= uart_stop,
	.start		= uart_start,
	.hangup		= uart_hangup,
	.break_ctl	= uart_break_ctl,
	.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
	.read_proc	= uart_read_proc,
#endif
	.tiocmget	= uart_tiocmget,
	.tiocmset	= uart_tiocmset,
#ifdef CONFIG_SERIAL_POLL
	.poll_init	= uart_poll_init,
	.poll_get_char	= uart_poll_get_char,
	.poll_put_char	= uart_poll_put_char,
#endif
};

那么线路规程的操作函数集来自于哪里呢? ./drivers/tty/n_tty.c。以下的这个线路规程函数集注册的地方在。

struct tty_ldisc_ops tty_ldisc_N_TTY = {
.magic           = TTY_LDISC_MAGIC,
.name            = "n_tty",
.open            = n_tty_open,
.close           = n_tty_close,
.flush_buffer    = n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
.read            = n_tty_read,
.write           = n_tty_write,
.ioctl           = n_tty_ioctl,
.set_termios     = n_tty_set_termios,
.poll            = n_tty_poll,
.receive_buf     = n_tty_receive_buf,
.write_wakeup    = n_tty_write_wakeup,
.fasync		 = n_tty_fasync,
.receive_buf2	 = n_tty_receive_buf2,
};


void tty_ldisc_begin(void)
{
	/* Setup the default TTY line discipline. */
	(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
}

再往下走,走到了如下的函数。该函数是串口驱动,由我们移植,一般来讲,这部分芯片厂商会写好的,稍作修改即可。

// archto/mips/sccv/drivers/serial/8250.c
static struct uart_ops serial8250_pops = {
.tx_empty	= serial8250_tx_empty,
.set_mctrl	= serial8250_set_mctrl,
.get_mctrl	= serial8250_get_mctrl,
.stop_tx	= serial8250_stop_tx,
.start_tx	= serial8250_start_tx,
.stop_rx	= serial8250_stop_rx,
.enable_ms	= serial8250_enable_ms,
.break_ctl	= serial8250_break_ctl,
.startup	= serial8250_startup,
.shutdown	= serial8250_shutdown,
.set_termios	= serial8250_set_termios,
.pm		= serial8250_pm,
#ifdef CONFIG_SERIAL_POLL_API
.poll		= serial8250_poll,
.poll_startup	= serial8250_poll_startup,
.poll_shutdown	= serial8250_poll_shutdown,
.in_flow_control= serial8250_in_flow_control,
#endif
.type		= serial8250_type,
.release_port	= serial8250_release_port,
.request_port	= serial8250_request_port,
.config_port	= serial8250_config_port,
.verify_port	= serial8250_verify_port,
#ifdef CONFIG_SERIAL_POLL
.poll_get_char = serial8250_get_poll_char,
.poll_put_char = serial8250_put_poll_char,
#endif
};

###对上面的内容依然一知半解的,但是看得好烦啊,云里来雾里去的。所以看下CGEL6内核的设备树方式实现串口驱动。

查看设备树文件知道串口的硬件信息如下:

	uart0: serial@01703000 {
		/* device_type = "serial";*/
		compatible = "zte,pl011-uart0";    //该字符串用于和驱动匹配
		reg = <0x0 0x01703000  0x0 0x1000>;
		interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART0_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};

	uart1: serial@01704000 {
		compatible = "zte,pl011-uart1";
		reg = <0x0 0x01704000  0x0 0x1000>;
		interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART1_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};
	uart2: serial@01705000 {
		compatible = "zte,pl011-uart2";
		reg = <0x0 0x01705000  0x0 0x1000>;
		interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART2_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};
	uart3: serial@01706000 {
		compatible = "zte,pl011-uart3";
		reg = <0x0 0x01706000  0x0 0x1000>;
		interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART3_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};

通过搜索字段zte,pl011-uart0找到代码中对应的驱动位于文件:linux/drivers/tty/serial/amba-pl011.c。不管三七二一了,先看看这个文件吧。

static struct uart_driver amba_reg = {
	.owner			= THIS_MODULE,
	.driver_name		= "ttyAMA",
	.dev_name		= "ttyAMA",
	.major			= SERIAL_AMBA_MAJOR,
	.minor			= SERIAL_AMBA_MINOR,
	.nr			= UART_NR,     //14,支持14个串口,该参数会用于分配tty_driver
	.cons			= AMBA_CONSOLE,
};


arch_initcall(pl011_init);  //注意这里不一样,一般的module_init的,至于为什么不一样呢?
arch_initcall定义的函数指针会比module_init的先初始化。如果进一步了解可以阅读文档:

https://www.cnblogs.com/chaozhu/p/6410271.html

static int __init pl011_init(void)
{
	int ret = -1;
	printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");

	ret = uart_register_driver(&amba_reg);
	if (ret < 0) {
		printk("Failed to register AMBA-PL011 driver\n");
		return ret;
	}

	//if (platform_driver_register(&arm_sbsa_uart_platform_driver))
	//	pr_warn("could not register SBSA UART platform driver\n");
	ret = platform_driver_register(&zx_uart_driver);
	if (ret)
		pr_warn("could not register zx UART platform driver\n");
	
	//return amba_driver_register(&pl011_driver);
	return ret;
}

从以上的初始化代码可以看得出来,该驱动的代码量要远比8250的简单不少。但是关键性的步骤还是相同的。下面分析下上面的初始化函数。

uart_register_driver(&amba_reg);

该函数是一个通用的函数,上面已经简单的分析过,回过头来再看一遍,发现这个函数主要是分配了tty_driver,并将该驱动与tty_operations uart_ops绑定。

之后另一个比较重要的函数就是

static const struct of_device_id zx_uart_dt_ids[] = {
	{ .compatible = "zte,pl011-uart0", },
	{ .compatible = "zte,pl011-uart1", },
	{ .compatible = "zte,pl011-uart2", },
	{ .compatible = "zte,pl011-uart3", },
	{ .compatible = "zte,pl011-uart4", },
	{ .compatible = "zte,pl011-uart5", },
	{ .compatible = "zte,pl011-uart6", },
	{ .compatible = "zte,pl011-uart7", },
	{ .compatible = "zte,pl011-uart", },
	{ /* sentinel */ }

static struct platform_driver zx_uart_driver = {
.driver = {
	.name	= "zx-uart",
	.owner	= THIS_MODULE,
	.pm	= &pl011_dev_pm_ops,
	.of_match_table = zx_uart_dt_ids,
},
.probe		= zx_uart_probe,
.remove		= zx_uart_remove,
};

platform_driver_register(&zx_uart_driver);  

该函数其实就是想平台驱动注册了一个驱动,当zx_uart_driver.driver->of_match_table->compatible(表zx_uart_dt_ids)与设备树中compatible匹配时,那么平台驱动 zx_uart_driver中的probe函数zx_uart_probe会被调用。

接下来,那就简单的看看zx_uart_probe干了什么吧。

static int zx_uart_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct uart_amba_port *uap;
	struct vendor_data *vendor = &vendor_zte;
	struct resource *res;
	void __iomem *base;
	int i, ret;

	/*申请内存,当设备或者驱动不使用了,内存会自动释放,无须手动free。*/
	uap = devm_kzalloc(&pdev->dev, sizeof(struct uart_amba_port),
			GFP_KERNEL);
	if (uap == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	i = of_alias_get_id(np, "serial");
	/*解析设备树,提取出alalis节点*/
	


	if (i < 0) {
		dev_err(&pdev->dev, "failed to get alias id: %d\n", i);
		ret = i;
		goto out;
	}

	/*从设备树中解析出IO资源,即是节点中的reg属性。一般是芯片控制器控制、数据寄存器的物理地址。
	uart0: serial@01703000 {
		/* device_type = "serial";*/
		compatible = "zte,pl011-uart0";
		reg = <0x0 0x01703000  0x0 0x1000>;           //对应于platform_get_resource的返回值res。
		interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART0_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};*/

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);//物理地址映射进虚拟地址空间。
	if (!base) {
		ret = -ENOMEM;
		goto out;
	}

#if 1
	uap->clk = devm_clk_get(&pdev->dev, NULL);

	printk(KERN_ALERT"zx_uart_probe serial id %d uap->clk is %p base is %p\n",i,uap->clk,base);
	
	if (IS_ERR(uap->clk)) {
		ret = PTR_ERR(uap->clk);
		goto out;
	}
#endif
	//uap->clk = NULL;  // add by whl 2016.11.29

	uap->vendor = vendor;
	uap->reg_lut = vendor->reg_lut;
	uap->fr_busy = vendor->fr_busy;
	uap->fr_dsr = vendor->fr_dsr;
	uap->fr_cts = vendor->fr_cts;
	uap->fr_ri = vendor->fr_ri;
	uap->lcrh_rx = vendor->lcrh_rx;
	uap->lcrh_tx = vendor->lcrh_tx;
	uap->old_cr = 0;
	uap->fifosize = 16;
	uap->port.dev = &pdev->dev;
	uap->port.mapbase = res->start;
	uap->port.membase = base;
	uap->port.iotype = UPIO_MEM;
	uap->port.irq = platform_get_irq(pdev, 0);
	uap->port.fifosize = uap->fifosize;
	uap->port.ops = &amba_pl011_pops;    //注意这一行比较重要,制定了该串口驱动对应的操作硬件寄存器函数。
	uap->port.flags = UPF_BOOT_AUTOCONF;
	uap->port.line = i;
	//INIT_DELAYED_WORK(&uap->tx_softirq_work, pl011_tx_softirq);

	/* Ensure interrupts from this UART are masked and cleared */
	pl011_writew(uap, 0, REG_IMSC);    //向中断控制寄存器写入0
	pl011_writew(uap, 0xffff, REG_ICR); //中断清除寄存器各位置1
	
	//pl011_writew(uap, 0x70, REG_LCRH);  //Word Length 8bit and Enable FIFOs
	
	amba_ports[i] = uap;

	platform_set_drvdata(pdev, uap);
	//printk(KERN_ALERT"zx_uart_probe serial id %d amba_reg-state is %p \n",i,amba_reg.state);

#ifdef CONFIG_EARLY_PRINTK
	disable_early_printk();
#endif
	ret = uart_add_one_port(&amba_reg, &uap->port);  //驱动和端口绑定。
	if (ret) {
		amba_ports[i] = NULL;
		pl011_dma_remove(uap);
	}
out:
	return ret;
}

该串口驱动对应的处理函数。

static struct uart_ops amba_pl011_pops = {
.tx_empty	= pl011_tx_empty,
.set_mctrl	= pl011_set_mctrl,
.get_mctrl	= pl011_get_mctrl,
.stop_tx	= pl011_stop_tx,
.start_tx	= pl011_start_tx,
.stop_rx	= pl011_stop_rx,
.enable_ms	= pl011_enable_ms,
.break_ctl	= pl011_break_ctl,
.startup	= pl011_startup,
.shutdown	= pl011_shutdown,
.flush_buffer	= pl011_dma_flush_buffer,
.set_termios	= pl011_set_termios,
.type		= pl011_type,
.release_port	= pl011_release_port,
.request_port	= pl011_request_port,
.config_port	= pl011_config_port,
.verify_port	= pl011_verify_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_init     = pl011_hwinit,
.poll_get_char = pl011_get_poll_char,
.poll_put_char = pl011_put_poll_char,
#endi

上面回调函数中的 pl011_start_tx负责向串口发送数据的。

static void pl011_start_tx(struct uart_port *port)
{
	struct uart_amba_port *uap =
	    container_of(port, struct uart_amba_port, port);

	if (!pl011_dma_tx_start(uap))
		pl011_start_tx_pio(uap);
}

static void pl011_start_tx_pio(struct uart_amba_port *uap)
{
	uap->im |= UART011_TXIM;
	//writew(uap->im, uap->port.membase + UART011_IMSC);
	pl011_writew(uap, uap->im, REG_IMSC);
	pl011_tx_chars(uap, false);//发送字符集
}

static void pl011_tx_chars(struct uart_amba_port *uap, bool from_irq)

{
struct circ_buf *xmit = &uap->port.state->xmit;
int count = uap->fifosize >> 1;

if (uap->port.x_char) {
	if (!pl011_tx_char(uap, uap->port.x_char, from_irq))
		return;
	uap->port.x_char = 0;
	--count;
}
if (uart_circ_empty(xmit) || uart_tx_stopped(&uap->port)) {
	pl011_stop_tx(&uap->port);
	return;
}

/* If we are using DMA mode, try to send some characters. */
if (pl011_dma_tx_irq(uap))
	return;

do {
	if (likely(from_irq) && count-- == 0)
		break;

	if (!pl011_tx_char(uap, xmit->buf[xmit->tail], from_irq)) //循环调用该函数,每次想数据寄存器中写入一个字符。
		break;

	xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
} while (!uart_circ_empty(xmit));

if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
	uart_write_wakeup(&uap->port);

if (uart_circ_empty(xmit))
	pl011_stop_tx(&uap->port);

}

至于,如何从串口中读取数据,可以看下代码:
pl011_startup
	pl011_allocate_irq
		request_irq(uap->port.irq, pl011_int, 0, "uart-pl011", uap)
		pl011_int  --这个函数在收到中断后,会被调用的。应该是处理从串口读取数据。

一个非常修改的框架图:

2013-07-12 19:06:23 SHK242673 阅读数 981
  • 串口通信详解-1.7.ARM裸机第七部分

    本期课程主要讲述S5PV210的串口通信。本课程共分为3个部分:首先用3节课时间系统讲解了通信的基础知识、串行通信的重要概念和知识等;然后用5节课时间详细分析了S5PV210的串口控制器的关键部分及代码编写;后用3节课时间讲述了如何基于S5PV210的基本串口通信函数实现stdio的移植。

    8375 人正在学习 去看看 朱有鹏
linux串口驱动分析



  • 硬件资源及描述
        s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)端口,每个端口都可以在中断模式或 DMA 模式下操作。UART 使用系统时钟可以支持最高 115.2Kbps 的波特率。每个 UART 通道对于接收器和发送器包括了 2 个 64 位的 FIFO。

寄存器 名称 地址 在linux中的描述 (2410 和 2440 处理器对内存地址映射关系相同)
UART 线性控制寄存器(ULCONn) ULCON0  
ULCON1  
ULCON2 
0x50000000
0x50004000
0x50008000
linux-2.6.31/arch/arm/plat-s3c/include/plat/regs-serial.h

#define S3C2410_PA_UART0      

    (S3C24XX_PA_UART)

#define S3C2410_PA_UART1     

    (S3C24XX_PA_UART + 0x4000 )

#define S3C2410_PA_UART2     

    (S3C24XX_PA_UART + 0x8000 )

#define S3C2443_PA_UART3     

    (S3C24XX_PA_UART + 0xC000 )

linux-2.6.31/arch/arm/plat-s3c/include/plat/map.h

#define S3C24XX_VA_UART      

    S3C_VA_UART//静态映射后的虚拟地址

#define S3C2410_PA_UART      

    (0x50000000)//物理地址

#define S3C24XX_SZ_UART       SZ_1M
#define S3C_UART_OFFSET       (0x4000)
UART 控制寄存器(UCONn) UCON0  
UCON1
UCON2
0x50000004
0x50004004
0x50008004
#define S3C2410_UCON      (0x04)
UART FIFO 控制寄存器(UFCONn) UFCON0
UFCON1
UFCON2
0x50000008
0x50004008
0x50008008
#define S3C2410_UFCON      (0x08)
UART MODEM 控制寄存器(UMCONn) UMCON0
UMCON1
0x5000000C
0x5000400C
#define S3C2410_UMCON      (0x0C)
UART 接收发送状态寄存器(UTRSTATn) UTRSTAT0
UTRSTAT1
UTRSTAT2
0x50000010
0x50004010
0x50008010
#define S3C2410_UTRSTAT      (0x10)
UART 错误状态寄存器(UERSTATn) UERSTAT0
UERSTAT1
UERSTAT2
0x50000014
0x50004014
0x50008014
#define S3C2410_UERSTAT      (0x14)
UART FIFO 状态寄存器(UFSTATn) UFSTAT0
UFSTAT1
UFSTAT2
0x50000018
0x50004018
0x50008018
#define S3C2410_UFSTAT      (0x18)
UART MODEM 状态寄存器(UMSTATn) UFSTAT0
UFSTAT1
0x5000001C
0x5000401C
#define S3C2410_UMSTAT      (0x1C)
UART 发送缓存寄存器(UTXHn) UTXH0

UTXH1

UTXH2
0x50000020(L)
0x50000023(B)
0x50004020(L)
0x50004023(B)
0x50008020(L)
0x50008023(B)
#define S3C2410_UTXH      (0x20)
UART 接收缓存寄存器(URXHn) URXH0

URXH1

URXH2
0x50000024(L)
0x50000027(B)
0x50004024(L)
0x50004027(B)
0x50008024(L)
0x50008027(B)
#define S3C2410_URXH      (0x24)
UART 波特率除数寄存器(UBRDIVn) UBRDIV0
UBRDIV1
UBRDIV2
0x50000028
0x50004028
0x50008028
#define S3C2410_UBRDIV      (0x28)

ULCON 描述 在linux中的描述 linux-2.6.31/arch/arm/plat-s3c/include/plat/regs-serial.h
Reserved [7]    
Infrared Mode [6] 决定是否使用红外模式
0 =正常模式操作
1 = 红外接收发送模式
#define S3C2410_LCON_IRM          (1<<6)
Parity Mode [5:3] 在UART发送接收操作中定义奇偶码的生成和检
验类型
0xx = No parity
100 = Odd parity
101 = Even parity
110 = Parity forced/checked as 1
111 = Parity forced/checked as 0
#define S3C2410_LCON_PNONE      (0x0)
#define S3C2410_LCON_PEVEN      (0x5 << 3)
#define S3C2410_LCON_PODD      (0x4 << 3)
#define S3C2410_LCON_PMASK      (0x7 << 3)
Number of Stop Bit [2] 定义度搜按个停止位用于帧末信号
0 =每帧一个停止位
1 =每帧两个停止位
#define S3C2410_LCON_STOPB      (1<<2)
Word Length [1:0] 指出发送接收每帧的数据位数
00 = 5-bits 01 = 6-bits
10 = 7-bits 11 = 8-bits
#define S3C2410_LCON_CS5      (0x0)
#define S3C2410_LCON_CS6      (0x1)
#define S3C2410_LCON_CS7      (0x2)
#define S3C2410_LCON_CS8      (0x3)
#define S3C2410_LCON_CSMASK      (0x3)
 
UFCON 描述 在linux中的描述

Tx FIFO Trigger Level
[7:6] 决定发送FIFO的触发等级
00 = Empty 01 = 16-byte
10 = 32-byte 11 = 48-byte
#define S3C2440_UFCON_TXTRIG0      (0<<6)
#define S3C2440_UFCON_TXTRIG16      (1<<6)
#define S3C2440_UFCON_TXTRIG32      (2<<6)
#define S3C2440_UFCON_TXTRIG48      (3<<6)

Rx FIFO Trigger Level
[5:4] 决定接收FIFO的触发等级
00 = 1-byte 01 = 8-byte
10 = 16-byte 11 = 32-byte
#define S3C2440_UFCON_RXTRIG1      (0<<4)
#define S3C2440_UFCON_RXTRIG8      (1<<4)
#define S3C2440_UFCON_RXTRIG16      (2<<4)
#define S3C2440_UFCON_RXTRIG32      (3<<4)
Reserved [3]    
Tx FIFO Reset [2] 在重置FIFO后自动清除
0 = Normal 
1= Tx FIFO reset
#define S3C2410_UFCON_RESETTX      (1<<2)
Rx FIFO Reset [1] 在重置FIFO后自动清除
0 = Normal 
1= Rx FIFO reset
#define S3C2410_UFCON_RESETRX      (1<<1)
#define S3C2410_UFCON_RESETBOTH      (3<<1)
FIFO Enable [0] 0 = Disable 
1 = Enable
 
默认设置为  
 #define S3C2410_UFCON_DEFAULT      (S3C2410_UFCON_FIFOMODE | \
                   S3C2410_UFCON_TXTRIG0  | \
                   S3C2410_UFCON_RXTRIG8 )

使能FIFO
Tx FIFO为空时触发中断
Rx FIFO中包含8个字节时触发中断
 
UFSTAT 描述 在linux中的表示
Reserved [15]    
Tx FIFO Full [14] 只要在发送操作中发送FIFO满,则自动置 1。
0 = 0-byte ≤ Tx FIFO data ≤ 63-byte
1 = Full
#define S3C2440_UFSTAT_TXFULL      (1<<14)
Tx FIFO Count [13:8] 发送FIFO中的数据数量 #define S3C2440_UFSTAT_TXSHIFT      (8)
Reserved [7]    
Rx FIFO Full [6] 只要在接收操作中接收FIFO满,则自动置 1。
0 = 0-byte ≤ Rx FIFO data ≤ 63-byte
1 = Full
#define S3C2440_UFSTAT_RXFULL      (1<<6)
Rx FIFO Count [5:0] 接收FIFO中的数据数量 #define S3C2440_UFSTAT_RXSHIFT      (0)
       



  • uart驱动程序概述
        linux的uart驱动程序建立在tty驱动程序之上(串口也是一个终端),程序源代码主要在drivers/serial/目录下。其中 serial_core.c实现了uart设备的通用tty驱动层,其中定义了一组通用接口,包括3个结构体:uart_driver, uart_port,uart_ops(include/serial_core.h)。因此,实现一个平台的uart驱动程序只要实现这3个结构体即可。


serial_core(定义): samsumg.c (实现):                                                                   
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;
};
static struct uart_driver s3c24xx_uart_drv = {
    .owner        = THIS_MODULE,
    .dev_name    = "s3c2410_serial",//设备名称,这个名称必须和
        //根文件系统中/etc/inittab文件中的串口名称一致

    .nr        = CONFIG_SERIAL_SAMSUNG_UARTS,
    .cons        = S3C24XX_SERIAL_CONSOLE,
    .driver_name    = S3C24XX_SERIAL_NAME,//驱动名称
    .major        = S3C24XX_SERIAL_MAJOR,
    .minor        = S3C24XX_SERIAL_MINOR,
};
        
        uart_driver封装了tty_driver,使底层uart驱动不用关心ttr_driver。一个tty驱动程序必须注册/注销 tty_driver,而uart驱动则变为注册/注销uart_driver。使用如下接口:

int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_driver *drv);
实际上,uart_register_driver()和uart_unregister_driver()中分别包含了tty_register_driver()和tty_unregister_driver()的操作。

serial_core(定义): samsumg.h  samsumg.c(实现):
struct uart_port {
    spinlock_t        lock;            /* port lock */
    unsigned long        iobase;            /* in/out[bwl] */
    unsigned char __iomem    *membase;        /* read/write[bwl] */
    unsigned int        (*serial_in)(struct uart_port *, int);
    void            (*serial_out)(struct uart_port *, int, int);
    unsigned int        irq;            /* irq number */
    unsigned int        uartclk;        /* base uart clock */
    unsigned int        fifosize;        /* tx fifo size */
    unsigned char        x_char;            /* xon/xoff char */
    unsigned char        regshift;        /* reg offset shift */
    unsigned char        iotype;            /* io access style */
    unsigned char        unused1;

#define UPIO_PORT        (0)
#define UPIO_HUB6        (1)
#define UPIO_MEM        (2)
#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 */

    unsigned int        read_status_mask;    /* driver specific */
    unsigned int        ignore_status_mask;    /* driver specific */
    struct uart_info    *info;            /* pointer to parent info */
    struct uart_icount    icount;            /* statistics */

    struct console        *cons;            /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
    unsigned long        sysrq;            /* sysrq timeout */
#endif

    upf_t            flags;

#define UPF_FOURPORT        ((__force upf_t) (1 << 1))
#define UPF_SAK            ((__force upf_t) (1 << 2))
#define UPF_SPD_MASK        ((__force upf_t) (0x1030))
#define UPF_SPD_HI        ((__force upf_t) (0x0010))
#define UPF_SPD_VHI        ((__force upf_t) (0x0020))
#define UPF_SPD_CUST        ((__force upf_t) (0x0030))
#define UPF_SPD_SHI        ((__force upf_t) (0x1000))
#define UPF_SPD_WARP        ((__force upf_t) (0x1010))
#define UPF_SKIP_TEST        ((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ        ((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD        ((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY        ((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART        ((__force upf_t) (1 << 14))
#define UPF_NO_TXEN_TEST    ((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER    ((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW        ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ        ((__force upf_t) (1 << 24))
/* 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        ((__force upf_t) (0x17fff))
#define UPF_USR_MASK        ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

    unsigned int        mctrl;            /* current modem ctrl settings */
    unsigned int        timeout;        /* character-based timeout */
    unsigned int        type;            /* port type */
    const struct uart_ops    *ops;
    unsigned int        custom_divisor;
    unsigned int        line;            /* port index */
    resource_size_t        mapbase;        /* for ioremap */
    struct device        *dev;            /* parent device */
    unsigned char        hub6;            /* this should be in the 8250 driver */
    unsigned char        suspended;
    unsigned char        unused[2];
    void            *private_data;        /* generic platform data pointer */
};
struct s3c24xx_uart_port {//封装了uart_port
    unsigned char            rx_claimed;
    unsigned char            tx_claimed;
    unsigned int            pm_level;
    unsigned long            baudclk_rate;

    unsigned int            rx_irq;
    unsigned int            tx_irq;

    struct s3c24xx_uart_info    *info;
    struct s3c24xx_uart_clksrc    *clksrc;
    struct clk            *clk;
    struct clk            *baudclk;
    struct uart_port        port;

#ifdef CONFIG_CPU_FREQ
    struct notifier_block        freq_transition;
#endif
};

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = { //3 uarts default
    [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,
        }
    },
    [1] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
            .iotype        = UPIO_MEM,
            .irq        = IRQ_S3CUART_RX1,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 1,
        }
    },
#if CONFIG_SERIAL_SAMSUNG_UARTS > 2

    [2] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
            .iotype        = UPIO_MEM,
            .irq        = IRQ_S3CUART_RX2,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 2,
        }
    },
#endif
#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
    [3] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
            .iotype        = UPIO_MEM,
            .irq        = IRQ_S3CUART_RX3,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 3,
        }
    }
#endif
};
        
        uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。串口核心层提供如下函数来添加1个端口:

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 uart_driver *drv, struct uart_port *port);

serial_core.h(定义): samsumg.c (实现):                                                                                           
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        (*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 *);
    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
    void    (*poll_put_char)(struct uart_port *, unsigned char);
    int        (*poll_get_char)(struct uart_port *);
#endif
};
static struct uart_ops s3c24xx_serial_ops = {
    .pm        = s3c24xx_serial_pm,
    .tx_empty    = s3c24xx_serial_tx_empty,
    .get_mctrl    = s3c24xx_serial_get_mctrl,
    .set_mctrl    = s3c24xx_serial_set_mctrl,
    .stop_tx    = s3c24xx_serial_stop_tx,
    .start_tx    = s3c24xx_serial_start_tx,
    .stop_rx    = s3c24xx_serial_stop_rx,
    .enable_ms    = s3c24xx_serial_enable_ms,
    .break_ctl    = s3c24xx_serial_break_ctl,
    .startup    = s3c24xx_serial_startup,
    .shutdown    = s3c24xx_serial_shutdown,
    .set_termios    = s3c24xx_serial_set_termios,
    .type        = s3c24xx_serial_type,
    .release_port    = s3c24xx_serial_release_port,
    .request_port    = s3c24xx_serial_request_port,
    .config_port    = s3c24xx_serial_config_port,
    .verify_port    = s3c24xx_serial_verify_port,
};
在使用串口核心层这个通用串口tty驱动层的接口后,一个串口驱动要完成的主要工作将包括:
  1. 定义uart_driver、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驱动的主体工作。
  • 串口驱动初始化过程
        在S3C2410 串口驱动的模块加载函数中会调用uart_register_driver()注册s3c24xx_uart_drv这个uart_driver,同时经过
s3c2410_serial_init()→s3c24xx_serial_init()→platform_driver_register()的调用导致s3c24xx_serial_probe()被执行,而s3c24xx_serial_probe()函数中会调用 s3c24xx_serial_init_port()初始化UART端口并调用uart_add_one_port()添加端口。
platform_device_driver 参考:
Linux Platform Device and Driver  Linux driver model ----- platform

回过头来看s3c24xx_uart_info结构体(s3c24xx_uart_port的成员),是一些针对s3c2440 uart 的信息,在/drivers/serial/s3c2440.c中:

static struct s3c24xx_uart_info s3c2440_uart_inf = {
    .name        = "Samsung S3C2440 UART",
    .type        = PORT_S3C2440,
    .fifosize    = 64,
    .rx_fifomask    = S3C2440_UFSTAT_RXMASK,
    .rx_fifoshift    = S3C2440_UFSTAT_RXSHIFT,
    .rx_fifofull    = S3C2440_UFSTAT_RXFULL,
    .tx_fifofull    = S3C2440_UFSTAT_TXFULL,
    .tx_fifomask    = S3C2440_UFSTAT_TXMASK,
    .tx_fifoshift    = S3C2440_UFSTAT_TXSHIFT,
    .get_clksrc    = s3c2440_serial_getsource,
    .set_clksrc    = s3c2440_serial_setsource,
    .reset_port    = s3c2440_serial_resetport,
};


  • 串口操作函数,uart_ops接口函数
S3C2410串口驱动uart_ops结构体的startup ()成员函数s3c24xx_serial_startup()用于启动端口,申请端口的发送、接收中断,使能端口的发送和接收。

static int s3c24xx_serial_startup(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);
    int ret;

    dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",
        port->mapbase, port->membase);

    rx_enabled(port) = 1;//接收使能

    ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
              s3c24xx_serial_portname(port), ourport);//申请接收中断,s3c24xx_serial_rx_chars是中断处理函数

    if (ret != 0) {
        printk(KERN_ERR "cannot get irq %d\n", ourport->rx_irq);
        return ret;
    }

    ourport->rx_claimed = 1;

    dbg("requesting tx irq...\n");

    tx_enabled(port) = 1;//发送使能

    ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
              s3c24xx_serial_portname(port), ourport);//申请发送中断

    if (ret) {
        printk(KERN_ERR "cannot get irq %d\n", ourport->tx_irq);
        goto err;
    }

    ourport->tx_claimed = 1;

    dbg("s3c24xx_serial_startup ok\n");

    /* the port reset code should have done the correct
     * register setup for the port controls */

    return ret;

 err:
    s3c24xx_serial_shutdown(port);
    return ret;
}

s3c24xx_serial_startup()的“反函数”为s3c24xx_serial_shutdown(),其释放中断,禁止发送和接收。

static void s3c24xx_serial_shutdown(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);

    if (ourport->tx_claimed) {
        free_irq(ourport->tx_irq, ourport);
        tx_enabled(port) = 0;
        ourport->tx_claimed = 0;
    }

    if (ourport->rx_claimed) {
        free_irq(ourport->rx_irq, ourport);
        ourport->rx_claimed = 0;
        rx_enabled(port) = 0;
    }
}

tx_empty()成员函数s3c24xx_serial_tx_empty()用于判断发送缓冲区是否为空,当使能FIFO模式的时候,判断UFSTATn寄存器,否则判断UTRSTATn寄存器的相应位。

static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port)
{
    struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
    unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
    unsigned long ufcon = rd_regl(port, S3C2410_UFCON);

    if (ufcon & S3C2410_UFCON_FIFOMODE) {
        if ((ufstat & info->tx_fifomask) != 0 ||
            (ufstat & info->tx_fifofull))
            return 0;

        return 1;
    }

    return s3c24xx_serial_txempty_nofifo(port);
}

static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
{
    return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);
}
在samsung.h中定义了如下操作寄存器的宏:
/* register access controls */

#define portaddr(port, reg) ((port)->membase + (reg))

#define rd_regb(port, reg) (__raw_readb(portaddr(port, reg)))
#define rd_regl(port, reg) (__raw_readl(portaddr(port, reg)))  //read regester long

#define wr_regb(port, reg, val) __raw_writeb(val, portaddr(port, reg))
#define wr_regl(port, reg, val) __raw_writel(val, portaddr(port, reg))

start_tx ()成员函数s3c24xx_serial_start_tx()用于启动发送,而stop_rx()成员函数 s3c24xx_serial_stop_tx()用于停止发送。

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;
    }
}
static void s3c24xx_serial_stop_tx(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);

    if (tx_enabled(port)) {
        disable_irq_nosync(ourport->tx_irq);
        tx_enabled(port) = 0;
        if (port->flags & UPF_CONS_FLOW)
            s3c24xx_serial_rx_enable(port);
    }
}

        S3C2410 串口驱动uart_ops结构体的set_termios()成员函数用于改变端口的参数设置,包括波特率、字长、停止位、奇偶校验等,它会根据传递给它的port、termios参数成员的值设置S3C2410 UART的ULCONn、UCONn、UMCONn等寄存器。

static void s3c24xx_serial_set_termios(struct uart_port *port,
                       struct ktermios *termios,
                       struct ktermios *old)
{
    struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
    struct s3c24xx_uart_port *ourport = to_ourport(port);
    struct s3c24xx_uart_clksrc *clksrc = NULL;
    struct clk *clk = NULL;
    unsigned long flags;
    unsigned int baud, quot;
    unsigned int ulcon;
    unsigned int umcon;
    unsigned int udivslot = 0;

    /*
     * We don't support modem control lines.
     */
    termios->c_cflag &= ~(HUPCL | CMSPAR);
    termios->c_cflag |= CLOCAL;

    /*
     * Ask the core to calculate the divisor for us.请求内核计算分频以便产生对应的波特率
     */

    baud = uart_get_baud_rate(port, termios, old, 0, 115200*8);

    if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
        quot = port->custom_divisor;
    else
        quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud);

    /* check to see if we need  to change clock source 检查以确定是否需要改变时钟源 */ 

    if (ourport->clksrc != clksrc || ourport->baudclk != clk) {
        dbg("selecting clock %p\n", clk);
        s3c24xx_serial_setsource(port, clksrc);

        if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) {
            clk_disable(ourport->baudclk);
            ourport->baudclk  = NULL;
        }

        clk_enable(clk);

        ourport->clksrc = clksrc;
        ourport->baudclk = clk;
        ourport->baudclk_rate = clk ? clk_get_rate(clk) : 0;
    }

    if (ourport->info->has_divslot) {
        unsigned int div = ourport->baudclk_rate / baud;

        udivslot = udivslot_table[div & 15];
        dbg("udivslot = %04x (div %d)\n", udivslot, div & 15);
    }
    /* 设置字长 */
    switch (termios->c_cflag & CSIZE) {
    case CS5:
        dbg("config: 5bits/char\n");
        ulcon = S3C2410_LCON_CS5;
        break;
    case CS6:
        dbg("config: 6bits/char\n");
        ulcon = S3C2410_LCON_CS6;
        break;
    case CS7:
        dbg("config: 7bits/char\n");
        ulcon = S3C2410_LCON_CS7;
        break;
    case CS8:
    default:
        dbg("config: 8bits/char\n");
        ulcon = S3C2410_LCON_CS8;
        break;
    }

    /* preserve original lcon IR settings */
    ulcon |= (cfg->ulcon & S3C2410_LCON_IRM);

    if (termios->c_cflag & CSTOPB)
        ulcon |= S3C2410_LCON_STOPB;

    umcon = (termios->c_cflag & CRTSCTS) ? S3C2410_UMCOM_AFC : 0;

    if (termios->c_cflag & PARENB) {
        if (termios->c_cflag & PARODD)
            ulcon |= S3C2410_LCON_PODD;
        else
            ulcon |= S3C2410_LCON_PEVEN;
    } else {
        ulcon |= S3C2410_LCON_PNONE;
    }

    spin_lock_irqsave(&port->lock, flags);

    dbg("setting ulcon to %08x, brddiv to %d, udivslot %08x\n",
        ulcon, quot, udivslot);

    wr_regl(port, S3C2410_ULCON, ulcon);
    wr_regl(port, S3C2410_UBRDIV, quot);
    wr_regl(port, S3C2410_UMCON, umcon);

    if (ourport->info->has_divslot)
        wr_regl(port, S3C2443_DIVSLOT, udivslot);

    dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n",
        rd_regl(port, S3C2410_ULCON),
        rd_regl(port, S3C2410_UCON),
        rd_regl(port, S3C2410_UFCON));

    /*
     * Update the per-port timeout.
     */
    uart_update_timeout(port, termios->c_cflag, baud);

    /*
     * Which character status flags are we interested in?
     */
    port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
    if (termios->c_iflag & INPCK)
        port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY;

    /*
     * Which character status flags should we ignore?
     */
    port->ignore_status_mask = 0;
    if (termios->c_iflag & IGNPAR)
        port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
    if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
        port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;

    /*
     * Ignore all characters if CREAD is not set.
     */
    if ((termios->c_cflag & CREAD) == 0)
        port->ignore_status_mask |= RXSTAT_DUMMY_READ;

    spin_unlock_irqrestore(&port->lock, flags);
}


  • 接收和发送中断处理函数
        在S3C2410 串口驱动中,与数据收发关系最密切的函数不是上述uart_ops成员函数,而是s3c24xx_serial_startup()为发送和接收中断注册的中断处理函数s3c24xx_serial_rx_chars()和s3c24xx_serial_tx_chars()。
        s3c24xx_serial_rx_chars ()读取URXHn寄存器以获得接收到的字符,并调用uart_insert_char()将该字符添加了tty设备的flip缓冲区中,当接收到64个字符或者不再能接受到字符后,调用tty_flip_buffer_push()函数向上层“推”tty设备的flip缓冲。
       s3c24xx_serial_tx_chars()读取uart_info中环形缓冲区中的字符,写入调用UTXHn寄存器。

#define S3C2410_UERSTAT_PARITY (0x1000)

static irqreturn_ts3c24xx_serial_rx_chars(int irq, void *dev_id)
{
    struct s3c24xx_uart_port *ourport = dev_id;
    struct uart_port *port = &ourport->port;
    struct tty_struct *tty = port->info->port.tty;
    unsigned int ufcon, ch, flag, ufstat, uerstat;
    int max_count = 64;

    while (max_count-- > 0) {
        ufcon = rd_regl(port, S3C2410_UFCON);
        ufstat = rd_regl(port, S3C2410_UFSTAT);

        if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
            break;

        uerstat = rd_regl(port, S3C2410_UERSTAT);
        ch = rd_regb(port, S3C2410_URXH);

        if (port->flags & UPF_CONS_FLOW) {
            int txe = s3c24xx_serial_txempty_nofifo(port);

            if (rx_enabled(port)) {
                if (!txe) {
                    rx_enabled(port) = 0;
                    continue;
                }
            } else {
                if (txe) {
                    ufcon |= S3C2410_UFCON_RESETRX;
                    wr_regl(port, S3C2410_UFCON, ufcon);
                    rx_enabled(port) = 1;
                    goto out;
                }
                continue;
            }
        }

        /* insert the character into the buffer */

        flag = TTY_NORMAL;
        port->icount.rx++;

        if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
            dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
                ch, uerstat);

            /* check for break */
            if (uerstat & S3C2410_UERSTAT_BREAK) {
                dbg("break!\n");
                port->icount.brk++;
                if (uart_handle_break(port))
                    goto ignore_char;
            }

            if (uerstat & S3C2410_UERSTAT_FRAME)
                port->icount.frame++;
            if (uerstat & S3C2410_UERSTAT_OVERRUN)
                port->icount.overrun++;

            uerstat &= port->read_status_mask;

            if (uerstat & S3C2410_UERSTAT_BREAK)
                flag = TTY_BREAK;
            else if (uerstat & S3C2410_UERSTAT_PARITY)
                flag = TTY_PARITY;
            else if (uerstat & (S3C2410_UERSTAT_FRAME |
                        S3C2410_UERSTAT_OVERRUN))
                flag = TTY_FRAME;
        }

        if (uart_handle_sysrq_char(port, ch))
            goto ignore_char;

        uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,
                 ch, flag);

 ignore_char:
        continue;
    }
    tty_flip_buffer_push(tty);

 out:
    return IRQ_HANDLED;
}


static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{
    struct s3c24xx_uart_port *ourport = id;
    struct uart_port *port = &ourport->port;
    struct circ_buf *xmit = &port->info->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;
    }

    /* if there isnt anything more to transmit, or the uart is now
     * stopped, disable the uart and exit
    */

    if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
        s3c24xx_serial_stop_tx(port);
        goto out;
    }

    /* try and drain the buffer... */

    while (!uart_circ_empty(xmit) && count-- > 0) {
        if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
            break;

        wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
        xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
        port->icount.tx++;
    }

    if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
        uart_write_wakeup(port);

    if (uart_circ_empty(xmit))
        s3c24xx_serial_stop_tx(port);

 out:
    return IRQ_HANDLED;
}


  • 串口测试
getty 功能说明:设置终端机模式,连线速率和管制线路,但是s3c2440_serial1无法工作,原因可能是因为开发板上只有一个串口。

linux查看串口驱动

阅读数 618

Linux串口驱动

阅读数 470

没有更多推荐了,返回首页