字符设备驱动的框架
设备节点:inode,类型为字符设备,记录设备号
设备号:内核确定驱动的唯一编号
cdev:字符驱动对象
框架代码
驱动
#include <linux/module.h>
#include <linux/file.h>
#include <linux/rtc.h>
static ssize_t rtc_read (struct file *fp, char __user *buf, size_t sz, loff_t *pos)
{
printk("%s\n", __func__);
return 0;
}
static ssize_t rtc_write (struct file *fp, const char __user *buf, size_t sz, loff_t *pos)
{
printk("%s\n", __func__);
return 0;
}
static int rtc_open (struct inode *pinode, struct file *fp)
{
printk("%s\n", __func__);
return 0;
}
static int rtc_release (struct inode *pinode, struct file *fp)
{
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)
{
if (register_chrdev(222, "rtc-demo", &rtc_fops) < 0) {
printk("failed to register_chrdev\n");
return -1;
}
return 0;
}
static void __exit rtc_exit(void)
{
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
测试应用
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct rtc_time {
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
int main()
{
int fd;
struct rtc_time tm;
if ((fd = open("/dev/rtc-demo", O_RDWR)) < 0) {
perror("open");
return -1;
}
if (read(fd, &tm, sizeof(tm)) < sizeof(tm)) {
perror("read");
return -1;
}
printf("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
close(fd);
return 0;
}
运行前构造inode
mknod /dev/rtc-demo c 222 0
register_chrdev 分析
注册设备驱动需要的操作
- 申请设备号
- 构建 cdev 结构
- 将 cdev 加入 kobj 树
open分析
- 根据pathname找到 inode
- 根据inode的i_flag知道是设备文件,并使用 inode->i_rdev找到驱动
- 将cdev->ops复制给 file->f_op,返回file
open后,file->f_op就关联了驱动代码
read分析
可见由于open后,file->f_op 为 cdev->fops 所以,基于文件的操作都通过file->f_op就能调用驱动。
另一种写法
#include <linux/module.h>
#include <linux/file.h>
#include <linux/rtc.h>
static ssize_t rtc_read (struct file *fp, char __user *buf, size_t sz, loff_t *pos)
{
printk("%s\n", __func__);
return 0;
}
static ssize_t rtc_write (struct file *fp, const char __user *buf, size_t sz, loff_t *pos)
{
printk("%s\n", __func__);
return 0;
}
static int rtc_open (struct inode *pinode, struct file *fp)
{
printk("%s\n", __func__);
return 0;
}
static int rtc_release (struct inode *pinode, struct file *fp)
{
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 dev_t dev;
static struct cdev *rtc_cdev;
static int __init rtc_init(void)
{
// 构建cdev
rtc_cdev = cdev_alloc();
cdev_init(rtc_cdev, &rtc_fops);
// 申请设备号
dev = MKDEV(222, 0);
register_chrdev_region(dev, 1, "rtc-demo");
// 将cdev加入kobj树
cdev_add(rtc_cdev, dev, 1);
return 0;
}
static void __exit rtc_exit(void)
{
// 将cdev从kobj树移除
cdev_del(rtc_cdev);
// 释放设备号
unregister_chrdev_region(dev, 1);
}
module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
动态申请设备号
主设备号高12位,次设备号低20位
设备号的转换
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
// 申请设备号
#if 0
dev = MKDEV(222, 0);
register_chrdev_region(dev, 1, "rtc-demo");
#else
alloc_chrdev_region(&dev, 0, 1, "rtc-demo");
printk("major : %d, minor : %d\n", MAJOR(dev), MINOR(dev));
#endif
自动创建设备节点
由于动态申请设备号所以需要自动创建设备节点
udev/mdev : 一个用户空间程序,通过扫描sysfs获得内核编号,用于创建设备节点。此外该程序还依赖 tmpfs。
为了防udev获得驱动的信息,驱动必须将信息导出到sysfs,方法是 class_create , device_create
/**
* class_create - create a struct class structure
* @owner: pointer to the module that is to "own" this struct class
* @name: pointer to a string for the name of this class.
*
* This is used to create a struct class pointer that can then be used
* in calls to device_create().
*
* Returns &struct class pointer on success, or ERR_PTR() on error.
*
* Note, the pointer created here is to be destroyed when finished by
* making a call to class_destroy().
*/
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_groups_vargs(class, parent, devt, drvdata, NULL,
fmt, vargs);
va_end(vargs);
return dev;
}
static const struct file_operations rtc_fops = {
.owner = THIS_MODULE,
.read = rtc_read,
.write = rtc_write,
.open = rtc_open,
.release = rtc_release,
};
static dev_t dev;
static struct cdev *rtc_cdev;
static struct class *rtc_test_class;
static struct device *rtc_device;
static int __init rtc_init(void)
{
// 构建cdev
rtc_cdev = cdev_alloc();
cdev_init(rtc_cdev, &rtc_fops);
// 申请设备号
#if 0
dev = MKDEV(222, 0);
register_chrdev_region(dev, 1, "rtc-demo");
#else
alloc_chrdev_region(&dev, 0, 1, "rtc-demo");
printk("major : %d, minor : %d\n", MAJOR(dev), MINOR(dev));
#endif
// 将cdev加入kobj树
cdev_add(rtc_cdev, dev, 1);
// 在/sys/class下创建rtc-class类
rtc_test_class = class_create(THIS_MODULE, "rtc-class");
if (IS_ERR(rtc_test_class)) {
printk("class_create failed\n");
return -1;
}
// 在/sys/class/rtc-class下创建rtc-demo%d设备
rtc_device = device_create(rtc_test_class, NULL, dev, 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从kobj树移除
cdev_del(rtc_cdev);
// 释放设备号
unregister_chrdev_region(dev, 1);
device_unregister(rtc_device);
class_destroy(rtc_test_class);
}
module_init(rtc_init);
module_exit(rtc_exit);
加载模块后,发现/sys/class已经创建了rtc-class类
yangxr@vexpress:/root # ls /sys/class/rtc-class/
rtc-demo0
yangxr@vexpress:/root # ls /sys/class/rtc-class/rtc-demo0/
dev power subsystem uevent
yangxr@vexpress:/root # cat /sys/class/rtc-class/rtc-demo0/dev
248:0
yangxr@vexpress:/root # cat /sys/class/rtc-class/rtc-demo0/uevent
MAJOR=248
MINOR=0
DEVNAME=rtc-demo0
运行mdev -s ,创建设备节点
yangxr@vexpress:/root # mdev -s
yangxr@vexpress:/root # ls /dev/rtc-demo0
/dev/rtc-demo0
填充fops
需要注意 用户空间和内核空间基于指针输入输出数据时,相关指向用户空间的指针要用 __user 描述。
填充read write
inline static unsigned long rtc_tm_to_time(struct rtc_time_s *ptm)
{
return ptm->hour * 3600 + ptm->min * 60 + ptm->sec;
}
static ssize_t rtc_write (struct file *fp, const char __user *buf, size_t sz, loff_t *pos)
{
struct rtc_time_s tm;
int len = sizeof(tm);
if ( !access_ok(buf, len))
return -1;
if (copy_from_user(&tm, buf, len) != 0) {
printk("rtc_write failed\n");
return -1;
}
regs->RTCLR = rtc_tm_to_time(&tm);
return len;
}
static void rtc_time_s_trans(unsigned long n, struct rtc_time_s *p)
{
p->hour = (n % 86400) / 3600;
p->min = (n % 3600) / 60;
p->sec = (n % 60);
}
static ssize_t rtc_read (struct file *fp, char __user *buf, size_t sz, loff_t *pos)
{
unsigned long cur_time = regs->RTCDR;
struct rtc_time_s tm;
int len = sizeof(tm);
rtc_time_s_trans(cur_time, &tm);
if (copy_to_user(buf, &tm, len) != 0) {
printk("rtc_read error\n");
return -1;
}
return len;
}
ioctl
struct file_operations {
...
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); // 需要兼容不同位宽时使用
};
ioctl 对应两种回调,常用 unlocked_ioctl
传参都为
long (*unlocked_ioctl) (struct file *fp, unsigned int cmd, unsigned long arg);
其中cmd使用ioctl的宏设置
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
比如定义设置RTC时间的命令,和读取RTC时间的命令
#define RTC_CMD_MAGIC 'R'
#define RTC_CMD_GET _IOR(RTC_CMD_MAGIC, 0, struct rtc_time_s *)
#define RTC_CMD_SET _IOW(RTC_CMD_MAGIC, 1, struct rtc_time_s *)
ioctl实现
static long rtc_ioctl (struct file *fp, unsigned int cmd, unsigned long arg)
{
struct rtc_time_s __user *buf = (struct rtc_time_s __user *)arg;
unsigned long cur_time;
struct rtc_time_s tm;
switch (cmd) {
case RTC_CMD_GET:
cur_time = regs->RTCDR;
rtc_time_s_trans(cur_time, &tm);
if (copy_to_user(buf, &tm, sizeof(tm)) != 0) {
return -1;
}
break;
case RTC_CMD_SET:
if (copy_from_user(&tm, buf, sizeof(tm)) != 0) {
return -1;
}
cur_time = rtc_tm_to_time(&tm);
regs->RTCLR = cur_time;
break;
default:
return -1;
}
return 0;
}
private_data
struct file {
...
/* needed for tty driver, and maybe others */
void *private_data;
};
fp->private_data可用于实现驱动会话。
如在 open 操作时分配会话上下文,挂在 private_data上,其他操作调用时 fp->private_data 可以获得当前用户的会话信息。
提高驱动的安全性
为了避免用户非法输入导致内核挂掉,需要先检查用户数据。
常见的检查方法:
检查ioctl命令
_IOC_TYPE(cmd) 判断命令type是否合法
_IOC_DIR(cmd) 判断命令是读还是写
检查用户内存地址是否合法
access_ok(addr, sz) 判断用户传递的内存是否合法
返回值:1 成功,0 失败
有些函数内部自带检测:copy_from_user, copy_to_user, get_user, put_user
分支预测优化:likely, unlikely
比如
if (copy_from_user(&tm, buf, len) != 0) { // 由于这里出错可能性低
printk("rtc_write failed\n"); // 所以这里不应该先缓存
return -1;
}
regs->RTCLR = rtc_tm_to_time(&tm); // 应该先缓存这里
使用 unlikely 告诉编译器为真的分支大概率不会执行,于是cache缓存时会跳过那部分指令
if (unlikely(copy_from_user(&tm, buf, len) != 0)) {
printk("rtc_write failed\n");
return -1;
}
regs->RTCLR = rtc_tm_to_time(&tm);
制作库
由于直接基于文件系统导出的驱动接口不能直观描述接口功能,所以还需要做一个库,库直接操作驱动接口,用户使用库实现应用。
如 rtc的库可以这样声明
#ifndef __RTCLIB_H
#define __RTCLIB_H
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct rtc_time {
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
int rtc_open(const char *pathname);
int rtc_close(int fd);
int rtc_set_time(int fd, struct rtc_time *ptm);
int rtc_get_time(int fd, struct rtc_time *ptm);
#endif
标签:__,kernel,struct,rtc,字符,int,dev,驱动,class
From: https://www.cnblogs.com/yangxinrui/p/16859312.html