字符设备有些不是数据类型的读写操作,比如控制LED灯开关、蜂鸣器响不响,串口的设置等等,要做一些控制类的操作,系统给了我们iotrl的接口。那我们就以控制LED灯的开关为例,我们的需求是使LED灯间隔1s闪烁。
用户态程序
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "led.h"
#define FILE_NAME "/dev/mydev"
int main(void)
{
int fd;
fd = open(FILE_NAME, O_RDWR);
if (0 > fd)
{
printf("Open failed.\n");
return -1;
}
while(1)
{
ioctl(fd, LED_ON);
sleep(1);
ioctl(fd, LED_OFF);
sleep(1);
}
close(fd);
}
ioctl函数原型如下:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
fd:文件描述符
request:设备相关的控制操作命令字
返回值:一般0代表成功,有些设备返回正整数代表成功及参数;负数代表失败。
其是个可变参数函数,第3个参数可选,一般是指针。
驱动程序
对应的驱动程序就是实现cdev_ops (见《字符设备驱动(2)-驱动框架及open、release》)的unlocked_ioctl方法,即:
struct file_operations {
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
};
函数接口第一个参数是文件的指针;
第二个参数是用户态传下来的设备相关的操作命令字;
第三个参数是用户态传下来的参数地址值。
需要说明的是,在比较古老的内核版本中,同iotcl系统调用的驱动接口也是ioctl,但是内核废除了这个接口,应该之前的ioctl在调用之前要获得大内核锁(BLK,一种全局的粗粒度锁),如果ioctl执行时间过长,会导致内核其他也需要大内核锁的代码需要延迟很长时间,严重降低了效率。所以新加入unlocked_ioctl,这个不加锁的ioctl,另外还有个compat_ioctl,这个接口是为了处理32位程序和64位内核兼容的一个函数接口,和体系结构有关。
回到unlocked_ioctl的实现,由于操作命令字可以有多个,很容易想到,我们可以使用if语句或者switch语句实现对应功能,所以:
static long my_ioctl(struct file *pf, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case LED_ON:
printk("led on.\n");
break;
case LED_OFF:
printk("led off.\n");
break;
default:
printk("Cmd error, cmd is %d", cmd);
return -1;
}
return 0;
}
struct file_operations cdev_ops = {
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
};
另外设备相关的操作命令字需要遵从一种编码规则,这个规则为:
31-30位是数据传输方向,00为没有传输数据,即没有参数,10为从设备读,即需要从驱动中获取数据,01为写设备,即需要把数据写入到驱动,11为双向传输,即既要写入数据又要获取数据。
29-16是如果命令带参数,参数所占用的空间大小。
15-8是每个驱动全局唯一的幻数,即指代哪一类型的设备或指设备对象,一般都是以驱动名称的头一个字符表示,比如LED,可以用‘L‘表示。
7-0是命令字,即这个设备的功能码,比如开是数字1,关是数字0。
一般定义都使用下列宏表示:
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
那么我们定义led.h如下:
#ifndef __LED_H__
#define __LED_H__
#define LED_ON _IOW('L', 1, int)
#define LED_OFF _IOW('L', 0, int)
#endif
完整驱动程序
cdev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include "led.h"
#define MAJOR_CHAR 100
#define MINOR_CHAR 0
#define MAX_LEN 64
static int my_open(struct inode *pnode, struct file *pfile)
{
printk("Open cdev.\n");
return 0;
}
static int my_close(struct inode *pnode, struct file *pfile)
{
printk("Close cdev.\n");
return 0;
}
static char kbuf[MAX_LEN] = {0};
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
ret = copy_to_user(ubuf, kbuf, len);
if (0 != ret)
{
printk("Copy to user failed.\n");
return -1;
}
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
ret = copy_from_user(kbuf, ubuf, len);
if (0 != ret)
{
printk("Copy from user failed.\n");
return -1;
}
return len;
}
static long my_ioctl(struct file *pf, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case LED_ON:
printk("led on.\n");
break;
case LED_OFF:
printk("led off.\n");
break;
default:
printk("Cmd error, cmd is %d", cmd);
return -1;
}
return 0;
}
struct cdev cdevice;
struct file_operations cdev_ops = {
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
};
//加载
static int hello_init(void)
{
dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
int ret = -1;
printk(KERN_ALERT "Hello World.\n");
//up kernel
//1、注册设备号
ret = register_chrdev_region(devno, 1, "hello");
if (0 != ret)
{
printk("Register char device failed.\n");
return ret;
}
//2、初始化字符设备结构体
cdev_init(&cdevice, &cdev_ops);
cdevice.owner = THIS_MODULE;
//3、添加字符设备结构体给内核
ret = cdev_add(&cdevice,devno , 1);
if (0 != ret)
{
//注意释放设备号
unregister_chrdev_region(devno,1);
printk("Unregister char device.\n");
return ret;
}
printk("Register char device success.\n");
//down hardware
return 0;
}
//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);
printk(KERN_ALERT "Goodbye World.\n");
// down hardware
// up kernel
//1、从内核中删除字符设备结构体
cdev_del(&cdevice);
//2、注销设备号
unregister_chrdev_region(devno, 1);
}
//注册(必须)
module_init(hello_init);
module_exit(hello_exit);
//license(必须)
MODULE_LICENSE("GPL");
//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");
加载及运行:
#insmod cdev.ko
[ 3247.741009] Hello World.
[ 3247.741015] Register char device success.
#mknod /dev/mydev c 100 0
#./a.out
[ 3355.729507] Open cdev.
[ 3355.729517] led on.
[ 3356.730390] led off.
[ 3357.731053] led on.
[ 3358.731302] led off.
[ 3359.732068] led on.
[ 3360.732772] led off.
[ 3361.733389] led on.
[ 3362.734047] led off.
tobecontinue
每周三、周六更新