【S5P6818】流水灯驱动程序

记录下找bug找到脑壳疼的一天
针对三星公司的S5P6818芯片,尝试写了个流水灯的驱动,驱动程序源码如下:

//相关头文件
#include <linux/module.h>  //module
#include <linux/printk.h>  //printk
#include <linux/cdev.h>    //struct cdev
#include <linux/fs.h>      //struct file_operations
#include <linux/device.h>  //struct class struct device 
#include <linux/ioport.h>  //request_mem_region
#include <linux/io.h>      //ioremap
#include <linux/uaccess.h> //copy_from_user

//1. 定义普通的字符设备
static struct cdev led_cdev;

static struct class *led_class = NULL;
static struct device *led_device = NULL;

static struct resource *gpioe_res = NULL;	//GPIO E组物理内存资源
static struct resource *gpioc_res = NULL;	//GPIO E组物理内存资源

static void __iomem *gpioe_base_val = NULL;    //GPIOE基地址
static void __iomem *gpioe_out_val = NULL;     //GPIOEOUT    0XC001E000
static void __iomem *gpioe_outenb_val = NULL;  //GPIOEOUTENB 0XC001E004
static void __iomem *gpioe_altfn0_val = NULL;  //GPIOEALTFN0 0XC001E020

static void __iomem *gpioc_base_val = NULL;    //GPIOC基地址
static void __iomem *gpioc_out_val = NULL;     //GPIOCOUT    0XC001E000
static void __iomem *gpioc_outenb_val = NULL;  //GPIOCOUTENB 0XC001E004
static void __iomem *gpioc_altfn0_val = NULL;  //GPIOCALTFN0 0XC001E020
static void __iomem *gpioc_altfn1_val = NULL;  //GPIOCALTFN1 0XC001E024

//3. 定义设备号变量
dev_t led_dev; //设备号
unsigned int major = 0; //主设备号 如果主设备号为0,则表示由内核动态分配设备号
unsigned int minor = 0; //次设备号

static int led_open (struct inode *node, struct file *file)
{
	printk("chenhai_led open \n");
	return 0;
}

//驱动程序与应用程序之间的数据传输
ssize_t led_write(struct file *file, const char __user *userbuf, size_t size, loff_t *offset)
{
	char kbuf[2];
	int ret;

	//1. 接收用户空间的数据
	ret = copy_from_user(kbuf, userbuf, size);

	//调试信息
	printk("kbuf[0]=%d,kbuf[1]=%d\n",kbuf[0],kbuf[1]);

	//2. 根据用户空间的指令来控制硬件
	switch(kbuf[0])
	{
		case 7:
			if(kbuf[1] == 1) *(unsigned int*)gpioe_out_val &= ~(1<<13); 
			else if(kbuf[1] == 0) *(unsigned int*)gpioe_out_val |= (1<<13); 
		break;
		
		case 8:
			if(kbuf[1] == 1) *(unsigned int*)gpioc_out_val &= ~(1<<17); 
			else if(kbuf[1] == 0) *(unsigned int*)gpioc_out_val |= (1<<17); 
		break;
		
		case 9:
			if(kbuf[1] == 1) *(unsigned int*)gpioc_out_val &= ~(1<<8); 
			else if(kbuf[1] == 0) *(unsigned int*)gpioc_out_val |= (1<<8); 
		break;
		
		case 10:
			if(kbuf[1] == 1) *(unsigned int*)gpioc_out_val &= ~(1<<7); 
			else if(kbuf[1] == 0) *(unsigned int*)gpioc_out_val |= (1<<7); 
		break;	
	}
		
	return size;
}

//2. 定义文件操作集
static struct file_operations  led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.write = led_write,
};

//初始化函数
static int __init led_init(void)
{
	int ret;

	//调试信息
	printk(KERN_WARNING"led_init...\n");

	//设备号分配
	if(major == 0) //如果主设备号为0,则进行动态分配
	{
		ret = alloc_chrdev_region(&led_dev,minor,1,"led_number");
	}else{         //静态分配
		ret = register_chrdev_region(led_dev, 1, "led_number");
	}
	if(ret < 0)    //检查设备号是否分配失败
	{
		printk(KERN_WARNING"register_chrdev_dev_error\n");
		goto register_chrdev_dev_error;
	}
	//4. 初始化字符设备
	cdev_init(&led_cdev,&led_fops);
	//5. 添加字符设备到内核中
	ret = cdev_add(&led_cdev, led_dev,1);
	if(ret < 0)  //检查是否添加失败
	{
		printk(KERN_WARNING"cdev_add error\n");
		goto cdev_add_error;
	}
	//6. 创建class(目录)
	led_class = class_create(THIS_MODULE, "led_class");
	if(led_class == NULL)
	{
		printk(KERN_WARNING"class_create error\n");
		ret = -ENOMEM;
		goto class_create_error;
	}

	//7. 在class下创建device   , device 属于class
	led_device = device_create(led_class, NULL,led_dev, NULL, "chenhai_led");//设备文件名字
	if(led_device == NULL) //若创建失败
	{
		printk(KERN_WARNING"device_create error\n");
		ret = -ENOMEM;
		goto device_create_error;
	}
	//8. 申请物理内存区
	//分析:D7 ---> GPIOE13 -->申请GPIO E组的物理地址 0XC001D000-0XC001C000=0X1000 --> 4kb
	gpioe_res = request_mem_region( 0XC001E000, 0X1000,"GPIOE_MEM");	
	if(gpioe_res == NULL) //若申请失败
	{
		printk(KERN_WARNING"request_mem_region error\n");
		ret = -ENOMEM;
		goto request_mem_region_error;
	}
	//9. IO动态映射
	gpioe_base_val = ioremap(0XC001E000, 0X1000); //得到物理地址对应虚拟地址的首地址
	printk(KERN_WARNING"gpioe_base_val = %p\n",gpioe_base_val);    
	if(gpioe_base_val == NULL)
	{
		printk(KERN_WARNING"ioremap error\n");
		ret = -ENOMEM;
		goto ioremap_error;
	}
	//10. 得到其他寄存器对应的虚拟地址,并通过虚拟地址来访问硬件,即IO映射
	gpioe_out_val = gpioe_base_val + 0x00;     
	gpioe_outenb_val = gpioe_base_val + 0x04; 
	gpioe_altfn0_val = gpioe_base_val + 0x20;  
	
	//申请物理内存区
	gpioc_res = request_mem_region(0XC001C000, 0X1000,"GPIOC_MEM");	
	if(gpioc_res == NULL) //若申请失败
	{
		printk(KERN_WARNING"request_mem_region error\n");
		ret = -ENOMEM;
		goto request_mem_region_error;
	}
	//IO动态映射
	gpioc_base_val = ioremap(0XC001C000, 0X1000); //得到物理地址对应虚拟地址的首地址
	printk(KERN_WARNING"gpioc_base_val = %p\n",gpioc_base_val);    
	if(gpioc_base_val == NULL)
	{
		printk(KERN_WARNING"ioremap error\n");
		ret = -ENOMEM;
		goto ioremap_c_error;	
	}
	gpioc_out_val = gpioc_base_val + 0x00;     
	gpioc_outenb_val = gpioc_base_val + 0x04; 
	gpioc_altfn0_val = gpioc_base_val + 0x20;
	gpioc_altfn1_val = gpioc_base_val + 0x24;
	//E组
	//第一步:设置引脚的功能 
	*(unsigned int*)gpioe_altfn0_val &= ~(3<<26);   //GPIOE13
	//第二步:设置引脚的工作模式
	*(unsigned int*)gpioe_outenb_val |= (1<<13);    //GPIOE13	
	//第三步:引脚赋初值:高或低电平
	*(unsigned int*)gpioe_out_val |= (1<<13);      //GPIOE13
	//C组
	*(unsigned int*)gpioc_altfn1_val &= ~(2<<2);
	*(unsigned int*)gpioc_altfn0_val &= ~(2<<14);
	*(unsigned int*)gpioc_altfn0_val &= ~(2<<16);
	
	*(unsigned int*)gpioc_outenb_val |= (1<<17) | (1<<7) | (1<<8);
	*(unsigned int*)gpioc_out_val |= (1<<17) | (1<<7) | (1<<8);

	return 0;

	//出错后,要把出错之前成功申请的资源进行释放
	ioremap_error:
		release_mem_region(0XC001E000,0X1000);
	ioremap_c_error:
		release_mem_region(0XC001C000,0X1000);
	request_mem_region_error:
		device_destroy(led_class, led_dev);
	device_create_error:
		class_destroy(led_class);
	class_create_error:
		cdev_del(&led_cdev);
	cdev_add_error: 
		unregister_chrdev_region(led_dev,1);
	register_chrdev_dev_error:
	return ret;
	
}
//退出函数
static void __exit led_exit(void)
{
	//调试信息
	printk(KERN_INFO "led_exit\n");
	iounmap(gpioe_base_val);  //IO解除映射
	gpioe_base_val = NULL;    //指针赋空,防止野指针出现
	release_mem_region(0XC001E000,0X1000); //释放物理内存区
	device_destroy(led_class, led_dev);       //销毁device
	class_destroy(led_class);				//销毁class
	cdev_del(&led_cdev);				    //删除设备
	unregister_chrdev_region(led_dev,1);     //注销设备号	
}
//模块入口
module_init(led_init);
//模块出口
module_exit(led_exit);
//模块描述
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chenhai");  			 //驱动的作者
MODULE_DESCRIPTION("the led's driver"); //驱动的描述

在运行时缺发现一个奇怪的问题,见下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0KY5I7E-1576154097412)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1576153170028.png)]
编译有没错,百度了好久一直不知道什么原因,找了大半天……很无奈然后重新细致的从头到尾浏览一遍代码才发现……

原来是在执行退出函数的时候没有释放物理内存,因为我在前面有申请GPIO C组的物理内存区,却忘记了释放

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OREPqL3P-1576154097413)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1576153801922.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3gYTDeLb-1576154097413)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1576153847960.png)]

所以补全代码应该是

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ge4cenqS-1576154097414)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1576153900406.png)]

其他就没有什么问题了,重新make一下生成内核模块leddrv.ko,终于正常了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0j8nJgg9-1576154097415)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1576153972046.png)]

发布了37 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Chen_qi_hai/article/details/103516500
今日推荐