#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
/* 字符设备数量、名称、以及缓存大小 */
#define CHRDEV_CNT 1
#define CHRDEV_NAME "chrdev"
#define BUF_SIZE 256
#define CHRDEV_MAGIC 'k' //指令类型
#define CHRDEV_MAXNR 3 //最大指令序号
/* ioctl 自定义指令 */
#define CMD_OPEN (_IO(CHRDEV_MAGIC, 1))
#define CMD_CLOSE (_IO(CHRDEV_MAGIC, 2))
#define CMD_SET (_IO(CHRDEV_MAGIC, 3))
/* 自定义字符设备结构体 */
struct chr_dev {
dev_t devnum; //设备号
struct cdev *pcdev; //cdev
struct class *class; //类
struct device *device; //设备
char kbuf[BUF_SIZE]; //数据存储区
};
static chr_dev firdev = {
.devnum = 0,
};
/*
* @brief 文件打开函数
* @param inode : 传递给驱动的inode
file : 要打开的设备文件
* @retval 0 成功, 其他 失败
*/
static int chrdev_open(struct inode *inode, struct file *file)
{
file->private_data = &firdev; //设置私有数据
// firdev.devnum = inode->i_rdev; //获取设备号
printk(KERN_INFO "chrdev_open,devnum:%x\n", firdev.devnum);
return 0;
}
/*
* @brief 文件关闭函数
* @param inode : 传递给驱动的inode
file : 要关闭的设备文件
* @retval 0 成功, 其他 失败
*/
static int chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "chrdev_release\n");
return 0;
}
/*
* @brief 读函数, 将内核中的数据拷贝到应用层
* @param file: 要打开的设备文件
buf : 返回给用户空间的数据缓冲区
cnt : 要读取的数据长度
offt: 相对于文件首地址的偏移
* @retval 读取的字节数, 负值表示读取失败
*/
ssize_t chrdev_read(struct file *filp, char __user *ubuf, size_t size, loff_t *ppos)
{
int ret = -1;
unsigned long p = *ppos;
unsigned int count = size;
struct chr_dev *dev = filp->private_data; //获取私有数据
//有效长度判断
if (p >= BUF_SIZE)
return 0;
if (count > BUF_SIZE - p)
count = BUF_SIZE - p;
//一定要用如下拷贝函数, 从内核空间拷贝至用户
if(copy_to_user(ubuf, (void*)(dev->kbuf + p), count)) {
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
else {
*ppos += count;
ret = count;
printk(KERN_INFO "to user success...\n");
}
return ret;
}
/*
* @brief 写函数, 将应用层传递过来的数据复制到内核中
* @param filp: 打开的文件描述符
buf : 要写给设备写入的数据
cnt : 要写入的数据长度
offt: 相对于文件首地址的偏移
* @retval 写入的字节数, 负值表示写入失败
*/
static ssize_t chrdev_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *ppos)
{
int ret = -1;
unsigned long p = *ppos;
unsigned int count = size;
struct chr_dev *dev = filp->private_data;
//有效长度判断
if(p >= BUF_SIZE)
return 0;
if (count > BUF_SIZE - p)
count = BUF_SIZE - p;
//一定要用如下拷贝函数,从用户空间拷贝至内核
if (copy_from_user(dev->kbuf + p, ubuf, count)) {
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
else {
*ppos += count;
ret = count;
printk(KERN_INFO "from user success...\n");
}
return ret;
}
/*
* @brief 文件定位函数
* @param filp: 打开的文件描述符
whence: 偏移起始位置
offset: 偏移的步数
* @retval 偏移的字节数, 负值表示偏移失败
*/
static loff_t chrdev_llseek(struct file *filp, loff_t whence, int offset)
{
unsigned int newpos = 0;
switch(whence) {
case SEEK_SET: newpos = offset; break;
case SEEK_CUR: newpos = filp->f_pos + offset; break;
case SEEK_END: newpos = BUF_SIZE -1 + offset; break;
default: return -EINVAL;
}
if ((newpos < 0) || (newpos > BUF_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
/*
* @brief IO控制函数
* @param filp: 打开的文件描述符
cmd: 命令
arg: 参数
* @retval 0表示执行成功, 负值表示失败
*/
long chrdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) != CHRDEV_MAGIC)
return -EINVAL;
if (_IOC_NR(cmd) > CHRDEV_MAXNR)
return -EINVAL;
switch(cmd) {
case CMD_OPEN:
printk("IO open device!\n");
return 0;
case CMD_CLOSE:
printk("IO close device!\n");
return 0;
case CMD_SET:
printk("IO setup device, arg:%d\n", arg);
return 0;
default:
return -EINVAL;
}
return 0;
}
//文件操作结构体, 外部操作此模块的接口, 需要我们填充
//.owner:指向拥有这个结构的模块的指针,用来在它的操作还在被使用时阻止模块被卸载
static struct file_operations chrdev_fops = {
.owner = THIS_MODULE,
//应用层间接调用的就是如下接口
.open = chrdev_open, //打开设备时调用
.release = chrdev_release,
.write = chrdev_write,
.read = chrdev_read,
.llseek = chrdev_llseek,
.unlocked_ioctl = chrdev_ioctl,
};
static int template_probe(struct platform_device *pdev)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int ret = 0;
//自动生成设备节点
//分配设备号 并注册进内核
/*
有缺陷, 这个函数分配的设备号会把当前主设备号的所有次设备号都占用
*/
// firdev.devnum = register_chrdev(0, "drvTemplate", &chrdev_fops);
// if(firdev.devnum < 0){//分配失败
// printk(KERN_ERR, "alloc_chrdev_region fail\n");
// goto chrdev_fail:
// }
//新版本设备注册使用 :
//alloc_chrdev_region 申请设备, register_chrdev_region 注册设备
//具体实现如下
/*
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
dev :alloc_chrdev_region函数向内核申请下来的设备号
baseminor :次设备号的起始
count: 申请次设备号的个数
name :执行 cat /proc/devices显示的名称
*/
ret = alloc_chrdev_region(&firdev.devnum, 0, 1, "drvTemplate");
if(IS_ERR(ret)){
printk(KERN_ERR, "alloc_chrdev_region fail\n");
goto chrdev_fail:
}
firdev.pcdev = cdev_alloc();
if(IS_ERR_OR_NULL(firdev.pcdev)){
printk(KERN_ERR, "cdev_alloc fail\n");
goto cdev_alloc_fail:
}
cdev_init(firdev.pcdev, &chrdev_fops);
firdev.pcdev->owner = THIS_MODULE;
ret = cdev_add(firdev.pcdev, firdev.devnum, 1);
if(IS_ERR(ret)){
printk(KERN_ERR, "cdev_add fail\n");
goto cdev_add_fail;
}
// 3.创建类
// 注册字符设备驱动完成后, 添加设备类的操作, 让内核帮我们发信息
// 给udev,让udev自动创建和删除设备文件
firdev.class = class_create(THIS_MODULE, CHRDEV_NAME);
if(IS_ERR(firdev.class)){
goto class_fail;
}
// 4.创建设备
// 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字
// 所以我们这里要的文件名是/dev/firdev
dev = device_create(firdev.class, NULL , firdev.devnum, NULL, CHRDEV_NAME);
if (IS_ERR(dev)) {
goto device_fail;
}
device_fail:
class_destroy(firdev.class);
//class_fail:
//unregister_chrdev(firdev.devnum, "drvTemplate");
cdev_add_fail:
cdev_del(*firdev.pcdev);
cdev_alloc_fail:
unregister_chrdev_region(firdev.devnum, 1);
chrdev_fail:
return -EINVAL;
}
static const struct of_device_id template_tree_node[] = {
{.compatible = "template_demo_dev"}, //用于匹配设备树
{},
};
static struct platform_driver template_driver = {
.probe = template_probe, //与平台设备匹配上之后会调动这个函数
.remove = template_remove, //退出时要释放资源
.driver = {
.name = "template_demo_drv",
.of_match_table = template_tree_node,//匹配设备树中的设备必须要指定这个值
},
};
//模块加载函数
static int __init chrdev_init(void)
{
printk("drvTemplate__init__\n");
int err;
err = platform_driver_register(&template_driver);
return err;
}
static void __exit chrdev_exit(void)
{
printk("drvTemplate__exit__\n");
device_destroy(firdev.class, firdev.devnum);
class_destroy(firdev.class);
//unregister_chrdev(firdev.devnum, "drvTemplate");
cdev_del(*firdev.pcdev);
unregister_chrdev_region(firdev.devnum, 1);
}
//模块加载与卸载时会调用如下接口
module_init(chrdev_init);
module_exit(chrdev_exit);
//下面这些都是跟模块相关, 需要加上才能编译
MODULE_LICENSE("GPL"); // 模块许可证
MODULE_AUTHOR("author"); // 模块作者
MODULE_DESCRIPTION("description"); // 模块信息
MODULE_ALIAS("alias"); // 模块别名
字符设备驱动模板
于 2023-02-27 09:27:47 首次发布