目前在拿着一本<嵌入式系统原理与应用>的书肝设备驱动开发,不同于STM32\51,系统级的开发更多开始调用函数而非自己撸所有,因此踩坑在所难免,写博客来记录一下.
1. 设备驱动程序框架问题
简单的字符设备驱动程序框架
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#define DEVICE_NAME "Dev_frame0"
int DEVICE_MAJOR=50;
int num=0;
static int device_open(struct inode * inode, struct file *filp)
{
printk("user open device.\n");
return 0;
}
static ssize_t device_read(struct file * filp, char * buffer, size_t count, loff_t * f_pos)
{
printk("user read data to device.\n");
return count;
}
static ssize_t device_write(struct file * filp, const char * buffer, size_t count, loff_t * f_pos)
{
printk("user write data to device.\n");
return count;
}
static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("user ioctl running.\n");
return cmd;
}
static int device_release(struct inode * inode, struct file *filp)
{
printk("device release\n");
return 0;
}
struct file_operations device_fops = {
.owner=THIS_MODULE,
.open=device_open,
.read=device_read,
.write=device_write,
.unlocked_ioctl=device_ioctl,
.release=device_release,
};
static int device_init(void)
{
int ret;
ret = register_chrdev( DEVICE_MAJOR,DEVICE_NAME,&device_fops );
if(ret<0)
{
printk("register_chrdev failure!\n");
return ret;
}
else
{
printk("register chrdev ok!\n");
}
return 0;
}
static void device_exit(void)
{
unregister_chrdev(DEVICE_MAJOR,DEVICE_NAME);
printk("unregister chrdev ok!\n");
}
module_init(device_init);
module_exit(device_exit);
module_param(num,int,S_IRUGO);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author Name");
MODULE_DESCRIPTION("device frame with parament");
MODULE_ALIAS("device frame");
MODULE_VERSION("V1.0");
头文件解释如下:
<linux/module.h> 包含了模块所需的符号和函数定义,如MODULE_AUTHOR();等
<linux/fs.h> 包含文件系统相关的函数与头文件,包含了file_operations结构体
<linux/cdev> cdev结构的头文件
BUG1:编译时出现下面的报错
/home/ubuntu/Documents/raspberry_pi/driver_frame/Dev_frame.c:39:8: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types]
.read=device_read,
^
/home/ubuntu/Documents/raspberry_pi/driver_frame/Dev_frame.c:39:8: note: (near initialization for ‘device_fops.read’)
/home/ubuntu/Documents/raspberry_pi/driver_frame/Dev_frame.c:40:9: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types]
.write=device_write,
^
/home/ubuntu/Documents/raspberry_pi/driver_frame/Dev_frame.c:40:9: note: (near initialization for ‘device_fops.write’)
/home/ubuntu/Documents/raspberry_pi/driver_frame/Dev_frame.c:41:18: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types]
.unlocked_ioctl=device_ioctl,
这种情况是由于Dev_frame.c中使用的read/write/ioctl函数不符合头文件中定义的形式
file_operations在fs.h中的部分定义如下
struct file_operations { struct module *owner; ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); }
而需要注意read与write函数参数类型并不相同,并且参数类型size_t与返回值类型ssize_t也是不同的
更重要的是老版内核中的ioctl函数已被更新的unlock_ioctl()函数所取代,其参数也是不同的
编写带参数的模块时还需注意,用module_param();声明的模块参数需要在声明以前先定义好
BUG2:新内核make与旧内核也有区别
insmod: ERROR: could not insert module Dev_frame.ko: Invalid module format
make模块以后terminal出现如上ERROR,提示是无效的模块格式.百度了一下,这种差别还是由于新旧内核改变了模块的编译原则,即便用旧有的方式编译通过,模块依旧是不能使用的.
由于没有时间深入研究里面的原因,在这仅仅贴上适用于新内核的makefile
# Makefile2.6
ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
obj-m := Dev_frame.o
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif
针对此驱动文件的测试程序如下
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#define DEVICE_NAME "Dev_frame0"
int main()
{
int fd,num;
fd = open(DEVICE_NAME, O_RDWR, S_IRUSR|S_IWUSR);
if(fd==-1)
{
printf("%s device open failure.\n",DEVICE_NAME);
return -1;
}
printf("Reading data.\n");
read(fd,&num,sizeof(int));
printf("Writing data.\n");
write(fd, &num, sizeof(int));
printf("Control data input.\n");
ioctl(fd,5);
printf("Device closed.\n");
close(fd);
return 0;
}
BUG3:在书中,示例程序里仅包含了三个头文件<sys/stat.h> <stdio.h> <fcntl.h>
然后make的时候就出现了如下报错
test.c: In function ‘main’:
test.c:17:2: warning: implicit declaration of function ‘read’ [-Wimplicit-function-declaration]
read(fd,&num,sizeof(int));
^
test.c:19:2: warning: implicit declaration of function ‘write’ [-Wimplicit-function-declaration]
write(fd, &num, sizeof(int));
^
test.c:21:2: warning: implicit declaration of function ‘ioctl’ [-Wimplicit-function-declaration]
ioctl(fd,5);
^
test.c:23:2: warning: implicit declaration of function ‘close’ [-Wimplicit-function-declaration]
close(fd);
这明显是没有包含头文件
查了一下,主要是缺少下面两个头文件
<unistd.h>
对于类 Unix 系统,unistd.h 中所定义的接口通常都是大量针对系统调用的封装(英语:wrapper functions),如 fork、pipe 以及各种I/O原语(read、write、close 等等)。
<sys/ioctl.h>
使用ioctl时必备
然而在程序调用ioctl()的时候,使用哪个ioctl()函数是有一个顺序的,这里也暂时没有深入研究
BUG4:运行测试文件,输出设备文件打开失败
正如我们都知道的,程序与设备驱动的信息传递是通过设备节点进行的,因此测试程序的DEVICE_NAME应该为设备节点的名称/dev/Dev_frame(之前已经sudo mknod /dev/Dev_frame),而不是模块里的DEVICE_NAME=Dev_frame0!因此通过这个我们可以认识到设备节点最好与设备名称同名以方便记忆\使用,并且建立设备文件后要对其属性进行修改(sudo chmod 666 /dev/filename)