系列讲解了uart-tty驱动框架以及open、write在驱动里的调用。本章研究下用户层读函数read如何从驱动中获取数据。
驱动调用解析Read和write操作就会交给line discipline处理。调用Open/Read/Write则调用驱动tty_open/tty_read/tty_write
// line discipline结构体 // include/linux/tty_ldisc.h struct tty_ldisc { struct tty_ldisc_ops *ops; struct tty_struct *tty;};// drivers/tty/tty_io.cstatic ssize_t tty_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) -> struct tty_struct *tty = file_tty(file); -> struct tty_ldisc *ld; -> ld = tty_ldisc_ref_wait(tty); // 等待ld整理出来 -> if (ld->ops->read) i = iterate_tty_read(ld, tty, file, to); -> ....、size = ld->ops->read(tty, file, buf, count); //调用到了ldisc层(线路规程)的read函数
line discipline结构体 初始化与操作函数
// drivers/tty/n_tty.cstatic struct tty_ldisc_ops n_tty_ops = { .magic = TTY_LDISC_MAGIC, .name = "n_tty", .open = n_tty_open, .close = n_tty_close, .flush_buffer = n_tty_flush_buffer, .read = n_tty_read, .write = n_tty_write, .ioctl = n_tty_ioctl, .set_termios = n_tty_set_termios, .poll = n_tty_poll, .receive_buf = n_tty_receive_buf, .write_wakeup = n_tty_write_wakeup, .receive_buf2 = n_tty_receive_buf2,};void n_tty_inherit_ops(struct tty_ldisc_ops *ops){ *ops = n_tty_ops; ops->owner = NULL; ops->refcount = ops->flags = 0;}EXPORT_SYMBOL_GPL(n_tty_inherit_ops);
调用到了ldisc层(线路规程)的read函数为n_tty_read。n_tty_read函数和n_tty_write函数直接调用下一层uart_write不一样。n_tty_read()函数是从缓冲区中读取数据的。
注:
tty_read()读不到东西会阻塞
tty_read() ----> ld->ops->read() ----> n_tty_read()
n_tty_read()中add_wait_queue(&tty->read_wait, &wait)没有数据的时候上层的read进程阻塞在此
而在串口有数据来的时候n_tty_receive_buf()—>wake_up_interruptible(&tty->read_wait),唤醒上面的read进程n_tty_read()中会继续运行,将数据拷到用户空间
n_tty_read -> if (file->f_flags & O_NONBLOCK) { // 用于一些数据的初始化和校验。把数据拷贝到用户空间分两种情况处理:一种是标准模式,另一种是非标准模式。在标准模式下,如果没有设置O_NONBLOCK,读操作只有在遇到文件结束符或者各行的字符都已编辑完毕后才返回。 if (!mutex_trylock(&ldata->atomic_read_lock)) return -EAGAIN; } else { if (mutex_lock_interruptible(&ldata->atomic_read_lock)) return -ERESTARTSYS; } down_read(&tty->termios_rwsem); -> minimum = time = 0; timeout = MAX_SCHEDULE_TIMEOUT; if (!ldata->icanon) { minimum = MIN_CHAR(tty); // 获取termios.c_cc[VMIN]数组的值,作为本次读取操作能够读取到的最大数据量; if (minimum) { // #define MIN_CHAr(tty) ((tty)->termios.c_cc[VMIN]) time = (HZ / 10) * TIME_CHAR(tty); } else { timeout = (HZ / 10) * TIME_CHAR(tty); minimum = 1; // 设置minimum的值是1,即缓冲区中的数据量超过1个,就要唤醒读取进程; } } packet = tty->packet; tail = ldata->read_tail; add_wait_queue(&tty->read_wait, &wait); // 将tty->read_wait放入队列中,有数据就唤醒函数
缓冲区里有数据就会唤醒n_tty_read函数继续运行,将缓冲区里的数据拷贝到用户空间
unsigned char __user *b = buf; // 指针b定义时指向的是用户空间的buf缓存,用来保存读取到的数据,随着字符的读出而向后递增;(b-buf)是已经读出的字符数;while (nr) { if (packet && tty->link->ctrl_status) { unsigned char cs; if (b != buf) break; spin_lock_irq(&tty->link->ctrl_lock); cs = tty->link->ctrl_status; tty->link->ctrl_status = 0; spin_unlock_irq(&tty->link->ctrl_lock); if (put_user(cs, b)) { retval = -EFAULT; break; } b++; nr--; break; } if (!input_available_p(tty, 0)) { // 检查输入缓冲区中是否有数据,通过检查原始字符的数量 up_read(&tty->termios_rwsem); tty_buffer_flush_work(tty->port); down_read(&tty->termios_rwsem); 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 (test_bit(TTY_HUPPING, &tty->flags)) break; if (!timeout) break; if (tty_io_nonblock(tty, file)) { retval = -EAGAIN; break; } if (signal_pending(current)) { retval = -ERESTARTSYS; break; } up_read(&tty->termios_rwsem); timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, timeout); down_read(&tty->termios_rwsem); continue; } } // 如果缓冲区中没有数据可以读取,当前进程要休眠等待,直到缓冲区有数据可以读取时才会被唤醒; // 假定此时缓冲区中没有数据,当前进程进入休眠;之后缓冲区有数据时,当前进程被唤醒并调度运行; if (ldata->icanon && !L_EXTPROC(tty)) { // 当前进程被唤醒时,此时缓冲区中应该有可以读取的数据; // 在规范模式下,缓冲区中的字符是经过加工了的,要累积到一个缓冲行才会唤醒等待读出的进程(缓冲行,即碰到’n’字符); // 此时的读取操作在canon_copy_from_read_buf()函数中完成 retval = canon_copy_from_read_buf(tty, &b, &nr); if (retval) break; } else { int uncopied; if (packet && b == buf) { if (put_user(TIOCPKT_DATA, b)) { retval = -EFAULT; break; } b++; nr--; } // 在非规范模式下,缓冲区中的字符是未经加工的,不存在缓冲行的概念,在原始模式可以把字符’’复制到用户空间,这里使用copy_from_read_buf()函数进行成片的拷贝; // 由于缓冲区是环形的,缓冲的字符可能跨越环形缓冲区的结尾,被分割成两部分,所以要使用copy_from_read_buf()函数两次; uncopied = copy_from_read_buf(tty, &b, &nr); uncopied += copy_from_read_buf(tty, &b, &nr); if (uncopied) { retval = -EFAULT; break; } } n_tty_check_unthrottle(tty); // 缓冲区的阀门,后面简介 if (b - buf >= minimum) // 指针buf指向用户空间的缓冲区,指针b指向该缓冲区中的下一个空闲位置,(b-buf)是已经读入buf缓冲区中的字符数量; break; // 如果(b - buf >= minimum),则本次读取结束; if (time) timeout = time;}
在控制台的线路规程中,使用struct n_tty_data结构体表示该设备的数据;其中包含的read_buf成员作为读取的缓冲区使用;
struct n_tty_data { size_t read_head; char read_buf[N_TTY_BUF_SIZE]; // #define N_TTY_BUF_SIZE 4096 size_t read_tail;}
定义的read_buf缓冲区是线性数组,但是却是作为环形缓冲区使用的;read_head成员是环形缓冲区空闲位置的开始,产生数据的进程从read_head位置开始往缓冲区写入数据;read_tail成员是环形缓冲区保存数据位置的开始,读取数据的进程从read_tail位置开始从缓冲区读取数据;
tty->read_buf[]// 环形缓冲区;tty->read_tail// 指向缓冲区当前可以读取的第一个字符;tty->read_head// 指向缓冲区当前可以写入的第一个地址;
缓冲区的阀门
n_tty_check_unthrottle(tty);// 缓冲区是环形的,空间也是有限的;如果缓冲区数据来的太快,应用程序来不及从缓冲区读取数据;// 为了防止环形缓冲区中的数据被覆盖,底层的驱动程序可能因为缓冲区已满而暂时关闭了“阀门”,禁止数据继续进入缓冲区;static void n_tty_check_unthrottle(struct tty_struct *tty){ if (tty->driver->type == TTY_DRIVER_TYPE_PTY) { if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) return; n_tty_kick_worker(tty); tty_wakeup(tty->link); return; } while (1) { int unthrottled; tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE); if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) // 检查缓冲区,如果缓冲区中剩余的字符数量减少到了关闭阀门的要求以下 break; n_tty_kick_worker(tty); unthrottled = tty_unthrottle_safe(tty); // 调用tty_unthrottle_safe()函数重新打开“阀门”,数据就可以重新进入缓冲区 if (!unthrottled) break; } __tty_set_flow_change(tty, 0);}
回到n_tty_read函数的最后一部分
if (tail != ldata->read_tail) n_tty_kick_worker(tty);up_read(&tty->termios_rwsem);remove_wait_queue(&tty->read_wait, &wait); // 当前进程已经读取到了所要求的输入,需要放在临界区的操作已完成,读取操作已经完成,将当前进程从等待read_wait中移除mutex_unlock(&ldata->atomic_read_lock);if (b - buf) retval = b - buf;return retval; // n_tty_read()函数以读取到的字符数量为返回值;
上述讲到通过canon_copy_from_read_buf和copy_from_read_buf将数值传到用户空间
// canon_copy_from_read_buf()函数只有在规范模式下会被调用,该函数按缓冲行将数据从tty缓冲区中读取到用户空间;通过copy_to_user函数完成数据拷贝static int canon_copy_from_read_buf(struct tty_struct *tty, unsigned char __user **b, size_t *nr) -> ret = tty_copy_to_user(tty, *b, tail, n); -> size_t size = N_TTY_BUF_SIZE - tail; -> void *from = read_buf_addr(ldata, tail); -> return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; -> uncopied = copy_to_user(to, from, size);
// copy_from_read_buf()函数在非规范模式下,将数据从tty缓冲区中直接读取到用户空间。// 该函数会被调用两次,第一次是从tty->disc_data->read_tail指针指向的位置到缓冲区结尾,第二次是从缓冲区开头,到tty->disc_data->read_head指针指向的位置;// 该函数的读取操作需要在ldata->atomic_read_lock信号锁的保护下进行;static int copy_from_read_buf(struct tty_struct *tty, unsigned char __user **b, size_t *nr){ struct n_tty_data *ldata = tty->disc_data; int retval; size_t n; bool is_eof; size_t head = smp_load_acquire(&ldata->commit_head); size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1); retval = 0; n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail); n = min(*nr, n); if (n) { unsigned char *from = read_buf_addr(ldata, tail); retval = copy_to_user(*b, from, n); // 通过copy_to_user函数完成数据拷贝 n -= retval; is_eof = n == 1 && *from == EOF_CHAR(tty); tty_audit_add_data(tty, from, n); zero_buffer(tty, from, n); smp_store_release(&ldata->read_tail, ldata->read_tail + n); if (L_EXTPROC(tty) && ldata->icanon && is_eof && (head == ldata->read_tail)) n = 0; *b += n; *nr -= n; } return retval;}
无论是规范还是不规范的模式下,都是从ldata->read_buf[xxx]里获取数据传到用户空间的,那么是什么时候或是什么函数将数据填入这个数组中呢?
// 调用结构体struct tty_ldisc_ops n_tty_ops 中的 .receive_buf = n_tty_receive_buf,n_tty_receive_buf -> n_tty_receive_buf_common -> __receive_buf(tty, cp, fp, n); -> if (!preops && !I_PARMRK(tty)) n_tty_receive_buf_fast(tty, cp, fp, count); else n_tty_receive_buf_standard(tty, cp, fp, count); -> wake_up_interruptible_poll(&tty->read_wait, EPOLLIN); // 唤醒
以RK3568串口分析,读数据是会调用中断,通过tty_schedule_flip将数据搬至线路规程层
// 函数serial8250_init中将struct uart_8250_port *up->ops = &univ8250_driver_ops;初始化static const struct uart_8250_ops univ8250_driver_ops = { .setup_irq = univ8250_setup_irq, .release_irq = univ8250_release_irq,};// 串口读数据时会调用中断univ8250_setup_irq serial_link_irq_chain -> serial8250_interrupt // drivers/tty/serial/8250/8250_core.c -> dw8250_handle_irq // drivers/tty/serial/8250/8250_dw.c -> serial8250_handle_irq // drivers/tty/serial/8250/8250_port.c -> if (!up->dma || handle_rx_dma(up, iir)) // up->dma->rx_dma(up) status = serial8250_rx_chars(up, status); -> do { serial8250_read_char(up, lsr); if (--max_count == 0) break; lsr = serial_in(up, UART_LSR); } while (lsr & (UART_LSR_DR | UART_LSR_BI)); tty_flip_buffer_push(&port->state->port); // 将数据插入接收数据缓冲区 -> tty_schedule_flip(port); // 将数据搬至线路规程层 // 是谁调用了univ8250_setup_irq函数呢?// 调用struct uart_ops serial8250_pops结构体中的 .startup = serial8250_startup,serial8250_startup // drivers/tty/serial/8250/8250_port.c -> serial8250_do_startup -> retval = up->ops->setup_irq(up);