22 自定义ioctl命令及用户进程操作GPIO

自定义ioctl命令


ioctl ---> kernel --->  cdev.fops->unlocked_ioctl(...)

系统调用ioctl函数的作用:用户进程用于通过相应的设备驱动来获取或者设置硬件状态。


在字符设备驱动里,unlock_ioctl函数原形:

long (*unlocked_ioctl) (struct file *fl, unsigned int cmd, unsigned long arg);
    //cmd参数需要与应用程序调用ioctl时的参数约定,才可以表示一种功能
    //cmd的值不能为2,内核里保留此值
    //man 2 ioctl_list:可以查看系统里的ioctl关于cmd的参数值
cmd是32位的数,分成以下四个部分:
    1.最高两位表示方向:读/写/读写(输出/输入/输出输入)
    2.16位至第29位表示ioctl的第三个参数的大小(unlocked_ioctl的arg)
    3.8位至第15位表示ioctl命令的类型
    4.最低8位表示ioctl命令类型里的第几个命令

include/asm-generic/ioctl.h:

                 'k'
_IOC_DIRBITS << 30 | _IOC_SIZEBITS << 16 | _IOC_TYPEBITS << 8 | _IOC_NRBITS
#define _IOC_NRBITS     8       //顺序号   0 --- 7
#define _IOC_TYPEBITS   8       //类型  8 --- 15
#define _IOC_SIZEBITS   14      //ioctl第三个参数的大小 16 --- 29
#define _IOC_DIRBITS    2       //方向, 有没有参数, 读/写  30 --- 31

//方向位
# define _IOC_NONE  0U
# define _IOC_WRITE 1U
# define _IOC_READ  2U

用于生成一个ioctl的命令的宏定义:

#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \
     ((type) << _IOC_TYPESHIFT) | \
     ((nr)   << _IOC_NRSHIFT) | \
     ((size) << _IOC_SIZESHIFT))

如:#define LED_ON _IOC(_IOC_WRITE, 'L', 99, 0);
                        //方向, 类型, 第99个命令, ioctl的第三个参数大小为0(即没有第三个参数)

定义一个没有指定方向,没有第三个参数,只指定ioctl命令的类型及命令类型里的序号。
    #define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)

    #define _IOC_TYPECHECK(t) \
        ((sizeof(t) == sizeof(t[1]) && \
          sizeof(t) < (1 << _IOC_SIZEBITS)) ? \
          sizeof(t) : __invalid_size_argument_for_IOC)

定义一个驱动里输出参数值(用户进程读),指定ioctl命令的类型及命令类型里的序号及第三个参数的大小。
    #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)))

用于获取ioctl命令里方向,类型等信息:

#define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)       (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)         (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)       (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

_IOC_DIR(nr)  //获取nr命令里的方向值
_IOC_TYPE(nr) //获取nr命令里的类型
_IOC_NR(nr)   //获取nr命令里的顺序号
_IOC_SIZE(nr) //获取nr命令里的第三个参数大小

用户进程是不可以直接操作硬件,只能通过调用设备驱动,让设备驱动来操作硬件。

设备驱动又可以实现一个字符设备驱动接口让用户进程来调用。


控制LED(test.c):

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <mach/gpio.h>
#include <linux/gpio.h>

#define     MYMA    1234
#define     MYMI    5500
#define     COUNT   1

#define     LED_GPIO    GPIOA(15)

dev_t devid;

struct cdev mycdev;

long my_unlocked_ioctl(struct file *fl, unsigned int cmd, unsigned long arg)
{
    if(cmd)
        gpio_set_value(LED_GPIO, 1);
    else
        gpio_set_value(LED_GPIO, 0);

    return 0;
}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = my_unlocked_ioctl,
};

static int __init test_init(void)
{
    int ret;

    devid = MKDEV(MYMA, MYMI);
    ret = register_chrdev_region(devid, COUNT, "mydev");
    if(ret < 0)
    {
        printk("register dev num failed\n");
        return ret;
    }

    cdev_init(&mycdev, &fops);
    mycdev.owner = THIS_MODULE;

    ret = cdev_add(&mycdev, devid, COUNT);
    if(ret < 0)
    {
        unregister_chrdev_region(devid, COUNT);
        printk("add mycdev failed\n");
        return ret;
    }

    ret = gpio_request(LED_GPIO, "myled");
    if(ret < 0)
    {
        unregister_chrdev_region(devid, COUNT);
        cdev_del(&mycdev);
        printk("request myled failed\n");
        return ret;
    }

    gpio_direction_output(LED_GPIO, 0);

    printk("init success\n");

    return 0;
}

static void __exit test_exit(void)
{
    gpio_set_value(LED_GPIO, 1);
    gpio_free(LED_GPIO);
    unregister_chrdev_region(devid, COUNT);
    cdev_del(&mycdev);

    printk("exited\n");
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

用户功能测试(app_test.c):

#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>

#define     LED_ON      1
#define     LED_OFF     0

int main(void)
{
    int fd;

    fd = open("/dev/mydev", O_RDWR);
    if(fd < 0)
        return fd;

    printf("this is the led ioctl to toggle\n");

    while(1)
    {
        ioctl(fd, LED_ON);
        sleep(1);
        ioctl(fd, LED_OFF);
        sleep(1);
    }

    return 0;
}

Makefile文件:

obj-m += test.o

KSRC := /目录路径/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-

all : 
    make -C $(KSRC) modules M=`pwd`

.PHONY : clean
clean : 
    make -C $(KSRC) modules clean M=`pwd`

编译加载驱动模块后,需创建设备文件后,才能运行功能测试程序:

mknod  /dev/mydev c 1234  5500

通过ioctl获取烟雾传感器的例子(test.c):

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#include <linux/ioctl.h> //生成ioctl命令的宏定义

#define MYMA  1234
#define MYMI  3344
#define COUNT    1

//烟雾传感器接在PA(7), 感应到烟雾时输出低电平,正常高电平
#define DETECT_IO   GPIOA(7)

自定义的ioctl命令
#define DETECTOR_MAGIC  0xAF
#define DETECT_RET   (_IOR(DETECTOR_MAGIC, 0x1, int))

dev_t devid; //用于存放设备号
struct cdev mycdev; 

//如有第三个参数,则arg的值为用户进程ioctl调用时传进来的地址
long myioctl(struct file *fl, unsigned int cmd, unsigned long arg)
{
    if (DETECTOR_MAGIC != _IOC_TYPE(cmd))
        return -EINVAL;

    if (DETECT_RET == cmd)
        *(int *)arg = gpio_get_value(DETECT_IO);

    return 0; //返回值表示操作是成功与否
}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = myioctl,
};

static int __init test_init(void)
{
    int ret;

    devid = MKDEV(MYMA, MYMI); //生成一个设备号
    ret = register_chrdev_region(devid, COUNT, "mydev");
    if (ret < 0)
        goto err0;

    cdev_init(&mycdev, &fops);
    mycdev.owner = THIS_MODULE;
    ret = cdev_add(&mycdev, devid, COUNT);
    if (ret < 0)
        goto err1;  

    gpio_request(DETECT_IO, "mydev"); //请求gpio口
    gpio_direction_input(DETECT_IO); //配置gpio口为输入

    return 0;
err1:
    unregister_chrdev_region(devid, COUNT);
err0:
    return ret;
}

static void __exit test_exit(void)
{
    unregister_chrdev_region(devid, COUNT);
    cdev_del(&mycdev);
    gpio_free(DETECT_IO); //释放gpio
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

app_test.c:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/ioctl.h>

自定义的ioctl命令
#define DETECTOR_MAGIC  0xAF
#define DETECT_RET   (_IOR(DETECTOR_MAGIC, 0x1, int))

int main(void)
{
    int fd, ret, val;

    fd = open("/dev/mydev", O_RDWR);
    if (fd < 0)
    {
        perror("open dev");
        return 1;
    }

    while (1)
    {
        ret = ioctl(fd, DETECT_RET, &val);
        if (ret < 0)
            break;

        if (!val) //有烟雾感应到了
        {
            printf("smoke detected ...\n");
            break;
        }
    }

    close(fd);
    return 0;
}
<think>嗯,用户问的是Linux能否在应用层直接操作I2C从设备的原理。首先,我需要回忆一下Linux的I2C子系统结构。记得I2C在Linux中有内核驱动支持,通常是通过内核模块来管理设备的。那用户想知道的是应用层直接操作,也就是不经过内核驱动,对吧? 接下来,应该考虑用户的实际使用场景。可能用户是嵌入式开发者,或者在做一些需要直接控制I2C设备的工作,比如传感器或者自定义硬件。这时候,直接操作I2C可能涉及到绕过内核驱动,直接通过用户空间的接口访问硬件。这种情况下,用户可能需要更高的灵活性或者对实时性有要求。 然后,我需要确认Linux提供的机制。记得Linux有I2C dev接口,也就是/dev/i2c-*设备文件,允许用户空间程序通过ioctl调用来控制I2C适配器。这样用户就可以在应用层打开这些设备文件,发送读写命令,直接和I2C从设备通信。这就是用户所说的“直接操作”的原理吧。 不过,用户可能还存在疑问,比如这种方式是否需要特定的权限,或者和内核驱动的区别。例如,当使用I2C dev时,用户空间的程序需要正确的权限来访问设备节点,可能需要root权限或者将用户加入特定的组。另外,直接操作I2C可能会和内核中已有的驱动冲突,导致设备被多个程序访问的问题。 还需要解释为什么这样的机制是可行的。因为I2C总线本身是主从结构,主设备(通常是SoC的I2C控制器)控制通信时序,而用户空间的程序通过ioctl指定从设备地址和读写操作,底层由内核的I2C适配器驱动处理具体的时序和电气信号。这样用户层就不需要关心具体的硬件细节,只需通过标准的接口发送命令即可。 可能用户还有更深层的需求,比如他们是否应该使用内核驱动还是用户空间驱动。这时候需要比较两者的优缺点。内核驱动通常更高效,能处理中断和DMA,适合性能敏感的应用;而用户空间驱动更灵活,便于调试和快速开发,但可能牺牲一些性能和实时性。 另外,用户可能关心如何具体实现,比如使用哪些系统调用,代码示例是什么样的。例如,使用open打开/dev/i2c-0,然后用ioctl设置从设备地址,接着用read/write或者i2c_msg结构体来传输数据。这部分可能需要提供简单的示例代码或者命令,如i2c-tools中的i2cset和i2cget。 还要注意安全性问题,直接操作硬件可能会影响系统稳定性,特别是如果多个进程同时访问同一个I2C设备的话,需要适当的同步机制。比如使用I2C_RDWR ioctl可以组合多个消息在一个传输中,减少竞争条件的风险。 总结来说,用户的问题核心在于理解Linux如何允许应用层绕过传统的内核驱动模型,直接通过设备文件与I2C从设备交互。这涉及到Linux的I2C子系统设计,用户空间接口的存在,以及具体的实现方法和注意事项。需要将这些点有条理地组织起来,确保用户不仅能明白原理,还能了解实际应用中的关键点和潜在问题。</think>Linux允许在应用层直接操作I2C从设备的原理可以通过以下技术架构来解释: ### 一、底层硬件支持机制 1. **I2C总线控制器驱动** - 内核中实现了`i2c-adapter`核心驱动(如`i2c-dev.c`) - 通过`/dev/i2c-*`设备节点暴露硬件接口 - 典型驱动路径:`drivers/i2c/busses/i2c-designware-core.c` 2. **用户空间访问接口** ```c // 示例:打开I2C总线设备 int fd = open("/dev/i2c-1", O_RDWR); ioctl(fd, I2C_SLAVE, 0x50); // 设置从设备地址 ``` ### 二、关键系统调用流程 ``` 应用层程序 内核空间 硬件层 | | | |-- open() ---------->| 初始化I2C控制器 | | |<--> GPIO/I2C寄存器 | |-- ioctl(I2C_SLAVE) -| 配置从机地址 | |-- read()/write() -->| 生成START/STOP信号 | | | 处理SCL/SDA时序 | |<---数据返回---------| 完成I2C事务 | ``` ### 三、用户空间操作原理 1. **直接寄存器访问** - 通过`ioctl(I2C_RDWR)`实现原始消息传输 - 结构体定义: ```c struct i2c_msg { __u16 addr; // 从机地址 __u16 flags; // 读写标志 __u16 len; // 数据长度 __u8 *buf; // 数据缓冲区 }; ``` 2. **i2c-tools工具链** ```bash # 扫描总线设备 i2cdetect -y 1 # 写入寄存器 i2cset -y 1 0x50 0x00 0xAA # 读取寄存器 i2cget -y 1 0x50 0x00 ``` ### 四、内核与用户空间的交互对比 | 特性 | 内核驱动模式 | 用户空间直接操作 | |---------------------|----------------------|-----------------------| | 开发复杂度 | 高(需编写内核模块) | 低(用户态程序) | | 实时性 | 纳秒级中断响应 | 微秒级系统调用延迟 | | 并发控制 | 内核锁机制 | 需应用层自行处理 | | 典型应用场景 | 标准设备驱动 | 快速原型开发/调试 | ### 五、实际开发注意事项 1. **权限配置** ```bash # 设置设备访问权限 sudo chmod 666 /dev/i2c-* # 或添加用户到i2c组 sudo usermod -aG i2c $USER ``` 2. **时序控制限制** - 最大传输速率受`i2c_adapter.timeout`限制 - 不支持硬件级时钟延展处理 3. **多进程访问同步** ```c // 建议使用文件锁 flock(fd, LOCK_EX); write(fd, buf, len); flock(fd, LOCK_UN); ``` ### 六、典型应用场景 1. 传感器快速原型开发(如BME280温度传感器) 2. EEPROM芯片调试 3. 自定义I2C设备测试 4. 嵌入式系统低功耗模式下的间歇性访问 通过这种设计,Linux在保持内核稳定性的同时,为开发者提供了灵活的低层硬件访问能力。用户空间直接操作I2C本质上是内核将总线控制权委托给应用层,通过标准系统调用接口实现硬件交互。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值