8250.c

转自:http://www.360doc.com/content/12/0630/16/532901_221365564.shtml


一:前言

前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下 linux kernel  自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步, 同以往一样,基于linux kernel2.6.25.

相应驱动代码位于: linux-2.6.25/drivers/serial/8250.c。 


二:8250串口驱动初始化  
相应的初始化函数为 serial8250_init().代码如下 : 
static int __init serial8250_init(void) 

     int ret, i;   
     if (nr_uarts > UART_NR) 

         nr_uarts = UART_NR;   
     printk(KERN_INFO "Serial: 8250/16550 driver " 
         "%d ports, IRQ sharing %sabled/n", nr_uarts, 
         share_irqs ? "en" : "dis");   
     for (i = 0; i < NR_IRQS; i++) 
         spin_lock_init(&irq_lists[i].lock);   
     ret = uart_register_driver(&serial8250_reg); 
     if (ret) 
         goto out;   
     serial8250_isa_devs = platform_device_alloc("serial8250", 
               PLAT8250_DEV_LEGACY); 
     if (!serial8250_isa_devs) { 
         ret = -ENOMEM; 
         goto unreg_uart_drv; 
     }   
     ret = platform_device_add(serial8250_isa_devs); 
     if (ret) 
         goto put_dev;   
     serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);   
     ret = platform_driver_register(&serial8250_isa_driver);      

 if (ret == 0) 
         goto out;   
     platform_device_del(serial8250_isa_devs); 
 put_dev: 
     platform_device_put(serial8250_isa_devs); 
 unreg_uart_drv: 
     uart_unregister_driver(&serial8250_reg); 
 out: 
     return ret; 

这段代码涉及到的知识要求,如platform ,uart等我们在之前都已经做过详细的分析。这里不再重复。在代码中 UART_NR: 表示串口的个数。这个参数在编译内核的时候可以自己配置,默认为32 。 
我们按照代码中的流程一步一步进行研究。 
1: 注册uart_driver.   
对应uart-driver的结构为 serial8250_reg. 定义如下: 
static struct uart_driver serial8250_reg = { 
     .owner             = THIS_MODULE, 
     .driver_name       = "serial", 
     .dev_name     = "ttyS", 
     .major             = TTY_MAJOR, 
     .minor             = 64, 
     .nr           = UART_NR, 
     .cons              = SERIAL8250_CONSOLE, 
}; 

TTY_MAJOR定义如下: 
#define TTY_MAJOR      4 
从上面可以看出。串口对应的设备节点为/dev/ ttyS0 ~ /dev/ ttyS(UART_NR).设备节点号为( 4,64 )起始的 UART_NR 个节点。  
2:初始化并注册 platform_device 
相关代码如下: 
serial8250_isa_devs = platform_device_alloc("serial8250", PAT8250_DEV_LEGACY); 
platform_device_add(serial8250_isa_devs); 
可以看出。serial8250_isa_devs->name为 serial8250。这个参数是在匹配 platform_device 和
platform_driver使用的。  
3:为uart-driver 添加  port. 
相关代码如下: 
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev) 
跟进这个函数看一下: 
static void __init 
serial8250_register_ports(struct uart_driver *drv, struct device *dev) 
{     

 int i;   
     serial8250_isa_init_ports();   
     for (i = 0; i < nr_uarts; i++) { 
         struct uart_8250_port *up = &serial8250_ports[i];   
         up->port.dev = dev; 
         uart_add_one_port(drv, &up->port); 
     } 
}

在这里函数里,初始化了port.然后将它添加到 uart-driver 中。我们还注意到。生成的 deivce 节点,在 sysfs 中是位于  platform_deivce 对应目录的下面 . 
serial8250_isa_init_ports()代码如下所示: 
static void __init serial8250_isa_init_ports(void) 

     struct uart_8250_port *up; 
     static int first = 1; 
     int i;   
     if (!first) 
         return; 
     first = 0;   
     for (i = 0; i < nr_uarts; i++) { 
         struct uart_8250_port *up = &serial8250_ports[i];   
         up->port.line = i; 
         spin_lock_init(&up->port.lock);   
         init_timer(&up->timer); 
         up->timer.function = serial8250_timeout;   
         /* 
          * ALPHA_KLUDGE_MCR needs to be killed. 
          */ 
         up->mcr_mask = ~ALPHA_KLUDGE_MCR; 
         up->mcr_force = ALPHA_KLUDGE_MCR;   
         up->port.ops = &serial8250_pops; 
     } 

     for (i = 0, up = serial8250_ports; 
          i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;i++, up++) { 
         up->port.iobase= old_serial_port[i].port; 
         up->port.irq= irq_canonicalize(old_serial_port[i].irq); 
         up->port.uartclk = old_serial_port[i].baud_base * 16; 
         up->port.flags    = old_serial_port[i].flags; 
         up->port.hub6= old_serial_port[i].hub6; 
         up->port.membase= old_serial_port[i].iomem_base; 
         up->port.iotype= old_serial_port[i].io_type; 
         up->port.regshift = old_serial_port[i].iomem_reg_shift; 
         if (share_irqs) 

              up->port.flags |= UPF_SHARE_IRQ; 

     } 

}

在这里,我们关注一下注要成员的初始化。 Uart_port 的各项操作位于 serial8250_pops 中。 Iobase 、 irq 等成员是从 old_serial_por 这个结构中得来的,这个结构如下所示: 
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 */ 
从上面看到。前两项对应了 com1、 com2的各项参数。如寄存器首始地址, Irq 号等。后面两项不太清楚。在上面的代码中,我们看到了 uart_port  各项成员的初始化。在后面很多操作中需要用到这个成员。我们等分析相关部份的时候,再到这个地方来看相关成员的值。 
 

4:注册platform_driver  
相关代码如下: 
platform_driver_register(&serial8250_isa_driver); 
serial8250_isa_driver定义如下: 
static struct platform_driver serial8250_isa_driver = { 
     .probe=  serial8250_probe, 
     .remove=  __devexit_p(serial8250_remove), 
     .suspend=  serial8250_suspend, 
     .resume=  serial8250_resume, 
     .driver =  {  
      .name = "serial8250", 
     .owner = THIS_MODULE, 
     }, 

  为了以后把分析集中到具体的驱动部份.我们先把这个 platform_driver 引出的问题讲述完 . 
经过前面有关platform的分 析我们知道,这个 platform 的 name 为 ” serial8250” 。刚好跟前面注册的 platform_device 相匹配 . 会调用  platform_driver-> probe. 在这里 , 对应的接口为 : 
serial8250_probe().代码如下: 
static int __devinit serial8250_probe(struct platform_device *dev) 

     struct plat_serial8250_port  *p =  dev->dev.platform_data; 
     struct uart_port port; 
     int ret, i;   
     memset(&port, 0, sizeof(struct uart_port));   
     for (i = 0; p && p->flags != 0; p++, i++) { 
         port.iobase= p->iobase; 
         port.membase= p->membase; 
         port.irq= p->irq; 
         port.uartclk= p->uartclk; 
         port.regshift= p->regshift; 
         port.iotype= p->iotype; 
         port.flags= p->flags; 
         port.mapbase= p->mapbase; 
         port.hub6= p->hub6; 
         port.private_data= p->private_data; 
         port.dev= &dev->dev; 
         if (share_irqs) 
              port.flags |= UPF_SHARE_IRQ; 
         ret  =  serial8250_register_port(&port); 
         if (ret < 0) { 
              dev_err(&dev->dev, "unable to register port at index %d " 
                   "(IO%lx MEM%llx IRQ%d): %d/n", i, 
                   p->iobase, (unsigned long long)p->mapbase, 
                   p->irq, ret); 
         } 
     } 
     return 0; 

从上述代码可以看出.会 将 dev->dev.platform_data 所代表的 port 添加到 uart_driver 中。这个
dev->dev.platform_data 究竟代表什么. 我们在看到的时候再来研究它. 
现在,我们把精力集中到 uart_port 的操作上。

三:config_port 过程  
在初始化uart_port的过程中 , 在以下代码片段 : 
serial8250_isa_init_ports(void) 
{     ......

for (i = 0, up = serial8250_ports; 
          i < ARRAY_SIZE(old_serial_port) && i < nr_uarts; 
          i++, up++) { 
         up->port.iobase   =  old_serial_port[i].port; 
         up->port.irq      =  irq_canonicalize(old_serial_port[i].irq); 
         up->port.uartclk  =  old_serial_port[i].baud_base * 16; 
         up->port.flags    =  old_serial_port[i].flags; 
         up->port.hub6     =  old_serial_port[i].hub6; 
         up->port.membase  =  old_serial_port[i].iomem_base; 
         up->port.iotype   =  old_serial_port[i].io_type; 
         up->port.regshift =  old_serial_port[i].iomem_reg_shift; 
         if (share_irqs) 
              up->port.flags |= UPF_SHARE_IRQ; 
     } 

而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 */   
由此可见,port->flags被定义成了 STD_COM_FLAGS, 定义如下: 
#ifdef CONFIG_SERIAL_DETECT_IRQ 
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST | 
ASYNC_AUTO_IRQ) 
#define STD_COM4_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_AUTO_IRQ) 
#else 
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASY NC_SKIP_TEST) 
#define STD_COM4_FLAGS ASYNC_BOOT_AUTOCONF 
#endif 
从这里看到,不管是否自己探测 IRQ, 都会定义 ASYNC_BOOT_AUTOCONF 。这样 , 在
uart_add_one_port()的时候。就会进入到port->config_port 来配置端口,在 8250 中 , 对应的接口为 : 
serial8250_config_port(). 代码如下: 
static void serial8250_config_port(struct uart_port *port, int flags) 

     struct uart_8250_port *up = (struct uart_8250_port *)port; 
     int probeflags =  PROBE_ANY;      

 int ret;   
     /* 
      * Find the region that we can probe for.  This in turn 
      * tells us whether we can probe for the type of port. 
      */ 
     ret = serial8250_request_std_resource(up); 
     if (ret < 0) 
         return;   
     ret = serial8250_request_rsa_resource(up); 
     if (ret < 0) 
         probeflags &= ~PROBE_RSA;   
     if (flags & UART_CONFIG_TYPE) 
         autoconfig(up, probeflags); 
     if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ) 
         autoconfig_irq(up);   
     if (up->port.type != PORT_RSA && probeflags & PROBE_RSA) 
         serial8250_release_rsa_resource(up); 
     if (up->port.type == PORT_UNKNOWN) 
         serial8250_release_std_resource(up); 
}
serial8250_request_std_resource 和serial8250_request_rsa_resource 都是分配操作的端口,回
顾在前面的分析中,port的相关参数会从  old_serial_port 中取得 . 而 old_serial_port 中又没有定义 port->iotype  和 port-> regshift. 也就是说对应这两项全为 0. 而 
#define UPIO_PORT      (0) 
即表示是要操作I/O端口。自己阅 读这两个函数,会发现在 serial8250_request_rsa_resource() 中是会返回失败的。另外, 在uart_add_one_port() 在进行端口匹配时 , 会先置 flags 为 UART_CONFIG_TYPE 。这样 , 在本次操作中 , if (flags & UART_CONFIG_TYPE)是会满足的,相应的就会进入 autoconfig() 。 
代码如下,这段代码比较长 , 分段分析如下 :  
static void autoconfig(struct uart_8250_port *up, unsigned int probeflags) 

     unsigned char status1, scratch, scratch2, scratch3; 
     unsigned char save_lcr, save_mcr; 
     unsigned long flags;   
     if (!up->port.iobase && !up->port.mapbase && !up->port.membase) 
         return;   
     DEBUG_AUTOCONF("ttyS%d: autoconf (0x%04x, 0x%p): ", 
              up->port.line, up->port.iobase, up->port.membase);   
     /* 
      * We really do need global IRQs disabled here - we're going to 
      * be frobbing the chips IRQ enable register to see if it exists. 
      */ 
     spin_lock_irqsave(&up->port.lock, flags);   
     up->capabilities = 0; 
     up->bugs = 0;   
  if (!(up->port.flags & UPF_BUGGY_UART)) {   
          /* 
          * Do a simple existence test first; if we fail this, 
          * there's no point trying anything else. 
          * 
          * 0x80 is used as a nonsense port to prevent against 
          * false positives due to ISA bus float.  The 
          * assumption is that 0x80 is a non-existent port; 
          * which should be safe since include/asm/io.h also 
          * makes this assumption. 
          * 
          * Note: this is safe as long as MCR bit 4 is clear 
          * and the device is in "PC" mode. 
          */   
          scratch = serial_inp(up, UART_IER); 
         serial_outp(up, UART_IER, 0); 
#ifdef __i386__ 
         outb(0xff, 0x080); 
#endif 
         /* 
          * Mask out IER[7:4] bits for test as some UARTs (e.g. TL 
          * 16C754B) allow only to modify them if an EFR bit is set. 
          */ 
         scratch2 = serial_inp(up, UART_IER) & 0x0f; 
         serial_outp(up, UART_IER, 0x0F); 
#ifdef __i386__ 
         outb(0, 0x080); 
#endif 
         scratch3 = serial_inp(up, UART_IER) & 0x0f; 
         serial_outp(up, UART_IER, scratch); 
         if (scratch2 != 0 || scratch3 != 0x0F) { 
              /*                * We failed; there's nothing here 
               */ 
              DEBUG_AUTOCONF("IER test failed (%02x, %02x) ", 
                         scratch2, scratch3); 
              goto out; 
         } 
     } 
在这里,先对 8250 是否存在做一个简单的判断 ,先将IER中的值取得 , 这样可以在测试之后恢复 IER 中的值 ,然后往IER中写放 0 , 再将IER 中的值取出 , 又往IER 中写入 0xOF. 然后再将 IER 中的值取出 . 最后将 IER 中的值恢复到原值 。 这样就可以根据写入的值和读出的值是否相等来判断该寄存器是否存在.   
     save_mcr = serial_in(up, UART_MCR); 
     save_lcr = serial_in(up, UART_LCR); 
在这里,先将 MCR 和  LCR 中的值取出 ,因为在后面的操作中会使用这两个寄存器 , 方便使用完了恢复 
     /* 
      * Check to see if a UART is really there.  Certain broken 
      * internal modems based on the Rockwell chipset fail this 
      * test, because they apparently don't implement the loopback 
      * test mode.  So this test is skipped on the COM 1 through 
      * COM 4 ports.  This *should* be safe, since no board 
      * manufacturer would be stupid enough to design a board 
      * that conflicts with COM 1-4 --- we hope! 
      */   
     if (!(up->port.flags & UPF_SKIP_TEST)) { 
         serial_outp(up, UART_MCR, UART_MCR_LOOP | 0x0A); 
         status1 = serial_inp(up, UART_MSR) & 0xF0; 
         serial_outp(up, UART_MCR, save_mcr); 
         if (status1 != 0x90) { 
              DEBUG_AUTOCONF("LOOP test failed (%02x) ", 
                          status1); 
              goto out; 
         } 
     } 
在这里,将 MCR的自检位置位 , 并允许向中断控制器产生中断 ,而且产生RTS信号 ,这样MSR寄存器应该可以检测到这个信号 . 如果没有检测到 . 自测失 败 !MCR 寄存器已经操作完了 , 恢复 MCR 寄存器的原值 。
     /* 
      * We're pretty sure there's a port here.  Lets find out what 
      * type of port it is.  The IIR top two bits allows us to find 
      * out if it's 8250 or 16450, 16550, 16550A or later.  This 
      * determines what we test for next. 
      * 
      * We also initialise the EFR (if any) to zero for later.  The 
      * EFR occupies the same register location as the FCR and IIR.       */ 
     serial_outp(up, UART_LCR, 0xBF); 
     serial_outp(up, UART_EFR, 0); 
     serial_outp(up, UART_LCR, 0);   
     serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO); 
     scratch = serial_in(up, UART_IIR) >> 6;   
     DEBUG_AUTOCONF("iir=%d ", scratch);   
     switch (scratch) { 
     case 0: 
         autoconfig_8250(up); 
         break; 
     case 1: 
         up->port.type = PORT_UNKNOWN; 
         break; 
     case 2: 
         up->port.type = PORT_16550; 
         break; 
     case 3: 
         autoconfig_16550a(up); 
         break; 
     } 
在这里,先允许使用 FIFO 寄存器 , 然后通过 IIR 寄存的高二位来判断芯片的类型  
#ifdef CONFIG_SERIAL_8250_RSA 
     /* 
      * Only probe for RSA ports if we got the region. 
      */ 
     if (up->port.type == PORT_16550A && probeflags & PROBE_RSA) { 
         int i; 
  
         for (i = 0 ; i < probe_rsa_count; ++i) { 
              if (probe_rsa[i] == up->port.iobase && 
                  __enable_rsa(up)) { 
                   up->port.type = PORT_RSA; 
                   break; 
              } 
         } 
     } 
#endif   
#ifdef CONFIG_SERIAL_8250_AU1X00      /* if access method is AU, it is a 16550 with a quirk */ 
     if (up->port.type == PORT_16550A && up->port.iotype == UPIO_AU) 
         up->bugs |= UART_BUG_NOMSR; 
#endif   
     serial_outp(up, UART_LCR, save_lcr);   
     if (up->capabilities != uart_config[up->port.type].flags) { 
         printk(KERN_WARNING 
                "ttyS%d: detected caps %08x should be %08x/n", 
              up->port.line, up->capabilities, 
              uart_config[up->port.type].flags); 
     }   
     up->port.fifosize = uart_config[up->port.type].fifo_size; 
     up->capabilities = uart_config[up->port.type].flags; 
     up->tx_loadsz = uart_config[up->port.type].tx_loadsz;   
     if (up->port.type == PORT_UNKNOWN) 
         goto out;   
     /* 
      * Reset the UART. 
      */ 
#ifdef CONFIG_SERIAL_8250_RSA 
     if (up->port.type == PORT_RSA) 
         serial_outp(up, UART_RSA_FRR, 0); 
#endif 
     serial_outp(up, UART_MCR, save_mcr); 
     serial8250_clear_fifos(up); 
     serial_in(up, UART_RX); 
     if (up->capabilities & UART_CAP_UUE) 
         serial_outp(up, UART_IER, UART_IER_UUE); 
     else 
         serial_outp(up, UART_IER, 0); 
  
 out: 
     spin_unlock_irqrestore(&up->port.lock, flags); 
     DEBUG_AUTOCONF("type=%s/n", uart_config[up->port.type].name); 

最后,复位串口控制器,我们假设使用的是 8250 串口芯片 ,在芯片类型判断的时候就会进入autoconfig_8250().代码如 下 : 
static void autoconfig_8250(struct uart_8250_port *up) { 
     unsigned char scratch, status1, status2;   
     up->port.type = PORT_8250;   
     scratch = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, 0xa5); 
     status1 = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, 0x5a); 
     status2 = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, scratch);   
     if (status1 == 0xa5 && status2 == 0x5a) 
         up->port.type = PORT_16450; 

如果存在SCR寄存器 , 则芯片是 16450 类型的 ,这不是我们需要研究的芯片 。   
回到serial8250_config_port()中 ,代码片 段如下所示 : 
static void serial8250_config_port(struct uart_port *port, int flags) 

     …… 
     if (flags & UART_CONFIG_TYPE) 
         autoconfig(up, probeflags); 
     if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ) 
         autoconfig_irq(up); 
  
     if (up->port.type != PORT_RSA && probeflags & PROBE_RSA) 
        serial8250_release_rsa_resource(up); 
     if (up->port.type == PORT_UNKNOWN) 
         serial8250_release_std_resource(up); 

如果定义了自己控测IRQ号 (CONFIG_SERIAL_8250_DETECT_IRQ), 一般情况下, 编译内核的时候一般都将其赋值为 CONFIG_SERIAL_8250_DETECT_IRQ = y, 此时就会进入autoconfig_irq(). 代码如下 : 
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) { 
  &nbs p;      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().代码片 段如下: 
static 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()中 .这个函数我们在上面已经分析过了 。
 
八:小结 
分析完了这个驱动,我们可以看到 ,专业的开发人员思维是多么的缜密 , 真是滴水不漏 , 该代码里有很多非常精彩的处理,需要细细揣摩。


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值