欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【S5P6818】流水灯驱动程序

程序员文章站 2022-03-13 17:24:42
...

记录下找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"); //驱动的描述

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

【S5P6818】流水灯驱动程序
编译有没错,百度了好久一直不知道什么原因,找了大半天……很无奈然后重新细致的从头到尾浏览一遍代码才发现……

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

【S5P6818】流水灯驱动程序

【S5P6818】流水灯驱动程序

所以补全代码应该是

【S5P6818】流水灯驱动程序

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

【S5P6818】流水灯驱动程序