本次驱动编写使用:OK6410+linux3.0.1
在内核内部使用struct cdev结构来表示字符设备。因此在内核调用字符设备前需要分配并且注册一个cdev结构体。在代码中需要包括<linux/cdev.h>头文件。
字符设备驱动中需要编写相关的驱动函数,从而与应用程序中的open()、write()等函数形成对应。编写驱动函数后利用file_operations使驱动函数与应用程序中的函数形成对应关系。接着利用函数cdv_init初始化cdev结构体
void cdev_init(struct cdev *cdev,struct file_operations *fops)
接着利用函数cdv_add()告诉内核该结构的信息。
int cdev_add(struct cdev *dev,dev_t num,unsigned int count)
其中dev_t num表示设备号。每个字符设备都有自己的主设备号和次设备号,其中主设备号用于区分驱动程序,次设备号由内核所用,用于指定设备文件所指的设备。dev_t类型在<linux/types.h>中定义,是一个32位数,其中12位表示主设备号,20位表示次设备号。
为了获得设备号,可以使用函数register_chrdev_region()获得设备号
int register_chrdev_region(dev_t first, unsigned int count, char *name)
如果我们明确知道想要的设备号的话,使用该函数会有好的效果。但是当我们不知道哪些设备号被调用了的时候,使用动态分配设备号的方法会更好。使用该方法也有利于你的程序在不同的板子上运行,避免别的板子的系统中已经使用你想要用的设备号。
动态分配函数为:
int alloc_chrdev_region(dev_t *dev,
unsigned int firstminor,
unsigned int count,
char *name)
当不使用设备时应该将设备编号进行释放,使用以下函数
void unregister_chrdev_region(dev_t first,unsigned int count)
想要移除一个字符设备,则使用cdev_del()
void cdev_del(struct cdev *dev)
在一些代码中会看到有的驱动不使用cdev接口进行定义,而是用以下方法:
int register_chrdev(unsigned int major,
const char *name,
struct file_operations *fops);
这是一种尚未升级到2.6版本接口的老代码。虽然这些接口仍然可以工作,但是新的代码应该尽量使用新的接口。
当加载驱动模块后,使用mknod创建字符设备节点后,可对该设备文件进行操作
或者是使用创建类和类设备的方法创建相应的设备文件,这样在加载了驱动模块后可直接使用。具体方法参见class_create()和device_create()的用法
驱动代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
struct class *led_class;
struct class_device *led_class_device;
volatile unsigned long *gpmcon = NULL;
volatile unsigned long *gpmdat = NULL;
struct cdev cdev;
dev_t dev;
int led_open(struct inode *inode,struct file *file)
{
gpmcon = (volatile unsigned long *)ioremap(0x7f008820,4);
writel(0x1111,gpmcon);
gpmdat = (volatile unsigned long *)ioremap(0x7f008824,4); /* 指针加1是以数据类型相加的*/
printk("open!\n");
return 0;
}
ssize_t led_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("$$$$$$$$$$led_read$$$$$$$$$\n");
return count;
}
ssize_t led_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
char mbuf[10];
printk("write!\n");
copy_from_user(mbuf,buf,count);
switch(mbuf[0])
{
case 1:
//writel(0x01, gpmdat);
*gpmdat &= ~(0x0F);
break;
case 0:
//writel(0x0F,gpmdat);
*gpmdat |= 0x0F;
break;
default:
break;
return count;
}
}
struct file_operations led = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
};
int led_init(void)
{
cdev_init(&cdev,&led);
alloc_chrdev_region(&dev,0,1,"led_chrdev");
cdev_add(&cdev,dev,1);
led_class = class_create(THIS_MODULE,"led");
/*if(IS_ERR(led_class))
return PTR_ERR(led_class);*/
led_class_device = device_create(led_class,NULL,dev,NULL,"led1");
/* if(unlikely(IS_ERR(led_class)))
return PTR_ERR(led_class); */
return 0;
}
void led_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(dev,1);
device_unregister(led_class_device);
class_destroy(led_class);
}
MODULE_LICENSE("GPL");
module_init(led_init);
module_exit(led_exit);
测试代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
char val = 1;
fd = open("/dev/led1",O_RDWR);
if(fd < 0)
{
printf("Can't open\n");
}
printf("argv[1] = %s\n",argv[1]);
if(argc != 2)
{
printf("Wrong usage!,Usage: test <on|off>\n");
}
else if(strcmp(argv[1],"on") == 0)
{
val = 1;
write(fd,&val,1);
printf("check!\n");
}
else if(strcmp(argv[1],"off") == 0)
{
val = 0;
write(fd,&val,1);
}
printf("end!\n");
close(fd);
return 0;
}
Makefile
KERDIR := /home/wade/arm/cha_driver/linux-3.0.1.new
all:
make -C $(KERDIR) M=`pwd` modules
clean:
make -C $(KERDIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += drv.o