一、简介

本文主要时讲解tty驱动文件的打开、数据的发送、数据的接收相关的源码解析,从用户层到硬件层的源码追踪过程。
具体的读写操作可先大致看一下流程图,下面的源码分析也依然是围绕该流程进行函数追踪。
在这里插入图片描述

二、tty数据接收流程分析

对于tty设备的打开操作,即用户调用read函数来读取设备的文件的数据,首先经过vfs层、字符设备驱动层,到达tty_open()函数,经过tty_core层、serial_core等层后,主要完成一下工作:
1、首先通过dev_t,再tty_driver链表中查找对应的驱动,并返回本设备在该driver中的index即序号;
2、初始化tty_struct,建立tty_struct和tty_driver之间的关联,经tty_struct->ldisc、tty_struct->port进行赋值,最后调用tty_struct->ld->ops->open()设置通讯参数:速率speed、校验位flag等等;
3、初始化tty_file_private,并将priv添加到tty_struct->tty_files;
4、调用tty_struct.ops.open()即n_tty_ops.open=n_tty_open完成设备的打开工作,此时已在tty线路规程,最终调用uart_startup();

打开文件流程图如下:
在这里插入图片描述

2.1 详细代码解析

从流程图中分析,再tty_read()中,代码先通过tty_struct()得到调用线路规程中的read操作:ld->ops->read(),即n_tty_read(),具体代码如下:

 
/**
 *	n_tty_read		-	read function for tty
 *	@tty: tty device
 *	@file: file object
 *	@buf: userspace buffer pointer
 *	@nr: size of I/O
 *
 *	Perform reads for the line discipline. We are guaranteed that the
 *	line discipline will not be closed under us but we may get multiple
 *	parallel readers and must handle this ourselves. We may also get
 *	a hangup. Always called in user context, may sleep.
 *
 *	This code must be sure never to sleep through a hangup.
 */
 
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);
	if (c < 0)
		return c;
 
	minimum = time = 0;
	timeout = MAX_SCHEDULE_TIMEOUT;
	if (!tty->icanon) {  //默认为icanon则该if不会执行
		time = (HZ / 10) * TIME_CHAR(tty);
		minimum = MIN_CHAR(tty);
		if (minimum) {
			if (time)
				tty->minimum_to_wake = 1;
			else if (!waitqueue_active(&tty->read_wait) ||
				 (tty->minimum_to_wake > minimum))
				tty->minimum_to_wake = minimum;
		} else {
			timeout = 0;
			if (time) {
				timeout = time;
				time = 0;
			}
			tty->minimum_to_wake = minimum = 1;
		}
	}
 
	/*
	 *	Internal serialization of reads.
	 */
	if (file->f_flags & O_NONBLOCK) {
		if (!mutex_trylock(&tty->atomic_read_lock))
			return -EAGAIN;
	} else {
		if (mutex_lock_interruptible(&tty->atomic_read_lock))
			return -ERESTARTSYS;
	}
	packet = tty->packet;  //=0,未初始化该成员
 //等待队列的操作
	add_wait_queue(&tty->read_wait, &wait);
	while (nr) {
		/* First test for status change. */
		if (packet && tty->link->ctrl_status) {//packet为0,if不满足
			unsigned char cs;
			if (b != buf)
				break;
			spin_lock_irqsave(&tty->link->ctrl_lock, flags);
			cs = tty->link->ctrl_status;
			tty->link->ctrl_status = 0;
			spin_unlock_irqrestore(&tty->link->ctrl_lock, flags);
			if (tty_put_user(tty, cs, b++)) {
				retval = -EFAULT;
				b--;
				break;
			}
			nr--;
			break;
		}
		/* 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保证用户空间的读进程一直要等待数据可读,否则一直睡眠
		if (!input_available_p(tty, 0)) {
			if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
				retval = -EIO;
				break;
			}
			if (tty_hung_up_p(file))
				break;
			if (!timeout)
				break;
			if (file->f_flags &O_NONBLOCK) {//无数据可读、且非阻塞打开,代表设备正“忙”,则直接返回退出
				retval = -EAGAIN;
				break;
			}
			if (signal_pending(current)) {//本线程是因为信号而被唤醒,不是由于数据可读,也要直接返回退出
				retval = -ERESTARTSYS;
				break;
			}
			/* 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_put_user(tty, TIOCPKT_DATA, b++)) {
				retval = -EFAULT;
				b--;
				break;
			}
			nr--;
		}
 
		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);
				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 {
			int uncopied;
			/* The copy function takes the read lock and handles
			   locking internally for this case */
			uncopied = copy_from_read_buf(tty, &b, &nr);
			uncopied += copy_from_read_buf(tty, &b, &nr);//投递数据到用户空间
			if (uncopied) {
				retval = -EFAULT;
				break;
			}
		}
 
		/* If there is enough space in the read buffer now, let the
		 * low-level driver know. We use n_tty_chars_in_buffer() to
		 * check the buffer, as it now knows about canonical mode.
		 * Otherwise, if the driver is throttled and the line is
		 * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
		 * we won't get any more characters.
		 */
		if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {
			n_tty_set_room(tty);
			check_unthrottle(tty);
		}
 
		if (b - buf >= minimum)//由于minimum=0,因此只要有1个以上的数据就返回
			break;
		if (time)
			timeout = time;
	}
	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);
	size = b - buf;
	if (size) {
		retval = size;
		if (nr)
			clear_bit(TTY_PUSH, &tty->flags);
	} else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
		 goto do_it_again;
 
	n_tty_set_room(tty);
	return retval;
}

该函数就是典型的等待队列使用套路:先查询数据是否可用,不可用就让出cpu,等待其他线程(即终端ISR中间调用flush_to_ldisc())来唤醒。当硬件接收到数据后,在中断处理之后,会将数据投递都ldata.read_buf[]中,然后唤醒该线程,之后本线程就调用copy_from_read_buf()完成数据成ldisc层(ldata.read_buf[])至应用程序缓冲区的复制。

 
static int s3c24xx_serial_startup(struct uart_port *port)
{
	struct s3c24xx_uart_port *ourport = to_ourport(port);
	int ret;
 
	dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",
	    port->mapbase, port->membase);
 
	rx_enabled(port) = 1;  // 使能接收
 
	ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
			  s3c24xx_serial_portname(port), ourport);   // 为数据接收注册中断程序
 
	if (ret != 0) {
		printk(KERN_ERR "cannot get irq %d\n", ourport->rx_irq);
		return ret;
	}
 
	ourport->rx_claimed = 1;  // 使能发送
 
	dbg("requesting tx irq...\n");
 
	tx_enabled(port) = 1;
 
	ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
			  s3c24xx_serial_portname(port), ourport);   // 为数据发送注册中断程序
 
	if (ret) {
		printk(KERN_ERR "cannot get irq %d\n", ourport->tx_irq);
		goto err;
	}
 
	ourport->tx_claimed = 1;
 
	dbg("s3c24xx_serial_startup ok\n");
 
	/* the port reset code should have done the correct
	 * register setup for the port controls */
	if (port->line == 2) {
		s3c2410_gpio_cfgpin(S3C2410_GPH(6), S3C2410_GPH6_TXD2);
		s3c2410_gpio_pullup(S3C2410_GPH(6), 1);
		s3c2410_gpio_cfgpin(S3C2410_GPH(7), S3C2410_GPH7_RXD2);
		s3c2410_gpio_pullup(S3C2410_GPH(7), 1);
	}
 
 
	return ret;
 
 err:
	s3c24xx_serial_shutdown(port);
	return ret;
}

上面函数主要完成下面的工作:
1、使能接收rx_enabled
2、为数据接收注册中断程序request_irq
3、使能发送tx_enabled
4、为数据发送注册中断程序request_irq

2.2 中断处理程序唤醒底层线程

串口接收数据是通过s3c24xx_serial_rx_chars在中断里面进行的

static irqreturn_t
s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
	struct s3c24xx_uart_port *ourport = dev_id;
	struct uart_port *port = &ourport->port;
	struct tty_struct *tty = port->state->port.tty;
	unsigned int ufcon, ch, flag, ufstat, uerstat;
	int max_count = 64;
 
	while (max_count-- > 0) {
		ufcon = rd_regl(port, S3C2410_UFCON);
		ufstat = rd_regl(port, S3C2410_UFSTAT);
 
		if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
			break;
 
		uerstat = rd_regl(port, S3C2410_UERSTAT);
		ch = rd_regb(port, S3C2410_URXH);
 
		if (port->flags & UPF_CONS_FLOW) {
			int txe = s3c24xx_serial_txempty_nofifo(port);
 
			if (rx_enabled(port)) {
				if (!txe) {
					rx_enabled(port) = 0;
					continue;
				}
			} else {
				if (txe) {
					ufcon |= S3C2410_UFCON_RESETRX;
					wr_regl(port, S3C2410_UFCON, ufcon);
					rx_enabled(port) = 1;
					goto out;
				}
				continue;
			}
		}
 
		/* insert the character into the buffer */
 
		flag = TTY_NORMAL;
		port->icount.rx++;
 
		if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
			dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
			    ch, uerstat);
 
			/* check for break */
			if (uerstat & S3C2410_UERSTAT_BREAK) {
				dbg("break!\n");
				port->icount.brk++;
				if (uart_handle_break(port))
				    goto ignore_char;
			}
 
			if (uerstat & S3C2410_UERSTAT_FRAME)
				port->icount.frame++;
			if (uerstat & S3C2410_UERSTAT_OVERRUN)
				port->icount.overrun++;
 
			uerstat &= port->read_status_mask;
 
			if (uerstat & S3C2410_UERSTAT_BREAK)
				flag = TTY_BREAK;
			else if (uerstat & S3C2410_UERSTAT_PARITY)
				flag = TTY_PARITY;
			else if (uerstat & (S3C2410_UERSTAT_FRAME |
					    S3C2410_UERSTAT_OVERRUN))
				flag = TTY_FRAME;
		}
 
		if (uart_handle_sysrq_char(port, ch))
			goto ignore_char;
 
		uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,
				 ch, flag);
 
 ignore_char:
		continue;
	}
	tty_flip_buffer_push(tty);
 
 out:
	return IRQ_HANDLED;
}

该部分源码分析:
1、读取UFCON寄存器;
2、读取UFSTAT寄存器;
3、然后读取接收fifo的数据量s3c24xx_serial_rx_fifocnt(ourport, ufstat),如果数据量为0,则退出处理;
4、读取错误状态寄存器uerstat = rd_regl(port, S3C2410_UERSTAT);
5、然后取出接收到的字符ch = rd_regb(port, S3C2410_URXH);
6、if (port->flags & UPF_CONS_FLOW),这一段代码其实是在做流控的处理;
7、if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) ,这段代码判断错误发生的类型;
8、if (uart_handle_sysrq_char(port, ch))如果接收到的是sysrq这个特殊字符,则进行特殊处理;
9、uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,ch, flag);这一步把接收到的字符送到串口驱动的buf中,这里面大循环就结束了;
10、tty_flip_buffer_push(tty);把串口驱动中的数据送到read_buf中;

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

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

2.3 数据发送过程分析

写过程也可以分为两个线程,但与读过程又有区别。
1、对于传统的串口硬件驱动,发送中断平时都是关闭的,只有用户执行写操作后,才会主动打开TX中断,同时,TX-ISR里自动检测xmit状态,为空则主动关闭TX中断;
2、对于由用户发起的写进程:首先将数据从用户buf填充至uart_state->xmit.buf[];
3、如果用户buf数据过大(>page_size),则wait_woken()该线程,与此同时,只要xmit已满或者用户buf被填充完毕,就开始flush数据至TX-FIFO;
4、对于TX-ISR线程:每发出一个bite,就检查xmit数据状态,当xmit为空时,关闭TX中断,当xmit不够时,唤醒上面的写进场,继续往xmit填充数据,具体流程如下:
在这里插入图片描述具体函数实现如下:
首先时tty_core层的调用

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

然后调用线路规程的n_tty_write:

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

最终调用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 函数

三、其他相关链接

Linux系统TTY串口驱动实例详解

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐