免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2687 | 回复: 0
打印 上一主题 下一主题

linux设备驱动之8250串口驱动2 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-01-25 15:03 |只看该作者 |倒序浏览
static void autoconfig_irq(struct uart_8250_port *up)
{
     unsigned char save_mcr, save_ier;
     unsigned char save_ICP = 0;
     unsigned int ICP = 0;
     unsigned long irqs;
     int irq;

     if (up->port.flags & UPF_FOURPORT) {
         ICP = (up->port.iobase & 0xfe0) | 0x1f;
         save_ICP = inb_p(ICP);
         outb_p(0x80, ICP);
         (void) inb_p(ICP);
     }

     /* forget possible initially masked and pending IRQ */
     probe_irq_off(probe_irq_on());
     save_mcr = serial_inp(up, UART_MCR);
     save_ier = serial_inp(up, UART_IER);
     serial_outp(up, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2);

     irqs = probe_irq_on();
     serial_outp(up, UART_MCR, 0);
     udelay(10);
     if (up->port.flags & UPF_FOURPORT) {
         serial_outp(up, UART_MCR,
                  UART_MCR_DTR | UART_MCR_RTS);
     } else {
         serial_outp(up, UART_MCR,
                  UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
     }
     serial_outp(up, UART_IER, 0x0f); /* enable all intrs */
     (void)serial_inp(up, UART_LSR);
     (void)serial_inp(up, UART_RX);
     (void)serial_inp(up, UART_IIR);
     (void)serial_inp(up, UART_MSR);
     serial_outp(up, UART_TX, 0xFF);
     udelay(20);
     irq = probe_irq_off(irqs);

     serial_outp(up, UART_MCR, save_mcr);
     serial_outp(up, UART_IER, save_ier);

     if (up->port.flags & UPF_FOURPORT)
         outb_p(save_ICP, ICP);

     up->port.irq = (irq > 0) ? irq : 0;
}
在上述代码的操作中,先将8250相关中断允许寄存器全打开.然后调用驱动使用的函数, 当它不得不探测来决定哪个中断线被设备在使用.
probe_irq_on()将中断暂时关掉,然后配置MCR寄存器使之发送DTR和RTS.之后再用probe_irq_off()来检测IRQ号.如
果检测成功,则值赋值给port->irq.
进行到这里,conifg_port动作就完成了.
经过这个config_port过程后,我们发现,并没有对serial8250_isa_devs->dev-> platform_data赋值,也就是说platform_driver->probe函数并无实质性的处理.在第一次for循环的时,就会因条件不符而退出.
四: startup操作
在前面分析uart驱动架构的时候,曾说过,在open的时候,会调用port->startup().在本次分析的驱动中,对应接口为serial8250_startup().分段分析如下:
static int serial8250_startup(struct uart_port *port)
{
     struct uart_8250_port *up = (struct uart_8250_port *)port;
     unsigned long flags;
     unsigned char lsr, iir;
     int retval;

     up->capabilities = uart_config[up->port.type].flags;
     up->mcr = 0;

     if (up->port.type == PORT_16C950) {
         /* Wake up and initialize UART */
         up->acr = 0;
         serial_outp(up, UART_LCR, 0xBF);
         serial_outp(up, UART_EFR, UART_EFR_ECB);
         serial_outp(up, UART_IER, 0);
         serial_outp(up, UART_LCR, 0);
         serial_icr_write(up, UART_CSR, 0); /* Reset the UART */
         serial_outp(up, UART_LCR, 0xBF);
         serial_outp(up, UART_EFR, UART_EFR_ECB);
         serial_outp(up, UART_LCR, 0);
     }

#ifdef CONFIG_SERIAL_8250_RSA
     /*
      * If this is an RSA port, see if we can kick it up to the
      * higher speed clock.
      */
     enable_rsa(up);
#endif

     /*
      * Clear the FIFO buffers and disable them.
      * (they will be reenabled in set_termios())
      */
     serial8250_clear_fifos(up);
上面的代码都不是对应8250芯片的情况
     /*
      * Clear the interrupt registers.
      */
     (void) serial_inp(up, UART_LSR);
     (void) serial_inp(up, UART_RX);
     (void) serial_inp(up, UART_IIR);
     (void) serial_inp(up, UART_MSR);
复位LSR,RX,IIR,MSR寄存器

     /*
      * At this point, there's no way the LSR could still be 0xff;
      * if it is, then bail out, because there's likely no UART
      * here.
      */
     if (!(up->port.flags & UPF_BUGGY_UART) &&
         (serial_inp(up, UART_LSR) == 0xff)) {
         printk("ttyS%d: LSR safety check engaged!\n", up->port.line);
         return -ENODEV;
     }
若LSR寄存器中的值为0xFF.异常
     /*
      * For a XR16C850, we need to set the trigger levels
      */
     if (up->port.type == PORT_16850) {
         unsigned char fctr;

         serial_outp(up, UART_LCR, 0xbf);

         fctr = serial_inp(up, UART_FCTR) & ~(UART_FCTR_RX|UART_FCTR_TX);
         serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_RX);
         serial_outp(up, UART_TRG, UART_TRG_96);
         serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_TX);
         serial_outp(up, UART_TRG, UART_TRG_96);

         serial_outp(up, UART_LCR, 0);
     }
16850系列芯片的处理,忽略

     if (is_real_interrupt(up->port.irq)) {
         /*
          * Test for UARTs that do not reassert THRE when the
          * transmitter is idle and the interrupt has already
          * been cleared.  Real 16550s should always reassert
          * this interrupt whenever the transmitter is idle and
          * the interrupt is enabled.  Delays are necessary to
          * allow register changes to become visible.
          */
         spin_lock_irqsave(&up->port.lock, flags);

         wait_for_xmitr(up, UART_LSR_THRE);
         serial_out_sync(up, UART_IER, UART_IER_THRI);
          udelay(1); /* allow THRE to set */
         serial_in(up, UART_IIR);
         serial_out(up, UART_IER, 0);
         serial_out_sync(up, UART_IER, UART_IER_THRI);
         udelay(1); /* allow a working UART time to re-assert THRE */
         iir = serial_in(up, UART_IIR);
         serial_out(up, UART_IER, 0);

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

         /*
          * If the interrupt is not reasserted, setup a timer to
          * kick the UART on a regular basis.
          */
         if (iir & UART_IIR_NO_INT) {
              pr_debug("ttyS%d - using backup timer\n", port->line);
              up->timer.function = serial8250_backup_timeout;
              up->timer.data = (unsigned long)up;
              mod_timer(&up->timer, jiffies +
                   poll_timeout(up->port.timeout) + HZ / 5);
         }
     }
如果中断号有效,还要进一步判断这个中断号是否有效.具体操作为,先等待8250发送寄存器空.然后允许发送中断空的中断.然后判断IIR寄存器是否收到
中断.如果有没有收到中断,则说明这根中断线无效.只能采用轮询的方式.关于轮询方式,我们在之后再以独立章节的形式给出分析
       /*
      * If the "interrupt" for this port doesn't correspond with any
      * hardware interrupt, we use a timer-based system.  The original
      * driver used to do this with IRQ0.
      */
     if (!is_real_interrupt(up->port.irq)) {
         up->timer.data = (unsigned long)up;
         mod_timer(&up->timer, jiffies + poll_timeout(up->port.timeout));
     } else {
         retval = serial_link_irq_chain(up);
         if (retval)
              return retval;
     }
如果没有设置中断号,则采用轮询方式.如果中断后有效.流程转入serial_link_irq_chain().在这个里面.会注册中断处理函数.
/*
      * Now, initialize the UART
      */
     serial_outp(up, UART_LCR, UART_LCR_WLEN8);

     spin_lock_irqsave(&up->port.lock, flags);
     if (up->port.flags & UPF_FOURPORT) {
         if (!is_real_interrupt(up->port.irq))
              up->port.mctrl |= TIOCM_OUT1;
     } else
         /*
          * Most PC uarts need OUT2 raised to enable interrupts.
          */
         if (is_real_interrupt(up->port.irq))
              up->port.mctrl |= TIOCM_OUT2;

     serial8250_set_mctrl(&up->port, up->port.mctrl);

     /*
      * Do a quick test to see if we receive an
      * interrupt when we enable the TX irq.
      */
     serial_outp(up, UART_IER, UART_IER_THRI);
     lsr = serial_in(up, UART_LSR);
     iir = serial_in(up, UART_IIR);
     serial_outp(up, UART_IER, 0);

     if (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT) {
         if (!(up->bugs & UART_BUG_TXEN)) {
              up->bugs |= UART_BUG_TXEN;
              pr_debug("ttyS%d - enabling bad tx status workarounds\n",
                    port->line);
         }
     } else {
         up->bugs &= ~UART_BUG_TXEN;
     }

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

     /*
      * Clear the interrupt registers again for luck, and clear the
      * saved flags to avoid getting false values from polling
      * routines or the previous session.
      */
     serial_inp(up, UART_LSR);
     serial_inp(up, UART_RX);
     serial_inp(up, UART_IIR);
     serial_inp(up, UART_MSR);
     up->lsr_saved_flags = 0;
     up->msr_saved_flags = 0;

     /*
      * Finally, enable interrupts.  Note: Modem status interrupts
      * are set via set_termios(), which will be occurring imminently
      * anyway, so we don't enable them here.
      */
     up->ier = UART_IER_RLSI | UART_IER_RDI;
     serial_outp(up, UART_IER, up->ier);

     if (up->port.flags & UPF_FOURPORT) {
         unsigned int icp;
         /*
          * Enable interrupts on the AST Fourport board
          */
         icp = (up->port.iobase & 0xfe0) | 0x01f;
         outb_p(0x80, icp);
         (void) inb_p(icp);
     }

     return 0;
}
最后,就是对8250芯片的初始化了.包括:在LCR中设置数据格式,在MCR中设置允许中断到8259.在IER中设置相关允许位.
另外在open的时候,还会调用port-> enable_ms ()接口,在本例中对应为: serial8250_enable_ms().代码如下:
static void serial8250_enable_ms(struct uart_port *port)
{
     struct uart_8250_port *up = (struct uart_8250_port *)port;

     /* no MSR capabilities */
     if (up->bugs & UART_BUG_NOMSR)
         return;

     up->ier |= UART_IER_MSI;
     serial_out(up, UART_IER, up->ier);
}
即允许moden中断
  
五:数据发送的操作
在uart驱动架构中分析过,在发送数据的时候,uart层先会将数据放入circ_buffer.最后再调用port-> start_tx().
在这里,这个接口对应为serial8250_start_tx().代码如下:
static void serial8250_start_tx(struct uart_port *port)
{
     struct uart_8250_port *up = (struct uart_8250_port *)port;

     if (!(up->ier & UART_IER_THRI)) {
         up->ier |= UART_IER_THRI;
         serial_out(up, UART_IER, up->ier);

         if (up->bugs & UART_BUG_TXEN) {
            , ;  unsigned char lsr, iir;
              lsr = serial_in(up, UART_LSR);
              up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;
              iir = serial_in(up, UART_IIR) & 0x0f;
              if ((up->port.type == PORT_RM9000) ?
                   (lsr & UART_LSR_THRE &&
                   (iir == UART_IIR_NO_INT || iir == UART_IIR_THRI)) :
                   (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT))
                   transmit_chars(up);
         }
     }

     /*
      * Re-enable the transmitter if we disabled it.
      */
     if (up->port.type == PORT_16C950 && up->acr & UART_ACR_TXDIS) {
         up->acr &= ~UART_ACR_TXDIS;
         serial_icr_write(up, UART_ACR, up->acr);
     }
}
这个函数非常简单.如果没有定义发送空中断.则在IER中打开这个中断.关于TXEN上的bug修复和16C950类型的芯片不是我们所关注的部份.
那,这里只是打开了这个中断.写数据到芯片的这个过程是在什么地方完成的呢?
是在中断处理中.如果是发送空的中断,就将circ buffer中的数据写出发送寄存器.跟踪一下代码.中断处理函数为serial8250_interrupt().
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);
}
这里可能有个疑问的地方,挂在这个链表上的到底是什么.这我们要从serial_link_irq_chain()来说起.该函数代码如下:
static int serial_link_irq_chain(struct uart_8250_port *up)
{
     struct irq_info *i = irq_lists + up->port.irq;
     int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0;

     spin_lock_irq(&i->lock);

     if (i->head) {
         list_add(&up->list, i->head);
         spin_unlock_irq(&i->lock);

         ret = 0;
     } else {
         INIT_LIST_HEAD(&up->list);
         i->head = &up->list;
         spin_unlock_irq(&i->lock);

         ret = request_irq(up->port.irq, serial8250_interrupt,
                     irq_flags, "serial", i);
         if (ret  0)
              serial_do_unlink(i, up);
     }

     return ret;
}
从这里看到,注册中断处理函数的参数i就是对应irq_lists + up->port.irq.即对应在irq_lists数组中的port->irq项.随后,将注册的uart_8250_port添加到了这个链表.
奇怪了,为什么要这么做了?我们返回old_serial_port的定义看看:
static const struct old_serial_port old_serial_port[] = {
     SERIAL_PORT_DFNS /* defined in asm/serial.h */
};
#define SERIAL_PORT_DFNS            
     /* UART CLK   PORT IRQ     FLAGS        */            
     { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS },     /* ttyS0 */  
     { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS },     /* ttyS1 */  
     { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS },     /* ttyS2 */  
     { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS },    /* ttyS3 */
在这里,注意到同一个IRQ号会对应两个port.
IRQ中发生中断的时候,怎么去判断是哪一个port所引起的.当然方法有多种多样.在这里,8250驱动的作者是将不同的port链入到IRQ对应的链
表来完成的.这样,如果IRQ产生了中断了,就判断挂在该链表中的port,看中断是否由它产生.
  
经过这个分析之后,我们应该很清楚serial8250_interrupt()中的处理流程了.对应产生IRQ的port,流程会转入serial8250_handle_port()中.代码如下:
static inline void
serial8250_handle_port(struct uart_8250_port *up)
{
     unsigned int status;
     unsigned long flags;

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

     status = serial_inp(up, UART_LSR);

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

     if (status & UART_LSR_DR)
         receive_chars(up, &status);
     check_modem_status(up);
     if (status & UART_LSR_THRE)
         transmit_chars(up);

     spin_unlock_irqrestore(&up->port.lock, flags);
}
对于产生中断的情况下,判断发送缓存区是否为空,如果为空,就可以发送数据了.对应的处理在transmit_chars(up).如果接收缓存区满,就那接收数据,这是在receive_chars()中处理的.对于接收数据,我们在下一节再分析.
transmit_chars()代码如下: 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;
     }
     if (uart_circ_empty(xmit)) {
         __stop_tx(up);
         return;
     }

     count = up->tx_loadsz;
     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);

     if (uart_circ_chars_pending(xmit)  WAKEUP_CHARS)
         uart_write_wakeup(&up->port);

     DEBUG_INTR("THRE...");

     if (uart_circ_empty(xmit))
         __stop_tx(up);
}
从上面的代码看出.会从xmit中取出数据,然后将其写入到发送寄存器中.特别的,在8250芯片的情况下, up->tx_loadsz等于1.也就是说,一次只能传送1个字节.
如果缓存区的数据传输玩了之后,就会调用__stop_tx().代码如下: static inline void __stop_tx(struct uart_8250_port *p)
{
     if (p->ier & UART_IER_THRI) {
         p->ier &= ~UART_IER_THRI;
         serial_out(p, UART_IER, p->ier);
     }
}
对应的,在IER中,将发送缓存区空的中断关掉.
  
六:数据读取操作
在前面的tty驱动架构分析中,曾说过,在
tty_driver中并末提供read接口.上层的read操作是直接到ldsic的缓存区中读数据的.那ldsic的数据是怎么送入进去的呢?继续看
中断处理中的数据接收流程.即为: receive_chars().代码片段如下:
tatic void
receive_chars(struct uart_8250_port *up, unsigned int *status)
{
     ……
     ……
     uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag);
}
最后流据会转入uart_inset_char().这个函数是uart层提供的一个接口,代码如下:
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->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);
}
Tty_insert_filp()函数的代码我们在之前已经分析过,这里不再赘述.就这样,数据就直接交给了ldisc.
  
七:轮询操作
在前面已经分析到,如果没有定义irq或者没有控测到irq号,就会采用轮询.在代码,采用定时器的方式.去判断是否有数据到来,或者将数据写入8250.定时器对应的运行函数为serial8250_backup_timeout().代码如下:
static void serial8250_backup_timeout(unsigned long data)
{
     struct uart_8250_port *up = (struct uart_8250_port *)data;
     unsigned int iir, ier = 0, lsr;
     unsigned long flags;

     /*
      * Must disable interrupts or else we risk racing with the interrupt
      * based handler.
      */
     if (is_real_interrupt(up->port.irq)) {
         ier = serial_in(up, UART_IER);
         serial_out(up, UART_IER, 0);
     }

     iir = serial_in(up, UART_IIR);

     /*
      * This should be a safe test for anyone who doesn't trust the
      * IIR bits on their UART, but it's specifically designed for
      * the "Diva" UART used on the management processor on many HP
      * ia64 and parisc boxes.
      */
     spin_lock_irqsave(&up->port.lock, flags);
     lsr = serial_in(up, UART_LSR);
     up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;
     spin_unlock_irqrestore(&up->port.lock, flags);
     if ((iir & UART_IIR_NO_INT) && (up->ier & UART_IER_THRI) &&
         (!uart_circ_empty(&up->port.info->xmit) || up->port.x_char) &&
         (lsr & UART_LSR_THRE)) {
         iir &= ~(UART_IIR_ID | UART_IIR_NO_INT);
         iir |= UART_IIR_THRI;
     }

     if (!(iir & UART_IIR_NO_INT))
         serial8250_handle_port(up);

     if (is_real_interrupt(up->port.irq))
         serial_out(up, UART_IER, ier);

     /* Standard timer interval plus 0.2s to keep the port running */
     mod_timer(&up->timer,
         jiffies + poll_timeout(up->port.timeout) + HZ / 5);
}
如果IRQ线有效,先在IER中禁用全部中断.等定时器处理函数处理完后,再恢复IER中的内容.这样主要是为了防止会产生发送缓存区空的中断.
流程最后还是会转入到serial8250_handle_port()中.这个函数我们在上面已经分析过了.
  
八:小结
分析完了这个驱动,我们可以看到.专业的开发人员思维是多么的缜密.真是滴水不漏.该代码里有很多非常精彩的处理,需要细细揣摩.


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/92500/showart_2159909.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP