字符设备驱动(4)-unlocked_ioctl

       字符设备有些不是数据类型的读写操作,比如控制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

​ 每周三、周六更新

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值