Linux tty驱动学习 - UART驱动的write操作流程

从tty核心层到最后把数据写入到硬件,整个操作流程如下:tty_write() -> do_tty_write() -> n_tty_write() -> uart_write() -> serial8250_start_tx() -> serial_out()。也就是从tty核心层到线路规程,然后到tty驱动层,再到UART驱动层,最后到UART端口的输出寄存器中。

首先看tty核心的写操作tty_write(),它从file的私有数据中得到tty_struct,再根据tty_struct获得对应的线路规程ld,最后以ld->ops->write为参数调用do_tty_write()。do_tty_write主要对要写入的数据块大小进行处理,然后调用作为参数传递进来的线路规程写函数来写数据到tty驱动层。发送到tty驱动层的数据块大小chunk初始化为2048,但是如果设置了TTY_NO_WRITE_SPLIT标志,则设为65536,即64K大小。如果tty核心层处理的数据大小tty->write_cnt小于chunk,则把tty->write_cnt调整为chunk大小,并重新为tty->write_buf申请内存空间。之后把数据从用户空间拷贝到tty->write_buf,然后调用线路规程N_TTY的n_tty_write()函数来进行进一步的操作。

static inline ssize_t do_tty_write(ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
	struct tty_struct *tty,struct file *file,const char __user *buf,size_t count)
{
	ssize_t ret, written = 0;
	unsigned int chunk;

	ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
	if (ret < 0)
		return ret;

	chunk = 2048;
	if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
		chunk = 65536;
	if (count < chunk)
		chunk = count;

	/* write_buf/write_cnt is protected by the atomic_write_lock mutex */
	if (tty->write_cnt < chunk) {
		unsigned char *buf_chunk;

		if (chunk < 1024)
			chunk = 1024;

		buf_chunk = kmalloc(chunk, GFP_KERNEL);
		if (!buf_chunk) {
			ret = -ENOMEM;
			goto out;
		}
		kfree(tty->write_buf);
		tty->write_cnt = chunk;
		tty->write_buf = buf_chunk;
	}

	/* Do the write .. */
	for (;;) {
		size_t size = count;
		if (size > chunk)
			size = chunk;
		ret = -EFAULT;
		if (copy_from_user(tty->write_buf, buf, size))
			break;
		ret = write(tty, file, tty->write_buf, size);
		if (ret <= 0)
			break;
		written += ret;
		buf += ret;
		count -= ret;
		if (!count)
			break;
		ret = -ERESTARTSYS;
		if (signal_pending(current))
			break;
		cond_resched();
	}
	if (written) {
		struct inode *inode = file->f_path.dentry->d_inode;
		inode->i_mtime = current_fs_time(inode->i_sb);
		ret = written;
	}
out:
	tty_write_unlock(tty);
	return ret;
}

n_tty_write()函数在n_tty.c中,它首先以当前进程初始化一个等待队列体,然后把该等待队列体加入到tty的等待队列头tty->write_wait中。在写操作的循环中,首先把当前进程的状态设置为TASK_INTERRUPTIBLE。然后根据发送的数据格式进行不同的处理,如果发送的数据要先进行预处理,则调用process_output_block()和process_output()先进行数据处理,然后把数据写入到uart驱动的发送缓存池中,最后调用tty驱动的flush_chars()把数据从发送缓存池送到uart端口中。如果数据不需要进行预处理,则直接调用tty驱动的write()函数,该函数会先把数据存在驱动的缓存池中,然后再发送到uart端口。如果往uart驱动的缓存池写数据失败,表示当前缓存池已满,无法继续进行数据的发送,那么后面的schedule()函数会被执行,该进程进入睡眠等待状态,系统会去调度执行其它的进程,直到有其它的进程通过tty->write_wait唤醒该进程。

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;

	/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
	if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
		retval = tty_check_change(tty);
		if (retval)
			return retval;
	}

	/* Write out any echoed characters that are still pending */
	process_echoes(tty);

	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))) {
			while (nr > 0) {
				ssize_t num = process_output_block(tty, b, nr);
				if (num < 0) {
					if (num == -EAGAIN)
						break;
					retval = num;
					goto break_out;
				}
				b += num;
				nr -= num;
				if (nr == 0)
					break;
				c = *b;
				if (process_output(c, tty) < 0)
					break;
				b++; nr--;
			}
			if (tty->ops->flush_chars)
				tty->ops->flush_chars(tty);
		} else {
			while (nr > 0) {
				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();
	}
break_out:
	__set_current_state(TASK_RUNNING);
	remove_wait_queue(&tty->write_wait, &wait);
	if (b - buf != nr && tty->fasync)
		set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
	return (b - buf) ? b - buf : retval;
}

tty驱动里面的write操作函数为uart_write(),该函数先把线路规程传递过来的数据保存到uart驱动的缓存池中,然后调用uart_start()函数发送数据。而uart驱动的flush_chars()函数uart_flush_chars()也是调用uart_start()来发送数据。

static int
uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
	struct uart_state *state = tty->driver_data;
	struct uart_port *port;
	struct circ_buf *circ;
	unsigned long flags;
	int c, ret = 0;

	/*
	 * This means you called this function _after_ the port was
	 * closed.  No cookie for you.
	 */
	if (!state) {
		WARN_ON(1);
		return -EL3HLT;
	}

	port = state->uart_port;
	circ = &state->xmit;

	if (!circ->buf)
		return 0;

	spin_lock_irqsave(&port->lock, flags);
	while (1) {
		c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
		if (count < c)
			c = count;
		if (c <= 0)
			break;
		memcpy(circ->buf + circ->head, buf, c);
		circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
		buf += c;
		count -= c;
		ret += c;
	}
	spin_unlock_irqrestore(&port->lock, flags);

	uart_start(tty);
	return ret;
}

static void uart_flush_chars(struct tty_struct *tty)
{
	uart_start(tty);
}

uart_start()的执行流程为:uart_start() -> __uart_start() -> port->ops->start_tx()。所以最后执行的是uart驱动的start_tx()函数,即serial8250_start_tx()。在serial8250_start_tx()中首先判断中断使能寄存器的状态,如果没有开启THRI中断,则把该中断打开,这样的话如果数据从发送寄存器送到移位寄存器就会产生中断。然后去读取线路状态寄存器,如果当前的发送寄存器为空,则调用transmit_charts()把数据送到发送寄存器中。transmit_chars()函数会先判断是否要发送xon/xoff字符,然后再判断是否设置的stop标志,如果设置则停止发送,同样如果驱动的发送缓存池为空,也要中止发送。然后在一个while里调用serial_out()发送数据到发送寄存器, 循环执行的次数为count,count被赋值为up->tx_loadsz,该值在配置uart端口的时候被设置,不同型号的芯片设置的大小不一样,像16550被设置为1。也就是说transmit_chars()函数每次只会发送一个字节的数据到发送寄存器中,但是因为在serial8250_start_tx()中有打开了THRI中断,所以当发送寄存器的数据发送出去后,就会产生一个中断,在uart中断处理函数的分析中有提到,如果是发送数据产生的中断,就会调用transmit_chars()函数发送数据。所以transmit_chars()函数会多次执行,直到所有的数据发送完成。uart_circ_chars_pending()函数用来获取uart驱动缓存池中待发送的数据大小,如果待发送的数据小于WAKEUP_CHARS(256 bytes),就会执行uart_write_wakeup()函数。

static void serial8250_start_tx(struct uart_port *port)
{
	struct uart_8250_port *up = (struct uart_8250_port *)port;

	if (!(up->ier & UART_IER_THRI)) {
		up->ier |= UART_IER_THRI;
		serial_out(up, UART_IER, up->ier);

		if (up->bugs & UART_BUG_TXEN) {
			unsigned char lsr;
			lsr = serial_in(up, UART_LSR);
			up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;
			if ((up->port.type == PORT_RM9000) ?
				(lsr & UART_LSR_THRE) :
				(lsr & UART_LSR_TEMT))
				transmit_chars(up);
		}
	}

	/*
	 * Re-enable the transmitter if we disabled it.
	 */
	if (up->port.type == PORT_16C950 && up->acr & UART_ACR_TXDIS) {
		up->acr &= ~UART_ACR_TXDIS;
		serial_icr_write(up, UART_ACR, up->acr);
	}
}

static void transmit_chars(struct uart_8250_port *up)
{
	struct circ_buf *xmit = &up->port.state->xmit;
	int count;

	if (up->port.x_char) {
		serial_outp(up, UART_TX, up->port.x_char);
		up->port.icount.tx++;
		up->port.x_char = 0;
		return;
	}
	if (uart_tx_stopped(&up->port)) {
		serial8250_stop_tx(&up->port);
		return;
	}
	if (uart_circ_empty(xmit)) {
		__stop_tx(up);
		return;
	}

	count = up->tx_loadsz;
	do {
		serial_out(up, UART_TX, xmit->buf[xmit->tail]);
		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
		up->port.icount.tx++;
		if (uart_circ_empty(xmit))
			break;
	} while (--count > 0);

	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
		uart_write_wakeup(&up->port);

	DEBUG_INTR("THRE...");

	if (uart_circ_empty(xmit))
		__stop_tx(up);
}


uart_write_wakep()调用tasklet_schedule()激活state->tlet。uart_state的tasklet是在在uart端口初始化的时候定义的,在uart_register_driver()中有调用tasklet_init()初始化state->tlet,它的操作函数定义为uart_tasklet_action。uart_tasklet_action()中接着调用tty_wakeup(),而在tty_wakeup()中最后调用了wake_up_interruptible_poll(&tty->write_wait, POLLOUT)。在线路规程的写操作n_tty_write()中,如果发现uart驱动的发送缓存池为满的时候就会把自己挂在tty->write_wait中,进入睡眠状态。而到了这里,如果检测到发送缓存池的待发送数据小于256字节,则唤醒n_tty_write()进程,让它继续往uart驱动的发送缓存池中写数据。
void uart_write_wakeup(struct uart_port *port)
{
	struct uart_state *state = port->state;
	/*
	 * This means you called this function _after_ the port was
	 * closed.  No cookie for you.
	 */
	BUG_ON(!state);
	tasklet_schedule(&state->tlet);
}

 

void tty_wakeup(struct tty_struct *tty)
{
	struct tty_ldisc *ld;

	if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) {
		ld = tty_ldisc_ref(tty);
		if (ld) {
			if (ld->ops->write_wakeup)
				ld->ops->write_wakeup(tty);
			tty_ldisc_deref(ld);
		}
	}
	wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
}

从整个写的流程可知,数据从用户空间传递到tty核心层,会根据chunk的大小进行分割。从线路规程到tty驱动,会根据uart驱动发送缓存池的大小再次分割发送的数据,。最后uart驱动把数据从缓存池送到发送寄存器是单个byte进行的,等单个byte发送完成产生中断后,再进行下一个byte的发送。线路规程的写操作在uart驱动的缓存空间不够时会进入睡眠等待状态,停止数据的发送,当驱动的空闲缓存空间达到一定值时,则会被唤醒继续发送数据。

你可能感兴趣的:(Linux内核和驱动学习)