日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux内核调用串口,linux驱动之串口驱动框架

發布時間:2024/8/1 linux 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux内核调用串口,linux驱动之串口驱动框架 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、前言

前面介紹了 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驱动之串口驱动框架的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

免费a v观看 | 涩涩在线| 久久久久久久亚洲精品 | 99久久精| 久久久久久国产精品亚洲78 | 黄色在线观看免费网站 | 日韩视频一区二区三区在线播放免费观看 | 黄色日视频 | 婷婷在线视频 | 黄色亚洲精品 | 成人免费在线观看入口 | 美女免费电影 | av中文资源在线 | 最新av网址在线观看 | 国产片免费在线观看视频 | 国产破处在线视频 | 国产高清在线观看 | 青青久草在线视频 | 91传媒在线看 | 久草精品视频 | 国产一区视频免费在线观看 | 久久综合一本 | 国产美女精品在线 | 色综合久久88色综合天天免费 | 日韩高清精品一区二区 | 欧美一级久久 | 精品自拍av | 美女黄频在线观看 | 综合久久久 | 久久精品免费看 | 国产二区视频在线观看 | 久久超级碰 | 日韩久久在线 | 怡红院av久久久久久久 | 91九色蝌蚪在线 | 成人欧美一区二区三区黑人麻豆 | 久久a级片 | 国产精品九九久久久久久久 | 婷婷国产一区二区三区 | 日日天天av | 四虎影视精品 | 国产高清视频免费观看 | 伊人狠狠色丁香婷婷综合 | 亚洲伦理电影在线 | 99热这里精品 | 日韩一二区在线观看 | 欧美视频www | 久久精品国产一区 | 99精品免费在线观看 | 国产区高清在线 | 黄色在线观看免费 | 日日草av | 久久视| 亚洲一二区视频 | 最新av网站在线观看 | 国产福利av | 亚洲精品美女久久久 | 久久欧美精品 | 九色91在线视频 | 日韩国产精品一区 | 怡红院久久 | 日韩精品一区二区三区在线视频 | 国产精品久久久久久影院 | 日韩免费久久 | 99精品在线免费观看 | 免费v片| 波多野结衣电影一区二区三区 | 久久爱www. | 国内精品久久影院 | 一级黄色片在线免费看 | 成人免费一区二区三区在线观看 | 超碰最新网址 | 伊人久久国产精品 | 国产韩国日本高清视频 | 丰满少妇在线观看网站 | 久久久久福利视频 | 成人观看| 久久人人干 | 久久久亚洲精品 | 日韩精品一区二区三区免费观看视频 | av福利在线| 国产在线国偷精品产拍免费yy | 久久一级片| 高潮毛片无遮挡高清免费 | 中文字幕一区二区三区四区久久 | 国产精品久久久一区二区 | 免费a级黄色毛片 | 日韩三区在线观看 | 欧美激情精品久久久久久变态 | 天天天操天天天干 | 在线免费高清视频 | 六月丁香六月婷婷 | 精品久久久久久久久久 | 亚洲成人精品在线 | 亚洲一区在线看 | 亚洲人人精品 | 久久国产一区二区三区 | 在线国产视频观看 | 香蕉网在线观看 | 国产视频中文字幕 | 免费三级av | av看片网 | 国产在线专区 | 日韩精品视频在线免费观看 | 国产色黄网站 | 91 在线视频播放 | 97国产 | 国产精品18久久久久久久久 | 亚洲狠狠干 | 亚洲资源在线 | 亚洲视频 中文字幕 | 一区二区三区高清在线 | 在线观看中文字幕一区 | 欧美久草网 | 久草网在线视频 | 精品国产伦一区二区三区观看体验 | 91禁看片| 在线成人一区二区 | 天天做天天干 | 精品一区二区精品 | 久久久免费网站 | 国产69久久久欧美一级 | 午夜私人影院久久久久 | 免费网站在线观看成人 | 五月天网站在线 | 在线观看完整版免费 | 国产精品99久久99久久久二8 | 国产白浆在线观看 | 天天综合网久久综合网 | 日韩天堂在线观看 | 91精品国自产在线观看 | 999毛片| 精品免费一区 | 狠狠干免费| 欧美性大战久久久久 | av免费黄色 | 草草草影院 | 狠狠狠狠狠狠狠干 | 蜜臀aⅴ国产精品久久久国产 | 久久成 | 欧美日韩精品在线 | 人人看人人做人人澡 | 操一草| 日韩av黄| 中文在线字幕免费观看 | 蜜臀一区二区三区精品免费视频 | aaa亚洲精品一二三区 | 亚洲黄色区 | 日韩1页| 98久久| 国产在线观看二区 | 九九热免费精品视频 | 91天天操 | 91欧美视频网站 | 一区二区三区四区五区在线 | 日韩视频二区 | 五月花丁香婷婷 | 在线免费国产 | 中文在线亚洲 | 成人a视频 | 99久久99久久精品国产片果冰 | 成人三级黄色 | 国产精品6| 91av电影在线观看 | 亚洲国产激情 | 日韩久久精品一区二区 | 天天操综合 | 国产精品毛片久久久 | 黄色大片入口 | 亚洲欧洲精品一区二区精品久久久 | 欧美色888| 91精品国自产拍天天拍 | 三级黄在线 | 久久久久日本精品一区二区三区 | 亚洲天堂精品 | 日韩欧美一区二区三区视频 | 久草在线高清视频 | 天天曰天天干 | 97看片网 | 成人九九视频 | 国产三级久久久 | 久久avav| 亚洲精品国产成人av在线 | 国产97色在线 | 亚洲精品综合在线观看 | 色婷婷综合久久久久 | 人人爱人人做人人爽 | 中文字幕在线观看你懂的 | 久久免费视频在线观看30 | 国产精品一区二区在线 | 日本亚洲国产 | 在线免费高清 | 九热在线 | 国产高清在线不卡 | 欧美日韩在线免费观看 | 日韩欧美一区视频 | 黄色网址在线播放 | 亚洲视频中文 | 日韩欧美视频在线观看免费 | 国产一区二区不卡在线 | 2020天天干天天操 | 欧美日韩天堂 | 天天爽夜夜爽人人爽一区二区 | 日韩国产欧美在线视频 | 亚洲精品久久久久久中文传媒 | av看片网址 | 亚洲女欲精品久久久久久久18 | 日韩三级中文字幕 | 国产91在线免费视频 | 色婷婷狠 | 久久免费看毛片 | 日韩二区在线播放 | 国产精品毛片一区视频播 | 精品综合久久久 | 成人av免费在线播放 | 麻豆免费看片 | 亚洲午夜av| 日韩欧美精品在线视频 | 免费精品在线视频 | 夜夜躁狠狠燥 | 又黄又爽又湿又无遮挡的在线视频 | 国产一区在线免费观看视频 | 婷婷免费在线视频 | 五月天国产 | 99视频精品| 亚洲国产成人av网 | 欧美另类交人妖 | 国产福利在线不卡 | 午夜精品三区 | 日韩色爱 | 亚洲欧美va | 夜又临在线观看 | 亚洲成a人片77777kkkk1在线观看 | 91九色蝌蚪在线 | 亚洲国产成人在线播放 | 99精品视频在线免费观看 | 亚洲资源视频 | 国产3p视频 | 国产精品一二三 | 五月婷婷激情 | 国产美女网 | 国产高清成人av | 婷婷丁香在线 | 色综合狠狠干 | 久久精品国产免费看久久精品 | 天天操天天操 | 欧美日韩在线网站 | 中文字幕在线观看2018 | 国产麻豆精品95视频 | 日本久久精品视频 | 一区二区三区免费在线观看 | 特级毛片在线观看 | 国产不卡一二三区 | 亚洲黄色av一区 | 久草视频免费播放 | 99九九热只有国产精品 | 色插综合 | 久草免费手机视频 | 国产999视频 | 在线影视 一区 二区 三区 | 日本精品一区二区三区在线播放视频 | 狠狠狠狠狠狠狠狠 | 欧美成年网站 | 中文字幕日韩高清 | 五月激情片 | 国产无遮挡又黄又爽馒头漫画 | 黄色亚洲精品 | 日韩一区二区三区视频在线 | 成人免费观看av | 91天堂素人约啪 | 国产精品久久久久久久久久久久午夜 | av中文在线影视 | 国产剧情一区二区在线观看 | 97免费在线观看 | 国产999精品久久久久久绿帽 | 五月开心激情网 | 天天操天天操天天操天天操天天操天天操 | 蜜臀av免费一区二区三区 | 九九涩涩av台湾日本热热 | 国产中文字幕视频在线观看 | 91九色视频导航 | 欧洲一区二区在线观看 | 国产91免费观看 | 97在线成人 | 欧美性网站| 五月天久久精品 | 91香蕉视频 mp4| 99精品黄色片免费大全 | 欧美一区在线观看视频 | 99草视频| 国产成a人亚洲精v品在线观看 | 亚洲乱码中文字幕综合 | 国产99久久精品一区二区永久免费 | 国产在线更新 | 69夜色精品国产69乱 | 久久伊人免费视频 | 97色在线观看免费视频 | 黄色国产高清 | 日韩精品一区二区在线观看视频 | 欧美日韩免费一区二区三区 | 欧美另类网站 | 精品女同一区二区三区在线观看 | 丁香六月av| 国产高清在线免费观看 | 免费人做人爱www的视 | 国产精品一区二区三区观看 | 中文字幕精品一区二区精品 | 国产精品地址 | 日韩中文字幕一区 | 国产一区二区三区免费观看视频 | 黄色福利网 | 天天操操操操操操 | 亚洲黄色一级大片 | av大片网站| 9在线观看免费高清完整版 玖玖爱免费视频 | 午夜黄色一级片 | 天天操天天添 | 国产破处视频在线播放 | 国语麻豆 | 成人性生交大片免费观看网站 | 免费观看国产视频 | 免费亚洲视频在线观看 | 欧美精品一区二区蜜臀亚洲 | 亚洲国产影院av久久久久 | 精品一二三区 | 国产精品久久久精品 | av成人在线电影 | 婷婷在线资源 | 91干干干 | 一级黄色片在线 | 丰满少妇一级 | 久久在视频 | 色五婷婷 | 天天爽天天摸 | 1024手机在线看 | 五月天久久| 欧美一性一交一乱 | 亚洲黄网站 | 亚洲精品婷婷 | 精品久久久久国产 | 激情中文字幕 | 免费毛片一区二区三区久久久 | 亚洲女同ⅹxx女同tv | 一级黄色片网站 | 日韩中文字幕免费在线观看 | 缴情综合网五月天 | 国产精品久久久久999 | 欧美精品免费在线观看 | 欧美二区视频 | 日韩欧在线 | 亚洲视频在线看 | 亚洲精品视频观看 | 亚洲国产精品久久久久 | 日韩视频在线观看免费 | 毛片网站观看 | 青青河边草免费视频 | 99视频在线精品 | 国产一级片一区二区三区 | 91久久精品一区 | 91亚洲精 | 欧洲色综合 | 精品亚洲成人 | 天天视频色 | 欧美精品亚洲二区 | 在线播放国产一区二区三区 | 国产精品每日更新 | 97超碰资源总站 | 免费精品视频在线 | 天天拍天天草 | 亚洲春色成人 | 激情文学丁香 | 三级a毛片 | 日韩激情视频在线 | 波多野结衣电影一区二区三区 | www日韩| 91福利视频久久久久 | 国色天香av| 国产99久久久精品视频 | 97人人网 | 国产精品色 | 六月丁香色婷婷 | 国产999精品久久久影片官网 | 亚洲四虎 | 亚洲综合网站在线观看 | 国产精品一区二区麻豆 | 日韩成人看片 | 欧美午夜性 | 六月丁香婷婷在线 | 精品在线亚洲视频 | 久久国产精品色av免费看 | 91国内在线视频 | 日韩城人在线 | 日韩精品视频在线免费观看 | 18国产精品白浆在线观看免费 | 九九热1 | 精品国产一区二区三区四区在线观看 | 国产精彩在线视频 | 婷婷丁香六月天 | 国产精品一区二区久久 | 国产一区二区在线播放 | 在线免费观看黄色小说 | 亚洲精品视频在线观看免费视频 | 18做爰免费视频网站 | 五月天天色 | 天天操天天草 | 天天天色综合a | 亚洲女在线 | 亚洲视频六区 | 波多野结衣一区二区三区中文字幕 | 91精品福利在线 | 色综合激情久久 | 黄网站免费大全入口 | 久久人人爽爽人人爽人人片av | 日韩av在线一区二区 | 麻豆免费视频观看 | 日韩精品在线免费播放 | 久久久免费看 | 啪啪资源 | 91精品视频在线观看免费 | 日韩免费在线观看 | 久久久久国产a免费观看rela | 国产精品久久久一区二区三区网站 | 四虎影视欧美 | 探花在线观看 | 欧美精品v国产精品v日韩精品 | 亚洲成人av一区 | 欧美另类亚洲 | 日本不卡123区 | 午夜精品久久久久久久久久久久久久 | 欧美日韩电影在线播放 | 亚洲日本中文字幕在线观看 | 麻豆影视在线免费观看 | 一级欧美一级日韩 | 日韩偷拍精品 | 国产精品一区二区三区免费视频 | 亚洲精品国产日韩 | 亚洲在线网址 | 精品黄色片 | 日韩高清www | 九九免费在线视频 | 国产婷婷精品 | 一区二区高清在线 | 欧美九九九| 色夜影院 | 国产一区二区观看 | 在线精品亚洲一区二区 | 91丨九色丨丝袜 | 在线观看黄网 | 91视频免费观看 | 97免费公开视频 | av在线播放一区二区三区 | 久久99亚洲精品久久久久 | 国产免费久久精品 | av电影在线观看完整版一区二区 | 亚洲女同ⅹxx女同tv | 欧美激情综合五月色丁香小说 | 欧美成人高清 | 日本精品视频在线 | 天天爽综合网 | 国产精品午夜在线观看 | 久久激情视频 | 免费在线播放av电影 | 亚洲精品乱码久久久久久高潮 | 精品在线视频一区二区三区 | 久久精品中文 | 免费成人在线观看 | 免费观看性生活大片3 | 日日夜操 | 国产成人高清在线 | 精品国产成人av | 中文一区二区三区在线观看 | 亚洲欧美精品一区二区 | 最新av在线播放 | 中文字幕精品三区 | 色婷婷久久一区二区 | 成人午夜精品 | 99视频精品 | 亚洲在线日韩 | 午夜久久网站 | 中文字幕资源网在线观看 | 国产精品久免费的黄网站 | 天天操天天操一操 | 久久亚洲专区 | 麻豆视频国产精品 | 国产精品久久久久影院日本 | av色网站 | 成人黄色片免费 | 国产精久久久久久妇女av | 中文字幕在线观看日本 | 亚洲国产精品va在线看黑人动漫 | 少妇性xxx | 久久综合久久综合久久 | 丁香五婷| 韩日精品在线观看 | 在线亚洲免费视频 | 毛片一区二区 | 精品福利av | 国产精品第一页在线观看 | 久久久久久久久久久久久国产精品 | 国产精品午夜久久久久久99热 | 久草视频在线免费播放 | 日韩有码在线观看视频 | 亚洲午夜久久久久 | 亚洲,国产成人av | 久久99深爱久久99精品 | 狠狠网站| 国产小视频精品 | 国产精品久久久久永久免费看 | 欧美色插| 四虎精品成人免费网站 | 国产第页| 亚洲一区二区三区四区在线视频 | 久久99精品热在线观看 | 91视频3p| 在线电影 你懂得 | 国产精品一区二区白浆 | 一级免费黄视频 | 五月综合在线观看 | 日韩在线精品一区 | 久久99在线视频 | 色婷婷97 | 500部大龄熟乱视频 欧美日本三级 | 成人黄色毛片视频 | 欧美午夜性生活 | 国产我不卡 | 国产精品一区二区在线免费观看 | 国产精品成人自拍 | 欧美-第1页-屁屁影院 | 婷婷五月在线视频 | www178ccom视频在线 | a级国产乱理论片在线观看 特级毛片在线观看 | 久久精品国产一区二区三区 | 制服丝袜一区二区 | 一区二区精品 | 毛片无卡免费无播放器 | 午夜久久影视 | 成人观看 | 日本激情视频中文字幕 | 丁香六月伊人 | 一级黄色片毛片 | 日产乱码一二三区别免费 | 久久精品成人 | 久久久久久久99 | 国产日产欧美在线观看 | 黄色特一级 | 九色激情网 | 国产视频一级 | 人人爽人人澡人人添人人人人 | 国产成人精品综合久久久久99 | 天堂av免费观看 | 久久久久国产一区二区三区四区 | 国产精品一区一区三区 | a电影在线观看 | 一区二区在线不卡 | 99久久国产免费看 | 国产亚洲欧美一区 | 国产一级性生活 | 一区二区三区免费播放 | 国内精品在线观看视频 | 日韩精品不卡在线 | 中文字幕第 | 亚洲理论影院 | 国产不卡在线看 | wwwwwww黄 | 天天操夜夜操 | 国产成人免费观看 | 国产手机视频在线 | 亚洲精品资源在线观看 | 免费看一级特黄a大片 | 99免费视频| 国产在线a免费观看 | 69国产在线观看 | 天天干天天在线 | 国产成人无码AⅤ片在线观 日韩av不卡在线 | 国产a级精品 | 久久人人爽人人爽人人片 | 久久免费av | avwww在线观看 | 欧美福利视频一区 | 免费色黄 | www.伊人网| 国产精品国内免费一区二区三区 | 黄色在线网站噜噜噜 | 免费情缘 | av福利电影 | 91九色视频网站 | 中文字幕电影高清在线观看 | 久久不色 | 久久福利剧场 | 国产黄色av | 日韩免费在线观看视频 | 一区二区三区不卡在线 | 国产精品久久久免费 | 99视频一区 | 国产在线观看av | 国产福利av在线 | 久久久久久久久综合 | av免费电影在线观看 | 久久综合网色—综合色88 | 成人黄色电影在线观看 | 日日干综合 | 亚洲最大在线视频 | 五月天婷亚洲天综合网精品偷 | 深爱五月激情五月 | 91精品秘密在线观看 | 国产不卡精品视频 | 精品久久久久久综合 | 午夜国产在线观看 | www.超碰97.com | 中文字幕超清在线免费 | 国产手机视频在线 | 九九热视频在线免费观看 | 欧美精品二| 天天干天天看 | 免费观看性生活大片 | 亚洲做受高潮欧美裸体 | av黄网站 | 国产色综合天天综合网 | 超碰在线观看av.com | 久久精品久久精品久久 | 中文字幕色在线 | 又黄又刺激视频 | 国产精成人品免费观看 | 国产精品久久久毛片 | 久久精品高清 | 日本xxxx裸体xxxx17 | 在线观看黄污 | 精品久久久久久国产偷窥 | 欧美日本三级 | 国产在线播放观看 | 婷婷av在线| 奇米影视777四色米奇影院 | 日韩免费电影一区二区 | 国产在线国偷精品产拍免费yy | 97超碰影视| 天天干天天操天天拍 | 999ZYZ玖玖资源站永久 | 正在播放一区 | 久久综合免费视频影院 | 亚洲精品美女久久久久 | 中文字幕 成人 | 国产视频不卡一区 | 六月丁香久久 | 免费网站污 | 四虎影视国产精品免费久久 | 色偷偷人人澡久久超碰69 | 午夜精品久久一牛影视 | 97**国产露脸精品国产 | 国产 在线观看 | 天天综合91 | 菠萝菠萝在线精品视频 | 日韩簧片在线观看 | 中文视频在线 | 国产亚洲视频在线 | 91亚洲在线| 成人在线视频观看 | 国产黄色在线看 | 在线 成人 | 日韩免费一级a毛片在线播放一级 | 久久天天躁夜夜躁狠狠躁2022 | 国产成人精品一区二区三区网站观看 | 激情网色 | 久久久免费精品 | 天天爽人人爽 | 国产中文在线视频 | 国产精品免费在线播放 | 国产手机av在线 | 九九九九免费视频 | 27xxoo无遮挡动态视频 | 999视频网 | 成年人av在线播放 | www五月天com | 国产1区在线 | 国产精品久久久久久久久免费看 | 九九99视频 | 97夜夜澡人人爽人人免费 | 中文欧美字幕免费 | 欧美国产三区 | 81国产精品久久久久久久久久 | 国产精品不卡在线播放 | 人人爽人人看 | 日韩免费在线观看 | 日韩网站在线看片你懂的 | 激情网站网址 | 久久国产亚洲 | 久久精品国产v日韩v亚洲 | 亚洲做受高潮欧美裸体 | 亚洲天天综合 | 欧美精品在线一区二区 | 欧美二区三区91 | 99视频免费 | 人人澡人摸人人添学生av | 91九色蝌蚪在线 | 亚洲综合色视频在线观看 | 日韩精品一区二区三区丰满 | 四虎国产精品永久在线国在线 | 伊人干综合 | 国产亚洲综合精品 | 国产精品国产三级国产专区53 | 亚洲草视频 | 久久这里只精品 | 在线观看成人av | 中文字幕国产在线 | 一区二区三区免费在线观看 | 亚洲精品视频在线观看网站 | 一区二区精品国产 | 日韩中文字幕视频在线 | 日韩视频一区二区三区在线播放免费观看 | 99九九热只有国产精品 | 日韩电影久久久 | 91久久丝袜国产露脸动漫 | www日韩欧美 | 久草在线免费资源 | 色婷av| 黄色影院在线免费观看 | 97在线精品 | 久久久穴| 亚洲精品视频一 | 国产福利精品一区二区 | 国产精选在线观看 | 97色综合 | 国产黄色资源 | 欧美大片第1页 | 久久综合九色综合久99 | 国产精品国产三级国产专区53 | 天天操天天操天天 | 亚洲日本中文字幕在线观看 | 激情综合色播五月 | 日韩在线电影一区 | 日韩视频免费观看高清完整版在线 | 五月色丁香 | 中文字幕在线观看完整版 | 国产精品s色 | 黄色毛片视频免费 | 色偷偷88888欧美精品久久 | 色偷偷人人澡久久超碰69 | 国色综合| 极品嫩模被强到高潮呻吟91 | 亚洲狠狠操 | 九九热精品在线 | 国产中文 | 天天操天天干天天摸 | 午夜的福利 | 欧美久久影院 | 国产成a人亚洲精v品在线观看 | 人人狠狠综合久久亚洲婷 | 日韩三级视频在线看 | 精品成人国产 | 综合激情av | 三级av免费看 | 狠狠久久 | 在线播放亚洲激情 | 久久久黄色免费网站 | 五月天视频网站 | 91插插插网站 | 国产91粉嫩白浆在线观看 | www五月天 | 日韩三级av | 欧美日韩视频在线观看免费 | 国产精品av电影 | 国产成人精品亚洲 | 成人免费电影 | 午夜av日韩 | 国产一区二区在线免费播放 | 久久久成人精品 | 日韩高清免费在线 | 欧美孕交vivoestv另类 | 成人一级 | 国产成人一区二区精品非洲 | 日韩欧美高清免费 | 成人黄在线 | 久久99热精品这里久久精品 | 日本精品免费看 | 在线观看国产区 | www.色婷婷.com | 日韩精品久久久免费观看夜色 | 久久全国免费视频 | 日韩av午夜在线观看 | 久久久高清视频 | 亚洲电影网站 | 黄a网| 激情欧美国产 | 五月婷在线 | 免费日韩三级 | 亚洲视频1区2区 | 国产日本在线观看 | 亚洲视频网站在线观看 | 五月婷婷色 | 免费久久视频 | 五月婷婷六月丁香在线观看 | 国产精品入口a级 | 亚洲日韩欧美视频 | 亚洲自拍偷拍色图 | 国产成人一区二区三区电影 | 天天插综合网 | 成年人在线观看网站 | 国产在线综合视频 | 九九九热 | 最近中文字幕大全中文字幕免费 | 日日日日 | 日日夜夜免费精品 | 四虎8848免费高清在线观看 | 精品一区二区在线免费观看 | 久久呀 | 免费观看91视频 | 九九综合久久 | 久久久国产精品成人免费 | 国产在线最新 | 成人精品亚洲 | 欧美有色 | 天天综合网在线 | 麻豆91在线播放 | 精品v亚洲v欧美v高清v | 国产高清视频免费最新在线 | 天天干,夜夜爽 | 又黄又色又爽 | 在线激情av电影 | 91精品国产乱码久久桃 | 天天操天天是 | 在线观看 国产 | 人人干天天射 | 中文字幕一区二 | 亚洲 欧美 成人 | 欧美专区日韩专区 | 国产精品免费观看久久 | 日韩啪视频 | 久久国产精品一区二区 | 国产成人精品一区二区三区福利 | 久久久精品国产一区二区电影四季 | 欧美日韩国产欧美 | 欧美二区视频 | 欧美成人久久 | 精品久久久久久久久久 | 香蕉成人在线视频 | 玖玖999| 成片免费观看视频 | 亚洲va欧美va| 久久国产乱 | 狠狠久久婷婷 | 国产色女人 | 最新真实国产在线视频 | 日本公妇在线观看 | 免费观看黄色12片一级视频 | 丁香视频在线观看 | 国产精品初高中精品久久 | 亚洲精品免费视频 | 国产精品久久久久亚洲影视 | 久久婷亚洲五月一区天天躁 | 又黄又刺激的网站 | 夜色成人网| 精品久久久久久综合日本 | 欧美一区二区视频97 | 91探花在线视频 | 国产精品视频大全 | 欧洲色吧| 成人黄在线 | 日韩网站免费观看 | 永久免费精品视频 | 成 人 黄 色 免费播放 | 免费成人av在线看 | 五月花丁香婷婷 | 中文字幕在线中文 | 亚洲日本一区二区在线 | 日韩欧美电影在线 | 国产在线成人 | 中文字幕乱在线伦视频中文字幕乱码在线 | 国产精品资源在线 | 久久国产影院 | 成人av日韩 | 国产一级黄色电影 | av中文天堂 | 色香蕉在线视频 | 日韩综合精品 | 超碰在线人人艹 | 亚洲国产精品久久久久婷婷884 | 狠狠色丁香久久婷婷综合_中 | 国产一级二级在线观看 | 超碰在线公开免费 | 日本精品久久久久影院 | 免费性网站 | 一区二区欧美激情 | 国产亚洲亚洲 | 天天摸夜夜添 | 深爱激情五月综合 | 天天做夜夜做 | 国产精品久久久久av福利动漫 | 成人香蕉视频 | 亚洲成a人片77777潘金莲 | 日韩啪啪小视频 | 免费观看一级一片 | 911国产 | 精品国产乱码久久久久久久 | 国产精品欧美久久久久天天影视 | 91香蕉视频黄色 | 日韩一二三区不卡 | 亚洲精品ww| 美女久久视频 | 97超碰免费在线观看 | 麻豆久久精品 | 成人免费在线网 | 九九99视频 | 激情影音先锋 | 日韩在线观看a | 男女全黄一级一级高潮免费看 | 成人av教育 | 亚洲国产精品久久久久 | 欧美福利网址 | 国产区精品在线 | 天堂在线视频免费观看 | 国产麻豆传媒 | 五月婷社区 | 国产五月色婷婷六月丁香视频 | 久久视频热 | 欧美激情操 | 亚洲日本一区二区在线 | 99精品国产一区二区三区不卡 | 色诱亚洲精品久久久久久 | 精品亚洲免费 | 69精品在线观看 | 欧美精品一区二区在线观看 | 69视频在线播放 | 久久久久久久久久久综合 | 国产精品va最新国产精品视频 | www夜夜 | 欧美性生交大片免网 | 久久精品视频在线免费观看 | 国产精品99久久免费黑人 | 激情五月五月婷婷 | 久久亚洲综合色 | 亚洲理论片在线观看 | 久久久久福利视频 | 亚洲男男gaygay无套 | 美女在线国产 | 99久热在线精品视频成人一区 | 在线视频手机国产 | 亚洲色图27p | 天天干天天想 | 黄色一级在线观看 | 高清视频一区二区三区 | 免费aa大片| 日批视频在线播放 | 在线观看久草 | 免费网站色 | 亚洲精品综合欧美二区变态 | 久久精品综合一区 | 久草视频在线播放 | 欧美久久久久久久 | 国产成人综合在线观看 | 91精品国产成人www | 国内视频一区二区 | 日韩精品中文字幕av | 国产视频一区二区在线播放 | 99久久精 | 精品国产一区二区三区四区在线观看 | 成人va在线观看 | av色一区 | 91黄色在线视频 | 国产99久久久国产精品 | 国产三级香港三韩国三级 | 99亚洲国产 | 玖玖999| www日日| 欧美另类tv | 欧美精品天堂 | 日韩在线第一区 | 啪啪午夜免费 | 中文字幕在线看视频国产中文版 | 超碰人人99 | 综合精品在线 | 欧美视频日韩视频 | 日本精品视频在线观看 | www.啪啪.com| 久久久久女人精品毛片九一 | 精品亚洲一区二区 | 一区二区三区高清不卡 | 免费在线观看av片 | 亚洲欧美日韩精品久久久 | 日韩欧美高清一区二区三区 | 久久av电影| 狠狠伊人 | av女优中文字幕在线观看 | 成年免费在线视频 | 欧美一二区在线 | 五月天堂网| 久久狠狠一本精品综合网 | 国产精品无av码在线观看 | 亚洲在线网址 | 波多野结衣在线中文字幕 | 激情五月亚洲 | 久久久久久久久久久久久影院 | 久久99国产视频 | 十八岁以下禁止观看的1000个网站 | 色全色在线资源网 | 国产成人黄色 | 亚洲精品久久激情国产片 | 国产精品九九热 | 狠狠躁18三区二区一区ai明星 | 91av电影在线观看 |