• uart驱动框架分析(二)uart_add_one_port


    作者:lizuobin (百问网论坛答疑助手)
    原文:
    https://blog.csdn.net/lizuobin2/article/details/51801183
    (所用开发板:mini2440, jz2440类似,代码很多,需要你建个si工程跟着代码分析)

    在前面的一篇文章中: tty初探 — uart驱动框架分析,我们分析了一个 uart_driver 的向上注册过程,主要是 tty 的一些东西,知道了 tty 注册了一个字符设备驱动,在用户空间 open 时将调用到 uart_port.ops.startup ,在用户空间 write 则调用 uart_port.ops.start_tx ,还知道了如何 read 数据等等。

    但是,这些都是内核帮我们实现好的,在真正的驱动开发过程中几乎不涉及那些代码的修改移植工作,真正需要我们触碰的是 uart_port 这个结构体,它真正的对应于一个物理的串口。

    其实,真正需要我们做的工作就是分配一个uart_port 结构,然后 uart_add_one_port 。分析过 s3c2440 uart 的驱动代码之后,我发现,这么一个简单的目标简直就像是经历了山路十八弯。

    先说一下大体的思路,uart_port 的注册过程是基于 platform 平台设备驱动模型,device 侧提供 3 个串口的硬件信息,并注册到 platform_bus_type 中去。然后 driver 也注册到 platform_bus_type 时,就会根据名字进行匹配,从而调用 driver->probe 函数,在 probe 函数里进行 uart_add_one_port 。思路简单,复杂在 s3c2440 注册 device 之前的工作扯了太多东西。

    先秀个最终分析的图:

    一、Linux 启动过程回忆

    在 uboot 启动内核的时候,内核刚刚启动我们就看到串口各种信息输出了,也就是说串口驱动的初始化工作是在 Linux 启动过程中一个比较靠前的位置。内核启动的时候首先会去判断 cpu id 是否支持,接着判断是否支持uboot 传递进来的单板 Id ,然后 start_kernel -》setup_arch 进行一系列的初始化工作,其中必然包含串口相关初始化。

    内核中所有支持的单板都用 MACHINE_START 和 MACHINE_END 来定义
    在板级相关文件linux-2.6.22.6archarmmach-xxx2440mach-xxx2440中:

    MACHINE_START(MINI2440, "FriendlyARM Mini2440 development board")
      .phys_io  = S3C2410_PA_UART,
      .io_pg_offst  = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
      .boot_params  = S3C2410_SDRAM_PA + 0x100,
      .init_irq  = s3c24xx_init_irq,
      .map_io    = mini2440_map_io,
      .init_machine  = mini2440_machine_init,
      .timer    = &s3c24xx_timer,
    MACHINE_END
    

    但是,里面的这些函数是何时被调用的,调用的先后顺序是怎样的,我们需要分析 Linux 的启动流程才能知道,信息量比较大,在前面的一篇文章中分析过了
    请参考:
    http://blog.csdn.net/lizuobin2/article/details/51779064

    如果你自己分析一遍的话,调用先后顺序应该是这样的:

    start_kernel -》setup_arch -》 map_io -》 init_irq -》 timer -》 init_machine -》 s3c_arch_init -》 s3c24xx_serial_modinit -》s3c2440_serial_init
    后面三个函数是通过类似于 module_init 等被组织进内核里去的放在一个特殊的段里,内核启动到一定时候就去把这个段里的每一个函数取出来去调用,也是与串口相关的,分析过程不再赘述。

    二、platform device 的注册之路

    分析出了整个的串口驱动的初始化、设置、注册流程,问题就简单多了,挨个函数分析便是。

    static void __init mini2440_map_io(void)
    {
      s3c24xx_init_io(mini2440_iodesc, ARRAY_SIZE(mini2440_iodesc));
      s3c24xx_init_clocks(12000000);
      s3c24xx_init_uarts(mini2440_uartcfgs, ARRAY_SIZE(mini2440_uartcfgs));
    }
    

    能一眼看出来,外部晶振的频率12M ,如果我们在移植其它单板的时候不是 12M ,记得修改。

    void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
    {
      ....
      s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
    }
    
    static struct cpu_table *cpu;
    
    void __init s3c_init_cpu(unsigned long idcode,struct cpu_table *cputab, unsigned int cputab_size)
    {
      cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);
      cpu->map_io();
    }
    
    static struct cpu_table * __init s3c_lookup_cpu(unsigned long idcode,
    struct cpu_table *tab,
    unsigned int count)
    {
    for (; count != 0; count--, tab++) {
    if ((idcode & tab->idmask) == tab->idcode)
    return tab;
      }
     
    return NULL;
    }
    
    static struct cpu_table cpu_ids[] __initdata = {
      {
        ...
        .idcode    = 0x32440000,
        .idmask    = 0xffffffff,
        .map_io    = s3c244x_map_io,
        .init_clocks  = s3c244x_init_clocks,
        .init_uarts  = s3c244x_init_uarts,
        .init    = s3c2440_init,
        .name    = name_s3c2440
      },
      ...
    };
    

    上边四段代码费尽周折,只为调用 cpu_ids 数组里的 s3c244x_map_io 函数。

    void __init s3c244x_map_io(void)
    {
    /* register our io-tables */
     
      iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));
     
    /* rename any peripherals used differing from the s3c2410 */
     
      s3c_device_sdi.name  = "s3c2440-sdi";
      s3c_device_i2c0.name  = "s3c2440-i2c";
      s3c_device_nand.name = "s3c2440-nand";
      s3c_device_usbgadget.name = "s3c2440-usbgadget";
    }
    

    也是醉醉的,竟然跟串口毫无关系。下面看 s3c24xx_init_uarts

    void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)
    {
      (cpu->init_uarts)(cfg, no);
    }
    

    呵,前边的工作果然也不是完全白做的,至少帮我们找到了 cpu ,那么就是调用 s3c244x_init_uarts

    void __init s3c244x_init_uarts(struct s3c2410_uartcfg *cfg, int no)
    {
      s3c24xx_init_uartdevs("s3c2440-uart", s3c2410_uart_resources, cfg, no);
    }
    

    继续往下看之前,我们先看一下参数 cfg , no ,s3c2410_uart_resources

    s3c24xx_init_uarts(mini2440_uartcfgs, ARRAY_SIZE(mini2440_uartcfgs));
    static struct s3c2410_uartcfg mini2440_uartcfgs[] __initdata = {
    
      [0] = {
        .hwport      = 0,
        .flags       = 0,
        .ucon        = 0x3c5,
        .ulcon       = 0x03,
        .ufcon       = 0x51,
      },
    /* 此处略去了 1、2 两个串口的信息 */
    };
    
    struct s3c24xx_uart_resources s3c2410_uart_resources[] __initdata = {
    [0] = {
        .resources  = s3c2410_uart0_resource,
        .nr_resources  = ARRAY_SIZE(s3c2410_uart0_resource),
      },
    /* 此处略去了 1、2 串口的信息 */
    };
    static struct resource s3c2410_uart0_resource[] = {
    
      [0] = {
        .start = S3C2410_PA_UART0,
        .end   = S3C2410_PA_UART0 + 0x3fff,
        .flags = IORESOURCE_MEM,
      },
      [1] = {
        .start = IRQ_S3CUART_RX0,
        .end   = IRQ_S3CUART_ERR0,
        .flags = IORESOURCE_IRQ,
      }
    };
    

    万事俱备,开始构建 device
    linux-2.6.22.6linux-2.6.22.6archarmplat-s3c24xxdevs.c中:

    struct platform_device *s3c24xx_uart_src[4] = {
      &s3c24xx_uart_device0,
      &s3c24xx_uart_device1,
      &s3c24xx_uart_device2,
      &s3c24xx_uart_device3,
    };
    
    static struct s3c2410_uartcfg uart_cfgs[CONFIG_SERIAL_SAMSUNG_UARTS];
    /*填充平台设备的过程,未注册 */
    void __init s3c24xx_init_uartdevs(char *name, struct s3c24xx_uart_resources *res,
              struct s3c2410_uartcfg *cfg, int no)
    {
    struct platform_device *platdev;
    struct s3c2410_uartcfg *cfgptr = uart_cfgs;
    struct s3c24xx_uart_resources *resp;
    int uart;
    
    /* 将mini2440_uartcfgs 数组里的参数拷贝到 cfgptr*/
    memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);
     
    for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {
        
    /*从 s3c24xx_uart_src 数组里取出平台设备 */
        platdev = s3c24xx_uart_src[cfgptr->hwport];
        
    /*获得对应的 resource ,物理寄存器和中断 */
        resp = res + cfgptr->hwport;
     
    /* 将 s3c24xx_uart_src 的平台设备放到平台设备数组 s3c24xx_uart_devs */
        s3c24xx_uart_devs[uart] = platdev;
        
    /*设置名字、资源 */
        platdev->name = name;
        platdev->resource = resp->resources;
        platdev->num_resources = resp->nr_resources;
    /*设置平台数据 mini2440_uartcfgs 数组里的东西 */
        platdev->dev.platform_data = cfgptr;
      }
     
      nr_uarts = no;
    }
    

    至此,device 构建设置完毕,等待注册:
    1、三个串口的 device 存放在 s3c24xx_uart_devs 数组里,后边肯定会从数组里取出来注册。

    2、三个串口的 device 的名字都是 “s3c2440-uart”。

    3、三 个串口的 device 资源文件里存放好了 io 物理地址,Irq 等信息。

    4、三 个串口的 device 资源数据。
    移植过程中可能需要修改的文件:mini2440_uartcfgs 、s3c2410_uart0_resource 、s3c24xx_uart_src 还有那个晶振频率。

    linux-2.6.22.6linux-2.6.22.6archarmplat-s3c24xxcpu.c中s3c_arch_init 函数中,将 device 注册到 platform_bus_type

    static int __init s3c_arch_init(void)
    {
     int ret;
     ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
     return ret;
    }
    

    三、uart_driver 的注册

    注意,是 uart_driver 的注册,是上一篇文章讲的过程,并不是对应于平台设备的平台驱动。为什么在这个时候注册 uart_driver,因为如果先注册平台设备的 driver 的话,那么在probe函数里 uart_add_one_port ,uart_prot 没地方注册,因此,要先注册 uart_driver ,简单贴下代码,不在分析。

    在串口驱动linux-2.6.22.6linux-2.6.22.6driversserials3c2410.c中

    static struct uart_driver s3c24xx_uart_drv = {
      .owner    = THIS_MODULE,
      .dev_name  = "s3c2410_serial",
      .nr    = CONFIG_SERIAL_SAMSUNG_UARTS,
      .cons    = S3C24XX_SERIAL_CONSOLE,
      .driver_name  = S3C24XX_SERIAL_NAME,
      .major    = S3C24XX_SERIAL_MAJOR,
      .minor    = S3C24XX_SERIAL_MINOR,
    };
    
    static int __init s3c24xx_serial_modinit(void)
    {
     int ret;
     ret = uart_register_driver(&s3c24xx_uart_drv);
     if (ret < 0) {
        printk(KERN_ERR "failed to register UART driver
    ");
    return -1;
      }
     
    return 0;
    }
    

    四、platform driver 的注册以及 probe 函数

    static int s3c2440_serial_probe(struct platform_device *dev)
    {
    dbg("s3c2440_serial_probe: dev=%p
    ", dev);
    return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
    }
     
    
    static struct platform_driver s3c2440_serial_driver = {
      .probe    = s3c2440_serial_probe,
      .remove    = __devexit_p(s3c24xx_serial_remove),
      .driver    = {
        .name  = "s3c2440-uart",
        .owner  = THIS_MODULE,
      },
    };
    
    s3c24xx_console_init(&s3c2440_serial_driver, &s3c2440_uart_inf);
    static int __init s3c2440_serial_init(void)
    {
    return s3c24xx_serial_init(&s3c2440_serial_driver, &s3c2440_uart_inf);
    }
    

    将驱动注册到 platform_bus_type ,此时会遍历 platform_bus_type 的 deivce 链表,取出 device 进行名字比较,我们前边注册的三个device的名字是一样的,没关系 Linux 允许这样做,每次匹配到一个都调用一次 Probe 函数。

    static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
      [0] = {
        .port = {
          .lock    = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
          .iotype    = UPIO_MEM,
          .irq    = IRQ_S3CUART_RX0,
          .uartclk  = 0,
          .fifosize  = 16,
          .ops    = &s3c24xx_serial_ops,/* 底层的操作函数 */
          .flags    = UPF_BOOT_AUTOCONF,
          .line    = 0,
        }
      },
    /* 此处略去了两个串口的信息 */
    };
    
    int s3c24xx_serial_probe(struct platform_device *dev,
    struct s3c24xx_uart_info *info)
    {
      struct s3c24xx_uart_port *ourport;
      int ret;
    /*取出 uart_port  */
      ourport = &s3c24xx_serial_ports[probe_index];
      
      probe_index++;
    /* 对 uart_port 进一步设置*/
      ret = s3c24xx_serial_init_port(ourport, info, dev);
    /* 将 uart_port 注册到 uart_driver */
      uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
      
      platform_set_drvdata(dev, &ourport->port);
     
      ret = device_create_file(&dev->dev, &dev_attr_clock_source);
     
      ret = s3c24xx_serial_cpufreq_register(ourport);
     
    return 0;
     
    }
    
    static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
    struct s3c24xx_uart_info *info, struct platform_device *platdev)
    {
      struct uart_port *port = &ourport->port;
      struct s3c2410_uartcfg *cfg;
      struct resource *res;
      int ret;
     
      cfg = s3c24xx_dev_to_cfg(&platdev->dev);
     
    /*setup info for port */
      port->dev  = &platdev->dev;
      ourport->info  = info;
     
    /* copy the info in from provided structure */
      ourport->port.fifosize = info->fifosize;
     /* 设置时钟 */
      port->uartclk = 1;
     
    /* sort our the physical and virtual addresses for each UART */
     
      res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
    
      /* 设置物理地址,虚拟地址 */
      port->mapbase = res->start;
      port->membase = S3C_VA_UART + res->start - (S3C_PA_UART & 0xfff00000);
      ret = platform_get_irq(platdev, 0);
    if (ret < 0)
        port->irq = 0;
    else {
        port->irq = ret;/* 设置中断号 */
        ourport->rx_irq = ret;
        ourport->tx_irq = ret + 1;
      }
      
      ret = platform_get_irq(platdev, 1);
     ourport->clk  = clk_get(&platdev->dev, "uart");
     
    /* reset the fifos (and setup the uart) */
      s3c24xx_serial_resetport(port, cfg);
    return 0;
    }
    
    int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
    {
      struct uart_state *state;
      struct tty_port *port;
      int ret = 0;
      struct device *tty_dev;
      BUG_ON(in_interrupt());
     
    if (uport->line >= drv->nr)
    return -EINVAL;
     
      state = drv->state + uport->line;
      port = &state->port;
     
      mutex_lock(&port_mutex);
      mutex_lock(&port->mutex);
    if (state->uart_port) {
        ret = -EINVAL;
    goto out;
      }
     /* 将 uart_prot 绑定到 uart_driver 对应的 state */
      state->uart_port = uport;
      state->pm_state = -1;
     
      uport->cons = drv->cons;
      uport->state = state;
     
    /*
       * If this port is a console, then the spinlock is already
       * initialised.
       */
    if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
        spin_lock_init(&uport->lock);
        lockdep_set_class(&uport->lock, &port_lock_key);
      }
    /*实际调用 port->ops->config_port(port, flags) 稍后再看 */
      uart_configure_port(drv, state, uport);
     
    /*
       *上一篇文章中,我们提到tty注册了一个字符设备 “ttySAC ”
       *那么,我们平时看到的 “ttySAC0”“ttySAC1”等就是在这里注册的
       */
      tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
    if (likely(!IS_ERR(tty_dev))) {
        device_init_wakeup(tty_dev, 1);
        device_set_wakeup_enable(tty_dev, 0);
      } else
        print(KERN_ERR "Cannot register tty device on line %d
    ",
               uport->line);
     
    /*
       * Ensure UPF_DEAD is not set.
       */
      uport->flags &= ~UPF_DEAD;
     
     out:
      mutex_unlock(&port->mutex);
      mutex_unlock(&port_mutex);
     
    return ret;
    }
     
    
    
    struct device *tty_register_device(struct tty_driver *driver, unsigned index,
    struct device *device)
    {
    char name[64];
      dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
     
    if (index >= driver->num) {
        printk(KERN_ERR "Attempt to register invalid tty line number "
    " (%d).
    ", index);
    return ERR_PTR(-EINVAL);
      }
     
    if (driver->type == TTY_DRIVER_TYPE_PTY)
        pty_line_name(driver, index, name);
    else
        tty_line_name(driver, index, name);
     
    return device_create(tty_class, device, dev, NULL, name);
    }
    
    static void tty_line_name(struct tty_driver *driver, int index, char *p)
    {
      sprintf(p, "%s%d", driver->name, index + driver->name_base);
    }
    

    tty_driver->name == "ttySAC",在此基础上加上 uart_port.line ,就组成了具体串口的设备节点的名字,例如“ttySAC0”。

    分析到这里,完了么?没有,还有一个非常重要的东西没有分析呢,那就是底层的操作函数。

    static struct uart_ops s3c24xx_serial_ops = {
      .pm    = s3c24xx_serial_pm,
      .tx_empty  = s3c24xx_serial_tx_empty,
      .get_mctrl  = s3c24xx_serial_get_mctrl,
      .set_mctrl  = s3c24xx_serial_set_mctrl,
      .stop_tx  = s3c24xx_serial_stop_tx,
      .start_tx  = s3c24xx_serial_start_tx,
      .stop_rx  = s3c24xx_serial_stop_rx,
      .enable_ms  = s3c24xx_serial_enable_ms,
      .break_ctl  = s3c24xx_serial_break_ctl,
      .startup  = s3c24xx_serial_startup,
      .shutdown  = s3c24xx_serial_shutdown,
      .set_termios  = s3c24xx_serial_set_termios,
      .type    = s3c24xx_serial_type,
      .release_port  = s3c24xx_serial_release_port,
      .request_port  = s3c24xx_serial_request_port,
      .config_port  = s3c24xx_serial_config_port,
      .verify_port  = s3c24xx_serial_verify_port,
    };
    

    这么多的函数,如果让我们自己来实现,那真的头都要大了。一般芯片厂家会帮我们搞好。其他的不分析了,分析一个 startup 函数,因为我们在用户空间 open 的时候会调用它,那么必然有一些初始化的工作。

    static int s3c24xx_serial_startup(struct uart_port *port)
    {
      struct s3c24xx_uart_port *ourport = to_ourport(port);
      int ret;
     
      dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)
    ",
          port->mapbase, port->membase);
     
      rx_enabled(port) = 1;
     
      ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
            s3c24xx_serial_portname(port), ourport);
     
     if (ret != 0) {
        printk(KERN_ERR "cannot get irq %d
    ", ourport->rx_irq);
     return ret;
      }
     
      ourport->rx_claimed = 1;
     
      dbg("requesting tx irq...
    ");
     
      tx_enabled(port) = 1;
     
      ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
            s3c24xx_serial_portname(port), ourport);
     
     if (ret) {
        printk(KERN_ERR "cannot get irq %d
    ", ourport->tx_irq);
     goto err;
      }
     
      ourport->tx_claimed = 1;
     
      dbg("s3c24xx_serial_startup ok
    ");
     
    /* the port reset code should have done the correct
       * register setup for the port controls */
     if (port->line == 2) {
        s3c2410_gpio_cfgpin(S3C2410_GPH(6), S3C2410_GPH6_TXD2);
        s3c2410_gpio_pullup(S3C2410_GPH(6), 1);
        s3c2410_gpio_cfgpin(S3C2410_GPH(7), S3C2410_GPH7_RXD2);
        s3c2410_gpio_pullup(S3C2410_GPH(7), 1);
      }
     
     
     return ret;
     
     err:
      s3c24xx_serial_shutdown(port);
     return ret;
    }
    

    主要工作是注册了两个中断,发送中断,接收中断,来看看一个和我们上篇文章的猜测是否一致。

    static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id)
    {
      ..../* 调用线路规程的...和上篇文章一致 */
      tty_flip_buffer_push(tty);
     
    out:
    return IRQ_HANDLED;
    }
    static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
    
    {
      ....
     
    if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
        uart_write_wakeup(port);
     
    if (uart_circ_empty(xmit))
        s3c24xx_serial_stop_tx(port);
     
    out:
    return IRQ_HANDLED;
    }
    void uart_write_wakeup(struct uart_port *port)
    {
      struct uart_state *state = port->state;
      /*
       * This means you called this function _after_ the port was
       * closed.  No cookie for you.
       */
      BUG_ON(!state);
      tasklet_schedule(&state->tlet); /* 也一致 */
    }
    

    --

    -关注公众号百问科技(ID:baiwenkeji)第一时间学习嵌入式干货。

    技术交流加个人威信13266630429,验证: 博客园

  • 相关阅读:
    go http的三种实现---2
    go http的三种实现---1
    go语言递归创建目录
    Golang中的正则表达式
    go语言strings包
    go语言获取字符串元素的个数
    VBA在Excel中的应用(三)
    ASP 转换HTML特殊字符
    ASP汉字转拼音函数的方法
    用ASP实现文件下载
  • 原文地址:https://www.cnblogs.com/weidongshan/p/11052166.html
Copyright © 2020-2023  润新知