您的位置:首页 > 运维架构 > Linux

学习笔记 --- LINUX RTC驱动分析

2014-03-31 12:56 603 查看
RTC也是一个字符设备,打开,读写,就是打开时钟,设置和读时钟。这里以S3C2440为例,S3C2440里面自带有RTC模块,可以直接操作寄存器来打开,写,读等操作。其驱动在rtc_s3c.c里面:

static int __devinit s3c_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc;
struct resource *res;
int ret;

pr_debug("%s: probe=%p\n", __func__, pdev);

/* find the IRQs */

s3c_rtc_tickno = platform_get_irq(pdev, 1);
if (s3c_rtc_tickno < 0) {
dev_err(&pdev->dev, "no irq for rtc tick\n");
return -ENOENT;
}

s3c_rtc_alarmno = platform_get_irq(pdev, 0);
if (s3c_rtc_alarmno < 0) {
dev_err(&pdev->dev, "no irq for alarm\n");
return -ENOENT;
}

pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
s3c_rtc_tickno, s3c_rtc_alarmno);

/* get the memory region */

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory region resource\n");
return -ENOENT;
}

s3c_rtc_mem = request_mem_region(res->start,
res->end-res->start+1,
pdev->name);

if (s3c_rtc_mem == NULL) {
dev_err(&pdev->dev, "failed to reserve memory region\n");
ret = -ENOENT;
goto err_nores;
}

s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);
if (s3c_rtc_base == NULL) {
dev_err(&pdev->dev, "failed ioremap()\n");
ret = -EINVAL;
goto err_nomap;
}

/* check to see if everything is setup correctly */

s3c_rtc_enable(pdev, 1);

pr_debug("s3c2410_rtc: RTCCON=%02x\n",
readb(s3c_rtc_base + S3C2410_RTCCON));

s3c_rtc_setfreq(&pdev->dev, 1);

device_init_wakeup(&pdev->dev, 1);

/* register RTC and exit */

rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
THIS_MODULE);

if (IS_ERR(rtc)) {
dev_err(&pdev->dev, "cannot attach rtc\n");
ret = PTR_ERR(rtc);
goto err_nortc;
}

rtc->max_user_freq = 128;

platform_set_drvdata(pdev, rtc);
return 0;

err_nortc:
s3c_rtc_enable(pdev, 0);
iounmap(s3c_rtc_base);

err_nomap:
release_resource(s3c_rtc_mem);

err_nores:
return ret;
}
前面获取plat信息(中断号,寄存器基地址等),通过设置寄存器,使能RTC,设置好频率,最后调用:

rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
THIS_MODULE);


这里传入的s3c_rtcops放的是平台实际去操作RTC的操作函数,后面再分析。

看这个函数,注册RTC设备:

/**
* rtc_device_register - register w/ RTC class
* @dev: the device to register
*
* rtc_device_unregister() must be called when the class device is no
* longer needed.
*
* Returns the pointer to the new struct class device.
*/
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);

if (err < 0)
goto exit;

id = id & MAX_ID_MASK;

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);

rtc_dev_prepare(rtc);

err = device_register(&rtc->dev);
if (err)
goto exit_kfree;

rtc_dev_add_device(rtc);
rtc_sysfs_add_device(rtc);
rtc_proc_add_device(rtc);

dev_info(dev, "rtc core: registered %s as %s\n",
rtc->name, dev_name(&rtc->dev));

return rtc;

exit_kfree:
kfree(rtc);

exit_idr:
mutex_lock(&idr_lock);
idr_remove(&rtc_idr, id);
mutex_unlock(&idr_lock);

exit:
dev_err(dev, "rtc core: unable to register %s, err = %d\n",
name, err);
return ERR_PTR(err);
}


可以看到这个函数就是注册一个RTC字符设备,并且创建设备节点(dev/rtc):

s3c_rtc_init
platform_driver_register
s3c_rtc_probe
rtc_device_register("s3c", &pdev->dev, &s3c_rtcops, THIS_MODULE)
rtc_dev_prepare
cdev_init(&rtc->char_dev, &rtc_dev_fops);
rtc_dev_add_device
cdev_add


上面cdev_init可以知道,这个字符设备提供应用的操作函数:

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,
};

应用open的时候:

app:    open("/dev/rtc0");
-------------------------------------------
kernel: sys_open
rtc_dev_fops.open
rtc_dev_open
// 根据次设备号找到以前用"rtc_device_register"注册的rtc_device
struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);
const struct rtc_class_ops *ops = rtc->ops;
file->private_data = rtc;  //rtc设备数据设置进file私有数据
err = ops->open ? ops->open(rtc->dev.parent) : 0;


应用读的时候用的iotrl:

app:    ioctl(fd, RTC_RD_TIME,...)
-------------------------------------------
kernel: sys_ioctl
rtc_dev_fops.ioctl
rtc_dev_ioctl
struct rtc_device *rtc = file->private_data;  //获取rtc设备数据
rtc_read_time(rtc, &tm);
err = rtc->ops->read_time(rtc->dev.parent, tm);
s3c_rtc_gettime
从上面open,ioctl可以看出,他们实际上是调用的都是rtc里面的ops操作:

static const struct rtc_class_ops s3c_rtcops = {
.open		= s3c_rtc_open,
.release	= s3c_rtc_release,
.read_time	= s3c_rtc_gettime,
.set_time	= s3c_rtc_settime,
.read_alarm	= s3c_rtc_getalarm,
.set_alarm	= s3c_rtc_setalarm,
.irq_set_freq	= s3c_rtc_setfreq,
.irq_set_state	= s3c_rtc_setpie,
.proc	        = s3c_rtc_proc,
};

这个结构体实际上就是对RTC硬件操作,跟平台有关的,其他的部分都是内核已经完成了一个好的驱动框架,我们换平台主要就是实现这个结构体,将其注册到:

rtc_device_register就可以了,rtc_device_register就是内核的RTC驱动层。

RTC测试:

ls /dev/rtc* -l

date /* 显示系统时间 */

date 123015402011.30 /* 设置系统时间 date [MMDDhhmm[[CC]YY][.ss]] */

hwclock -w /* 把系统时间写入RTC */

断电,重启,执行date

————————————————————————————————————————————————————————————————————————————

RTC(real time clock)实时时钟,主要作用是给Linux系统提供时间。RTC因为是电池供电的,所以掉电后时间不丢失。Linux内核把RTC用作“离线”的时间与日期维护器。当Linux内核启动时,它从RTC中读取时间与日期,作为基准值。在运行期间内核完全抛开RTC,以软件的形式维护系统的当前时间与日期,并在需要时将时间回写RTC芯片。另外如果RTC提供了IRQ中断并且可以定时,那么RTC还可以作为内核睡眠时唤醒内核的闹钟。应用程序可以用RTC提供的周期中断做一些周期的任务。 linux有两种rtc驱动的接口,一个是老的接口,专门用在PC机上的。另外一钟新接口是基于linux设备驱动程序的。这个新的接口创建了一个RTC驱动模型,实现了RTC的大部分基本功能。而底层驱动无须考虑一些功能的实现,只需将自己注册的RTC核心中,其他工作由RTC核心来完成。下面分析RTC新接口的驱动模型。
一. 驱动模型结构

与RTC核心有关的文件有:

/drivers/rtc/class.c 这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口

/drivers/rtc/rtc-dev.c 这个文件定义了基本的设备文件操作函数,如:open,read等

/drivers/rtc/interface.c 顾名思义,这个文件主要提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数

/drivers/rtc/rtc-sysfs.c 与sysfs有关

/drivers/rtc/rtc-proc.c 与proc文件系统有关

/include/linux/rtc.h 定义了与RTC有关的数据结构

RTC驱动模型结构如下图:



二. 基本数据结构

1. struct rtc_device 结构

struct rtc_device

{

struct device dev;

struct module *owner;

int id;

char name[RTC_DEVICE_NAME_SIZE];

const struct rtc_class_ops *ops;

struct mutex ops_lock;

struct cdev char_dev;

unsigned long flags;

unsigned long irq_data;

spinlock_t irq_lock;

wait_queue_head_t irq_queue;

struct fasync_struct *async_queue;

struct rtc_task *irq_task;

spinlock_t irq_task_lock;

int irq_freq;

int max_user_freq;

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL

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驱动程序的基本数据结构,但是他不像其他核心的基本结构一样,驱动程序以他为参数调用注册函数注册到核心。这个结构是由注册函数返回给驱动程序的。

2. struct rtc_class_ops 结构

struct rtc_class_ops {

int (*open)(struct device *);

void (*release)(struct device *);

int (*ioctl)(struct device *, unsigned int, unsigned long);

int (*read_time)(struct device *, struct rtc_time *);

int (*set_time)(struct device *, struct rtc_time *);

int (*read_alarm)(struct device *, struct rtc_wkalrm *);

int (*set_alarm)(struct device *, struct rtc_wkalrm *);

int (*proc)(struct device *, struct seq_file *);

int (*set_mmss)(struct device *, unsigned long secs);

int (*irq_set_state)(struct device *, int enabled);

int (*irq_set_freq)(struct device *, int freq);

int (*read_callback)(struct device *, int data);

int (*alarm_irq_enable)(struct device *, unsigned int enabled);

int (*update_irq_enable)(struct device *, unsigned int enabled);

};

这个结构是RTC驱动程序要实现的基本操作函数,注意这里的操作不是文件操作。驱动程序通过初始化这样一个结构,将自己实现的函数与RTC核心联系起来。这里面的大部分函数都要驱动程序来实现。而且这些函数都是操作底层硬件的,属于最底层的函数。

3. struct rtc_time 结构

struct rtc_time {

int tm_sec;

int tm_min;

int tm_hour;

int tm_mday;

int tm_mon;

int tm_year;

int tm_wday;

int tm_yday;

int tm_isdst;

};

代表了时间与日期,从RTC设备读回的时间和日期就保存在这个结构体中

三. class.c

1. 模块初始化函数:rtc_init

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;

}

rtc_init首先调用class_create创建了一个类--rtc。我们知道类是一个设备的高层视图,他抽象出了底层的实现细节。类的作用就是向用户空间提供设备的信息,驱动程序不需要直接处理类。然后初始化类结构的相应成员,rtc_suspend,rtc_resume这两个函数也是在class.c中实现的。接下来调用rtc_dev_init(),这个函数为RTC设备动态分配设备号,保存在rtc_devt中。最后调用rtc_sysfs_init,初始化rtc_class的属性。

2. 为底层驱动提供接口:rtc_device_register,rtc_device_unregister

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);

/*--------------------(1)---------------------*/

if (err < 0)

goto exit;

id = id & MAX_ID_MASK;

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);

/*-------------------(2)--------------------*/

rtc_dev_prepare(rtc);

err = device_register(&rtc->dev);

if (err)

goto exit_kfree;

/*-------------------(3)--------------------*/

rtc_dev_add_device(rtc);

rtc_sysfs_add_device(rtc);

rtc_proc_add_device(rtc);

dev_info(dev, "rtc core: registered %s as %s\n",

rtc->name, dev_name(&rtc->dev));

/*-------------------(4)--------------------*/

return rtc;

exit_kfree:

kfree(rtc);

exit_idr:

mutex_lock(&idr_lock);

idr_remove(&rtc_idr, id);

mutex_unlock(&idr_lock);

exit:

dev_err(dev, "rtc core: unable to register %s, err = %d\n",

name, err);

return ERR_PTR(err);

}

(1):处理一个idr的结构,idr在linux内核中指的就是整数ID管理机制,从本质上来说,idr是一种将整数ID号和特定指针关联在一起的机制。这个机制最早是在2003年2月加入内核的,当时是作为POSIX定时器的一个补丁。现在,在内核的很多地方都可以找到idr的身影。详细实现请参照相关内核代码。这里从内核中获取一个idr结构,并与id相关联。

(2):分配了一个rtc_device的结构--rtc,并且初始化了相关的成员:id, rtc_class_ops等等。

(3):首先调用rtc_dev_prepare(在rtc-dev.c中定义)。因为RTC设备本质来讲还是字符设备,所以这里初始化了字符设备相关的结构:设备号以及文件操作。然后调用device_register将设备注册到linux设备模型核心。这样在模块加载的时候,udev daemon就会自动为我们创建设备文件rtc(n)。

(4):先后调用rtc_dev_add_device,rtc_sysfs_add_device,rtc_proc_add_device三个函数。rtc_dev_add_device注册字符设备,rtc_sysfs_add_device只是为设备添加了一个闹钟属性,rtc_proc_add_device 创建proc文件系统接口。

四. rtc-dev.c

rtc-dev.c 初始化了一个file_operations结构--rtc_dev_fops,并定义了这些操作函数。

1. rtc_dev_fops rtc基本的文件操作

[align=center][/align]
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,

;

2. 函数的实现(以rtc_dev_read为例)

rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)

{

struct rtc_device *rtc = file->private_data;

DECLARE_WAITQUEUE(wait, current);

unsigned long data;

ssize_t ret;

if (count != sizeof(unsigned int) && count < sizeof(unsigned long))

return -EINVAL;

add_wait_queue(&rtc->irq_queue, &wait);

do {

__set_current_state(TASK_INTERRUPTIBLE);

spin_lock_irq(&rtc->irq_lock);

data = rtc->irq_data;

rtc->irq_data = 0;

spin_unlock_irq(&rtc->irq_lock);

if (data != 0) {

ret = 0;

break;

}

if (file->f_flags & O_NONBLOCK) {

ret = -EAGAIN;

break;

}

if (signal_pending(current)) {

ret = -ERESTARTSYS;

break;

}

schedule();

} while (1);

set_current_state(TASK_RUNNING);

remove_wait_queue(&rtc->irq_queue, &wait);

if (ret == 0) {

/* Check for any data updates */

if (rtc->ops->read_callback)

data = rtc->ops->read_callback(rtc->dev.parent,

data);

if (sizeof(int) != sizeof(long) &&

count == sizeof(unsigned int))

ret = put_user(data, (unsigned int __user *)buf) ?:

sizeof(unsigned int);

else

ret = put_user(data, (unsigned long __user *)buf) ?:

sizeof(unsigned long);

}

return ret;

}

这里的read不是应用程序用来获取时间的,而是有其他的作用,他帮助应用程序周期性的完成一些工作。如果要使用这个功能,应用程序首先保证RTC驱动程序提供这样的功能。这个功能是这样实现的:进程读取/dev/rtc(n),进程睡眠直到RTC中断将他唤醒。我们可以发现,这里的睡眠是ldd3中提到的手工睡眠。这个函数的手工休眠过程如下:首先调用DECLARE_WAITQUEUE(wait,
current),声明一个等待队列入口,然后调用add_wait_queue将这个入口加入到RTC的irq等待队列里,然后进入循环。在循环里首先把进程的状态改成TASK_INTERRUPTIBLE,这样进程就不能再被调度运行。但是现在进程还在运行,没有进入睡眠状态。程序然后读取RTC里面的irq_data,如果不是零,那么程序跳出这个循环,进程不会睡眠。因为这个irq_data在rtc的中断处理程序会被赋值,而读过之后就会清零,所以如果数据不是零的话说明发生过一次中断。如果是零那么没有发生中断,调用schedule,进程会被调度出可运行队列,从而让出处理器,真正进入睡眠。跳出循环代表被唤醒,然后将进程状态改变为可运行,移除等待队列入口。最后将读回的数据传给用户空间。

五. interface.c

interface.c里的所有函数的实现都对应于rtc-dev.c 中ioctl相应的命令。对应关系如下:

RTC_ALM_READ rtc_read_alarm 读取闹钟时间

RTC_ALM_SET rtc_set_alarm 设置闹钟时间

RTC_RD_TIME rtc_read_time 读取时间与日期

RTC_SET_TIME rtc_set_time 设置时间与日期

RTC_PIE_ON RTC_PIE_OFF rtc_irq_set_state 开关RTC全局中断的函数

RTC_AIE_ON RTC_AIE_OFF rtc_alarm_irq_enable 使能禁止RTC闹钟中断

RTC_UIE_OFF RTC_UIE_ON rtc_update_irq_enable 使能禁止RTC更新中断

RTC_IRQP_SET rtc_irq_set_freq 设置中断的频率

以上就是所有ioctl的命令与实现的对应关系。其中如果不涉及中断的话,有两个命令需要我们特别关心一下,就是RTC_RD_TIME与RTC_SET_TIME。因为RTC最基本的功能就是提供时间与日期。这两个命令恰恰是获取时间和设置时间。下面分析一下这两个命令的实现,也就是rtc_set_alarm与rtc_read_time函数的实现:

1. rtc_read_time 函数

int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)

{

int err;

err = mutex_lock_interruptible(&rtc->ops_lock);

if (err)

return err;

if (!rtc->ops)

err = -ENODEV;

else if (!rtc->ops->read_time)

err = -EINVAL;

else {

memset(tm, 0, sizeof(struct rtc_time));

err = rtc->ops->read_time(rtc->dev.parent, tm);

}

mutex_unlock(&rtc->ops_lock);

return err;

}

这个函数用了一个信号来保证在同一时刻只有一个进程可以获取时间。锁定了这个信号量后,调用rtc->ops里面read函数,这个函数是由具体的驱动程序实现的,操作底层硬件。读回的时间存放在rtc_time结构里面的。

2. rtc_set_time 函数

int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)

{

int err;

err = rtc_valid_tm(tm);

if (err != 0)

return err;

err = mutex_lock_interruptible(&rtc->ops_lock);

if (err)

return err;

if (!rtc->ops)

err = -ENODEV;

else if (rtc->ops->set_time)

err = rtc->ops->set_time(rtc->dev.parent, tm);

else if (rtc->ops->set_mmss) {

unsigned long secs;

err = rtc_tm_to_time(tm, &secs);

if (err == 0)

err = rtc->ops->set_mmss(rtc->dev.parent, secs);

} else

err = -EINVAL;

mutex_unlock(&rtc->ops_lock);

return err;

}

这个函数其实和rtc_read_time函数差不多,同样是锁定信号量,同样是调用底层驱动函数。但是这里的设置时间提供了两个调用:一个是set_time,一个是set_mmss。因为有的RTC硬件只计算秒数,不关心墙钟时间,所以如果是这样的RTC,必须实现set_mmss来设置时间。

六. rtc-sysfs.c 部分

这个部分主要是有关sysfs的操作。rtc-sysfs.c中定义了这样一个设备属性组,如下:

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),

{ },

};

这个属性组是在class.c的模块初始化函数中,由rtc_sysfs_init函数赋值给rtc_class->dev_attrs的,以后属于这个类的设备都会有这些属性。但是我们知道要想一个设备结构拥有一种属性,必须调用device_create_file,这样才会使这个属性出现在sysfs相关设备目录里。但是在这里的代码中只是给这个类的dev_attrs域赋值了这个属性组指针,而没有调用device_create_file。我原来以为是在rtc_device_resgister函数中,由rtc_sysfs_add_device完成这个工作,但是这个函数只是给设备添加了闹钟属性,并没有处理这个属性组。最后发现这个工作是由device_register来完成的。这里的调用关系有点复杂:

device_register调用device_add

device_add调用 device_add_attrs

device_add_attrs调用device_add_attributes

device_add_attributes调用device_create_file来完成设备的属性设置的。

设置完属性后,在/sys/class/rtc/rtc(n)的目录下就会出现name,date,time等文件,用户读这些文件的时候就会调用相应的函数。如读取name文件,就会调用rtc_sysfs_show_name函数,这个函数也是在rtc-sysfs.c中实现的,作用是读取并显示时间。
七. rtc-proc.c

这个文件提供RTC的proc文件系统接口。proc文件系统是软件创建的文件系统,内核通过他向外界导出信息,下面的每一个文件都绑定一个函数,当用户读取这个文件的时候,这个函数会向文件写入信息。rtc-proc.c中初始化了一个文件操作:

static const struct file_operations rtc_proc_fops = {

.open = rtc_proc_open,

.read = seq_read,

.llseek = seq_lseek,

.release = rtc_proc_release,

};

RTC驱动在向RTC核心注册自己的时候,由注册函数rtc_device_resgister调用rtc_proc_add_device来实现proc接口的初始化,这个函数如下定义:

void rtc_proc_add_device(struct rtc_device *rtc)

{

if (rtc->id == 0)

proc_create_data("driver/rtc", 0, NULL, &rtc_proc_fops, rtc);

}

他主要调用了proc_create_data。proc_create_data完成创建文件节点的作用,并将文件的操作函数与节点联系起来。调用这个函数后,在/proc/driver目录下就会有一个文件rtc,应用程序打开这个文件就会调用rtc_proc_open函数,这个函数如下定义:

static int rtc_proc_open(struct inode *inode, struct file *file)

{

struct rtc_device *rtc = PDE(inode)->data;

if (!try_module_get(THIS_MODULE))

return -ENODEV;

return single_open(file, rtc_proc_show, rtc);

}

我们知道一个proc的文件必须与一个操作函数组成一个proc入口项,这个文件才能正常工作。这个函数最主要作用就是调用single_open,创建一个proc文件入口项,使其操作函数是rtc_proc_show,并初始化seq_file接口。rtc_proc_show函数如下定义:

static int rtc_proc_show(struct seq_file *seq, void *offset)

{

int err;

struct rtc_device *rtc = seq->private;

const struct rtc_class_ops *ops = rtc->ops;

struct rtc_wkalrm alrm;

struct rtc_time tm;

err = rtc_read_time(rtc, &tm);

if (err == 0) {

seq_printf(seq,

"rtc_time\t: %02d:%02d:%02d\n"

"rtc_date\t: %04d-%02d-%02d\n",

tm.tm_hour, tm.tm_min, tm.tm_sec,

tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);

}

err = rtc_read_alarm(rtc, &alrm);

if (err == 0) {

seq_printf(seq, "alrm_time\t: ");

if ((unsigned int)alrm.time.tm_hour <= 24)

seq_printf(seq, "%02d:", alrm.time.tm_hour);

else

seq_printf(seq, "**:");

if ((unsigned int)alrm.time.tm_min <= 59)

seq_printf(seq, "%02d:", alrm.time.tm_min);

else

seq_printf(seq, "**:");

if ((unsigned int)alrm.time.tm_sec <= 59)

seq_printf(seq, "%02d\n", alrm.time.tm_sec);

else

seq_printf(seq, "**\n");

seq_printf(seq, "alrm_date\t: ");

if ((unsigned int)alrm.time.tm_year <= 200)

seq_printf(seq, "%04d-", alrm.time.tm_year + 1900);

else

seq_printf(seq, "****-");

if ((unsigned int)alrm.time.tm_mon <= 11)

seq_printf(seq, "%02d-", alrm.time.tm_mon + 1);

else

seq_printf(seq, "**-");

if (alrm.time.tm_mday && (unsigned int)alrm.time.tm_mday <= 31)

seq_printf(seq, "%02d\n", alrm.time.tm_mday);

else

seq_printf(seq, "**\n");

seq_printf(seq, "alarm_IRQ\t: %s\n",

alrm.enabled ? "yes" : "no");

seq_printf(seq, "alrm_pending\t: %s\n",

alrm.pending ? "yes" : "no");

}

seq_printf(seq, "24hr\t\t: yes\n");

if (ops->proc)

ops->proc(rtc->dev.parent, seq);

return 0;

}

这个函数就是最后给用户显示信息的函数了,可以看出他通过调用rtc_deivce中的操作函数,读取时间,日期和一些其他的信息显示给用户。

六. 总结

RTC核心使底层硬件对用户来说是透明的,并且减少了编写驱动程序的工作量。RTC新的驱动接口提供了更多的功能,使系统可以同时存在多个RTC。/dev,sysfs,proc这三种机制的实现使得应用程序能灵活的使用RTC,RTC核心虽然表面上看上去很简单,但是还是涉及到很多知识,有些东西书上讲的还是不够详细,还需要通过分析代码加深理解。 另外RTC核心代码的组织方式也值得学习,不同功能的代码放在不同的文件中,简单明了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: