linux串口自检_vb 打开串口 自检串口程序 - CSDN
  • 原文译至:...我是怎么出于各种目的(如modem,连接空的module或连接到一个dumb终端)在Linux下检查并配置串口的呢? Linux 提供了各种工具. Linux 使用 ttySx 作为一个串口设备的名称. 例如, COM1 (DOS/

    原文译至:http://www.cyberciti.biz/faq/find-out-linux-serial-ports-with-setserial/

    如何根据不同的需求(如modem,连接空的module或连接到一个dumb终端)在Linux下检查并配置串口呢?

    Linux 提供了各种工具, Linux 使用 ttySx 作为一个串口设备的名称。例如,COM1 (DOS/Windows 名字) 是 ttyS0, COM2 是 ttyS1 等等。

    任务: 显示检测到的系统串口支持

    简单的运行 dmesg 命令
    $ dmesg | grep tty
    输出:

    [   37.531286] serial8250: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A
    [   37.531841] 00:0b: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A
    [   37.532138] 0000:04:00.3: ttyS1 at I/O 0x1020 (irq = 18) is a 16550A

    setserial 命令

    setserial 是一个程序用于设定并/或报告某个串口关联的配置信息。该信息包括串口用到的I/O 端口和中断号,以及Break键是否应被解释为Secure Attention Key 等等。 仅仅是输出如下的命令:
    $ setserial -g /dev/ttyS[0123]
    输出:

    /dev/ttyS0, UART: 16550A, Port: 0x03f8, IRQ: 4
    /dev/ttyS1, UART: 16550A, Port: 0x1020, IRQ: 18
    /dev/ttyS2, UART: unknown, Port: 0x03e8, IRQ: 4
    /dev/ttyS3, UART: unknown, Port: 0x02e8, IRQ: 3

    带-g选项的setserial帮助找到你的Linux板子上的物理串口。

    Linux 串口控制台程序

    一旦串口被确定了,你就能使用许多的工具来配置Linux板子:

    1. minicom- 用于控制modem和连接到dump 设备的最好的串口通信程序。
    2. wvidial or other GUI dial up networking program - 一个内建智能PPP 拨号器。
    3. getty / agetty - agetty 打开一个 tty 端口, 提示登录名称并调用 /bin/login 命令。
    4. grub / lilo configuration - 配置串口为系统控制台。

    展开全文
  • 今天就在此基础上分析一下linux kernel自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix kernel2.6.25.相应驱动代码位于:linux-2.6.25/drivers/serial/8250.c。  二:8250串口...

    一:前言 
    前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下linux kernel自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix 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 $Revision: 1.90 $ " 
             "%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/ ttyS0(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 | ASYNC_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) { 
             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()中.这个函数我们在上面已经分析过了. 
      
    八:小结 
    分析完了这个驱动,我们可以看到.专业的开发人员思维是多么的缜密.真是滴水不漏.该代码里有很多非常精彩的处理,需要细细揣摩.

    展开全文
  • Linux系统启动过程(1):BIOS加电自检引言:PC/AT组成结构逻辑图 关于微机组成部分的基础知识分享,请见上篇文章《简介现代微型计算机的组成结构》一、概念理解BIOS (Basic Input Output System : 基本输入输出...

    Linux系统启动过程(1):BIOS加电自检

    引言:PC/AT组成结构逻辑图

     关于微机组成部分的基础知识分享,请见上篇文章《简介现代微型计算机的组成结构》

    一、概念理解

    BIOS (Basic Input Output System : 基本输入输出系统),实际上就是被"固化"在计算机硬件中、直接与硬件打交道的一组程序(内含三个主要程序模块),它为计算机提供最低级、最直接的硬件控制。

    BIOS程序一般被存放在主板ROM(只读存储芯片)之中,即使在关机或掉电以后,该程序也不会丢失。

    计算机的很多硬件中都有BIOS程序,最常见的比如:主板(也称为系统BIOS)、显示卡以及其它一些设备(例如IDE控制器、SCSI卡或网卡等)中都存在有BIOS程序;但是,计算机的启动过程是在系统BIOS的控制下进行的。所以系统BIOS是我们要介绍的主角。

    BIOS与CMO的联系与区别,详见另一篇介绍《电池、CMOS与BIOS的联系与区别》

    二、加电自检过程

    第一步:CPU RESET

    第二步:POST(自检程序)启动

    第三步:执行显示卡的BIOS(初始化)

    第四步:执行其它控制卡的BIOS(初始化)

    第五步:检测CPU与内存

    第六步:检测标准的系统硬件与即插即用设备

    第七步:显示硬件配置列表信息

    第八步:更新ESCD

    CPU RESET

    现在我们从PC微机冷启动入手,把主机的电源线插入墙壁的电源插座,正常供电的220V、50HZ的市电就输入到微机的主机电源(参考ATX规范),此时交流的市电转换为±5V、±12V、3.3V等规格的直流电。

    这是主板供电插头的pin脚定义图,8引脚为PG(Power Good)信号;14引脚为PW-ON(Power-On)信号(与GND(Ground)短接后即可触发电源工作);9、14引脚为待机供电状态,在未按下机箱电源开关之前9、14引脚输出电压均为+5V,其它引脚无输出电压。

    当我们按下机箱开机按纽(即闭合)瞬间,主板的南桥(或者I/O芯片)接收到低电位信号,其接着向ATX电源14引脚(绿)发出低电位信号,将POWER(14)+5V高电位拉低,触发ATX电源工作,这时各个引脚输出+12V,-12V.+5V ,-5V,+3.3V电压,向主板和其它各个设备供电,包括8引脚PG(Power Good)信号。流行见下图:

     

    ATX电源8脚发出PG信号通过一个或几个门电路然后分成2路(这时还是PG信号),分别给南桥、北桥;

    ①当南桥,北桥接收PG信号后,各自输出一个时钟控制信号加给时钟芯片。

    ②南桥输出的时钟控制信号控制路外围电时钟的产生,北桥控制内存、AGP、CPU时钟的产生。

    ③当时钟电路正常工作产生各路时钟送给各部分电路,其中南桥接收到时钟信号后,其复位电路工作、产生PCIRST、RSTDRV,两个复位信号,PCIRST信号去复位北桥。

    ④北桥复位完成后,由北桥产生 CUP复位信号发给CPU,当CPU接收到复位信号,就开始初始化。

    ⑤ CPU复位完成,即CPU内的各寄存器(通用寄存器、段寄存器、标志寄存器等)均复位以后,发出寻址选中BIOS程序,CPU马上就从地址FFFF0H处开始执行指令,这个地址在系统BIOS的地址范围内,无论是AWARD BIOS还是AMI BIOS,放在这里的只是一条跳转指令,跳到系统BIOS中真正的启动代码处。这就是8引脚PG信号转化成复位信号的全过程。

    POST(自检程序)启动

    系统BIOS的启动代码处第一个例程POST程序,它主要任务是检测系统中的一些关键设备是否存在和能否正常工作,如内存和显卡等。由于POST的检测过程在显示卡初始化之前,因此如果在POST自检的过程中发现了一些致命错误,如没有找到内存或者内存有问题时(POST过程只检查640K常规内存),是无法在屏幕上显示出来的,这时系统PIOS可通过喇叭发声来报告错误情况,声音的长短和次数代表了错误的类型。

    调用显示卡的BIOS(初始化)

    接下来系统BISO将查找显示卡的BIOS程序,存放显示卡BIOS程序的ROM芯片的起始地址通常在C0000H处,系统BIOS找到显卡BIOS之后调用它的初始化代码,由显卡BIOS程序来完成显示卡的初始化。这就是我们开机看到的第一个画面,显示器屏幕顶端会出现显卡BIOS的版本信息、显卡类型、显存容量等信息,不过这个画面几乎是一闪而过的。

    调用其它控制卡的BIOS(初始化)

    接着系统BIOS会查找其它设备的BIOS程序,按照CMOS RAM中存储的配置信息或主板上配置信息(如CPU的外频和倍频信息、内存数量等)去找相关设备,找到之后同样要调用这些BIOS内部的初始化代码来初始化这些设备。

    查找完所有其它设备的BIOS之后,系统BIOS将显示它自己的启动画面,其中包括有系统BIOS的类型、序列号和版本号等内容。同时屏幕底端左下角会出现主板信息代码,包含BIOS的日期、主板芯片组型号、主板的识别编码及厂商代码等;此时可以按“Del”键进入CMOS设置画面重新设置相关配置信息。

    检测CPU与内存

    接着系统BIOS将检测CPU的类型和工作频率,并将检测结果显示在屏幕上,这就是我们开机看到的CPU类型和主频。接下来系统BIOS开始测试主机所有的内存容量,并同时在屏幕上显示内存测试的数值,就是大家所熟悉的屏幕上半部份那个飞速翻滚的内存计数器。这个过程我们可以在BIOS设置中选择耗时少的"快速检测"或者耗时多的"全面检测"两种方式;比如,在CMOS中将“Quick Power On Test”设置为“Disabled”,则将检测三次。

    检测标准的系统硬件与即插即用设备

    内存测试通过之后,系统BIOS将开始检测系统中安装的一些标准硬件设备,这些设备包括:硬盘、CD-ROM、软驱、串行接口和并行接口等连接的设备。此时硬盘电源指示灯闪亮,键盘的三个指示灯(Num Lock、Caps Lock、Scroll Lock)再次地一起闪亮。

    标准的系统硬件检测完毕后,系统BIOS(内部的支持即插即用的代码)将开始检测和配置系统中安装在PCI扩展槽上(或ISA等)即插即用设备(如显示卡、声卡、视频卡、Modem等和串口、并口等I/O设备)。

    每找到一个设备之后,系统BIOS都会在屏幕上显示出设备的名称和型号等信息,同时为该设备分配中断、DMA通道和I/O端口等资源。

    显示硬件配置列表信息

    到这一步为止,所有硬件都已经检测配置完毕了,系统BIOS会重新清屏并在屏幕上方显示出一个系统配置列表,其中概略地列出了系统中安装的各种标准硬件设备,以及它们使用的资源和一些相关工作参数。

    更新ESCD

    ESCD(扩展系统配置数据)是系统BIOS用来与操作系统交换硬件配置信息的数据,这些数据被存放在CMOS(一小块特殊的RAM,由主板上的电池来供电)之中。通常ESCD数据只在系统硬件配置发生改变后才会进行更新,所以不是每次启动机器时我们都能够看到"Update  ESCD... Success"这样的信息。

    所有硬件检查完毕,系统BIOS的启动代码将进行它的最后一项工作,根据CMOS中所设置的引导顺序,加载MBR引导记录,为引导操作系统做准备。…待续!



    展开全文
  • linux 设备驱动之 8250 串口驱动  ------------------------------------------  本文系本站原创,欢迎转载!  转载请注明出处:http://ericxiao.cublog.cn/  ------------------------------------------  ...
    linux 设备驱动之 8250 串口驱动  
    ------------------------------------------  
    本文系本站原创,欢迎转载! 
    转载请注明出处:http://ericxiao.cublog.cn/ 
    ------------------------------------------ 
    一:前言 
    前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下linux kernel 自带的
    串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix 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 $Revision: 1.90 $ " 
             "%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/ ttyS0(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 | ASYNC_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) { 
             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()中.这个函数我们在上面已经分析过了. 
      
    八:小结 
    分析完了这个驱动,我们可以看到.专业的开发人员思维是多么的缜密.真是滴水不漏.该代码里有很多非常
    精彩的处理,需要细细揣摩. 
      
    展开全文
  • RS485在linux下的调试

    2019-09-07 21:41:08
    1、ubuntu串口调试工具 ubuntu下的串口调试工具为:cutecom sudo apt-get install cutecom 打开 sudo cutecom 2、ubuntu串口查找 sudo cat /proc/tty/driver/serial 可以获得如下: 0: uart:16550A ...
  • 今天就在此基础上分析一下 linux kernel 自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步, 同以往一样,基于linux kernel2.6.25. 相应驱动代码位于: linux-2.6.25/drivers/serial/8250.c。  ...
  • 这是一个和Linux串口控制台类似的解决方案, 通过一个串口输入指令,用户可以任意操作像STM32这样的单片机,执行许多非常实用的自定义功能。
  • u-boot启动分析 1.完成配置任务后,再一次make $(obj)u-boot: depend \ $(SUBDIR_TOOLS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds //start.o是第一个依赖文件,因此先分析start.s ...
  • 本节将从Linux 引导流程、 Boot Loader 的概念、Boot Loader 的主要任务、Boot Loader 的框架结构以及 Boot Loader 的优化等四个方面来讨论嵌入式系统的 Boot Loader,详细分析了系统上电到执行到linux kenel处的...
  • dmesg命令用于打印Linux系统开机启动信息,kernel会将开机信息存储在ringbuffer中。您若是开机时来不及查看信息,可利用dmesg来查看(print or control the kernel ring buffer)。开机信息亦保存在/var/log/dmesg...
  • GitHub Linux内核调试的方式以及工具集锦 LDD-LinuxDeviceDrivers/study/debug 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作 因本人技术水平和知识面...
  • 玩过Linux的朋友, 是不是对Linux无所不能的串口Shell命令控制台羡慕不已, 要是自己做的STM32F系列低档次的MCU也有这种控制交互能力, 会给调试/维护和配置省下多少麻烦事呀, 比如启动/关闭调试或自检模式, 打印调试...
  • 本文系本站原创,欢迎转载! ... ------------------------------------------ ...今天就在此基础上分析一下linux kernel 自带的 串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix
  • 要开发Linux平台下开发调试程序很难适应,还好之前做的都是些小项目,在Linux平台下索性直接用Code::Blocks这种跨平台的IDE直接开发调试了,直到最近要跟用Ubuntu系统的下位机进行串口通信才想起来这个玩意......
  • linux系统移植步骤

    2018-07-13 22:52:08
    在众多嵌入式操作系统中,Linux目前发展最快、应用最为广泛。性能优良、源码开放的Linux具有体积小、内核可裁减、网络功能完善、可移植性强等诸多优点,非常适合作为嵌入式操作系统。一个最基本的Linux操作系统应该...
1 2 3 4 5 ... 20
收藏数 1,318
精华内容 527
关键字:

linux串口自检