Chinaunix首页 | 论坛 | 博客
  • 博客访问: 251085
  • 博文数量: 76
  • 博客积分: 1491
  • 博客等级: 上尉
  • 技术积分: 590
  • 用 户 组: 普通用户
  • 注册时间: 2009-02-06 08:57
文章分类

全部博文(76)

文章存档

2012年(3)

2010年(30)

2009年(43)

分类: LINUX

2010-01-25 15:01:16

------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处: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 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().代码如下:


阅读(2218) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~