zoukankan      html  css  js  c++  java
  • RTC子系统


    title: RTC子系统
    tags: linux
    date: 2019/1/2 17:15:27
    toc: true

    RTC子系统

    引入

    hctosys.c

    查看下内核打印的错误信息如下,很明确指定了程序的入口了

    drivers/rtc/hctosys.c: unable to open rtc device (rtc0)
    

    程序流程如下:

    // 这个也就是定义了段属性,内核在启动的时候会调用的
    late_initcall(rtc_hctosys);
    	#define late_initcall(fn)		__define_initcall("7",fn,7)
    
    
    static int __init rtc_hctosys(void)
    {
    	int err;
    	struct rtc_time tm;
    	struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
    
    	if (rtc == NULL) {
    		printk("%s: unable to open rtc device (%s)
    ",
    			__FILE__, CONFIG_RTC_HCTOSYS_DEVICE);
    		return -ENODEV;
    	}
    	....
    }
    
    

    interface.c

    搜索下为什么打不开设备rtc_class_open,可以发现是寻找全局变量rtc_class总线的设备

    struct rtc_device *rtc_class_open(char *name)
    {
    	struct device *dev;
    	struct rtc_device *rtc = NULL;
    
    	down(&rtc_class->sem);
    	list_for_each_entry(dev, &rtc_class->devices, node) {
    ....
    }
    

    可以看到在drivers tcinterface.c中包含了RTC的打开,设置事件设置闹钟等

    class.c

    搜索这个全局的链表,可以看到在drivers tcclass.c中注册的

    struct class *rtc_class;
    struct rtc_device *rtc_device_register(const char *name, struct device *dev,
    					const struct rtc_class_ops *ops,
    					struct module *owner)
    static int __init rtc_init(void)
    subsys_initcall(rtc_init);
    
    #define subsys_initcall(fn)		__define_initcall("4",fn,4)
    
    #define __define_initcall(level,fn,id) 
    	static initcall_t __initcall_##fn##id __attribute_used__ 
    	__attribute__((__section__(".initcall" level ".init"))) = fn
    
    
    

    subsys_initcall其实就是定义一个段属性,内核会主动调用的,查看该文件的入口

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

    小结

    • hctosys.c中的入口rtc_hctosys是使用__define_initcall("7",fn,7)

    • class.c的入口rtc_init是使用__define_initcall("4",fn,4)

    • 内核按照先后顺序调用,内核的链接脚本如下

        __initcall_start = .;
         *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
        __initcall_end = .;
      
    • 搜索这个链接脚本,可以看到

      extern initcall_t __initcall_start[], __initcall_end[];
      static void __init do_initcalls(void)
      {
      	for (call = __initcall_start; call < __initcall_end; call++) {
      ...
      }            
      
    • 也就是按找序号调用了,先执行rtc_init,再去打开,想想也是这样的

    流程一览

    mark

    框架分析

    rtc_init

    drivers tcclass.c这是内核入口初始化注册设备,rtc_init()->rtc_dev_init(),来注册字符设备

    • 创建类
    • 分配主次设备号
    static int __init rtc_init(void)
    {
    	rtc_class = class_create(THIS_MODULE, "rtc");
        
    	rtc_class->suspend = rtc_suspend;
    	rtc_class->resume = rtc_resume;
    	rtc_dev_init();
    		#define RTC_DEV_MAX 16 
    		alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
    	rtc_sysfs_init(rtc_class);
    	return 0;
    }
    

    rtc_device_register

    搜索这个函数,是在drivers tc tc-s3c.c调用的

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

    具体形式如下:

    • cdev_init > cdev_add 注册字符设备驱动
    struct rtc_device *rtc_device_register(const char *name, struct device *dev,
    					const struct rtc_class_ops *ops,
    					struct module *owner)
    {
    		> 设置rtc结构体
    		rtc->id = id;
    		rtc->ops = ops;
    		rtc->owner = owner;
    		rtc->max_user_freq = 64;
    		rtc->dev.parent = dev;
    		rtc->dev.class = rtc_class;		//这个是全局的,内核刚开始就注册了的在rtc_init
    		rtc->dev.release = rtc_device_release;
        
        	//这里是总线bus 后面会有 device_add 会来匹配,如果没有对应的bus匹配
        	snprintf(rtc->dev.bus_id, BUS_ID_SIZE, "rtc%d", id);
        		//>dev->bus->probe
    			//drv->probe //没有dev->bus->probe执行
        
        
        	rtc_dev_prepare(rtc);
        		rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
        		cdev_init(&rtc->char_dev, &rtc_dev_fops);
    		rtc_dev_add_device(rtc);
        		cdev_add(&rtc->char_dev, rtc->dev.devt, 1)
            // vfs 相关 sysfs 和 proc
            rtc_sysfs_add_device
            rtc_proc_add_device(rtc);
    
    }
    

    s3c_rtc_probe

    简述

    • 获得硬件资源,设置寄存器

    • 设置rtc_device结构体,包含了实际的硬件操作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_dev_fops,通用接口,最终应该会使用具体的硬件操作接口

      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;
      	rtc->ops = ops;
          rtc_dev_prepare(rtc);
          	>cdev_init(&rtc->char_dev, &rtc_dev_fops);
          	
      }
      
      //drivers
      tc
      tc-dev.c
      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,
      };
      
      

    详细

    s3c_rtc_probe会调用rtc_device_register来注册rtc设备,这是一个platform平台了,资源文件如下

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

    搜索资源文件名找到相应设备文件archarmplat-s3c24xxdevs.c 包含了寄存器地址和中断号

    struct platform_device s3c_device_rtc = {
    	.name		  = "s3c2410-rtc",
    	.id		  = -1,
    	.num_resources	  = ARRAY_SIZE(s3c_rtc_resource),
    	.resource	  = s3c_rtc_resource,
    };
    
    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
    	}
    };
    
    

    具体的函数流程如下:

    s3c_rtc_probe
    	//获得中断号 RQ_TICK节拍和RTC闹钟 ,寄存器等资源
    	s3c_rtc_tickno = platform_get_irq(pdev, 1);
    	s3c_rtc_alarmno = platform_get_irq(pdev, 0);
    	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);
    	
    	//使能RTC,设置寄存器
    	s3c_rtc_enable(pdev, 1);
    	//读取RTC寄存器
    	pr_debug("s3c2410_rtc: RTCCON=%02x
    ",readb(s3c_rtc_base + S3C2410_RTCCON));
    	//设置频率,设置TICONT寄存器,使能节拍中断,设置节拍计数值
    	s3c_rtc_setfreq(s3c_rtc_freq);
    
    	//注册驱动
    	rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);
    		> 设置rtc结构体
    		rtc->id = id;
    		rtc->ops = ops;
    		rtc->owner = owner;
    		rtc->max_user_freq = 64;
    		rtc->dev.parent = dev;
    		rtc->dev.class = rtc_class;		//这个是全局的,内核刚开始就注册了的在rtc_init
    		rtc->dev.release = rtc_device_release;
    		
    		//初始化cdev结构体,绑定file_operations  
    		rtc_dev_prepare(rtc);  
    			//这个应该是唤醒队列了
    			>init_waitqueue_head(&rtc->irq_queue);
    			>cdev_init(&rtc->char_dev, &rtc_dev_fops);
    		// 添加到内核,这里好像是udev机制
    		device_register(&rtc->dev);
    
    		//添加到驱动设备 		
    		rtc_dev_add_device(rtc);
    			> cdev_add
    		// /sysfs/文件
    		rtc_sysfs_add_device(rtc);
    			> device_create_file(&rtc->dev, &dev_attr_wakealarm);
    		//创建 /proc/下的一些东西
    		rtc_proc_add_device(rtc);
    			>create_proc_entry("driver/rtc", 0, NULL)
    			>ent->proc_fops = &rtc_proc_fops;
    

    open

    公用

    我们注册驱动的时候是注册了rtc_dev_fops,它是一个公共的,具体如何找到实际硬件的open?

    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,
    };
    
    
    static int rtc_dev_open(struct inode *inode, struct file *file)
    {
    	int err;
        //获取对应的rtc_device
    	struct rtc_device *rtc = container_of(inode->i_cdev,
    					struct rtc_device, char_dev);
        //这里就能得到实际的ops了
    	const struct rtc_class_ops *ops = rtc->ops;
    
        //这里就调用实际的open
        err = ops->open ? ops->open(rtc->dev.parent) : 0;
    }
    

    芯片级

    申请了闹钟中断和tick中断

    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,
    };
    
    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);
    
    	if (ret) {
    		dev_err(dev, "IRQ%d error %d
    ", s3c_rtc_alarmno, ret);
    		return ret;
    	}
    
    	ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,
    			  IRQF_DISABLED,  "s3c2410-rtc tick", rtc_dev);
    
    	if (ret) {
    		dev_err(dev, "IRQ%d error %d
    ", s3c_rtc_tickno, ret);
    		goto tick_err;
    	}
    
    	return ret;
    
     tick_err:
    	free_irq(s3c_rtc_alarmno, rtc_dev);
    	return ret;
    }
    
    

    ioctl

    同样的,公共级别的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;
        if (ops->ioctl) {
            err = ops->ioctl(rtc->dev.parent, cmd, arg);
    	...
    }
    

    芯片级

    这里也就是读写时间,操作寄存器了

    static int s3c_rtc_ioctl(struct device *dev,
    			 unsigned int cmd, unsigned long arg)
    {
    	unsigned int ret = -ENOIOCTLCMD;
    
    	switch (cmd) {
    	case RTC_AIE_OFF:
    	case RTC_AIE_ON:
    		s3c_rtc_setaie((cmd == RTC_AIE_ON) ? 1 : 0);
    		ret = 0;
    		break;
    
    	case RTC_PIE_OFF:
    	case RTC_PIE_ON:
    		tick_count = 0;
    		s3c_rtc_setpie((cmd == RTC_PIE_ON) ? 1 : 0);
    		ret = 0;
    		break;
    
    	case RTC_IRQP_READ:
    		ret = put_user(s3c_rtc_freq, (unsigned long __user *)arg);
    		break;
    
    	case RTC_IRQP_SET:
    		/* check for power of 2 */
    
    		if ((arg & (arg-1)) != 0 || arg < 1) {
    			ret = -EINVAL;
    			goto exit;
    		}
    
    		pr_debug("s3c2410_rtc: setting frequency %ld
    ", arg);
    
    		s3c_rtc_setfreq(arg);
    		ret = 0;
    		break;
    
    	case RTC_UIE_ON:
    	case RTC_UIE_OFF:
    		ret = -EINVAL;
    	}
    
     exit:
    	return ret;
    }
    
    

    加入时钟

    可以看到内核是有驱动的,只是没有注册平台设备文件 ,添加这个设备文件

    ls /dev/rtc* 
    

    arch/arm/plat-s3c24xx/Common-smdk.c加入s3c_device_rtc这个结构即可

    static struct platform_device __initdata *smdk_devs[] = {
    	&s3c_device_nand,
    .....
        &s3c_device_rtc,
    ....
    };
    

    测试

    上电启动信息如下

    s3c2410-rtc s3c2410-rtc: setting the system clock to 2165-10-04 10:44:26 (1882584970)
    

    查看下设备

    # ls /dev/rtc*
    /dev/rtc0
    

    date命令

    使用date命令读时间

    # 查看时间
    # date
    Tue Aug 28 04:17:30 UTC 2029
    # date  "+ %Y/%m/%d %H:%M:%S"
     2029/08/28 04:18:06
    

    使用date设置时间,格式是date 月日时分年.秒

    # date 010314322019.30
    Thu Jan  3 14:32:30 UTC 2019
    
    #  date  "+ %Y/%m/%d %H:%M:%S"
     2019/01/03 14:33:04
    #  date  "+ %Y/%m/%d %H:%M:%S"
     2019/01/03 14:33:10
    #  date  "+ %Y/%m/%d %H:%M:%S"
     2019/01/03 14:33:11
    
    

    hwclock命令

      -r, --show          读取并打印硬件时钟(read hardware clock and print result )
      -s, --hctosys      将硬件时钟同步到系统时钟(set the system time from the hardware clock )
      -w, --systohc     将系统时钟同步到硬件时钟(set the hardware clock to the current system time )
    

    使用如下

    #读取硬件时钟
    # hwclock -r
    Wed Dec 31 23:59:59 1969  0.000000 seconds
    # 同步,设置软时钟到硬件时钟
    # hwclock -w
    # hwclock -r
    Thu Jan  3 14:35:49 2019  0.000000 seconds
    
    
  • 相关阅读:
    225. Implement Stack using Queues
    232. Implement Queue using Stacks
    LeetCode 763 划分字母区间
    CentOS7+eDEX-UI打造属于你的极客桌面
    好玩又有趣的linux终端命令
    Linux 应急响应入门——入侵排查
    active_anon/inactive_anon
    Red Hat 平台的推荐交换大小是多少?
    为什么RHEL系统使用交换空间而不是释放缓存和缓冲内存?
    RHEL 交换内存(Swap)使用率为 100%
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10216885.html
Copyright © 2011-2022 走看看