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

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

欢迎和我一起多多交流。

 

看一个驱动模型,先从注册函数看起。

先看下设备文件:

在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\n");  
    }  
  
    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\n", idcode);  
        panic("Unknown S3C24XX CPU");  
    }  
  
    printk("CPU %s (id 0x%08lx)\n", cpu->name, idcode);  
  
    if (cpu->init == NULL) {  
        printk(KERN_ERR "CPU %s support not enabled\n", 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\n");  
    } 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\n");  
            return ret;  
        }  
    }  
  
    dbg("%s: adding port\n", __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\n");  
  
    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->硬件

 

基本就是这样吧,学好不容易啊......

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Linux驱动)