最新消息:20190717 VPS服务器:Vultr新加坡,WordPress主题:大前端D8,统一介绍入口:关于

【转】uart启动流程,及8250.c分析

Driver crifan 2046浏览 0评论

【转】uart启动流程,及8250.c分析

 

一.串口初始化流程

在init/main.c start_kernel()–>setup_arch()–>arch_mem_init()–>plat_mem_setup()–>clx_serial_setup()

 

二.函数分析

函数一:clx_serial_setup

void __init clx_serial_setup(void)
{
        struct uart_port s;
        int line = 0;

        memset(&s, 0, sizeof(s));

        REG8(UART0_FCR) |= UARTFCR_UUE;     //设置UFCR.UME=1,使能UART0
        REG8(UART1_FCR) |= UARTFCR_UUE;     //设置UFCR.UME=1,使能UART1

        s.type = PORT_16550A;               // 16550A工业标准
        s.iotype = UPIO_MEM;                // I/o类型是mem
        s.regshift = 2;                     // uart_port.regshift=2
        s.fifosize = 1;                     // uart_port.fifosize=1,传输fifo=1

        s.uartclk= clx_clocks.uartclk;      // UART时钟
        s.flags = STD_COM_FLAGS;            //STD_COM_FLAGS=ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST,意思是启动时自动配置端口,并且在自动配置期间跳过UART检测

#if !defined(CONFIG_CLX_UART0_REMR)
        s.line = line;                // line=0,uart0号串口
        s.irq = IRQ_UART0;                  // uart0的irq中断号
        s.membase = (unsigned char __iomem *)UART0_BASE; //uart0 的基地址
        if (early_serial_setup(&s) != 0)     //调用early_serial_setup完成串口0设置,具体看后面函数二
                printk(KERN_ERR "Serial ttyS0 setup failed!\n");
        line++;
#endif

#if !defined(CONFIG_CLX_UART1_REMR)
        s.line = line;                // line=1,uart1号串口
        s.irq = IRQ_UART1;                  // uart1的irq中断号
        s.membase = (unsigned char __iomem *)UART1_BASE; //uart1 的基地址
        if (early_serial_setup(&s) != 0)    //调用early_serial_setup完成串口1设置
                printk(KERN_ERR "Serial ttyS1 setup failed!\n");
#endif
}
 

函数二:early_serial_setup

int __init early_serial_setup(struct uart_port *port) 路径drivers/serial/8250.c
{         
        if (port->line >= ARRAY_SIZE(serial8250_ports))
                return -ENODEV;   //如果对应的串口号没在数组列表中,则表示没有该设备,返回ENODEV
            
        serial8250_isa_init_ports(); //该函数的作用是完成对应端口的初始化工作,具体分析看后面函数三
        serial8250_ports[port->line].port       = *port; //也就是serial8250_port[端口号]=传递过来参数的指针
        serial8250_ports[port->line].port.ops   = &serial8250_pops; //对应端口的操作
        return 0;
}

 

函数三: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;
    //初始化nr_uarts个串口 结构体的port.line,time定时器
        for (i = 0; i < nr_uarts; i++) {   //这里nr_uarts 是配置的4个
                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;   // 用户位mask
                up->mcr_force = ALPHA_KLUDGE_MCR;   // forced位mask

                up->port.ops = &serial8250_pops;    //port.ops设置
        }
     //port端口的初始化,在后面这些值会被覆盖,serial8250_ports[port->line].port= *port;
        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;
        }
}

 

三.8250.c uart驱动分析

1.初始化分析

static int __init serial8250_init(void)
{
        int ret, i;

        if (nr_uarts > UART_NR)
                nr_uarts = UART_NR;

     //输出有几个串口,默认值是32,是否采用共享中断;这里串口有4个,没有使用串口中断
        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); //注册uart串口驱动
        if (ret)
                goto out;

   //创建一个platform_device结构:serial8250_isa_devs
        serial8250_isa_devs = platform_device_alloc("serial8250",
                                                    PLAT8250_DEV_LEGACY);
       
        if (!serial8250_isa_devs) {
                ret = -ENOMEM;
                goto unreg_uart_drv;
        }
        //将该结构serial8250_isa_devs注册到总线上
        ret = platform_device_add(serial8250_isa_devs);
        if (ret)
                goto put_dev;
   //添加端口,具体分析见后面函数一
        serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);


    //platform driver驱动注册
        ret = platform_driver_register(&serial8250_isa_driver); //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;
}

函数一:serial8250_register_ports

static void __init
serial8250_register_ports(struct uart_driver *drv, struct device *dev) //两个参数,第一个表示被注册的uart_driver结构体,第二个参数device
{
        int i;
              
        serial8250_isa_init_ports(); //该函数二中函数函数三也分析
       
        //添加nr_uarts=4个端口     
        for (i = 0; i < nr_uarts; i++) {

       //serial8250_ports[i]对应的值在前面二中函数二early_serial_setup() ,以被赋了相应值
                struct uart_8250_port *up = &serial8250_ports[i];

                up->port.dev = dev;
                uart_add_one_port(drv, &up->port); //添加端口函数
        }
}

结构体一: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的分析我们知道.这个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.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%lx IRQ%d): %d\n", i,
                                p->iobase, p->mapbase, p->irq, ret);
                }
        }
        return 0;
}

从上述代码可以看出.会将dev->dev.platform_data所代表的port添加到uart_driver中.这个dev->dev.platform_data究竟代表什么.我们在看到的时候再来研究它.
后面的解释是这样的 经过这个 config_port过程后,我们发现,并没有对serial8250_isa_devs->dev-> platform_data赋值,也就是说platform_driver->probe函数并无实质性的处理.在第一次for循环的时,就会因条件不符而退出.

2.现在,我们把精力集中到uart_port的操作上

serial8250_register_ports()–>uart_add_one_port()–>uart_configure_port()–>port->ops->config_port()–>serial8250_config_port()

函数一:serial8250_config_port

static void serial8250_config_port(struct uart_port *port, int flags)
{
//参数一:对应的端口信息,
参数二:flags=UART_CONFIG_TYPE

        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都是分配操作的端口.
自己阅读这两个函数代表.会发现在serial8250_request_rsa_resource()中是会返回失败的.
另外,在uart_add_one_port()在进行端口匹配时,会先置flags为UART_CONFIG_TYPE.
这样,在本次操作中, if (flags & UART_CONFIG_TYPE)是会满足的.相应的就会进入autoconfig().

在autoconfig中又会调用autoconfig_16550a(up);

其他的可以不去管。
经过这个 config_port过程后,我们发现,并没有对serial8250_isa_devs->dev-> platform_data赋值,也就是说platform_driver->probe函数并无实质性的处理.在第一次for循环的时,就会因条件不符而退出.

3.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;
   
         //从结构体uart_config中取得相应的配置
        up->capabilities = uart_config[up->port.type].flags;
        up->mcr = 0;
       
        if (up->port.type == PORT_16C950) { //这里我们没有调用
          ……………………
        }

#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())
         */
       //清楚FIFO buffers并 disable 他们,但会在以后set_termios()函数中,重新使能他们
        serial8250_clear_fifos(up);

        /*
         * Clear the interrupt registers.
         */
   复位LSR,RX,IIR,MSR寄存器
        (void) serial_inp(up, UART_LSR);
        (void) serial_inp(up, UART_RX);
        (void) serial_inp(up, UART_IIR);
        (void) serial_inp(up, UART_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.
         */
        //若LSR寄存器中的值为0xFF.异常
        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;
        }
     /*
         * For a XR16C850, we need to set the trigger levels
         */
       //16850系列芯片的处理,忽略
        if (up->port.type == PORT_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); //ULCR.WLS=11,即选择8位

        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);

        /*
         * 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);
        }

        /*
         * And clear the interrupt registers again for luck.
         */
        (void) serial_inp(up, UART_LSR);
        (void) serial_inp(up, UART_RX);
        (void) serial_inp(up, UART_IIR);
        (void) serial_inp(up, UART_MSR);

        return 0;
} 

转载请注明:在路上 » 【转】uart启动流程,及8250.c分析

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
63 queries in 0.090 seconds, using 18.95MB memory