从tty到uart层,分析uart数据流程(二)

     上一节中,我们主要了解了uart的一些重要的数据结构,和uart的发送数据的流程。下来我们会分析uart接受数据的流程,和发送比较起来,收流程更加复杂一点!关于发送流程,我们会从底层一步一步分析,直到tty core层。

- ---------------------------------------------  /tty/serial/omap_serial.c -------------------------------------------------

static inline irqreturn_t serial_omap_irq(int irq, void *dev_id)
{
    ....................................
    spin_lock_irqsave(&up->port.lock, flags);
    lsr = serial_in(up, UART_LSR);  //读取线路状态寄存器;
    if (iir & UART_IIR_RLSI) {
        if (!up->use_dma) {  //若果使用非DMA
            if (lsr & UART_LSR_DR)  //如果数据准备好
                receive_chars(up, &lsr);   //接受字符
        } else {
            up->ier &= ~(UART_IER_RDI | UART_IER_RLSI);  //
            serial_out(up, UART_IER, up->ier);
            if ((serial_omap_start_rxdma(up) != 0) &&
                    (lsr & UART_LSR_DR))
                receive_chars(up, &lsr);
        }
    }
    ....................................  
}

     在receive_chars中,将会做很多事情:

static inline void receive_chars(struct uart_omap_port *up,
        unsigned int *status)
{
    struct tty_struct *tty = up->port.state->port.tty;
    unsigned int flag, lsr = *status;
    unsigned char ch = 0;
    int max_count = 256;

    do {
        if (likely(lsr & UART_LSR_DR))  //如果数据准备好,则接受数据;
            ch = serial_in(up, UART_RX);
        flag = TTY_NORMAL;   //设置标志为TTY_NORMAL
        up->port.icount.rx++;

        if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {   //如果产生错误?
            /*
             * For statistics only
             */
            if (lsr & UART_LSR_BI) {      //break signal set bit
                lsr &= ~(UART_LSR_FE | UART_LSR_PE);
                up->port.icount.brk++;
                /*
                 * We do the SysRQ and SAK checking
                 * here because otherwise the break
                 * may get masked by ignore_status_mask
                 * or read_status_mask.
                 */
                if (uart_handle_break(&up->port))    //break信号处理
                    goto ignore_char;
            } else if (lsr & UART_LSR_PE) {   //如果是校验错误,设置相应计数器
                up->port.icount.parity++;
            } else if (lsr & UART_LSR_FE) {   //如果是帧错误
                up->port.icount.frame++;
            }

            if (lsr & UART_LSR_OE)    //如果是溢出错误
                up->port.icount.overrun++;

            /*
             * Mask off conditions which should be ignored.
             */
            lsr &= up->port.read_status_mask;

#ifdef CONFIG_SERIAL_OMAP_CONSOLE
            if (up->port.line == up->port.cons->index) {
                /* Recover the break flag from console xmit */
                lsr |= up->lsr_break_flag;
            }
#endif
            if (lsr & UART_LSR_BI)   //设置错误标志
                flag = TTY_BREAK;
            else if (lsr & UART_LSR_PE)
                flag = TTY_PARITY;
            else if (lsr & UART_LSR_FE)
                flag = TTY_FRAME;
        }

        if (uart_handle_sysrq_char(&up->port, ch))
            goto ignore_char;
        uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag);  //将字符插入tty缓存中
ignore_char:
        lsr = serial_in(up, UART_LSR);
    } while ((lsr & (UART_LSR_DR | UART_LSR_BI)) && (max_count-- > 0));
    spin_unlock(&up->port.lock);
    tty_flip_buffer_push(tty);   //讲数据刷新到tty core的buf中
    spin_lock(&up->port.lock);
}

    下面我们会一步一步分析标红的函数:

------------------------------- /tty/serial-core.c ----------------------------------------------------

static inline int uart_handle_break(struct uart_port *port)
{
    struct uart_state *state = port->state;
#ifdef SUPPORT_SYSRQ
    if (port->cons && port->cons->index == port->line) {
        if (!port->sysrq) {   //如果port->sysrq为0
            port->sysrq = jiffies + HZ*5; // 设置时间为 5 s后;
            return 1;
        }
        port->sysrq = 0;   //如果不为0,设置为0,即再次发生中断时。
    }
#endif
    if (port->flags & UPF_SAK)
        do_SAK(state->port.tty);
    return 0;
}
        主要用于设置多长时间, 后面我们会看到,当break信号发生中断时,该函数主要设置接受后面的普通字符所占的时长(我们设置为5s)。当设置好时长后,将会退出中断,等再次发生中断时,将会执行uart_handle_sysrq_char函数去处理这些在五秒内接受的“含有特殊意义”的字符。

static inline int
uart_handle_sysrq_char(struct uart_port *port, unsigned int ch)
{
#ifdef SUPPORT_SYSRQ
    if (port->sysrq) {
        if (ch && time_before(jiffies, port->sysrq)) {//如果当前时间在刚才设置的5s时间内
               handle_sysrq(ch);   //处理sysrq的特殊字符
            port->sysrq = 0;
            return 1;
        }
        port->sysrq = 0; //处理完后将其置0;
    }
#endif
    return 0;
}

        由上面可以得知,系统在断点信号后,将会处理特殊字符。

static inline void
uart_insert_char(struct uart_port *port, unsigned int status,
         unsigned int overrun, unsigned int ch, unsigned int flag)
{
    struct tty_struct *tty = port->state->port.tty;

    if ((status & port->ignore_status_mask & ~overrun) == 0)  //如果不是overrun
        tty_insert_flip_char(tty, ch, flag);   //向tty插入字符;

    /*
     * Overrun is special.  Since it's reported immediately,
     * it doesn't affect the current character.
     */
    if (status & ~port->ignore_status_mask & overrun)  //产生overrun
        tty_insert_flip_char(tty, 0, TTY_OVERRUN);
}

static inline int tty_insert_flip_char(struct tty_struct *tty,
                    unsigned char ch, char flag)
{
    struct tty_buffer *tb = tty->buf.tail;
    if (tb && tb->used < tb->size) {
        tb->flag_buf_ptr[tb->used] = flag;
        tb->char_buf_ptr[tb->used++] = ch;
        return 1;
    }
    return tty_insert_flip_string_flags(tty, &ch, &flag, 1);
}

         首先我们来看看tty->buf的数据结构;

struct tty_struct {
   ..........................
    struct tty_bufhead buf;        /* Locked internally */
   ........................
};  --->

struct tty_bufhead {
    struct work_struct work;
    spinlock_t lock;
    struct tty_buffer *head;    /*数据存储的队列头*/  
    struct tty_buffer *tail;    /* 数据存储的队列尾 */
    //<==Qing  tail:the point to data buffer  having been read;====>//
    struct tty_buffer *free;    /* 空的数据队列*/
    int memory_used;        /* Buffer space used excluding
                                free queue */
};--->

   tail是数据链表的尾部,我们要插入数据,便是从尾部插入!

   head是数据存储的队列头,要读取数据,便从头部开始;

    free是空的数据队列,要删除已读的存储节点,可以考虑插入到free中!

struct tty_buffer {
    struct tty_buffer *next;  //buf的next指针
    char *char_buf_ptr;  //数据存储的指针,实际上指向data的首地址;
    unsigned char *flag_buf_ptr;  //存储flag的指针;
    int used;  //被使用的长度
    int size;  //buf的长度
    int commit;  //经常保存used的数据;
    int read;   //被读过的数据长度
    /* Data points here */
    unsigned long data[0];//数据的真实存储的起始位置
};

        在这里值得注意的是unsigned long data[0];被称作“柔性数组”,是GCC的正确语法。 data数据成员不占空间,我们一般会这样使用它:

eg. kmalloc(sizeof(struct tty_buffer)+len);其中len表示给该结构体多分配的空间,一般是用来存储数据的buffer。

           这样的好处是,只需要一次free,并不需要再次free掉len长度的空间。因为len长度的数据已经被释放!

         理解这些数据结构中成员的含义,是理解下面流程的基本.

     接下来我们接着分析:

static inline int tty_insert_flip_char(struct tty_struct *tty,
                    unsigned char ch, char flag)
{
    struct tty_buffer *tb = tty->buf.tail;   //获取数据存储队列的尾部!
    if (tb && tb->used < tb->size) {   //如果已使用的比总空间小,
        tb->flag_buf_ptr[tb->used] = flag;  //便可向该空间插入一个字符的数据;
        tb->char_buf_ptr[tb->used++] = ch;
        return 1;
    }

    //如果该存储节点的空间已满,则调用下面的函数扩充空间;

    return tty_insert_flip_string_flags(tty, &ch, &flag, 1);
}------>

int tty_insert_flip_string_flags(struct tty_struct *tty,
        const unsigned char *chars, const char *flags, size_t size)
{
    int copied = 0;
    do {
        int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);
        int space = tty_buffer_request_room(tty, goal);  //请求获取goal长度的存储空间
        struct tty_buffer *tb = tty->buf.tail;
        /* If there is no space then tb may be NULL */
        if (unlikely(space == 0))
            break;
        memcpy(tb->char_buf_ptr + tb->used, chars, space);  //将数据copy到存储节点空间
        memcpy(tb->flag_buf_ptr + tb->used, flags, space);
        tb->used += space;
        copied += space;
        chars += space;
        flags += space;
        /* There is a small chance that we need to split the data over
           several buffers. If this is the case we must loop */
    } while (unlikely(size > copied));  //知道copy完size个字节!
    return copied;
}
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 */
    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) {   //为其分配size+0xff的空间,并将该空间设置为tail。可以继续深追代码!
            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;
}
     请求完空间后,将返回到tty_insert_flip_string_flags函数,其字符填入该存储空间!这样就成功地完成了插入字符!这样我们便知道,中断送来的字符被存储在tty->buf的队列成员中,然而在上层,其在tty->read_buf中读取数据的!所以下面的一个动作很重要,uart驱动需要靠他将数据刷新到真正的tty->read_buf中
void tty_flip_buffer_push(struct tty_struct *tty)
{
    unsigned long flags;
    spin_lock_irqsave(&tty->buf.lock, flags);
    if (tty->buf.tail != NULL)
        tty->buf.tail->commit = tty->buf.tail->used;  //记录已读的数据
    spin_unlock_irqrestore(&tty->buf.lock, flags);

    if (tty->low_latency)
        flush_to_ldisc(&tty->buf.work);  //刷新数据到线路规程层!这是核心!
    else
        schedule_work(&tty->buf.work);
} ---->

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) {//存在下一个节点,这样的循环会把从head到tail的所有数据都刷新上去!
            int count;
            char *char_buf;
            unsigned char *flag_buf;

            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;
            if (count > tty->receive_room)
                count = tty->receive_room;
            char_buf = head->char_buf_ptr + head->read;  //设置未读的空间为位置
            flag_buf = head->flag_buf_ptr + head->read;  //设置未读的空间为位置
            head->read += count;   //刷新的已读数据量!这保证while循环的正常进行!
            spin_unlock_irqrestore(&tty->buf.lock, flags);
            disc->ops->receive_buf(tty, char_buf,
                            flag_buf, count);   //调用线路规程中的receive_buf函数将该节点的数据刷新到readbuf中!

            spin_lock_irqsave(&tty->buf.lock, flags);
        }  //end while
        clear_bit(TTY_FLUSHING, &tty->flags);
    }

    /* 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_buffer_free的实现:
static void tty_buffer_free(struct tty_struct *tty, struct tty_buffer *b)
{
    /* Dumb strategy for now - should keep some stats */
    tty->buf.memory_used -= b->size;
    WARN_ON(tty->buf.memory_used < 0);

    if (b->size >= 512)
        kfree(b);     //如果SIZE >=512,则把它 kfree掉。
    else {
        b->next = tty->buf.free;  //将其添加到free链表
        tty->buf.free = b;
    }
}
     下面我们将分析receive_buf函数,

------------------------------- /tty/N_tty.c ----------------------------------------------------
static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
                  char *fp, int count)       //cp 便是字符,fp是flag
{
    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);
        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);     //将字符copy到tty->read_buf
        tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt += i;
        spin_unlock_irqrestore(&tty->read_lock, cpuflags);
    } else {
        for (i = count, p = cp, f = fp; i; i--, p++) {
            if (f)
                flags = *f++;
            switch (flags) {
            case TTY_NORMAL:
                n_tty_receive_char(tty, *p);
                break;
            case TTY_BREAK:
                n_tty_receive_break(tty);
                break;
            case TTY_PARITY:
            case TTY_FRAME:
                n_tty_receive_parity_error(tty, *p);
                break;
            case TTY_OVERRUN:
                n_tty_receive_overrun(tty);
                break;
            default:
                printk(KERN_ERR "%s: unknown flag %d\n",
                       tty_name(tty, buf), flags);
                break;
            }
        }
        if (tty->ops->flush_chars)
            tty->ops->flush_chars(tty);
    }

    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);
}
 从这个流程来看,中断的字符被flush到tty->read_buf 中!我们再来看上层是如何将数据读取到用户空间的!

------------------------------- /tty/tty_io.c ----------------------------------------------------

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);  //调用ldisc的read函数;
    else
        i = -EIO;
    tty_ldisc_deref(ld);
    if (i > 0)
        inode->i_atime = current_fs_time(inode->i_sb);
    return i;

}

------------------------------- /tty/N_tty.c ----------------------------------------------------

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:

    BUG_ON(!tty->read_buf);

    c = job_control(tty, file);
    if (c < 0)
        return c;

    minimum = time = 0;
    timeout = MAX_SCHEDULE_TIMEOUT;
    if (!tty->icanon) {
        time = (HZ / 10) * TIME_CHAR(tty);
        minimum = MIN_CHAR(tty);
        if (minimum) {
            if (time)
                tty->minimum_to_wake = 1;
            else if (!waitqueue_active(&tty->read_wait) ||
                 (tty->minimum_to_wake > minimum))
                tty->minimum_to_wake = minimum;
        } else {
            timeout = 0;
            if (time) {
                timeout = time;
                time = 0;
            }
            tty->minimum_to_wake = minimum = 1;
        }
    }

    /*
     *    Internal serialization of reads.
     */
    if (file->f_flags & O_NONBLOCK) {
        if (!mutex_trylock(&tty->atomic_read_lock))
            return -EAGAIN;
    } else {
        if (mutex_lock_interruptible(&tty->atomic_read_lock))
            return -ERESTARTSYS;
    }
    packet = tty->packet;

    add_wait_queue(&tty->read_wait, &wait);   //添加到等待队列
    while (nr) {
        /* First test for status change. */
        if (packet && tty->link->ctrl_status) {
            unsigned char cs;
            if (b != buf)
                break;
            spin_lock_irqsave(&tty->link->ctrl_lock, flags);
            cs = tty->link->ctrl_status;
            tty->link->ctrl_status = 0;
            spin_unlock_irqrestore(&tty->link->ctrl_lock, flags);
            if (tty_put_user(tty, cs, b++)) {
                retval = -EFAULT;
                b--;
                break;
            }
            nr--;
            break;
        }
        /* This statement must be first before checking for input
           so that any interrupt will set the state back to
           TASK_RUNNING. */
        set_current_state(TASK_INTERRUPTIBLE);    //设置为可中断状态!

        if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
            ((minimum - (b - buf)) >= 1))
            tty->minimum_to_wake = (minimum - (b - buf));

        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 (!timeout)
                break;
            if (file->f_flags & O_NONBLOCK) {
                retval = -EAGAIN;
                break;
            }
            if (signal_pending(current)) {
                retval = -ERESTARTSYS;
                break;
            }
            /* FIXME: does n_tty_set_room need locking ? */
            n_tty_set_room(tty);
            timeout = schedule_timeout(timeout);   //调度,此时不执行下面的程序,直到被唤醒!
            BUG_ON(!tty->read_buf);
            continue;
        }
        __set_current_state(TASK_RUNNING);

        /* Deal with packet mode. */
        if (packet && b == buf) {
            if (tty_put_user(tty, TIOCPKT_DATA, b++)) {
                retval = -EFAULT;
                b--;
                break;
            }
            nr--;
        }

        if (tty->icanon && !L_EXTPROC(tty)) {
            /* N.B. avoid overrun if nr == 0 */
            while (nr && tty->read_cnt) {
                int eol;

                eol = test_and_clear_bit(tty->read_tail,
                        tty->read_flags);
                c = tty->read_buf[tty->read_tail];   //读取字符
                spin_lock_irqsave(&tty->read_lock, flags);
                tty->read_tail = ((tty->read_tail+1) &
                          (N_TTY_BUF_SIZE-1));
                tty->read_cnt--;
                if (eol) {
                    /* this test should be redundant:
                     * we shouldn't be reading data if
                     * canon_data is 0
                     */
                    if (--tty->canon_data < 0)
                        tty->canon_data = 0;
                }
                spin_unlock_irqrestore(&tty->read_lock, flags);

                if (!eol || (c != __DISABLED_CHAR)) {
                    if (tty_put_user(tty, c, b++)) {
                        retval = -EFAULT;
                        b--;
                        break;
                    }
                    nr--;
                }
                if (eol) {
                    tty_audit_push(tty);
                    break;
                }
            }
            if (retval)
                break;
        } else {
            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);   //copy到用户空间!

            if (uncopied) {
                retval = -EFAULT;
                break;
            }
        }

        /* 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);
        }

        if (b - buf >= minimum)
            break;
        if (time)
            timeout = time;
    }
    mutex_unlock(&tty->atomic_read_lock);
    remove_wait_queue(&tty->read_wait, &wait);

    if (!waitqueue_active(&tty->read_wait))
        tty->minimum_to_wake = minimum;

    __set_current_state(TASK_RUNNING);
    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;
}
通过上面的分析,整个读流程就OK了,第一次写分析,有诸多不足之处,还请见谅!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux内核UART设备驱动注册流程如下: 1. 分配tty_driver结构体 在驱动初始化时,首先需要分配一个tty_driver结构体,该结构体描述了tty设备的驱动属性信息,包括驱动名称、打开、关闭、读写等操作的回调函数指针等。 2. 注册tty_driver 调用tty_register_driver函数,将tty_driver结构体注册到内核中,该函数会将tty_driver结构体添加到tty_drivers链表中,同时会创建一个tty_class结构体和一个tty_class_dev结构体,并将其关联起来。 3. 创建tty设备节点 调用tty_register_device函数,该函数会根据tty_driver结构体中的信息创建tty设备节点,并将其添加到tty_drivers链表中。 4. 设置tty设备驱动回调函数 在tty_driver结构体中设置相应的驱动回调函数,例如open、close、read、write等操作的回调函数指针。 5. 注册tty设备驱动与硬件设备的关联 在驱动初始化时,需要将tty设备驱动与硬件设备进行关联,通常是通过platform_device_register函数将platform_device结构体注册到内核中,并调用platform_driver_register函数将platform_driver结构体注册到内核中。 6. 实现tty设备驱动回调函数 在驱动初始化时,需要实现相应的tty设备驱动回调函数,例如open、close、read、write等操作的回调函数。当用户调用相应的操作时,内核会自动调用相应的回调函数执行相应的操作。 7. 注销tty设备驱动 在驱动卸载时,需要调用tty_unregister_driver函数注销tty_driver结构体,并释放相关资源。同时也需要注销与硬件设备的关联关系。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值