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);}
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 tty驱动学习 - UART驱动的write操作流程
- Linux tty驱动学习 - UART驱动的read操作流程
- Linux tty驱动学习 - UART驱动的open操作
- Linux tty驱动学习 - UART驱动注册
- Linux tty驱动学习 - 串口通信,UART及UART驱动概述
- Linux tty驱动学习 - LDD3的tty驱动
- uart驱动与tty驱动
- linux下uart驱动的开发流程
- Linux tty驱动学习 - 在用户空间设置串口参数操作流程
- Linux设备模型之tty&&uart驱动架构分析
- 嵌入式Linux驱动笔记(七)------浅析tty与uart框架
- Linux的TTY驱动分析
- Linux终端设备驱动 ----UART的驱动
- UART控制器驱动流程
- UART控制器驱动流程
- 嵌入式linux的uart驱动
- tty初探—uart驱动框架分析
- tty初探-uart驱动框架分析
- jquery 控制button的disabled属性
- 在Windows Server 2008 R2上安装Ftp服务
- IOS 时间年月日的加减法
- seo人如何发外链?
- 日志2016-1-6
- Linux tty驱动学习 - UART驱动的write操作流程
- CocoaPod search 查看不到最新版本库问题
- HDOJ 2000 ASCII码排序
- three.js学习笔记 用Tween.js做动画
- JS实现全屏
- 实例9:实现根据身高计算标准体重
- ios Xcode 运行常见错误解决
- Java项目部署总结(三)nginx安装与配置
- Java 字符串与数值、数组之间的转化