嵌入式Linux驱动笔记(七)------浅析tty与uart框架

2019-07-12 15:26发布

你好!这里是风筝的博客,

欢迎和我一起多多交流。


  看一个驱动模型,先从注册函数看起。 先看下设备文件: 在init.c(arch/arm/plat-samsung)文件,有: static struct cpu_table *cpu; 注意哦,这里有个结构体指针变量cpu,一定要记住,有大用!!! 将下来: static int __init s3c_arch_init(void)   {       int ret;          /* init is only needed for ATAGS based platforms */       if (!IS_ENABLED(CONFIG_ATAGS) ||           (!soc_is_s3c24xx() && !soc_is_s3c64xx()))           return 0;          // do the correct init for cpu          if (cpu == NULL) {           /* Not needed when booting with device tree. */           if (of_have_populated_dt())               return 0;           panic("s3c_arch_init: NULL cpu ");       }          ret = (cpu->init)();       if (ret != 0)           return ret;   #if IS_ENABLED(CONFIG_SAMSUNG_ATAGS)       ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);   #endif       return ret;   }   注意:platform_add_devices,没错,熟悉的函数,像platform注册设备!   然后点开函数里的参数:s3c24xx_uart_devs 惊呆了: struct platform_device *s3c24xx_uart_devs[4] = {   };   里面啥都没有,这是咋回事呢??? 为什么s3c24xx_uart_devs指针数组里面什么都没有呢?   别慌,先记住s3c24xx_uart_devs,也是有大用!!! 然后我们看下mach-smdk2440.c这个文件。 MACHINE_START(S3C2440, "SMDK2440")       /* Maintainer: Ben Dooks  */       .atag_offset    = 0x100,          .init_irq   = s3c2440_init_irq,       .map_io     = smdk2440_map_io,       .init_machine   = smdk2440_machine_init,       .init_time  = smdk2440_init_time,   MACHINE_END   其中,MACHINE_START是什么呢?   可以看下这篇文章:MACHINE_START与MACHINE_END     这里我们只需要注意smdk2440_map_io,其他的不是我们关心的重点! static void __init smdk2440_map_io(void)   {       s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));       s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));       samsung_set_timer_source(SAMSUNG_PWM3, SAMSUNG_PWM4);   }   这里可以注意下smdk2440_uartcfgs,这是一个结构体数组,存放的是s3c2440的三个串口的一些信息。     里面有两个重要函数:s3c24xx_init_io和s3c24xx_init_uarts。 我们先跟踪进入s3c24xx_init_io函数,记得等下还有s3c24xx_init_uarts函数要分析! void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)   {       arm_pm_idle = s3c24xx_default_idle;          /* initialise the io descriptors we need for initialisation */       iotable_init(mach_desc, size);       iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));          if (cpu_architecture() >= CPU_ARCH_ARMv5) {           samsung_cpu_id = s3c24xx_read_idcode_v5();       } else {           samsung_cpu_id = s3c24xx_read_idcode_v4();       }          s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));          samsung_pwm_set_platdata(&s3c24xx_pwm_variant);   }   这里注意s3c_init_cpu这个cpu的初始化函数,在进入这个函数前,需要留意下这个函数传入的两个参数:samsung_cpu_id和cpu_ids     samsung_cpu_id是由第十或者十二行得来,具体是哪个,我也不清楚...... 但是cpu_ids却是知道: static struct cpu_table cpu_ids[] __initdata = {       {           .idcode     = 0x32410000,           .idmask     = 0xffffffff,           .map_io     = s3c2410_map_io,           .init_uarts = s3c2410_init_uarts,           .init       = s3c2410_init,           .name       = name_s3c2410       },       {           .idcode     = 0x32410002,           .idmask     = 0xffffffff,           .map_io     = s3c2410_map_io,           .init_uarts = s3c2410_init_uarts,           .init       = s3c2410a_init,           .name       = name_s3c2410a       },       {           .idcode     = 0x32440000,           .idmask     = 0xffffffff,           .map_io     = s3c2440_map_io,           .init_uarts = s3c244x_init_uarts,           .init       = s3c2440_init,           .name       = name_s3c2440       },           //太多了,省略部分不相干的   }   好了,知道这个结构体了,我们进去s3c_init_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);          if (cpu == NULL) {           printk(KERN_ERR "Unknown CPU type 0x%08lx ", idcode);           panic("Unknown S3C24XX CPU");       }          printk("CPU %s (id 0x%08lx) ", cpu->name, idcode);          if (cpu->init == NULL) {           printk(KERN_ERR "CPU %s support not enabled ", cpu->name);           panic("Unsupported Samsung CPU");       }          if (cpu->map_io)           cpu->map_io();   }   看好了,黑暗势力正式登场!!还记得开头说的吗?cpu出现了!快抓住他!!!     看下cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);这一句: 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 & tab->idmask))               return tab;       }          return NULL;   }   这个函数就是真的看不懂了,不知道他们要匹配什么,因为不知道idcode(由samsung_cpu_id传进来的参数),但是可以知道的是,一直在tab(由cpu_ids传进来的参数)里寻找匹配项,然后返回给cpu这个结构体指针(s3c_init_cpu函数里第四行)。     不过可以肯定的是一定会匹配成功的,不然系统启动就会看到“Unknown CPU type”了。 接下来就会调用cpu->map_io()(即调用cpu_ids->map_io). ok,我们就返回去看下cpu_ids的map_io函数: .map_io = s3c2440_map_io, 进入s3c2440_map_io函数 : void __init s3c2440_map_io(void)   {       s3c244x_map_io();          s3c24xx_gpiocfg_default.set_pull = s3c24xx_gpio_setpull_1up;       s3c24xx_gpiocfg_default.get_pull = s3c24xx_gpio_getpull_1up;   }   继续进入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_nand_setname("s3c2440-nand");       s3c_device_ts.name = "s3c2440-ts";       s3c_device_usbgadget.name = "s3c2440-usbgadget";       s3c2410_device_dclk.name = "s3c2440-dclk";   }   这里是进行初始化和参数的设置,到这里调用关系基本就结束了,       接下来分析另一个函数s3c24xx_init_uarts: void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)   {       if (cpu == NULL)           return;          if (cpu->init_uarts == NULL && IS_ENABLED(CONFIG_SAMSUNG_ATAGS)) {           printk(KERN_ERR "s3c24xx_init_uarts: cpu has no uart init ");       } else           (cpu->init_uarts)(cfg, no);   }   哈哈,这里又一次黑暗势力登场:cpu!!   cpu被赋值过,所以不会等于null,第一个if不成立。 并且cpu->init_uarts不是等于null(因为cpu是从cpu_ids得来), 所以最后会进入(cpu->init_uarts)(cfg, no);这个函数 : .init_uarts = s3c244x_init_uarts, 即s3c244x_init_uarts函数: void __init s3c244x_init_uarts(struct s3c2410_uartcfg *cfg, int no)   {       s3c24xx_init_uartdevs("s3c2440-uart", s3c2410_uart_resources, cfg, no);   }   继续 进入: void __init s3c24xx_init_uartdevs(char *name,                     struct s3c24xx_uart_resources *res,                     struct s3c2410_uartcfg *cfg, int no)   {   #ifdef CONFIG_SERIAL_SAMSUNG_UARTS       struct platform_device *platdev;       struct s3c2410_uartcfg *cfgptr = uart_cfgs;       struct s3c24xx_uart_resources *resp;       int uart;          memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);          for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {           platdev = s3c24xx_uart_src[cfgptr->hwport];              resp = res + cfgptr->hwport;              s3c24xx_uart_devs[uart] = platdev;              platdev->name = name;           platdev->resource = resp->resources;           platdev->num_resources = resp->nr_resources;              platdev->dev.platform_data = cfgptr;       }          nr_uarts = no;   #endif   }   看到了吗?第二个黑暗势力登场:s3c24xx_uart_devs!     这里就是对s3c24xx_uart_devs数组的填充了(第十八行),因为s3c24xx_uart_devs是指针数组,现在指向了platdev(即s3c24xx_uart_src数组),还有一些参数的设置,比如设置名字都为:s3c2440-uart,还有设置一些"资源", 从而实现platform的device注册。   接下来看驱动文件。 在samsung.c文件: static const struct platform_device_id s3c24xx_serial_driver_ids[] = {       {           .name       = "s3c2410-uart",           .driver_data    = S3C2410_SERIAL_DRV_DATA,       }, {           .name       = "s3c2412-uart",           .driver_data    = S3C2412_SERIAL_DRV_DATA,       }, {           .name       = "s3c2440-uart",           .driver_data    = S3C2440_SERIAL_DRV_DATA,       }, {           .name       = "s3c6400-uart",           .driver_data    = S3C6400_SERIAL_DRV_DATA,       }, {           .name       = "s5pv210-uart",           .driver_data    = S5PV210_SERIAL_DRV_DATA,       }, {           .name       = "exynos4210-uart",           .driver_data    = EXYNOS4210_SERIAL_DRV_DATA,       }, {           .name       = "exynos5433-uart",           .driver_data    = EXYNOS5433_SERIAL_DRV_DATA,       },       { },   };   static struct platform_driver samsung_serial_driver = {       .probe      = s3c24xx_serial_probe,       .remove     = s3c24xx_serial_remove,       .id_table   = s3c24xx_serial_driver_ids,       .driver     = {           .name   = "samsung-uart",           .pm = SERIAL_SAMSUNG_PM_OPS,           .of_match_table = of_match_ptr(s3c24xx_uart_dt_match),       },   };   这就是驱动文件注册。   记得我们之前设备注册时的名字吗?就是s3c2440-uart! 所以驱动和设备匹配成功后,会调用s3c24xx_serial_probe函数: static int s3c24xx_serial_probe(struct platform_device *pdev)   {       struct device_node *np = pdev->dev.of_node;       struct s3c24xx_uart_port *ourport;       int index = probe_index;       int ret;   //内容太多,省略一部分       ret = s3c24xx_serial_init_port(ourport, pdev);       if (ret < 0)           return ret;          if (!s3c24xx_uart_drv.state) {           ret = uart_register_driver(&s3c24xx_uart_drv);           if (ret < 0) {               pr_err("Failed to register Samsung UART driver ");               return ret;           }       }          dbg("%s: adding port ", __func__);       uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);       platform_set_drvdata(pdev, &ourport->port);          /*       * Deactivate the clock enabled in s3c24xx_serial_init_port here,       * so that a potential re-enablement through the pm-callback overlaps       * and keeps the clock enabled in this case.       */       clk_disable_unprepare(ourport->clk);          ret = s3c24xx_serial_cpufreq_register(ourport);       if (ret < 0)           dev_err(&pdev->dev, "failed to add cpufreq notifier ");          probe_index++;          return 0;   }   其中,s3c24xx_serial_init_port是对端口做初始化,里面主要就是获取资源,不做描述了。   重点是:uart_register_driver注册函数: int uart_register_driver(struct uart_driver *drv)   {       struct tty_driver *normal;       int i, retval;          BUG_ON(drv->state);          /*       * Maybe we should be using a slab cache for this, especially if       * we have a large number of ports to handle.       */       drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);       if (!drv->state)           goto out;          normal = alloc_tty_driver(drv->nr);       if (!normal)           goto out_kfree;          drv->tty_driver = normal;          normal->driver_name  = drv->driver_name;       normal->name     = drv->dev_name;       normal->major        = drv->major;       normal->minor_start  = drv->minor;       normal->type     = TTY_DRIVER_TYPE_SERIAL;       normal->subtype      = SERIAL_TYPE_NORMAL;       normal->init_termios = tty_std_termios;       normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;       normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;       normal->flags        = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;       normal->driver_state    = drv;       tty_set_operations(normal, &uart_ops);          /*       * Initialise the UART state(s).       */       for (i = 0; i < drv->nr; i++) {           struct uart_state *state = drv->state + i;           struct tty_port *port = &state->port;              tty_port_init(port);           port->ops = &uart_port_ops;       }          retval = tty_register_driver(normal);       if (retval >= 0)           return retval;          for (i = 0; i < drv->nr; i++)           tty_port_destroy(&drv->state[i].port);       put_tty_driver(normal);   out_kfree:       kfree(drv->state);   out:       return -ENOMEM;   }   刚开始,对normal分配一个tty的驱动,接着设置参数,其中,名字为:ttySAC   注意一个,第三十三行,给normal设置了uart_ops这个tty_operations类型的结构体,当然,里面有open、write、ioctl等函数。 但是注意哦,用户空间并不是直接访问这 继续看第四十六行:tty_register_driver函数: int tty_register_driver(struct tty_driver *driver)   {       int error;       int i;       dev_t dev;       struct device *d;          if (!driver->major) {           error = alloc_chrdev_region(&dev, driver->minor_start,                           driver->num, driver->name);           if (!error) {               driver->major = MAJOR(dev);               driver->minor_start = MINOR(dev);           }       } else {           dev = MKDEV(driver->major, driver->minor_start);           error = register_chrdev_region(dev, driver->num, driver->name);       }       if (error < 0)           goto err;          if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {           error = tty_cdev_add(driver, dev, 0, driver->num);           if (error)               goto err_unreg_char;       }          mutex_lock(&tty_mutex);       list_add(&driver->tty_drivers, &tty_drivers);       mutex_unlock(&tty_mutex);          if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {           for (i = 0; i < driver->num; i++) {               d = tty_register_device(driver, i, NULL);               if (IS_ERR(d)) {                   error = PTR_ERR(d);                   goto err_unreg_devs;               }           }       }       proc_tty_register_driver(driver);       driver->flags |= TTY_DRIVER_INSTALLED;       return 0;   }   这里,就是我们熟悉的,对字符设备进行注册了。同时还会调用tty_cdev_add(第三十四行)添加tty字符设备: static int tty_cdev_add(struct tty_driver *driver, dev_t dev,           unsigned int index, unsigned int count)   {       int err;          /* init here, since reused cdevs cause crashes */       driver->cdevs[index] = cdev_alloc();       if (!driver->cdevs[index])           return -ENOMEM;       driver->cdevs[index]->ops = &tty_fops;       driver->cdevs[index]->owner = driver->owner;       err = cdev_add(driver->cdevs[index], dev, count);       if (err)           kobject_put(&driver->cdevs[index]->kobj);       return err;   }   注意这里的tty_fops(第十行)这个file_operations结构体哦: static const struct file_operations tty_fops = {       .llseek     = no_llseek,       .read       = tty_read,       .write      = tty_write,       .poll       = tty_poll,       .unlocked_ioctl = tty_ioctl,       .compat_ioctl   = tty_compat_ioctl,       .open       = tty_open,       .release    = tty_release,       .fasync     = tty_fasync,   };   用户空间真正调用的open、read、write等函数就在这!!!   当然了,有tty_cdev_add这个tty设备添加函数,当然也要就tty的驱动函数了,就是之前在tty_register_driver函数里的tty_register_device这个注册函数,里面就是对字符设备的注册,就不过多描述了。 最后,继续回到我们的s3c24xx_serial_probe函数,里面还有重要的函数: uart_add_one_port和s3c24xx_serial_cpufreq_register 一个是添加uart的端口(ttySAC0、ttySAC1等等之类的), 另一个就是中断注册了。     说了那么多,感觉头都大了,错综复杂的........ 不过可以肯定的一点是: 用户空间->tty->uart->硬件   基本就是这样吧,学好不容易啊......