我也是在学习过程中,后面会过度到platform总线模型框架,以及DTS框架,最终目的是通过rk3288的I2C,GPIO,interrupt,串口资源,把6轴的驱动搞起来,并且将采集的数据通过终端显示,通过无线网卡远程登录查看。终极目标是通过一段简单的驱动读取IMU的ID,通过脚本对ID进行识别,并自动加载对应的驱动程序。
一.驱动模块基本框架
如果只是想加载一个驱动模块,那么很简单。只需要下面一段代码
。
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h> //为什么这么多头文件?
#include <linux/device.h> //我不是很关心每个头文件都是干吗的,所以我都是从kenerl代码
#include <linux/tty.h> //里面随便找的驱动复制的头文件,多了总比少了好,用就是了。省事儿。
#include <linux/kmod.h>
#include <linux/gfp.h>
static int charDev_init(void) //module 入口函数,返回值为int
{
printk(KERN_ERR "hello guys!!");
return 0;
}
static void charDev_exit(void) //module出口函数,无返回值
{
printk(KERN_ERR "byebye guys!!");
}
module_init(charDev_init); //module 入口
module_exit(charDev_exit); //module 出口
MODULE_LICENSE("GPL"); //GPL协议声明,不然加载不了模块
二.填充file_operations结构体,注册设备。
那么上层app需要对底层的驱动进行操作,该怎么办,这里就需要
填充fileoperations
结构体,并且
申请设备号
,
注册类
,
注册设备节点
操作,函数如下,分别来看。
#include<linux/fs.h>
struct file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
...//其他我不常用的删掉了
};
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
#include<linux/device.h>
#define class_create(owner, name)
void class_destroy(struct class *cls)
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
void class_destroy(struct class *cls)
1.file_operations结构体
1)struct module *owner
一般赋值为THIS_MODULE
2)int (*open)
上层app会先打开设备节点,调用open函数,那么驱动中就会调用这个open函数
3)ssize_t (*read)
上层app open设备节点之后,进行读操作,会调用驱动中read函数,write同理。
2.注册设备号
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
1)
unsigned int
major
注册的主设备号,在分配主设备号之前,最好查看一下系统中有哪些主设备号没有被使用,保险起见,这个参数设置为’0’,系统会自动分配主设备号,并返回主设备号。
2)
const char
*name
驱动的名字,可以通过cat /proc/devices看到,后面这些 mem tty等,就是它的名字,这个名字自己来定。
[root@firefly-rk3288:~/mnt/02_charDev]# cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
3)
const struct file_operations
*fops
这个就是前面填充的file_operations结构体
4)返回值 int
当major为0时,返回主设备号
当major非零时,返回注册成功标志,0为成功,-1为失败。
init里面注册,那么exit的时候就要反注册,就不多说了。
3.注册类
#define class_create(owner, name)
可以看到这个宏定义实际调用的是下面的函数,它有返回值,返回值为一个class类指针。
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
1)owner
THIS_MODULE
2)name
类的名字,通过ls /sys/class可以看到,下面这些都是类的名字
[root@firefly-rk3288:~/mnt/02_charDev]# ls /sys/class/
android_usb/ devfreq-event/ gpio/ ieee80211/ misc/ ptp/ scsi_device/ spi_transport/ usbmon/
backlight/ devfreq/ graphics/ input/ mmc_host/ pwm/ scsi_disk/ spidevx/ vc/
bdi/ dht11/ hevc-service/ iommu/ net/ rc/ scsi_host/ switch/ video4linux/
3)返回值 class*
注册类成功后的class指针,这个指针注册设备节点的时候会用。
消除类也就不多说了。
4.注册设备节点
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, …)
1)
class
*class
这个就是前面类注册返回的类指针
2)
struct device
*parent
NULL
3)
dev_t
devt
这里要提到一个宏MKDEV(major,minor),devt的值就是从这里来,major我们再前面注册设备号的时候已经得到了,minor我们可以自己指定。
4)
void
*drvdata
NULL
5)
const char
*fmt
设备节点的名字,自己定
设备节点的消除也就不多说了,下面是简单的框架代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
int inputPara=0;
int dev_major;
int dev_minor=0;
char *chardev_device_name="charDevice"; //设备驱动名字
struct class *class; //用来承接创建类的返回值
char *chardev_class_name="charClass"; //类名字
char *charDev_node_name="charTest"; //设备节点名字
module_param(inputPara, int, S_IRUSR); //insmod的时候传入的参数,参数名字inputPara,类型int,S_IRUSR是权限
int charDev_open (struct inode *inode, struct file *file){
return 0;
}
ssize_t charDev_read (struct file *file, char __user *buf, size_t size, loff_t *ppos){
return 0;
}
ssize_t charDev_write (struct file *file, const char __user *buf, size_t size, loff_t *ppos){
return 0;
}
static const struct file_operations f_op = { //file_operations结构体
.open = charDev_open,
.read = charDev_read,
.write = charDev_write,
.owner = THIS_MODULE,
};
static int charDev_init(void) //init函数,MODULE_INIT的时候会进行初始化,当前框架里面
{ //注册设备号,注册类,注册设备节点都在这里
dev_major = register_chrdev(0,chardev_device_name,&f_op); //注册设备号,f_op结构体
if(dev_major < 0)
{
printk("can not regist char device!\n");
return dev_major;
}
printk(KERN_ERR "dev_major = %d,chardev device name is %s\n",dev_major,chardev_device_name);
class = class_create(THIS_MODULE, chardev_class_name); //注册类
if(IS_ERR(class))
{
printk("can not create charDev class!");
unregister_chrdev(dev_major,chardev_device_name);
return PTR_ERR(class);
}
printk(KERN_ERR "chardev class name is %s\n",chardev_class_name);
device_create(class, NULL, MKDEV(dev_major,dev_minor), NULL, charDev_node_name); //注册设备节点
printk(KERN_ERR "chardev node create ok!\n");
if(inputPara) //这里是测试参数是否传入,执行insmod devDrv.ko inputPara=整数(等号前后不能有空格)。
{
printk(KERN_ERR "input Param is %d\n",inputPara);
}
return 0;
}
static void charDev_exit(void)
{
device_destroy(class, MKDEV(dev_major,dev_minor));
printk(KERN_ERR "remove chardev node!\n");
class_destroy(class);
printk(KERN_ERR "remove chardev class!\n");
unregister_chrdev(dev_major,chardev_device_name);
printk(KERN_ERR "remove chardev device!\n");
}
module_init(charDev_init);
module_exit(charDev_exit);
MODULE_LICENSE("GPL");