linux驱动基础系列--linux rtc子系统

时间:2021-06-03 20:24:54

前言

  linux驱动子系统太多了,连时钟也搞了个子系统,这导致一般的时钟芯片的驱动也会涉及到至少2个子系统,一个是时钟芯片接口子系统(比如I2c接口的时钟芯片),一个是内核给所有时钟芯片提供的rtc子系统。当然也可以自己写一个字符设备直接操作芯片然后给上层用户空间提供接口,但这种方法实在是太糟糕了,内核既然提供了,为什么不用呢!(真要这样做,请参考内核的drivers/char/rtc.c的实现)

另外: 强烈建议看内核文档Documentation/rtc.txt 里面对新旧两套实现及api有详细的描述。

  下面还是基于linux内核2.6.35版本及驱动文件rtc-ds1307.c为例。中间涉及到时钟芯片寄存器操作的跳过(时钟芯片寄存器算少的而且 也简单),只是重点分析一下linux下的时钟芯片驱动框架,I2c驱动框架部分也直接跳过-见另一篇I2c子系统的文章

rtc-ds1307.c支持如下所示的芯片:

        static const struct i2c_device_id ds1307_id[] = {
{ "ds1307", ds_1307 },
{ "ds1337", ds_1337 },
{ "ds1338", ds_1338 },
{ "ds1339", ds_1339 },
{ "ds1388", ds_1388 },
{ "ds1340", ds_1340 },
{ "ds3231", ds_3231 },
{ "m41t00", m41t00 },
{ "rx8025", rx_8025 },
{ }
};

使用rtc子系统

  dsxxx这类芯片是I2C接口的,所以最开始是通过I2C驱动注册到内核的I2C子系统,即调用i2c_add_driver实现。

        static int __init ds1307_init(void)
{
return i2c_add_driver(&ds1307_driver);
}

下面重点看ds1307_driver里面实现的ds1307_probe,至于I2C子系统的匹配过程什么的这些看我的另一篇I2C子系统的文章。

ds1307_probe主要实现如下功能:

  1. 检测I2C控制器是否满足我们需要的功能
        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)
&& !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
return -EIO;
  1. 分配一个驱动的上下文对象ds1307,及初始化它和i2c设备对象
        if (!(ds1307 = kzalloc(sizeof(struct ds1307), GFP_KERNEL)))
return -ENOMEM; i2c_set_clientdata(client, ds1307);
ds1307->client = client;
ds1307->type = id->driver_data;
ds1307->offset = 0;
buf = ds1307->regs;
if (i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) {
ds1307->read_block_data = i2c_smbus_read_i2c_block_data;
ds1307->write_block_data = i2c_smbus_write_i2c_block_data;
} else {
ds1307->read_block_data = ds1307_read_block_data;
ds1307->write_block_data = ds1307_write_block_data;
}
  1. 根据不同的时钟芯片类型做相应的初始化,这里就不详叙了,具体肯定需要参考时钟芯片手册然后借助I2C控制器来完成寄存器的读写等
  2. 最重要的操作,注册到rtc子系统
        ds1307->rtc = rtc_device_register(client->name, &client->dev,
&ds13xx_rtc_ops, THIS_MODULE);

rtc_device_registerrtc子系统提供的设备注册函 数,其实rtc主要的作用就是实现了向用户空间导出时钟芯片操作的接口,下面详细分析下rtc子系统是怎么做到这些的(如果是我们自己实现的话,肯定是以 字符设备的驱动实现,rtc子系统也确实是这样实现的,只不过它额外实现了sysfsproc的操作接口,比较全面,这些可以通过编译内核的时候配 置)。

rtc子系统分析

linux rtc子系统的实现在drivers/rtc目录下(刚才分析的ds1307也在这目录下),通过该目录下的makefile中的如下内容:

        obj-$(CONFIG_RTC_LIB) += rtc-lib.o
obj-$(CONFIG_RTC_HCTOSYS) += hctosys.o
obj-$(CONFIG_RTC_CLASS) += rtc-core.o
rtc-core-y := class.o interface.o
rtc-core-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o
rtc-core-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o
rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o

kconfig文件,可以知道要内核支持rtc子系统,那么选项"Real Time Clock"是必须要选上的,它选上了,也就意味着:

        obj-$(CONFIG_RTC_LIB) += rtc-lib.o
obj-$(CONFIG_RTC_CLASS) += rtc-core.o
rtc-core-y := class.o interface.o

都选上了,如果想增加/dev下的设备文件接口,那么选上CONFIG_RTC_INTF_DEV,如果想增加proc下的接口支持,选上CONFIG_RTC_INTF_PROC,如果想增加sysfs下的操作接口支持,选上CONFIG_RTC_INTF_SYSFS

下面分析rtc子系统的核心实现class.c

static int __init rtc_init(void)
{
rtc_class = class_create(THIS_MODULE, "rtc");
if (IS_ERR(rtc_class)) {
printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
return PTR_ERR(rtc_class);
}
rtc_class->suspend = rtc_suspend;
rtc_class->resume = rtc_resume;
rtc_dev_init();
rtc_sysfs_init(rtc_class);
return 0;
} static void __exit rtc_exit(void)
{
rtc_dev_exit();
class_destroy(rtc_class);
idr_destroy(&rtc_idr);
} subsys_initcall(rtc_init);
module_exit(rtc_exit);

rtc_init初始化很简单:

  1. 通过class_create在/sys/class创建rtc目录来表示rtc时钟这一类设备的集合;
  2. rtc_dev_init在配置了CONFIG_RTC_INTF_DEV的情况下,会通过err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");来申请一个动态的字符设备号,为将来的/dev/下设备节点导出做准备;
  3. rtc_sysfs_init在配置了CONFIG_RTC_INTF_SYSFS的情况下,会对class初始化,rtc_class->dev_attrs = rtc_attrs;这其实也是为了将来设备导入到rtc子系统时在sysfs下创建相关文件做准备;

rtc_exit的反初始化就不多说了。

class.c里面还提供了5个接口,其中rtc_device_release是释放设备的时候用的,rtc_suspendrtc_resume是 电源管理部分的,这个不多说,下面主要分析rtc_device_registerrtc_device_unregister,它是我们编写时钟芯片 驱动时会用到的一部分,如我们上面分析ds1307的时候就用到了它。

        ds1307->rtc = rtc_device_register(client->name, &client->dev,
&ds13xx_rtc_ops, THIS_MODULE); struct rtc_device *rtc_device_register(const char *name
, struct device *dev
, const struct rtc_class_ops *ops
, struct module *owner)

第一个参数name,以我们上面的例子就是client->name,实际就是时钟芯片的名字,这个是属于I2c子系统设备端部分,该部分可以通过设备树(推荐方式)也可以通过i2c_register_board_info实现(不推荐)

第二个参数为时钟芯片对应的device指针,以我们上面的例子就是&client->dev,它是在I2C控制器驱动注册的时候扫描静态设备链表或者内核根据设备树动态创建的

第三个参数为操作集,rtc核心层就是通过该函数集来回调具体芯片的操作,这和fops类似

第四个参数为模块拥有者指针,内核会根据该对象指针对模块引用跟踪

rtc_device_register主要做了如下工作:

  1. 通过idr机制获取一个唯一号
        if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) {
err = -ENOMEM;
goto exit;
} mutex_lock(&idr_lock);
err = idr_get_new(&rtc_idr, NULL, &id);
mutex_unlock(&idr_lock);
  1. 分配rtc核心层设备对象rtc,并进行初始化
        rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
if (rtc == NULL) {
err = -ENOMEM;
goto exit_idr;
} rtc->id = id;
rtc->ops = ops;
rtc->owner = owner;
rtc->max_user_freq = 64;
rtc->dev.parent = dev;
rtc->dev.class = rtc_class;
rtc->dev.release = rtc_device_release; mutex_init(&rtc->ops_lock);
spin_lock_init(&rtc->irq_lock);
spin_lock_init(&rtc->irq_task_lock);
init_waitqueue_head(&rtc->irq_queue); strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
dev_set_name(&rtc->dev, "rtc%d", id);
  1. 注册到设备模型中,这个其实会在init时候的那个rtc_class类目录下及/sys/devices的某个子目录下创建目录及相关属性文件
        err = device_register(&rtc->dev);
  1. 如果定义了CONFIG_RTC_INTF_DEV, 生成/dev下的设备节点为应用层提供操作
        rtc_dev_prepare(rtc);
rtc_dev_add_device(rtc);
  1. 如果定义了CONFIG_RTC_INTF_SYSFS, 生成/sysfs下的文件为应用层提供操作
        rtc_sysfs_add_device(rtc);
  1. 如果定义了CONFIG_RTC_INTF_PROC, 生成/proc下的文件为应用层提供操作
        rtc_proc_add_device(rtc);

上面几个函数的实现不再继续跟踪分析了,基本就是字符设备操作和proc及sysfs操作的使用

情景分析

操作/dev下设备节点分析

  1. open设备节点时,会调用rtc_dev_open,这个是因为rtc_init里面调用rtc_dev_init(),它动态申请了一个设备号到rtc_devt中,然后在前面rtc_device_registerrtc_dev_prepare中,通过:
        rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
cdev_init(&rtc->char_dev, &rtc_dev_fops);
rtc->char_dev.owner = rtc->owner;

rtc_dev_add_device(rtc);里面的

        if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))
printk(KERN_WARNING "%s: failed to add char device %d:%d\n",
rtc->name, MAJOR(rtc_devt), rtc->id);
else
pr_debug("%s: dev (%d:%d)\n", rtc->name,
MAJOR(rtc_devt), rtc->id);
实现了字符设备的注册,并通过后面的`err = device_register(&rtc->dev);`实现了设备节点的创建,而注册的`fops`为`rtc_dev_fops`,它是`rtc`子系统实现的:
    static const struct file_operations rtc_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = rtc_dev_read,
.poll = rtc_dev_poll,
.unlocked_ioctl = rtc_dev_ioctl,
.open = rtc_dev_open,
.release = rtc_dev_release,
.fasync = rtc_dev_fasync,
};

因此,/dev下设备节点的open会导致rtc_dev_open调用(这中间经过系统调用到vfs的过程就省略分析了,呵呵),rtc_dev_open里面通过如下语句最终调用到我们注册的open函数:

        err = ops->open ? ops->open(rtc->dev.parent) : 0;

如果看我们上面分析ds1307,那就是调用到了ds13xx_rtc_ops.open, ds1307没有实现open

2. read write等操作就和open类似了,不再重复

操作/proc下文件分析

  和proc相关的在rtc_device_register里面rtc_proc_add_device(rtc);


void rtc_proc_add_device(struct rtc_device *rtc)
{
if (rtc->id == 0)
proc_create_data("driver/rtc", 0, NULL, &rtc_proc_fops, rtc);
}

通过它的实现可以发现rtc子系统里的proc部分只有第一个rtc设备绑定,且设定的操作集为rtc_proc_fops, 应用层怎么调用到该接口就和proc部分有关了,这里省略

操作sysfs里面rtc相关部分的属性文件

rtc_initrtc_sysfs_init里面有通过rtc_class->dev_attrs = rtc_attrs;来初始化classdev_attrs ,这会导致err = device_register(&rtc->dev);注册的时候在相关目录(/sys/class/rtc/rtc0/ 注意class下的rtc/rtc0只是符号链接,因为具体和设备目录和设备接口有关,所以这里以/sys/class/rtc/rtc0/为例)下创建一些属性文件,现在看rtc_attrs

    static struct device_attribute rtc_attrs[] = {
__ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),
__ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),
__ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),
__ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),
__ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,
rtc_sysfs_set_max_user_freq),
__ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),
{ },
};

但我们操作这些属性文件的时候,rtc_attrs里面对应的函数就会被调用到。

---------------------完!

2014年4月