본문 바로가기
Linux BSP

[Device Driver] ioctl 함수

by TYB 2024. 2. 26.
반응형

ioctl 함수는 read write로 할 수 없는 io control 역할 수행.

 

gpio 제어인데 중간에 입출력이 바뀌는 경우가 있잖슴? gpio 방향 설정이나, alternative 함수를 설정한다던지 하는 경우에 사용하는게 ioctl이다.

 

v4l(vedio for linux) 같은 경우가 ioctl을 사용하는 대표적인 예시임.

 

open한 fd(file desriptor 번호)가 ioctl에 매개변수로 들어감. 2번째는 request로 나와있지만, command임.  3번째는 사용할 수도 잇고 안할수도 있음. 자료형이 필요할 때 사용함. 이 함수를 사용해서 read write로 데이터를 넘기려면 매개변수가 필요한데, 매개변수를 어떻게 해석할 것인지 에 대해 넣어주는게 argp임.
dev.c의 ioctl 함수

 

app.c, 즉, application에서 ioctl 호출하는 방법.

 


ioctl 함수의 호출관계

application에서 device driver의 함수 ioctl을 호출했다면 dev.c의 ioctl함수에서는 cmd를 체크함. _IO는 _IOR은 읽기에 해당되는 명령어를 만드는거 W는 write _IOWR은 W,R 다 가능한거.

command라는 값은 ioctl의 2번째 매개변수고 매개변수가 0x51 이런식으로 들어오면 2가지로 cmd 검사를 하게됨.

unsigned long arg는 구조체의 주소를 넘길 때 포인터로 넘겼는데, long arg로 받는다. 주소값을 상수로  받는거지.

근데 크기정보가 없기 때문에, cmd에 2번째 요소에 자료형의 크기를 넘겨주는거임.

그래서 argp의 구조체 시작주소부터 cmd의 2번째 요소의 자료형의 크기만큼 구조체를 읽는거임.

그러면 그걸 메모리 복사함수로 


cmd 명령어를 여러 필드로 구분하는데 사용되는 매크로 함수들이 있음. 

cmd 구성. unsigned int인 32bit 자료형임. 최하위 8bit부터 각각의 용도가 있으~

최상위 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

 


 

코드 리뷰 가즈아

헤더 파일안에 아까 맨 위 이론에서 했던 매크로 함수들을 세팅해줍니다. 132bit의 크기를 가진 구조체 만들어주고

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관련된 걸 직접 해봅싀다.

명령어가 유효한지 체크를 해주고 

decode에 사용

 

함수 쭉 보면 app 쪽에서 

초기에는 8번 버튼 눌릴 때 까지 0xff led 유지시키고

2번째는 1번 버튼 눌릴 때 까지 0x0f 와 0xf0 번갈아가면서 출력하고

3번째는 1번 버튼 눌리면 프로그램 종료

 

반응형