---- 整理自 王利涛老师 课程
实验环境:宅学部落 www.zhaixue.cc
文章目录
1. 字符设备驱动开发框架
- 实验:00rtc
// rtc.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
static int rtc_open(struct inode *inode, struct file *fp)
{
printk("%s...\n", __func__);
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
printk("%s...\n", __func__);
return 0;
}
static ssize_t rtc_read(struct file *fp, char __user *buf,
size_t size, loff_t *pos)
{
printk("%s...\n", __func__);
return 0;
}
static ssize_t rtc_write(struct file *fp, const char __user *buf,
size_t size, loff_t *pos)
{
printk("%s...\n", __func__);
return 0;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.release = rtc_release,
};
static int __init rtc_init(void)
{
int ret = 0;
ret = register_chrdev(222, "rtc-demo", &rtc_fops);
if (ret < 0) {
printk("Register char module: rtc failed..\n");
return 0;
}
else {
printk("Register char module: rtc success!\n");
}
return 0;
}
static void __exit rtc_exit(void)
{
printk("Goodbye char module: rtc!\n");
unregister_chrdev(222,"rtc-demo");
}
module_init(rtc_init);
module_exit(rtc_exit);
ifneq ($(KERNELRELEASE),)
obj-m := rtc.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
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
close(fd);
return 0;
}
2. 编写第一个字符驱动:RTC
- 实验:01rtc
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.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
static volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;
static void rtc_time_translate(void)
{
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
}
static int rtc_open(struct inode *inode, struct file *fp)
{
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
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();
if (copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0){
printk("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 count = 0;
return count;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.release = rtc_release,
};
static int __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
ret = register_chrdev(222, "rtc-demo", &rtc_fops);
if (ret < 0) {
printk("Register char module: rtc failed..\n");
return 0;
}
else {
printk("Register char module: rtc success!\n");
}
return 0;
}
static void __exit rtc_exit(void)
{
printk("Goodbye char module: rtc!\n");
unregister_chrdev(222,"rtc-demo");
}
module_init(rtc_init);
module_exit(rtc_exit);
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
close(fd);
return 0;
}
3. 字符设备的注册过程
- register_chrdev() 注册流程
- 申请设备号:__register_chrdev_region
- 创建 cdev 对象:cdev_alloc
- 初始化 cdev 对象:cdev->ops = fops
- 将 cdev 添加到驱动模型中:cdev_add,kboj、kset
- 将 cdev 和 chrdevs 数组中的 char_device_struct 建立关联:cd->cdev = cdev
使用 cdev 结构体 来描述一个字符设备
- 内核给出的操作 struct cdev 结构的接口主要有以下几个:
- cdev_init,该函数主要对 struct cdev 结构体做初始化, 最重要的就是建立 cdev 和 file_operations 之间的连接。
- cdev_alloc,该函数主要分配一个 struct cdev 结构,动态申请一个 cdev 内存,并做了 cdev_init 中所做的前面 3 步初始化工作(第 4 步初始化工作需要在调用 cdev_alloc 后,显式的做初始化,即:.ops=xxx_ops)。
- cdev_add,该函数向内核注册一个 struct cdev 结构,即正式通知内核由 struct cdev *p 代表的字符设备已经可以使用了。
- cdev_del,该函数向内核注销一个 struct cdev 结构,即正式通知内核由 struct cdev *p 代表的字符设备已经不可以使用了。
4. 字符设备的 open 和 read 过程
mknod
:每创建一个文件,文件系统就会分配一个 inode,文件与 inode 一一对应
// kernel\linux-5.10.4\include\linux\fs.h
struct inode { // 内核中所有文件都使用inode来表示
umode_t i_mode;
unsigned short i_opflags;
kuid_t i_uid; // inode拥有者ID
kgid_t i_gid; // inode所属群组ID
unsigned int i_flags; // 文件类型
...
dev_t i_rdev; // 若是设备文件,表示设备号
...
struct list_head i_devices; // 多个设备文件可以共用同一个驱动程序,通过字符设备的inode中的i_devices和cdev中的list组成一个链表
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev; // <== 这里
char *i_link;
unsigned i_dir_seq;
};
...
} __randomize_layout;
open("dev/rtc-demo0", O_RDWR);
- 通过系统调用,调用 sys_open,根据设备名对应的 inode 信息 i_flags 找到设备类型,创建 file
- 根据设备号 (inode->i_rdev) 查找 cdev:kboj_lookup
- 保存找到的 cdev:inode->i_cdev = cdev
- 将 inode 添加到 cdev->list 链表中
- 用 cdev 中的 ops 初始化 file:file->f_op = cdev->ops
- 调用 open 回调函数:file->f_op->open(inode, filp)
- open 执行完成,通过 VFS 返回给用户空间一个文件描述符:fd
fd 和内核中的 file 相对应,上层通过 fd 就可以找到 file,找到对应的 read、write 函数
5. 使用 cdev 接口编写字符驱动
- 实验:02rtc
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.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
static volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;
static void rtc_time_translate(void)
{
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
}
static dev_t dev;
static struct cdev *rtc_cdev;
static int rtc_open(struct inode *inode, struct file *fp)
{
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
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();
if (copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0){
printk("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 count = 0;
return count;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.release = rtc_release,
};
static int __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
dev = MKDEV(222, 0); // <==这里,通过major和minor构建设备号,MKDEV(int major,int minor);
rtc_cdev = cdev_alloc(); // <==这里
cdev_init(rtc_cdev, &rtc_fops); // <==这里
register_chrdev_region(dev, 1, "rtc-demo"); // <==这里,静态申请设备号
ret = cdev_add(rtc_cdev, dev, 1); // <==这里
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
}
else {
printk("Register char module: rtc success!\n");
}
return 0;
}
static void __exit rtc_exit(void)
{
cdev_del(rtc_cdev); // <==这里
unregister_chrdev_region(dev, 1);
printk("Goodbye char module: rtc!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
6. 静态申请和动态申请设备号
6.1 构建设备号
- 每个设备都有设备号:主设备号(12位)+ 次设备号(20)=(32位)
一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
- 从设备号中提取 major 和 minor
MAJOR(dev);
MINOR(dev);
- 通过 major 和 minor 构建设备号
MKDEV(ma, mi);
注:这只是构建设备号。并未注册,需要调用 register_chrdev_region 静态申请(前面实验中)。
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
6.2 分配设备号
6.2.1 静态申请
6.2.2 动态分配
6.2.3 分析
- 可以看到静态申请和动态分配二者都是调用了
__register_chrdev_region
函数:
register_chrdev_region
和alloc_chrdev_region
的区别,register_chrdev_region 直接将 Major 注册进入,而 alloc_chrdev_region 从 Major = 0 开始,逐个查找设备号,直到找到一个闲置的设备号,并将其注册进去。
register_chrdev_region | alloc_chrdev_region | register_chrdev |
devno = MKDEV(major,minor); ret = register_chrdev_region(devno, 1, "xxx"); cdev_init(&cdev,&xxx_ops); ret = cdev_add(&cdev,devno,1); | alloc_chrdev_region(&devno, minor, 1, "xxx"); major = MAJOR(devno); cdev_init(&cdev,&xxx_ops); ret = cdev_add(&cdev,devno,1) |
6.3 实验
- 实验:03rtc
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.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
static volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;
static void rtc_time_translate(void)
{
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
}
static dev_t devno; // <==这里
static struct cdev *rtc_cdev; // <==这里
static int rtc_open(struct inode *inode, struct file *fp)
{
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
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();
if (copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0){
printk("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 count = 0;
return count;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.release = rtc_release,
};
static int __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
ret = alloc_chrdev_region(&devno, 0, 1, "rtc-demo"); // <==这里。更简便、更智能的方法是让内核给我们自动分配一个主设备号,
// 使用alloc_chrdev_region就可以自动分配了。
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(devno), MINOR(devno));
rtc_cdev = cdev_alloc(); // <==这里
cdev_init(rtc_cdev, &rtc_fops); // <==这里
ret = cdev_add(rtc_cdev, devno, 1); // <==这里
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
}
else {
printk("Register char module: rtc success!\n");
}
return 0;
}
static void __exit rtc_exit(void)
{
cdev_del(rtc_cdev); // <==这里
unregister_chrdev_region(devno, 1);
printk("Goodbye char module: rtc!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
7. 自动创建设备结点
alloc_chrdev_region
:动态设备号,返回的设备号不一定是固定的- udev:运行在用户空间,用来 自动创建 设备节点:
/dev/xxx
。内核中定义的struct class
结构体,顾名思义,一个 struct class 结构体类型变量对应一个类,内核同时提供了class_create(…)
函数,可以用它来创建一个类,这个类存放于 sysfs 下面,一旦创建好了这个类,再调用 device_create(…) 函数来在 /dev 目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的 udev 会自动响应 device_create() 函数,去 /sysfs 下寻找对应的类从而创建设备节点。 - mdev:busybox
- 内核:class_create、device_create(在驱动初始化代码里调用 class_create 为该设备创建一个 class,再为每个设备调用 device_create 创建对应的设备)
- 实验:04
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.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
static 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(void)
{
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
}
static dev_t devno;
static struct cdev *rtc_cdev;
static int rtc_open(struct inode *inode, struct file *fp)
{
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
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();
if (copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0){
printk("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 count = 0;
return count;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.release = rtc_release,
};
static int __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
ret = alloc_chrdev_region(&devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(devno), MINOR(devno));
rtc_cdev = cdev_alloc();
cdev_init(rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_cdev, devno, 1);
if (ret < 0) {
printk("cdev_add failed..\n");
return -1;
}
else {
printk("Register char module: rtc success!\n");
}
// 宏class_create()用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。
// 此函数的执行效果就是在/sys/class/目录下创建一个新的文件夹rtc-class,此文件夹的名字为此函数的第二个输入参数rtc-class
rtc_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_class)) {
printk("class_create failed!\n");
return -1;
}
// device_create会在/dev目录下生成rtc-demo0的设备文件
rtc_device = device_create(rtc_class, NULL, devno, NULL, "rtc-demo%d", 0);
if (IS_ERR(rtc_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
cdev_del(rtc_cdev);
unregister_chrdev_region(devno, 1);
device_unregister(rtc_device);
class_destroy(rtc_class);
printk("Goodbye char module: rtc!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL"); // 需要加LICENSE
MODULE_AUTHOR("uuuuu");
mdev -s
:在系统启动时,通过执行“mdev -s
”扫描/sys/class
和/sys/block
,在目录中查找 dev 文件。例如:/sys/class/rtc-class/rtc-demo0/dev
,它的内容为 “250:0”,即主设备号是 250,次设备号是 0,dev 的上一级目录为设备名,这里是 rtc-demo0。/sys/class 下的每个文件夹都代表着一个子系统。mdev -d
:自动扫描/sys/class
和/sys/block
注:mdev 是 busybox 提供的一个工具,用在嵌入式系统中,相当于 简化版的 udev,作用是在系统启动和热插拔或动态加载驱动程序时,自动创建设备节点。文件系统中的 /dev 目录下的设备节点都是由 mdev 创建的。- 补充:我们可以修改系统的启动脚本
rcS
,添加mdev -d
。系统启动后,不需要手动执行 mdev -s,就可以在 /dev 目录下创建设备节点 rtc-demo0。
这样,我们不再需要自己手动创建节点。
8. 实现字符驱动接口:write
- 实验:05rtc
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
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);
close(fd);
return 0;
}
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.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
static volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm; // <==here
static struct class *rtc_class;
static struct device *rtc_device;
static void rtc_time_translate(void)
{
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
}
static void rtc_tm_to_time(void)
{
cur_time = tm.hour * 3600 + tm.min * 60 + tm.sec;
}
static dev_t devno;
static struct cdev *rtc_cdev;
static int rtc_open(struct inode *inode, struct file *fp)
{
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
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();
if (copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0){ // <==here
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 (copy_from_user(&tm, buf, len) != 0) { // <==here
printk("rtc_write error!\n");
return -1;
}
rtc_tm_to_time();
regs->RTCLR = cur_time;
return len;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.release = rtc_release,
};
static int __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
ret = alloc_chrdev_region(&devno, 0, 1, "rtc-demo");
if (ret) {
printk("alloc char device number failed!\n");
return ret;
}
printk("RTC devnum:%d minornum:%d\n", MAJOR(devno), MINOR(devno));
rtc_cdev = cdev_alloc();
cdev_init(rtc_cdev, &rtc_fops);
ret = cdev_add(rtc_cdev, 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, devno, NULL, "rtc-demo%d", 0);
if (IS_ERR(rtc_device)) {
printk("device_create failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
cdev_del(rtc_cdev);
class_destroy(rtc_class);
unregister_chrdev_region(devno, 1);
printk("Goodbye char module: rtc!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuuuu");
9. 设备驱动的私有数据:private_data
注:当驱动复杂时,不方便访问如 rtc_chrdev 这个结构体,可以通过 private_data
这种方法。
- 实验:06rtc
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.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_char_device {
struct cdev *rtc_cdev;
dev_t devno;
}; // <==here
#define RTC_BASE 0x10017000
static 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(void)
{
tm.hour = (cur_time % 86400) / 3600;
tm.min = (cur_time % 3600) / 60;
tm.sec = cur_time % 60;
}
static void rtc_tm_to_time(void)
{
cur_time = tm.hour * 3600 + tm.min * 60 + tm.sec;
}
static struct rtc_char_device rtc_chrdev; // <==here
static int rtc_open(struct inode *inode, struct file *fp)
{
fp->private_data = &rtc_chrdev; // 在 Linux 内核中,file 结构体用于表示一个打开的文件,其中包含了对该文件的各种操作和状态信息。
// 当打开一个字符设备时,系统会创建一个 file 结构体来表示这个打开的设备文件。
// private_data 是 file 结构体中的一个字段,它可以用来存储用户定义的数据指针。
return 0;
}
static int rtc_release(struct inode *inode, struct file *fp)
{
struct rtc_char_device *p = fp->private_data; // <==here
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();
if (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 (copy_from_user(&tm, buf, len) != 0) {
printk("rtc_write error!\n");
return -1;
}
rtc_tm_to_time();
regs->RTCLR = cur_time;
return len;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.release = rtc_release,
};
static int __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
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 void __exit rtc_exit(void)
{
cdev_del(rtc_chrdev.rtc_cdev);
unregister_chrdev_region(rtc_chrdev.devno, 1);
device_unregister(rtc_device);
class_destroy(rtc_class);
printk("Goodbye char module: rtc!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuuuu");
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
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);
close(fd);
return 0;
}
10. 实现字符驱动接口:ioctl
- 实验:07rtc
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include <sys/ioctl.h>
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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
tm.hour = 22;
tm.min = 22;
tm.sec = 22;
ret = ioctl(fd, 1, (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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
ret = ioctl(fd, 2, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
ret = ioctl(fd, 3, (unsigned long)&tm);
if (ret) {
printf("ioctl failed!\n");
return -1;
}
close(fd);
return 0;
}
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.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_char_device {
struct cdev *rtc_cdev;
dev_t devno;
};
#define RTC_BASE 0x10017000
static 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 struct rtc_char_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_char_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 (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 (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 void set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
}
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case 1:
printk("cmd = 1\n");
if (copy_from_user(&tm, (struct rtc_time __user *)arg, sizeof(tm)))
return -EFAULT;
set_rtc_time(&tm);
break;
case 2:
printk("cmd = 2\n");
break;
case 3:
printk("cmd = 3\n");
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 __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
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 void __exit rtc_exit(void)
{
cdev_del(rtc_chrdev.rtc_cdev);
unregister_chrdev_region(rtc_chrdev.devno, 1);
device_unregister(rtc_device);
class_destroy(rtc_class);
printk("Goodbye char module: rtc!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuuuu");
- 遗留问题:为什么 cmd=1 和 cmd=3 打印出来了,cmd=2 没有打印出来?
- 需要命令编码规范
11. ioctl 命令编码规范
+------+------+------+---------+
| 8bit | 8bit | 2bit | 8~14bit |
+------+------+------+---------+
| type | nr | dir | size |
+------+------+------+---------+
cmd 命令的宏:
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOC_TYPECHECK(size) sizeof(size)
- 实验:08rtc
#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)
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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
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;
}
close(fd);
return 0;
}
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/interrupt.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_char_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)
static 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 struct rtc_char_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_char_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 (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 (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 void set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
}
static void set_rtc_alarm(struct rtc_time *t)
{
unsigned long tmp = 0;
tmp = regs->RTCCR;
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = rtc_tm_to_time(t);
regs->RTCMR = tmp;
regs->RTCICR = 1;
regs->RTCIMSC = 1;
tmp = regs->RTCCR;
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_time __user *buf = (struct rtc_time __user *)arg;
switch (cmd) {
case RTC_SET_TIME:
printk("cmd = 1\n");
if (copy_from_user(&tm, buf, sizeof(tm)))
return -EFAULT;
set_rtc_time(&tm);
break;
case RTC_SET_ALARM:
printk("cmd = 2\n");
if (copy_from_user(&tm, buf, sizeof(tm)))
return -EFAULT;
set_rtc_alarm(&tm);
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 irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
cur_time = regs->RTCDR;
rtc_time_translate(cur_time);
printk("\nalarm: beep~ beep~ \n");
printk("\n %d:%d:%d\n", tm.hour, tm.min, tm.sec);
regs->RTCICR = 1;
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
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;
}
ret = request_irq(39, rtc_alarm_handler, 0, "rtc-test", NULL);
if (ret == -1) {
printk("request_irq failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
cdev_del(rtc_chrdev.rtc_cdev);
unregister_chrdev_region(rtc_chrdev.devno, 1);
device_unregister(rtc_device);
class_destroy(rtc_class);
printk("Goodbye char module: rtc!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuuuu");
12. 让你的驱动更加稳定高效
- 检查ioctl命令
_IOC_TYPE(cmd):判断应用程序传下来的命令 type 是否正确
_IOC_DIR(cmd):判断命令是读还是写 - 检查用户层传递的内存地址是否合法
access_ok(addr, size):判断用户层传递的内存地址是否合法
返回值:1 - 成功,0 - 失败
有些函数内部自带检测:copy_from_user,copy_to_user,get_user,put_user
有些没有,需要用户自己检测:__get_user,__put_user - 分支预测优化:likely,unlikely
CPU cache 和流水线结构 - 实验:09rtc
#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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
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("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
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;
}
/* ioctl invalid cmd test */
ret = ioctl(fd, RTC_SET_TEST, (unsigned long)&tm);
if (ret) {
printf("main: ioctl failed!\n");
return -1;
}
close(fd);
return 0;
}
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/interrupt.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_char_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)
static 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 struct rtc_char_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_char_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 void set_rtc_time(struct rtc_time *t)
{
cur_time = rtc_tm_to_time(t);
regs->RTCLR = cur_time;
}
static void set_rtc_alarm(struct rtc_time *t)
{
unsigned long tmp = 0;
tmp = regs->RTCCR;
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = rtc_tm_to_time(t);
regs->RTCMR = tmp;
regs->RTCICR = 1;
regs->RTCIMSC = 1;
tmp = regs->RTCCR;
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
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(&tm);
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 irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
cur_time = regs->RTCDR;
rtc_time_translate(cur_time);
printk("\nalarm: beep~ beep~ \n");
printk("\n %d:%d:%d\n", tm.hour, tm.min, tm.sec);
regs->RTCICR = 1;
return IRQ_HANDLED;
}
static int __init rtc_init(void)
{
int ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
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;
}
ret = request_irq(39, rtc_alarm_handler, 0, "rtc-test", NULL);
if (ret == -1) {
printk("request_irq failed!\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
cdev_del(rtc_chrdev.rtc_cdev);
unregister_chrdev_region(rtc_chrdev.devno, 1);
device_unregister(rtc_device);
class_destroy(rtc_class);
printk("Goodbye char module: rtc!\n");
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuuuu");