ioctl 함수는 read write로 할 수 없는 io control 역할 수행.
gpio 제어인데 중간에 입출력이 바뀌는 경우가 있잖슴? gpio 방향 설정이나, alternative 함수를 설정한다던지 하는 경우에 사용하는게 ioctl이다.
v4l(vedio for linux) 같은 경우가 ioctl을 사용하는 대표적인 예시임.
ioctl 함수의 호출관계
command라는 값은 ioctl의 2번째 매개변수고 매개변수가 0x51 이런식으로 들어오면 2가지로 cmd 검사를 하게됨.
unsigned long arg는 구조체의 주소를 넘길 때 포인터로 넘겼는데, long arg로 받는다. 주소값을 상수로 받는거지.
근데 크기정보가 없기 때문에, cmd에 2번째 요소에 자료형의 크기를 넘겨주는거임.
그래서 argp의 구조체 시작주소부터 cmd의 2번째 요소의 자료형의 크기만큼 구조체를 읽는거임.
그러면 그걸 메모리 복사함수로
cmd 명령어를 여러 필드로 구분하는데 사용되는 매크로 함수들이 있음.
최상위 2bit가 _IO, _IOR, _IOW, _IOWR을 각각 00,01,10,11로 표현해줌.
cmd 명령을 만들거나, 받고 해석할 때 사용하는 매크로 함수들임.
|
|
_IO는 부가적인 데이터가 없으니까 변수형이 필요가 없는거 나머지 3개는 변수형이 오는거고, sizeof해서 그 값을 14byte에 써주면 자동으로 data의 크기가 되는거지. |
cmd에 4가지 필드를 나눠주는 매크로 함수 4개임. |
Makefile
APP := ioctl_app
MOD := ioctl_dev
SRC := $(APP).c
obj-m := $(MOD).o
CROSS = ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
CC := arm-linux-gnueabihf-gcc
KDIR := /home/ubuntu/pi_bsp/kernel/linux
PWD := $(shell pwd)
default:$(APP)
$(MAKE) -C $(KDIR) M=$(PWD) modules $(CROSS)
cp $(MOD).ko /srv/nfs
$(APP):
$(CC) $(APP).c -o $(APP)
cp $(APP) /srv/nfs
clean:
rm -rf *.ko
rm -rf *.mod.*
rm -rf .*.cmd
rm -rf *.o
rm -rf modules.order
rm -rf Module.symvers
rm -rf $(MOD).mod
rm -rf .tmp_versions
rm -rf $(APP)
ioctl_app.c
#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 )
{
ioctl(dev, IOCTLTEST_KEYLEDINIT );
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;
}
}
info.size = 1;
info.buff[0] = 0x0F;
for( cnt=0; cnt<10; cnt++ )
{
ioctl(dev, IOCTLTEST_WRITE, &info );
info.buff[0] = ~info.buff[0] & 0xff;
usleep( 500000 );
}
printf( "wait... input3\n" );
cnt = 0;
state = 0xFF;
while(1)
{
info.size = 1;
info.buff[0] = 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;
usleep( 100000 );
}
ioctl(dev, IOCTLTEST_LEDOFF );
ioctl(dev, IOCTLTEST_KEYLEDFREE );
close(dev);
}
else
{
perror("open");
return 1;
}
return 0;
}
ioctl_dev.c
#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 )
{
if( _IOC_DIR( cmd ) & _IOC_READ )
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 :
gpioLedInit();
gpioKeyInit();
break;
case IOCTLTEST_KEYLEDFREE :
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;
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);
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" );
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");
ioctl_test.h
#ifndef __IOCTL_H__
#define __IOCTL_H__
#define IOCTLTEST_MAGIC '6'
typedef struct
{
unsigned long size;
unsigned char buff[128];
} __attribute__((packed)) ioctl_test_info;
#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
우분투에서 make 해주시고~
ubuntu@ubuntu8:~/pi_bsp/drivers/p306_ledkey_ioctl_rw$ make
라즈베리파이에서 /dev아래에 있는 아까 썼던 디바이스 파일들 삭제해주시고~
pi@pi08:/mnt/ubuntu_nfs $ ls -l /dev/minor
pi@pi08:/mnt/ubuntu_nfs $ sudo rm -rf /dev/minor_*
pi@pi08:/mnt/ubuntu_nfs $ ls -l /dev/minor
lsmod해서 device driver 등록되있는거 남아 있으면 삭제해주고~
pi@pi08:/mnt/ubuntu_nfs $ lsmod |grep minor
pi@pi08:/mnt/ubuntu_nfs $ sudo rmmod minor_dev
pi@pi08:/mnt/ubuntu_nfs $ lsmod |grep minor
우리가 make한거 등록해줍시다~
pi@pi08:/mnt/ubuntu_nfs $ sudo mknod /dev/ioctldev c 230 1
pi@pi08:/mnt/ubuntu_nfs $ sudo insmod ioctl_dev.ko
pi@pi08:/mnt/ubuntu_nfs $ sudo ./ioctl_app
코드 리뷰 가즈아
packed는 4바이트의 배수로 구조체를 만들지 않고, 실제 사이즈 대로 잡는거임. 만약 long이 아닌 char였다면 129바이트가 되는거.
_IO 매그로 함수의 원형을 살펴보자
83 #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
84 #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size) ))
85 #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size )))
86 #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPE CHECK(size)))
짧은 함수를 여러번 호출하는 경우는 매크로 함수로 만들어주는게 더 낫다. 함수로 호출할 때마다 오버헤드가 발생하기 때문에
그러면 저 안에서 또 사용하는 _IOC 함수를 따라가보면
69 #define _IOC(dir,type,nr,size) \
70 (((dir) << _IOC_DIRSHIFT) | \
71 ((type) << _IOC_TYPESHIFT) | \
72 ((nr) << _IOC_NRSHIFT) | \
73 ((size) << _IOC_SIZESHIFT))
74
75 #define _IOC_TYPECHECK(t) (sizeof(t))
그리고 app.c, dev.c에서 include 해주고 코드 시작하믄 됨.
_IO는 최상위 비트가 00 자료형 안들어가므로 14bit는 다 0 하위 8bit 2개 (매직번호, 구분 번호)
0의 아스키 코드가 0x30이므로 6은 0x36이고 매직번호가 0x36으로 고정된거임.
11번 라인의 #define IOCTLTEST_KEYLEDINIT _IO(IOCTLTEST_MAGIC, 0) => 0x00003600
15번 라인은 00003604
11~15번 라인까지가 _IO에 대한 정의 부분이고
16~20까지는 _IOW, _IOR, _IOWR에 대한 정의 부분인데
16번의 값은 볼려면 IOCTLTEST_READ: 0x80843605인데
계산이 어떻게 되는지 볼라면
app.c에서 ioctl을 호출함.
ioctl_test.h 내부의 값. 0x00003600임. |
dev.c로 넘어감
dev 쪽에 보면 하드웨어 제어하는 부분을 다 ioctl로 하기 위해 주석처리 해놓았음.
gpio관련된 걸 직접 해봅싀다.
명령어가 유효한지 체크를 해주고
함수 쭉 보면 app 쪽에서
초기에는 8번 버튼 눌릴 때 까지 0xff led 유지시키고
2번째는 1번 버튼 눌릴 때 까지 0x0f 와 0xf0 번갈아가면서 출력하고
3번째는 1번 버튼 눌리면 프로그램 종료
'Linux BSP' 카테고리의 다른 글
[Device Driver] 어플리케이션 없이 insmod만으로 동작하는 모듈 프로그램으로 커널 타이머 GPIO제어 (0) | 2024.02.26 |
---|---|
[Device Driver] 어플리케이션 없이 insmod만으로 동작하는 모듈 프로그램으로 커널 타이머 인터럽트 구현 (0) | 2024.02.26 |
[Linux Device Driver] 주번호, 부번호의 개념과 예제 (1) | 2024.02.26 |
[Linux Device Driver] application-> 디바이스 드라이버 8바이트 매개변수 입력 (0) | 2024.02.26 |
[Linux Device Driver] Device Driver와 application으로 led, 버튼 제어 (0) | 2024.02.23 |