Linux系统驱动(三)ioctl函数

一、ioctl函数

Linux内核开发者想要将数据的读写和设备的控制分开完成操作,

所以设计了ioctl函数。也就是说数据的读写在read/write中完成,
而设备的控制在ioctl中完成。

(一)函数格式

US:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...)
功能:设备的控制操作
参数:
	@fd:文件描述符
	@request:命令码
	@...:可变参,填写必须填写地址
返回值:成功返回0,失败返回-1,置位错误码
-----------------------------------------------
KS:
#include <linux/uaccess.h>
file_operations:
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long myled_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
	//request传给了cmd
	//...传递给arg,即arg存放的是参数的地址,接收到后需要强转为指针
  • 注:ioctl是应用层接口,内核层次也有与之对应的接口函数
  • 底层函数的返回值返回给上层应用层函数

(二)ioctl命令码的组成

  • 注:*Documentation目录下存放帮助文档,如果需要了解函数如何使用,就进这个目录下
    在这里插入图片描述

1. 命令码的组成

linux@ubuntu:~/linux-5.10.61/Documentation$ vi ./userspace-api/ioctl/ioctl-decoding.rst
在文档中可以看到如下信息:
在这里插入图片描述

在这里插入图片描述
[31:30] 读写标志位:01 — 写,10 — 读;11 — 读写;此处的方向位是相对用户来定的,用户具有写权限或者用户具有读权限
[29:16] 第三个参数占内存的大小,单位是字节;
[16:8] 每个驱动由唯一的ascii字符来标识;
[7:0] 功能码;

2. 自己封装命令码

#define LED_ON 0b01 00000000000100  01001100 00000001
				|			|			|		|--->功能码,自定义
				|			|			|-->此处以'L'为例0114--0b01001100
				|			|-->此处以int型数据为例,此处定义4字节
				|-->读写标志位
#define LED_OFF 0b01 00000000000100  01001100 00000000

2. 内核提供了封装命令码的宏

通过内核的宏封装命令码:
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
参数:
	@type:类型,即用来标识驱动的字符
	@nr	 :功能码,来区分同一驱动不同的功能
  • 注:此处的size要传数据类型,因为这个宏传入的参数实际在_IOC这个宏中要使用sizeof来确定大小。
#define _IOC(dir,type,nr,size) \
        (((dir)  << 30) | \
         ((type) << 8) | \
         ((nr)   << 0) | \
         ((size) << 16))

使用示例:

#define LED_ON   _IOW('L',0,int)
#define LED_OFF  _IOW('L',1,int)

(三)使用示例

功能需求:
通过ioctl函数实现LED灯的流水

需求分析:
用户层通过ioctl函数,将命令码和

代码实现:

1. 驱动

myioctl.h

#ifndef __MYIOCTL_H__
#define __MYIOCTL_H__

#define CHRNAME "myioctl"
//寄存器基地址---物理地址(使用前需要先转换为虚拟地址)
#define RCC_AHB4_BASE   0x50000A28
#define GPIOE_BASE      0x50006000
#define GPIOF_BASE      0x50007000

/***RCC_AHB4***/
typedef struct{
    unsigned int gpioA:1;
    unsigned int gpioB:1;
    unsigned int gpioC:1;
    unsigned int gpioD:1;
    unsigned int gpioE:1;
    unsigned int gpioF:1;
    unsigned int gpioG:1;
    unsigned int gpioH:1;
    unsigned int gpioI:1;
    unsigned int gpioJ:1;
}rcc_bit_1_t;
typedef struct{
    rcc_bit_1_t rcc_ahb4;
}rcc_ahb4_t;

//使用位域填充寄存器,方便操作
//对应1个bit管理一个引脚的寄存器
//unsigned int是四字节大小,刚好等于一个寄存器的大小,使用位域也不会再对unsigned int类型进行压缩
//即使没有占满四字节,该结构体类型仍然占用四字节
typedef struct{
    unsigned int gpiox0:1;
    unsigned int gpiox1:1;
    unsigned int gpiox2:1;
    unsigned int gpiox3:1;
    unsigned int gpiox4:1;
    unsigned int gpiox5:1;
    unsigned int gpiox6:1;
    unsigned int gpiox7:1;
    unsigned int gpiox8:1;
    unsigned int gpiox9:1;
    unsigned int gpiox10:1;
    unsigned int gpiox11:1;
    unsigned int gpiox12:1;
    unsigned int gpiox13:1;
    unsigned int gpiox14:1;
    unsigned int gpiox15:1;
}gpio_bit_1_t;
//对应两bit管理一个引脚的寄存器
typedef struct{
    unsigned int gpiox0:2;
    unsigned int gpiox1:2;
    unsigned int gpiox2:2;
    unsigned int gpiox3:2;
    unsigned int gpiox4:2;
    unsigned int gpiox5:2;
    unsigned int gpiox6:2;
    unsigned int gpiox7:2;
    unsigned int gpiox8:2;
    unsigned int gpiox9:2;
    unsigned int gpiox10:2;
    unsigned int gpiox11:2;
    unsigned int gpiox12:2;
    unsigned int gpiox13:2;
    unsigned int gpiox14:2;
    unsigned int gpiox15:2;
}gpio_bit_2_t;
//定义gpio的类型
typedef struct{
    gpio_bit_2_t MODER;
    gpio_bit_1_t OTYPER;
    gpio_bit_2_t OSPEEDR;
    gpio_bit_2_t PUPDR;
    gpio_bit_1_t IDR;
    gpio_bit_1_t ODR;
}gpio_t;

/***定义命令码***/
#define LED1_ON _IOW('L',00,int)
#define LED1_OFF _IOW('L',01,int)
#define LED2_ON _IOW('L',10,int)
#define LED2_OFF _IOW('L',11,int)
#define LED3_ON _IOW('L',20,int)
#define LED3_OFF _IOW('L',21,int)

#endif

myioctl.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include "myioctl.h"

int major; //因为该参数不仅要在注册设备的函数中使用,还要再销毁设备的函数中使用,因此必须定义为全局变量
rcc_ahb4_t *mrcc_ahb4;
gpio_t * mgpioe;
gpio_t * mgpiof;
//实现驱动层的打开函数
int myioctl_open(struct inode *inode, struct file *file){
    //1. 将物理地址映射为虚拟地址
    mrcc_ahb4 = ioremap(RCC_AHB4_BASE,sizeof(rcc_ahb4_t));
    //ioremap函数第一个参数是unsigned long类型的参数
    if(NULL == mrcc_ahb4){//失败返回NULL
        pr_err("ioremap error");
        return -ENOMEM;
    }
    mgpioe=ioremap(GPIOE_BASE,sizeof(gpio_t));
    if(NULL == mrcc_ahb4){//失败返回NULL
        pr_err("ioremap error");
        return -ENOMEM;
    }
    mgpiof=ioremap(GPIOF_BASE,sizeof(gpio_t));
    if(NULL == mrcc_ahb4){//失败返回NULL
        pr_err("ioremap error");
        return -ENOMEM;
    }
    //2. 初始化LED灯,并且将其初始值设为熄灭状态
    //时钟使能
    mrcc_ahb4->rcc_ahb4.gpioE=1;
    mrcc_ahb4->rcc_ahb4.gpioF=1;
    //输出模式01
    mgpioe->MODER.gpiox10=1;
    mgpioe->MODER.gpiox8=1;
    mgpiof->MODER.gpiox10=1;
    //输出低电平:熄灭
    mgpioe->ODR.gpiox10=0;
    mgpiof->ODR.gpiox10=0;
    mgpioe->ODR.gpiox8=0;

    return 0; //注意注意!!!!驱动中如果函数返回值不是void,就必须加return
}
//实现驱动层的关闭函数
int myioctl_close(struct inode *inode, struct file *file){
    //取消地址映射
    iounmap(mrcc_ahb4);
    iounmap(mgpioe);
    iounmap(mgpiof);
    return 0;
}
//实现驱动层的ioctol函数
long myioctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
    //arg:第三个参数的首地址,由cmd[29:16]决定大小,此处无需第三个参数
    switch(cmd){
        case LED1_ON:
            //判断是否有写权限
            if(cmd & (0x1<<30)){
                mgpioe->ODR.gpiox10=1;
            }
            else{
                pr_err("NO Write pression\n");
            }
            break;
        case LED1_OFF:
            //判断是否有写权限
            if(cmd & (0x1<<30)){
                mgpioe->ODR.gpiox10=0;
            }
            else{
                pr_err("NO Write pression\n");
            }
            break;
        case LED2_ON:
            //判断是否有写权限
            if(cmd & (0x1<<30)){
                mgpiof->ODR.gpiox10=1;
            }
            else{
                pr_err("NO Write pression\n");
            }
            break;
        case LED2_OFF:
            //判断是否有写权限
            if(cmd & (0x1<<30)){
                mgpiof->ODR.gpiox10=0;
            }
            else{
                pr_err("NO Write pression\n");
            }
            break;
        case LED3_ON:
            //判断是否有写权限
            if(cmd & (0x1<<30)){
                mgpioe->ODR.gpiox8=1;
            }
            else{
                pr_err("NO Write pression\n");
            }
            break;
        case LED3_OFF:
            //判断是否有写权限
            if(cmd & (0x1<<30)){
                mgpioe->ODR.gpiox8=1;
            }
            else{
                pr_err("NO Write pression\n");
            }
            break;
    }

    return 0; //应用层ioctl成功返回0; 失败返回-1
}

//file_operations结构体
const struct file_operations myfops={
    .open=myioctl_open,
    .release=myioctl_close,
    .unlocked_ioctl=myioctl_ioctl,
};

/***驱动三要素***/
static int __init myioctl_init(void){
    //入口注册设备
    major=register_chrdev(0,CHRNAME,&myfops); //第一个参数为0,表示由系统分配主设备号,此时返回值就是系统分配的主设备号
    if(major < 0){//说明出错,返回了错误码,错误码均为负数
        pr_err("register_chrdev error:%d\n",major);
        return major; //失败返回错误码
    }
    printk("major=%d\n",major);
    return 0; //成功返回0
}

static void __exit myioctl_exit(void){
    //出口销毁设备
    unregister_chrdev(major,CHRNAME);
}

module_init(myioctl_init);
module_exit(myioctl_exit);
MODULE_LICENSE("GPL");

2. 应用

test.h

#ifndef __TEST_H__
#define __TEST_H__

#include <stdio.h>
#include <sys/ioctl.h> //yingyong
#include <my_head.h>

/***定义命令码***/
#define LED1_ON _IOW('L',00,int)
#define LED1_OFF _IOW('L',01,int)
#define LED2_ON _IOW('L',10,int)
#define LED2_OFF _IOW('L',11,int)
#define LED3_ON _IOW('L',20,int)
#define LED3_OFF _IOW('L',21,int)

#endif

test.c

#include "test.h"

int main(int argc, char const *argv[])
{
    //入参合法性检查
    if(2!=argc){
        printf("Usage:%s dev_file!\n",argv[0]);
        exit(-1);
    }

    int fd=0;
    fd=open(argv[1],O_RDWR);
    if(0 > fd){
        printf("open error\n");
        exit(-1);
    }
	//使用死循环,实现三个LED灯轮流点亮
    while(1){
        ioctl(fd,LED1_ON);
        sleep(1);
        ioctl(fd,LED1_OFF);
        ioctl(fd,LED2_ON);
        sleep(1);
        ioctl(fd,LED2_OFF);
        ioctl(fd,LED3_ON);
        sleep(1);
        ioctl(fd,LED3_OFF);
    }
    return 0;
}
  • 注:在宏定义中##表示字符串拼接
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值