一、前言

前面介绍了 Linux内核 的 2 个驱动框架—— I2C 和 SPI ,这 2 个框架相对简单一些,直来直去,没有比较难以理解的点,层次分明。而今天我们要讲述的是我们熟悉的 串口驱动,该驱动加框也较之之前的驱动来说,复杂了许多。串口 是我们常用的通讯手段,但其软件框架在 Linux内核 中非常复杂。当然,这里面也有历史原因在。本文将简单地介绍 UART驱动框架,希望能够帮助各位读者。

二、UART驱动

2.1 tty简介

串口(UART) 通常是以 tty驱动 的形式在用户层表现出来,那么我们首先需要知道 tty 是什么东西。

tty 是 Linux系统 中出现得最多的一种设备类型,可以直接查看 /dev目录 下,往往可以看到多个 tty设备节点。要了解 tty设备 就需要从历史的角度说起。

在计算机系统中,终端 是 电子设备,用于向系统输入数据,或者接收系统发送的数据并显示出来,所以该设备的作用是用于 人机交互。而 电传打字机(Teletype) 是一种久远的 远距离信息传送设备,其通过 键盘 输入数据,通过 印字机器 显示接收到的数据。电传打字机 就是一种典型的 终端,其英语简称即 tty。

按照笔者的理解,tty 即用于与 用户 交互的设备,其向计算机系统输入数据,也接收来自计算机系统的数据。有兴趣的读者可以参考链接中的文章 Linux TTY framework(1)_基本概念。

对于 Linux系统 来说,通过 串口 连接的设备一般都可以作为 终端设备。经过日积月累,终端、tty、串口等概念逐渐不再去分。一般来讲,所有 串口设备 都是 tty设备,而 tty设备 除了 串口外还有其他的具体形式,比如 虚拟终端 、伪终端等。

其实这里还涉及到一个概念 控制台(console),但本文目前不做讲述,后续有机会再写相关文章。

2.2 串口tty框架

在讲解相关细节之前,我们需要对 串口tty驱动 有个整体性的认识,这样有非常助于后面理解代码细节。在看细节之前,我们需要知道:

整个驱动的整体架构是如何组织的

数据结构之间的关系是如何

函数的传递是如何递进的

下面将展示一张图,该图有助于我们理解

串口tty驱动框架图

我们可以看到:

从左往右 看,整个驱动可以分为 3个层次,分为为 字符设备层、tty层 和 串口硬件层。

字符设备层 是 tty驱动 作为 字符设备 直接与应用层进行交互,中间通过 tty层 进行抽象,使得底层的具体形式可以被解耦。最终是底层硬件的 串口层,该层负责数据的设备之间的交互及一系列管理操作的具体实现。

从上往下 看,驱动主要由几个数据结构组成,分别为 UART_driver、UART_state 和 tty_driver。UART_driver 是全局的 根数据结构,所有的结构体都由其来进行保存和控制。tty_driver 则是对 tty层的具体实现,最后 UART_state 和 Uart_pot 则是底层驱动的具体实现。

2.3 串口tty驱动

本节将对代码进行讲述,其中我们选择 8250 这个串口IP的代码作为示例来进行讲述。当然只是为了学习驱动的实现,其他的串口驱动读者可以按照自己的理解再去阅读。

本节会分别按照 初始化 和 读/写 来进行讲述。

2.3.1 串口硬件初始化

串口驱动的框架一大部分是在初始化阶段进行,对 初始化 进行梳理是必要的阶段。下面先来看看代码调用图谱。

首先是对 UART_driver结构体 进行创建和相关初始化,其负责高度的软件抽象和相关软件逻辑。其代码调用图谱如下:

->serial8250_init

->serial8250_isa_init_ports(初始化serial8250_ports,数据类型为uart_8250_port)

->serial8250_init_port(将serial8250_pops赋值给uart_port->ops)

->univ8250_rsa_support

->uart_register_driver(注册uart_driver,即全局变量serial8250_reg,将uart_ops设置给tty_driver)

->alloc_tty_driver(分配uart_driver->uart_state)

->tty_port_init(初始化uart_driver->uart_state->tty_port,tty_port的操作集为uart_port_ops)

->tty_register_driver(注册tty_driver,将tty_driver加入全局链表tty_drivers)

/* 8250_core */

/* 初始化8250串口框架 */

module_init(serial8250_init);

static int __init serial8250_init(void)

{

int ret;

......

/* 初始化各个串口的UART_port数据结构 */

serial8250_isa_init_ports();

......

/* 注册UART驱动即UART_driver */

serial8250_reg.nr = UART_NR;

ret = uart_register_driver(&serial8250_reg);

......

serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

......

}

/* 初始化各个串口的UART_port数据结构 */

static void __init serial8250_isa_init_ports(void)

{

struct uart_8250_port *up;

static int first = 1;

int i, irqflag = 0;

......

/* 初始化每个UART_port的操作函数集 */

for (i = 0; i < nr_uarts; i++) {

struct uart_8250_port *up = &serial8250_ports[i];

struct uart_port *port = &up->port;

/* 设置当前UART_port的编号 */

port->line = i;

/* 初始化UART_port的ops成员 */

serial8250_init_port(up);

{

struct uart_port *port = &up->port;

port->ops = &serial8250_pops;

......

}

/* 此时port->ops为serial8250_pops,所以base_ops指向了serial8250_pops */

if (!base_ops)

base_ops = port->ops;

/* 将UART_port的ops成员指向univ8250_port_ops */

port->ops = &univ8250_port_ops;

......

/* 设置uart_8250_port的ops成员指向了univ8250_driver_ops,该成员在设置中断时有用到 */

up->ops = &univ8250_driver_ops;

......

}

/*

univ8250_port_ops的成员赋值为*base_ops指向的地址,即serial8250_pops

所以此时UART_port的ops成员指向serial8250_pops

*/

univ8250_port_ops = *base_ops;

/* 设置UART_port->ops的部分成员 */

univ8250_rsa_support(&univ8250_port_ops);

{

ops->config_port = univ8250_config_port;

ops->request_port = univ8250_request_port;

ops->release_port = univ8250_release_port;

}

......

}

/* 注册UART_driver驱动 */

int uart_register_driver(struct uart_driver *drv)

{

struct tty_driver *normal;

int i, retval;

/* 为UART_driver的每个UART_state分配空间,每个串口硬件都有UART_state */

drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

......

/* 为串口驱动UART_driver分配一个tty_driver */

normal = alloc_tty_driver(drv->nr);

{

struct tty_driver *ret = tty_alloc_driver(lines, 0);

......

return ret;

}

......

/* 设置UART_driver的tty_driver成员 */

drv->tty_driver = normal;

/* 初始化相关成员 */

normal->driver_name = drv->driver_name;

normal->name = drv->dev_name;

/* 这里的设备号会在后续的open过程中使用到 */

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_driver的ops成员指向struct tty_operations uart_ops */

tty_set_operations(normal, &uart_ops);

{

driver->ops = op;

};

/* 初始化每个UART_state的tty_port成员 */

for (i = 0; i < drv->nr; i++) {

struct uart_state *state = drv->state + i;

struct tty_port *port = &state->port;

/* 初始化每个tty_port */

tty_port_init(port);

{

memset(port, 0, sizeof(*port));

tty_buffer_init(port);

/* 以下为tty_buffer_init函数体 */

{

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_queue,后面的读取数据的时候要用到

buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;

}

init_waitqueue_head(&port->open_wait);

init_waitqueue_head(&port->delta_msr_wait);

mutex_init(&port->mutex);

mutex_init(&port->buf_mutex);

spin_lock_init(&port->lock);

port->close_delay = (50 * HZ) / 100;

port->closing_wait = (3000 * HZ) / 100;

/* 设置tty_port的client_ops指向default_client_ops,会在接收和发送数据时使用到 */

port->client_ops = &default_client_ops;

kref_init(&port->kref);

}

/* 设置tty_port的ops成员指向uart_port_ops */

port->ops = &uart_port_ops;

}

retval = tty_register_driver(normal);

......

}

/* 注册tty_driver驱动 */

int tty_register_driver(struct tty_driver *driver)

{

int error;

int i;

dev_t dev;

struct device *d;

/* 分配设备好 */

if (!driver->major) {

error = alloc_chrdev_region(&dev, driver->minor_start,

driver->num, driver->name);

if (!error) {

driver->major = MAJOR(dev);

driver->minor_start = MINOR(dev);

}

} else {

dev = MKDEV(driver->major, driver->minor_start);

error = register_chrdev_region(dev, driver->num, driver->name);

}

if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {

/* 添加tty的字符设备 */

error = tty_cdev_add(driver, dev, 0, driver->num);

{

int err;

driver->cdevs[index] = cdev_alloc();

......

/* 设置字符设备的操作集为tty_fops,该操作集用于tty设备和应用层交互 */

driver->cdevs[index]->ops = &tty_fops;

driver->cdevs[index]->owner = driver->owner;

/* 添加字符设备 */

err = cdev_add(driver->cdevs[index], dev, count);

......

return err;

}

......

}

/* 将当前的tty_driver添加全局的tty_drivers链表 */

mutex_lock(&tty_mutex);

list_add(&driver->tty_drivers, &tty_drivers);

mutex_unlock(&tty_mutex);

/* 注册每一个tty设备 */

if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {

for (i = 0; i < driver->num; i++) {

d = tty_register_device(driver, i, NULL);

......

}

}

}

......

return 0;

......

}

上面是针对 8250串口核心 做的一些基本的初始化。按照笔者理解,在实际应用中用户有可能需要加载其他串口驱动来修改串口的一些属性,所以当用户执行驱动装载时有以下的初始化步骤,同样也是针对 UART_port 和 UART_state 的初始化和操作。其调用谱图和代码示例如下:

dw8250_probe

->serial8250_register_8250_port

->serial8250_find_match_or_unused(从全局serial8250_ports找出可以使用的元素,该元素可以理解为uart_port)

->uart_add_one_port(填充uart_port)

->tty_port_register_device_attr_serdev

->tty_port_link_device

->tty_register_device_attr

->tty_cdev_add(将tty_ops赋给tty_driver的cdec成员)

/* 初始化用户的串口硬件模块 */

static int dw8250_probe(struct platform_device *pdev)

{

struct uart_8250_port uart = {};

struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);

int irq = platform_get_irq(pdev, 0);

struct uart_port *p = &uart.port;

struct device *dev = &pdev->dev;

struct dw8250_data *data;

int err;

u32 val;

......

/* 对p指向uart_port结构体进行初始化,根据用户自己的进行设置 */

spin_lock_init(&p->lock);

p->mapbase = regs->start;

p->irq = irq; //串口中断号

p->handle_irq = dw8250_handle_irq; //串口中断处理函数,会在串口中断时执行

p->pm = dw8250_do_pm;

p->type = PORT_8250;

p->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT;

p->dev = dev;

p->iotype = UPIO_MEM;

p->serial_in = dw8250_serial_in; //串口输入寄存器设置函数

p->serial_out = dw8250_serial_out; //串口输出寄存器设置函数

p->set_ldisc = dw8250_set_ldisc; //串口线路规程设置函数

p->set_termios = dw8250_set_termios; //串口中断参数规程设置函数

/* 设置寄存器地址 */

p->membase = devm_ioremap(dev, regs->start, resource_size(regs));

......

/* 分配私有数据 */

data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);

......

data->dma.fn = dw8250_fallback_dma_filter;

data->usr_reg = DW_UART_USR;

p->private_data = data;

......

/* 注册用户自己的串口驱动 */

data->line = serial8250_register_8250_port(&uart);

if (data->line < 0) {

err = data->line;

goto err_reset;

}

......

}

/* 注册用户的串口驱动 */

int serial8250_register_8250_port(struct uart_8250_port *up)

{

struct uart_8250_port *uart;

int ret = -ENOSPC;

......

mutex_lock(&serial_mutex);

/* 从8250_core中找出一个没有用且匹配的uart_8250_port */

uart = serial8250_find_match_or_unused(&up->port);

/* 对uart_8250_port 进行初始化 */

if (uart && uart->port.type != PORT_8250_CIR) {

......

/* 初始化找到的uart_port数据结构uart->port为uart_port数据结构 */

uart->port.iobase = up->port.iobase;

uart->port.membase = up->port.membase;

uart->port.irq = up->port.irq;

uart->port.irqflags = up->port.irqflags;

uart->port.uartclk = up->port.uartclk;

uart->port.fifosize = up->port.fifosize;

uart->port.regshift = up->port.regshift;

uart->port.iotype = up->port.iotype;

uart->port.flags = up->port.flags | UPF_BOOT_AUTOCONF;

uart->bugs = up->bugs;

uart->port.mapbase = up->port.mapbase;

uart->port.mapsize = up->port.mapsize;

uart->port.private_data = up->port.private_data;

uart->tx_loadsz = up->tx_loadsz;

uart->capabilities = up->capabilities;

uart->port.throttle = up->port.throttle;

uart->port.unthrottle = up->port.unthrottle;

uart->port.rs485_config = up->port.rs485_config;

uart->port.rs485 = up->port.rs485;

uart->dma = up->dma;

if (uart->port.fifosize && !uart->tx_loadsz)

uart->tx_loadsz = uart->port.fifosize;

if (up->port.dev)

uart->port.dev = up->port.dev;

if (up->port.flags & UPF_FIXED_TYPE)

uart->port.type = up->port.type;

/* 设置uart_port的默认属性 */

serial8250_set_defaults(uart);

if (up->port.serial_in)

uart->port.serial_in = up->port.serial_in;

if (up->port.serial_out)

uart->port.serial_out = up->port.serial_out;

if (up->port.handle_irq)

uart->port.handle_irq = up->port.handle_irq;

if (up->port.set_termios)

uart->port.set_termios = up->port.set_termios;

if (up->port.set_ldisc)

uart->port.set_ldisc = up->port.set_ldisc;

if (up->port.get_mctrl)

uart->port.get_mctrl = up->port.get_mctrl;

if (up->port.set_mctrl)

uart->port.set_mctrl = up->port.set_mctrl;

if (up->port.startup)

uart->port.startup = up->port.startup;

if (up->port.shutdown)

uart->port.shutdown = up->port.shutdown;

if (up->port.pm)

uart->port.pm = up->port.pm;

if (up->port.handle_break)

uart->port.handle_break = up->port.handle_break;

if (up->dl_read)

uart->dl_read = up->dl_read;

if (up->dl_write)

uart->dl_write = up->dl_write;

if (uart->port.type != PORT_8250_CIR) {

if (serial8250_isa_config != NULL)

serial8250_isa_config(0, &uart->port,

&uart->capabilities);

.....

/* 将新的uart_port添加到uart_driver */

ret = uart_add_one_port(&serial8250_reg, &uart->port);

if (ret == 0)

ret = uart->port.line;

} else {

dev_info(uart->port.dev,

"skipping CIR port at 0x%lx / 0x%llx, IRQ %d\n",

uart->port.iobase,

(unsigned long long)uart->port.mapbase,

uart->port.irq);

ret = 0;

}

}

mutex_unlock(&serial_mutex);

return ret;

}

/* 把一个uart_port添加到uart_driver */

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)

{

struct uart_state *state;

struct tty_port *port;

int ret = 0;

struct device *tty_dev;

int num_groups;

/* 根据串口编号获取对应的uart_state和uart_port */

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

port = &state->port;

......

/* 设置新的uart_port到uart_state中,并将对uart_port的state成员进行赋值 */

state->uart_port = uport;

uport->state = state;

......

/* 分配并设置属性组 */

uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),

GFP_KERNEL);

if (!uport->tty_groups) {

ret = -ENOMEM;

goto out;

}

uport->tty_groups[0] = &tty_dev_attr_group;

if (uport->attr_group)

uport->tty_groups[1] = uport->attr_group;

/* 将新的uart_port及其属性注册到内核中 */

tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,

uport->line, uport->dev, port, uport->tty_groups);

......

}

struct device *tty_port_register_device_attr_serdev(struct tty_port *port,

struct tty_driver *driver, unsigned index,

struct device *device, void *drvdata,

const struct attribute_group **attr_grp)

{

struct device *dev;

/* 该函数设置tty_drvier的tty_port成员,建立联系 */

tty_port_link_device(port, driver, index);

{

if (WARN_ON(index >= driver->num))

return;

driver->ports[index] = port;

}

/* 该函数会设置tty_port的一部分成员 */

dev = serdev_tty_port_register(port, device, driver, index);

{

/* 设置tty_port的client_ops成员 */

port->client_ops = &client_ops;

}

/* 注册tty_port对应的device */

return tty_register_device_attr(driver, index, device, drvdata,

attr_grp);

}

......

struct device *tty_register_device_attr(struct tty_driver *driver,

unsigned index, struct device *device,

void *drvdata,

const struct attribute_group **attr_grp)

{

char name[64];

dev_t devt = MKDEV(driver->major, driver->minor_start) + index;

struct ktermios *tp;

struct device *dev;

int retval;

......

dev = kzalloc(sizeof(*dev), GFP_KERNEL);

......

/* 设置dev的相关属性 */

dev->devt = devt;

dev->class = tty_class;

dev->parent = device;

dev->release = tty_device_create_release;

dev_set_name(dev, "%s", name);

dev->groups = attr_grp;

dev_set_drvdata(dev, drvdata);

......

/* 注册dev */

retval = device_register(dev);

if (retval)

goto err_put;

if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {

......

/* 添加tty_driver的cdev,该函数在前面已经出现过,这里的cdev是对应新的tty_port */

retval = tty_cdev_add(driver, devt, index, 1);

if (retval)

goto err_del;

}

......

}

前面我们简单的过了一遍 8250串口核心 和 8250串口硬件 的初始化流程。在初始化以后,数据结构就如 串口tty驱动框架图 所示。当然以上内容删减了许多细节,有兴趣的读者可以阅读源码加深理解。

2.3.2 串口使用流程

相比于 初始化,理解 串口读取 的难度会更加高一些,以为读写过程会更加复杂,希望笔者的一点笔墨可以帮助大家稍微理解一下其过程。

这里需要先知道,串口读取 一般是由 2 个线程来完成:

前台线程:即用户在应用空间使用 read 等系统调用的线程,该线程在进入内核空间会执行相关的函数进行等待。当时机成熟时由 后台线程 进行唤醒并读取相关数据到应用恐案件。

后台线程:由 串口驱动 在初始化时会定义一个线程,由该线程在中断有数据时进行读写。并把读取到的数据存放在某个缓存中,最后会唤醒 前台线程 去读取这个缓存中的数据。

在讲解线程的执行之前需要知道 中断的初始化及其处理函数,因为 串口读取 与 中断 密不可分。下面会先讲述 中断初始化及其处理,然后再讲述 读取的前台及后台线程。

2.3.2.1 打开串口

串口中断 的设置是在串口打开时设置的,所以我们直接查看串口打开时的调用图谱及代码即可知道相关内容,如下所示:

tty_open(struct file_operations tty_fops)

/* 申请并初始化相关结构体 */

->tty_open_by_driver

->tty_init_dev

->alloc_tty_struct

->tty_ldisc_init(设置默认线路规程为N_TTY)

/* 执行open操作,比如设置寄存器或者中断等 */

->uart_open

->tty_port_open

->uart_port_activate

->uart_startup

->uart_port_startup

->serial8250_startup

->serial8250_do_startup

->setup_irq(univ8250_setup_irq)

->serial_link_irq_chain

->request_irq(serial8250_interrupt)

可以看到,打开的流程很长,其调用图谱也非常的深,我们逐步看一下代码吧

/* 在前面的章节中可以看到,tty_open是tty设备使用字符设备cdev对外的接口,当我们打开 */

static int tty_open(struct inode *inode, struct file *filp)

{

struct tty_struct *tty;

int noctty, retval;

dev_t device = inode->i_rdev;

unsigned saved_flags = filp->f_flags;

......

/* 分配一个tty_file_private结构体并赋值给文件的private_data成员 */

retval = tty_alloc_file(filp);

{

struct tty_file_private *priv;

priv = kmalloc(sizeof(*priv), GFP_KERNEL);

......

file->private_data = priv;

return 0;

}

......

/*

根据内核注释,tty_open_current_tty用于获取当前tty设备的锁,但从函数命名和返回值来看这应该是另一种说法

根据笔者理解,这里应该获取当前tty设备的tty_struct结构体

如果获取失败则表明当前的tty设备没有打开,需要调用tty_open_by_driver重新打开

*/

tty = tty_open_current_tty(device, filp);

if (!tty)

tty = tty_open_by_driver(device, inode, filp);

......

/* 将当前文件的私有数据即tty_file_private成员进行填充,再将其链入tty_stuct的tty_files链表 */

tty_add_file(tty, filp);

{

struct tty_file_private *priv = file->private_data;

priv->tty = tty;

priv->file = file;

spin_lock(&tty->files_lock);

list_add(&priv->list, &tty->tty_files);

spin_unlock(&tty->files_lock);

}

......

/* tty_struct结构题的ops成员(sturct tty_operations)中的open函数,即uart_open */

if (tty->ops->open)

retval = tty->ops->open(tty, filp);

else

retval = -ENODEV;

filp->f_flags = saved_flags;

......

}

static struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,

struct file *filp)

{

struct tty_struct *tty;

struct tty_driver *driver = NULL;

int index = -1;

int retval;

mutex_lock(&tty_mutex);

/* 根据设备好找到对应的tty_driver */

driver = tty_lookup_driver(device, filp, &index);

......

/* 检查我们是否重复打开tty */

tty = tty_driver_lookup_tty(driver, filp, index);

......

/* 如果重复打开则执行该分支 */

if (tty) {

if (tty_port_kopened(tty->port)) {

tty_kref_put(tty);

mutex_unlock(&tty_mutex);

tty = ERR_PTR(-EBUSY);

goto out;

}

mutex_unlock(&tty_mutex);

retval = tty_lock_interruptible(tty);

tty_kref_put(tty); /* drop kref from tty_driver_lookup_tty() */

if (retval) {

if (retval == -EINTR)

retval = -ERESTARTSYS;

tty = ERR_PTR(retval);

goto out;

}

retval = tty_reopen(tty);

if (retval < 0) {

tty_unlock(tty);

tty = ERR_PTR(retval);

}

} else {

/* 没有重复打开则初始化出一个tty_struct */

tty = tty_init_dev(driver, index);

mutex_unlock(&tty_mutex);

}

......

return tty;

}

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)

{

struct tty_struct *tty;

int retval;

......

/* 分配tty_struct结构体并初始化,该函数比较重要 */

......

tty_lock(tty);

/* 该函数主要讲tty_struct的地址链接到tty_driver的成员中 */

retval = tty_driver_install_tty(driver, tty);

if (retval < 0)

goto err_free_tty;

/* 复制tty_struct中的tty_port成员 */

if (!tty->port)

tty->port = driver->ports[idx];

......

tty->port->itty = tty;

/* 调用线程规程并打开 */

retval = tty_ldisc_setup(tty, tty->link);

/* 以下为tty_ldisc_setup函数体 */

{

int retval = tty_ldisc_open(tty, tty->ldisc);

if (retval)

return retval;

if (o_tty) {

retval = tty_ldisc_open(o_tty, o_tty->ldisc);

if (retval) {

tty_ldisc_close(tty, tty->ldisc);

return retval;

}

}

return 0;

}

......

/* Return the tty locked so that it cannot vanish under the caller */

return tty;

......

}

/* 分配并初始化tty_struct */

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

/* 以下为tty_ldisc_init函数体 */

{

/* 线路规程初始化为N_TTY */

struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY);

if (IS_ERR(ld))

panic("n_tty: init_tty");

tty->ldisc = ld;

}

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

spin_lock_init(&tty->files_lock);

INIT_LIST_HEAD(&tty->tty_files);

INIT_WORK(&tty->SAK_work, do_SAK_work);

tty->driver = driver;

tty->ops = driver->ops;

tty->index = idx;

tty_line_name(driver, idx, tty->name);

tty->dev = tty_get_device(tty);

return tty;

}

static int uart_open(struct tty_struct *tty, struct file *filp)

{

struct uart_driver *drv = tty->driver->driver_state;

int retval, line = tty->index;

struct uart_state *state = drv->state + line;

/* 设置tty_struct的driver_data成员为uart_state */

tty->driver_data = state;

/* 打开tty_port */

retval = tty_port_open(&state->port, tty, filp);

if (retval > 0)

retval = 0;

return retval;

}

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

/* 只有在硬件没有初始化好的情况才能打开设备 */

mutex_lock(&port->mutex);

/* 检测tty_port是否初始化过 */

if (!tty_port_initialized(port)) {

clear_bit(TTY_IO_ERROR, &tty->flags);

if (port->ops->activate) {

/* 执行tty_port的activate方法,即uart_port_ops中的uart_port_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);

}

/* 使能uart_port */

static int uart_port_activate(struct tty_port *port, struct tty_struct *tty)

{

struct uart_state *state = container_of(port, struct uart_state, port);

struct uart_port *uport;

......

/* 启动uart串口 */

return uart_startup(tty, state, 0);

}

/* 启动串口 */

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;

/* 执行uart_port_startup */

retval = uart_port_startup(tty, state, init_hw);

......

return retval;

}

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 (!state->xmit.buf) {

/* 如果没有则获取并赋值 */

page = get_zeroed_page(GFP_KERNEL);

if (!page)

return -ENOMEM;

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

uart_circ_clear(&state->xmit);

}

/* 执行uart_port的startup函数,即serial8250_pops的serial8250_startup */

retval = uport->ops->startup(uport);

/* 以下为serial8250_startup 函数体 */

{

if (port->startup)

return port->startup(port);

return serial8250_do_startup(port);

}

if (retval == 0) {

......

/* 初始化串口的硬件设置 */

uart_change_speed(tty, state, NULL);

/* 设置串口的RTS和DTR信号 */

if (init_hw && C_BAUD(tty))

uart_port_dtr_rts(uport, 1);

}

......

return retval;

}

int serial8250_do_startup(struct uart_port *port)

{

struct uart_8250_port *up = up_to_u8250p(port);

unsigned long flags;

unsigned char lsr, iir;

int retval;

/* 省略的部分语句为根据端口类型设置硬件寄存器,不在本文的讲述范围内,有兴趣的读者可以阅读源码 */

......

/*

* For the Altera 16550 variants, set TX threshold trigger level.

*/

if (((port->type == PORT_ALTR_16550_F32) ||

(port->type == PORT_ALTR_16550_F64) ||

(port->type == PORT_ALTR_16550_F128)) && (port->fifosize > 1)) {

/* Bounds checking of TX threshold (valid 0 to fifosize-2) */

if ((up->tx_loadsz < 2) || (up->tx_loadsz > port->fifosize)) {

pr_err("ttyS%d TX FIFO Threshold errors, skipping\n",

serial_index(port));

} else {

serial_port_out(port, UART_ALTR_AFR,

UART_ALTR_EN_TXFIFO_LW);

serial_port_out(port, UART_ALTR_TX_LOW,

port->fifosize - up->tx_loadsz);

port->handle_irq = serial8250_tx_threshold_handle_irq;

}

}

if (port->irq && !(up->port.flags & UPF_NO_THRE_TEST)) {

unsigned char iir1;

/*

当发送器是空闲并且中断已经被清除时,需要测试UART不要让它再次声明发送中断。

实际上当发送器是空闲且允许中断时,16500 应该总是重新声明该中断。

为了让改变寄存器生效,需要加入必要的延时。

*/

/* 关闭中断 */

spin_lock_irqsave(&port->lock, flags);

if (up->port.irqflags & IRQF_SHARED)

disable_irq_nosync(port->irq);

/* 设置寄存器 */

wait_for_xmitr(up, UART_LSR_THRE);

serial_port_out_sync(port, UART_IER, UART_IER_THRI);

/* 延迟并等待寄存器生效 */

udelay(1);

/* 设置寄存器 */

iir1 = serial_port_in(port, UART_IIR);

serial_port_out(port, UART_IER, 0);

serial_port_out_sync(port, UART_IER, UART_IER_THRI);

/* 延迟并等待寄存器生效 */

udelay(1);

iir = serial_port_in(port, UART_IIR);

serial_port_out(port, UART_IER, 0);

/* 使能中断 */

if (port->irqflags & IRQF_SHARED)

enable_irq(port->irq);

spin_unlock_irqrestore(&port->lock, flags);

/*

* If the interrupt is not reasserted, or we otherwise

* don't trust the iir, setup a timer to kick the UART

* on a regular basis.

*/

if ((!(iir1 & UART_IIR_NO_INT) && (iir & UART_IIR_NO_INT)) ||

up->port.flags & UPF_BUG_THRE) {

up->bugs |= UART_BUG_THRE;

}

}

/* 调用uart_8250_port结构体中的setup_irq方法,即univ8250_driver_ops中的univ8250_setup_irq */

retval = up->ops->setup_irq(up);

if (retval)

goto out;

/* 初始化相关的硬件寄存器 */

......

}

/* 设置中断 */

static int univ8250_setup_irq(struct uart_8250_port *up)

{

struct uart_port *port = &up->port;

int retval = 0;

......

if (!port->irq) {

} else

/* 如果设置了中断号,则执行serial_link_irq_chain */

retval = serial_link_irq_chain(up);

return retval;

}

static int serial_link_irq_chain(struct uart_8250_port *up)

{

struct hlist_head *h;

struct hlist_node *n;

struct irq_info *i;

int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0;

......

if (i->head) {

......

} else {

......

/* 申请中断 */

ret = request_irq(up->port.irq, serial8250_interrupt,

irq_flags, up->port.name, i);

if (ret < 0)

serial_do_unlink(i, up);

}

return ret;

}

总结而言,open流程 可以分为 2个部分:

申请并初始化结构体,比如 tty_struct

设置相关寄存器和初始化中断

2.3.2.1 打开串口

上面讲述了串口的 open流程,完成了中断设置和寄存器设置。此时我们的应用线程开始读取串口数据。那么读取流程需要分开 2 个部分来讲述:

中断后台进程:负责从硬件读取数据存放到 buffer 中,并通过机制通知 用户前台进程 获取数据。

用户前台进程:负责等待接收 buffer 中的数据

下面先讲述 后台中断读取,其调用图谱如下:

/* 中断设置,该部分以前在前文中讲过,不再赘述 */

serial8250_init

->serial8250_isa_init_ports(up->ops = &univ8250_driver_ops;)

/* 中断处理函数 */

serial8250_interrupt

->handle_irq(dw8250_handle_irq,是在dw8250_probe设置的)

->serial8250_handle_irq

->serial8250_rx_chars

|->serial8250_read_char//从硬件中读取数据到tty_buffer中

| ->uart_insert_char

| ->tty_insert_flip_char

| ->__tty_insert_flip_char

| ->__tty_buffer_request_room

| ->tty_buffer_alloc

|->tty_flip_buffer_push//将tty_buffer中数据投放到线路规程层(ldsc)的缓冲区中

| ->tty_schedule_flip

| ->flush_to_ldisc

| ->receive_buf

| ->tty_port_default_receive_buf(tty_port)

| ->tty_ldisc_receive_buf

| ->n_tty_receive_buf

下面直接看中断代码:

static irqreturn_t serial8250_interrupt(int irq, void *dev_id)

{

struct irq_info *i = dev_id;

struct list_head *l, *end = NULL;

int pass_counter = 0, handled = 0;

......

l = i->head;

/* 轮眉每个串口的中断函数 */

do {

struct uart_8250_port *up;

struct uart_port *port;

up = list_entry(l, struct uart_8250_port, list);

port = &up->port;

/* 执行中断函数,也就是dw8250_handle_irq,该函数在dw8250_probe设置 */

if (port->handle_irq(port)) {

handled = 1;

end = NULL;

} else if (end == NULL)

end = l;

l = l->next;

......

} while (l != end);

......

return IRQ_RETVAL(handled);

}

static int dw8250_handle_irq(struct uart_port *p)

{

struct uart_8250_port *up = up_to_u8250p(p);

struct dw8250_data *d = p->private_data;

unsigned int iir = p->serial_in(p, UART_IIR);

unsigned int status;

unsigned long flags;

......

if (serial8250_handle_irq(p, iir))

return 1;

......

return 0;

}

int serial8250_handle_irq(struct uart_port *port, unsigned int iir)

{

unsigned char status;

unsigned long flags;

struct uart_8250_port *up = up_to_u8250p(port);

......

if (status & (UART_LSR_DR | UART_LSR_BI)) {

if (!up->dma || handle_rx_dma(up, iir))

/* 从串口中读取字节 */

status = serial8250_rx_chars(up, status);

}

......

return 1;

}

unsigned char serial8250_rx_chars(struct uart_8250_port *up, unsigned char lsr)

{

struct uart_port *port = &up->port;

int max_count = 256;

do {

/* 循环读取字节到tty_buffer中,该buffer位于uart_port数据结构中 */

serial8250_read_char(up, lsr);

if (--max_count == 0)

break;

lsr = serial_in(up, UART_LSR);

} while (lsr & (UART_LSR_DR | UART_LSR_BI));

tty_flip_buffer_push(&port->state->port);

return lsr;

}

static void serial8250_read_char(struct uart_8250_port *up, unsigned char lsr)

{

struct uart_port *port = &up->port;

unsigned char ch;

char flag = TTY_NORMAL;

if (likely(lsr & UART_LSR_DR))

/* 读取串口接收到的字节 */

ch = serial_in(up, UART_RX);

......

/* 将数据放入tty_buffer中 */

uart_insert_char(port, lsr, UART_LSR_OE, ch, flag);

}

void uart_insert_char(struct uart_port *port, unsigned int status,

unsigned int overrun, unsigned int ch, unsigned int flag)

{

struct tty_port *tport = &port->state->port;

if ((status & port->ignore_status_mask & ~overrun) == 0)

/* 将字节ch保存到tty_buffer中 */

if (tty_insert_flip_char(tport, ch, flag) == 0)

++port->icount.buf_overrun;

if (status & ~port->ignore_status_mask & overrun)

/* 将字节ch保存到tty_buffer中 */

if (tty_insert_flip_char(tport, 0, TTY_OVERRUN) == 0)

++port->icount.buf_overrun;

}

static inline int tty_insert_flip_char(struct tty_port *port,

unsigned char ch, char flag)

{

/* 获取tty_buffer的尾部 */

struct tty_buffer *tb = port->buf.tail;

int change;

......

if (!change && tb->used < tb->size) {

if (~tb->flags & TTYB_NORMAL)

*flag_buf_ptr(tb, tb->used) = flag;

/* 如果tty_buffer中还有空间,则将字节ch插入tty_buffer中尾部中并返回 */

*char_buf_ptr(tb, tb->used++) = ch;

return 1;

}

/* 如果没有空间则执行__tty_insert_flip_char */

return __tty_insert_flip_char(port, ch, flag);

}

int __tty_insert_flip_char(struct tty_port *port, unsigned char ch, char flag)

{

struct tty_buffer *tb;

int flags = (flag == TTY_NORMAL) ? TTYB_NORMAL : 0;

/* 开辟tty_buffer的空间 */

if (!__tty_buffer_request_room(port, 1, flags))

return 0;

/* 将字节ch插入刚刚开辟的tty_buffer中 */

tb = port->buf.tail;

if (~tb->flags & TTYB_NORMAL)

*flag_buf_ptr(tb, tb->used) = flag;

*char_buf_ptr(tb, tb->used++) = ch;

return 1;

}

static int __tty_buffer_request_room(struct tty_port *port, size_t size,

int flags)

{

struct tty_bufhead *buf = &port->buf;

struct tty_buffer *b, *n;

int left, change;

/* 获取剩余空间 */

b = buf->tail;

if (b->flags & TTYB_NORMAL)

left = 2 * b->size - b->used;

else

left = b->size - b->used;

change = (b->flags & TTYB_NORMAL) && (~flags & TTYB_NORMAL);

if (change || left < size) {

/* 开辟tty_buffer的空间 */

n = tty_buffer_alloc(port, size);

if (n != NULL) {

n->flags = flags;

/* 设置缓冲区到尾部 */

buf->tail = n;

smp_store_release(&b->commit, b->used);

smp_store_release(&b->next, n);

} else if (change)

size = 0;

else

size = left;

}

return size;

}

static struct tty_buffer *tty_buffer_alloc(struct tty_port *port, size_t size)

{

struct llist_node *free;

struct tty_buffer *p;

/* 对齐缓冲区大小 */

size = __ALIGN_MASK(size, TTYB_ALIGN_MASK);

if (size <= MIN_TTYB_SIZE) {

free = llist_del_first(&port->buf.free);

if (free) {

p = llist_entry(free, struct tty_buffer, free);

goto found;

}

}

......

/* 分配缓冲区 */

p = kmalloc(sizeof(struct tty_buffer) + 2 * size, GFP_ATOMIC);

if (p == NULL)

return NULL;

found:

tty_buffer_reset(p, size);

atomic_add(size, &port->buf.mem_used);

return p;

}

到了这里,我们基本上就完成了 数据到tty_buffer 的填充,接下来我们就需要使用 tty_flip_buffer_push 唤醒 前台线程,下面为代码实例:

void tty_flip_buffer_push(struct tty_port *port)

{

tty_schedule_flip(port);

}

void tty_schedule_flip(struct tty_port *port)

{

struct tty_bufhead *buf = &port->buf;

/* paired w/ acquire in flush_to_ldisc(); ensures

* flush_to_ldisc() sees buffer data.

*/

smp_store_release(&buf->tail->commit, buf->tail->used);

/* 唤醒workqueue执行处理,即调用flush_to_ldisc,该函数在tty_buffer_init时初始化 */

queue_work(system_unbound_wq, &buf->work);

}

static void flush_to_ldisc(struct work_struct *work)

{

struct tty_port *port = container_of(work, struct tty_port, buf.work);

struct tty_bufhead *buf = &port->buf;

mutex_lock(&buf->lock);

while (1) {

struct tty_buffer *head = buf->head;

struct tty_buffer *next;

int count;

.....

/* 判断是否有数据可读 */

next = smp_load_acquire(&head->next);

count = smp_load_acquire(&head->commit) - head->read;

if (!count) {

if (next == NULL)

break;

buf->head = next;

tty_buffer_free(port, head);

continue;

}

/* 接收数据 */

count = receive_buf(port, head, count);

if (!count)

break;

head->read += count;

}

mutex_unlock(&buf->lock);

}

static int receive_buf(struct tty_port *port, struct tty_buffer *head, int count)

{

/* 获取tty_buffer的缓冲区地址 */

unsigned char *p = char_buf_ptr(head, head->read);

char *f = NULL;

if (~head->flags & TTYB_NORMAL)

f = flag_buf_ptr(head, head->read);

/*

这里执行的是tty_port的client_ops操作集

其初始化是在函数tty_port_init中进行初始化,该回调的函数名为tty_port_default_receive_buf

*/

return port->client_ops->receive_buf(port, p, f, count);

}

static int tty_port_default_receive_buf(struct tty_port *port,

const unsigned char *p,

const unsigned char *f, size_t count)

{

int ret;

struct tty_struct *tty;

struct tty_ldisc *disc;

......

/* 使用线路规程的接收方法对数据进行接收 */

ret = tty_ldisc_receive_buf(disc, p, (char *)f, count);

tty_ldisc_deref(disc);

return ret;

}

int tty_ldisc_receive_buf(struct tty_ldisc *ld, const unsigned char *p,

char *f, int count)

{

/*

使用线路规程的接收方法,前面讲说初始化使用的是ntty作为默认的线路规程

所以这里的回调receive_buf2为n_tty_receive_buf2函数,执行该函数接收数据

请助理,这里p是存放有接收数据的缓冲区

*/

if (ld->ops->receive_buf2)

count = ld->ops->receive_buf2(ld->tty, p, f, count);

else {

count = min_t(int, count, ld->tty->receive_room);

if (count && ld->ops->receive_buf)

ld->ops->receive_buf(ld->tty, p, f, count);

}

return count;

}

static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,

char *fp, int count)

{

return n_tty_receive_buf_common(tty, cp, fp, count, 1);

}

static int n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,

char *fp, int count, int flow)

{

struct n_tty_data *ldata = tty->disc_data;

int room, n, rcvd = 0, overflow;

......

while (1) {

......

/* 在这里对数据进行接收 */

/* ignore parity errors if handling overflow */

if (!overflow || !fp || *fp != TTY_PARITY)

__receive_buf(tty, cp, fp, n);

cp += n;

if (fp)

fp += n;

count -= n;

rcvd += n;

}

tty->receive_room = room;

/* Unthrottle if handling overflow on pty */

if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {

if (overflow) {

tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);

tty_unthrottle_safe(tty);

__tty_set_flow_change(tty, 0);

}

} else

n_tty_check_throttle(tty);

up_read(&tty->termios_rwsem);

return rcvd;

}

static void __receive_buf(struct tty_struct *tty, const unsigned char *cp,

char *fp, int count)

{

struct n_tty_data *ldata = tty->disc_data;

bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty));

/*

这里判断分支比较多,需要根据不同的设置执行不同的接收函数。

不同的接收函数对数据的处理不同,但在最终都会调用put_tty_queue来投放数据

下面以n_tty_receive_char_fast函数为例子进行说明

*/

if (ldata->real_raw)

n_tty_receive_buf_real_raw(tty, cp, fp, count);

else if (ldata->raw || (L_EXTPROC(tty) && !preops))

n_tty_receive_buf_raw(tty, cp, fp, count);

else if (tty->closing && !L_EXTPROC(tty))

n_tty_receive_buf_closing(tty, cp, fp, count);

else {

if (ldata->lnext) {

char flag = TTY_NORMAL;

if (fp)

flag = *fp++;

n_tty_receive_char_lnext(tty, *cp++, flag);

count--;

}

if (!preops && !I_PARMRK(tty))

n_tty_receive_buf_fast(tty, cp, fp, count);

else

n_tty_receive_buf_standard(tty, cp, fp, count);

flush_echoes(tty);

/*

flush_chars回到调用的是uart_ops操作集的uart_flush_chars函数。

该函数使能串口发送应用程的数据

*/

if (tty->ops->flush_chars)

tty->ops->flush_chars(tty);

}

if (ldata->icanon && !L_EXTPROC(tty))

return;

/* publish read_head to consumer */

smp_store_release(&ldata->commit_head, ldata->read_head);

/* 唤醒前台进程来接收数据 */

if (read_cnt(ldata)) {

kill_fasync(&tty->fasync, SIGIO, POLL_IN);

wake_up_interruptible_poll(&tty->read_wait, POLLIN);

}

}

static void

n_tty_receive_buf_fast(struct tty_struct *tty, const unsigned char *cp,

char *fp, int count)

{

struct n_tty_data *ldata = tty->disc_data;

char flag = TTY_NORMAL;

/* 循环接收字节 */

while (count--) {

if (fp)

flag = *fp++;

if (likely(flag == TTY_NORMAL)) {

/* 从缓冲区中获取字节 */

unsigned char c = *cp++;

if (!test_bit(c, ldata->char_map))

/* 将接收到的字节投放到线路规程的数据缓冲区中 */

n_tty_receive_char_fast(tty, c);

......

}

}

}

static inline void n_tty_receive_char_fast(struct tty_struct *tty, unsigned char c)

{

struct n_tty_data *ldata = tty->disc_data;

......

/* 投送字节到线路规程的数据中 */

put_tty_queue(c, ldata);

}

static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata)

{

/* 将字节赋值给线路规程中的read_buf中 */

*read_buf_addr(ldata, ldata->read_head) = c;

/* 以下为read_buf_addr函数体 */

{

return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];

}

/* 记录缓冲区当前的头部下标 */

ldata->read_head++;

}

到了这里,数据的 中断后台接收流程 就已经完成了。这里我们还需要注意 前台接收进程 的流程,求该流程比较简单,相比于 后台接收流程 来了说调用图谱十分简洁。其代码实例和调用图谱如下:

tty_read

->n_tty_read

->canon_copy_from_read_buf

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;

......

/* 调用线路规程的read方法,同理这里使用的是ntty作用默认线路规程,所以其函数名为n_tty_read */

if (ld->ops->read)

i = ld->ops->read(tty, file, buf, count);

......

return i;

}

static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,

unsigned char __user *buf, size_t nr)

{

struct n_tty_data *ldata = tty->disc_data;

unsigned char __user *b = buf;

/* 声明一个等待队列实例 */

DEFINE_WAIT_FUNC(wait, woken_wake_function);

int c;

int minimum, time;

ssize_t retval = 0;

long timeout;

int packet;

size_t tail;

......

packet = tty->packet;

tail = ldata->read_tail;

/* 加入等待队列 */

add_wait_queue(&tty->read_wait, &wait);

while (nr) {

/* First test for status change. */

if (packet && tty->link->ctrl_status) {

unsigned char cs;

if (b != buf)

break;

spin_lock_irq(&tty->link->ctrl_lock);

cs = tty->link->ctrl_status;

tty->link->ctrl_status = 0;

spin_unlock_irq(&tty->link->ctrl_lock);

if (put_user(cs, b)) {

retval = -EFAULT;

break;

}

b++;

nr--;

break;

}

if (!input_available_p(tty, 0)) {

up_read(&tty->termios_rwsem);

tty_buffer_flush_work(tty->port);

down_read(&tty->termios_rwsem);

if (!input_available_p(tty, 0)) {

......

/* 如果当前由于某种原因不可读,则进程让出当前CPU,等待唤醒 */

timeout = wait_woken(&wait, TASK_INTERRUPTIBLE,

timeout);

down_read(&tty->termios_rwsem);

continue;

}

}

if (ldata->icanon && !L_EXTPROC(tty)) {

......

} else {

int uncopied;

if (packet && b == buf) {

if (put_user(TIOCPKT_DATA, b)) {

retval = -EFAULT;

break;

}

b++;

nr--;

}

/* 根据不同情况将数据投放到应用层 */

uncopied = copy_from_read_buf(tty, &b, &nr);

......

}

n_tty_check_unthrottle(tty);

if (b - buf >= minimum)

break;

if (time)

timeout = time;

}

if (tail != ldata->read_tail)

n_tty_kick_worker(tty);

up_read(&tty->termios_rwsem);

/* 移除出等待队列 */

remove_wait_queue(&tty->read_wait, &wait);

mutex_unlock(&ldata->atomic_read_lock);

if (b - buf)

retval = b - buf;

return retval;

}

static int copy_from_read_buf(struct tty_struct *tty,

unsigned char __user **b,

size_t *nr)

{

struct n_tty_data *ldata = tty->disc_data;

int retval;

size_t n;

bool is_eof;

size_t head = smp_load_acquire(&ldata->commit_head);

size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1);

retval = 0;

n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail);

n = min(*nr, n);

if (n) {

/* 获取当前数据包的所在地址 */

const unsigned char *from = read_buf_addr(ldata, tail);

/* 以下为read_buf_addr函数体 */

{

/* 获取线路规程中的读取buffer地址 */

return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];

}

/* 将地址拷贝到应用空间,其中b为应用空间buffer地址,from为串口接收数据包所在地址 */

retval = copy_to_user(*b, from, n);

n -= retval;

......

*b += n;

*nr -= n;

}

return retval;

}

到了这里,串口读流程 基本完成了。其 写流程 框架类似,但数据流相反,这里不做讲述,有兴趣的读者可以自行阅读源码理解。

三、结语

本文更新得有些许慢,主要是平时看代码写文章的时间少了,再加上 tty框架 复杂且代码多,所以阅读起来理解不是很容易。按照笔者理解,包括笔者在内大部分人一辈子都应该不会自己去写一个 串口或者tty驱动,但学习 串口和tty驱动 有助于理解和使用 线路规程,想 串口蓝牙 等使用方法相信不少人会遇到过,学习串口驱动框架有助于我们工作生活中开发和调试。

本文与该系列的其他文章类似,主要在于梳理驱动框架来帮助读者理解,而非讲述使用方法。所以对于很多不影响流程的细节都省略掉,笔者建议读者有条件的可以阅读一遍代码可以加深理解。如果本文有误或者有讲得不好的地方,请各位读者指出并海涵。

四、参考链接

linux内核调用串口,linux驱动之串口驱动框架相关推荐

  1. linux 内核配置mmc,Linux内核编译,配置本机驱动

    1.前言 编译Linux内核失败的原因很多时候就是驱动选错,适合自己本机的驱动没编译进去.面对特殊平台(或者有些洁癖者,我就是^_^),要编译精简内核,只要本机驱动,其他都不需要.面对内核里面这么多驱 ...

  2. linux 内核 网卡驱动 移植,Linux内核移植步骤_添加DM9000网卡驱动(设备树).docx

    Linux内核移植步骤_添加DM9000网卡驱动(设备树) Linux内核移植步骤2015年05月13日星期三上午 11:05往设备树中添加网卡驱动:1.选平台,指定交叉编译工具链:(1).在Make ...

  3. 跟踪 linux 内核调用_Linux用户和内核空间中的动态跟踪

    跟踪 linux 内核调用 您是否曾经遇到过这样的情况,即您意识到没有在代码中的某些点插入调试打印 ,所以现在您将不知道您的CPU是否命中了特定的代码行来执行,直到您重新编译该代码为止.调试语句? 不 ...

  4. 简述arm linux内核启动流程,Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述 一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader是系统启 ...

  5. 编译3.0的linux内核,1-3-编译Linux内核

    1-3-编译Linux内核 1.将Linux源码包拷贝到共享文件夹. 2.进入共享文件夹. 3.解压,命令#tar xvfj Kernel_3.0.8_TQ210_for_Linux_v2.2.tar ...

  6. 手机linux内核版本了解,Linux内核版本介绍与查询

    Linux内核版本命名在不同时期有着不同的规范,在涉及到Linux版本问题时经常容易混淆,主线版本/稳定版/长期支持版本经常搞不清楚,本文主要记录下内核版本命名的规则以及如何查看Linux系统版本信息 ...

  7. linux内核 can总线,Linux Canbus调试笔记

    http://blog.csdn.net/flydream0/article/details/8161418 CAN总线在嵌入式Linux下驱动程序的实现 http://www.21ic.com/ap ...

  8. 武汉linux内核好找吗,Linux内核入门

    Linux内核入门 收藏 如何获取Linux内核源代码 下载Linux内核当然要去http://www.kernel.org/了,网站提供了两种文件下载,一种是完整的Linux内核,另一种是内核增量补 ...

  9. linux内核工程导论,Linux内核工程导论——内存管理(3)

    Linux内核工程导论--内存管理(三) 用户端内核内存参数调整 /proc/sys/vm/ (需要根据内核版本调整) 交换相关 swap_token_timeout Thisfile contain ...

  10. linux 内核优化实战,Linux编译优化必须掌握的几个姿势总结

    01.编译选项和内核编译 Linux内核(英语:linux kernel),是一种计算机操作系统内核,已C语言和汇编语言写成,匹配POSIX标准,以GNU通用公共许可证发布.从技术上说Linux只是一 ...

最新文章

  1. linux gdb 运行的程序,GDB(十)--调试正在运行的进程
  2. matlab norm 范式
  3. android蓝牙python,Android蓝牙连接问题
  4. 重磅!阿里开源自研语音识别模型DFSMN,准确率高达96.04%
  5. 征战蓝桥 —— 2014年第五届 —— C/C++A组第5题——锦标赛
  6. Redis缓存,你真的懂了吗
  7. 中国农业大学计算机研究生专业课,中国农业大学2019计算机考研纯干货分享
  8. Jquery中BlockUI的遮罩效果详解
  9. 腾讯加入QQ群,代码生成地址
  10. 《学习笔记》在AngularJS视图中实现指令
  11. 秒懂三层交换机的作用及使用
  12. 选择正规的资质好的期货公司开户
  13. oBlog 4.0 正式版 2006-09-06
  14. 错误 C1189 #error MFC does not support WINVER less than 0x0501.
  15. DIY 一个 JSON解析器。
  16. python labelImg xml 格式的数据集解析及可视化
  17. 电脑计算机网络都打不开怎么办,实用教程:网络连接正常但打不开网页怎么办?...
  18. iOS 使用oc 版本的Lottie 库
  19. java 拒绝连接_java.net.ConnectException:连接被拒绝
  20. 查看C++源文件的预处理结果

热门文章

  1. 华为手机隐藏代码大全!一秒查出你的手机使用记录
  2. 初识C语言学习笔记 入门
  3. 【Inkscape】SVG矢量图形编辑器
  4. 基于vue+ts实现的svg可视化web组态编辑器
  5. 【DEVOPS】SVN Server迁移 - 从VisualSvnServer到iF.SVNAdmin
  6. win10系统怎么改奇摩输入法_Win10系统如何切换输入法
  7. KETTLE使用教程
  8. Java面试八股文(素材来自网络)
  9. 穿越机F4飞控F405代码pcb文件,原理图
  10. 使用js实现简单的动画效果