1. 程式人生 > >22 自定義ioctl命令及使用者程序操作GPIO

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;
}