总说:
RTC设备驱动是一个标准的字符设备驱动,应用程序通过open、release、read、write和ioctl等函数完成对RTC设备的操作。
Linux内核将RTC设备抽象为rtc_device结构体,因此RTC设备驱动就是申请并初始化rtc_device,最后将rtc_device注册到Linux内核里面,这样Linux内核就有一个RTC设备的。
至于RTC设备的操作肯定是用一个操作集合(结构体)来表示的,rtc_device结构体里有一个rtc_class_ops结构体,(编写RTC内核驱动的主要步骤就是填写rtc_class_ops),rtc_class_ops为RTC设备的最底层操作函数集合,包括从RTC设备中读取时间、向RTC设备写入新的时间值等。因此,rtc_class_ops是需要用户根据所使用的RTC设备编写的。
(这个结构体中使用的struct device参数就是rtc_device_register()使用的那个dev,它代表总线上的物理设备。这个struct device的driver_data数据一般保存struct device的状态,包括指向rtc_device的指针。驱动开发者至少应该提供read_time/set_time这两个接口,其他函数都是可选的。)
用户操作rtc驱动结构图:
第一部分:框架
从上面的图中我们可以看出RTC框架大致可以分为三部分:rtc-s3c.c ,class.c 以及 rtc-dev.c 。他们依次向上抽象注册。同时又对下面底层的函数进行回调,从而构成了整个完整的RTC框架。我们现在先分开介绍。
rtc-s3c.c 层:
该层是RTC框架的底层,与硬件打交道。用时又由于这是RTC框架的底层,所以各个芯片对应的这一层可能会不一样,所以该层是与具体芯片相关的。同时又由于内核面向对象的思想,所以在该层一定要有一个结构体表示RTC设备——rtc_device(见下面)。
class.c层:
该层作为rtc-s3c.c 和 rtc-dev.c 的中间层,对这两层有一个连接作用。同时由于RTC设备最终要注册进内核,所以该层也有对RTC实行注册中转的作用。总的来说该层的作用就是连接中转。同时由于该层已经与具体硬件没有关系了,所以该层的代码不用修改。
rtc-dev.c 层:
该层作为RTC对上层的抽象层。在该层中将RTC设备抽象为字符设备,并用字符设备的框架对其进行注册。同时由于该层是纯软件的概念了,所以不能对相关的硬件进行操作。而要操作相关硬件时要通过回调函数找到对相关硬件的操作函数,并执行该函数。这用我们就又实现了从抽象到具体的过程。
通过上面的分析,你也许就明白了,其实上面这三层是相互关联,相互调用的关系。从rtc-s3c.c ,class.c 到 rtc-dev.c经过层层抽象,将RTC设备抽象到字符设备实现对RTC设备向内核的注册,而又通过rtc-dev.c对rtc-s3c.c 的回调实现从抽象到具体的操作。上面就是这三层的关系。而我们现在分析各个层中的函数来详细了解他们内部的关系。
第二部分:结合代码分析RTC框架
我们先分析rtc-s3c.c 进而分析class.c 最后rtc-dev.c,从中我们了解各个层中函数调用的过程(在代码分析这部分我会删除部分不重要的判断或是其他的语句,来使代码看起来更加的清晰明了)。
rtc-s3c.c :
我们从入口函数开始分析:
static int __init s3c_rtc_init(void)
{
printk(banner);
return platform_driver_register(&s3c2410_rtcdrv);
}
从入口函数中我们可以看出他最主要的是注册了一个平台驱动结构体:s3c2410_rtcdrv。那我们就要看看这个平台驱动结构体做了什么:
static struct platform_driver s3c2410_rtcdrv = {
.probe = s3c_rtc_probe,
.remove = s3c_rtc_remove,
.suspend = s3c_rtc_suspend,
.resume = s3c_rtc_resume,
.driver = {
.name = "s3c2410-rtc",
.owner = THIS_MODULE,
},
};
从上面我们可以看出他是一个名为s3c2410-rtc的平台驱动,而在平台——设备——驱动模型中我们知道,既然有这个驱动那就一定有一个与其同名的平台设备,通过搜索我们在arch\arm\plat-s3c24xx\devs.c文件中找到了与其同名的设备:
static struct resource s3c_rtc_resource[] = {
[0] = {
.start = S3C24XX_PA_RTC,//寄存器地址
.end = S3C24XX_PA_RTC + 0xff,//同上
.flags = IORESOURCE_MEM, //中断
},
[1] = {
.start = IRQ_RTC,
.end = IRQ_RTC,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_TICK,
.end = IRQ_TICK,
.flags = IORESOURCE_IRQ
}
};
struct platform_device s3c_device_rtc = {
.name = "s3c2410-rtc",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_rtc_resource),
.resource = s3c_rtc_resource,
};
而通过上面关于资源的介绍我们知道了RTC设备的寄存器地址,以及相关的中断号。而这些将在我们后面对RTC做硬件初始化和设置的时候用到。
此时我们认为平台设备与平台驱动匹配,所以我们进入probe函数。
static int s3c_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc; /* 分配一个rtc_device设备,该结构体对应RTC设备 */
struct resource *res;
int ret;
/* !!!!!!!!找到中断 */
s3c_rtc_tickno = platform_get_irq(pdev, 1); /* 获得资源中的IRQ_TICK中断 */
s3c_rtc_alarmno = platform_get_irq(pdev, 0); /* 获得资源中的IRQ_RTC中断 */
/* !!!!!!!!获得寄存器资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /* 获得资源地址 */
s3c_rtc_mem = request_mem_region(res->start, /* 为获得的寄存器申请空间 */
res->end-res->start+1,
pdev->name);
s3c_rtc_base = ioremap(res->start, res->end - res->start + 1); /* 对资源接口进行重映射 */
/*!!!!!! 检测是否设置正确 */
s3c_rtc_enable(pdev, 1); /* rtc使能 */
s3c_rtc_setfreq(s3c_rtc_freq); /* 设置RTC的滴答时钟频率 */
/*!!!!!! 注册RTC */
rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops, /* 将rtc_class_ops结构体向上注册 */
THIS_MODULE);
rtc->max_user_freq = 128;
platform_set_drvdata(pdev, rtc); /* 将设置到的RTC设备放入平台设备中,方便后面的调用 */
return 0;
}
从上面的程序中我们知道在probe中主要做了以下五件事情:
1. 找到中断
2. 获得寄存器资源
3. rtc使能
4. 设置RTC的滴答时钟频率
5. 注册RTC
第一和第二在代码上面已经讲得很清楚了没什么好讲的了,就介绍第3件事:rtc使能
static void s3c_rtc_enable(struct platform_device *pdev, int en)
{
void __iomem *base = s3c_rtc_base; /* 获得寄存器基地址 */
unsigned int tmp;
/* re-enable the device, and check it is ok */
if ((readb(base+S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){
dev_info(&pdev->dev, "rtc disabled, re-enabling\n");
tmp = readb(base + S3C2410_RTCCON);
writeb(tmp|S3C2410_RTCCON_RTCEN, base+S3C2410_RTCCON); /* 使能RTC控制 */
}
if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
dev_info(&pdev->dev, "removing RTCCON_CNTSEL\n");
tmp = readb(base + S3C2410_RTCCON);
writeb(tmp& ~S3C2410_RTCCON_CNTSEL, base+S3C2410_RTCCON); /* 使能合并BCD计数器 */
}
if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
dev_info(&pdev->dev, "removing RTCCON_CLKRST\n");
tmp = readb(base + S3C2410_RTCCON);
writeb(tmp & ~S3C2410_RTCCON_CLKRST, base+S3C2410_RTCCON); /* RTC不重启 */
}
}
这里主要是通过操作硬件相关寄存器,进而控制RTC,实现:
1. 使能RTC控制
2. 使能合并BCD计数器
3. RTC不重启
那么我们接着分析第四件事:设置RTC的滴答时钟频率
static void s3c_rtc_setfreq(int freq)//设置RTC的滴答时钟频率
{
unsigned int tmp;
spin_lock_irq(&s3c_rtc_pie_lock);
tmp = readb(s3c_rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
s3c_rtc_freq = freq;
tmp |= (128 / freq)-1;
writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
spin_unlock_irq(&s3c_rtc_pie_lock);
}
设置滴答时钟的中断周期,通过芯片手册:
周期= ( n+1 ) / 128 秒
n: 滴答时钟计数值(1~127)
而通过代码上面的设置就是设置的中断周期。
而下面我们就要讲这一层中最重要的函数,注册RTC函数: rtc_device_register。而该函数的详细定义在class.c层中。
rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops, THIS_MODULE);
他定义了一个与RTC硬件操作相关的结构体:s3c_rtcops。
static const struct rtc_class_ops s3c_rtcops = {
//硬层的操作函数(会在字符设备操作函数的时候回调)
.open = s3c_rtc_open,
.release = s3c_rtc_release,
.ioctl = s3c_rtc_ioctl,
.read_time = s3c_rtc_gettime,
.set_time = s3c_rtc_settime,
.read_alarm = s3c_rtc_getalarm,
.set_alarm = s3c_rtc_setalarm,
.proc = s3c_rtc_proc,
};
这个结构体是这层的重点,因为他从硬件上说明了如何操作RTC设备。而对RTC的基本操作也可以从上面找到。我们会在后面讲rtc-dev.c层时回调来讲解这些函数。
class.c:
下面我们就进入class.c这个中间层中做了什么。我们从上面讲解的rtc_device_register函数入手,看RTC设备在这层向上注册的时候做了什么。
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
{
struct rtc_device *rtc;
int id, err;
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);
/* 设置填充rtc_device结构体 */
id = id & MAX_ID_MASK;
rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
rtc->id = id; /* rtc的ID */
rtc->ops = ops; /* rtc的操作函数,即s3c_rtcops */
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);
strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
snprintf(rtc->dev.bus_id, BUS_ID_SIZE, "rtc%d", id);
/* 设备准备,为注册字符设备做准备 */
rtc_dev_prepare(rtc);
/* 将RTC设备放入设备层 */
err = device_register(&rtc->dev);
/* 添加字符设备到系统 */
rtc_dev_add_device(rtc);
rtc_sysfs_add_device(rtc);
rtc_proc_add_device(rtc);
return rtc;
}
从上面看,他主要做了:
1. 设置填充rtc_device结构体
2. 设备准备,为注册字符设备做准备
3. 将RTC设备放入设备层
4. 添加字符设备
由于2和4的具体实现是在rtc-dev.c中,所以我们会在下层中分析。而1在上面的程序中已经讲得很清楚了,而唯一要我们注意的就是rtc_device这个结构体,我在下面说明,而3中就是将RTC设备放入设备层,没有什么。
struct rtc_device
{
struct device dev;
struct module *owner;
int id; //代表是那个rtc设备
char name[RTC_DEVICE_NAME_SIZE]; //代表rtc设备的名称
const struct rtc_class_ops *ops; //rtc操作函数集,需要驱动实现
struct mutex ops_lock; //操作函数集的互斥锁
struct cdev char_dev; //代表rtc字符设备,因为rtc就是个字符设备
unsigned long flags; //rtc的状态标志,例如RTC_DEV_BUSY
unsigned long irq_data; //rtc中断数据
spinlock_t irq_lock; //访问数据是要互斥,需要spin_lock
wait_queue_head_t irq_queue; //数据查询中用到rtc队列
struct fasync_struct *async_queue; //异步队列
struct rtc_task *irq_task; //在中断中使用task传输数据
spinlock_t irq_task_lock; //task传输互斥
int irq_freq; //rtc的中断频率
int max_user_freq; //rtc的最大中断频率
struct timerqueue_head timerqueue; //定时器队列
struct rtc_timer aie_timer; //aie(alaram interrupt enable)定时器
struct rtc_timer uie_rtctimer; //uie(update interrupt enable)定时器
struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */ //pie(periodic interrupt enable)定时器
int pie_enabled; //pie使能标志
struct work_struct irqwork;
/* Some hardware can't support UIE mode */
int uie_unsupported; //uie使能标志
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL //RTC UIE emulation on dev interface配置项,目前没有开启
struct work_struct uie_task;
struct timer_list uie_timer;
/* Those fields are protected by rtc->irq_lock */
unsigned int oldsecs;
unsigned int uie_irq_active:1;
unsigned int stop_uie_polling:1;
unsigned int uie_task_active:1;
unsigned int uie_timer_active:1;
#endif
};
rtc-dev.c:
这一层要做的就是将我们的RTC设备抽象为一个字符设备,其实这也体现了内核的编程思想,即找到共性的,通用的地方将他们抽象出来作为一个抽象的与具体设备无关的层这就是我们的rtc-dev.c层,而将不能移植的部分局域化,集中在某几个特征文件中。从而在软件上实现层次化和模块化。那么我们就在这个层中找一下他抽象成什么样的字符设备,同时了解一下他的回调函数。在分析代码前我们先回忆一下我们的字符设备是怎么注册的。
下面我们开始分析代码从中看他是如何实现字符驱动的注册的。
我们先从他的入口函数开始分析:
#define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */
void __init rtc_dev_init(void)
{
int err;
err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
}
我们可以看出他一开始就为字符设备注册了一份区域,同时我们也知道这个字符设备的次设备号的基地址为0,最多可以注册RTC_DEV_MAX个这样的设备。而上面的RTC_DEV_MAX的宏定义为:
而分析完入口函数,我们接着上面class.c中要分析的两个函数:rtc_dev_prepare(rtc)和rtc_dev_add_device(rtc)(class.c中)
我们先分析rtc_dev_prepare(rtc):
void rtc_dev_prepare(struct rtc_device *rtc)
{
rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); /* 获得设备号 */
mutex_init(&rtc->char_lock);
spin_lock_init(&rtc->irq_lock);
init_waitqueue_head(&rtc->irq_queue);
cdev_init(&rtc->char_dev, &rtc_dev_fops); /* 初始化cdev结构体 */
rtc->char_dev.owner = rtc->owner;
}
从上面看其最主要的就是初始化cdev结构体,通过初始化cdev机构体,并记住其中的file_operations结构体:rtc_dev_fops,为后面的注册cdev做准备。我们看看他里面都有哪些操作函数:
static const struct file_operations rtc_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = rtc_dev_read,
.poll = rtc_dev_poll,
.ioctl = rtc_dev_ioctl,
.open = rtc_dev_open,
.release = rtc_dev_release,
.fasync = rtc_dev_fasync,
};
从中我们可以看出他主要是对字符设备的操作,我们一会儿会分析他们如何回调RTC设备中(class_ops)的操作函数的。而现在在注册字符驱动中我们就剩将cdev结构体注册进内核没做了。而我们在class.c中也就剩rtc_dev_add_device(rtc)函数了。那我们看rtc_dev_add_device(rtc)里面会不会有注册函数那。
void rtc_dev_add_device(struct rtc_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);
}
我们发现里面确实有注册函数:cdev_add(&rtc->char_dev, rtc->dev.devt, 1)。而通过这个函数实现将字符设备向内核的注册。
我们分析到现在就分析完了三个层次,也了解了他们层层抽象,层层调用的关系。而我们现在要想的是,如果我们想对这个RTC设备操作,该怎么操作?我们的用户只可以操作到字符设备层,而字符设备能操作到RTC设备。
要看字符设备的操作就是要看他的file_operations结构体。我们现在先从open函数开始看:
static int rtc_dev_open(struct inode *inode, struct file *file)
{
int err;
struct rtc_device *rtc = container_of(inode->i_cdev,
struct rtc_device, char_dev); /* 获得RTC设备结构体 */
const struct rtc_class_ops *ops = rtc->ops; /* 获得RTC设备的操作函数 */
file->private_data = rtc;
err = ops->open ? ops->open(rtc->dev.parent) : 0; /* 如果RTC设备存在open函数,调用他的open函数 */
return err;
}
从上面我简化的函数可以看出字符设备的open函数(file_operations)其实就是回调RTC设备的open函数(class_ops),现在就实现了用抽象层到物理层的回调。RTC设备的open函数:
static int rtc_dev_open(struct inode *inode, struct file *file)
{
int err;
struct rtc_device *rtc = container_of(inode->i_cdev,
struct rtc_device, char_dev); /* 获得RTC设备结构体 */
const struct rtc_class_ops *ops = rtc->ops; /* 获得RTC设备的操作函数 */
file->private_data = rtc;
err = ops->open ? ops->open(rtc->dev.parent) : 0; /* 如果RTC设备存在open函数,调用他的open函数 */
return err;
}
从上面看很简单就是为RTC设备申请了两个中断。
下面我们在分析字符驱动中的ioctl函数:
static int rtc_dev_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int err = 0;
struct rtc_device *rtc = file->private_data;
const struct rtc_class_ops *ops = rtc->ops;
struct rtc_time tm;
struct rtc_wkalrm alarm;
void __user *uarg = (void __user *) arg;
/* check that the calling task has appropriate permissions
* for certain ioctls. doing this check here is useful
* to avoid duplicate code in each driver.
*/
switch (cmd) {
case RTC_EPOCH_SET:
case RTC_SET_TIME:
·············
case RTC_IRQP_SET:
·············
case RTC_PIE_ON:
·············
}
/* avoid conflicting IRQ users */
if (cmd == RTC_PIE_ON || cmd == RTC_PIE_OFF || cmd == RTC_IRQP_SET) {
spin_lock_irq(&rtc->irq_task_lock);
if (rtc->irq_task)
err = -EBUSY;
spin_unlock_irq(&rtc->irq_task_lock);
if (err < 0)
return err;
}
/* try the driver's ioctl interface */
if (ops->ioctl) {
err = ops->ioctl(rtc->dev.parent, cmd, arg);
if (err != -ENOIOCTLCMD)
return err;
}
/* if the driver does not provide the ioctl interface
* or if that particular ioctl was not implemented
* (-ENOIOCTLCMD), we will try to emulate here.
*/
switch (cmd) {
case RTC_ALM_READ:
err = rtc_read_alarm(rtc, &alarm);
········
case RTC_ALM_SET:
if (copy_from_user(&alarm.time, uarg, sizeof(tm)))
return -EFAULT;
alarm.enabled = 0;
alarm.pending = 0;
alarm.time.tm_wday = -1;
alarm.time.tm_yday = -1;
alarm.time.tm_isdst = -1;
/* RTC_ALM_SET alarms may be up to 24 hours in the future.
* Rather than expecting every RTC to implement "don't care"
* for day/month/year fields, just force the alarm to have
* the right values for those fields.
*
* RTC_WKALM_SET should be used instead. Not only does it
* eliminate the need for a separate RTC_AIE_ON call, it
* doesn't have the "alarm 23:59:59 in the future" race.
*
* NOTE: some legacy code may have used invalid fields as
* wildcards, exposing hardware "periodic alarm" capabilities.
* Not supported here.
*/
{
unsigned long now, then;
err = rtc_read_time(rtc, &tm);
if (err < 0)
return err;
rtc_tm_to_time(&tm, &now);
alarm.time.tm_mday = tm.tm_mday;
alarm.time.tm_mon = tm.tm_mon;
alarm.time.tm_year = tm.tm_year;
err = rtc_valid_tm(&alarm.time);
if (err < 0)
return err;
rtc_tm_to_time(&alarm.time, &then);
/* alarm may need to wrap into tomorrow */
if (then < now) {
rtc_time_to_tm(now + 24 * 60 * 60, &tm);
alarm.time.tm_mday = tm.tm_mday;
alarm.time.tm_mon = tm.tm_mon;
alarm.time.tm_year = tm.tm_year;
}
}
err = rtc_set_alarm(rtc, &alarm);
break;
case RTC_RD_TIME: /* 读RTC的时间 */
err = rtc_read_time(rtc, &tm);
if (err < 0)
return err;
if (copy_to_user(uarg, &tm, sizeof(tm)))
return -EFAULT;
break;
case RTC_SET_TIME: /* 设置RTC的时间 */
if (copy_from_user(&tm, uarg, sizeof(tm)))
return -EFAULT;
err = rtc_set_time(rtc, &tm);
break;
case RTC_IRQP_READ:
if (ops->irq_set_freq)
err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
break;
case RTC_IRQP_SET:
if (ops->irq_set_freq)
err = rtc_irq_set_freq(rtc, rtc->irq_task, arg);
break;
case RTC_WKALM_SET:
if (copy_from_user(&alarm, uarg, sizeof(alarm)))
return -EFAULT;
err = rtc_set_alarm(rtc, &alarm);
break;
case RTC_WKALM_RD:
err = rtc_read_alarm(rtc, &alarm);
if (err < 0)
return err;
if (copy_to_user(uarg, &alarm, sizeof(alarm)))
return -EFAULT;
break;
default:
err = -ENOTTY;
break;
}
return err;
}
从上面看这个函数中调用了很多RTC设备的操作函数,也是主要的对RTC设备的控制和操作。
其实这个RTC框架就是一个从RTC设备到字符设备的抽象,同时又通过字符设备回调RTC设备的操作函数,实现对RTC设备的控制。