一、编写驱动流程
①确认主设备号
查询LINUX系统中已经被使用过的主设备号
cat /proc/devices
②编写file_operations结构体
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,
unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
③file_operations结构体中包含驱动入口函数
在函数中注册驱动,注册至chardev[ ]数组中
int __register_chrdev(unsigned int major, 主设备号
const char *name, 模块名
const struct file_operations *fops 结构体)
④自动生成设备和设备文件
设备和设备文件区别:设备包含多个设备文件,都是同一个主设备号,利用次设备号来区分。
class_create(THIS_MODULE,XXX); //相当于创建一个名为XXX的文件夹
class_device_create(创建的设备名, NULL, MKDEV(major_num, 0), NULL, “模块名”);
二、例子
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <asm/io.h>
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin4"; //模块名
//初始化寄存器的地址(volatile作用:确保指令不会因编译器的优化而省略,且每次直接读值)
volatile unsigned int* GPFSEL0=NULL; //GPIO功能选择0
volatile unsigned int* GPSET0=NULL; //GPIO引脚输出设置为0
volatile unsigned int* GPCLR0=NULL; //GPIO引脚输出清除0
//led_read函数
static int pin4_read(struct file *file,char __user *buf, size_t size, loff_t *ppos)
{
printk("pin4_readn");
return 0;
}
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_openn"); //内核的打印函数和printf类似
//配置Pin4引脚为输出引脚(将bit的12-14配置成100)
*GPFSEL0 &=~(0x6<<12); //0x6 0110左移12位,取反后结果为把bit1314配置成0
*GPFSEL0 |=(0x1<<12); //把第12位配置成1
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
int userCmd;
printk("pin4_writen");
//1.获取上层write函数的值,函数:copy_from_user
//copy_from_user函数参数第一个参数是一个char类型的指针const char __user *buf,可用char和int类型
copy_from_user(&userCmd,buf,count);
//2.根据值来操作io口(高电平或低电平)
if(userCmd==1){
printk("set 1n");
*GPSET0 |=0x1<<4; //拉高电平置0(第4个引脚)
}
else if(userCmd==0){
printk("set 0n");
*GPCLR0 |=0x1<<4; //拉高电平清0(第4个引脚)
}
else{
printk("undon");
}
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
int __init pin4_drv_init(void) // 真实驱动入口
{
int ret;
printk("insmod driver pin4 successn");
devno = MKDEV(major,minor); // 2.创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //3.注册驱动 告诉内核,把这个驱动加入到内核的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo"); // 让代码在dev自动生成设备
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
//将物理地址转换成虚拟地址:ioremap(真正的物理地址,映射的大小),io口寄存器映射成普通的内存单元进行访问
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4); //ioremap函数参数:第一个参数真正的物理地址,第二个参数映射的大小
GPSET0=(volatile unsigned int*)ioremap(0x3f20001c,4);
GPCLR0=(volatile unsigned int*)ioremap(0x3f200028,4);
return 0;
}
void __exit pin4_drv_exit(void)
{
//解绑映射iounmap
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0);
device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin4_drv_init); //入口,内核加载驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
注:为什么需要使用虚拟地址???
答:在LINUX系统中,可以同时运行多个程序,会走多个main函数。找到物理地址后会利用ioremap()函数来映射出虚拟地址供程序运行
最后
以上就是背后荔枝最近收集整理的关于Linux驱动—编写流程的全部内容,更多相关Linux驱动—编写流程内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复