【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"); //驱动的描述
在运行时缺发现一个奇怪的问题,见下图
编译有没错,百度了好久一直不知道什么原因,找了大半天……很无奈然后重新细致的从头到尾浏览一遍代码才发现……
原来是在执行退出函数的时候没有释放物理内存,因为我在前面有申请GPIO C组的物理内存区,却忘记了释放
所以补全代码应该是
其他就没有什么问题了,重新make一下生成内核模块leddrv.ko,终于正常了
推荐阅读