Linux TTY驱动程序代码位于/drivers/tty下面。TTY的层次接口包括TTY应用层、TTY文件层、TTY线路规程层、TTY驱动层、TTY设备驱动层。TTY应用层负责应用逻辑,TTY文件层负责文件接口,TTY线路规程负责串行通信协议处理,包括特定协议的封装与解封,TTY驱动层对各种TTY设备进行分类和抽象。TTY设备驱动层实现具体的TTY设备(芯片或者控制器)驱动,即设备配置与数据收发。具体的TTY设备有串口、USB串口、VT设备、PTY设备等,最常见的是串口,即UART。
UART驱动没有主机端和设备端之分,只有控制器驱动。imx6ull的UART驱动已由厂家写好,位于/drivers/tty/serial/imx.c文件中。剩下的工作主要是在设备树中配置串口节点信息,当UART驱动和设备树匹配成功后,相应的UART被驱动起来,在/dev/目录下生成ttymxcX(x=0…n)文件。
正如前面分析的一样,每个设备驱动都有一个xxx_driver结构体,串口也不例外,struct uart_driver
结构体描述UART驱动。struct uart_driver
结构体与前面的xxx_driver结构体有所不同,struct uart_driver
结构体本身并不包含底层UART硬件的操作方法,其是所有串口设备驱动的抽象和封装,起到了连接硬件设备驱动和TTY驱动的作用。注册了struct uart_driver
后还不能使用UART设备,还需要关联具体的UART设备。
include<linux/serial_core.h>
struct uart_driver {
struct module *owner;
const char *driver_name; // 驱动名称
const char *dev_name; // 设备名称
int major; // 主设备号
int minor; // 次设备号
int nr; // 设备数量
struct console *cons; // 控制台
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state;
struct tty_driver *tty_driver;
};
加载和卸载模块的时候要注册和注销驱动,使用下面的函数注册和注销struct uart_driver
结构体。uart_register_driver
函数将struct uart_driver
与TTY驱动层关联起来。
// 注册uart驱动,参数为`struct uart_driver`结构体指针
int uart_register_driver(struct uart_driver *uart)
// 注销uart驱动,参数为`struct uart_driver`结构体指针
void uart_unregister_driver(struct uart_driver *uart)
一个串口控制器或串口芯片上往往有多个串行端口(serial ports,对应于一个物理上的串口),这些串行端口具备相同的操作机制。Linux内核将这些串行端口用struct uart_port
结构体描述。struct uart_port
用于描述一个UART端口的中断、I/O内存地址、FIFO大小、端口类型等信息。
struct uart_port {
spinlock_t lock; /* 自旋锁 */
unsigned long iobase; /* IO基地址 */
unsigned char __iomem *membase; /* 内存地址 */
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);
void (*set_mctrl)(struct uart_port *, unsigned int);
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 *);
int (*rs485_config)(struct uart_port *,struct serial_rs485 *rs485);
unsigned int irq; /* 中断号 */
unsigned long irqflags; /* 中断标志 */
unsigned int uartclk; /* 时钟 */
unsigned int fifosize; /* 发送fifo大小 */
unsigned char x_char; /* xon/xoff握手字节 */
unsigned char regshift; /* 寄存器偏移 */
unsigned char iotype; /* IO访问类型 */
unsigned char unused1;
unsigned int read_status_mask; /* 读状态验码 */
unsigned int ignore_status_mask; /* 忽略状态掩码 */
struct uart_state *state; /* 指向父设备的状态 */
struct uart_icount icount; /* statistics */
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; /* 此结构体很重要,uart硬件的操作函数 */
unsigned int custom_divisor;
unsigned int line; /* port index */
unsigned int minor;
resource_size_t mapbase; /* 内存映射 */
resource_size_t mapsize; /* 内存映射大小 */
struct device *dev; /* 父设备 */
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) */
struct serial_rs485 rs485;
void *private_data; /* generic platform data pointer */
};
// uart硬件操作函数集合,底层硬件驱动必须实现这个结构体,每个函数的具体作用可以参考Documentation/serial/driver文档
struct uart_ops {
// 发送fifo和发送移位寄存器是否为空,为空返回TIOCSER_TEMT,否则返回0
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 *, struct ktermios *);
void (*pm)(struct uart_port *, unsigned int state,unsigned int oldstate);
const char *(*type)(struct uart_port *);
void (*release_port)(struct uart_port *); // 释放port使用的内存和IO资源
int (*request_port)(struct uart_port *); // 请求port需要的内存和IO资源,失败返回-EBUSY
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
};
`uart_port`需要和`uart_driver`关联才能工作,关联的函数如下:
// 添加端口
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
// 删除端口
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
// uart3的设备树节点
uart3: serial@021ec000 {
compatible = "fsl,imx6ul-uart",
"fsl,imx6q-uart", "fsl,imx21-uart"; // 兼容属性,可利用兼容属性找到对应的驱动程序
reg = <0x021ec000 0x4000>;
interrupts = ;
clocks = <&clks IMX6UL_CLK_UART3_IPG>,
<&clks IMX6UL_CLK_UART3_SERIAL>;
clock-names = "ipg", "per";
dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
dma-names = "rx", "tx";
status = "disabled";
};
// uart3的的pinctrl节点
pinctrl_uart3: uart3grp {
fsl,pins = <
MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0x1b0b1
MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0x1b0b1
>;
};
// 引用uart3节点
&uart3{
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};
// 用于和设备树匹配的表,和设备树中的兼容属性一致
static const struct of_device_id imx_uart_dt_ids[] = {
{ .compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },
{ .compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },
{ .compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },
{ /* sentinel */ }
};
// 平台驱动结构体
static struct platform_driver serial_imx_driver = {
.probe = serial_imx_probe, // probe函数,匹配成功后自动调用
.remove = serial_imx_remove,
.suspend = serial_imx_suspend,
.resume = serial_imx_resume,
.id_table = imx_uart_devtype, // 传统的匹配表
.driver = {
.name = "imx-uart", // 驱动名称
.of_match_table = imx_uart_dt_ids, // 设备树匹配表
},
};
模块初始化的时候调用uart_register_driver
注册了uart_driver
结构体,主要工作是关联TTY驱动层,设置一些参数。
// 模块初始化函数
static int __init imx_serial_init(void)
{ // 注册uart_driver结构体
int ret = uart_register_driver(&imx_reg);
if (ret)
return ret;
// 注册为平台驱动程序,用于自动匹配和调用probe函数
ret = platform_driver_register(&serial_imx_driver);
if (ret != 0)
uart_unregister_driver(&imx_reg);
return ret;
}
// 模块退出函数
static void __exit imx_serial_exit(void)
{ // 注销平台驱动
platform_driver_unregister(&serial_imx_driver);
// 注销uart_driver结构体
uart_unregister_driver(&imx_reg);
}
// 串口设备结构体指针数组,probe函数中会将分配的设备结构体指针保存到此数组中
#define UART_NR 8
static struct imx_port *imx_ports[UART_NR];
// 驱动具体定义的uart_driver结构体,成员state指向的内存在uart_register_driver函数中根据uart port数量进行分配
static struct uart_driver imx_reg = {
.owner = THIS_MODULE,
.driver_name = DRIVER_NAME,
.dev_name = DEV_NAME,
.major = SERIAL_IMX_MAJOR,
.minor = MINOR_START,
.nr = ARRAY_SIZE(imx_ports),
.cons = IMX_CONSOLE,
};
串口模块加载的时候首先调用了uart_register_driver
函数,首先分析uart_register_driver
函数:
uart_register_driver
->kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL) // 分配uart_state数组
->alloc_tty_driver // 分配tty_driver结构体
// 设置tty_driver,串口相关参数在打开设备初始化的时候会用到
->normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL // 波特率等信息
->normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600
->tty_set_operations(normal, &uart_ops) // 将tty驱动的操作函数和uart的操作函数关联起来
->tty_port_init // 初始化tty_port
->tty_register_driver // 注册tty驱动
接着分析serial_imx_probe
函数:
serial_imx_probe()
->devm_kzalloc() // 分配imx_port结构体内存
->serial_imx_probe_dt() // 获取设备树信息
->of_match_device() // 将设备树匹配表与设备进行匹配,获取设备对应的设备树匹配表
->of_alias_get_id() // 获取uart别名id,与aliases设备树节点有关
->sport->port.line = ret; // 将uart别名id保存到uart_port结构体line成员中
->of_get_property() // 获取fsl,uart-has-rtscts属性,有就设置have_rtscts=1
->of_get_property() // 获取fsl,dte-mode属性,有就设置dte_mode=1
->sport->devdata = of_id->data // 设置设备结构体的驱动数据成员,指向设备树匹配表中的date成员
->platform_get_resource() // 获取内存地址信息,即设备树中的reg属性
->devm_ioremap_resource() // 映射获取的内存地址
->platform_get_irq() // 获取接收中断(中断索引号为0),设备树中配置了此中断
->platform_get_irq() // 获取发送中断(中断索引号为1),设备树中没有配置此中断
->platform_get_irq() // 获取rts中断(中断索引号为2),设备树中没有配置此中断
/*==================设置uart_port结构体,重要====================*/
->sport->port.dev = &pdev->dev;
->sport->port.mapbase = res->start; // 设置映射前的基地址
->sport->port.membase = base; // 设置映射后的基地址
->sport->port.type = PORT_IMX,
->sport->port.iotype = UPIO_MEM; // IO类型为IO内存
->sport->port.irq = rxirq; // 设置接收中断
->sport->port.fifosize = 32; // 设置fifo大小
->sport->port.ops = &imx_pops; // 设置uart硬件操作函数集合,很重要
->sport->port.rs485_config = imx_rs485_config; // 设置rs485配置函数
->sport->port.rs485.flags = SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
->sport->port.flags = UPF_BOOT_AUTOCONF;
->init_timer(&sport->timer); // 初始化定时器
->sport->timer.function = imx_timeout; // 设置定时器超时函数
->sport->timer.data = (unsigned long)sport; // 设置定时器超时函数的参数
->devm_clk_get // 获取ipg时钟
->devm_clk_get // 获取per时钟
->clk_get_rate // uart的时钟频率
->devm_request_irq // 请求接收中断,中断服务函数为imx_int
->imx_ports[sport->port.line] = sport // 保存设备结构体指针,数组索引为uart别名id
->platform_set_drvdata // 设置设备驱动中的私有数据
->uart_add_one_port // 关联uart_driver(imx_reg)和uart_port(port)
->if (uport->line >= drv->nr) // 检查uart端口数量是否超过了uart_driver中规定的数量
->state = drv->state + uport->line // 根据串口编号找到具体串口的state地址
->state->uart_port = uport // 关联state中的port和具体的port
->uport->state = state // 关联具体port中的state和uart_driver中的state
->uart_configure_port // 配置串口
->ops->config_port() // 调用imx_config_port配置串口
->port.type = PORT_IMX // 设置port类型为Motorola i.MX SoC
->uart_report_port // 打印串口配置信息
->ops->set_mctrl() // 调用imx_set_mctrl函数设置串口
->imx_set_mctrl // 设置串口模式
->tty_port_register_device_attr // 注册tty设备
注册过程分析完,还需要分析底层的操作函数,底层操作函数定义如下,这些函数与常用的IO操作函数都有对应关系,数据接收函数需要在中断中分析。
static struct uart_ops imx_pops = {
.tx_empty = imx_tx_empty, // 发送FIFO是否为空
.set_mctrl = imx_set_mctrl,
.get_mctrl = imx_get_mctrl,
.stop_tx = imx_stop_tx, // 停止发送
.start_tx = imx_start_tx, // 发送数据时调用
.stop_rx = imx_stop_rx, // 停止接收
.enable_ms = imx_enable_ms,
.break_ctl = imx_break_ctl,
.startup = imx_startup, // 启动串口,open串口设备时调用
.shutdown = imx_shutdown, // 关闭串口,close串口设备调用
.flush_buffer = imx_flush_buffer,
.set_termios = imx_set_termios, // 设置串口参数
.type = imx_type,
.config_port = imx_config_port,
.verify_port = imx_verify_port,
#if defined(CONFIG_CONSOLE_POLL)
.poll_init = imx_poll_init,
.poll_get_char = imx_poll_get_char,
.poll_put_char = imx_poll_put_char,
#endif
};
分析imx_startup
函数:
imx_startup
->clk_prepare_enable // 使能per时钟
->clk_prepare_enable // 使能ipg时钟
->imx_setup_ufcr
->rx_fifo_trig = RXTL_UART // 设置接收FIFO的触发字节数为16字节
->val |= TXTL << UFCR_TXTL_SHF | rx_fifo_trig // 设置发送FIFO的触发字节数为2
->temp &= ~UCR2_SRST // 软件复位uart控制器,FIFO等被清空
->temp |= UCR1_RRDYEN // 开启接收中断
->temp |= UCR1_UARTEN // 使能串口
->temp |= UCR4_OREN // 接收溢出中断使能
->temp |= (UCR2_RXEN | UCR2_TXEN) // 接受和发送使能
分析imx_start_tx
函数:
imx_start_tx
->writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1) // 开启发送中断
串口的发送和接收都在中断中进行,下面分析在serial_imx_probe
函数中注册的中断服务函数imx_int
:
imx_int
// 收到了数据,进接收中断服务函数
->if ((sts & USR1_RRDY || sts & USR1_AGTIM) &&!sport->dma_is_enabled)
->imx_rxint
->while (readl(sport->port.membase + USR2) & USR2_RDR) // 循环读数据,直到读完
->readl(sport->port.membase + URXD0) // 读取数据
->tty_insert_flip_char // 回调tty层的函数,将收到的数据提交上去
->tty_insert_flip_string_flags
->tty_buffer_request_room // 分配空间
->memcpy // 将数据拷贝到tty层
// 发送FIFO空或发送完成,进发送中断服务函数
->if ((sts & USR1_TRDY && readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN) ||
(sts2 & USR2_TXDC && readl(sport->port.membase + UCR4) & UCR4_TCEN))
->imx_txint
->imx_transmit_buffer
// 判断数据是否发送完或是否要停止发送
->if (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port))
imx_stop_tx // 上述条件成立,停止发送
// 发送缓冲区未空且发送FIFO未满就继续填充数据
->while (!uart_circ_empty(xmit) && !(readl(sport->port.membase + uts_reg(sport)) & UTS_TXFULL))
->writel(xmit->buf[xmit->tail], sport->port.membase + URTX0)
->imx_stop_tx(&sport->port) // 发送缓冲区空,停止发送
stty -F /dev/ttymxc2 -a // 查看某个串口的设置信息
stty -a < /dev/ttymxc2 // 查看某个串口的设置信息
// 设置串口,波特率115200,8位数据,无校验,1位停止位,禁止回显
stty -F /dev/ttymxc2 speed 115200 cs8 -parenb -cstopb -echo -icanon
// 取消规范模式,读请求直接从输入队列读取字符。使用cat时可直接显示字符,不需要数据中有回车
stty -F /dev/ttymxc2 -icanon
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define OK (0)
#define ERROR (-1)
#define PATH "/dev/ttymxc2"
int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio;
if (tcgetattr(fd, &newtio) != 0) {
perror("SetupSerial 1");
return -1;
}
newtio.c_cflag &= ~CSIZE; //字符长度掩码。取值为:CS5,CS6,CS7或CS8
switch(nBits)
{
case 5:
newtio.c_cflag |= CS5;
break;
case 6:
newtio.c_cflag |= CS6;
break;
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch(nEvent)
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch(nSpeed)
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
case 460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
default:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
}
if(nStop == 1)
newtio.c_cflag &= ~CSTOPB;
else if (nStop == 2)
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VTIME] = 0; // 读取串口数据等待的时间
newtio.c_cc[VMIN] = 1; // 最少读取到1个字节的数据才返回
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
newtio.c_oflag &= ~OPOST;
newtio.c_oflag &= ~(ONLCR | OCRNL);
newtio.c_iflag &= ~(ICRNL | INLCR);
newtio.c_iflag &= ~(IXON | IXOFF | IXANY);
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd, TCSANOW, &newtio)) != 0) {
perror("com set error");
return -1;
}
printf("set done!\n\r");
return 0;
}
/* 选项
* hex-- 十六进制
* str-- 字符
*/
int main(int argc, char* argv[])
{
int fd, ret, i = 0;
int hex;
char ch;
if (2 != argc) {
printf("input parameter error\n");
return ERROR;
}
if (0 == strcmp("hex", argv[1]))
hex = 1;
else if (0 == strcmp("str", argv[1]))
hex = 0;
else {
printf("option error\n");
return ERROR;
}
fd = open(PATH, O_RDWR | O_NOCTTY);
if (fd <= 0) {
printf("open error\n");
return ERROR;
}
printf("open %s success\n", PATH);
ret = set_opt(fd, 115200, 8, 'N', 1);
if (ret == -1) {
printf("set_opt error\n");
return ERROR;
}
while (1) {
ret = read(fd, &ch, sizeof(ch));
if (ret < 0) {
printf("read error\n");
return ERROR;
}
// 读取到数据,打印并写回
if (ret == sizeof(ch)) {
if (1 == hex) {
printf("%2X ", (unsigned char)ch);
if (0 == ((i + 1) % 0xF))
printf("\n");
++i;
}
else
printf("%c", ch);
ret = write(fd, &ch, sizeof(ch));
if (ret < 0) {
printf("write error\n");
return ERROR;
}
}
}
close(fd);
return 0;
}