read(), write()로 할 수 없는 것을 하기 위해 ioctl()이 존재한다. 예를 들어, GPIO 방향을 설정한다던지 등의 작업은 read(), write()로 할 수 없다. 그래서 그것을 할 수 있도록 하는 시스템콜 표준 함수가 필요하다. 그것이 ioctl()이다. (카메라 디바이스를 다룰 때 ioctl()을 통해서 많이 제어한다.)
세번째 인자인 argp는 가변인자이다. 쓸 수도 있고, 안쓸 수도 있다.
그리고 ioctl()을 통해서도 read, write가 가능한데 그때 자료형을 넘길 때 argp 인자를 사용한다.(cf. 구조체를 사용해서 read/write를 하는 경우가 많다.)
근데 위에 코드는 옛날버전이다. 지금은 함수가 개편되어서 ioctl의 첫번재 인자로 struct inode를 사용하지 않는다.
구분번호
: 명령을 구분하는 명령어의 순차번호
매직번호
: 다른 디바이스 드라이버의 ioctl 명령과 구분하기 위한 값(아스키코드 자료형으로 들어간다. 예를들어, 매직번호를 '6'으로 주면 해당 위치에는 0x36이 비트값으로 표현되어 들어가게 됨)
데이터 크기
: 매개변수arg를 통해 전달되는 메모리의 크기 (데이터 크기가 필요한 이유는 가장 위의 그림에서 매개변수가 전달될 때 char argp라는 '포인터'가 unsigned long arg라는 시작주소'값'으로 형변환되어서 받아졌기 때문이다. 그래서 원래 포인터의 자료형 크기에 대한 정보를 알고 있어야 다시 원래 자료형으로 형변환 해서 사용할 수 있게 된다. 이렇게 복잡하게 하는 이유는 전부 char argp가 가변인자이기 때문이다. 가변인자의 특성을 지원하기 위해서 이러한 구조가 나오게 되었다.)
읽기/쓰기 구분
: 읽기를 위한 요구명령인지 쓰기를 위한 요구 명령인지를 구분하는 속성
그리고 ioctl()은 명령을 만들 때 규칙이 있다. 그 규칙을 지켜서 만들어줘야 한다. 명령의 형태는 크게 네개이다. 매크로 함수가 4개인 것이다.
1. _IO : 부가적인 데이터가 없는 명령을 만드는 매크로
2. _IOR : 디바이스 드라이버에서 데이터를 읽어오기 위한 명령을 만드는 매크로
3. _IOW : 디바이스 드라이버에 데이터를 써넣기 위한 명령을 만드는 매크로
4. _IOWR : 읽기/쓰기에 대한 명령을 만들 때
이 4가지의 매크로 함수는 위 그림에서 최상위 2비트만 결정해주는 것이다.
1. _IOC_NR : 구분번호를 꺼내오는 함수(최하위 8비트를 꺼내오는 함수)
2. _IOC_TYPE
3. _IOC_SIZE
4. _IOC_DIR
#ifndef __IOCTL_H__
#define __IOCTL_H__
#define IOCTLTEST_MAGIC '6'
typedef struct
{
unsigned long size;
unsigned char buff[128];
} __attribute__((packed)) ioctl_test_info; // 132바이트 구조체
#define IOCTLTEST_KEYLEDINIT _IO(IOCTLTEST_MAGIC, 0)
#define IOCTLTEST_KEYLEDFREE _IO(IOCTLTEST_MAGIC, 1)
#define IOCTLTEST_LEDOFF _IO(IOCTLTEST_MAGIC, 2)
#define IOCTLTEST_LEDON _IO(IOCTLTEST_MAGIC, 3)
#define IOCTLTEST_GETSTATE _IO(IOCTLTEST_MAGIC, 4)
#define IOCTLTEST_READ _IOR(IOCTLTEST_MAGIC, 5,ioctl_test_info)
#define IOCTLTEST_WRITE _IOW(IOCTLTEST_MAGIC, 6,ioctl_test_info)
#define IOCTLTEST_WRITE_READ _IOWR(IOCTLTEST_MAGIC, 7,ioctl_test_info)
#define IOCTLTEST_MAXNR 8
#endif
매크로 함수는 전처리기의 치환의 개념이기 때문에, 함수보다 실행 시 오버헤드가 적다. 그래서 한두줄 밖에 안되는 코드들은 함수로써의 유지보수의 필요성이 적어지기 때문에 그럴 때 매크로 함수를 사용한다. 엄밀히 말하면 매크로 함수는 함수가 아니다. 왜냐하면 실행단계에서 함수 스택 프레임이 생성되고 해제되는 과정없이 그저 전처리 단계에서 치환되는 개념으로 들어가기 때문이다.
...
#define IOCTLTEST_READ _IOR(IOCTLTEST_MAGIC, 5,ioctl_test_info)
...
10
(읽기:10
, 쓰기:01
, 읽기/쓰기:11
)00000010000100
00110110
(0x36 아스키코드)00000101
결과적으로 해당 _IOR매크로 함수
로 생성된 최종 int형 32비트 명령어는 0b10000000100001000011011000000101
이다.
약간 예전에 어셈블리 명령어를 상수형으로 분해해서 해석할때랑 느낌이 거의 똑같다. int라는 32비트 자료형을 정해진 규칙으로 뚝뚝 끊어서 의미를 담아서 하나의 int값이 어떠한 명령어를 대변하도록 하는 느낌인 것이다.
typedef struct
{
unsigned char size;
unsigned char buff[128];
} ioctl_test_info;
만약 size 변수의 자료형이 char인데 구조체 선언에 ((packed))옵션이 없었다면, size의 자료형이 1바이트인 char임에도 구조체 자체의 크기는 4의 배수인 132바이트로 잡혔을 것이다.(빈공간 포함하여 생성하기 때문이다.)
typedef struct
{
unsigned char size;
unsigned char buff[128];
} __attribute__((packed)) ioctl_test_info;
하지만 여기서 ((packed))옵션을 줬다면, 129바이트로 생성될 것이다. 이것이 ((packed))옵션의 역할이다.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/moduleparam.h>
#include <linux/gpio.h>
#include "ioctl_test.h"
#define GPIOLEDCNT 8
#define GPIOKEYCNT 8
#define OFF 0
#define ON 1
#define LEDKEY_DEV_NAME "ioctldev"
#define LEDKEY_DEV_MAJOR 230
int gpioLed[GPIOLEDCNT] = {6,7,8,9,10,11,12,13};
int gpioKey[GPIOKEYCNT] = {16,17,18,19,20,21,22,23};
static int onevalue = 1;
static char * twostring = NULL;
module_param(onevalue, int ,0);
module_param(twostring,charp,0);
int gpioLedInit(void);
void gpioLedSet(long);
void gpioLedFree(void);
int gpioKeyInit(void);
int gpioKeyGet(void);
void gpioKeyFree(void);
int gpioLedInit(void)
{
int i;
int ret = 0;
char gpioName[10];
for(i=0;i<GPIOLEDCNT;i++)
{
sprintf(gpioName,"led%d",i);
ret = gpio_request(gpioLed[i], gpioName);
if(ret < 0) {
printk("Failed Request gpio%d error\n", 6);
return ret;
}
}
for(i=0;i<GPIOLEDCNT;i++)
{
ret = gpio_direction_output(gpioLed[i], OFF);
if(ret < 0) {
printk("Failed direction_output gpio%d error\n", 6);
return ret;
}
}
return ret;
}
void gpioLedSet(long val)
{
int i;
for(i=0;i<GPIOLEDCNT;i++)
{
gpio_set_value(gpioLed[i], (val>>i) & 0x01);
}
}
void gpioLedFree(void)
{
int i;
for(i=0;i<GPIOLEDCNT;i++)
{
gpio_free(gpioLed[i]);
}
}
int gpioKeyInit(void)
{
int i;
int ret=0;
char gpioName[10];
for(i=0;i<GPIOKEYCNT;i++)
{
sprintf(gpioName,"key%d",i);
ret = gpio_request(gpioKey[i], gpioName);
if(ret < 0) {
printk("Failed Request gpio%d error\n", 6);
return ret;
}
}
for(i=0;i<GPIOKEYCNT;i++)
{
ret = gpio_direction_input(gpioKey[i]);
if(ret < 0) {
printk("Failed direction_output gpio%d error\n", 6);
return ret;
}
}
return ret;
}
int gpioKeyGet(void)
{
int i;
int ret;
int keyData=0;
for(i=0;i<GPIOKEYCNT;i++)
{
ret=gpio_get_value(gpioKey[i]) << i;
keyData |= ret;
}
return keyData;
}
void gpioKeyFree(void)
{
int i;
for(i=0;i<GPIOKEYCNT;i++)
{
gpio_free(gpioKey[i]);
}
}
static int ledkey_open (struct inode *inode, struct file *filp)
{
int num0 = MAJOR(inode->i_rdev);
int num1 = MINOR(inode->i_rdev);
printk( "call open -> major : %d\n", num0 );
printk( "call open -> minor : %d\n", num1 );
try_module_get(THIS_MODULE);
return 0;
}
static ssize_t ledkey_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
char kbuf;
int ret;
kbuf = gpioKeyGet();
// put_user(kbuf,buf);
ret=copy_to_user(buf,&kbuf,count);
return count;
}
static ssize_t ledkey_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
char kbuf;
int ret;
// get_user(kbuf,buf);
ret=copy_from_user(&kbuf,buf,count);
gpioLedSet(kbuf);
return count;
// return -EFAULT;
}
static long ledkey_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
ioctl_test_info ctrl_info = {0,{0}};
int err=0, size;
if( _IOC_TYPE( cmd ) != IOCTLTEST_MAGIC ) return -EINVAL; // 만약 들어온 커맨드의 매직넘버가 다르다면..
if( _IOC_NR( cmd ) >= IOCTLTEST_MAXNR ) return -EINVAL; // 커맨드의 구분번호가 잘못된 범위에 해당한다면..
size = _IOC_SIZE( cmd );
if( size ) // size가 0이 아니라면 진입...(읽기/쓰기 작업이 발생하는 경우에만 구조체의 크기인 132를 물고 진입하게 된다.)
{
if( _IOC_DIR( cmd ) & _IOC_READ )
// 현재 arg에는 구조체의 시작주소의 '값'이 들어있다고 했다. .(어떤 자료형인지에 대한 정보는 유실된 상태)
// 그래서 그걸 void 포인터로 받아서 `주소`로 다시 형변환 해주고 해당 주소로부터 사이즈 132까지의 메모리를 읽고 싶은 상황이다. (어떤 자료형에 대한 주소인지 모르기 때문에 void로 받고 size로 최대 크기 지정하는 것임)
// 그래서 해당 주소에서 해당 사이즈 만큼 접근하는 것이 가능한 상황인지를 access_ok()함수를 통해서 확인하고 있다.
err = access_ok( (void *) arg, size );
if( _IOC_DIR( cmd ) & _IOC_WRITE )
err = access_ok( (void *) arg, size );
if( !err ) return err;
}
switch( cmd )
{
char buf;
case IOCTLTEST_KEYLEDINIT : // 원래 모듈을 insmod 할 때 호출되던 init()에 있던 초기화를 어플리케이션 코드의 명령을 받아서 실행될 수 있도록 구조를 바꾼 것이다.
gpioLedInit();
gpioKeyInit();
break;
case IOCTLTEST_KEYLEDFREE : // 원래 모듈을 rmmod 할 때 호출되던 exit()에 있던 해제루틴을 어필리케이션 코드의 명령을 받아서 실행될 수 있도록 구조를 바꾼 것이다.
gpioLedFree();
gpioKeyFree();
break;
case IOCTLTEST_LEDOFF :
gpioLedSet(0);
break;
case IOCTLTEST_LEDON :
gpioLedSet(255);
break;
case IOCTLTEST_GETSTATE :
buf = gpioKeyGet();
return buf; // 여기서 리턴되는 값은 어플리케이션으로 리턴됨
case IOCTLTEST_READ :
ctrl_info.buff[0] = gpioKeyGet();
if(ctrl_info.buff[0] != 0)
ctrl_info.size=1; // 키가 눌러지면 우선 사이즈에 1을 넣어준다.
err = copy_to_user((void *)arg,(const void *)&ctrl_info,size); // 원래의 주소로 읽은 값을 복사하여 전달해준다.
break;
case IOCTLTEST_WRITE :
err = copy_from_user((void *)&ctrl_info,(void *)arg,size); // void 포인터로 arg를 형변환하고, cmd에 들어가있는 size 정보로 올바른 주소에서 올바른 양의 데이터를 읽엇거 복사한다는게 포인트이다.
if(ctrl_info.size == 1)
gpioLedSet(ctrl_info.buff[0]);
break;
case IOCTLTEST_WRITE_READ :
err = copy_from_user((void *)&ctrl_info,(void *)arg,size);
if(ctrl_info.size == 1)
gpioLedSet(ctrl_info.buff[0]);
ctrl_info.buff[0] = gpioKeyGet();
if(ctrl_info.buff[0] != 0)
ctrl_info.size=1;
else
ctrl_info.size=0; // 읽은 값이 없다는 의미
err = copy_to_user((void *)arg,(const void *)&ctrl_info,size);
break;
default:
err =-E2BIG;
break;
}
return err;
}
static int ledkey_release (struct inode *inode, struct file *filp)
{
printk( "call release \n" );
module_put(THIS_MODULE);
return 0;
}
struct file_operations ledkey_fops =
{
//.owner = THIS_MODULE,
.read = ledkey_read,
.write = ledkey_write,
.unlocked_ioctl = ledkey_ioctl,
.open = ledkey_open,
.release = ledkey_release,
};
static int ledkey_init(void)
{
int result;
printk( "call ledkey_init \n" );
result = register_chrdev( LEDKEY_DEV_MAJOR, LEDKEY_DEV_NAME, &ledkey_fops);
if (result < 0) return result;
// gpioLedInit();
// gpioKeyInit();
return 0;
}
static void ledkey_exit(void)
{
printk( "call ledkey_exit \n" );
unregister_chrdev( LEDKEY_DEV_MAJOR, LEDKEY_DEV_NAME );
// gpioLedFree();
// gpioKeyFree();
}
module_init(ledkey_init);
module_exit(ledkey_exit);
MODULE_AUTHOR("KCCI-AIOT KSH");
MODULE_DESCRIPTION("led key test module");
MODULE_LICENSE("Dual BSD/GPL");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include "ioctl_test.h"
#define DEVICE_FILENAME "/dev/ioctldev"
int main()
{
ioctl_test_info info={0,{0}};
int dev;
int state;
int cnt;
int ret;
int oldState=0;
printf("info size : %d\n",sizeof(info));
dev = open( DEVICE_FILENAME, O_RDWR|O_NDELAY );
if( dev >= 0 )
{
ret = ioctl(dev, IOCTLTEST_KEYLEDINIT); // 어플리케이션 명령을 통해서 하드웨어를 초기화했다.
if(ret < 0)
{
perror("ioctl()");
return ret;
}
printf("IOCTLTEST_KEYLEDINIT : %#010x\n",IOCTLTEST_KEYLEDINIT);
printf( "wait... input1\n" );
ioctl(dev, IOCTLTEST_LEDON );
while(1)
{
state = ioctl(dev, IOCTLTEST_GETSTATE );//key값 리턴
if((state != 0) && (oldState != state))
{
printf("key : %#04x\n",state);
oldState = state;
if(state == 0x80) break;
}
}
ioctl(dev, IOCTLTEST_LEDOFF );
sleep(1);
printf( "wait... input2\n" );
while(1)
{
info.size = 0;
ioctl(dev, IOCTLTEST_READ, &info );
if( info.size > 0 )
{
printf("key : %#x\n",info.buff[0]);
if(info.buff[0] == 1) break; // 1번 버튼을 눌러서 탈출
}
}
info.size = 1;
info.buff[0] = 0x0F;
for( cnt=0; cnt<10; cnt++ )
{
ioctl(dev, IOCTLTEST_WRITE, &info );
info.buff[0] = ~info.buff[0] & 0x000000ff; // 최하위 1바이트만 관심있게 보겠다는 의미의 코드
// 불빛을 이쁘게 깜빡거리기 위한 코드임11111
usleep( 500000 );
}
printf( "wait... input3\n" );
cnt = 0;
state = 0xFF;
while(1)
{
info.size = 1;
info.buff[0] = (char)state;
ret = ioctl(dev, IOCTLTEST_WRITE_READ, &info );
if(ret < 0)
{
printf("ret : %d\n",ret);
perror("ioctl()");
}
if( info.size > 0 )
{
printf("key : %#x\n",info.buff[0]);
if(info.buff[0] == 1) break;
}
state = ~state;
// 불빛을 이쁘게 깜빡거리기 위한 코드임22222
usleep( 100000 );
}
ioctl(dev, IOCTLTEST_LEDOFF );
ioctl(dev, IOCTLTEST_KEYLEDFREE );
close(dev);
}
else
{
perror("open");
return 1;
}
return 0;
}