前面笔者已实现了用户程序控制led灯闪烁的驱动代码,但是由于代码不规范,显得乱七八糟的,因此需要规范化。如果比较大一点的工程没有规范的话,也不利于后期的跟新与维护。分析先前的程序不规范点有二:
- 定义的变量多而且散乱
- 程序没有错误处理机制
C语言虽然是面向过程的语言,但是可以利用结构体来实现面向对象的思想。通过引入面向对象的思想,来解决第一个问题。通过结构体将将相关的变量类型进行一次封装,构造出一个对象;而对于错误处理, 可以通过prink提示错误信息,然后goto语句跳转到错误处理的过程来处理。
面向对象
在面向对象的思想中,一切皆是对象。将led设备抽象成为一个对象,那它的主设备号、寄存器基地址等信息都可以看做它的属性,我们用结构体将这样一些数据类型进行封装。
struct led_desc{
//声明结构体类型,描述led信息
unsigned int dev_major; //描述主设备号
struct class *cls;
struct device *dev;
void *reg_virt_base; //寄存器基地址
};
定义结构体对象 ,led_dev明显是一个结构体指针
struct led_desc *led_dev;
为led_dev分配空间,对象的实例化
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
如果要引用这个对象的属性(结构体访问成员变量),例如
led_dev->dev_major
因为使用malloc分配的是块空间(堆),结束时需要手动释放资源(空间)
kfree(led_dev);
错误处理机制
当程序申请资源失败,我们不仅要分析判断这个错误,还要打印错误提示
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{//打印错误
printk(KERN_ERR "register_chrdev error\n");
}
但是,,这样的做法是不对的。我们程序不能直接抛出错误信息,然后退出,给系统留下一个烂摊子啊!因为之前可能存在,已申请但是没有被释放的资源。正确的做法
错误源 ---> 程序判断 ---> (打印错误信息) ---> 设置错误码 ---> 跳转到错误处理 ---> 退出
int ret;
//led_dev分配空间,对象的实例化
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR "malloc error\n");
return -ENOMEM;
}
//动态向系统申请设备号
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
err_0:
kfree(led_dev);
return ret;
总之,错误处理是要处理那些 在错误出现之前申请的资源,将其回收。
驱动代码
下面是对之前led驱动程序的一些改进。
- 使用了结构体来描述设备信息
- 使用goto进行错误处理
- 修改申请主设备号为动态方式
- 使用readl writel 接口函数读写地址
//led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
ssize_t led_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos);
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos);
int led_drv_open(struct inode *inode, struct file *filp);
int led_drv_close(struct inode *inode, struct file *filp);
#define GPX2_CON 0x11000C40
#define GPX2_SIZE 8
//volatile unsigned long *gpx2conf;
//volatile unsigned long *gpx2dat;
//static unsigned int dev_major = 250;
//static struct class *devcls;
//static struct device *dev;
const struct file_operations my_fops = {
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
struct led_desc{
//声明结构体类型,描述led信息
unsigned int dev_major; //描述主设备号
struct class *cls;
struct device *dev;
void *reg_virt_base; //寄存器基地址
};
//定义一个结构体变量,创建对象
struct led_desc *led_dev;
static int __init led_drv_init(void)
{
int ret;
printk("-------%s-------------\n", __FUNCTION__);
//led_dev分配空间,对象的实例化
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR "malloc error\n");
return -ENOMEM;
}
//动态向系统申请设备号
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
//创建设备结点
led_dev->cls = class_create(THIS_MODULE, "led_cls");
if(IS_ERR(led_dev->cls))
{
printk(KERN_ERR "class_create error\n");
ret = PTR_ERR(led_dev->cls); //½«Ö¸Õë³ö´íµÄ¾ßÌåÔÒòת»»³ÉÒ»¸ö³ö´íÂë
goto err_1;
}
led_dev->dev = device_create(led_dev->cls, NULL,
MKDEV(led_dev->dev_major, 0), NULL, "led%d", 0);
if(IS_ERR(led_dev->dev))
{
printk(KERN_ERR "device_create error\n");
ret = PTR_ERR(led_dev->dev); //½«Ö¸Õë³ö´íµÄ¾ßÌåÔÒòת»»³ÉÒ»¸ö³ö´íÂë
goto err_2;
}
//将物理地址映射成为虚拟地址,用指针指向这个地址
led_dev->reg_virt_base = ioremap(GPX2_CON, GPX2_SIZE);
if(led_dev->reg_virt_base == NULL)
{
printk(KERN_ERR "ioremap error\n");
ret = -ENOMEM;
goto err_3;
}
//GPX2_7设置成输出模式
u32 value = readl(led_dev->reg_virt_base);
value &= ~(0xf<<28);
value |= (0x1<<28);
writel(value, led_dev->reg_virt_base);
return 0;
err_3:
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
err_2:
class_destroy(led_dev->cls);
err_1:
unregister_chrdev(led_dev->dev_major, "led_dev_test");
err_0:
kfree(led_dev);
return ret;
}
static void __exit led_drv_exit(void)
{
printk("-------%s-------------\n", __FUNCTION__);
//取消地址映射
iounmap(led_dev->reg_virt_base);
//销毁这个设备结点
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
class_destroy(led_dev->cls);
//释放这个设备号
unregister_chrdev(led_dev->dev_major, "led_dev_test");
//释放结构体空间
kfree(led_dev);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
static int kernel_val = 555;
// read(fd, buf, size);
ssize_t led_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------\n", __FUNCTION__);
int ret;
ret = copy_to_user(buf, &kernel_val, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
return 0;
}
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------\n", __FUNCTION__);
int ret;
int value;
ret = copy_from_user(&value, buf, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
if(value){
writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );
}else{
writel( readl(led_dev->reg_virt_base + 4) & ~(1<<7), led_dev->reg_virt_base + 4 );
}
return 0;
}
int led_drv_open(struct inode *inode, struct file *filp)
{
printk("-------%s-------\n", __FUNCTION__);
return 0;
}
int led_drv_close(struct inode *inode, struct file *filp)
{
printk("-------%s-------\n", __FUNCTION__);
return 0;
}
查看实验结果
修改应用程序,识别设备结点为 /dev/led0
编译并移动文件到nfs根目录
root@linux:/mnt/hgfs/sharefolder/kernel/linux-3.14-fs4412/drivers/mydrivers/chr_drv# make
root@linux:/mnt/hgfs/sharefolder/kernel/linux-3.14-fs4412/drivers/mydrivers/chr_drv# make install
开发板加载模块,执行应用程序
[root@farsight drv_module]# ls
chr_drv.ko chr_test led_drv.ko led_test
[root@farsight drv_module]# insmod led_drv.ko
[ 5097.315000] -------led_drv_init-------------
[root@farsight drv_module]# ./led_test
[ 5104.010000] -------led_drv_open-------
又可以观察开发板上led是闪烁状态了。