系列讲解了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.c static 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.c static 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);
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)