1、常用头文件包含
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/cdev.h> #include <linux/device.h>
2、驱动初始化函数和驱动卸载函数及注册
//初始化和卸载 static int __init newchrled_init(void) static void __exit newchrled_exit(void) //注册 module_init(newchrled_init); module_exit(newchrled_exit); // 版权信息 MODULE_LICENSE("GPL"); MODULE_AUTHOR("zgf");
2.1驱动设备结构体以及实例化
// LED设备结构体 struct newchrled_dev { struct cdev cdev; // 字符设备 struct class *class; // 类 struct device *device; // 设备 dev_t devid; // 设备号 int major; // 主设备号 int minor; // 次设备号 }; struct newchrled_dev newchrled; // led设备
2.2驱动初始化函数和驱动卸载函数
2.2.1地址映射(可选) vir_addr = ioremap(psy_addr,byte_size)
物理地址宏定义格式:#define CCM_CCGR1_BASE (0x020c406c)
虚拟地址定义格式:static void __iomem *IMX6U_CCM_CCGR1;
地址反映射 iounmap(vir_addr)
2.2.2设备初始化
如:寄存器操作,时钟,复用,电气属性,输入输出模式,初始高低电平
对于连续bit的操作可采用读改写方式。使用readl和writel访问虚拟地址映射空间
val = readl(IMX6U_CCM_CCGR1); // 读 val &= ~(3 << 26); // 改 val |= (3 << 26); writel(val, IMX6U_CCM_CCGR1); // 写
2.2.3分配设备号(分给定主设备号和不给定主设备号)(带返回值)
if(newchrled.major) // 给定主设备号 { newchrled.devid = MKDEV(newchrled.major, 0); ret = register_chrdev_region(newchrled.devid, NEWCHRLED_COUNT, NEWCHRLED_NAME); } else // 未给定主设备号 { ret = alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_COUNT,NEWCHRLED_NAME); newchrled.major = MAJOR(newchrled.devid); newchrled.minor = MINOR(newchrled.devid); }
释放设备号 unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);
2.2.4 字符设备结构体cdev
表示一个字符设备 struct cdev cdev; // 字符设备
1、字符设备结构体初始化
cdev_init(&newchrled.cdev, &newchrled_fops);
1.1添加file_operations结构体
static const struct file_operations newchrled_fops = { .owner = THIS_MODULE, .open = newchrled_open, .release = newchrled_release, .write = newchrled_write, };
常用的操作函数原型
static int test_open(struct inode *inode, struct file *filp) //open函数中可添加文件私有数据,如: filp->private_data = &newchrled; static int test_release(struct inode *inode, struct file *filp) static ssize_t test_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) // 添加私有数据后,可以在其他操作中间接操作设备,更加安全。如: struct newchrled_dev *dev = (struct newchrled_dev*)filp->private_data; static ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
2、将字符设备添加到linux系统(带返回值)
ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_COUNT);
3、卸载驱动时,从内核中删除字符设备
cdev_del(&newchrled.cdev);
驱动卸载顺序:
删除字符设备---->释放设备号---->摧毁设备----->摧毁类(暂用)
使用与注册顺序相反的卸载顺序试了一下也能用,但不排除运气成分:
摧毁设备----->摧毁类---->删除字符设备---->释放设备号
2.2.5 自动创建设备节点
1、类创建(带返回值)
newchrled.class = class_create(THIS_MODULE,NEWCHRLED_NAME);
异常处理方法
if(IS_ERR(newchrled.class)) { printk("fail class_creat!\r\n"); ret = PTR_ERR(newchrled.class); goto fail_class; }
2、设备创建(带返回值)
newchrled.device = device_create(newchrled.class,NULL,newchrled.devid,NULL,NEWCHRLED_NAME);
3、设备摧毁
device_destroy(newchrled.class,newchrled.devid);
4、类摧毁
class_destroy(newchrled.class);
对于带返回值的注册函数,中间要穿插异常处理方法,
异常点1,异常点2.......异常点n
处理n, 处理n-1...........处理1
3、测试函数
1、常用的头文件包含
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h>
2、int main(int argc, char *argv[])
argc:参数个数
*argv[] 参数
argv[]参数是字符串形式,需要通过atoi函数转换成int形式,用于判断
测试函数中的
open,close,read,write函数与内核中的.read,.release,.read,.write函数对应,即用户间接调用内核程序
open一个文件会得到文件描述符fd,以后的read,write,close操作均对这个操作符执行表示操作这个文件
fd为负数,-1是不正常的状态。
read,write,close均带有返回值,为负数时不正常的状况,可以输出提示信息,更好定位错误位置。