总览
本文使用 linux-2.6.22.6 内核, 使用jz2440开发板.
上文驱动之字符设备-框架是根据jz2440教程书写的. 整个过程比较繁杂.
在此期间, 搜索了一下Linux内核开发工具, 寻得一款非常好用的 Link+IDE, 安装配置见 LinK+, 一款Linux内核开发IDE.
这款软件可以自动生成Linux驱动开发的软件模板, 可以大大减少工作量, 提高效率!
因此, 此文基于LinK+的生成的驱动框架, 对按键驱动进行开发.
使用LinK+生成驱动模板
打开 LinK+IDE, 新建工程, 选择 Linux Kernel Development(LinK+)
下面的 Device Driver Project
然后如图配置即可.
确定后, LinK+IDE会自动生成代码, 基于这个代码模板, 去实现按键功能函数即可.
注意 自动生成的模板中, 函数device_create
和linux-2.6.22.6不兼容, 需要去掉最后一个NULL!
不修改的话, 编译时有个警告, 尝试加载模块时会报错:
Unable to handle kernel NULL pointer dereference at virtual address 00000000
加载已有工程
如果已有驱动工程, 需要加入 LinK+. 核心思路就是基于Makefile编译, 而不是用Eclipse自带的工具链编译.
基本过程可以参考LinK+, 一款Linux内核开发IDE, 但比内核的设置要简单的多.
New Project...
-> C/C++
下Makefile Project with Existing Code
-> 选好项目路径, Finish
打开
- 右键工程
Properties
- 选中
C/C++ Build
->右边Builder Settings
标签:
- 取消
Use default build command
, 编译命令就是 make
- 确定
Build directory
路径是makefile所在的路径, 如 ${workspace_loc:/drv_key/KERN_SRC}
- 选中
C/C++ Build
->右边Behaviour
标签:
Build(incremental build)
, 改为 modules
. 组成编译指令 make modules
- 展开
C/C++ General
->选中Paths and Symbols
->右边Includes
标签
Add...
, 新增 /linux-2.6.22.6/include
路径到所有配置, 所有语言.
- 右键工程
Clean Project
, 相当于执行 make clean
- 右键工程
Build Project
, 相当于执行 make modules
, 编译完成.
驱动源码
drv_key.c
#include"drv_key.h"
#define DRV_KEY_N_MINORS 4 #define DRV_KEY_FIRST_MINOR 0 #define DRV_KEY_NODE_NAME "key" #define DRV_KEY_BUFF_SIZE 1
MODULE_LICENSE("GPL"); MODULE_AUTHOR("DRAAPHO");
int drv_key_major=0; dev_t drv_key_device_num; struct class *drv_key_class;
typedef struct privatedata { int nMinor; char buff[DRV_KEY_BUFF_SIZE]; struct cdev cdev; struct device *drv_key_device; } drv_key_private;
drv_key_private devices[DRV_KEY_N_MINORS];
static int drv_key_open(struct inode *inode,struct file *filp) { drv_key_private *priv = container_of(inode->i_cdev , drv_key_private ,cdev); filp->private_data = priv;
if ((priv->nMinor == 0) || (priv->nMinor == 1)) s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_INP); if ((priv->nMinor == 0) || (priv->nMinor == 2)) s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_INP); if ((priv->nMinor == 0) || (priv->nMinor == 3)) s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_INP);
PINFO("minor=%d\n", priv->nMinor); PINFO("In char driver open() function\n"); return 0; }
static ssize_t drv_key_read(struct file *filp, char __user *ubuff,size_t count,loff_t *offp) { int n=0; char key_vals[3]={0}; drv_key_private *priv; priv = filp->private_data;
if ((priv->nMinor == 0) || (priv->nMinor == 1)) key_vals[0] = !s3c2410_gpio_getpin(S3C2410_GPF0); if ((priv->nMinor == 0) || (priv->nMinor == 2)) key_vals[1] = !s3c2410_gpio_getpin(S3C2410_GPF2); if ((priv->nMinor == 0) || (priv->nMinor == 3)) key_vals[2] = !s3c2410_gpio_getpin(S3C2410_GPG3); copy_to_user(ubuff, key_vals, sizeof(key_vals));
return n; }
static const struct file_operations drv_key_fops= { .owner = THIS_MODULE, .open = drv_key_open,
.read = drv_key_read, };
static int __init drv_key_init(void) {
int i; int res;
res = alloc_chrdev_region(&drv_key_device_num,DRV_KEY_FIRST_MINOR,DRV_KEY_N_MINORS ,DRIVER_NAME); if(res) { PERR("register device no failed\n"); return -1; } drv_key_major = MAJOR(drv_key_device_num); drv_key_class = class_create(THIS_MODULE , DRIVER_NAME); if(!drv_key_class) { PERR("class creation failed\n"); return -1; }
for(i=0;i<DRV_KEY_N_MINORS;i++) { drv_key_device_num= MKDEV(drv_key_major ,DRV_KEY_FIRST_MINOR+i); cdev_init(&devices[i].cdev , &drv_key_fops); cdev_add(&devices[i].cdev,drv_key_device_num,1);
devices[i].drv_key_device =
device_create(drv_key_class , NULL ,drv_key_device_num , DRV_KEY_NODE_NAME"%d",DRV_KEY_FIRST_MINOR+i); if(!devices[i].drv_key_device) { class_destroy(drv_key_class); PERR("device creation failed\n"); return -1; } devices[i].nMinor = DRV_KEY_FIRST_MINOR+i; }
PINFO("INIT\n"); return 0; }
static void __exit drv_key_exit(void) {
int i; PINFO("EXIT\n");
for(i=0;i<DRV_KEY_N_MINORS;i++) { drv_key_device_num= MKDEV(drv_key_major ,DRV_KEY_FIRST_MINOR+i);
cdev_del(&devices[i].cdev);
device_destroy(drv_key_class ,drv_key_device_num);
} class_destroy(drv_key_class); unregister_chrdev_region(drv_key_device_num ,DRV_KEY_N_MINORS);
}
module_init(drv_key_init); module_exit(drv_key_exit);
|
drv_key.h
#define DRIVER_NAME "drv_key" #define PDEBUG(fmt,args...) printk(KERN_DEBUG"%s:"fmt,DRIVER_NAME, ##args) #define PERR(fmt,args...) printk(KERN_ERR"%s:"fmt,DRIVER_NAME,##args) #define PINFO(fmt,args...) printk(KERN_INFO"%s:"fmt,DRIVER_NAME, ##args) #include<linux/cdev.h> #include<linux/device.h> #include<linux/fs.h> #include<linux/init.h> #include<linux/kdev_t.h> #include<linux/module.h> #include<linux/types.h> #include<linux/uaccess.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h>
|
Makefile
obj-m := drv_key.o KERN_SRC := /home/draapho/share/kernel/linux-2.6.22.6/ PWD := $(shell pwd)
modules: make -C $(KERN_SRC) M=$(PWD) modules
install: make -C $(KERN_SRC) M=$(PWD) modules_install depmod -a
clean: make -C $(KERN_SRC) M=$(PWD) clean
|
测试文件 drv_key_test.c
#include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <stdio.h>
void print_usage(char *file) { printf("Usage:\n"); printf("%s <dev>\n",file); printf("eg. \n"); printf("%s /dev/key0\n", file); printf("%s /dev/key1\n", file); } int main(int argc, char **argv) { int fd; char* filename; char val; if (argc != 2) { print_usage(argv[0]); return 0; } filename = argv[1]; fd = open(filename, O_RDWR); if (fd < 0) { printf("error, can't open %s\n", filename); } else { char key_vals[3]; int cnt = 0;
while(1) { read(fd, key_vals, sizeof(key_vals)); if (key_vals[0] || key_vals[1] || key_vals[2]) { printf("%04d key pressed: %d %d %d\n", cnt++, key_vals[0], key_vals[1], key_vals[2]); } usleep(100); } } return 0; }
|
编译并测试
Ubuntu主机端
$ make clean $ make modules
$ arm-linux-gcc drv_key_test.c -o drv_key_test
|
开发板端
开发环境上, 开发板烧录好内核, 使用nfs挂载共享文件. 所以无需任何文件传输的步骤.
开发环境的具体配置可参考: 基于DHCP建立嵌入式Linux开发环境
$ insmod drv_key.ko drv_key:INIT
$ cat /proc/devices 249 drv_key 250 drv_key 251 drv_key 252 drv_key
$ ls /dev/key* /dev/key0 /dev/key1 /dev/key2 /dev/key3
$ ls /sys/class/drv_key/ key0 key1 key2 key3 $ cat /sys/class/drv_key/key3/dev 249:3
$ rmmod drv_key.ko drv_key:EXIT
|
下面进行按键测试
$ insmod drv_key.ko
$ ./drv_key_test /dev/key0 drv_key:minor=0 drv_key:In char driver open() function
$ ./drv_key_test /dev/key0 & $ top
$ kill <PID>
|
结论: 这种方式的按键驱动是有巨大风险的, 因为其性能取决于应用层怎么写, 这是不可接受的!
实际开发中, 按键多半采用中断或poll方式.
驱动框架的比较
LinK+自动生成的框架和jz2440教程的框架主要有如下区别:
alloc_chrdev_region
or register_chrdev_region
vs register_chrdev
device_create
vs class_device_create
- 子设备号Minor获取上的区别.
- 使用 private_data 更符合linux的规范
总体而言, jz2440的教程使用的框架比较老, LinK+使用的框架更合适高版本的linux内核.
原创于 DRA&PHO