tty driver(5)

来源:互联网 发布:java数据结构编程 编辑:程序博客网 时间:2024/06/05 02:51

与读操作相关的数据变量有:

struct tty_struct {/*这些变量都是在行规函数中处理的*/
    unsigned int receive_room;/*read_buf中还有多少剩余空间*/
    char *read_buf;/*read_buf的开始地址*/
    int read_head;/*read_buf写的最后位置*/
    int read_tail;/*用户空间从这里开始读出*/
    int read_cnt; /*read_buf中有多少数据,需要用户读出*/
    unsigned long read_flags[128];
}

/*有关 receive room
 * 这两个行规的 receive room差别还是很大的 65536 & N_TTY_BUF_SIZE【4096】
 **/

ppp_asynctty_open(struct tty_struct *tty) -> tty->receive_room = 65536;

static void n_tty_set_room(struct tty_struct *tty)
{
    int    left = N_TTY_BUF_SIZE - tty->read_cnt - 1;
    tty->receive_room = left;
}

/*哪里分配的read memory buffer?
 *当tty_open的时候,在具体行规的open里申请的memory
 **/

n_tty_open -> tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);

/*初始化 read_head,tail and cnt*/
reset_buffer_flags(struct tty_struct *tty) -> tty->read_head = tty->read_tail = tty->read_cnt = 0;


1] 数据从tty_buffer 到 read_buf

flush_to_ldisc -> disc->ops->receive_buf(tty, char_buf,flag_buf, count);

static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
                  char *fp, int count)
{
    const unsigned char *p;
    char *f, flags = TTY_NORMAL;
    int    i;
    char    buf[64];
    unsigned long cpuflags;

    if (!tty->read_buf)
        return;

    if (tty->real_raw) {
        spin_lock_irqsave(&tty->read_lock, cpuflags);
        i = min(N_TTY_BUF_SIZE - tty->read_cnt,
            N_TTY_BUF_SIZE - tty->read_head);
        i = min(count, i);
    /*
      *把来自tty_buffer的数据copy到(tty->read_buf + tty->read_head)开始的地方
     *更新read_head,[从这里看read_head表示数据写到read_buf那里了]
      *更新read_cnt[表示read_buf里有多少数据]
     **/

        memcpy(tty->read_buf + tty->read_head, cp, i);
        tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt += i;
        spin_unlock_irqrestore(&tty->read_lock, cpuflags);
    }

    n_tty_set_room(tty);/*更新 receive_room*/

    /*唤醒read_wait上的进程*/
    if (waitqueue_active(&tty->read_wait))
        wake_up_interruptible(&tty->read_wait);

    /*
     * Check the remaining room for the input canonicalization
     * mode.  We don't want to throttle the driver if we're in
     * canonical mode and don't have a newline yet!
     */

    if (tty->receive_room < TTY_THRESHOLD_THROTTLE)
        tty_throttle(tty);
}

2] 从read_buf到用户空间buffer

tty_read -> n_tty_read{
    copy_from_read_buf(tty, &b, &nr);
    /* If there is enough space in the read buffer now, let the
     * low-level driver know. We use n_tty_chars_in_buffer() to
     * check the buffer, as it now knows about canonical mode.
     * Otherwise, if the driver is throttled and the line is
     * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
     * we won't get any more characters.
     */

    if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {
        n_tty_set_room(tty);
        check_unthrottle(tty);
    }
    n_tty_set_room(tty);

}

/**
 *    copy_from_read_buf    -    copy read data directly
 *    @tty: terminal device
 *    @b: user data
 *    @nr: size of data
 *
 *    Helper function to speed up n_tty_read.  It is only called when
 *    ICANON is off; it copies characters straight from the tty queue to
 *    user space directly.  It can be profitably called twice;
 *      once to drain the space from the tail pointer to the (physical) end of the buffer
 *    and once to drain the space from the (physical) beginning of the buffer to head pointer.
 **/

static int copy_from_read_buf(struct tty_struct *tty, unsigned char __user **b, size_t *nr)
{
    copy_to_user(*b, &tty->read_buf[tty->read_tail], n);
    tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
    tty->read_cnt -= n;
}

/*unthrottle的判断标准*/
n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE;
static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)
{
    unsigned long flags;
    ssize_t n = 0;

    spin_lock_irqsave(&tty->read_lock, flags);
    if (!tty->icanon) {
        n = tty->read_cnt;
    }
    spin_unlock_irqrestore(&tty->read_lock, flags);
    return n;
}


tty driver 读操作,这里缺张图:描述memory copy的过程,各个 memory copy 在那个函数中实现的。

/***********************************************************************************/

(一)数据从中断处理程序到tty_buffer

/***********************************************************************************/
有关tty buffer的数据结构:
struct tty_buffer {
    struct tty_buffer *next;
    /*char_bur_ptr and flag_buf_ptr的数据类型为什么不一样?*/
    char *char_buf_ptr;
    unsigned char *flag_buf_ptr;
    int used;
    int size;
    int commit;
    int read;
    /* Data points here */
    unsigned long data[0];
};

/*tty_struct操作的数据结构是tty_bufhead,不是tty_buffer,需要用tty_buffer 初始化tty_bufhead*/
struct tty_bufhead {
    struct work_struct work;
    spinlock_t lock;
    struct tty_buffer *head;    /* Queue head */
    struct tty_buffer *tail;    /* Active buffer */
    struct tty_buffer *free;    /* Free queue head */
    int memory_used;        /* Buffer space used excluding free queue */
};

/*    Allocate a new tty buffer to hold the desired number of characters.
 *    Return NULL if out of memory or the allocation would exceed the
 *    per device queue
 */
static struct tty_buffer *tty_buffer_alloc(struct tty_struct *tty, size_t size)
{
    struct tty_buffer *p;

    if (tty->buf.memory_used + size > 65536)
        return NULL;
    /*分配的memory包含 struct tty_buffer and data regions: char and flag*/
    p = kmalloc(sizeof(struct tty_buffer) + 2 * size, GFP_ATOMIC);
    if (p == NULL)
        return NULL;
    /*member size描述 char data size*/
    p->size = size;
    /*通过该指针形成一个link?*/
    p->next = NULL;
    
    /* commit, read, used 描述什么?*/
    p->commit = 0;
    p->read = 0;
    p->used = 0;
    /*data[0]就是这个用途?否则的话要 + sizeof()*/
    p->char_buf_ptr = (char *)(p->data);
    p->flag_buf_ptr = (unsigned char *)p->char_buf_ptr + size;
    /*Buffer space used,但不是占用的 memory,使用的memory有个2倍等*/
    tty->buf.memory_used += size;
    return p;
}

/*    Make at least size bytes of linear space available for the tty
 *    buffer. If we fail return the size we managed to find.
 */
int tty_buffer_request_room(struct tty_struct *tty, size_t size)
{
    struct tty_buffer *b, *n;
    int left;
    unsigned long flags;

    spin_lock_irqsave(&tty->buf.lock, flags);

    /* OPTIMISATION: We could keep a per tty "zero" sized buffer to
       remove this conditional if its worth it. This would be invisible
       to the callers
         * 什么时候初始化的tty->buf.tail,何时分配的memory?
        void tty_buffer_init(struct tty_struct *tty)
        {
            spin_lock_init(&tty->buf.lock);
            tty->buf.head = NULL;
            tty->buf.tail = NULL;
            tty->buf.free = NULL;
            tty->buf.memory_used = 0;
            INIT_WORK(&tty->buf.work, flush_to_ldisc);
        }
      指针变量的初始化是在函数tty_buffer_init,而memory的分配就是在该函数中通过
       tty_buffer_find得到的。
     */
    /*1] 如果最后一个即tail.tty_buf存在,则判断还有多少空间,
     *   如果存在但剩余的空间不够,则要构建tty_buff link,and commit <- used
         *   如果不存在tail.tty_buf,则要调用函数tty_buffer_find找到或创建一个tty_buffer
         *2] 只要当前的 tty->buf.tail,不满足size要求,tty->buf.tail会被更新,如果是第一个
         *    tty_buffer, tty->buf.head 也会被更新
     */
    if ((b = tty->buf.tail) != NULL)
        left = b->size - b->used;
    else
        left = 0;

    if (left < size) {
        /* This is the slow path - looking for new buffers to use */
        if ((n = tty_buffer_find(tty, size)) != NULL) {
            if (b != NULL) {
                b->next = n;
                b->commit = b->used;
            } else
                tty->buf.head = n;
            tty->buf.tail = n;
        } else
            size = left;
    }

    spin_unlock_irqrestore(&tty->buf.lock, flags);
    return size;
}
这里假设第一次read data,也就是tty->buf.tail = NULL;tty->buf.free = NULL;
此时:tty_buffer_find -> tty_buffer_alloc.

int tty_buffer_request_room(struct tty_struct *tty, size_t size)
{
    struct tty_buffer *b, *n;
    int left;
    n = tty_buffer_find(tty, size);
    tty->buf.head = n;
    tty->buf.tail = n;
    return size;
}

/*    Prepare a block of space in the buffer for data. Returns the length
 *    available and buffer pointer to the space which is now allocated and
 *    accounted for as ready for normal characters. This is used for drivers
 *    that need their own block copy routines into the buffer.
 */
int tty_prepare_flip_string(struct tty_struct *tty, unsigned char **chars,
                                size_t size)
{
    /*tty_buffer_request_room得到一个tty_buffer*/
    int space = tty_buffer_request_room(tty, size);
    if (likely(space)) {
        struct tty_buffer *tb = tty->buf.tail;
        /*存放char data的位置*/
        *chars = tb->char_buf_ptr + tb->used;
        /*初始化flag region*/
        memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space);
        /*还没往这个char data开始的位置赋值, used已经更新*/
        tb->used += space;
    }
    return space;
}

/*通过tty_prepare_flip_string得到char data的存储位置后,就要给他赋值*/
/*---------------------------------------------------------------------------*/
static void smd_ch_irq_tasklet_handler(unsigned long data)
{
    -----

    for (;;) {
        /*1]中断处理函数read到数据;
                 *2]申请tty_buffer去保存该数据;
         *3]把中断得到数据copy到tty_buffer中;
           *4]调用tty_flip_buffer_push
         */
        avail = smd_stream_read_avail(tty_info->ch);
        if (avail == 0)
            break;
        
        avail = tty_prepare_flip_string(tty, &ptr, avail);
        smd_stream_read(tty_info->ch,ptr,avail);
        smd_send_intr(tty_info->a2b_int_rx);        
        tty_flip_buffer_push(tty);
    }

    tty_kref_put(tty);
}

/***********************************************************************************/

(二)数据从tty_buffer拷贝到 tty read_buf

/***********************************************************************************/
/*以上内容是如何把中断得到数据保存到tty_buffer中, 下一步是tty_flip_buffer_push*/

void tty_flip_buffer_push(struct tty_struct *tty)
{
    unsigned long flags;
    spin_lock_irqsave(&tty->buf.lock, flags);
    if (tty->buf.tail != NULL)
    /*update the commit to used*/
        tty->buf.tail->commit = tty->buf.tail->used;
    spin_unlock_irqrestore(&tty->buf.lock, flags);
    /*schedule_work的work function 就是flush_to_ldisc*/
    if (tty->low_latency)
        flush_to_ldisc(&tty->buf.work);
    else
        schedule_work(&tty->buf.work);
}

/*flush_to_ldisc的任务只是把tty->buf.head指向的list的所有数据调用函数n_tty receive_buf传送出去
 *
 */
static void flush_to_ldisc(struct work_struct *work)
{
    struct tty_struct *tty =
        container_of(work, struct tty_struct, buf.work);
    unsigned long     flags;
    struct tty_ldisc *disc;

    disc = tty_ldisc_ref(tty);
    if (disc == NULL)    /*  !TTY_LDISC */
        return;

    spin_lock_irqsave(&tty->buf.lock, flags);

    if (!test_and_set_bit(TTY_FLUSHING, &tty->flags)) {
        struct tty_buffer *head;
        while ((head = tty->buf.head) != NULL) {
            int count;
            char *char_buf;
            unsigned char *flag_buf;
            /*1] 判断read, commit位置看是否有数据需要读
               *   如果没有,判断下一个 tty_buffer, 并释放当前没有读数据的tty_buffer*/
            count = head->commit - head->read;
            if (!count) {
                if (head->next == NULL)
                    break;
                tty->buf.head = head->next;
                tty_buffer_free(tty, head);
                continue;
            }
            /* Ldisc or user is trying to flush the buffers
               we are feeding to the ldisc, stop feeding the
               line discipline as we want to empty the queue */
            if (test_bit(TTY_FLUSHPENDING, &tty->flags))
                break;
            if (!tty->receive_room)
                break;
            /*2] tty->receive_room是如何更新的?*/
            if (count > tty->receive_room)
                count = tty->receive_room;
            /*3] 更新pointer and call into line discipline operations*/
            char_buf = head->char_buf_ptr + head->read;
            flag_buf = head->flag_buf_ptr + head->read;
            head->read += count;
            spin_unlock_irqrestore(&tty->buf.lock, flags);
            disc->ops->receive_buf(tty, char_buf,flag_buf, count);
            spin_lock_irqsave(&tty->buf.lock, flags);
        }
        clear_bit(TTY_FLUSHING, &tty->flags);
    }
    /*4] ?????????????????*/
    /* We may have a deferred request to flush the input buffer,
       if so pull the chain under the lock and empty the queue */
    if (test_bit(TTY_FLUSHPENDING, &tty->flags)) {
        __tty_buffer_flush(tty);
        clear_bit(TTY_FLUSHPENDING, &tty->flags);
        wake_up(&tty->read_wait);
    }
    spin_unlock_irqrestore(&tty->buf.lock, flags);

    tty_ldisc_deref(disc);
}

/*把tty_buff中的数据copy到 read_buf中*/
static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
                  char *fp, int count)
{
    const unsigned char *p;
    char *f, flags = TTY_NORMAL;
    int    i;
    char    buf[64];
    unsigned long cpuflags;

    if (!tty->read_buf)
        return;

    if (tty->real_raw) {
        spin_lock_irqsave(&tty->read_lock, cpuflags);
/*这里的read_cnt,read_head什么意思?
 *reset_buffer_flags -> tty->read_head = tty->read_tail = tty->read_cnt = 0;
 */
        i = min(N_TTY_BUF_SIZE - tty->read_cnt,
            N_TTY_BUF_SIZE - tty->read_head);
        i = min(count, i);
/*这里的 read_buf哪里赋的值?
 * n_tty_open -> {tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);}
 */
/*1]copy the data from tty_buffer char data to read_buf
 *2]update the controld variable
 *3]updata tty->receive_room;
 *4]
 */
        memcpy(tty->read_buf + tty->read_head, cp, i);
        tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt += i;
        cp += i;
        count -= i;

        i = min(N_TTY_BUF_SIZE - tty->read_cnt,
            N_TTY_BUF_SIZE - tty->read_head);
        i = min(count, i);
        memcpy(tty->read_buf + tty->read_head, cp, i);
        tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt += i;
        spin_unlock_irqrestore(&tty->read_lock, cpuflags);
    }

    n_tty_set_room(tty);

    if ((!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) ||
        L_EXTPROC(tty)) {
        kill_fasync(&tty->fasync, SIGIO, POLL_IN);
        if (waitqueue_active(&tty->read_wait))
            wake_up_interruptible(&tty->read_wait);
    }

    /*
     * Check the remaining room for the input canonicalization
     * mode.  We don't want to throttle the driver if we're in
     * canonical mode and don't have a newline yet!
     */
    if (tty->receive_room < TTY_THRESHOLD_THROTTLE)
        tty_throttle(tty);
}

static void n_tty_set_room(struct tty_struct *tty)
{
    /* tty->read_cnt is not read locked ? */
    int    left = N_TTY_BUF_SIZE - tty->read_cnt - 1;
    int old_left;

    /*
     * If we are doing input canonicalization, and there are no
     * pending newlines, let characters through without limit, so
     * that erase characters will be handled.  Other excess
     * characters will be beeped.
     */
    if (left <= 0)
        left = tty->icanon && !tty->canon_data;
    /*保存原来的 receive_room, and update it with new*/
    old_left = tty->receive_room;
    tty->receive_room = left;

    /* Did this open up the receive buffer? We may need to flip
         * 这里的意思是old_left必须为 0,也就是tty->receive_room为0
      * tty_set_ldisc -> tty->receive_room = 0;
         * n_tty_set_room -> tty->receive_room = left;也就是该函数
     * 直到old_left == 0,调用schedule_work(&tty->buf.work);
     */
    if (left && !old_left)
        schedule_work(&tty->buf.work);
}

到这里数据从tty_buffer拷贝到tty的read_buff memory.

/***********************************************************************************/

(三)如何把数据从read_buf拷贝到 user space.

/***********************************************************************************/

static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
             unsigned char __user *buf, size_t nr)
{
    unsigned char __user *b = buf;
    DECLARE_WAITQUEUE(wait, current);
    int c;
    int minimum, time;
    ssize_t retval = 0;
    ssize_t size;
    long timeout;
    unsigned long flags;
    int packet;

do_it_again:
    minimum = time = 0;
    timeout = MAX_SCHEDULE_TIMEOUT;
    if (!tty->icanon) {
        tty->minimum_to_wake = minimum = 1;
    }

    add_wait_queue(&tty->read_wait, &wait);
    while (nr) {

        int uncopied;
        /* The copy function takes the read lock and handles
           locking internally for this case */
        uncopied = copy_from_read_buf(tty, &b, &nr);
        uncopied += copy_from_read_buf(tty, &b, &nr);
        if (uncopied) {
            retval = -EFAULT;
            break;
        }

    }

    size = b - buf;
    if (size) {
        retval = size;
        if (nr)
            clear_bit(TTY_PUSH, &tty->flags);
    } else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
        goto do_it_again;

    n_tty_set_room(tty);
    return retval;
}


/**
 *    copy_from_read_buf    -    copy read data directly
 *    @tty: terminal device
 *    @b: user data
 *    @nr: size of data
 *
 *    Helper function to speed up n_tty_read.  It is only called when
 *    ICANON is off; it copies characters straight from the tty queue to
 *    user space directly.  It can be profitably called twice; once to
 *    drain the space from the tail pointer to the (physical) end of the
 *    buffer, and once to drain the space from the (physical) beginning of
 *    the buffer to head pointer.
 *
 *    Called under the tty->atomic_read_lock sem
 *
 */

static int copy_from_read_buf(struct tty_struct *tty,
                      unsigned char __user **b,
                      size_t *nr)

{
    int retval;
    size_t n;
    unsigned long flags;

    retval = 0;
    spin_lock_irqsave(&tty->read_lock, flags);
    n = min(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail);
    n = min(*nr, n);
    spin_unlock_irqrestore(&tty->read_lock, flags);
    if (n) {
        retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n);
        n -= retval;
        tty_audit_add_data(tty, &tty->read_buf[tty->read_tail], n);
        spin_lock_irqsave(&tty->read_lock, flags);
        tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt -= n;
        /* Turn single EOF into zero-length read */
        if (L_EXTPROC(tty) && tty->icanon && n == 1) {
            if (!tty->read_cnt && (*b)[n-1] == EOF_CHAR(tty))
                n--;
        }
        spin_unlock_irqrestore(&tty->read_lock, flags);
        *b += n;
        *nr -= n;
    }
    return retval;
}
/*    char *read_buf;
 *    int read_head; used to describe the write read buffer starting address
 *    int read_tail; used to describe the read read buffer starting address
 *    int read_cnt;  used to describe the data size in the read buffer
 *该函数中提及的tty read的member: read_cnt, read_tail
 *从copy_to_user的输入参数可以看出read_tail是被读数据的开始地址;
 *read_cnt是需要被读出的数据大小
 *1] reset_buffer_flags -> tty->read_head = tty->read_tail = tty->read_cnt = 0;
 *2] n_tty_receive_buf: tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
 *            tty->read_cnt += i;
 *3] copy_from_read_buf:tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
 *            tty->read_cnt -= n;
 */

/***********************************************************************************/

(四)和设备文件的接口.

/***********************************************************************************/
/**
 *    tty_read    -    read method for tty device files
 *    @file: pointer to tty file
 *    @buf: user buffer
 *    @count: size of user buffer
 *    @ppos: unused
 *
 *    Perform the read system call function on this terminal device. Checks
 *    for hung up devices before calling the line discipline method.
 *
 *    Locking:
 *        Locks the line discipline internally while needed. Multiple
 *    read calls may be outstanding in parallel.
 */

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
            loff_t *ppos)
{
    int i;
    struct inode *inode = file->f_path.dentry->d_inode;
    struct tty_struct *tty = file_tty(file);
    struct tty_ldisc *ld;

    if (tty_paranoia_check(tty, inode, "tty_read"))
        return -EIO;
    if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
        return -EIO;

    /* We want to wait for the line discipline to sort out in this
       situation */
    ld = tty_ldisc_ref_wait(tty);
    if (ld->ops->read)
        i = (ld->ops->read)(tty, file, buf, count);
    else
        i = -EIO;
    tty_ldisc_deref(ld);
    if (i > 0)
        inode->i_atime = current_fs_time(inode->i_sb);
    return i;
}