摘要: 介绍了字符设备驱动的控制方式——ioctl,同时利用该方式在mini2440上实现led驱动。
驱动中,除了read,write,open,close之外,还有很多的访问方式,其中对于设备的控制,ioctl是一种重要的方式。
一、ioctl函数
int ioctl(int fd,unsigned long cmd,…)
fd:要控制的设备文件描述符
cmd:发送给设备的控制命令
…:可选参数,依赖于第二个cmd参数
当应用程序使用ioctl的时候,驱动程序将由如下驱动函数来响应:
2.6.36内核以前:
long(*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned longarg)
2.6.36内核以后:
long(*unlock_ioctl)( struct file *filp,unsigned int cmd,unsigned long arg)
二、控制实现步骤
1.定义命令
命令其实是一个整数,但是为了可读性更好,分为四个段,分别是:类型(幻数)8位,序号8位,传送方向2位,参数大小14位。
type(类型/幻数):表明这是属于哪个设备的命令;
number(序号):用来区分同一个设备的不同命令;
direction:参数传递方向,可能的值是没有传输方向,读或者写;
size:参数长度。
命令宏,Linux提供了一组宏来帮助定义命令:
_IO(type,nr):不带参数的命令;
_IOR(type,nr,datatype):从设备中读参数的命令;
_IOW(type,nr,datatype):向设备写入参数的命令;
_IOWR(type,nr,datatype):双向传输。
其中命令的type 和size是通过datatype中取对应位得来。
2.实现操作
这个模式就是switch case,我们给进去定义的命令,然后让switch去匹配,匹配到哪个就执行相应的操作,都没有匹配到就返回-EINVAL。
switchcmd
case命令A:
//执行A对应的操作
case命令B:
//执行B对应的操作
default:
//return–EINVAL
三、LED字符驱动实现
首先,mini2440上和四个led相连接的gpio口分别是PORTB的5,6,7,8四个端口。那么就需要定义寄存器的基地址,并且使用ioremap把它映射到用户空间给我们使用。
led.c如下:
#include
#include
#include
#include
#include
//#include
#include "led.h"//step 1一些列需要的头文件
#define LEDCON 0x56000010
#define LEDDAT 0x56000014//GPB5,6,7,8,控制寄存器基地址,和数据寄存器基地址,一个用来设置端口模式,一个用来给数据
unsigned int *led_config;
unsigned int *led_data;//ioremap之后得到的虚拟地址赋给他们
struct cdev cdev;//定义设备的结构体
dev_t devno;//定义设备号
int led_open(struct inode *inode,structfile *filep)//setp 6
{
led_config= ioremap(LEDCON,4);//把LEDCON控制寄存器物理地址映射到用户空间,映射4个字节
writel(0x155555,led_config);//01对应输出模式,这里把所有i/o口配置成输出模式
led_data= ioremap(LEDDAT,4);//数据寄存器的映射
return0;
}
long led_ioctl(struct file *filep,unsignedint cmd,unsigned long arg) //step 7
{
switch(cmd)
{
caseLED_ON:
writel(0x1ff,led_data);//具体命令实现处,给LED点灯
return0;
caseLED_OFF:
writel(0x000,led_data);//关灯
return0;
default:
return-EINVAL;
}
}
static struct file_operations led_fops= //step 5 对应的对led的操作实现都在这里,一个打开,一个ioctl,所以下面我们就要去写实现函数
{
.open= led_open,
.unlocked_ioctl= led_ioctl,
};
static int led_init(void)//step 3
{
cdev_init(&cdev,&led_fops);//字符设备初始化函数,将cdev和f_ops绑定起来
alloc_chrdev_region(&devno,0,1,"myled");//次设备号为0,一个设备,设备名称为myled,把动态分配得到的设备号给devno
cdev_add(&cdev,devno,1);//注册设备,devno为主设备号,设备数为1
return0;
}
static void led_exit(void)//step 4
{
cdev_del(&cdev);//删除驱动cdev
unregister_chrdev_region(devno,1);//释放设备号
}
module_init(led_init);//step 2
module_exit(led_exit);
led_app.c如下:
#include
#include
#include
#include
#include "led.h" //open需要的一系列的头文件以及led.h
int main(int argc,char *argv[])//主函数,argc保存主函数的参数个数,后面存放具体的参数
{
intfd;//文件句柄
intcmd;//定义cmd
if(argc<2)
{
printf("please enter the secondpara!\n");
return0;
}//这里判断小于2是因为我们输入的时候要输入进来是0还是1,使用方法是./ledapp 0或者./ledapp 1,需要第二个参数
cmd= atoi(argv[1]);//atoi用来把字符串转换成整形数,因为我们的cmd其实都是一个整形数
fd= open("/dev/myled",O_RDWR);//打开文件
if(cmd==1)
ioctl(fd,LED_ON);
else
ioctl(fd,LED_OFF);
//具体ioctl操作,开关灯
return0;
}
led.h如下:
#define LED_MAGIC 'L'
#define LED_ON _IO(LED_MAGIC,0)
#define LED_OFF _IO(LED_MAGIC,1)
Makefile如下:
obj-m := led.o
KDIR :=/home/passionbird/project/test/linux-2.6.32.2
all:
make-C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
rm-f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order
虚拟机下按照以下步骤:
#make
#chmod 777 led.ko
#cp led.ko /opt/rootfs
#arm-linux-gcc –static ledapp.c –oledapp
#chmod 777 ledapp
#cp ledapp /opt/rootfs
开发板上电:
#boot
#insmod led.ko
#cat proc/devices
#mknod /dev/myled c 251 0
#./ledapp 1
#./ledapp 0
#rmmod led
这篇帖子就总结到这里吧,如有不正确的地方还请指出,大家共同进步!