本篇文章以编写电位器驱动程序为例,详细介绍并总结下设备驱动的开发流程
硬件:am3354(TI)
系统内核:linux3.2
我们在有了板子和选定好使用的内核后,在开始编写驱动之前要查看原理图,即外设使用的那几个引脚。我们还需要在板子文件中(arch/arm/根据厂商芯片名.c)把引脚设置好。因为可能你使用的这几个引脚被用于别的功能了。
在确定好使用那几个引脚后,我们要确定是使用何种框架编写(是按照杂项设备(特殊的字符设备,可以节省设备号),平台设备,字符设备),一般我是习惯用字符设备的那一套流程写
在初始化函数里,我们需要做的事情:确定并申请设备号,把设备和操作函数关联起来,创建设备节点。
static int major =250;//定义主设备号
static int minior=0;
int number_of_device= 1;
struct cdev cdev;
dev_t dev =0;
struct class *myclass;
// 模块加载函数
static int light_init(void)
{
int ret;
int error;
led_init(); //设备引脚方向
dev=MKDEV(major,minior);
ret = register_chrdev_region(dev,number_of_device,"res");//申请设备号
if(ret<0)
{
printk("unable to register driver!\n");
return ret;
}
//生成设备节点
cdev_init(&cdev,&light_fops);//初始化cdev设备结构体
cdev.owner=THIS_MODULE;
cdev.ops=&light_fops;
error = cdev_add(&cdev,dev,1);//把设备结构体加入到系统中
if(error)
{
printk("unable to add driver!\n");
}
myclass=class_create(THIS_MODULE,"res");//创建类节点
if(IS_ERR(myclass))
{
printk("Err: failed in creating class.\n");
return 0;
}
printk (KERN_INFO "char device registered\n");
device_create(myclass,NULL, dev, NULL,"res");//创建设备节点
return 0;
}
里面用到的几个函数详细解释下
(1)
dev=MKDEV(major,minior);
cdev结构体成员dev_t成员定义了设备号,为32位,使用上面的宏可以通过主设备号和次设备号生成dev_t
(2)
ret = register_chrdev_region(dev,number_of_device,"res");
该函数的作用是向系统申请设备号,参数:设备号,几个设备,设备名字。该函数用于已知起始设备号的情况。成功会返回一个非负整数
(3)
cdev_init(&cdev,&light_fops);//初始化cdev设备结构体
error = cdev_add(&cdev,dev,1);//把设备结构体加入到系统中
这几个函数是要连在一块用,在内核中所有的字符设备都会记录在一个叫kobj_map结构的cdev_map变量中,这个变量包含一个散列表来快速存取所有对象。kobj_map()函数就是把字符设备编号和cdev变量一起保存到散列表里,当后续需要打开一个字符设备文件时,内核通过调用kobj_lookup()函数,根据设备编号就可以找到cdev变量
(4)
myclass=class_create(THIS_MODULE,"res");//创建类节点
if(IS_ERR(myclass))
{
printk("Err: failed in creating class.\n");
return 0;
}
printk (KERN_INFO "char device registered\n");
device_create(myclass,NULL, dev, NULL,"res");//创建设备节点
内核中定义了struct class 结构体,同时内核提供了class_create()函数,可以用来创建一个类,这个类存放在sysfs下面,一旦创建号这个类,在调用device_create()函数来创建设备节点。
在初始化完成后,我们需要完善结构体中的读,写,控制操作函数,方便上层应用的调用
struct file_operations light_fops =
{
.owner = THIS_MODULE,
//.unlocked_ioctl = light_ioctl,
.open = light_open,
.write =light_write,
.release = light_release,
};
通过对代码的剖析,我们知道编写字符驱动的框架其实很简单,掌握了本质,事情就变得简单。
附:代码
/**********************
* Copyright (C) Enbo.Tech
* Date: 2018-05-15
* Author: Liwx
* Vision: V1.0
* Introduction: this is a simple digital res driver
***********************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*******************************************/
#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
static int major =250;//定义主设备号
static int minior=0;
int number_of_device= 1;
struct cdev cdev;
dev_t dev =0;
struct class *myclass;
char write_data[10];
/*******************************************/
void led_up(void)
{
printk (KERN_INFO" chip select\n");
gpio_set_value(GPIO_TO_PIN(1,15), 0); // select chip
gpio_set_value(GPIO_TO_PIN(1,9), 1); // set up mode
}
void create_pulse(unsigned char n)
{
int num=0;
printk (KERN_INFO" chip pulse is %d\n",n);
for(num=0;num < n;num++)
{
gpio_set_value(GPIO_TO_PIN(1,8), 1);
mdelay(10);
gpio_set_value(GPIO_TO_PIN(1,8), 0);
mdelay(10);
}
}
void led_down(void)
{
gpio_set_value(GPIO_TO_PIN(1,15), 0); //select chip
gpio_set_value(GPIO_TO_PIN(1,9), 0); // set down mode
printk (KERN_INFO" chip select\n");
}
void led_init(void)
{
int result;
printk(KERN_INFO"gpio init\n");
/* Allocating GPIOs and setting direction */
result = gpio_request(GPIO_TO_PIN(1,8), "TTS_INC");
if (result != 0)
printk(KERN_INFO"gpio_request(1_8) failed!\n");
result = gpio_request(GPIO_TO_PIN(1,9), "TTS_U/D");
if (result != 0)
printk(KERN_INFO"gpio_request(1_9) failed!\n");
result = gpio_request(GPIO_TO_PIN(1,15), "TTS_CS");
if (result != 0)
printk(KERN_INFO"gpio_request(1_15) failed!\n");
result = gpio_direction_output(GPIO_TO_PIN(1,8), 1);
if (result != 0)
printk(KERN_INFO"gpio_direction(1_8) failed!\n");
result = gpio_direction_output(GPIO_TO_PIN(1,9), 1);
if (result != 0)
printk(KERN_INFO"gpio_direction(1_9) failed!\n");
result = gpio_direction_output(GPIO_TO_PIN(1,15), 1);
if (result != 0)
printk(KERN_INFO"gpio_direction(1_15) failed!\n");
}
MODULE_AUTHOR("Liwx");
MODULE_LICENSE("Dual BSD/GPL");
// 打开和关闭函数
int light_open(struct inode *inode,struct file *filp)
{
printk (KERN_INFO "open device success!\n");
return 0;
}
int light_release(struct inode *inode,struct file *filp)
{
return 0;
}
/*文件的读操作,上层对此设备调用read时会执行*/
static ssize_t light_write(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
ssize_t ret =0;
if (copy_from_user (write_data, buf, count)) {
ret = -EFAULT;
}
printk (KERN_INFO"Received \n");
if(write_data[0]==1)
{
printk (KERN_INFO" res up\n");
led_up();
create_pulse(write_data[1]);
}
else
{
printk (KERN_INFO" res down\n");
led_down();
create_pulse(write_data[1]);
}
return count;
}
struct file_operations light_fops =
{
.owner = THIS_MODULE,
//.unlocked_ioctl = light_ioctl,
.open = light_open,
.write =light_write,
.release = light_release,
};
// 模块加载函数
static int light_init(void)
{
int ret;
int error;
led_init();
dev=MKDEV(major,minior);
ret = register_chrdev_region(dev,number_of_device,"res");
if(ret<0)
{
printk("unable to register driver!\n");
return ret;
}
//生成设备节点
cdev_init(&cdev,&light_fops);
cdev.owner=THIS_MODULE;
cdev.ops=&light_fops;
error = cdev_add(&cdev,dev,1);
if(error)
{
printk("unable to add driver!\n");
}
myclass=class_create(THIS_MODULE,"res");
if(IS_ERR(myclass))
{
printk("Err: failed in creating class.\n");
return 0;
}
printk (KERN_INFO "char device registered\n");
device_create(myclass,NULL, dev, NULL,"res");
return 0;
}
// 模块卸载函数
static void light_exit(void)
{
dev_t devno = MKDEV(major,minior);
cdev_del(&cdev);
unregister_chrdev_region (devno, number_of_device);
device_destroy(myclass, devno);
class_destroy(myclass);
printk("Goodbye,cruel world!\n");
}
module_init(light_init);
module_exit(light_exit);
应用程序
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
char cmd_data[2];
int main(int argc, char **argv)
{
int i, n, fd;
int oc;
extern int optind;
const char *count_s;
const char *dir_s;
int count=0;
int dir;
fd = open("/dev/res", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/res!\n");
exit(1);
}
count=atoi(argv[1]);
if(count <=100)
printf("pulse's number is %d\n",count);
else
{
printf("please input number is less than 100\n");
return 0;
}
dir=atoi(argv[2]);
if(dir == 1)
printf("u/d is high level\n");
else
printf("u/d is low level\n");
cmd_data[0]=dir;
cmd_data[1]=count;
int byte=write(fd,cmd_data,2);
if(byte < 0)
printf("write error\n");
return 0;
}
makefile
#!/bin/bash
#通知编译器我们要编译模块的哪些源码
#这里是编译itop4412_hello.c这个文件编译成中间文件itop4412_hello.o
obj-m += res_driver_xd.o
#源码目录变量,这里用户需要根据实际情况选择路径
#作者是将Linux的源码拷贝到目录/home/topeet/android4.0下并解压的
KDIR := /home/liwx/src_xd/kernel-3.2
#当前目录变量
PWD ?= $(shell pwd)
#make命名默认寻找第一个目标
#make -C就是指调用执行的路径
#$(KDIR)Linux源码目录,作者这里指的是/home/topeet/android4.0/iTop4412_Kernel_3.0
#$(PWD)当前目录变量
#modules要执行的操作
all:
make -C $(KDIR) M=$(PWD) modules
#make clean执行的操作是删除后缀为o的文件
clean:
rm -rf *.o