串口驱动详细分析

来源:互联网 发布:php正则$1 编辑:程序博客网 时间:2024/05/17 23:31
串口驱动(使用中断)完整读操作
当串口数据满,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_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里面的数据传递给用户空间。而这个数据是第一部分最后放进来的数据。

1 0
原创粉丝点击