相应头文件:
#include
开发步骤:
0.驱动开发的简单架构
1.定义设备结构体
2.申请设备号
3.定义文件操作集
4.设备初始化
5.注册设备
6.申请物理内存区
7.通过映射物理地址来获得相应虚拟地址
8.创建设备的类
9.创建设备文件结点
10.应用程序的编写
11.代码演示
0.驱动开发的简单架构
驱动代码虽然也是用C语言编写,但没有main函数。取而代之有起始宏( module_init() )和结束宏( module_exit() ),宏中指定了驱动开始嵌入内核时执行的函数和驱动被移出内核时所执行的函数,另外还有MODULE_LICENSE(“GPL”),也是必须的,指定了GPL授权。
当在开发板执行命令:insmod 驱动名 ;时,就把驱动作为模块嵌入到内核中,此时会自动执行module_init()指定的函数。
当在开发板执行命令:rmmod 驱动名 ;时,就把驱动模块从内核中移出,此时会自动执行module_exit()指定的函数。
static int __init start(void) //初始函数的格式,函数名可以自己定
{
}
static void __exit end(void) //退出函数的格式,函数名可以自己定
{
}
module_init(start);
module_exit(end);
MODULE_LICENSE("GPL");
1.定义设备结构体
直接定义一个 cdev 结构体变量即可。cdev结构体在头文件 #include
如:
static struct cdev cdev;
2.申请设备号
0.设备号:一个设备号由主设备号和次设备号组成。主设备号代表该驱动属于什么种类的驱动,而次设备号代表驱动在当前种类驱动中的第几个。
1.设备号的用处是:用于标识这个设备,就如人的名字一样,电脑标识的一般是数字
2.注册设备号的函数有动态注册和静态注册的
动态:
alloc_chrdev_region( *设备号,起始次设备号,注册个数,名字 );
static dev_t dev_num; //定义设备号变量
static const char dev_name[]="led";
int ret;
ret = alloc_chrdev_region(&dev_num,0,1,dev_name);
if(ret<0)
{
printk("申请设备号失败\n"); //从内核打印信息用printk
}
动态申请设备号,会自动生成设备号并放到dev_num 变量中。
静态:
用MKDEV(主设备号,次设备号)生成指定的设备号,如:
static int major =10;
static int minor = 5;
static dev_t dev_num;
dev_num = MKDEV(major,minor);
静态申请要人工指定主设备号和次设备号,这样一来,设备号就确定了。
注销函数:unregister_chrdev_region( dev_num,1 );//用于注销申请的设备号
3.定义文件操作集
在linux内核源码的 include/linux 下的 fs.h 文件中,指定了一个文件操作集的结构体,里面包含驱动可以使用的系统调用函数。
有驱动程序绝对要有相对应的应用程序,因为驱动程序一般是用于被应用程序调用的。而应用程序与驱动程序间是如何联系的呢?
拿上图的 int (*open) (struct inode *, struct file *); 为例,是一个函数指针,用于指向一个函数(假设指向函数a)。意思是,但应用程序调用 open()函数时,驱动程序就自动执行函数a。注意的是,int (*open) (struct inode *, struct file *); 为例,自己写open对应的函数时,格式一定要与int (*open) (struct inode *, struct file *)一样,只允许函数名自己定义。最后会有代码会演示。
所以,我们可以根据自己要用什么函数来定义自己的文件操作集。把结构体上的函数指针赋值就行了。
还有.owner = THIS_MODULE 一定要写,用于初始化。记住就好
/*下面是指定当应用程序调用open函数时,驱动程序会调用device_open函数,同理还有write函数,这里特别的是release是指应用程序的close()函数*/
static const struct file_operations fops={
.owner = THIS_MODULE,
.open = device_open,
.write = device_write,
.release = device_release,
};
无注销函数
4.设备初始化
函数:cdev_init( *设备结构体变量,*文件操作集 );
作用:把 设备结构体变量 和 文件操作集 结合起来。相当于告诉内核这个驱动用哪些系统调用函数。
cdev_init(&cdev,&fops);
无注销函数。
5.注册设备
函数:cdev_add( *设备结构体变量,设备号变量,注册设备个数 );
作用:把 设备结构体变量 赋予 他 一个设备号。
失败会返回一个负值的错误码
ret = cdev_add(&cdev,dev_num,1);
if(ret<0)
{
printk("cdev add failed\n");
goto failed_cdev_add;
}
对应注销函数:cdev_del(*cdev结构体变量);
6.申请物理内存区
写驱动一定要看原理图和用户手册的,申请物理内存区就是获取相应虚拟地址的前一个过程,先要把 设备(例子LED灯)所在的物理地址作为资源申请到内核中。
我的开发板的操控LED灯的寄存器如下:
控制寄存器地址是:0XE0200280;数据寄存器地址是:0XE0200284(怎么找寄存器这里不详细说)
函数:request_mem_region( 起始物理地址,申请多大字节,给这段内存资源一个名字 );
返回:返回一个 struct resource 结构体的指针变量
static struct resource *res = NULL;
res = request_mem_region(0xe0200280,8,"GPJ2CON_MEM");
if(res==NULL)
{
printk("failed to request mem\n");
}
至于为什么第二个参数填8,因为我们ARM开发板,一个寄存器大小是32位。也就是4个字节,那我操作的是2个寄存器,所有填8.
对应注销函数:void release_mem_region(0XE0200280,8);
7.通过映射物理地址来获得相应虚拟地址
有操作系统的,操作的是虚拟地址。所以要通过相对的物理地址映射出虚拟地址给系统操作,从而实现操作系统操作物理地址。
函数:ioremap( 起始物理地址,多少个字节 );
返回虚拟地址起始地址。
unsigned int GPJ2CON_VA=NULL;
GPJ2CON_VA = ioremap(0xe0200280,8);
if(GPJ2CON_VA==NULL)
{
printk("failed to ioremap\n");
}
对应的注销函数:void iounmap(GPJ2CON_VA);
8.创建设备的类class
应用程序调用驱动程序是通过打开 根目录/dev下的 设备文件结点来调用的,而文件结点需要分类。要先创建类class,才能创建设备文件结点。
创建类成功后,可以在路径 /sys/class 下看到,而且进入类后,可以看到属于该类的 设备文件结点。
static struct class *class = NULL; ---->这个要设成全局变量
class = class_create(THIS_MODULE,"class_name");
对应的注销函数: void class_destroy(class变量);
static struct device* dev_device = NULL;
dev_device = device_create(dev_class,NULL,dev_num,NULL,"led_device");
if(dev_device == NULL)
{
printk("failed to create device\n");
}
对应的注销函数:void device_destroy( class变量,设备号变量 );
#include
#include
#include
#include
#include
#include
#include
#include
/*第一步:定义设备结构体cdev*/
static struct cdev cdev;
static dev_t dev_num;
static const char device_name[]="led_device";
static struct resource *py_mem = NULL;
static unsigned int *GPJ2CON_VA = NULL;
static unsigned int *GPJ2DAT_VA = NULL;
static struct class *dev_class = NULL;
static struct device *dev_device = NULL;
char wbuf[1];
static int device_open(struct inode *inode, struct file *f)
{
printk("device open..\n");
*GPJ2CON_VA &=~0xffff;
*GPJ2CON_VA |=0x1111;
*GPJ2DAT_VA &=~0xf;
*GPJ2DAT_VA |=0xf;
return 0;
}
static int device_release(struct inode *inode, struct file *f)
{
printk("device close...\n");
*GPJ2DAT_VA|=0xf;
return 0;
}
static ssize_t device_write(struct file *f, const char __user*buf,
size_t len, loff_t *t)
{
int ret;
ret = copy_from_user(wbuf,buf,len);
if(ret!=0)
{
printk("failed to copy from user\n");
return -1;
}
if(wbuf[0]=='0')
{
*GPJ2DAT_VA &=~0xf;
*GPJ2DAT_VA |=0xe;
}
if(wbuf[0]=='1')
{
*GPJ2DAT_VA &=~0xf;
*GPJ2DAT_VA |=0xd;
}
if(wbuf[0]=='2')
{
*GPJ2DAT_VA &=~0xf;
*GPJ2DAT_VA |=0xb;
}
if(wbuf[0]=='3')
{
*GPJ2DAT_VA &=~0xf;
*GPJ2DAT_VA |=0x7;
}
return 0;
}
/*第三步:定义文件操作集*/
static const struct file_operations fops={
.owner = THIS_MODULE,
.open = device_open,
.write = device_write,
.release = device_release,
};
static int __init led_init(void)
{
int ret;
/*第二步:申请设备号*/
ret = alloc_chrdev_region(&dev_num,0,1,device_name);
if(ret<0)
{
printk("cannot register dev num\n");
return -1;
}
/*第四步:设备初始化*/
cdev_init(&cdev,&fops);
/*第五步:注册设备*/
ret = cdev_add(&cdev,dev_num,1);
if(ret<0)
{
printk("cdev add failed\n");
goto failed_cdev_add;
}
/*第六步:申请物理内存( 地址 )区:0xe0200280~0xe0200287*/
py_mem = request_mem_region(0xe0200280,8,"GPJ2CON_MEM");
if(py_mem==NULL)
{
printk("failed to request mem\n");
goto failed_request_mem;
}
/*第七步:通过有映射物理地址获得相应虚拟地址*/
GPJ2CON_VA = ioremap(0xe0200280,8);
if(GPJ2CON_VA==NULL)
{
printk("failed to ioremap\n");
goto failed_ioremap;
}
/*GPJ2CON_VA为int型,占4个字节,+1相当于移4个字节
(32个位)*/
GPJ2DAT_VA = GPJ2CON_VA+1;
/*第八步:创建设备的类*/
dev_class = class_create(THIS_MODULE,"led_class");
if(dev_class == NULL)
{
printk("failed to create class\n");
goto failed_create_class;
}
/*第九步:创建设备文件结点*/
dev_device = device_create(dev_class,NULL,dev_num,NULL,"led_device");
if(dev_device == NULL)
{
printk("failed to create device\n");
goto failed_create_device;
}
printk("init completed!\n");
return 0;
failed_create_device:
class_destroy(dev_class);
failed_create_class:
iounmap(GPJ2CON_VA);
failed_ioremap:
release_mem_region(0xe0200280,8);
failed_request_mem:
cdev_del(&cdev);
failed_cdev_add:
unregister_chrdev_region(dev_num,1);
return -1;
}
static void __exit led_exit(void)
{
class_destroy(dev_class);
iounmap(GPJ2CON_VA);
release_mem_region(0xe0200280,8);
cdev_del(&cdev);
unregister_chrdev_region(dev_num,1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
#include
#include
int main()
{
char buf[1];
char i;
int fd = open("/dev/led_device",O_WRONLY);
if(fd<0)
{
perror("failed to open");
return -1;
}
while(1)
{
for(i='0';i<'4';i++)
{
buf[0]=i;
write(fd,buf,1);
sleep(1);
}
}
return 0;
}