继第一节第一个驱动程序框架记录之后,本篇文章将会在上一篇驱动程序的框架下编写点亮LED的驱动程序,同样会对上一个框架进行修改,优化。接下来进入正题
1、点亮LED程序框架分析
在最开始之前先来梳理一下点亮LED程序的框架
1、通过对驱动程序的框架分析,我们知道第一步都要从入口函数开始,第一个驱动里我写的入口函数很简单,只是完成了必要操作
这里的话就要进行改进。
2、要点亮LED首先要对LED使用的引脚进行配置,引脚的模式只用配置一次,所以该部分工作放在打开设备文件时进行,
因为设备文件也只是打开一次。
3、对LED进行点亮或者熄灭处理,在写应用程序时我们一般通过write函数进行写文件操作,所以,这一部分比较频繁的操作同样
放在了驱动程序的write函数里。
内容就是这些,在简单梳理之后就可以写程序了
2、程序
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct class *first_class;
static struct class_device *fisrst_class_device;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int leddrv_open(struct inode *inode, struct file *file)
{
*gpfcon &= ~((3 << 8) | (3 << 10) | (3 << 12));
*gpfcon |= ((1 << 8) | (1 << 10) | (1 << 12));
return 0;
}
static ssize_t leddrv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
copy_from_user(&val, buf, count);
if (val == 1) {
*gpfdat &= ~((1 << 4) | (1 << 5) | (1 << 6));
} else {
*gpfdat |= ((1 << 4) | (1 << 5) | (1 << 6));
}
return 0;
}
struct file_operations leddrv_fops = {
.owner = THIS_MODULE,
.open = leddrv_open,
.write = leddrv_write,
};
//入口函数
int major;
static int leddrv_init(void)
{
major = register_chrdev(0, "led_drv", &leddrv_fops);
first_class = class_create(THIS_MODULE, "led_drv");
fisrst_class_device = class_device_create(first_class, NULL,MKDEV(major,0),NULL,"led");
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
//出口函数
static void leddrv_exit(void)
{
unregister_chrdev(major, "led_drv");
class_device_unregister(fisrst_class_device);
class_destroy(first_class);
iounmap(gpfcon);
}
module_init(leddrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");
测试程序
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/led", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s \n" , argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
3、总结
驱动程序改进的地方:
1、第一节中在注册设备驱动的时候,选择的主设备号是固定的,是通过我们查找了一个空闲的设备号来确定的,
这样的做法肯定是不方便的,那么,在上面的程序中,在注册设备驱动的时候传入的主设备号为0的
时候,内核就会给我们自动分配一个空闲的主设备号,在卸载的时候使用该设备号卸载驱动程序。
2、在上一节中只是完成了编写、插入一个驱动模块的工作,在打开设备文件时发现并没有设备文件,然后
我们通过手动执行mknod命令创建了一个设备文件,才完成了后续操作,在这一节中我们使用
"class_create"和"class_device_create"创建了一个类和类下的一个设备,
那么什么是类呢?我们插入的驱动模式内核都把他当做是一个类来看待,在"/sys/class"目录下就有使用
class_create函数创建的类文件,在该文件夹里有一个设备文件,是以我们创建时候命名的设备来命名的,
进入该设备文件夹里,使用"cat dev"命令,可以显示该设备驱动的主次设备号,
实际上内核在自动创建/dev/目录下的设备节点时就是使用该信息进行创建的。
3、硬件使用的地址都是物理地址,但是在内核中使用的地址是虚拟地址,所以在操作LED设备物理地址
的时候首先需要把该地址转换为虚拟地址,这里使用"ioremap"函数进行映射,在出口函数里使用
"iounmap"函数把之前映射的地址取消
4、用户空间的write函数调用的是驱动层的write函数,它们之间必须有参数的传递过程,在驱动函数write的参数中,
第二个和第三个参数就是用户空间传递下来的缓冲区和数据的长度,使用"copy_from_user"函数把数据拷贝到
内核空间中供我们使用,同样使用"copy_to_user"函数把数据从内核空间拷贝到用户空间
5、一系列工作做完之后就可以像裸机那样去操作LED灯了,进行点亮或者熄灭
结果:
注册的字符设备,主设备号是252
进入/sys/class目录,发现注册的类
进入led_drv目录下,查看类里面的设备
再进入文件夹,查看设备的主次编号
结果和我们注册时候写的是一样的,到这里,简单的点亮LED的驱动程序就已经完成