字符设备驱动学习(1)

时间:2021-10-21 06:21:49

首先,以查询方式的按键驱动开始字符设备驱动的学习。
目的:按键驱动查询方式获取按键值:
1、写出驱动框架
2、硬件操作,相关实现。

一.写出框架:对于驱动的学习,框架思想非常重要
1.1.file_operation: file_operation 结构是一个字符驱动如何应用程序建立连接. 这个结构, 定义在 linux/fs.h中, 是一个函数指针的集合. 每个打开文件与它自身的函数集合相关连( 通过包含一个称为 f_op 的成员, 它指向一个 file_operations 结构). 这些操作大部分负责实现系统调用。

1.2.相关初始化,注册函数、卸载函数
1、首先。要定义cdev结构体:内核在内部使用类型 struct cdev 的结构来代表字符设备. 在内核调用你的设备操作前, 你编写分配并注册一个或几个这些结构.
并定义主设备号、以及dev_t类型的设备号变量。dev_t类型设备号通过MKDEV宏获得。

2、对于设备号可以根据主设备号进行静态申请设备号(register_chrdev_region())和动态申请设备号(alloc_chrdev_region()主设备号为零时动态申请)

3、设备号申请完成后,将cdev初始化并与file_operation关联起来,在将我们定义的cdev这个结构体加入内核中cdev链表(在内核里,有cdev类型的元素的链表,使用cdev与物理设备及其驱动进行绑定,我们在用户空间对设备文件操作,因为设备文件含有主次设备号,就能找到cdev链表中找到与这个设备文件相关的cdev结构体,进而可以使用与它绑定的file_operation中的驱动程序去操作物理设备)。

4、卸载函数:注销字符设备 cdev_del( &cdev );
注销设备号 unregister_chrdev_region( dev_t num, 1 );因为设备号有限,卸载驱动时需要注销设备号。

5、对于以上的初始化、卸载函数现在系统还识别不了,需要告知操作系统
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE(“Dual BSD/GPL”);//开源协议

6、为了让加载驱动模块是,系统自己创建设备文件,就需要给内核sysfs(一个虚拟的文件系统)提供信息,udev机制,可以自动创建设备结点,能自动创建的前提是根文件系统有挂载sysfs虚拟文件系统,利用mdev/udev机制,根据/sys中的系统信息自动创建设备节点。方法:创建一个class,然后再class下创建一个设备class device (具体实现看代码)。

二、硬件操作实现

1、看原理图:确定相关使用的引脚
2、看手册:相关寄存器
3、编程实现功能(在有系统的情况下使用的是虚拟地址:ioremap()在入口函数里进行地址映射,在init函数中实现)
4、在open函数里配置引脚,
5、在read函数里

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/irq.h>


#define KEY_DRIVER1_MAJOR 0



/*声明用到的结构体和变量*/
struct cdev key_driver1_cdev;
static int key_driver1_major = KEY_DRIVER1_MAJOR;
dev_t key_dev_num;//dev_t类型设备号
static struct class *key_drv_class;
static struct class_device *key_drv_class_dev;
/*用于地址映射*/
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

#define rGPFCON 0X56000050
#define rGPGCON 0X56000060

static int key_driver1_open(struct inode *node, struct file *file)
{

/*采用查询方式,将按键的引脚进行配置为输入*/
*gpfcon &= ~((0x3 << (0*2)) | (0x3 << (2*2)));

*gpgcon &= ~((0x3 << (3*2)) | (0x3 << (11*2)));

return 0;
}

static int key_driver1_read (struct file * file, char __user * buf, size_t size, loff_t *ppos)
{
unsigned char key_val[4];
int regval;
int ret;

if(size != sizeof(key_val))
return -EINVAL;
/*读取GPF0,2的值*/
regval = *gpfdat;
key_val[0] = (regval & (1<<0)) ? 1 : 0;
key_val[1] = (regval & (1<<2)) ? 1 : 0;
/*读取GPF3,11的值*/
regval = *gpgdat;
key_val[2] = (regval & (1<<3)) ? 1 : 0;
key_val[3] = (regval & (1<<11)) ? 1 : 0;

ret = copy_to_user(buf,key_val,sizeof(key_val));//从设备拷贝数据到用户空间


return sizeof(key_val);

}


/*文件操作结构体*/
static const struct file_operations key_driver1_fops =
{
.owner = THIS_MODULE,
.read = key_driver1_read,
.open = key_driver1_open,
};


/*设备驱动模块加载函数*/
int key_driver1_init(void)
{
int result;
int err;
key_dev_num = MKDEV(key_driver1_major, 0);

/* 申请设备号*/
if (key_driver1_major)
result = register_chrdev_region(key_dev_num, 1, "key_driver1");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&key_dev_num, 0, 1, "key_driver1");
key_driver1_major = MAJOR(key_dev_num);
}
if (result < 0)
return result;
/*
*进行cdev的初始化,将cdev与key_driver1_fops关联


*/

cdev_init(&key_driver1_cdev, &key_driver1_fops);
key_driver1_cdev.owner = THIS_MODULE;
key_driver1_cdev.ops = &key_driver1_fops;
err = cdev_add(&key_driver1_cdev, key_dev_num, 1);
if (err)
printk(KERN_NOTICE "Error %d ", err);

/*
* 为设备创建类
* 加载驱动模块时在/sys/class 目录下自动创建car_class 文件夹
*/

key_drv_class = class_create(THIS_MODULE, "key_driver1");
if(IS_ERR(key_drv_class))
{
printk("cannot create key_drv_class!\n");
return 0;
}
/*创建设备文件节点,避免需要手动创建*/
key_drv_class_dev = class_device_create(key_drv_class, NULL,key_dev_num, NULL, "key_driver1");

/*将GPIO 的物理地址映射到内核空间,*/
gpfcon = (volatile unsigned long *)ioremap(rGPFCON, 16);
gpfdat = gpfcon + 1;

gpgcon = (volatile unsigned long *)ioremap(rGPGCON, 16);
gpgdat = gpgcon + 1;


return 0;

}

/*模块卸载函数*/
void key_driver1_exit(void)
{

cdev_del(&key_driver1_cdev); /*注销cdev*/
unregister_chrdev_region(key_dev_num, 1); /*释放设备号*/

/*删除设备文件和dev/class 目录下的相应文件夹*/
class_device_unregister(key_drv_class_dev);
class_destroy(key_drv_class);
/*解除GPIO 映射*/
iounmap(gpfcon);
iounmap(gpgcon);

}

MODULE_AUTHOR("maomao");
MODULE_LICENSE("Dual BSD/GPL");

module_init(key_driver1_init);
module_exit(key_driver1_exit);