---- 整理自 王利涛老师 课程
文章目录
1. 第一个 platform 驱动
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
// 定义一个平台设备
struct platform_device rtc_dev = {
.name = "rtc-demo", // 设备名称
.id = -1, // 设备ID,-1表示自动分配
.dev = {
.release = rtc_device_release,
},
};
static int __init rtc_device_init(void)
{
return platform_device_register(&rtc_dev); // 注册平台设备
}
static void __exit rtc_device_exit(void)
{
platform_device_unregister(&rtc_dev); // 注销平台设备
}
module_init(rtc_device_init);
module_exit(rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
set_rtc_alarm(regs);
return IRQ_HANDLED;
}
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1) {
printk("request_irq failed!\n");
return -1;
}
set_rtc_alarm(regs);
return 0;
}
static int rtc_driver_remove(struct platform_device *dev)
{
printk("%s: driver remove: %s\n", __func__, dev->name);
free_irq(39,NULL);
return 0;
}
// 平台驱动结构体
static struct platform_driver rtc_drv = {
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.driver = {
.name = "rtc-demo", // 驱动名称
}
};
static int __init rtc_driver_init(void)
{
return platform_driver_register(&rtc_drv); // 注册平台驱动
}
static void __exit rtc_driver_exit(void)
{
platform_driver_unregister(&rtc_drv); // 注销平台驱动
}
module_init(rtc_driver_init);
module_exit(rtc_driver_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m := rtc_device.o rtc_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
2. platform 驱动注册过程分析
2.1 platform 总线的注册过程
2.2 platform 设备的注册过程
2.3 platform 驱动的注册过程
3. platform bus match 方法
- 比对 device 和 driver 的 name 变量是否一样
- of
- ACPI
- id_table:驱动的复用,兼容多个设备
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
// 定义一个平台设备
struct platform_device rtc_dev = {
.name = "rtc-demo0", // 设备名称
.id = -1, // 设备ID,-1表示自动分配
.dev = {
.release = rtc_device_release,
},
};
static int __init rtc_device_init(void)
{
return platform_device_register(&rtc_dev); // 注册平台设备
}
static void __exit rtc_device_exit(void)
{
platform_device_unregister(&rtc_dev); // 注销平台设备
}
module_init(rtc_device_init);
module_exit(rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
set_rtc_alarm(regs);
return IRQ_HANDLED;
}
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1) {
printk("request_irq failed!\n");
return -1;
}
set_rtc_alarm(regs);
return 0;
}
static int rtc_driver_remove(struct platform_device *dev)
{
printk("%s: driver remove: %s\n", __func__, dev->name);
free_irq(39,NULL);
return 0;
}
// 平台驱动ID表
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-demo0",
.driver_data = 0,
},
{
.name = "rtc-demo1",
.driver_data = 1,
},
};
// 平台驱动结构体
static struct platform_driver rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids, // ID表
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.driver = {
.name = "rtc-demo", // 驱动名称
}
};
static int __init rtc_driver_init(void)
{
return platform_driver_register(&rtc_drv); // 注册平台驱动
}
static void __exit rtc_driver_exit(void)
{
platform_driver_unregister(&rtc_drv); // 注销平台驱动
}
module_init(rtc_driver_init);
module_exit(rtc_driver_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m := rtc_device.o rtc_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
4. 注册一个字符设备驱动
- 文件 I/O 接口去读取时间、设置时间、设置闹钟
- 在内核层面去设置一个闹钟中断
- 去注册字符驱动,引出各种操作接口
- 手动创建设备节点
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
// 定义一个平台设备
struct platform_device rtc_dev = {
.name = "rtc-demo0", // 设备名称
.id = -1, // 设备ID,-1表示自动分配
.dev = {
.release = rtc_device_release,
},
};
static int __init rtc_device_init(void)
{
return platform_device_register(&rtc_dev); // 注册平台设备
}
static void __exit rtc_device_exit(void)
{
platform_device_unregister(&rtc_dev); // 注销平台设备
}
module_init(rtc_device_init);
module_exit(rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
struct rtc_device {
struct cdev *rtc_cdev;
dev_t devno;
};
#define RTC_BASE 0x10017000
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;
static void rtc_time_translate(unsigned long time)
{
tm.hour = (time % 86400) / 3600;
tm.min = (time % 3600) / 60;
tm.sec = time % 60;
}
static unsigned long rtc_tm_to_time(struct rtc_time *t)
{
unsigned long time;
time = t->hour * 3600 + t->min * 60 + t->sec;
return time;
}
static void set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
}
static void set_rtc_alarm(void)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
//tmp = regs->RTCDR; /* get current time */
tmp = rtc_tm_to_time(&tm); /* get alarm time */
regs->RTCMR = tmp;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
struct rtc_time tm;
cur_time = regs->RTCDR;
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
printk("alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
regs->RTCICR = 1; /* clear RTCINTR interrupt */
return IRQ_HANDLED;
}
static struct rtc_device rtc_chrdev;
static int rtc_open(struct inode *inode, struct file *fp)
{
fp->private_data = &rtc_chrdev;
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
struct rtc_device *p = fp->private_data;
printk("char device: %d:%d closed\n", MAJOR(p->devno), MINOR(p->devno));
return 0;
}
static ssize_t rtc_read(struct file *fp, char __user *buf,
size_t size, loff_t *pos)
{
cur_time = regs->RTCDR;
rtc_time_translate(cur_time);
if (unlikely(copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0)){
printk("rtc_read error!\n");
return -1;
}
return sizeof(struct rtc_time);
}
static ssize_t rtc_write(struct file *fp, const char __user *buf,
size_t size, loff_t *pos)
{
int len = 0;
len = sizeof(struct rtc_time);
if (!access_ok(buf, len))
return -1;
if (unlikely(copy_from_user(&tm, buf, len) != 0)) {
printk("rtc_write error!\n");
return -1;
}
cur_time = rtc_tm_to_time(&tm);
regs->RTCLR = cur_time;
return len;
}
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_time __user *buf = (struct rtc_time __user *)arg;
if (_IOC_TYPE(cmd) != RTC_IOC_MAGIC) {
printk("rtc ioctl: invalid cmd!\n");
return -EINVAL;
}
switch (cmd) {
case RTC_SET_TIME:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_time(&tm);
break;
case RTC_SET_ALARM:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_alarm();
break;
default:
printk("error cmd!\n");
return -1;
}
return 0;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.unlocked_ioctl = rtc_ioctl,
.release = rtc_release,
};
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1) {
printk("request_irq failed!\n");
return -1;
}
ret = alloc_chrdev_region(&rtc_chrdev.devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(rtc_chrdev.devno), \
MINOR(rtc_chrdev.devno));
rtc_chrdev.rtc_cdev = cdev_alloc();
cdev_init(rtc_chrdev.rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_chrdev.rtc_cdev, rtc_chrdev.devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
} else {
printk("Register char module: rtc success!\n");
}
return 0;
}
static int rtc_driver_remove(struct platform_device *dev)
{
cdev_del(rtc_chrdev.rtc_cdev);
unregister_chrdev_region(rtc_chrdev.devno, 1);
free_irq(39,NULL);
printk("%s: driver remove: %s\n", __func__, dev->name);
return 0;
}
// 平台驱动ID表
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-demo0",
.driver_data = 0,
},
{
.name = "rtc-demo1",
.driver_data = 1,
},
};
// 平台驱动结构体
static struct platform_driver rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids, // ID表
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.driver = {
.name = "rtc-demo", // 驱动名称
}
};
static int __init rtc_driver_init(void)
{
return platform_driver_register(&rtc_drv); // 注册平台驱动
}
static void __exit rtc_driver_exit(void)
{
platform_driver_unregister(&rtc_drv); // 注销平台驱动
}
module_init(rtc_driver_init);
module_exit(rtc_driver_exit);
MODULE_LICENSE("GPL");
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
#define RTC_SET_TEST _IOW('T', 2, struct rtc_time)
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
int main(void)
{
int fd;
int len, ret;
struct rtc_time tm;
fd = open("/dev/rtc-demo", O_RDWR);
if(fd == -1) {
printf("connot open file..\n");
exit(1);
}
len = sizeof(struct rtc_time);
if ((ret = read(fd, &tm, len)) < len) {
printf("read error!\n");
exit(1);
}
printf("current time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
#if 0
tm.hour = 11;
tm.min = 11;
tm.sec = 11;
ret = write(fd, &tm, len);
if (ret < len) {
printf("write error!\n");
return -1;
}
ret = read(fd, &tm, len);
if (ret < len) {
printf("read error!\n");
return -1;
}
printf("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
#endif
/* set rtc time */
tm.hour = 22;
tm.min = 22;
tm.sec = 22;
ret = ioctl(fd, RTC_SET_TIME, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
if ((ret = read(fd, &tm, len)) < len) {
printf("read failed!\n");
return -1;
}
printf("set time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
/* set rtc alarm */
tm.hour = 22;
tm.min = 22;
tm.sec = 25;
ret = ioctl(fd, RTC_SET_ALARM, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
printf("set alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
close(fd);
return 0;
}
ifneq ($(KERNELRELEASE),)
obj-m := rtc_device.o rtc_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
5. 自动创建设备节点
- 设备模型:热插拔事件 uevent
- mdev 实时监测,解析 uevent,创建设备文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
// 定义一个平台设备
struct platform_device rtc_dev = {
.name = "rtc-demo0", // 设备名称
.id = -1, // 设备ID,-1表示自动分配
.dev = {
.release = rtc_device_release,
},
};
static int __init rtc_device_init(void)
{
return platform_device_register(&rtc_dev); // 注册平台设备
}
static void __exit rtc_device_exit(void)
{
platform_device_unregister(&rtc_dev); // 注销平台设备
}
module_init(rtc_device_init);
module_exit(rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
struct rtc_device {
struct cdev *rtc_cdev;
dev_t devno;
};
#define RTC_BASE 0x10017000
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;
static struct class *rtc_class;
static struct device *rtc_device;
static void rtc_time_translate(unsigned long time)
{
tm.hour = (time % 86400) / 3600;
tm.min = (time % 3600) / 60;
tm.sec = time % 60;
}
static unsigned long rtc_tm_to_time(struct rtc_time *t)
{
unsigned long time;
time = t->hour * 3600 + t->min * 60 + t->sec;
return time;
}
static void set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
}
static void set_rtc_alarm(void)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
//tmp = regs->RTCDR; /* get current time */
tmp = rtc_tm_to_time(&tm); /* get alarm time */
regs->RTCMR = tmp;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
struct rtc_time tm;
cur_time = regs->RTCDR;
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
printk("alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
regs->RTCICR = 1; /* clear RTCINTR interrupt */
return IRQ_HANDLED;
}
static struct rtc_device rtc_chrdev;
static int rtc_open(struct inode *inode, struct file *fp)
{
fp->private_data = &rtc_chrdev;
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
struct rtc_device *p = fp->private_data;
printk("char device: %d:%d closed\n", MAJOR(p->devno), MINOR(p->devno));
return 0;
}
static ssize_t rtc_read(struct file *fp, char __user *buf,
size_t size, loff_t *pos)
{
cur_time = regs->RTCDR;
rtc_time_translate(cur_time);
if (unlikely(copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0)){
printk("rtc_read error!\n");
return -1;
}
return sizeof(struct rtc_time);
}
static ssize_t rtc_write(struct file *fp, const char __user *buf,
size_t size, loff_t *pos)
{
int len = 0;
len = sizeof(struct rtc_time);
if (!access_ok(buf, len))
return -1;
if (unlikely(copy_from_user(&tm, buf, len) != 0)) {
printk("rtc_write error!\n");
return -1;
}
cur_time = rtc_tm_to_time(&tm);
regs->RTCLR = cur_time;
return len;
}
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_time __user *buf = (struct rtc_time __user *)arg;
if (_IOC_TYPE(cmd) != RTC_IOC_MAGIC) {
printk("rtc ioctl: invalid cmd!\n");
return -EINVAL;
}
switch (cmd) {
case RTC_SET_TIME:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_time(&tm);
break;
case RTC_SET_ALARM:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_alarm();
break;
default:
printk("error cmd!\n");
return -1;
}
return 0;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.unlocked_ioctl = rtc_ioctl,
.release = rtc_release,
};
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1) {
printk("request_irq failed!\n");
return -1;
}
ret = alloc_chrdev_region(&rtc_chrdev.devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(rtc_chrdev.devno), \
MINOR(rtc_chrdev.devno));
rtc_chrdev.rtc_cdev = cdev_alloc();
cdev_init(rtc_chrdev.rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_chrdev.rtc_cdev, rtc_chrdev.devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
} else {
printk("Register char module: rtc success!\n");
}
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
rtc_device = device_create(rtc_class, NULL,rtc_chrdev.devno, NULL, \
"rtc-demo%d", 0);
if (IS_ERR(rtc_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
static int rtc_driver_remove(struct platform_device *dev)
{
cdev_del(rtc_chrdev.rtc_cdev);
unregister_chrdev_region(rtc_chrdev.devno, 1);
free_irq(39,NULL);
device_unregister(rtc_device);
class_destroy(rtc_class);
printk("%s: driver remove: %s\n", __func__, dev->name);
return 0;
}
// 平台驱动ID表
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-demo0",
.driver_data = 0,
},
{
.name = "rtc-demo1",
.driver_data = 1,
},
};
// 平台驱动结构体
static struct platform_driver rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids, // ID表
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.driver = {
.name = "rtc-demo", // 驱动名称
}
};
static int __init rtc_driver_init(void)
{
return platform_driver_register(&rtc_drv); // 注册平台驱动
}
static void __exit rtc_driver_exit(void)
{
platform_driver_unregister(&rtc_drv); // 注销平台驱动
}
module_init(rtc_driver_init);
module_exit(rtc_driver_exit);
MODULE_LICENSE("GPL");
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
#define RTC_SET_TEST _IOW('T', 2, struct rtc_time)
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
int main(void)
{
int fd;
int len, ret;
struct rtc_time tm;
fd = open("/dev/rtc-demo0", O_RDWR);
if(fd == -1) {
printf("connot open file..\n");
exit(1);
}
len = sizeof(struct rtc_time);
if ((ret = read(fd, &tm, len)) < len) {
printf("read error!\n");
exit(1);
}
printf("current time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
#if 0
tm.hour = 11;
tm.min = 11;
tm.sec = 11;
ret = write(fd, &tm, len);
if (ret < len) {
printf("write error!\n");
return -1;
}
ret = read(fd, &tm, len);
if (ret < len) {
printf("read error!\n");
return -1;
}
printf("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
#endif
/* set rtc time */
tm.hour = 22;
tm.min = 22;
tm.sec = 22;
ret = ioctl(fd, RTC_SET_TIME, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
if ((ret = read(fd, &tm, len)) < len) {
printf("read failed!\n");
return -1;
}
printf("set time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
/* set rtc alarm */
tm.hour = 22;
tm.min = 22;
tm.sec = 25;
ret = ioctl(fd, RTC_SET_ALARM, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
printf("set alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
close(fd);
return 0;
}
ifneq ($(KERNELRELEASE),)
obj-m := rtc_device.o rtc_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
6. platform resource
- 虚拟总线,ARM SOC 芯片:集成了各种 IP
- 每个 IP 工作依赖平台的各种的资源:时钟、总线、中断、寄存器、IO
- platform_device 定义各种依赖的资源
- platform_driver 获取资源,进行初始化硬件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
// 资源定义,包含内存和中断资源
static struct resource rtc_resource[] = {
[0] = {
.start = 0x10017000, // 内存资源起始地址
.end = 0x10017000 + 4 * 8, // 内存资源结束地址
.flags = IORESOURCE_MEM, // 标记为内存资源
},
[1] = {
.start = 39, // 中断资源编号
.end = 39, // 中断资源结束编号
.flags = IORESOURCE_IRQ, // 标记为中断资源
},
};
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
// 定义一个平台设备
struct platform_device rtc_dev = {
.name = "rtc-demo0", // 设备名称
.id = -1, // 设备ID,-1表示自动分配
.num_resources = 2, // 资源数量
.resource = rtc_resource, // 资源列表
.dev = {
.release = rtc_device_release,
},
};
static int __init rtc_device_init(void)
{
return platform_device_register(&rtc_dev); // 注册平台设备
}
static void __exit rtc_device_exit(void)
{
platform_device_unregister(&rtc_dev); // 注销平台设备
}
module_init(rtc_device_init);
module_exit(rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
struct rtc_device {
struct cdev *rtc_cdev;
dev_t devno;
};
//#define RTC_BASE 0x10017000
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;
static struct class *rtc_class;
static struct device *rtc_device;
static void rtc_time_translate(unsigned long time)
{
tm.hour = (time % 86400) / 3600;
tm.min = (time % 3600) / 60;
tm.sec = time % 60;
}
static unsigned long rtc_tm_to_time(struct rtc_time *t)
{
unsigned long time;
time = t->hour * 3600 + t->min * 60 + t->sec;
return time;
}
static void set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
}
static void set_rtc_alarm(void)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
//tmp = regs->RTCDR; /* get current time */
tmp = rtc_tm_to_time(&tm);
regs->RTCMR = tmp;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
struct rtc_time tm;
cur_time = regs->RTCDR; /* get current time */
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
printk("alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
regs->RTCICR = 1; /* clear RTCINTR interrupt */
return IRQ_HANDLED;
}
static struct rtc_device rtc_chrdev;
static int rtc_open(struct inode *inode, struct file *fp)
{
fp->private_data = &rtc_chrdev;
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
struct rtc_device *p = fp->private_data;
printk("char device: %d:%d closed\n", MAJOR(p->devno), MINOR(p->devno));
return 0;
}
static ssize_t rtc_read(struct file *fp, char __user *buf,
size_t size, loff_t *pos)
{
cur_time = regs->RTCDR;
rtc_time_translate(cur_time);
if (unlikely(copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0)){
printk("rtc_read error!\n");
return -1;
}
return sizeof(struct rtc_time);
}
static ssize_t rtc_write(struct file *fp, const char __user *buf,
size_t size, loff_t *pos)
{
int len = 0;
len = sizeof(struct rtc_time);
if (!access_ok(buf, len))
return -1;
if (unlikely(copy_from_user(&tm, buf, len) != 0)) {
printk("rtc_write error!\n");
return -1;
}
cur_time = rtc_tm_to_time(&tm);
regs->RTCLR = cur_time;
return len;
}
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_time __user *buf = (struct rtc_time __user *)arg;
if (_IOC_TYPE(cmd) != RTC_IOC_MAGIC) {
printk("rtc ioctl: invalid cmd!\n");
return -EINVAL;
}
switch (cmd) {
case RTC_SET_TIME:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_time(&tm);
break;
case RTC_SET_ALARM:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_alarm();
break;
default:
printk("error cmd!\n");
return -1;
}
return 0;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.unlocked_ioctl = rtc_ioctl,
.release = rtc_release,
};
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
struct resource *res_mem, *res_irq;
int device_irq = -1;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
regs = (rtc_reg_t *)ioremap(res_mem->start, res_mem->end - res_mem->start);
device_irq = platform_get_irq(dev, 0);
ret = request_irq(device_irq, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
ret = alloc_chrdev_region(&rtc_chrdev.devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(rtc_chrdev.devno), \
MINOR(rtc_chrdev.devno));
rtc_chrdev.rtc_cdev = cdev_alloc();
cdev_init(rtc_chrdev.rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_chrdev.rtc_cdev, rtc_chrdev.devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
} else {
printk("Register char module: rtc success!\n");
}
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
rtc_device = device_create(rtc_class, NULL,rtc_chrdev.devno, NULL, \
"rtc-demo%d", 0);
if (IS_ERR(rtc_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
static int rtc_driver_remove(struct platform_device *dev)
{
cdev_del(rtc_chrdev.rtc_cdev);
unregister_chrdev_region(rtc_chrdev.devno, 1);
free_irq(39,NULL);
device_unregister(rtc_device);
class_destroy(rtc_class);
printk("%s: driver remove: %s\n", __func__, dev->name);
return 0;
}
// 平台驱动ID表
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-demo0",
.driver_data = 0,
},
{
.name = "rtc-demo1",
.driver_data = 1,
},
};
// 平台驱动结构体
static struct platform_driver rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids, // ID表
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.driver = {
.name = "rtc-demo", // 驱动名称
}
};
static int __init rtc_driver_init(void)
{
return platform_driver_register(&rtc_drv); // 注册平台驱动
}
static void __exit rtc_driver_exit(void)
{
platform_driver_unregister(&rtc_drv); // 注销平台驱动
}
module_init(rtc_driver_init);
module_exit(rtc_driver_exit);
MODULE_LICENSE("GPL");
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
#define RTC_SET_TEST _IOW('T', 2, struct rtc_time)
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
int main(void)
{
int fd;
int len, ret;
struct rtc_time tm;
fd = open("/dev/rtc-demo0", O_RDWR);
if(fd == -1) {
printf("connot open file..\n");
exit(1);
}
len = sizeof(struct rtc_time);
if ((ret = read(fd, &tm, len)) < len) {
printf("read error!\n");
exit(1);
}
printf("current time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
#if 0
tm.hour = 11;
tm.min = 11;
tm.sec = 11;
ret = write(fd, &tm, len);
if (ret < len) {
printf("write error!\n");
return -1;
}
ret = read(fd, &tm, len);
if (ret < len) {
printf("read error!\n");
return -1;
}
printf("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
#endif
/* set rtc time */
tm.hour = 22;
tm.min = 22;
tm.sec = 22;
ret = ioctl(fd, RTC_SET_TIME, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
if ((ret = read(fd, &tm, len)) < len) {
printf("read failed!\n");
return -1;
}
printf("set time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
/* set rtc alarm */
tm.hour = 22;
tm.min = 22;
tm.sec = 25;
ret = ioctl(fd, RTC_SET_ALARM, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
printf("set alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
close(fd);
return 0;
}
ifneq ($(KERNELRELEASE),)
obj-m := rtc_device.o rtc_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
7. 设备类接口的抽象与回调
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
// 资源定义,包含内存和中断资源
static struct resource rtc_resource[] = {
[0] = {
.start = 0x10017000, // 内存资源起始地址
.end = 0x10017000 + 4 * 8, // 内存资源结束地址
.flags = IORESOURCE_MEM, // 标记为内存资源
},
[1] = {
.start = 39, // 中断资源编号
.end = 39, // 中断资源结束编号
.flags = IORESOURCE_IRQ, // 标记为中断资源
},
};
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
// 定义一个平台设备
struct platform_device rtc_dev = {
.name = "rtc-demo0", // 设备名称
.id = -1, // 设备ID,-1表示自动分配
.num_resources = 2, // 资源数量
.resource = rtc_resource, // 资源列表
.dev = {
.release = rtc_device_release,
},
};
static int __init rtc_device_init(void)
{
return platform_device_register(&rtc_dev); // 注册平台设备
}
static void __exit rtc_device_exit(void)
{
platform_device_unregister(&rtc_dev); // 注销平台设备
}
module_init(rtc_device_init);
module_exit(rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
struct rtc_class_operations {
int (*set_time)(struct rtc_time *);
int (*set_alarm)(struct rtc_time *);
};
struct rtc_device {
struct cdev *rtc_cdev;
dev_t devno;
const struct rtc_class_operations *ops;
};
//#define RTC_BASE 0x10017000
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;
static struct class *rtc_class;
static struct device *class_device;
static void rtc_time_translate(unsigned long time)
{
tm.hour = (time % 86400) / 3600;
tm.min = (time % 3600) / 60;
tm.sec = time % 60;
}
static unsigned long rtc_tm_to_time(struct rtc_time *t)
{
unsigned long time;
time = t->hour * 3600 + t->min * 60 + t->sec;
return time;
}
static int set_rtc_time(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->set_time)
ret = rtc->ops->set_time(t);
return ret;
}
static int pl031_set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
return 0;
}
static void set_rtc_alarm(void)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
//tmp = regs->RTCDR; /* get current time */
tmp = rtc_tm_to_time(&tm);
regs->RTCMR = tmp;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
struct rtc_time tm;
cur_time = regs->RTCDR; /* get current time */
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
printk("alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
regs->RTCICR = 1; /* clear RTCINTR interrupt */
return IRQ_HANDLED;
}
static struct rtc_device rtc_dev;
struct rtc_class_operations pl031_rtc_ops = {
.set_time = pl031_set_rtc_time,
};
static int rtc_open(struct inode *inode, struct file *filp)
{
filp->private_data = &rtc_dev;
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
struct rtc_device *p = fp->private_data;
printk("char device: %d:%d closed\n", MAJOR(p->devno), MINOR(p->devno));
return 0;
}
static ssize_t rtc_read(struct file *fp, char __user *buf,
size_t size, loff_t *pos)
{
cur_time = regs->RTCDR;
rtc_time_translate(cur_time);
if (unlikely(copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0)){
printk("rtc_read error!\n");
return -1;
}
return sizeof(struct rtc_time);
}
static ssize_t rtc_write(struct file *fp, const char __user *buf,
size_t size, loff_t *pos)
{
int len = 0;
len = sizeof(struct rtc_time);
if (!access_ok(buf, len))
return -1;
if (unlikely(copy_from_user(&tm, buf, len) != 0)) {
printk("rtc_write error!\n");
return -1;
}
cur_time = rtc_tm_to_time(&tm);
regs->RTCLR = cur_time;
return len;
}
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_device *pdev = file->private_data;
struct rtc_time __user *buf = (struct rtc_time __user *)arg;
if (_IOC_TYPE(cmd) != RTC_IOC_MAGIC) {
printk("rtc ioctl: invalid cmd!\n");
return -EINVAL;
}
switch (cmd) {
case RTC_SET_TIME:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_time(pdev, &tm);
break;
case RTC_SET_ALARM:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_alarm();
break;
default:
printk("error cmd!\n");
return -1;
}
return 0;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.unlocked_ioctl = rtc_ioctl,
.release = rtc_release,
};
static int rtc_driver_probe(struct platform_device *dev)
{
irqreturn_t ret = 0;
struct resource *res_mem;
//struct resource *res_irq;
int device_irq = -1;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
regs = (rtc_reg_t *)ioremap(res_mem->start, res_mem->end - res_mem->start);
device_irq = platform_get_irq(dev, 0);
ret = request_irq(device_irq, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
ret = alloc_chrdev_region(&rtc_dev.devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(rtc_dev.devno), \
MINOR(rtc_dev.devno));
rtc_dev.rtc_cdev = cdev_alloc();
cdev_init(rtc_dev.rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_dev.rtc_cdev, rtc_dev.devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
} else {
printk("Register char module: rtc success!\n");
}
rtc_dev.ops = &pl031_rtc_ops;
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
class_device = device_create(rtc_class, NULL, rtc_dev.devno, NULL, \
"rtc-demo%d", 0);
if (IS_ERR(class_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
static int rtc_driver_remove(struct platform_device *dev)
{
cdev_del(rtc_dev.rtc_cdev);
unregister_chrdev_region(rtc_dev.devno, 1);
free_irq(39,NULL);
device_unregister(class_device);
class_destroy(rtc_class);
printk("%s: driver remove: %s\n", __func__, dev->name);
return 0;
}
// 平台驱动ID表
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-demo0",
.driver_data = 0,
},
{
.name = "rtc-demo1",
.driver_data = 1,
},
};
// 平台驱动结构体
static struct platform_driver rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids, // ID表
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.driver = {
.name = "rtc-demo", // 驱动名称
}
};
static int __init rtc_driver_init(void)
{
return platform_driver_register(&rtc_drv); // 注册平台驱动
}
static void __exit rtc_driver_exit(void)
{
platform_driver_unregister(&rtc_drv); // 注销平台驱动
}
module_init(rtc_driver_init);
module_exit(rtc_driver_exit);
MODULE_LICENSE("GPL");
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
#define RTC_SET_TEST _IOW('T', 2, struct rtc_time)
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
int main(void)
{
int fd;
int len, ret;
struct rtc_time tm;
fd = open("/dev/rtc-demo0", O_RDWR);
if(fd == -1) {
printf("connot open file..\n");
exit(1);
}
len = sizeof(struct rtc_time);
if ((ret = read(fd, &tm, len)) < len) {
printf("read error!\n");
exit(1);
}
printf("current time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
#if 0
tm.hour = 11;
tm.min = 11;
tm.sec = 11;
ret = write(fd, &tm, len);
if (ret < len) {
printf("write error!\n");
return -1;
}
ret = read(fd, &tm, len);
if (ret < len) {
printf("read error!\n");
return -1;
}
printf("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
#endif
/* set rtc time */
tm.hour = 22;
tm.min = 22;
tm.sec = 22;
ret = ioctl(fd, RTC_SET_TIME, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
if ((ret = read(fd, &tm, len)) < len) {
printf("read failed!\n");
return -1;
}
printf("set time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
/* set rtc alarm */
tm.hour = 22;
tm.min = 22;
tm.sec = 25;
ret = ioctl(fd, RTC_SET_ALARM, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
printf("set alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
close(fd);
return 0;
}
ifneq ($(KERNELRELEASE),)
obj-m := rtc_device.o rtc_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
8. 实现自己的 RTC 子系统
- 字符驱动注册功能与具体的寄存器操作分离
- 抽象的 class 接口
#ifndef __RTC_H__
#define __RTC_H__
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
struct rtc_class_operations {
int (*set_time)(struct rtc_time *);
int (*read_time)(struct rtc_time *);
int (*set_alarm)(struct rtc_time *);
int (*read_alarm)(struct rtc_time *);
};
struct rtc_device {
struct cdev *rtc_cdev;
dev_t devno;
int irq;
struct rtc_class_operations *ops;
};
extern int register_rtc_device(struct rtc_class_operations *ops);
extern void unregister_rtc_device(void);
#endif
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include "rtc.h"
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
#define RTC_READ_ALARM _IOR(RTC_IOC_MAGIC, 3, struct rtc_time)
struct rtc_device rtc_dev;
EXPORT_SYMBOL_GPL(rtc_dev);
static int set_rtc_time(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->set_time) {
ret = rtc->ops->set_time(t);
}
return ret;
}
static int read_rtc_time(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->read_time) {
ret = rtc->ops->read_time(t);
}
return ret;
}
static int read_rtc_alarm(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->read_alarm) {
ret = rtc->ops->read_alarm(t);
}
return ret;
}
static int set_rtc_alarm(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->set_alarm)
ret = rtc->ops->set_alarm(t);
return ret;
}
static int rtc_open(struct inode *inode, struct file *filp)
{
filp->private_data = &rtc_dev;
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
return 0;
}
static ssize_t rtc_read(struct file *filp, char __user *buf,
size_t size, loff_t *pos)
{
struct rtc_device *pdev = filp->private_data;
struct rtc_time tm;
read_rtc_time(pdev, &tm);
if (unlikely(copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0)) {
printk("rtc_read error!\n");
return -1;
}
return sizeof(struct rtc_time);
}
static ssize_t rtc_write(struct file *filp, const char __user *buf,
size_t size, loff_t *pos)
{
struct rtc_device *pdev = filp->private_data;
int len = 0;
struct rtc_time tm;
len = sizeof(struct rtc_time);
if (!access_ok(buf, len))
return -1;
set_rtc_time(pdev, &tm);
if (unlikely(copy_from_user(&tm, buf, len) != 0)) {
printk("rtc_write error!\n");
return -1;
}
return len;
}
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_device *pdev = file->private_data;
struct rtc_time __user *buf = (struct rtc_time __user *)arg;
struct rtc_time tm;
if (_IOC_TYPE(cmd) != RTC_IOC_MAGIC) {
printk("rtc ioctl: invalid cmd!\n");
return -EINVAL;
}
switch (cmd) {
case RTC_SET_TIME:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_time(pdev, &tm);
break;
case RTC_SET_ALARM:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_alarm(pdev, &tm);
break;
case RTC_READ_ALARM:
if (_IOC_DIR(cmd) != _IOC_READ) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
read_rtc_alarm(pdev, &tm);
if (unlikely(copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0)) {
printk("rtc_read error!\n");
return -1;
}
break;
default:
printk("error cmd!\n");
return -1;
}
return 0;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.unlocked_ioctl = rtc_ioctl,
.release = rtc_release,
};
static struct class *rtc_class;
static struct device *class_device;
int register_rtc_device(struct rtc_class_operations *rtc_class_ops)
{
int ret = 0;
ret = alloc_chrdev_region(&rtc_dev.devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(rtc_dev.devno), \
MINOR(rtc_dev.devno));
rtc_dev.ops = rtc_class_ops;
rtc_dev.rtc_cdev = cdev_alloc();
cdev_init(rtc_dev.rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_dev.rtc_cdev, rtc_dev.devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
} else {
printk("Register char module: rtc success!\n");
}
class_device = device_create(rtc_class, NULL, rtc_dev.devno, NULL, \
"rtc-demo%d", 0);
if (IS_ERR(class_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
EXPORT_SYMBOL(register_rtc_device);
void unregister_rtc_device(void)
{
cdev_del(rtc_dev.rtc_cdev);
unregister_chrdev_region(rtc_dev.devno, 1);
free_irq(39,NULL);
device_unregister(class_device);
}
EXPORT_SYMBOL(unregister_rtc_device);
static int __init rtc_dev_init(void)
{
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_dev_exit(void)
{
class_destroy(rtc_class);
}
module_init(rtc_dev_init);
module_exit(rtc_dev_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
// 资源定义,包含内存和中断资源
static struct resource rtc_resource[] = {
[0] = {
.start = 0x10017000, // 内存资源起始地址
.end = 0x10017000 + 4 * 8, // 内存资源结束地址
.flags = IORESOURCE_MEM, // 标记为内存资源
},
[1] = {
.start = 39, // 中断资源编号
.end = 39, // 中断资源结束编号
.flags = IORESOURCE_IRQ, // 标记为中断资源
},
};
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
// 定义一个平台设备
struct platform_device pl031_rtc_dev = {
.name = "rtc-demo0", // 设备名称
.id = -1, // 设备ID,-1表示自动分配
.num_resources = 2, // 资源数量
.resource = rtc_resource, // 资源列表
.dev = {
.release = rtc_device_release,
},
};
static int __init pl031_rtc_device_init(void)
{
return platform_device_register(&pl031_rtc_dev);
}
static void __exit pl031_rtc_device_exit(void)
{
platform_device_unregister(&pl031_rtc_dev);
}
module_init(pl031_rtc_device_init);
module_exit(pl031_rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include "rtc.h"
typedef volatile struct {
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
//static struct rtc_time tm;
static void rtc_time_translate(struct rtc_time *tm, unsigned long time)
{
tm->hour = (time % 86400) / 3600;
tm->min = (time % 3600) / 60;
tm->sec = time % 60;
}
static unsigned long rtc_tm_to_time(struct rtc_time *t)
{
unsigned long time;
time = t->hour * 3600 + t->min * 60 + t->sec;
return time;
}
static int pl031_read_rtc_time(struct rtc_time *t)
{
cur_time = regs->RTCDR;
rtc_time_translate(t, cur_time);
return sizeof(struct rtc_time);
}
static int pl031_set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
return 0;
}
static int pl031_read_rtc_alarm(struct rtc_time *t)
{
cur_time = regs->RTCMR;
rtc_time_translate(t, cur_time);
return sizeof(struct rtc_time);
}
static int pl031_set_rtc_alarm(struct rtc_time *t)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
//tmp = regs->RTCDR; /* get current time */
tmp = rtc_tm_to_time(t);
regs->RTCMR = tmp;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
return 0;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
struct rtc_time tm;
cur_time = regs->RTCDR; /* get current time */
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
printk("alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
regs->RTCICR = 1; /* clear RTCINTR interrupt */
return IRQ_HANDLED;
}
struct rtc_class_operations pl031_ops = {
.read_time = pl031_read_rtc_time,
.set_time = pl031_set_rtc_time,
.read_alarm = pl031_read_rtc_alarm,
.set_alarm = pl031_set_rtc_alarm,
};
static int pl031_driver_probe(struct platform_device *dev)
{
struct resource *res_mem;
//struct resource *res_irq;
int dev_irq = -1;
irqreturn_t ret = 0;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
regs = (rtc_reg_t *)ioremap(res_mem->start, res_mem->end - res_mem->start);
//res_irq = platform_get_resource(dev, IORESOURCE_IRQ, 0);
dev_irq = platform_get_irq(dev, 0);
ret = request_irq(dev_irq, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
register_rtc_device(&pl031_ops);
return 0;
}
static int pl031_driver_remove(struct platform_device *dev)
{
unregister_rtc_device();
printk("%s: remove: %s\n", __func__, dev->name);
return 0;
}
// 平台驱动结构体
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-demo0",
.driver_data = 0,
},
{
.name = "rtc-demo1",
.driver_data = 1,
},
{ }
};
static struct platform_driver pl031_rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids,
.probe = pl031_driver_probe,
.remove = pl031_driver_remove,
.driver = {
.name = "rtc-demo",
}
};
static int __init pl031_platform_driver_init(void)
{
return platform_driver_register(&pl031_rtc_drv);
}
static void __exit pl031_platform__driver_exit(void)
{
platform_driver_unregister(&pl031_rtc_drv);
}
module_init(pl031_platform_driver_init);
module_exit(pl031_platform__driver_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m += rtc_core.o
obj-m += pl031_device.o
obj-m += pl031_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
9. 设备的资源管理
- devm_kmalloc、devm_kfree
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
#define RTC_SET_TEST _IOW('T', 2, struct rtc_time)
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
int main(void)
{
int fd;
int len, ret;
struct rtc_time tm;
fd = open("/dev/rtc-demo0", O_RDWR);
if(fd == -1) {
printf("connot open file..\n");
exit(1);
}
len = sizeof(struct rtc_time);
if ((ret = read(fd, &tm, len)) < len) {
printf("read error!\n");
exit(1);
}
printf("current time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
#if 0
tm.hour = 11;
tm.min = 11;
tm.sec = 11;
ret = write(fd, &tm, len);
if (ret < len) {
printf("write error!\n");
return -1;
}
ret = read(fd, &tm, len);
if (ret < len) {
printf("read error!\n");
return -1;
}
printf("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
#endif
/* set rtc time */
tm.hour = 22;
tm.min = 22;
tm.sec = 22;
ret = ioctl(fd, RTC_SET_TIME, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
if ((ret = read(fd, &tm, len)) < len) {
printf("read failed!\n");
return -1;
}
printf("set time %d:%d:%d\n", tm.hour, tm.min, tm.sec);
/* set rtc alarm */
tm.hour = 22;
tm.min = 22;
tm.sec = 25;
ret = ioctl(fd, RTC_SET_ALARM, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
printf("set alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
close(fd);
return 0;
}
#ifndef __RTC_H__
#define __RTC_H__
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
struct rtc_class_operations {
int (*set_time)(struct rtc_time *);
int (*read_time)(struct rtc_time *);
int (*set_alarm)(struct rtc_time *);
int (*read_alarm)(struct rtc_time *);
};
struct rtc_device {
struct cdev *rtc_cdev;
struct device dev;
dev_t devno;
int irq;
struct rtc_class_operations *ops;
};
extern int register_rtc_device(struct rtc_class_operations *ops);
extern void unregister_rtc_device(void);
#endif
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include "rtc.h"
#define RTC_IOC_MAGIC 'R'
#define RTC_SET_TIME _IOW(RTC_IOC_MAGIC, 1, struct rtc_time)
#define RTC_SET_ALARM _IOW(RTC_IOC_MAGIC, 2, struct rtc_time)
#define RTC_READ_ALARM _IOW(RTC_IOC_MAGIC, 3, struct rtc_time)
struct rtc_device *rtc_dev;
static int set_rtc_time(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->set_time) {
ret = rtc->ops->set_time(t);
}
return ret;
}
static int read_rtc_time(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->read_time) {
ret = rtc->ops->read_time(t);
}
return ret;
}
static int read_rtc_alarm(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->read_alarm) {
ret = rtc->ops->read_alarm(t);
}
return ret;
}
static int set_rtc_alarm(struct rtc_device *rtc, struct rtc_time *t)
{
int ret;
if(rtc->ops->set_alarm)
ret = rtc->ops->set_alarm(t);
return ret;
}
static int rtc_open(struct inode *inode, struct file *filp)
{
filp->private_data = rtc_dev;
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
return 0;
}
static ssize_t rtc_read(struct file *filp, char __user *buf,
size_t size, loff_t *pos)
{
struct rtc_device *pdev = filp->private_data;
struct rtc_time tm;
read_rtc_time(pdev, &tm);
if (unlikely(copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0)) {
printk("rtc_read error!\n");
return -1;
}
return sizeof(struct rtc_time);
}
static ssize_t rtc_write(struct file *filp, const char __user *buf,
size_t size, loff_t *pos)
{
struct rtc_device *pdev = filp->private_data;
int len = 0;
struct rtc_time tm;
len = sizeof(struct rtc_time);
if (!access_ok(buf, len))
return -1;
set_rtc_time(pdev, &tm);
if (unlikely(copy_from_user(&tm, buf, len) != 0)) {
printk("rtc_write error!\n");
return -1;
}
return len;
}
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_device *pdev = file->private_data;
struct rtc_time __user *buf = (struct rtc_time __user *)arg;
struct rtc_time tm;
if (_IOC_TYPE(cmd) != RTC_IOC_MAGIC) {
printk("rtc ioctl: invalid cmd!\n");
return -EINVAL;
}
switch (cmd) {
case RTC_SET_TIME:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_time(pdev, &tm);
break;
case RTC_SET_ALARM:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
if (unlikely(copy_from_user(&tm, buf, sizeof(tm)))) {
printk("rtc ioctl: copy_from_user error!\n");
return -EFAULT;
}
set_rtc_alarm(pdev, &tm);
break;
case RTC_READ_ALARM:
if (_IOC_DIR(cmd) != _IOC_READ) {
printk("rtc ioctl: invalid ioclt cmd!\n");
return -EINVAL;
}
read_rtc_alarm(pdev, &tm);
if (unlikely(copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0)) {
printk("rtc_read error!\n");
return -1;
}
break;
default:
printk("error cmd!\n");
return -1;
}
return 0;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.unlocked_ioctl = rtc_ioctl,
.release = rtc_release,
};
static struct class *rtc_class;
static struct device *class_device;
int register_rtc_device(struct rtc_class_operations *rtc_class_ops)
{
int ret = 0;
int devno = -1;
ret = alloc_chrdev_region(&devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
class_device = device_create(rtc_class, NULL, devno, NULL, \
"rtc-demo%d", 0);
if (IS_ERR(class_device)) {
printk("device_create failed!\n");
return -1;
}
// rtc_dev = kmalloc(sizeof(struct rtc_device), GFP_KERNEL);
rtc_dev = devm_kzalloc(class_device, sizeof(struct rtc_device), GFP_KERNEL);
rtc_dev->devno = devno;
rtc_dev->ops = rtc_class_ops;
rtc_dev->rtc_cdev = cdev_alloc();
cdev_init(rtc_dev->rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_dev->rtc_cdev, rtc_dev->devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
} else {
printk("Register char module: rtc success!\n");
}
return 0;
}
EXPORT_SYMBOL(register_rtc_device);
void unregister_rtc_device(void)
{
cdev_del(rtc_dev->rtc_cdev);
unregister_chrdev_region(rtc_dev->devno, 1);
free_irq(39,NULL);
//devm_kfree(class_device, rtc_dev);
device_unregister(class_device);
// kfree(rtc_dev);
}
EXPORT_SYMBOL(unregister_rtc_device);
static int __init rtc_dev_init(void)
{
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_dev_exit(void)
{
class_destroy(rtc_class);
}
module_init(rtc_dev_init);
module_exit(rtc_dev_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
// 资源定义,包含内存和中断资源
static struct resource rtc_resource[] = {
[0] = {
.start = 0x10017000, // 内存资源起始地址
.end = 0x10017000 + 4 * 8, // 内存资源结束地址
.flags = IORESOURCE_MEM, // 标记为内存资源
},
[1] = {
.start = 39, // 中断资源编号
.end = 39, // 中断资源结束编号
.flags = IORESOURCE_IRQ, // 标记为中断资源
},
};
static void rtc_device_release(struct device *dev)
{
printk("%s: %s released...\n", __func__, dev_name(dev));
}
// 定义一个平台设备
struct platform_device pl031_rtc_dev = {
.name = "rtc-demo0", // 设备名称
.id = -1, // 设备ID,-1表示自动分配
.num_resources = 2, // 资源数量
.resource = rtc_resource, // 资源列表
.dev = {
.release = rtc_device_release,
},
};
static int __init pl031_rtc_device_init(void)
{
return platform_device_register(&pl031_rtc_dev);
}
static void __exit pl031_rtc_device_exit(void)
{
platform_device_unregister(&pl031_rtc_dev);
}
module_init(pl031_rtc_device_init);
module_exit(pl031_rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include "rtc.h"
typedef volatile struct {
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
//static struct rtc_time tm;
static void rtc_time_translate(struct rtc_time *tm, unsigned long time)
{
tm->hour = (time % 86400) / 3600;
tm->min = (time % 3600) / 60;
tm->sec = time % 60;
}
static unsigned long rtc_tm_to_time(struct rtc_time *t)
{
unsigned long time;
time = t->hour * 3600 + t->min * 60 + t->sec;
return time;
}
static int pl031_read_rtc_time(struct rtc_time *t)
{
cur_time = regs->RTCDR;
rtc_time_translate(t, cur_time);
return sizeof(struct rtc_time);
}
static int pl031_set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
return 0;
}
static int pl031_read_rtc_alarm(struct rtc_time *t)
{
cur_time = regs->RTCMR;
rtc_time_translate(t, cur_time);
return sizeof(struct rtc_time);
}
static int pl031_set_rtc_alarm(struct rtc_time *t)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
//tmp = regs->RTCDR; /* get current time */
tmp = rtc_tm_to_time(t);
regs->RTCMR = tmp;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
return 0;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
struct rtc_time tm;
cur_time = regs->RTCDR; /* get current time */
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
printk("alarm %d:%d:%d\n", tm.hour, tm.min, tm.sec);
regs->RTCICR = 1; /* clear RTCINTR interrupt */
return IRQ_HANDLED;
}
struct rtc_class_operations pl031_ops = {
.read_time = pl031_read_rtc_time,
.set_time = pl031_set_rtc_time,
.read_alarm = pl031_read_rtc_alarm,
.set_alarm = pl031_set_rtc_alarm,
};
static int pl031_driver_probe(struct platform_device *dev)
{
struct resource *res_mem;
//struct resource *res_irq;
int dev_irq = -1;
irqreturn_t ret = 0;
printk("%s: probe and init hello_device: %s\n", __func__, dev->name);
res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
regs = (rtc_reg_t *)ioremap(res_mem->start, res_mem->end - res_mem->start);
//res_irq = platform_get_resource(dev, IORESOURCE_IRQ, 0);
dev_irq = platform_get_irq(dev, 0);
ret = request_irq(dev_irq, rtc_alarm_handler, 0, "rtc0-test", NULL);
if (ret == -1){
printk("request_irq failed!\n");
return -1;
}
register_rtc_device(&pl031_ops);
return 0;
}
static int pl031_driver_remove(struct platform_device *dev)
{
unregister_rtc_device();
printk("%s: remove: %s\n", __func__, dev->name);
return 0;
}
// 平台驱动结构体
static struct platform_device_id arm_rtc_primcell_driver_ids[] = {
{
.name = "rtc-demo0",
.driver_data = 0,
},
{
.name = "rtc-demo1",
.driver_data = 1,
},
{ }
};
static struct platform_driver pl031_rtc_drv = {
.id_table = arm_rtc_primcell_driver_ids,
.probe = pl031_driver_probe,
.remove = pl031_driver_remove,
.driver = {
.name = "rtc-demo",
}
};
static int __init pl031_platform_driver_init(void)
{
return platform_driver_register(&pl031_rtc_drv);
}
static void __exit pl031_platform__driver_exit(void)
{
platform_driver_unregister(&pl031_rtc_drv);
}
module_init(pl031_platform_driver_init);
module_exit(pl031_platform__driver_exit);
MODULE_LICENSE("GPL");
调用 device_unregister 时释放: