Helper2416-35——Linux驱动——第一个驱动程序(点亮LED)
[复制链接]
本帖最后由 yuanlai2010 于 2014-8-26 14:29 编辑
Linux驱动——第一个驱动程序(点亮LED)
参与Helper2416开发板助学计划心得
经过几天的琢磨,今天终于完成了我的第一个驱动,也算是敲开了驱动编写的大门
描述:
Linux驱动程序大致分为三种:字符设备驱动、块设备驱动、网络驱动,.这个LED驱动程序就是最简单的字符驱动程序中的最简单的一个实例,不过却也涵盖了驱动程序开发的基本步骤。
先上源码,然后再一点点解剖:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int LED_Major = 0;
static struct class *led_driver_class;
volatile unsigned long *GPBCON, *GPBDAT;
static int s3c24xx_led_open(struct inode *inode, struct file *file)
{
printk("s3c24xx_led_open\n");
(*GPBCON) |= 1<<2;
return 0;
}
static int s3c24xx_led_close(struct inode *inode, struct file *file)
{
printk("s3c24xx_led_close\n");
return 0;
}
static int s3c24xx_led_write(struct file *file, char __user *buff, size_t count, loff_t *offp)
{
int value;
copy_from_user(&value, buff, count);
if(value == 1){
(*GPBDAT) &= ~(1<<1);
}else if(value == 0){
(*GPBDAT) |= 1<<1;
}else{
printk("zhe writed value must be 1 or 0\n");
return 1;
}
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = s3c24xx_led_open,
.write = s3c24xx_led_write,
.release = s3c24xx_led_close,
};
static int __init led_init(void)
{
LED_Major = register_chrdev(LED_Major, "led_driver", &led_fops);
led_driver_class = class_create(THIS_MODULE, "led_driver");
device_create(led_driver_class, NULL, MKDEV(LED_Major, 0), NULL, "led_driver");
GPBCON = (volatile unsigned long*)ioremap(0x56000010,8);
GPBDAT = GPBCON + 1;
printk("led_init\n");
return 0;
}
static void __exit led_exit(void)
{
unregister_chrdev(LED_Major, "led_driver");
device_destroy(led_driver_class, MKDEV(LED_Major, 0) );
class_destroy(led_driver_class);
iounmap(GPBCON);
printk("led_exit\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_AUTHOR("yuanlai");
MODULE_DESCRIPTION("helper2416 led Driver");
MODULE_LICENSE("GPL");复制代码
第一步:搭好驱动程序的框架
首先就是头文件:
头文件比较多,暂且还不知道这些头文件是不是都有用到,但是至少驱动能够编译成功。
接着就是structfile_operations 这个结构体及驱动处理函数:
加载驱动的时候,就需要把这个struct file_operations的结构体注册进内核,该结构体在头文件linux/fs.h定义,应该是告诉系统这个驱动程序具体有哪些函数用来操作硬件的,然后内核就是通过这个结构体的成员(函数指针)来找到这些函数来操作硬件的,以下便是这个结构体看起来的样子:
struct file_operations {
struct module *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};复制代码
对于具体要用到哪些成员,就需要根据驱动程序的具体情况来决定了。不过.owner = THIS_MODULE, 这条是一定要有的。由于只是控制一盏LED的亮灭状态,所以我也只用到了open、write、close这几个成员。
最后就是模块初始化与卸载处理函数:
模块的初始化这个函数(led_init)是在insmod的时候自动运行的,在代码中还需要用module_init()这个宏来包装一下。在这个函数中就需要把上面提到的structfile_operations这个结构体注册到内核里面去,这里使用register_chrdev()函数完成注册。
接着还需要在/dev/目录下创建相应设备节点,尽管可以通过shell用命令手工创建,但那是在太麻烦了点,所以通过先创建一个类,然后再在这个类下创建一个所需要的设备文件。
同样的模块退出函数(led_exit())是在rmmod的时候自动运行的,同样也需要module_exit()这个宏来包装一下。这里需要注销之前注册的structfile_operations结构体,然后销毁之前创建的类和设备(删除/dev/目录下的设备节点)
忘了,在最后的最后,还需要加上这些信息,尤其是MODULE_LICENSE("GPL");这条,不然很多时候编译会出错。
MODULE_AUTHOR("yuanlai");
MODULE_DESCRIPTION("helper2416 led Driver");
MODULE_LICENSE("GPL");复制代码
第二部:完善硬件操作
由于这仅仅是一个最简单的一盏LED的驱动,所以硬件操作比较简单,不过由于Linux操作的都是虚拟地址,所以当操作实际硬件的时候,就需要把物理地址映射成虚拟地址,具体的虚拟地址是多少这个是由系统来决定的,我们可以通过ioremap()这个函数来获得硬件物理地址所对应的虚拟地址,当然在驱动卸载时也记得用iounmap()解除此次映射。为了方便,我在驱动加载的时候,就用ioremap完成了IO的映射,并在led_exit()中用iounmap取消映射。然后具体的硬件操作都体现在s3c24xx_led_open()、s3c24xx_led_write()这两个函数里面了,怎么样,是不是和裸机差不多?
第三步:编译模块(驱动)
编译模块是一定需要有内核源码树的,我这里直接使用的是BOSS提供的Fedora18虚拟机镜像里的/home/jyxtec/workspace/kernel/s3c-linux
下面是Makefile文件具体内容,直接执行make后就在当前目录生成了我期待已久的.ko文件了。
obj-m := led_module.o
KERNEL_DIR :=/home/jyxtec/workspace/kernel/s3c-linux
PWD := $(shell pwd)
all:
make ARCH=arm CROSS_COMPILE=arm-linux- -C /home/jyxtec/workspace/kernel/s3c-linux M=$(PWD) modules
clean:
rm *.o *.ko复制代码
驱动测试
拿到.ko文件后当然就是急着去测试咯,只有测试成功了才是真正值得兴奋的时刻。
测试程序代码如下,实现频率为2秒的LED闪烁。
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
int value;
fd = open("/dev/led_driver", O_WRONLY);
if(fd < 0)
{
perror("cannot open device led");
exit(1);
}
while(1)
{
/* 点亮 LED */
value = 1;
write(fd, &value, 4);
sleep(1);
/* 熄灭 LED */
value = 0;
write(fd, &value, 4);
sleep(1);
}
close(fd);
return 0;
}复制代码
首先需要把.ko 文件和测试程序的可执行文件发送到目标版(Helper2416)
[root@jyxtec Linux]# ls
led_module.ko led_test
[root@jyxtec Linux]#复制代码
然后执行 insmod led_module.ko 打印出以下信息,模块应该是已经加载成功了。
[root@jyxtec Linux]# insmod led_module.ko
led_init
[root@jyxtec Linux]#复制代码
为了验证是否整的已经加载,我们再来查看/dev/目录是否有我们所期待的设备文件 led_driver ., 确实是有的。
[root@jyxtec Linux]# ls -l /dev/led*
crw-rw---- 1 root root 252, 0 Jan 1 03:40 /dev/led_driver
[root@jyxtec Linux]#复制代码
接着就是运行测试程序了,由于我在驱动程序的open函数中加入打印信息的语句,所以会打印下面这些信息,然后开发板上的LED已经闪烁起来,由于测试程序是个无限循环,所以最后只能通过Ctle + C 结束程序。
minicom.png (64.02 KB, 下载次数: 0)
2014-8-26 14:24 上传
测试成功,第一个驱动程序成功的运行起来了!
论坛ID:yuanlai2010
发表时间:2014-08-26