22 自定義ioctl命令及使用者程序操作GPIO
阿新 • • 發佈:2018-11-25
自定義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;
}