Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1869430
  • 博文数量: 376
  • 博客积分: 2147
  • 博客等级: 大尉
  • 技术积分: 3642
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-06 10:47
文章分类

全部博文(376)

文章存档

2019年(3)

2017年(28)

2016年(15)

2015年(17)

2014年(182)

2013年(16)

2012年(115)

我的朋友

分类: LINUX

2012-04-19 15:29:27

//中断处理函数,在下面2种情况下被调用
//1.输入一个字符时;2.输出一个字符时,当serial8250_start_tx打开发生器空中断后,因为发送器为空,所以输出的时候利用该中断从输出缓冲区向发送寄存器拷贝字符
static irqreturn_t serial8250_interrupt(int irq, void *dev_id)
{
    struct irq_info *i = dev_id;
    struct list_head *l, *end = NULL;
    int pass_counter = 0, handled = 0;

    DEBUG_INTR("serial8250_interrupt(%d)...", irq);

    spin_lock(&i->lock);

    l = i->head;
    do {
        struct uart_8250_port *up;
        unsigned int iir;

        up = list_entry(l, struct uart_8250_port, list);

        iir = serial_in(up, UART_IIR);
        if (!(iir & UART_IIR_NO_INT)) {
            serial8250_handle_port(up);

            handled = 1;

            end = NULL;
        } else if (up->port.iotype == UPIO_DWAPB &&
              (iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
            /* The DesignWare APB UART has an Busy Detect (0x07)
             * interrupt meaning an LCR write attempt occured while the
             * UART was busy. The interrupt must be cleared by reading
             * the UART status register (USR) and the LCR re-written. */
            unsigned int status;
            status = *(volatile u32 *)up->port.private_data;
            serial_out(up, UART_LCR, up->lcr);

            handled = 1;

            end = NULL;
        } else if (end == NULL)
            end = l;

        l = l->next;

        if (l == i->head && pass_counter++ > PASS_LIMIT) {
            /* If we hit this, we're dead. */
            printk(KERN_ERR "serial8250: too much work for "
                "irq%d\n", irq);
            break;
        }
    } while (l != end);

    spin_unlock(&i->lock);

    DEBUG_INTR("end.\n");

    return IRQ_RETVAL(handled);
}

//1.当输入一个字符时被调用,例如我们在控制台输入'l'
//2.输出的时候时候,当serial8250_start_tx打开发生器空中断后,导致它被调用
static void serial8250_handle_port(struct uart_8250_port *up)
{
    unsigned int status;
    unsigned long flags;

    spin_lock_irqsave(&up->port.lock, flags);
//1.输入时读取中断状态寄存器,status 为0x61,表示UART_LSR_DR,UART_LSR_THRE,UART_LSR_TEMT位置位
//既有数据,且发送器和传输保存寄存器为空;2.输出时status 为0x60
    status = serial_inp(up, UART_LSR);

    DEBUG_INTR("status = %x...", status);

    if (status & (UART_LSR_DR | UART_LSR_BI))
        receive_chars(up, &status);//对接收字符进行处理
    check_modem_status(up);
    if (status & UART_LSR_THRE)
        transmit_chars(up);//进入transmit_chars进行处理

    spin_unlock_irqrestore(&up->port.lock, flags);
}

static void transmit_chars(struct uart_8250_port *up)
{
    struct circ_buf *xmit = &up->port.info->xmit;
    int count;

    if (up->port.x_char) {
        serial_outp(up, UART_TX, up->port.x_char);
        up->port.icount.tx++;
        up->port.x_char = 0;
        return;
    }
    if (uart_tx_stopped(&up->port)) {
        serial8250_stop_tx(&up->port);
        return;
    }
//1.当输入字符进入transmit_char时xmit为空,所以条件为真
//2.当输出字符进入transmit_char时xmit不为空,所以条件为假
    if (uart_circ_empty(xmit)) {
        __stop_tx(up);
        return;
    }

    count = up->tx_loadsz;//对cns3xxx初始化tx_loadsz为0,所以下面的while都是直到xmit为空为此
    do {
        serial_out(up, UART_TX, xmit->buf[xmit->tail]);//每次输出一个字符
        xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
        up->port.icount.tx++;
        if (uart_circ_empty(xmit))
            break;
    } while (--count > 0);
//如果输出缓冲区的字符少于WAKEUP_CHARS,则唤醒等待输出的进程
    if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
        uart_write_wakeup(&up->port);

    DEBUG_INTR("THRE...");
//输出字符,导致缓冲区为空,调用 __stop_tx关闭中断
    if (uart_circ_empty(xmit))
        __stop_tx(up);
}

static inline void __stop_tx(struct uart_8250_port *p)
{
//如果发生器空中断打开,则关闭,
//1.当输入字符导致__stop_tx时,此时中断已经关闭,所以条件为假
//2.当输出字符导致__stop_tx时,此时中断已经关闭,所以条件为假
    if (p->ier & UART_IER_THRI) {
        p->ier &= ~UART_IER_THRI;
        serial_out(p, UART_IER, p->ier);
    }
}

#define uart_circ_chars_pending(circ)    \
    (CIRC_CNT((circ)->head, (circ)->tail, UART_XMIT_SIZE))

/* Return count in buffer.  */
#define CIRC_CNT(head,tail,size) (((head) - (tail)) & ((size)-1))

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

    do {
        if (likely(lsr & UART_LSR_DR))
            ch = serial_inp(up, UART_RX);//读取字符
        else
            /*
             * Intel 82571 has a Serial Over Lan device that will
             * set UART_LSR_BI without setting UART_LSR_DR when
             * it receives a break. To avoid reading from the
             * receive buffer without UART_LSR_DR bit set, we
             * just force the read character to be 0
             */
            ch = 0;

        flag = TTY_NORMAL;
        up->port.icount.rx++;

        lsr |= up->lsr_saved_flags;
        up->lsr_saved_flags = 0;

        if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {//输入错误
            /*
             * For statistics only
             */
            if (lsr & UART_LSR_BI) {
                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))
                    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;

            if (lsr & UART_LSR_BI) {
                DEBUG_INTR("handling break....");
                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;
//把字符放入tty接收缓冲区
        uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag);

ignore_char:
        lsr = serial_inp(up, UART_LSR);
    } while ((lsr & (UART_LSR_DR | UART_LSR_BI)) && (max_count-- > 0));//循环到无字符,或者达到最大输入字符数
    spin_unlock(&up->port.lock);
    tty_flip_buffer_push(tty);//通知上层读取字符
    spin_lock(&up->port.lock);
    *status = lsr;
}

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->info->port.tty;
    //把字符放入接收缓冲区
    if ((status & port->ignore_status_mask & ~overrun) == 0)
        tty_insert_flip_char(tty, ch, flag);

    /*
     * Overrun is special.  Since it's reported immediately,
     * it doesn't affect the current character.
     */
    if (status & ~port->ignore_status_mask & 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;
//系统启动后,第一次输入时候,tty->buf.tail为NULL(在TTY OPEN初始化);以后输入时则不为空
    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 space = tty_buffer_request_room(tty, size - copied);
        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);//把输入放入tty buff缓冲区
        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;
}

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可用缓冲区,找不到则重新分配
            if (b != NULL) {
                b->next = n;//如果b不为NULL,则把心的缓冲区链入其后
                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;
}

static struct tty_buffer *tty_buffer_find(struct tty_struct *tty, size_t size)
{
    struct tty_buffer **tbh = &tty->buf.free;
    while ((*tbh) != NULL) {//在自由buf链寻找可用的缓冲区
        struct tty_buffer *t = *tbh;
        if (t->size >= size) {
            *tbh = t->next;
            t->next = NULL;
            t->used = 0;
            t->commit = 0;
            t->read = 0;
            tty->buf.memory_used += t->size;
            return t;
        }
        tbh = &((*tbh)->next);
    }
    /* Round the buffer size out */
    size = (size + 0xFF) & ~0xFF;
    return tty_buffer_alloc(tty, size);//找不到则分配
    /* Should possibly check if this fails for the largest buffer we
       have queued and recycle that ? */
}

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;
    p = kmalloc(sizeof(struct tty_buffer) + 2 * size, GFP_ATOMIC);
    if (p == NULL)
        return NULL;
    p->used = 0;
    p->size = size;
    p->next = NULL;
    p->commit = 0;
    p->read = 0;
    p->char_buf_ptr = (char *)(p->data);
    p->flag_buf_ptr = (unsigned char *)p->char_buf_ptr + size;
    tty->buf.memory_used += size;
    return p;
}

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
        flush_to_ldisc(&tty->buf.work.work);
    else//否则通过工作队列,其实也是调用 flush_to_ldisc
        schedule_delayed_work(&tty->buf.work, 1);
}

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;
    struct tty_buffer *tbuf, *head;
    char *char_buf;
    unsigned char *flag_buf;

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

    spin_lock_irqsave(&tty->buf.lock, flags);
    /* So we know a flush is running */
    set_bit(TTY_FLUSHING, &tty->flags);
    head = tty->buf.head;
    if (head != NULL) {
        tty->buf.head = NULL;
        for (;;) {
            int count = head->commit - head->read;
            if (!count) {//当第2此循环时count为0
                if (head->next == NULL)
                    break;
                tbuf = head;
                head = head->next;
                tty_buffer_free(tty, tbuf);
                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) {
                schedule_delayed_work(&tty->buf.work, 1);
                break;
            }
            if (count > tty->receive_room)//当我们输入'l'时,count为1,receive_room为4095
                count = tty->receive_room;
            char_buf = head->char_buf_ptr + head->read;
            flag_buf = head->flag_buf_ptr + head->read;
            head->read += count;//头部加count
            spin_unlock_irqrestore(&tty->buf.lock, flags);
            disc->ops->receive_buf(tty, char_buf,
                            flag_buf, count);//此时调用n_tty_receive_buf
            spin_lock_irqsave(&tty->buf.lock, flags);
        }
        /* Restore the queue head */
        tty->buf.head = head;
    }
    /* 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);
    }
    clear_bit(TTY_FLUSHING, &tty->flags);
    spin_unlock_irqrestore(&tty->buf.lock, flags);

    tty_ldisc_deref(disc);
}
阅读(8279) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~