嵌入式字符设备驱动编写步骤

目录

编写步骤: 

设备号的申请注册注销:

通用函数(静态动态申请都可以):

静态申请:

动态申请:

设备号的注销:

设备节点的创建与销毁:

手动创建设备节点:

自动创建设备节点:

设备节点的销毁:

IO资源的映射与配置:

读写函数编写,操作IO口,比如点灯:

应用实验程序的编写

驱动示例代码:


编写步骤: 

/*编写步骤:
*
*1、编写驱动模块的基本框架  https://blog.csdn.net/shenlong1356/article/details/88367429
*2、注册注销设备号
*3、创建设备节点
*4、编写file_operations 结构体成员 open write .....
*5、点灯LED1 GPF4  查出寄存器地址 进行物理地址到虚拟地址的映射 ioremap iounmap 
*/

设备号的申请注册注销:

通用函数(静态动态申请都可以)

register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)

major  :确定一个主设备号,如果major=0,则会自动分配设备号

name  : 设备名

fops    : 构造一个file_operations结构体, 然后放在chrdevs数组中

    #define ledmajor 250
	
    
    const struct file_operations led_fops = {
	.open = led_open ,
	.read = led_read ,
	.write = led_write ,
	.release = led_close ,

};


    int ret;
	
	ret = register_chrdev(ledmajor , "led_test" , &led_fops) ;  //注册设备号

静态申请:

     int register_chrdev_region(dev_t from, unsigned count, const char *name);

/* 参数:
    dev_t from - 要申请的设备号(起始)
    unsigned count - 要申请的设备号数量
    const char *name - 设备名
   返回值:
    成功:0
    失败:负数(绝对值是错误码)*/

动态申请:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/* 参数:
    dev_t *dev - 用于保存分配到的第一个设备号(起始)
    unsigned baseminor - 起始次设备号
    unsigned count - 要分配设备号的数量
    const char *name - 设备名
   返回值:
    成功:0
    失败:负数(绝对值是错误码)*/

设备号的注销:

unregister_chrdev(unsigned int major, const char * name);

void unregister_chrdev_region(dev_t from, unsigned count);
/* 参数:
    dev_t from - 要释放的第一个设备号(起始)
    unsigned count - 要释放的次设备号数量 */

 

设备节点的创建与销毁:

手动创建设备节点

  1. 使用mknod手工创建:mknod filename type major minor

自动创建设备节点

利用udev(mdev)来实现设备文件的自动创建

struct class *class_create(struct module *owner, const char *name);
/*
  功能:在/sys/class目录下创建一个目录,目录名是name指定的
  参数:
    struct module *owner - THIS_MODULE
    const char *name - 设备名
  返回值:
    成功:class指针
    失败: - bool IS_ERR(const void *ptr)  判断是否出错
         long PTR_ERR(const void *ptr)  转换错误码
*/
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
/* 
  功能:
    在class指针指向的目录下再创建一个目录,目录名由const char *fmt, ...指出、并导出设备信息(dev_t)
  参数:
    struct class *cls - class指针
    struct device *parent - 父对象,NULL
    dev_t devt - 设备号
    void *drvdata - 驱动私有数据
    const char *fmt, ... - fmt是目录名字符串格式,...就是不定参数
  返回值:
    成功 - device指针
    失败 - bool IS_ERR(const void *ptr)  判断是否出错
       long PTR_ERR(const void *ptr)   转换错误码
*/
    static  struct class *ledclass ;
    static  struct device *led_dev;


	ledclass = class_create( THIS_MODULE , "led_class");  
	device_create(ledclass , NULL , MKDEV(250 , 0), "led_device") ; //创建设备节点

设备节点的销毁:

void class_destroy(struct class *cls);
/*
  功能:删除class指针指向的目录
  参数:
    struct class *cls - class指针
*/
void device_destroy(struct class *cls, dev_t devt);
/*
  功能:删除device_create创建的目录
  参数:
    struct class *cls - class指针
    dev_t devt - 设备号
*/
	device_destroy(ledclass,  MKDEV(250 , 0));    //注意二者的顺序

	class_destroy(ledclass);

IO资源的映射与配置:

    #define GPFCON 0x56000050   //控制寄存器物理地址
    #define GPFDAT 0x56000054   //数据寄存器
    #define GPF_SIZE	8

    volatile unsigned long *gpfconvir;    //虚拟地址
    volatile unsigned long *gpfdatvir;




    gpfconvir = (volatile unsigned long *)ioremap(GPFCON, 16); //物理地址到虚拟地址映射
	gpfdatvir = gpfconvir + 1;

	*gpfconvir &= ~(3<<8);   //对8 9 bit 清零 也就是 GPF4
	*gpfconvir |= (1<<8) ;	 //配置为输出

读写函数编写,操作IO口,比如点灯:

unsigned long copy_to_user(void *to, const void *from, unsigned long n)
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数

一般在read函数中调用,传递数据给用户(用户读数据)

unsigned long copy_from_user(void *to, const void *from, unsigned long n);
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数

一般在write函数中调用接受来自用户的数据(用户写数据)

ssize_t led_read (struct file *filp, char __user *buf , size_t count, loff_t *fops)
{
	int value = 110 ;
	int ret;

   ret = copy_to_user(buf, &value, count);
	if(ret > 0)
	{
		printk("read filed\n");
		return -EFAULT;
	}

	return 0 ;

}

/*操作灯函数*/
ssize_t led_write (struct file *filp, const char __user * buf, size_t count, loff_t *fops)
{

	int value ;
	int ret ;

	ret = copy_from_user(&value, buf, count);
	if(ret > 0)
	{
		printk("write filed!\n");
		return -EFAULT;

	}

	if(value > 0)
	{
		*gpfdatvir |= (1<<4);		//灭灯
	}
	else
	{
		*gpfdatvir &= ~(1<<4);   //亮灯
	}
	
	
	return 0;

}

应用实验程序的编写

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{

	int fd;
	int value = 0;

	fd = open("/dev/led_device", O_RDWR); //打开对应的设备节点led_device

	if(fd < 0)
	{
		perror("open\n");	

		exit(1);
	}


	//控制灯的亮灭  实现灯的闪烁
	
	while(1)
	{
		value = 0;
		write(fd, &value, 4);
		sleep(1);

		value = 1;
		write(fd, &value, 4);
		sleep(1);
		
	}

	close(fd);

	return 0 ;

}


驱动示例代码:


#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>    //字符设备头文件
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>




/*编写步骤:
*
*1、编写驱动模块的基本框架
*2、注册注销设备号
*3、创建设备节点
*4、编写file_operations 结构体成员 open write .....
*5、点灯LED1 GPF4  查出寄存器地址 进行物理地址到虚拟地址的映射 ioremap iounmap 
*/

#define ledmajor 250
static  struct class *ledclass ;
static  struct device *led_dev;

#define GPFCON 0x56000050   //控制寄存器物理地址
#define GPFDAT 0x56000054   //数据寄存器
#define GPF_SIZE	8

volatile unsigned long *gpfconvir;    //虚拟地址
volatile unsigned long *gpfdatvir;


ssize_t led_read (struct file *filp, char __user *buf , size_t count, loff_t *fops)
{
	int value = 110 ;
	int ret;

   ret = copy_to_user(buf, &value, count);
	if(ret > 0)
	{
		printk("read filed\n");
		return -EFAULT;
	}

	return 0 ;

}

/*操作灯函数*/
ssize_t led_write (struct file *filp, const char __user * buf, size_t count, loff_t *fops)
{

	int value ;
	int ret ;

	ret = copy_from_user(&value, buf, count);
	if(ret > 0)
	{
		printk("write filed!\n");
		return -EFAULT;

	}

	if(value > 0)
	{
		*gpfdatvir |= (1<<4);		//灭灯
	}
	else
	{
		*gpfdatvir &= ~(1<<4);   //亮灯
	}
	
	
	return 0;

}

int led_open (struct inode *inod, struct file *filp)
{
//	printk("open ok\n");

	return 0;

}

int led_close(struct inode *inod, struct file *filp)
{
//	printk("close ok\n");

	return 0;
}


const struct file_operations led_fops = {
	.open = led_open ,
	.read = led_read ,
	.write = led_write ,
	.release = led_close ,

};

static int __init led_drv_init(void)
{


	int ret;
	
	ret = register_chrdev(ledmajor , "led_test" , &led_fops) ;  //注册设备号

	if (ret == 0)
		{
			printk("register ok!\n" );

	}
	else
		{

		printk("register filed!\n");
		return -1;
	}

	gpfconvir = (volatile unsigned long *)ioremap(GPFCON, 16); //物理地址到虚拟地址映射
	gpfdatvir = gpfconvir + 1;

	*gpfconvir &= ~(3<<8);   //对8 9 bit 清零 也就是 GPF4
	*gpfconvir |= (1<<8) ;	 //配置为输出

	ledclass = class_create( THIS_MODULE , "led_class");  
	device_create(ledclass , NULL , MKDEV(250 , 0), "led_device") ; //创建设备节点
	
	return 0;
	
}

static void __exit led_drv_exit(void)
{

	iounmap(gpfconvir);     //释放io资源
	
	device_destroy(ledclass,  MKDEV(250 , 0));    //注意三者的顺序

	class_destroy(ledclass);

	unregister_chrdev(ledmajor , "led_test") ;    //注销设备号

}


module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

错误总结:

寄存器数据类型要正确,寄存器地址映射要正确;

发布了135 篇原创文章 · 获赞 112 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/shenlong1356/article/details/88370808