您的位置:首页 > 运维架构 > Linux

串口驱动详细分析

2016-12-20 17:51 309 查看
串口驱动(使用中断)完整读操作

当串口数据满,fifo数据达到设定阈值时,发生接收中断。

当串口数据空时,发生发送中断。

如下:

1)发送和接收:

发送: 循环buffer -(驱动做)-> 发送 fifo -(硬件自己做)-> 发送移位寄存器

       把数据写到发送fifo中。fifo把收到的数据传给发送移位寄存器(自动的,非driver控制),然后每个时钟脉冲往串口线上写一个bit数据

       所以是一直是中断告知还可以发送几个。当不足时一直引发中断。

       Tx FIFO trigger level选择fifo触发水平,选择什么时候引发中断,如fifo 低于 到4个或8 16 个字节等时中断 

       

接收: 接收移位寄存器 -(硬件自己做)-> 接收fifo -(驱动做)-> flip_buf

 

       接收移位寄存器收到数据后,发送给接收fifo,接收fifo事先设置好触发门限,当里面的数据量超过门限时,就会触发一个中断,调用驱动里的中断处理函数,把数据写到flip_buf中。

       Rx FIFO trigger level选择fifo触发水平,选择什么时候引发中断,如收到4个或8 16 个字节等时中断 

第一部分:   

读操作:TTY驱动从硬件收到数据后,负责把数据传递到TTY 核心,TTY核心将从TTY驱动收到的数据缓存到一个tty_flip_buffer 类型的结构中。该结构包含两个数据数

    组。从TTY设备接收到的数据被存储于第一个数组,当这个数组满, 等待数据的用户将被通知。当用户从这个数组读数据时, 任何从TTY驱动新来的数据将被存储在第2个数组。

    当第二个数组存满后,数据再次提交给用户, 并且驱动又开始填充第1个数组,以此交替。

        一次中断只是把接收的fifobuffer中的数据放到flipbuffer中去,接收的fifo的中断门限是4-12字节,进行一次接收操作往往要中断好多次,

    这样中断开销比较大,所以在while的循环条件中判断一下是否还有接收的有效数据,如果有,就继续在中断程序中继续接收,
当然,永远都在接收中断中(如果一直有数据要接收)也不合适,所以while循环还有计数,最多循环64次。
在循环中,首先是要判断一下接收数据用的flip-buffer是不是已经满了, if (tty->flip.count >= TTY_FLIPBUF_SIZE)。

    如果满了,就要跳到另一个buffer上去, tty->flip.tqueue.routine((void *) tty)是用来实现跳到另一个buffer上的功能,
然后把收到的数据写到flip-buffer中,相应的状态,统计数据都要改,接着再来while 循环,循环结束后就要调用

    tty_flip_buffer_push(tty)来让用户把存在缓冲里的数据取走,接收一次都要把缓存清空。

static inline void receive_chars(struct w55fa93_uart_port *up, int *status)

{

        struct tty_struct *tty = up->port.state->port.tty;

        struct uart_port *port = &up->port;

        unsigned int ch, flag;

        //int max_count = 256;

        int max_count = 64;

        if(*status & UART_FSR_RFE)

                return;

        //printk("%s get a char,FSR 0x%x\r\n",up->info->name,*status);

        do {

                ch = rd_regb(port, W55FA93_COM_RX);

                flag = TTY_NORMAL;

                up->port.icount.rx++;

                if (unlikely(*status & (UART_FSR_BI | UART_FSR_PE |

                                       UART_FSR_FE | UART_FSR_ROE))) {

                        /*

                         * For statistics only

                         */

                        if (*status & UART_FSR_BI) {

                                *status &= ~(UART_FSR_FE | UART_FSR_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))

                                        goto ignore_char;

                        } else if (*status & UART_FSR_PE)

                                up->port.icount.parity++;

                        else if (*status & UART_FSR_FE)

                                up->port.icount.frame++;

                        if (*status & UART_FSR_ROE)

                                up->port.icount.overrun++;

                        /*

                         * Mask off conditions which should be ignored.

                         */

                        *status &= up->port.read_status_mask;

#ifdef CONFIG_SERIAL_W55FA93_CONSOLE

                        if (up->port.line == up->port.cons->index) {

                                /* Recover the break flag from console xmit */

                                *status |= up->lsr_break_flag;

                                up->lsr_break_flag = 0;

                        }

#endif

                        if (*status & UART_FSR_BI) {

                                flag = TTY_BREAK;

                        } else if (*status & UART_FSR_PE)

                                flag = TTY_PARITY;

                        else if (*status & UART_FSR_FE)

                                flag = TTY_FRAME;

                }

                if (uart_handle_sysrq_char(&up->port, ch))

                        goto ignore_char;

                uart_insert_char(&up->port, *status, UART_FSR_ROE, ch, flag);

                //if(up->port.irq == IRQ_UART)

                //      printk("%s get a char,FSR 0x%x  : 0x%x\r\n",up->info->name,*status,ch);

        ignore_char:

                *status = rd_regl(port, W55FA93_COM_FSR);

        } while ((!(*status & UART_FSR_RFE)) && (max_count-- > 0));

        tty_flip_buffer_push(tty);

}

解析uart_insert_char:将数据放入tty缓存

uart_insert_char(&up->port, *status, UART_FSR_ROE, ch, flag);-->tty_insert_flip_char(tty, ch, flag)-->tty_
4000
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);

                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);

                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));

        return copied;

}

总结:即已经将将数据放入到tb->char_buf_ptr缓冲区中。

解析tty_flip_buffer_push函数:

tty_flip_buffer_push(tty)-->flush_to_ldisc(&tty->buf.work.work)-->disc->ops->receive_buf(tty, char_buf,flag_buf, count)( .receive_buf  = n_tty_receive_buf,)-->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.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)) {
.
.
.

                        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;

                        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);

        }

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

}

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);

                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);
.
.
.

}

总结:即已经将将数据从tb->char_buf_ptr缓冲区中拷贝到tty->read_buf中了。

第二部分:

当用户空间开始读数据时。

tty_read-->ld->ops->read-->n_tty_read-->copy_from_read_buf(tty, &b, &nr)-->copy_to_user(*b, &tty->read_buf[tty->read_tail], n)

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,

                        loff_t *ppos)

{

        int i;

        struct tty_struct *tty;

        struct inode *inode;

        struct tty_ldisc *ld;

        tty = (struct tty_struct *)file->private_data;

        inode = file->f_path.dentry->d_inode;

        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;

}

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;
.
.
.

                        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;

                        }

                }
.
.
.

}

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;

                spin_unlock_irqrestore(&tty->read_lock, flags);

                *b += n;

                *nr -= n;

        }

        return retval;

}

总结:最后将read_buf里面的数据传递给用户空间。而这个数据是第一部分最后放进来的数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 驱动 串口