前言

  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月

linux驱动基础系列--linux rtc子系统的更多相关文章

  1. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

  2. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  3. linux驱动基础系列--Linux mmc sd sdio驱动分析

    前言 主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.块设备驱动.设备模型等也不进行详细说明原 ...

  4. linux驱动基础系列--Linux下Spi接口Wifi驱动分析

    前言 本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 .主要是想对spi接口的wifi驱动框架有一个整体的把 ...

  5. linux驱动基础系列--Linux 串口、usb转串口驱动分析

    前言 主要是想对Linux 串口.usb转串口驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如字符设备驱动.平台驱动等也不进行详细说明原理.如果有任何错误地方,请指出, ...

  6. linux驱动基础系列--Linux I2c驱动分析

    前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...

  7. Linux驱动修炼之道-RTC子系统框架与源码分析【转】

    转自:http://helloyesyes.iteye.com/blog/1072433 努力成为linux kernel hacker的人李万鹏原创作品,为梦而战.转载请标明出处 http://bl ...

  8. linux驱动由浅入深系列:高通sensor架构实例分析之二(驱动代码结构)【转】

    本文转载自:https://blog.csdn.net/radianceblau/article/details/73498303 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ...

  9. linux驱动由浅入深系列:高通sensor架构实例分析之三(adsp上报数据详解、校准流程详解)【转】

    本文转载自:https://blog.csdn.net/radianceblau/article/details/76180915 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ...

随机推荐

  1. Python 3基础教程22-单个列表操作

    本文来介绍列表的操作,先看看单个列表的操作,列表有多个方法.以下多行代码,建议你写一个方法,测试运行一个方法,不然看起来很乱. # 元组操作 x = [5,6,2,1,6,7,2,7,9] # app ...

  2. jmeter4.0☞如何汉化(二)

    如何汉化jmeter打开jmeter,选择options_choose language_Chinese(simplified),如下图: 刚刚下载使用jmeter4.0的时候有点懵圈,英语实在是差劲 ...

  3. 解决Unbuntu终端菱形乱码问题

    原因:安装时为了学习方便选择中文安装,其字符编码相关配置如下(在/etc/default/locale中) LANG="Zh_CN.UTF-8 "LANGUAGE="zh ...

  4. QC的使用学习(一)

    今天学习的时间很少,就利用睡前的一点时间来学习一下刚安装好的QC. 1.后台站点管理.主要是对八大选项的了解: site project:顾名思义,就站点项目管理,管理域和项目. site user: ...

  5. 图的同构 (Graph Isomorphism)

    整理摘自:https://www.jianshu.com/p/c33b5d1b4cd9 同构是在数学对象之间定义的一类映射,它能揭示出在这些对象的属性或者操作之间存在的关系.若这两个数学结构之间存在同 ...

  6. shell语句for循环

    一:常用格式 格式一 for 变量 do 语句 done 格式二 for 变量 in 列表 do 语句 done 格式三 for ((变量=初始值; 条件判断; 变量变化)) do 语句 done 二 ...

  7. 了解游戏编程与 AI

    噫语系列... 闲话 最近在重写我的一个 QQ 群机器人项目,并尝试将它改成更通用的结构,以方便在未来加入对 Wechat 和 Telegram 的支持. 在查资料的过程中,发现很多人认为一个群内多人 ...

  8. MySQL日常管理

    DB2最佳分页语句 SELECT * FROM ( SELECT inner2_.*, ROWNUMBER() OVER(ORDER BY ORDER OF inner2_) AS rownumber ...

  9. P3375【模板】KMP字符串匹配

    前言: 额……很久以前就写了KMP模板(只是半知不解),话说看完了manacher,再回过头看KMP,是真TM简单啊!字符串专题整体较抽象,所以必须牢记思路并时常复习 题目描述 如题,给出两个字符串s ...

  10. [洛谷P1131][ZJOI2007]时态同步

    题目大意:给你一棵树,每条边有边权,要求增加一些边的边权,使得根节点到每个叶子节点的距离相等,求出最少共增加多少边权. 题解:树形$DP$,对于每个点,如果它到它的子树中的叶子节点距离不同,一定要在这 ...