1 、驱动程序和应用层的联系
1.1 从应用层看
1)首先APP斗调用Open(“dev/xxx”,O_REWR)打开设备文件后,会得到此设备文件的属性,知道属性中的“设备类型”和“主设备号”。
2)然后VFS层通过“设备类型”(如字符设备类型)去内核中的“chrdev”这个数组。在通过APP得到的“主设备号”以此为索引从内核的"chrdev"数组中知道相应的“file_operation”结构。这个结构是驱动程序“register_chrdev”注册到内核的,这样索引找到它。这个结构中有相应的一些成员函数(如read,write等),这些成员函数就对应硬件的读写操作等。
file_operation的结构体如下:
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 *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
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 *, struct dentry *, 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 (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
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 (*dir_notify)(struct file *filp, unsigned long arg);
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);
};
1.2 从驱动程序看:如操作LED
1)首先定义一个file_operation结构体(操作这个LCD用),比如此结构体中的open指向led_open等。
2)然后具体实现这些函数,比如led_open。
3)最后,在驱动的入口函数中,使用“register_chardev”把上面定义的结构体放到内核“chardev”字符设备数组中。
区分不同的设备:主设备号和此设备号。
因此,对应不适用input system(输入子系统)的简易驱动程序,其主要的步骤为:
1)先写open,write,read等函数
2)写file_operations结构,然后将上面的open,write函数填充到相应函数指针处
3)在入口函数中注册驱动程序
4)在出口函数中卸载驱动
5)修饰入口函数和出口函数
2.1 具体驱动函数的实现
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *firstdrv_class; //建立一个类
static struct class_device *firstdrv_class_devs[4]; //在类下建立一个设备
//led驱动程序和led单片机的程序区别在于单片机直接操作物理地址,但驱动程序需要使用ioremap函数把物理地址映射为虚拟地址
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int first_drv_open(struct inode *inode,struct file *file)
{
//配置GPF4 5 6 为输出引脚
*gpfcon &= ~((3<<8) | (3<<10) | (3<<12));
*gpfcon |= ((1<<8) | (1<<10) | (1<<12));
return 0;
}
static ssize_t first_drv_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos) //__usr表示用户空间
{
int val;
int minor = MINOR(file->f_dentry->d_inode->i_rdev);
copy_from_user(&val,buf, count); //把用户空间的数据传递到内核空间 copy_to_user,是从内核空间拷贝数据到用户空间
printk("enter");
switch(minor)
{
case 0: // dev/leds
{
if(val == 1)
{
//点亮所有的灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
printk("case 0 1");
}
else
{
//熄灭所有的灯
*gpfdat |= ((1<<4) | (1<<5) | (1<<6));
printk("case 0 0");
}
}
case 1: // dev/led1
{
if(val == 1)
{
*gpfdat |= ((1<<4) | (1<<5) | (1<<6));
//点亮第一盏灯灯
*gpfdat &= ~(1<<4 );
}
else
{
//熄灭第一盏灯的灯
*gpfdat |= (1<<4) ;
}
}
case 2: // dev/led2
{
if(val == 1)
{
*gpfdat |= ((1<<4) | (1<<5) | (1<<6));
//点亮第二盏灯灯
*gpfdat &= ~(1<<5 );
}
else
{
//熄灭第二盏灯灯
*gpfdat |= (1<<5) ;
}
}
case 3: // dev/led3
{
if(val == 1)
{
*gpfdat |= ((1<<4) | (1<<5) | (1<<6));
//点亮第二盏灯灯
*gpfdat &= ~(1<<6 );
}
else
{
//熄灭第三盏灯
*gpfdat |= (1<<6);
}
}
}
//printk("first_drv_write");
return 0;
}
//怎么告诉内核,首先告诉内核有这个一个结构,然后填充所需要的函数,最后注册
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE,
.open = first_drv_open,
.write = first_drv_write,
};
//驱动的入口函数
int major;
int minor = 0;
int first_drv_init(void)
{
//第一个参数为主设备号,第二个参数为名字(可以顺便取),第三个参数为file_operations结构体
major = register_chrdev(0, "first_drv",&first_drv_fops); //注册驱动程序,如果写0,系统自动分配空闲的主设备号
//自动创建根文件设备节点
firstdrv_class = class_create(THIS_MODULE,"first_drv");
if(IS_ERR(firstdrv_class))
return PTR_ERR(firstdrv_class);
firstdrv_class_devs[0] = class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"leds"); //major主设备号 0次设备号 xyz名字,自动创建一个dev/xyz 的设备节点
if(unlikely(IS_ERR(firstdrv_class_devs[0])))
return PTR_ERR(firstdrv_class_devs[0]);
for(minor = 1;minor < 4;minor++)
{
firstdrv_class_devs[minor] = class_device_create(firstdrv_class,NULL,MKDEV(major,minor),NULL,"led%d",minor); //major主设备号 0次设备号 xyz名字,自动创建一个dev/xyz 的设备节点
if(unlikely(IS_ERR(firstdrv_class_devs[minor])))
return PTR_ERR(firstdrv_class_devs[minor]);
}
//入口函数出映射
gpfcon = (volatile unsigned long *)ioremap(0x56000050,16);
gpfdat = gpfcon + 1;
return 0;
}
//驱动的出口函数
void first_drv_exit(void)
{
int i;
unregister_chrdev(major,"first_drv"); //卸载
for(i = 0;i < 4;i++)
{
class_device_unregister(firstdrv_class_devs[i]);
}
class_destroy(firstdrv_class);
//删除建立的映射
iounmap(gpfcon);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
2.2测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
//firstdrvtest on
//firstdrvtest off
void print_usage(char *file)
{
printf("Usage:\n");
printf("%s <dev> <on|off>\n",file);
printf("eg. \n");
printf("%s /dev/leds on\n",file);
printf("%s /dev/leds off\n",file);
printf("%s /dev/led1 on\n",file);
printf("%s /dev/led1 off\n",file);
}
int main(int argc,char **argv)
{
int fd;
int val;
char *filename;
if(argc != 3)
{
print_usage(argv[0]);
return 0;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd<0)
{
printf("can't open!\n");
return 0;
}
/*
if(argc !=2)
{
printf("Usage :\n");
printf("%s <on|off>\n",argv[0]); //linux <>表示参数不可省略
return 0;
}
*/
if(strcmp(argv[2],"on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd,&val,4);
}