驱动函数的基本框架

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);
}
发布了29 篇原创文章 · 获赞 1 · 访问量 547

猜你喜欢

转载自blog.csdn.net/qq_45173769/article/details/103860524