这里写目录标题
io口驱动框架:
我们在编写驱动程序的时候,IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件
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 unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
*GPFSEL0 &= ~(0x6 << 12);
*GPFSEL0 |= (0x1 << 12);
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
char usercmd[12] = {'\0'};
copy_from_user(usercmd,buf,count);
if(!strcmp(usercmd,"1")){
printk("set 1\n");
*GPSET0 |= 0x1 << 4;
}else if(!strcmp(usercmd,"0")){
printk("set 0\n");
*GPCLR0 |= 0x1 << 4;
}else{
printk("usercmd = %s\n",usercmd);
printk("undo\n");
}
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
};
int __init pin4_drv_init(void)
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo");
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 = (volatile unsigned int *)ioremap(0x3f200028,4);
return 0;
}
void __exit pin4_drv_exit(void)
{
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");
GPL v2:开源许可证
C语言关键字static得作用是什么?
①隐藏作用, 可以在不同的文件中定义同名变量和同名函数。
②对于变量来说, 保持变量持久, 静态数据区的变量会在程序刚刚运行时就完成
初始化, 也是唯一一次初始化; 储存在静态数据区, 静态存储区只有两种变量(全
局变量和 static 静态变量) 。
③默认初始化为 0x00,和全局变量一样的属性, 减少程序员的工作量。
file_operations结构体
该结构体下有许多指针函数,open,read,write也在其中
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(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);//异步读操作
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);//写操作
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t,loff_t);//异步写操作
int (*readdir) (struct file *, void *, filldir_t); //读取目录
unsigned int (*poll) (struct file *, struct poll_table_struct *);//查询对一个或多个文件描述符的读或写是否会阻塞
int (*ioctl) (struct inode *, struct file *, unsigned int,unsigned long);//系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写).
int (*mmap) (struct file *, struct vm_area_struct *);//用来请求将设备内存映射到进程的地址空间
int (*open) (struct inode *, struct file *);//打开操作,使用时第一个操作
int (*flush) (struct file *);//操作在进程关闭它的设备文件描述符的拷贝时调用
int (*release) (struct inode *, struct file *);//在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
int (*fsync) (struct file *, struct dentry *, int datasync);//用户调用来刷新任何挂着的数据. 如果这个指针是 NULL, 系统调用返回 -EINVAL.
int (*aio_fsync) (struct kiocb *, int datasync);//这是 fsync 方法的异步版本.
int (*fasync) (int, struct file *, int);//这个操作用来通知设备它的 FASYNC 标志的改变
int (*lock) (struct file *, int, struct file_lock *);//用来实现文件加锁
/*实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作;*/
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long,loff_t *);//读汇聚/散发
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long,loff_t *);//写汇聚/散发
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t,void __user *);//sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个
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);
};
重要函数介绍
(1) 驱动注册
函数: int register_chrdev(int major,char *module_name,struct file_operations * file_operation );
这便是驱动链表注册函数,功能是把我们建立好的结构体插入到链表当中,并注册成字符(chr)设备(dev),块设备(block device)用 register_chrdev。
参数说明:
1. major :驱动的主设备号
2. module_name:这是驱动在链表中的名字
3. *file_operation :file_operations的结构体指针
(2)生成设备号
宏 MKDEV:dev_t MKDEV(int major,int minor);
功能: 把 主设备号 (major) 和 次设备号(minor)整合成设备号并输出。
返回值: 返回一个16位的设备号,高八位为主设备号,第八位为次设备号。
(3)设备类创建
类创建函数: struct class class_create(struct module *owner, const char *name);
功能说明: 生成一个类提供给 device_create以创建相对应的设备模块。
参数说明:
1. *owner: 通常赋值 THIS_MODULE,表示这个驱动会生成一个单独的驱动模块;
2. *name :驱动类的名字。
(4)设备创建函数
设备创建函数: struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, …)
参数说明:
1. *class :设备模块的类;
2. *parent: 通常赋值 NULL;
3. devt :设备号,由MKDEV 获得;
4. *drvdata :设备相关数据,通常赋值NULL;
5. *fmt:设备名称。
上层测试代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
char cmd[12];
fd = open("/dev/pin4",O_RDWR);
if(fd < 0){
printf("open failed!\n");
}else{
printf("open success!\n");
}
printf("input cmd 1/0: 1 high, 0 low\n");
scanf("%s",&cmd);
printf("cmd = %s\n",cmd);
write(fd,cmd,1);
return 0;
}