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

来源:互联网 发布:系统网管软件 编辑:程序博客网 时间:2024/05/17 09:32

从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 intuart_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驱动的缓存空间不够时会进入睡眠等待状态,停止数据的发送,当驱动的空闲缓存空间达到一定值时,则会被唤醒继续发送数据。

0 0
原创粉丝点击