你好!这里是风筝的博客,
欢迎和我一起多多交流。
看一个驱动模型,先从注册函数看起。
先看下设备文件:
在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->硬件
基本就是这样吧,学好不容易啊......