嵌入式Linux——RTC驱动(1):RTC框架分析

2019-07-13 02:20发布

简介:         本文通过分层的方式介绍RTC驱动,通过分析RTC在不同层次中所做的不同工作,以及各个层次之间的关系来了解RTC驱动框架。本文分为两部分,第一部分总的介绍RTC框架,而第二部分将结合详细的代码来分析各个层次的关系以及在本层中他们所做的事情。     Linux内核:linux-2.6.22.6     所用开发板:JZ2440 V3(S3C2440A) 声明 :         本文是结合韦东山老师的开发板JZ2440 V3(S3C2440A)所写。所以希望大家在看本文之前对开发板中的RTC有所了解。因为我们在后面分析代码时也会介绍对寄存器的操作。而我在上一篇文章:S3C2440A 第十七章——实时时钟 中翻译了芯片手册这部分的内容,如果你对那部分知识不是很了解,可以去看一下。 第一部分:介绍RTC框架         我这两天看了一些关于介绍RTC框架的文章,但是发现大家很少使用框架图来说明这个 框架(或许是有我没有看到),而有也是将文件系统或是其他的接口相关的文件都加到了这个框架图上而他们内部的关系却不是很明确,然而这会使得RTC的框架看起来有点复杂,而对初学者来说有些困难,所以我自己画了一个框架图,同时在框架图中标出了他们的调用关系。希望对大家分析RTC框架有用。         从上面的图中我们可以看出RTC框架大致可以分为三部分:rtc-s3c.cclass.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的平台驱动,而在平台——设备——驱动模型中我们知道,既然有这个驱动那就一定有一个与其同名的平台设备,通过搜索我们在archarmplat-s3c24xxdevs.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 
        由于第一和第二在上面已经讲得很清楚了,所以我们直接讲第三个: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 "); 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 "); 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 "); 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) { 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_rtcopsstatic 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,同时又由于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层,而将不能移植的部分局域化,集中在某几个特征文件中从而在软件上实现层次化和模块化。那么我们就在这个层中找一下他抽象为什么样的字符设备,同时了解一下他的回调函数。在分析代码前我们先回忆一下我们的字符设备是怎么注册的:           下面我们开始分析代码从中看他是如何实现字符驱动的注册的         我们先从他的入口函数开始分析: void __init rtc_dev_init(void) { int err; err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc"); }         我们可以看出他一开始就为字符设备注册了一份区域,同时我们也知道这个字符设备的次设备号的基地址为0,最多可以注册16个这样的设备。而上面的RTC_DEV_MAX的宏定义为: #define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */         而分析完入口函数,我们接着上面class.c中要分析的两个函数:rtc_dev_prepare(rtc)和rtc_dev_add_device(rtc)继续分析。 我们先分析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结构体,为后面的注册cdev做准备。而在上面我们看到了file_operations结构体:rtc_dev_fops。我们看看他里面都有哪些操作函数: 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设备中的操作函数的,而现在我们发现,在注册字符驱动中我们就剩将cdev结构体注册进内核了。而我们在class.c中也就剩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 ", rtc->name, MAJOR(rtc_devt), rtc->id); else pr_debug("%s: dev (%d:%d) ", 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函数其实就是回调RTC设备的open函数,现在就实现了用抽象层到物理层的回调。我们看看RTC设备的open函数做了什么: static int s3c_rtc_open(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct rtc_device *rtc_dev = platform_get_drvdata(pdev); int ret; ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq, IRQF_DISABLED, "s3c2410-rtc alarm", rtc_dev); ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq, IRQF_DISABLED, "s3c2410-rtc tick", rtc_dev); return ret; }         从上面看很简单就是为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设备的控制。希望我的分析对你有用。   参考文章: ARM-Linux驱动--RTC(实时时钟)驱动分析
Linux RTC驱动模型分析
RTC驱动模型分析
linux RTC 驱动模型分析
30.Linux-RTC驱动分析及使用