往期内容
本专栏往期内容:Uart子系统
- UART串口硬件介绍
- 深入理解TTY体系:设备节点与驱动程序框架详解
- Linux串口应用编程:从UART到GPS模块及字符设备驱动
- 解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解
- IMX 平台UART驱动情景分析:注册篇
interrupt子系统专栏:
- 专栏地址:interrupt子系统
- Linux 链式与层级中断控制器讲解:原理与驱动开发
– 末片,有专栏内容观看顺序pinctrl和gpio子系统专栏:
专栏地址:pinctrl和gpio子系统
编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用
– 末片,有专栏内容观看顺序
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有专栏内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有专栏内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有专栏内容观看顺序
1.内核代码
硬件相关:
-
drivers/tty/serial/imx.c📎imx.c — imx系列的
-
drivers/tty/serial/stm32-usart.c📎stm32-usart.c — stm32系列的
串口核心层:
- drivers/tty/serial/serial_core.c📎serial_core.c
TTY层:
- drivers/tty/tty_io.c📎tty_io.c
上一篇文章讲了uart串口驱动程序的初始化注册流程(IMX 平台UART驱动情景分析:注册篇),本篇主要介绍通过open去打开串口时候,内核驱动是怎么去操作的。
同样涉及到的一些结构体还是得看一下之前的文章:解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解
2.框图
3.设备节点
- 为什么是/dev/ttymxc、/dev/ttySTM?
- 为什么是/dev/ttymxc5、/dev/ttySTM3?
在linux中有一种叫做sysfs的虚拟文件系统,旨在提供一种访问内核数据结构的方法,从而允许用户空间程序查看和控制系统的设备和资源。sysfs文件系统通常被挂载在/sys目录下。sysfs提供了一种以树状结构组织的系统信息的方式,其中每个设备都有一个唯一的目录来表示它自己,其中包含有关设备的各种属性和状态信息的文件。这些文件通常是只读的,但有些也可以用于修改设备的某些设置。sysfs还提供了一个机制来通知用户空间程序有关设备状态更改的信息,从而使其能够对这些更改做出反应。sysfs文件系统被广泛用于Linux内核中,它为开发者提供了一种简单的方式来管理和控制系统中的各种设备和资源。
通过class_create和device_create来创建设备类和设备节点,系统会根据提供的设备信息来创建相关的设备节点
3.open过程分析
它要做的事情:
-
找到tty_driver
- 分配/设置tty_struct
- 行规程相关的初始化
-
调用tty_driver->ops->open // ops是struct tty_operations指针类型, 就是uart_open
-
tty_port_open // uart下有多个port, 下面就是打开某个port
-
port->ops->activate(port, tty); // ops是struct tty_port_operations指针类型
-
uart_startup(tty, state, 0);
-
uart_port_startup(tty, state, init_hw);
- 调用uart_port->ops->startup // ops是struct uart_ops指针类型
-
-
-
-
在之前讲解uart_driver的时候,在其probe函数的serial_imx_probe函数中就有提到了file_operations,如下:
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,
};
当app调用了open的时候,就会调用到tty_open
:
\Linux-4.9.88\Linux-4.9.88\drivers\tty\tty_io.c
/*** tty_open - 打开TTY设备* @inode: 设备文件的inode* @filp: TTY的文件指针** tty_open 和 tty_release 维护着TTY计数,表示对TTY的打开次数。* 我们不能使用inode计数,因为不同的inode可能指向同一个TTY。** 打开计数在PTY主控和串口线路中是必需的:当最后一次关闭时,DTR会被拉低。* (这不是仅通过 tty->count 来完成的。 - Ted 1/27/92)** PTY的termios状态在第一次打开时会被重置,以便设置不会在重用时持续存在。** 锁定:tty_mutex 保护 tty、tty_lookup_driver 和 tty_init_dev。* tty->count 保护其他部分。* ->siglock 保护 ->signal/->sighand** 注意:没有引用的 tty_unlock/lock 情况在 tty_mutex 的保护下是安全的。*/static int tty_open(struct inode *inode, struct file *filp)
{struct tty_struct *tty; // TTY结构体指针int noctty, retval; // noctty表示是否是控制终端,retval用于存储返回值dev_t device = inode->i_rdev; // 获取设备号unsigned saved_flags = filp->f_flags; // 保存文件标志// 确保文件描述符是不可寻址的nonseekable_open(inode, filp);retry_open: // 重试打开标签retval = tty_alloc_file(filp); // 分配TTY文件结构if (retval)return -ENOMEM; // 如果失败,返回内存不足错误// 尝试打开当前TTYtty = tty_open_current_tty(device, filp);if (!tty)// 如果当前TTY不存在,则通过驱动尝试打开TTYtty = tty_open_by_driver(device, inode, filp);if (IS_ERR(tty)) { // 检查打开是否失败tty_free_file(filp); // 释放TTY文件结构retval = PTR_ERR(tty); // 获取错误码if (retval != -EAGAIN || signal_pending(current))return retval; // 如果不是重试错误或有信号,则返回错误schedule(); // 进入调度,等待goto retry_open; // 重试打开}tty_add_file(tty, filp); // 将文件描述符添加到TTYcheck_tty_count(tty, __func__); // 检查TTY计数tty_debug_hangup(tty, "opening (count=%d)\n", tty->count); // 调试输出当前TTY计数if (tty->ops->open)retval = tty->ops->open(tty, filp); // 调用TTY的打开操作elseretval = -ENODEV; // 如果没有操作则返回设备不存在错误filp->f_flags = saved_flags; // 恢复文件标志if (retval) { // 如果打开失败tty_debug_hangup(tty, "open error %d, releasing\n", retval); // 调试输出错误信息tty_unlock(tty); // 解锁TTYtty_release(inode, filp); // 释放TTYif (retval != -ERESTARTSYS)return retval; // 如果不是重启错误,则返回if (signal_pending(current))return retval; // 如果有信号则返回错误schedule(); // 进入调度,等待/** 如果发生了挂起,需要重置f_op。*/if (tty_hung_up_p(filp))filp->f_op = &tty_fops; // 如果TTY挂起,重置文件操作goto retry_open; // 重试打开}clear_bit(TTY_HUPPED, &tty->flags); // 清除挂起标志// 处理控制终端的逻辑read_lock(&tasklist_lock); // 读取锁定任务列表spin_lock_irq(¤t->sighand->siglock); // 锁定信号处理程序noctty = (filp->f_flags & O_NOCTTY) || // 判断是否为控制终端(IS_ENABLED(CONFIG_VT) && device == MKDEV(TTY_MAJOR, 0)) ||device == MKDEV(TTYAUX_MAJOR, 1) ||(tty->driver->type == TTY_DRIVER_TYPE_PTY &&tty->driver->subtype == PTY_TYPE_MASTER);if (!noctty && // 如果不是控制终端并且当前进程是会话领导current->signal->leader &&!current->signal->tty &&tty->session == NULL) {/** 不允许仅具有TTY写权限的进程获取控制终端的权限。* 这样做是为了防止进程获得不应有的特权。*/if (filp->f_mode & FMODE_READ)__proc_set_tty(tty); // 设置TTY为控制终端}spin_unlock_irq(¤t->sighand->siglock); // 解锁信号处理程序read_unlock(&tasklist_lock); // 解锁任务列表tty_unlock(tty); // 解锁TTYreturn 0; // 返回成功
}
主要是分两个方面,第一次打开tty和其它。
(1)第一次打开tty
\Linux-4.9.88\Linux-4.9.88\drivers\tty\tty_io.c
static int tty_open(struct inode *inode, struct file *filp)
{struct tty_struct *tty; // TTY结构体指针int noctty, retval; // noctty表示是否是控制终端,retval用于存储返回值dev_t device = inode->i_rdev; // 获取设备号unsigned saved_flags = filp->f_flags; // 保存文件标志// 确保文件描述符是不可寻址的nonseekable_open(inode, filp);retry_open: // 重试打开标签retval = tty_alloc_file(filp); // 分配TTY文件结构if (retval)return -ENOMEM; // 如果失败,返回内存不足错误// 尝试打开当前TTYtty = tty_open_current_tty(device, filp); if (!tty)// 如果当前TTY不存在,则通过驱动尝试打开TTYtty = tty_open_by_driver(device, inode, filp);if (IS_ERR(tty)) { // 检查打开是否失败tty_free_file(filp); // 释放TTY文件结构retval = PTR_ERR(tty); // 获取错误码if (retval != -EAGAIN || signal_pending(current))return retval; // 如果不是重试错误或有信号,则返回错误schedule(); // 进入调度,等待goto retry_open; // 重试打开}//。。。。。。}
先来讲解第一次打开tty设备,删除了一部分暂时不用的。第一次打开时,也就是调用了tty_open_current_tty
函数尝试去打开,此时肯定是没有tty,可以先进入tty_open_current_tty
中去看看其定义:
\Linux-4.9.88\Linux-4.9.88\drivers\tty\tty_io.c
/*** tty_open_current_tty - 获取当前任务的锁定TTY* @device: 设备号* @filp: TTY的文件指针* @return: 当前任务的锁定TTY,如果@device是/dev/tty则返回** 重新打开当前任务的控制TTY。** 我们不能像其他节点那样返回驱动和索引,因为devpts将不工作。* 它期望inode来自devpts文件系统。*/
static struct tty_struct *tty_open_current_tty(dev_t device, struct file *filp)
{struct tty_struct *tty; // TTY结构指针int retval;// 检查设备号是否为主TTY设备if (device != MKDEV(TTYAUX_MAJOR, 0))return NULL; // 不是控制TTY,返回NULLtty = get_current_tty(); // 获取当前任务的TTYif (!tty)return ERR_PTR(-ENXIO); // 获取失败,返回错误filp->f_flags |= O_NONBLOCK; // 设置为非阻塞模式tty_lock(tty); // 锁定TTY以防并发修改tty_kref_put(tty); // 安全地降低引用计数retval = tty_reopen(tty); // 尝试重新打开TTYif (retval < 0) {tty_unlock(tty); // 解锁TTYtty = ERR_PTR(retval); // 返回错误}return tty; // 返回锁定的TTY
}
- 此函数用于获取当前任务的控制TTY,主要用于处理TTY的重新打开。
- 函数首先检查设备号是否是主TTY设备。如果不是,则返回NULL。
- 它会尝试获取当前任务的TTY并锁定该TTY,以防止并发修改。
- 如果获取TTY成功,设置文件标志为非阻塞模式,然后尝试重新打开TTY。如果重新打开失败,返回相应的错误。
也就是说第一次打开的时候,这个函数tty_open_current_tty
肯定是返回失败的,就会进入if分支去调用tty_open_by_driver
函数(tty_open
函数中的tty_open_by_driver
):
\Linux-4.9.88\Linux-4.9.88\drivers\tty\tty_io.c
/*** tty_open_by_driver - 打开TTY设备* @device: 要打开的设备的dev_t* @inode: 设备文件的inode* @filp: TTY的文件指针** 执行驱动查找,检查是否需要重新打开,或者执行第一次TTY初始化。** 返回锁定的初始化或重新打开的 &tty_struct** 声明全局 tty_mutex 以序列化:* - 并发的第一次TTY初始化* - 并发TTY驱动程序移除与查找* - 并发TTY从驱动表中移除*/
static struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,struct file *filp)
{struct tty_struct *tty; // TTY结构指针struct tty_driver *driver = NULL; // TTY驱动指针int index = -1; // TTY索引int retval;mutex_lock(&tty_mutex); // 锁定全局TTY互斥量driver = tty_lookup_driver(device, filp, &index); // 查找TTY驱动if (IS_ERR(driver)) {mutex_unlock(&tty_mutex); // 查找失败,解锁return ERR_CAST(driver); // 返回错误}// 检查是否正在重新打开现有的TTYtty = tty_driver_lookup_tty(driver, filp, index);if (IS_ERR(tty)) {mutex_unlock(&tty_mutex); // 查找失败,解锁goto out; // 跳转到结束}if (tty) { // 找到现有TTYmutex_unlock(&tty_mutex); // 解锁retval = tty_lock_interruptible(tty); // 锁定TTYtty_kref_put(tty); // 降低引用计数if (retval) {if (retval == -EINTR)retval = -ERESTARTSYS; // 中断错误处理tty = ERR_PTR(retval); // 返回错误goto out; // 跳转到结束}retval = tty_reopen(tty); // 重新打开TTYif (retval < 0) {tty_unlock(tty); // 解锁TTYtty = ERR_PTR(retval); // 返回错误}} else { // 没有现有TTY,进行初始化tty = tty_init_dev(driver, index); // 初始化TTYmutex_unlock(&tty_mutex); // 解锁}
out:tty_driver_kref_put(driver); // 降低驱动引用计数return tty; // 返回TTY结构
}
是不是可以看到,第一次调用时没有tty的就会去调用tty_init_dev
来初始化一个tty出来,那么可以进入tty_init_dev
来看看:
\Linux-4.9.88\Linux-4.9.88\drivers\tty\tty_io.c
/*** tty_init_dev - 初始化TTY设备* @driver: 我们正在打开设备的TTY驱动* @idx: 设备索引* @ret_tty: 返回的TTY结构** 准备TTY设备。这可能不是一个“新的”干净设备,也可以是一个活跃的设备。* PTY驱动需要特殊处理。** 锁定:* 该函数在tty_mutex下调用,保护我们免受TTY结构或驱动本身消失的影响。** 退出时,TTY设备附加了线路纪律,并且引用计数为1。* 如果为PTY/TTY使用创建了一对,且另一个是PTY主设备,则它的引用计数也为1。** WSH 06/09/97:重写以消除竞争条件,并在打开失败时正确清理。* 新代码用互斥锁保护打开,因此非常简单。* 对于(最常见的)重新打开TTY的情况,互斥锁的锁定可能可以放松。*/
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
{struct tty_struct *tty; // TTY结构指针int retval; // 返回值/** 第一次打开是复杂的,尤其是对于PTY设备。* 此代码保证要么一切成功并且TTY准备就绪,* 要么表槽被清空并且分配的内存被释放。* (除非termios和锁定的termios可能被保留。)*/// 尝试获取驱动模块的引用if (!try_module_get(driver->owner))return ERR_PTR(-ENODEV); // 获取失败,返回错误// 分配TTY结构tty = alloc_tty_struct(driver, idx);if (!tty) {retval = -ENOMEM; // 分配失败goto err_module_put; // 跳转到模块释放}tty_lock(tty); // 锁定TTY结构retval = tty_driver_install_tty(driver, tty); // 安装TTY驱动if (retval < 0)goto err_free_tty; // 安装失败,跳转到释放TTY// 如果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); // 警告驱动未设置端口retval = tty_ldisc_lock(tty, 5 * HZ); // 锁定线路纪律,最多等待5秒if (retval)goto err_release_lock; // 锁定失败,跳转到释放锁tty->port->itty = tty; // 设置TTY的port指向// 所有结构都已安装... 调用ldisc打开例程。retval = tty_ldisc_setup(tty, tty->link); // 设置线路纪律if (retval)goto err_release_tty; // 设置失败,跳转到释放TTYtty_ldisc_unlock(tty); // 解锁线路纪律// 返回锁定的TTY,以防在调用者中消失return tty;err_free_tty:tty_unlock(tty); // 解锁TTYfree_tty_struct(tty); // 释放TTY结构
err_module_put:module_put(driver->owner); // 释放驱动模块的引用return ERR_PTR(retval); // 返回错误// 调用TTY释放例程以清理此槽
err_release_tty:tty_ldisc_unlock(tty); // 解锁线路纪律tty_info_ratelimited(tty, "ldisc open failed (%d), clearing slot %d\n",retval, idx); // 记录失败信息
err_release_lock:tty_unlock(tty); // 解锁TTYrelease_tty(tty, idx); // 释放TTYreturn ERR_PTR(retval); // 返回错误
}
(2)tty_open
tty_open// 如果设备节点是(5,0)也就是/dev/tty, 表示当前TTY// 对于普通串口, 第一次open时必定失败tty = tty_open_current_tty(device, filp);// 第一次open串口时走这个分支if (!tty)// 通过driver来open tty,就是找到tty_driver,然后分配/设置tty_structtty = tty_open_by_driver(device, inode, filp);// 1. 先找到对应的tty_driverdriver = tty_lookup_driver(device, filp, &index);// 2. 如果曾经open过,会有对应的tty_structtty = tty_driver_lookup_tty(driver, filp, index);// 3. 第1打开这个串口时肯定没有对应的tty_struct// 所以使用下面的函数初始化设备tty = tty_init_dev(driver, index);// 3.1 分配tty_strcttty = alloc_tty_struct(driver, idx);tty->ops = driver->ops;// 3.2 安装tty: 也就是driver->ttys[tty->index] = tty;retval = tty_driver_install_tty(driver, tty);// 3.3 调用行规程的open函数, 过于复杂,不分析// n_tty.c中的n_tty_open函数retval = tty_ldisc_setup(tty, tty->link);......// ops是tty_operations类型// 对于串口ops就是serial_core.c中的uart_ops// uart_openif (tty->ops->open)retval = tty->ops->open(tty, filp);elseretval = -ENODEV;
那么讲完第一次打开tty时没有tty的情况下,调用相关函数初始化tty_truct,就可以去进行打开tty设备了。回到tty_open函数:
\Linux-4.9.88\Linux-4.9.88\drivers\tty\tty_io.c
/*** tty_open - 打开TTY设备* @inode: 设备文件的inode* @filp: TTY的文件指针** tty_open 和 tty_release 维护着TTY计数,表示对TTY的打开次数。* 我们不能使用inode计数,因为不同的inode可能指向同一个TTY。** 打开计数在PTY主控和串口线路中是必需的:当最后一次关闭时,DTR会被拉低。* (这不是仅通过 tty->count 来完成的。 - Ted 1/27/92)** PTY的termios状态在第一次打开时会被重置,以便设置不会在重用时持续存在。** 锁定:tty_mutex 保护 tty、tty_lookup_driver 和 tty_init_dev。* tty->count 保护其他部分。* ->siglock 保护 ->signal/->sighand** 注意:没有引用的 tty_unlock/lock 情况在 tty_mutex 的保护下是安全的。*/static int tty_open(struct inode *inode, struct file *filp)
{struct tty_struct *tty; // TTY结构体指针int noctty, retval; // noctty表示是否是控制终端,retval用于存储返回值dev_t device = inode->i_rdev; // 获取设备号unsigned saved_flags = filp->f_flags; // 保存文件标志//.............if (tty->ops->open)retval = tty->ops->open(tty, filp); // 调用TTY的打开操作elseretval = -ENODEV; // 如果没有操作则返回设备不存在错误filp->f_flags = saved_flags; // 恢复文件标志if (retval) { // 如果打开失败tty_debug_hangup(tty, "open error %d, releasing\n", retval); // 调试输出错误信息tty_unlock(tty); // 解锁TTYtty_release(inode, filp); // 释放TTYif (retval != -ERESTARTSYS)return retval; // 如果不是重启错误,则返回if (signal_pending(current))return retval; // 如果有信号则返回错误schedule(); // 进入调度,等待/** 如果发生了挂起,需要重置f_op。*/if (tty_hung_up_p(filp))filp->f_op = &tty_fops; // 如果TTY挂起,重置文件操作goto retry_open; // 重试打开}clear_bit(TTY_HUPPED, &tty->flags); // 清除挂起标志//........
}
去调用了retval = tty->ops->open(tty, filp)
,其中ops是tty_operations类型的结构体,这个在之前相关结构体介绍的时候已经讲解过了,那么可以猜猜这个open是什么,其实逆向思维一下,之前讲解注册方面的时候,就提到过了:硬件层驱动调用相关函数向上注册uart_driver(进入核心层) —> 之后调用相关函数去注册tty_driver(进入了tty层),那么是不是可以猜到,此时我在tty层调用了tty_open,反过来就是进入核心层去调用uart_open:
\Linux-4.9.88\drivers\tty\serial\serial_core.c
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
};
这不就看到了,在核心层中早着定义了tty_operations结构体,承上启下。那么可以确定了tty_open后就是调用了uart_open,来看看uart_open的定义
(3)uart_open
uart_opentty_port_open// ops是tty_port_operations类型,对应serial_core.c中的uart_port_ops// uart_port_activateif (port->ops->activate) {int retval = port->ops->activate(port, tty);if (retval) {mutex_unlock(&port->mutex);return retval;}}uart_port_activateuart_startupuart_port_startup// ops是uart_ops类型,在硬件驱动中设置// 硬件相关的驱动中uart_port的uart_ops里必须提供startup函数retval = uport->ops->startup(uport);
\Linux-4.9.88\Linux-4.9.88\drivers\tty\serial\serial_core.c
/** 调用 uart_open 的操作由 tty_lock 进行序列化,位于* drivers/tty/tty_io.c:tty_open() 中。* 注意:如果打开失败,将会调用 uart_close()。** 在未来,我们希望废除“打开不存在的端口”的行为,* 实现一种替代方法来设置 setserial 的基地址/端口/类型。* 这将使我们能够减少一定数量的额外测试。*/
static int uart_open(struct tty_struct *tty, struct file *filp)
{struct uart_driver *drv = tty->driver->driver_state; // 获取与TTY相关的UART驱动int retval, line = tty->index; // 获取TTY的索引struct uart_state *state = drv->state + line; // 获取对应的UART状态tty->driver_data = state; // 将UART状态指针存储在TTY结构中retval = tty_port_open(&state->port, tty, filp); // 调用tty_port_open打开端口if (retval > 0)retval = 0; // 如果返回值大于0,设置为0(成功)return retval; // 返回操作结果
}
uart_open
函数负责打开UART设备,首先获取与TTY结构相关的UART驱动状态。- 使用
tty_port_open
函数尝试打开TTY端口,并将返回值传递给调用者。
继续进入tty_port_open
函数:
\Linux-4.9.88\drivers\tty\tty_port.c
/*** tty_port_open** 调用者持有 tty 锁。** 注意:可能会释放并重新获取 tty 锁(在 tty_port_block_til_ready() 中),* 所以 tty 和 tty_port 的状态可能已更改(例如,可能已挂起)。*/
int tty_port_open(struct tty_port *port, struct tty_struct *tty,struct file *filp)
{spin_lock_irq(&port->lock); // 获取自旋锁以保护端口状态++port->count; // 增加打开计数spin_unlock_irq(&port->lock); // 释放自旋锁tty_port_tty_set(port, tty); // 将TTY结构设置为端口的当前TTY/** 只有在硬件尚未初始化时才执行设备特定的打开。* 使用端口互斥锁序列化打开和关闭操作。*/mutex_lock(&port->mutex); // 获取端口的互斥锁if (!tty_port_initialized(port)) { // 检查端口是否已初始化clear_bit(TTY_IO_ERROR, &tty->flags); // 清除TTY的I/O错误标志if (port->ops->activate) { // 如果存在激活操作int retval = port->ops->activate(port, tty); // 调用激活操作if (retval) {mutex_unlock(&port->mutex); // 释放互斥锁return retval; // 返回错误码}}tty_port_set_initialized(port, 1); // 设置端口为已初始化}mutex_unlock(&port->mutex); // 释放互斥锁return tty_port_block_til_ready(port, tty, filp); // 等待端口准备好
}
注意有int retval = port->ops->activate(port, tty);
调用激活操作,也就是激活uart设备,ops还是tty_port_operation结构体类型的,来看看其定义:
\Linux-4.9.88\drivers\tty\serial\serial_core.c
static const struct tty_port_operations uart_port_ops = {.carrier_raised = uart_carrier_raised,.dtr_rts = uart_dtr_rts,.activate = uart_port_activate,.shutdown = uart_tty_port_shutdown,
};
那么来看看uart_port_activate
函数:
\Linux-4.9.88\drivers\tty\serial\serial_core.c
static int uart_port_activate(struct tty_port *port, struct tty_struct *tty)
{// 获取与 tty_port 关联的 uart_state 结构体struct uart_state *state = container_of(port, struct uart_state, port);struct uart_port *uport;// 检查 uart_port 的有效性uport = uart_port_check(state);if (!uport || uport->flags & UPF_DEAD)return -ENXIO; // 返回错误,表示没有有效的设备// 根据标志设置低延迟模式port->low_latency = (uport->flags & UPF_LOW_LATENCY) ? 1 : 0;/** 启动串口。*/return uart_startup(tty, state, 0);
}
继续进入uart_startup
看看:
\Linux-4.9.88\drivers\tty\serial\serial_core.c
static int uart_startup(struct tty_struct *tty, struct uart_state *state,int init_hw)
{struct tty_port *port = &state->port;int retval;if (tty_port_initialized(port))return 0;retval = uart_port_startup(tty, state, init_hw); if (retval)set_bit(TTY_IO_ERROR, &tty->flags);return retval;
}
继续进入uart_port_startup
:
/** 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 = uart_port_check(state);unsigned long page;int retval = 0;if (uport->type == PORT_UNKNOWN)return 1; // 如果端口类型未知,返回1表示不需要初始化/** 确保设备处于 D0 状态(工作状态)。*/uart_change_pm(state, UART_PM_STATE_ON);/** 初始化并分配发送和临时缓冲区。*/if (!state->xmit.buf) {/* 这部分由每个端口的互斥锁保护 */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) {// 如果是控制台设备并且 cflag 不为 0,进行初始化if (uart_console(uport) && uport->cons->cflag) {tty->termios.c_cflag = uport->cons->cflag; // 设置 TTY 的控制标志uport->cons->cflag = 0; // 清除控制台的 cflag}/** 初始化硬件端口设置。*/uart_change_speed(tty, state, NULL); // 配置波特率/** 一旦端口打开并准备好响应,设置 RTS 和 DTR 信号。*/if (init_hw && C_BAUD(tty))uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR); // 设置 RTS 和 DTR}/** 允许在此端口上执行 setserial 操作。用户可能希望设置* 端口/IRQ/类型,然后在此处如果失败后重新配置端口。*/if (retval && capable(CAP_SYS_ADMIN))return 1; // 如果失败但具有管理员权限,返回1表示可以继续return retval; // 返回结果
}
retval = uport->ops->startup(uport);
调用硬件启动操作,此时的ops就是uart_ops类型的结构体了,也就是在硬件层中定义的,这个需要我们在编写硬件驱动程序的时候去提供:
\Linux-4.9.88\Linux-4.9.88\drivers\tty\serial\imx.c
static const struct uart_ops imx_pops = {.tx_empty = imx_tx_empty,.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,.shutdown = imx_shutdown,.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
};
那么再最后看一下这个uport->ops->startup(uport)
,也就是imx_startup
函数的实现吧:
static int imx_startup(struct uart_port *port)
{struct imx_port *sport = (struct imx_port *)port;struct tty_port *tty_port = &sport->port.state->port;int retval, i;unsigned long flags, temp;/* 某些调制解调器可能需要重置 */if (!tty_port_suspended(tty_port)) {retval = device_reset(sport->port.dev); // 重置设备if (retval && retval != -ENOENT)return retval; // 如果重置失败,返回错误}retval = clk_prepare_enable(sport->clk_per); // 准备并启用主时钟if (retval)return retval;retval = clk_prepare_enable(sport->clk_ipg); // 准备并启用 IPG 时钟if (retval) {clk_disable_unprepare(sport->clk_per); // 禁用主时钟return retval;}imx_setup_ufcr(sport, 0); // 设置 UFCR 寄存器/* 在请求中断之前禁用 DREN 位(数据就绪中断使能) */temp = readl(sport->port.membase + UCR4);/* 设置 CTS 的触发级别 */temp &= ~(UCR4_CTSTL_MASK << UCR4_CTSTL_SHF);temp |= CTSTL << UCR4_CTSTL_SHF;writel(temp & ~UCR4_DREN, sport->port.membase + UCR4); // 禁用数据就绪中断/* 重置 FIFO 和状态机 */i = 100; // 重试次数temp = readl(sport->port.membase + UCR2);temp &= ~UCR2_SRST; // 清除软件复位位writel(temp, sport->port.membase + UCR2);while (!(readl(sport->port.membase + UCR2) & UCR2_SRST) && (--i > 0))udelay(1); // 等待重置完成/* 检查是否可以启用 DMA 支持 */if (is_imx6q_uart(sport) && !uart_console(port)&& !sport->dma_is_inited)imx_uart_dma_init(sport); // 初始化 DMAif (sport->dma_is_inited)INIT_WORK(&sport->tsk_dma_tx, dma_tx_work); // 初始化 DMA 发送工作spin_lock_irqsave(&sport->port.lock, flags); // 加锁/** 最后,清除并启用中断*/writel(USR1_RTSD, sport->port.membase + USR1); // 启用 RTS 状态中断writel(USR2_ORE, sport->port.membase + USR2); // 启用 溢出错误中断temp = readl(sport->port.membase + UCR1);if (!sport->dma_is_inited)temp |= UCR1_RRDYEN; // 启用接收准备中断if (sport->have_rtscts)temp |= UCR1_RTSDEN; // 启用 RTS 状态中断temp |= UCR1_UARTEN; // 启用 UARTwritel(temp, sport->port.membase + UCR1); // 写入 UCR1 寄存器temp = readl(sport->port.membase + UCR4);temp |= UCR4_OREN; // 启用接收中断writel(temp, sport->port.membase + UCR4); // 写入 UCR4 寄存器temp = readl(sport->port.membase + UCR2);temp |= (UCR2_RXEN | UCR2_TXEN); // 启用接收和发送if (!sport->have_rtscts)temp |= UCR2_IRTS; // 如果没有 RTS/CTS,启用 IRTS/** 确保边缘敏感的 RTS 中断被禁用,* 我们正在使用 RTSD。*/if (!is_imx1_uart(sport))temp &= ~UCR2_RTSEN; // 禁用 RTS 中断writel(temp, sport->port.membase + UCR2); // 写入 UCR2 寄存器if (!is_imx1_uart(sport)) {temp = readl(sport->port.membase + UCR3);/** RI 和 DCD 的效果取决于 UFCR_DCEDTE 位。* 在 DCE 模式下,它们控制输出,在 DTE 模式下启用相应的 IRQ。* 至少在 i.MX25 上 DCD IRQ 无法清除,因此必须禁用。* 我没有测试硬件来检查 RI 是否有同样的问题,* 但我认为这很可能,所以现在也禁用它。*/temp |= IMX21_UCR3_RXDMUXSEL | UCR3_ADNIMP |UCR3_DTRDEN | UCR3_RI | UCR3_DCD;if (sport->dte_mode)temp &= ~(UCR3_RI | UCR3_DCD); // 如果是 DTE 模式,禁用 RI 和 DCDwritel(temp, sport->port.membase + UCR3); // 写入 UCR3 寄存器}/** 启用调制解调器状态中断*/imx_enable_ms(&sport->port);spin_unlock_irqrestore(&sport->port.lock, flags); // 解锁return 0; // 返回成功
}