最近在ARM+LINUX嵌入式环境下学习字符型设备的驱动程序的编写,现整理如下:
字符型设备的编写其实还是有规律可循的,我自己总结了一个框图。
驱动程序框架:
驱动程序里最重要的就是
module_init(XXX_init);和module_exit(XXX_exit);
第一句话是告诉内核编写的这段程序是驱动程序,也就是将驱动程序以模块的形式注册进内核。后一句就是用于将驱动程序卸载掉。
接着就是编写static int __init XXX_init(void)函数,其中最重要的就是注册字符型设备,给它分配一个主设备号ret=register_chrdev(XXX_MAJOR,DEVICE_NAME,&XXX_fops);这个函数的第一个参数是注册的主设备号,可以自己指定,我这里就是采用外部宏定义的方式,也可以让系统自动分配,将该参数写0即可。分配的主设备号范围是0~254.第二个参数是自己指定的设备名,也就是将驱动加入内核后添加在/dev下的设备名,这个名字很重要,应用程序在调用open()函数时也要用到这个设备名。第三个就是一个结构体指针,我们的任务就是要丰满它。
static struct file operations XXX_fops =
{
.ioctl = XXX_ioctl,
};
实际上file operations这个结构体的内容非常多如下,但是使用时并不用全部使用。
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 (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
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); ...
};
可以看到里面主要的一项就是.ioctl = XXX_ioctl,我们对硬件的一些操作就是在XXX_ioctl这个函数里面来实现的。
这些就是一个字符型设备驱动的主要内容,把以上这些函数的丰满一下,就可以组成一个完整的驱动程序。下面举例说明。
这是一个蜂鸣器的驱动。开发板是友善之臂的mini2440。
下图是蜂鸣器部分的局部电路图,可以看到只要让GPB0口输出1就可以让蜂鸣器想起来。
蜂鸣器的驱动程序:
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#define DEVICE_NAME "beep" //设备名beep
#define BEEP_MAJOR 253 //主设备号253
static int qq2440_beep_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
s3c2410_gpio_setpin(S3C2410_GPB0, cmd);
}
static struct file_operations qq2440_beep_fops = {
.owner = THIS_MODULE,
.ioctl = qq2440_beep_ioctl,
};
static int __init qq2440_beep_module_init(void)
{
int ret;
int i;
ret = register_chrdev(BEEP_MAJOR, DEVICE_NAME, &qq2440_beep_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't register major number/n");
return ret;
}
devfs_mk_cdev(MKDEV(BEEP_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);
s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);
s3c2410_gpio_setpin(S3C2410_GPB0, 0);
printk(DEVICE_NAME " initialized/n");
return 0;
}
static void __exit qq2440_beep_module_exit(void)
{
devfs_remove(DEVICE_NAME);
unregister_chrdev(BEEP_MAJOR, DEVICE_NAME);
printk(DEVICE_NAME " removed/n");
}
module_init(qq2440_beep_module_init);
module_exit(qq2440_beep_module_exit);
可以看到这里只是将我之前框图的函数丰满了一下。
1. module_init(qq2440_beep_module_init);
module_exit(qq2440_beep_module_exit);
2. static int __init qq2440_beep_module_init(void)
注册设备,初始化硬件。
3. static void __exit qq2440_beep_module_exit(void)
卸载设备的函数。
4.static struct file_operations qq2440_beep_fops =
编写file operations结构体。
5. tatic int qq2440_beep_ioctl
丰满ioctl函数。
注意一点,我的设备名是beep。
接着我们就可以把该驱动程序放在kernel-2.6.23/drivers/char的目录下。并打开Kconfig文件,添加
Config QQ2440_BEEP_MODULE //驱动程序文件名
tristate”QQ2440_BEEP_MODULE”
depends on ARCH_S3C2410
help
qq2440_beep_module test
这样在内核根目录下make menuconfig后就可以把QQ2440_BEEP_MODULE显示在菜单上。把它前面的状态改为【M】,我们以模块的形式加载。
接着我们还需要修改Makefile文件。在里面添加
obj-$(CONFIG_QQ2440_BEEP_MODULE)+=qq2440_beep_module.o
然后我们在内核根目录下make modules时,就可以生成我们所需的qq2440_beep_module.ko文件了。把它放到开发板上,通过insmod加载,rmmod卸载。
下面是我的测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/beep", 0); //可以看到,open函数里调用了设备名,这也是驱动程序和
//应用程序的连接点
if (fd < 0) {
perror("cannt open device beep");
exit(1);
}
ioctl(fd,1,0); //这里就是我们编写的ioctl函数的具体实现
close(fd);
return 0;
}
另外,再附上LED的驱动程序和测试程序,用于比较。
驱动程序:
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#define DEVICE_NAME "leds"
#define LED_MAJOR 231
static unsigned long led_table [] = {
S3C2410_GPB5,
S3C2410_GPB6,
S3C2410_GPB7,
S3C2410_GPB8,
};
static unsigned int led_cfg_table [] = {
S3C2410_GPB5_OUTP,
S3C2410_GPB6_OUTP,
S3C2410_GPB7_OUTP,
S3C2410_GPB8_OUTP,
};
static int qq2440_leds_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
switch(cmd) {
case 0:
case 1:
if (arg > 4) {
return -EINVAL;
}
s3c2410_gpio_setpin(led_table[arg], !cmd);
return 0;
default:
return -EINVAL;
}
}
static struct file_operations qq2440_leds_fops = {
.owner = THIS_MODULE,
.ioctl = qq2440_leds_ioctl,
};
static int __init qq2440_leds_init(void)
{
int ret;
int i;
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &qq2440_leds_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't register major number/n");
return ret;
}
devfs_mk_cdev(MKDEV(LED_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);
for (i = 0; i < 4; i++) {
s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
s3c2410_gpio_setpin(led_table[i], 1);
}
printk(DEVICE_NAME " initialized/n");
return 0;
}
static void __exit qq2440_leds_exit(void)
{
devfs_remove(DEVICE_NAME);
unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}
module_init(qq2440_leds_init);
module_exit(qq2440_leds_exit);
测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
int on;
int led_no;
int fd;
if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||
on < 0 || on > 1 || led_no < 0 || led_no > 3) {
fprintf(stderr, "Usage: leds led_no 0|1/n");
exit(1);
}
fd = open("/dev/leds0", 0);
if (fd < 0) {
perror("open device leds");
exit(1);
}
ioctl(fd, on, led_no);
close(fd);
return 0;
}