写在前面:

我们没有讲UART驱动,不过我们认为,只要系统学习了第2期,应该具备分析UART驱动的能力,小编做答疑几年以来,陆陆续续有不少人问到UART驱动怎么写,所以今天就分享一篇深度长文(17000字,阅读时间43分钟),作者是我们的答疑助手lizuobin,涉及很多数据结构,为了看懂本文,特意打开source insight 跟踪了代码,你也应该这样,如果你的代码不一样,那或许linux版本不一样。

作者:lizuobin

原文(有些许修正):

https://blog.csdn.net/lizuobin2/article/details/51773305

本文参考了大量牛人的博客,对大神的分享表示由衷的感谢。

主要参考:

Linux TTY驱动--Uart_driver底层:

http://blog.csdn.net/sharecode/article/details/9196591

Linux TTY驱动--Serial Core层 :

http://blog.csdn.net/sharecode/article/details/9197567

前面学习过了 i2c、spi,这俩都是基于设备总线驱动模型,分析起来相对比较简单,今天打算迎难而上学习一下 Uart 驱动,因为它涉及了tty 、线路规程,确实有些难度,幸好有万能的互联网让我可以学习大神们的博客。一天下来总算有些收获,下面总结一下(主要是框架)。

整个uart 框架大概如上图所示,简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件接触,我们需要填充一个 struct uart_ops 的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 Ops 结构,用户空间通过 tty 注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。下面,就来分析分析它们的层次结构。

在 s3c2440平台,它是这样来注册串口驱动的:分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。

static struct uart_driver s3c24xx_uart_drv = {
.owner = THIS_MODULE,
.dev_name = "s3c2410_serial",
.nr = CONFIG_SERIAL_SAMSUNG_UARTS,
.cons = S3C24XX_SERIAL_CONSOLE,
.driver_name = S3C24XX_SERIAL_NAME,
.major = S3C24XX_SERIAL_MAJOR,
.minor = S3C24XX_SERIAL_MINOR,
};
static int __init s3c24xx_serial_modinit(void)
{
int ret; ret = uart_register_driver(&s3c24xx_uart_drv);
if (ret < 0) {
printk(KERN_ERR "failed to register UART driver\n");
return -1;
} return 0;
}

uart_driver 中,我们只是填充了一些名字、设备号等信息,这些都是不涉及底层硬件访问的,到底怎么回事呢?来看一下完整的 uart_driver 结构或许就明白了。

struct uart_driver {
struct module *owner; /* 拥有该uart_driver的模块,一般为THIS_MODULE */
const char *driver_name; /* 串口驱动名,串口设备文件名以驱动名为基础 */
const char *dev_name; /* 串口设备名 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
int nr; /* 该uart_driver支持的串口个数(最大) */
struct console *cons; /* 其对应的console.若该uart_driver支持serial console,否则为NULL */ /* 下面这俩,它们应该被初始化为NULL */
struct uart_state *state; <span style="white-space:pre"> </span>/* 下层,串口驱动层 */
struct tty_driver *tty_driver; /* tty相关 */
};

在我们上边填充的结构体中,有两个成员未被赋值,对于tty_driver 代表的是上层,它会在 uart_register_driver中的过程中赋值,而uart_state 则代表下层,uart_state 也会在uart_register_driver的过程中分配空间,但是它里面真正设置硬件相关的东西是 uart_state->uart_port ,这个uart_port 是需要我们从其它地方调用 uart_add_one_port 来添加的。

1、下层(串口驱动层)

首先,我们需要认识这几个结构体

struct uart_state {
struct tty_port port;
int pm_state;
struct circ_buf xmit;
struct tasklet_struct tlet;
struct uart_port *uart_port; // 对应于一个串口设备
};

在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息。

struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* io端口基地址(物理) */
unsigned char __iomem *membase; /* io内存基地址(虚拟) */
unsigned int (*serial_in)(struct uart_port *, int);
void (*serial_out)(struct uart_port *, int, int);
unsigned int irq; /* 中断号 */
unsigned long irqflags; /* 中断标志 */
unsigned int uartclk; /* 串口时钟 */
unsigned int fifosize; /* 串口缓冲区大小 */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* 寄存器位移 */
unsigned char iotype; /* IO访问方式 */
unsigned char unused1; unsigned int read_status_mask; /* 关心 Rx error status */
unsigned int ignore_status_mask; /* 忽略 Rx error status */
struct uart_state *state; /* pointer to parent state */
struct uart_icount icount; /* 串口信息计数器 */ struct console *cons; /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq; /* sysrq timeout */
#endif upf_t flags; unsigned int mctrl; /* 当前的Moden 设置 */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* 端口类型 */
const struct uart_ops *ops; /* 串口端口操作函数 */
unsigned int custom_divisor;
unsigned int line; /* 端口索引 */
resource_size_t mapbase; /* io内存物理基地址 */
struct device *dev; /* 父设备 */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char unused[2];
void *private_data; /* generic platform data pointer */
};

这个结构体,是需要我们自己来填充的,比如s3c2440 有3个串口,那么就需要填充3个 uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port 。

在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,这个也是需要我们自己来实现的,一般芯片厂家都写好了或者只需要稍作修改。

struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); /* 串口的Tx FIFO缓存是否为空 */
void (*set_mctrl)(struct uart_port *, unsigned int mctrl); /* 设置串口modem控制 */
unsigned int (*get_mctrl)(struct uart_port *); /* 获取串口modem控制 */
void (*stop_tx)(struct uart_port *); /* 禁止串口发送数据 */
void (*start_tx)(struct uart_port *); /* 使能串口发送数据 */
void (*send_xchar)(struct uart_port *, char ch); /* 发送xChar */
void (*stop_rx)(struct uart_port *); /* 禁止串口接收数据 */
void (*enable_ms)(struct uart_port *); /* 使能modem的状态信号 */
void (*break_ctl)(struct uart_port *, int ctl); /* 设置break信号 */
int (*startup)(struct uart_port *); /* 启动串口,应用程序打开串口设备文件时,该函数会被调用 */
void (*shutdown)(struct uart_port *);/* 关闭串口,应用程序关闭串口设备文件时,该函数会被调用 */
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,
struct ktermios *old); /* 设置串口参数 */
void (*set_ldisc)(struct uart_port *);/* 设置线路规程 */
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate); /* 串口电源管理 */
int (*set_wake)(struct uart_port *, unsigned int state); /*
* Return a string describing the type of the port
*/
const char *(*type)(struct uart_port *); /*
* Release IO and memory resources used by the port.
* This includes iounmap if necessary.
*/
void (*release_port)(struct uart_port *); /*
* Request IO and memory resources used by the port.
* This includes iomapping the port if necessary.
*/
int (*request_port)(struct uart_port *); /* 申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口 */
void (*config_port)(struct uart_port *, int); /* 执行串口所需的自动配置 */
int (*verify_port)(struct uart_port *, struct serial_struct *); /* 核实新串口的信息 */
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};

实在是太复杂了。但这一层就跟裸机程序一样,用来操作硬件寄存器,只不过内核把“格式”给我们规定死了。

2、上层(tty 核心层)

tty 层要从 uart_register_driver来看起了,因为tty_driver是在注册过程中构建的,我们也顺便了解注册过程。

int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval; /* 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个uart_port */
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); /* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */
normal = alloc_tty_driver(drv->nr);
drv->tty_driver = normal; /* 对 tty_driver 进行设置 */
normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv; tty_set_operations(normal, &uart_ops); /*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port; /* driver->state->tty_port */ tty_port_init(port);
port->close_delay = 500; /* .5 seconds */
port->closing_wait = 30000; /* 30 seconds */
/* 初始化 tasklet */
tasklet_init(&state->tlet, uart_tasklet_action,
(unsigned long)state);
} /* tty层:注册 driver->tty_driver */
retval = tty_register_driver(normal); }

注册过程干了哪些事:

1、根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个 uart_port 。

2、分配一个 tty_driver ,并将drv->tty_driver 指向它。

3、对 tty_driver 进行设置,其中包括默认波特率、校验方式等,还有一个重要的

Ops ,uart_ops ,它是tty核心与我们串口驱动通信的接口。

4、初始化每一个 uart_state 的 tasklet 。

5、注册 tty_driver 。

注册 uart_driver 实际上是注册 tty_driver,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,我们不需要修改,了解一下工作原理即可。

static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char, // 单字节写函数
.flush_chars = uart_flush_chars, // 刷新数据到硬件函数
.write_room = uart_write_room, // 指示多少缓冲空闲的函数
.chars_in_buffer= uart_chars_in_buffer, // 只是多少缓冲满的函数
.flush_buffer = uart_flush_buffer, // 刷新数据到硬件
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios, // 当termios设置被改变时又tty核心调用
.set_ldisc = uart_set_ldisc, // 设置线路规程函数
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup, // 挂起函数,当驱动挂起tty设备时调用
.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, // 获得当前tty的线路规程的设置
.tiocmset = uart_tiocmset, // 设置当前tty线路规程的设置
#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 核心的 Ops ,简单看看,等后面分析调用关系时,在来细看,下面来看 tty_driver 的注册。

int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
void **p = NULL; if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
} /* 如果没有主设备号则申请 */
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start,
driver->num, driver->name);
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
} if (p) { /* 为线路规程和termios分配空间 */
driver->ttys = (struct tty_struct **)p;
driver->termios = (struct ktermios **)(p + driver->num);
} else {
driver->ttys = NULL;
driver->termios = NULL;
} /* 创建字符设备,使用 tty_fops */
cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num); mutex_lock(&tty_mutex); /* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
list_add(&driver->tty_drivers, &tty_drivers);
mutex_unlock(&tty_mutex); if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
for (i = 0; i < driver->num; i++)
tty_register_device(driver, i, NULL);
} /* proc 文件系统注册driver */
proc_tty_register_driver(driver);
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
}

tty_driver 注册过程干了哪些事:

1、为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。

2、注册字符设备,名字是 uart_driver->name 我们这里是“ttySAC”,文件操作函数集是 tty_fops。

3、将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers 。

4、向 proc 文件系统添加 driver ,这个暂时不了解。

至此,文章起初的结构图中的4个ops已经出现了3个,另一个关于线路规程的在哪?继续往下看。

3、调用关系分析

tty_driver 不是注册了一个字符设备么,那我们就以它的 tty_fops 入手,以 open、read、write 为例,看看用户空间是如何访问到最底层的硬件操作函数的。

3.1 tty_open

static int tty_open(struct inode *inode, struct file *filp)
{
int ret; lock_kernel();
ret = __tty_open(inode, filp);
unlock_kernel();
return ret;
} 为了方便分析,我把看不懂的代码都删掉了。 static int __tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty = NULL;
int noctty, retval;
struct tty_driver *driver;
int index;
dev_t device = inode->i_rdev;
unsigned saved_flags = filp->f_flags;
...
//在全局tty_drivers链表中获取Core注册的tty_driver
driver = get_tty_driver(device, &index); tty = tty_init_dev(driver, index, 0); // tty->ops = driver->ops; filp->private_data = tty; if (tty->ops->open)
/* 调用tty_driver->tty_foperation->open */
retval = tty->ops->open(tty, filp); return 0;
} 从 tty_drivers 全局链表获取到前边我们注册进去的 tty_driver ,然后分配设置一个 struct tty_struct 的东西,最后调用 tty_struct->ops->open 函数,其实 tty_struct->ops == tty_driver->ops 。 struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx, int first_ok)
{
struct tty_struct *tty;
int retval;
/* 分配一个 tty_struct */
tty = alloc_tty_struct(); /* 初始化 tty ,设置线路规程 Ops 等 */
initialize_tty_struct(tty, driver, idx); //tty_ldisc_open(tty, ld)-> return ld->ops->open(tty) -> n_tty_open
retval = tty_ldisc_setup(tty, tty->link); return tty;
} void initialize_tty_struct(struct tty_struct *tty,
struct tty_driver *driver, int idx)
{
memset(tty, 0, sizeof(struct tty_struct)); /* 设置线路规程为 N_TTY */
tty_ldisc_init(tty);//struct tty_ldisc *ld = tty_ldisc_get(N_TTY);tty_ldisc_assign(tty, ld); ...
tty_buffer_init(tty);
tty->driver = driver; /* 初始化等待队列头 */
init_waitqueue_head(&tty->write_wait);
init_waitqueue_head(&tty->read_wait); /* 将driver->ops 拷贝到 tty->ops */
tty->ops = driver->ops;
tty->index = idx;
} void tty_buffer_init(struct tty_struct *tty)
{
spin_lock_init(&tty->buf.lock);
tty->buf.head = NULL;
tty->buf.tail = NULL;
tty->buf.free = NULL;
tty->buf.memory_used = 0; /* 初始化延时工作队列 */
INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);
}

整个 tty_open 的工作:

1、获取 tty_driver

2、根据 tty_driver 初始化一个 tty_struct

2.1 设置 tty_struct 的线路规程为 N_TTY (不同类型的线路规程有不同的 ops)

2.2 初始化一个延时工作队列,唤醒时调用flush_to_ldisc ,读函数时我们需要分析它。

2.3 初始化 tty_struct 里的两个等待队列头。

2.4 设置 tty_struct->ops == tty_driver->ops 。

3、在 tty_ldisc_setup 函数中调用到线路规程的open函数,对于 N_TTY 来说是 n_tty_open 。

4、如果 tty_struct->ops 也就是 tty_driver->ops 定义了 open 函数则调用,显然是有的 uart_open 。

对于 n_tty_open ,它应该是对线路规程如何“格式化数据”进行设置,太复杂了,忽略掉吧,跟我们没多大关系。对于 uart_open 还是有必要贴下代码。

static int uart_open(struct tty_struct *tty, struct file *filp)
{
struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
struct uart_state *state;
struct tty_port *port;
int retval, line = tty->index; state = uart_get(drv, line);
port = &state->port;
tty->driver_data = state;
state->uart_port->state = state; /* uport->ops->startup(uport) 调用到最底层的ops里的startup 函数*/
retval = uart_startup(state, 0); }

根据 tty_struct 获取到 uart_driver ,再由 uart_driver 获取到里面 的uart_state->uart_port->ops->startup 并调用它。至此,open函数分析完毕,它不是简单的 “打开”,还有大量的初始化工作,最终调用到最底层的 startup 函数。

3.2 tty_write

static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct tty_struct *tty;
struct inode *inode = file->f_path.dentry->d_inode;
ssize_t ret;
struct tty_ldisc *ld; tty = (struct tty_struct *)file->private_data; ld = tty_ldisc_ref_wait(tty);
if (!ld->ops->write)
ret = -EIO;
else
/* 调用 线路规程 n_tty_write 函数 */
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
tty_ldisc_deref(ld);
return ret;
}
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
ssize_t retval = 0;
// 将当前进程添加到等待队列
add_wait_queue(&tty->write_wait, &wait);
while (1) {
// 设置当前进程为可中断的
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
retval = -EIO;
break;
}
/* 自行定义了输出方式 */
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
....
} else {
while (nr > 0) {
/* 调用到 uart_write */
c = tty->ops->write(tty, b, nr);
if (c < 0) {
retval = c;
goto break_out;
}
if (!c)
break;
b += c;
nr -= c;
}
}
if (!nr)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
// 进程调度 开始休眠
schedule();
}
}

n_tty_write 调用 tty->ops->write 也就是 uart_write。

static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
uart_start(tty);
return ret;
} static void uart_start(struct tty_struct *tty)
{
__uart_start(tty);
} static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port; if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
!tty->stopped && !tty->hw_stopped)
/* 调用到最底层的 start_tx */
port->ops->start_tx(port);
}

uart_write 又调用到了最底层的 uart_port->ops->start_tx 函数。

猜测一下,大概“写”的思路:

1、将当前进程加入到等待队列

2、设置当前进程为可打断的

3、层层调用最终调用到底层的 start_tx 函数,将要发送的数据存入 DATA 寄存器,由硬件自动发送。

4、进程调度,当前进程进入休眠。

5、硬件发送完成,进入中断处理函数,唤醒对面队列。

当然这只是我自己的猜测,到底是不是这样,具体分析底层操作函数的时候应该会明白。

3.3 tty_read

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
int i;
struct tty_struct *tty;
struct inode *inode;
struct tty_ldisc *ld; tty = (struct tty_struct *)file->private_data;
inode = file->f_path.dentry->d_inode; ld = tty_ldisc_ref_wait(tty);
/* 调用线路规程 n_tty_read */
if (ld->ops->read)
i = (ld->ops->read)(tty, file, buf, count);
else
i = -EIO;
tty_ldisc_deref(ld);
if (i > 0)
inode->i_atime = current_fs_time(inode->i_sb);
return i;
}

调用线路规程的 read 函数,对于 N_TTY 来说是 n_tty_read (删掉了一堆看不懂的代码,还是有很多)

static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
unsigned char __user *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
int minimum, time;
ssize_t retval = 0;
ssize_t size;
long timeout;
unsigned long flags;
int packet; do_it_again: BUG_ON(!tty->read_buf); c = job_control(tty, file); minimum = time = 0;
timeout = MAX_SCHEDULE_TIMEOUT;
/* 如果是非标准模式 */
if (!tty->icanon) {
...
} packet = tty->packet; add_wait_queue(&tty->read_wait, &wait);
while (nr) {
/* First test for status change. */
if (packet && tty->link->ctrl_status) {
/* 看不懂的都删掉 */
}
/* This statement must be first before checking for input
so that any interrupt will set the state back to
TASK_RUNNING. */
set_current_state(TASK_INTERRUPTIBLE); if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
((minimum - (b - buf)) >= 1))
tty->minimum_to_wake = (minimum - (b - buf)); if (!input_available_p(tty, 0)) {
/* 看不懂的都删掉 */ /* FIXME: does n_tty_set_room need locking ? */
n_tty_set_room(tty);
/* 进程调度 休眠 */
timeout = schedule_timeout(timeout);
continue;
}
__set_current_state(TASK_RUNNING); /* Deal with packet mode. */
if (packet && b == buf) {
/* 看不懂的都删掉 */
} /* 如果是标准模式 */
if (tty->icanon) {
/* N.B. avoid overrun if nr == 0 */
while (nr && tty->read_cnt) {
int eol; eol = test_and_clear_bit(tty->read_tail,
tty->read_flags); /* 从tty->read_buf 获取数据 */
c = tty->read_buf[tty->read_tail];
spin_lock_irqsave(&tty->read_lock, flags);
tty->read_tail = ((tty->read_tail+1) &
(N_TTY_BUF_SIZE-1));
tty->read_cnt--;
if (eol) {
/* this test should be redundant:
* we shouldn't be reading data if
* canon_data is 0
*/
if (--tty->canon_data < 0)
tty->canon_data = 0;
}
spin_unlock_irqrestore(&tty->read_lock, flags); if (!eol || (c != __DISABLED_CHAR)) {
/* 将数据拷贝到用户空间 */
if (tty_put_user(tty, c, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (eol) {
tty_audit_push(tty);
break;
}
}
if (retval)
break;
} else {
/* 非标准模式不关心删掉 */
}
....
}
mutex_unlock(&tty->atomic_read_lock);
remove_wait_queue(&tty->read_wait, &wait); if (!waitqueue_active(&tty->read_wait))
tty->minimum_to_wake = minimum; __set_current_state(TASK_RUNNING);
...
n_tty_set_room(tty);
return retval;
}

“读”过程干了哪些事:

1、将当前进程加入等待队列

2、设置当前进程可中断

3、进程调度,当前进程进入休眠

4、在某处被唤醒

5、从 tty->read_buf 取出数据,通过 tty_put_user 拷贝到用户空间。

那么,在何处唤醒,猜测应该是在中断处理函数中,当DATA寄存器满,触发中断,中断处理函数中调用 tty_flip_buffer_push 。

void tty_flip_buffer_push(struct tty_struct *tty)
{
unsigned long flags;
spin_lock_irqsave(&tty->buf.lock, flags);
if (tty->buf.tail != NULL)
tty->buf.tail->commit = tty->buf.tail->used;
spin_unlock_irqrestore(&tty->buf.lock, flags); if (tty->low_latency)
flush_to_ldisc(&tty->buf.work.work);
else
schedule_delayed_work(&tty->buf.work, 1);
}

tty_flip_buffer_push 有两种方式调用到 flush_to_ldisc ,一种直接调用,另一种使用延时工作队列,在很久很久以前,我们初始化了这么一个工作队列~(tty_open 初始化 tty_struct 时前面有提到)。

在flush_to_ldisc 会调用到 disc->ops->receive_buf ,对于 N_TTY 来说是 n_tty_receive_buf ,在 n_tty_receive_buf 中,将数据拷贝到 tty->read_buf ,然后 wake_up_interruptible(&tty->read_wait) 唤醒休眠队列。

然后就是前面提到的,在n_tty_read 函数中 从 tty->read_buf 里取出数据拷贝到用户空间了。

至此,关于 uart 的框架分析基本就结束了, 对于 tty 以及线路规程是什么东西,大概了解是个什么东西。虽然大部分东西都不需要我们自己实现,但是了解它们有益无害。

下一篇文章--以 s3c2440 为例,分析底层的操作函数,以及 s3c2440 是如何初始化 uart_port 结构的,,这些是在移植驱动过程中需要做的工作~

                                                                  --END--

关注公众号百问科技(ID:baiwenkeji)第一时间学习嵌入式干货。

技术交流加个人威信13266630429,验证: 博客园

tty初探 — uart驱动框架分析的更多相关文章

  1. uart驱动框架分析(二)uart_add_one_port

    作者:lizuobin (百问网论坛答疑助手) 原文: https://blog.csdn.net/lizuobin2/article/details/51801183 (所用开发板:mini2440 ...

  2. Linux USB驱动框架分析(2)【转】

    转自:http://blog.chinaunix.net/uid-23046336-id-3243543.html   看了http://blog.chinaunix.net/uid-11848011 ...

  3. Linux USB驱动框架分析 【转】

    转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...

  4. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

  5. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  6. Linux USB驱动框架分析【转】

    转自:http://blog.csdn.net/jeffade/article/details/7701431 Linux USB驱动框架分析(一) 初次接触和OS相关的设备驱动编写,感觉还挺有意思的 ...

  7. 【原创】Linux PCI驱动框架分析(二)

    背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...

  8. 【原创】Linux PCI驱动框架分析(三)

    背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...

  9. Linux下USB驱动框架分析【转】

    转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...

随机推荐

  1. JD笔试试题(凭记忆写的+人生感悟 try finally )

    京东笔试:技术篇(一套卷.包含測试.算法,研发) 一:填空题(4分 * 15) 15 个 涉及的面很广的选择题,可是比較側重基础.包含数据结构的.c++类的,操作系统的,计算机网络的. 二:编程题(2 ...

  2. java性能监控工具jstat-windows

    jstat Monitors Java Virtual Machine (JVM) statistics. This command is experimental and unsupported. ...

  3. Quality control

    定义测试         为测试添加测试项     测试项目按类型分2种 Qualitative 定性,描述类的,比如颜色,是,否 Quantitative 定量,有明确的衡量         定性 ...

  4. 【搞机】9.7英寸iPad Pro 上手开箱(图文)

    前言 话说,去年就一直想买个iPad ,可是苦于iPad air 2 出的太早,立即就要更新了,就没有买. 今年出了9.7英寸的iPad Pro .看到添加了这么多新特性.就按耐不住心情预订了~ 还刻 ...

  5. Sencha Touch 之初接触

    1.Sencha Touch开发与普通web开发有什么区别? Sencha Touch(为方便起见,本文后面一律简写为ST)页面的开发跟普通html页面相比,总体来说没有本质上的区别,只是引入了对ht ...

  6. ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开

    ASP.NET MVC Filters 4种默认过滤器的使用[附示例]   过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...

  7. http://www.cnblogs.com/yycxbjl/archive/2010/04/20/1716689.html

    http://www.cnblogs.com/yycxbjl/archive/2010/04/20/1716689.html PS: 开发工具 VS2010, 所有工程都为Debug状态,本人刚接触 ...

  8. vs2010音频文件压缩 调用lame_enc.dll将WAV格式转换成MP3

    /* //My_lame.h */ #pragma once#include "stdafx.h"#include <windows.h>#include <st ...

  9. class中的私有属性的访问

    在类中的私有属性设置: class Name(): __init__(self): self.__name = 'arnol'` 如何查看: 1,在类中定义一个方法: def getname(self ...

  10. 为什么在 Java 中用 (low+high)>>>1 代替 (low+high)/2 或 (low+high)>>1 来计算平均值呢?好在哪里?

    >>>与>>是位运算符,只对整型有效(不能用于浮点型).当是整型的时候(low+high)>>1可以代替(low+high)/2.>>>是无 ...