본문 바로가기
Linux BSP

[Linux Device Driver] 입출력 다중화(Poll)과 Blocking I/O를 구현한 디바이스 드라이버

by TYB 2024. 2. 28.
반응형

지금까지 디바이스 드라이버에서 했던 모든 걸 종합해서 한번 만들어볼게유

사용한 기술:

입출력 다중화 Poll

Blocking I/O

ioctl 함수

커널 타이머


코드임다.

 

ubuntu@ubuntu8:~/pi_bsp/drivers/keyled_wcm$ cat Makefile


APP := keyled_app
MOD := keyled_dev
OBJ := $(APP).o
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:$(OBJ)
        $(MAKE) -C $(KDIR) M=$(PWD) modules $(CROSS)
        cp $(MOD).ko /srv/nfs
%.o:%.c
        $(CC) -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)

ubuntu@ubuntu8:~/pi_bsp/drivers/keyled_wcm$ cat ioctl_test.h
#ifndef __IOCTL_H__
#define __IOCTL_H__

#define IOCTLTEST_MAGIC '6'
typedef struct
{
        unsigned long timer_val;
} __attribute__((packed)) keyled_data;

#define TIMER_START     _IO(IOCTLTEST_MAGIC, 0)
#define TIMER_STOP              _IO(IOCTLTEST_MAGIC, 1)
#define TIMER_VALUE             _IOW(IOCTLTEST_MAGIC, 2, keyled_data)
#define TIMER_MAXNR             3
#endif

ubuntu@ubuntu8:~/pi_bsp/drivers/keyled_wcm$ cat keyled_app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#include <string.h>
#include "ioctl_test.h"

#define DEVICE_FILENAME "/dev/keyled_dev"

int main(int argc, char *argv[])
{
        int dev;
        char key_no;
        char led_no;
        char timer_val;
        int ret;
        int cnt = 0;
        int loopFlag = 1;
        struct pollfd Events[2];
        char inputString[80];
        keyled_data info;

        if(argc != 3)
        {
        printf("Usage : %s [led_val(0x00~0xff)] [timer_val(1/100)]\n",argv[0]);
                return 1;
        }
        led_no = (char)strtoul(argv[1],NULL,16);
        if(!((0 <= led_no) && (led_no <= 255)))
        {
                printf("Usage : %s [led_data(0x00~0xff)]\n",argv[0]);
                return 2;
        }
        printf("Author:WCM\n");
    timer_val = atoi(argv[2]);
        info.timer_val = timer_val;

//      dev = open(DEVICE_FILENAME, O_RDWR | O_NONBLOCK);
        dev = open(DEVICE_FILENAME, O_RDWR );
        if(dev < 0)
        {
                perror("open");
                return 2;
        }

        ioctl(dev,TIMER_VALUE,&info);
    write(dev,&led_no,sizeof(led_no));
    ioctl(dev,TIMER_START);

        memset( Events, 0, sizeof(Events));

        Events[0].fd = dev;
        Events[0].events = POLLIN;
        Events[1].fd = fileno(stdin);
        Events[1].events = POLLIN;

        while(loopFlag)
        {

                ret = poll(Events, 2, 1000);
                if(ret==0)
                {
//              printf("poll time out : %d\n",cnt++);
                        continue;
                }
                if(Events[0].revents & POLLIN)  //dev : keyled
                {
                read(dev,&key_no,sizeof(key_no));
                        printf("key_no : %d\n",key_no);
                        switch(key_no)
                        {
                                case 1:
                        printf("TIMER STOP! \n");
                        ioctl(dev,TIMER_STOP);
                                        break;
                                case 2:
                        ioctl(dev,TIMER_STOP);
                        printf("Enter timer value! \n");
                                        break;
                                case 3:
                        ioctl(dev,TIMER_STOP);
                        printf("Enter led value! \n");
                                        break;
                                case 4:
                        printf("TIMER START! \n");
                        ioctl(dev,TIMER_START);
                                        break;
                                case 8:
                        printf("APP CLOSE ! \n");
                        ioctl(dev,TIMER_STOP);
                                        loopFlag = 0;
                                break;

                        }
                }
                else if(Events[1].revents & POLLIN) //keyboard
                {
                fflush(stdin);
                        fgets(inputString,sizeof(inputString),stdin);
                        if((inputString[0] == 'q') || (inputString[0] == 'Q'))
                                break;
                        inputString[strlen(inputString)-1] = '\0';

                        if(key_no == 2) //timer value
                        {
                                timer_val = atoi(inputString);
                                info.timer_val = timer_val;
                                ioctl(dev,TIMER_VALUE,&info);
                ioctl(dev,TIMER_START);

                        }
                        else if(key_no == 3) //led value
                        {
                                led_no = (char)strtoul(inputString,NULL,16);
                        write(dev,&led_no,sizeof(led_no));
                ioctl(dev,TIMER_START);
                        }
                        key_no = 0;
                }
        }
        close(dev);
        return 0;
}

 


ubuntu@ubuntu8:~/pi_bsp/drivers/keyled_wcm$ cat keyled_dev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <asm/uaccess.h>
#include "ioctl_test.h"

#define DEBUG 1
#define LEDKEY_DEV_NAME            "keyled_dev"
#define LEDKEY_DEV_MAJOR            230

#define OFF 0
#define ON 1
#define GPIOLEDCNT 8
#define GPIOKEYCNT 8
static int gpioLed[GPIOLEDCNT] = {6,7,8,9,10,11,12,13};
static int gpioKey[GPIOKEYCNT] = {16,17,18,19,20,21,22,23};
static int timerVal = 100;
static int ledVal =0;
module_param(timerVal, int ,0);
module_param(ledVal,int,0);
struct timer_list timerLed;

typedef struct {
        int key_irq[8];
        int keyNumber;
} keyData;

static int gpioLedInit(void);
static void gpioLedSet(long);
static void gpioLedFree(void);
static int gpioKeyInit(void);
static void gpioKeyFree(void);
static int gpioKeyIrqInit(keyData * pKeyData);
static void gpioKeyIrqFree(keyData * pKeyData);

void kerneltimer_func(struct timer_list *t);
void kerneltimer_registertimer(unsigned long timeover)
{
    timer_setup(&timerLed, kerneltimer_func,0);
    timerLed.expires = get_jiffies_64() + timeover;
    add_timer(&timerLed);
}
void kerneltimer_func(struct timer_list *t)
{
    ledVal = ~ledVal & 0xff;
        gpioLedSet(ledVal);
    mod_timer(t,get_jiffies_64() + timerVal);
}

DECLARE_WAIT_QUEUE_HEAD(WaitQueue_Read);

static irqreturn_t key_isr(int irq, void *data)
{
        int i;
        keyData * pKeyData = (keyData *)data;
        for(i=0;i<GPIOKEYCNT;i++)
        {
                if(irq == pKeyData->key_irq[i])
                {
                        pKeyData->keyNumber = i+1;
                        break;
                }
        }
#if DEBUG
        printk("key_isr() irq : %d, KeyNumber : %d\n",irq, pKeyData->keyNumber);
#endif
        wake_up_interruptible(&WaitQueue_Read);
        return IRQ_HANDLED;
}
static 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 gpio_request() gpio%d error \n",i);
                        return ret;
                }

                ret = gpio_direction_output(gpioLed[i],OFF);
                if(ret < 0) {
                        printk("Failed gpio_direction_output() gpio%d error \n",i);
                        return ret;
                }
        }
        return ret;
}

static void gpioLedSet(long val)
{
        int i;
        for(i=0;i<GPIOLEDCNT;i++)
        {
                gpio_set_value(gpioLed[i],(val>>i) & 0x1);
        }
}
static void gpioLedFree(void)
{
        int i;
        for(i=0;i<GPIOLEDCNT;i++)
        {
                gpio_free(gpioLed[i]);
        }
}
static int gpioKeyInit(void)
{
        int i;
        int ret=0;
        char gpioName[10];
        for(i=0;i<GPIOKEYCNT;i++)
        {
                sprintf(gpioName,"key%d",gpioKey[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;
}
/*
static 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;
                ret=gpio_get_value(gpioKey[i]);
                keyData = keyData | ( ret << i );
        }
        return keyData;
}
*/
static void gpioKeyFree(void)
{
        int i;
        for(i=0;i<GPIOKEYCNT;i++)
        {
                gpio_free(gpioKey[i]);
        }
}

static int gpioKeyIrqInit(keyData * pKeyData)
{

        int i;
        int result;
        char * irqName[8] = {"IrqKey0","IrqKey1","IrqKey2","IrqKey3","IrqKey4","IrqKey5","IrqKey6","IrqKey7"};
        for(i=0;i<GPIOKEYCNT;i++)
        {
                pKeyData->key_irq[i] = gpio_to_irq(gpioKey[i]);
                if(pKeyData->key_irq[i] < 0)
                {
                        printk("gpioKeyIrq() Failed gpio %d\n",gpioKey[i]);
                        return pKeyData->key_irq[i];
                }
        }
        for(i=0;i<GPIOKEYCNT;i++)
        {
                result = request_irq(pKeyData->key_irq[i],key_isr,IRQF_TRIGGER_RISING,irqName[i],pKeyData);
                if(result < 0)
                {
                        printk("request_irq() failed irq %d\n",pKeyData->key_irq[i]);
                        return result;
                }
        }
        return 0;
}

static void gpioKeyIrqFree(keyData * pKeyData)
{
        int i;
        for(i=0;i<GPIOKEYCNT;i++)
        {
                free_irq(pKeyData->key_irq[i],pKeyData);
        }
}

static int ledkey_open (struct inode *inode, struct file *filp)
{
        int result;
    int num0 = MAJOR(inode->i_rdev);
    int num1 = MINOR(inode->i_rdev);
        keyData * pKeyData = (keyData *)kmalloc(sizeof(keyData),GFP_KERNEL);
        if(!pKeyData)
                return -ENOMEM;
        pKeyData->keyNumber = 0;

#if DEBUG
    printk( "ledkey open -> major : %d\n", num0 );
    printk( "ledkey open -> minor : %d\n", num1 );
#endif
        try_module_get(THIS_MODULE);


        result=gpioLedInit();
        if(result < 0)
                return result;
        //gpioLedSet(ledVal);
        result=gpioKeyInit();
        if(result < 0)
                return result;
        result = gpioKeyIrqInit(pKeyData);
        if(result < 0)
                return result;

        filp->private_data = pKeyData;
        //kerneltimer_registertimer(timerVal);

    return 0;
}

static ssize_t ledkey_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
//      int result;
//    char kbuf;
        keyData * pKeyData = (keyData *)filp->private_data;
#if DEBUG
    printk( "ledkey read -> buf : %08X, count : %08X \n", (unsigned int)buf, count );
#endif
//    kbuf = (char)gpioKeyGet();
//      kbuf = pKeyData->keyNumber;

        if(pKeyData->keyNumber == 0)
        {
                if(!(filp->f_flags & O_NONBLOCK))
                {
//                      wait_event_interruptible(WaitQueue_Read,pKeyData->keyNumber);
                        wait_event_interruptible_timeout(WaitQueue_Read,pKeyData->keyNumber,100);     //100 * 1/HZ = 100 * 1/100 = 100 * 0.01 = 1Sec

                }
        }

    put_user(pKeyData->keyNumber,buf);
//  result = copy_to_user(buf, &(pKeyData->keyNumber), count);
        if(pKeyData->keyNumber)
                pKeyData->keyNumber = 0;
    return count;
}

static ssize_t ledkey_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
//      int i;
//      int result;
        char kbuff;
/*      char kbuff[10];
        for(i=0;i<count;i++)
                get_user(kbuff[i],buf++);
*/
/*      char kbuff[10];
        copy_from_user(kbuff,buf,count);
*/
#if DEBUG
    printk( "ledkey write -> buf : %08X, count : %08X \n", (unsigned int)buf, count );
#endif
        get_user(kbuff,buf);
        ledVal = (int) kbuff;
//      result = copy_from_user(&kbuff,buf,count);
        //gpioLedSet(kbuff);
    return count;
}
static __poll_t ledkey_poll (struct file *filp, struct poll_table_struct *wait)
{
        unsigned int mask =0;
        keyData *pkeyData = (keyData*)filp->private_data;
#ifdef DEBUG
        //printk("_key : %u\n",(wait->_key & POLLIN));
#endif
        if(wait->_key & POLLIN)
                poll_wait(filp, &WaitQueue_Read, wait);
        if(pkeyData->keyNumber >0)//2초가 안되더라도 눌렸다면 poll in을 넣어줘서 깨어나게 만듬.
                mask= POLLIN;
        return mask;
}

static long ledkey_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
    int err=0;
        int size;
    keyled_data ctrl_infokey;
        ctrl_infokey.timer_val = timerVal;
    if( _IOC_TYPE( cmd ) != IOCTLTEST_MAGIC ) return -EINVAL;
    if( _IOC_NR( cmd ) >= TIMER_MAXNR ) return -EINVAL;

    size = _IOC_SIZE( cmd );//132비트
    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 )
    {
        case TIMER_START :
                        if(!timer_pending(&timerLed))//만약 타이머가 등록되어있다면
                                kerneltimer_registertimer(timerVal);
            break;
        case TIMER_STOP :
                        if(timer_pending(&timerLed))//만약 타이머가 등록되어있다면
                                del_timer(&timerLed);//타이머 제거
            break;
        case TIMER_VALUE :
                        err = copy_from_user((void *)&ctrl_infokey,(void *)arg,size);
                        timerVal = ctrl_infokey.timer_val;
            break;
        default:
            err =-E2BIG;
            break;
    }
    return err;
}

static int ledkey_release (struct inode *inode, struct file *filp)
{
        keyData * pKeyData = (keyData *)filp->private_data;
#if DEBUG
    printk( "ledkey release \n" );
#endif
        module_put(THIS_MODULE);
        gpioLedSet(0);
        gpioKeyIrqFree(pKeyData);
    gpioKeyFree();
        gpioLedFree();
        if(pKeyData)
                kfree(pKeyData);
        if(timer_pending(&timerLed))//만약 타이머가 등록되어있다면
        del_timer(&timerLed);//타이머 제거

    return 0;
}

static struct file_operations ledkey_fops =
{
//    .owner    = THIS_MODULE,
    .open     = ledkey_open,
    .read     = ledkey_read,
    .write    = ledkey_write,
        .poll     = ledkey_poll,
        .unlocked_ioctl = ledkey_ioctl,
    .release  = ledkey_release,
};

static int ledkey_init(void)
{
    int result;

    printk( "ledkey ledkey_init \n" );

    result = register_chrdev( LEDKEY_DEV_MAJOR, LEDKEY_DEV_NAME, &ledkey_fops);
    if (result < 0) return result;

    return 0;
}

static void ledkey_exit(void)
{
    printk( "ledkey ledkey_exit \n" );
    unregister_chrdev( LEDKEY_DEV_MAJOR, LEDKEY_DEV_NAME );
}

module_init(ledkey_init);
module_exit(ledkey_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("KCCI-AIOT WCM");
MODULE_DESCRIPTION("led key test module");

우분투에서 make해주고

ubuntu@ubuntu8:~/pi_bsp/drivers/keyled_wcm$ make

 

라즈베리파이에 와서 char 특수파일 등록하고, 디바이스 드라이버를 커널에 등록해주고, 실행파일을 실행해줍니다.

pi@pi08:/mnt/ubuntu_nfs $ sudo mknod /dev/keyled_dev c 230 0

pi@pi08:/mnt/ubuntu_nfs $ sudo insmod keyled_dev.ko

pi@pi08:/mnt/ubuntu_nfs $ sudo ./keyled_app 0x0f 100

 

동작을 살펴보면,

입력받은 led값에 따라 led가 켜지고, timer의 시간 주기에 따라 반전되서 깜빡입니다. 100이면 1초에 한번씩 0x0f 키고 0xf0키고 번갈아가면서 계속 하는겨 

디바이스 드라이버는 처리할 데이터가 없을경우 프로세스를 대기(sleep) 상태로 전환하고, key 인터럽트 발생 시 wake up하여 준비/실행 상태로 전환하여 처리함.

key1을 누르면 타이머가 정지됩니다.

key2를 누르면 키보드로 커널 타이머 주기를 입력 받습니다.(입력을 100으로 받았다면 1/100을 곱해서 1초에 한번씩 동작하는게 됩니다)

key3을 입력시 led값을 입력받아 변경된 값으로 on/off 또는 'Q' 'q' 입력시 타이머는 멈추고 응용프로세스를 종료합니다.

key4 입력시 타이머가 동작시킵니다.

key8을 입력하면 프로그램이 종료됩니다.

 

 


동작 영상

 

 

 

 


구현 간 어려웠던 점은

1. 동일한 header파일을 사용하는 app과 dev의 변수나 구조체 통일.

2. 커널 타이머가 등록되어 있는데, 또 등록하려고 하면, 라즈베리파이의 커널이 뻗어버린다. 강제로 전원 끄고 다시 켜는 방법밖에 없음. kernel을 다룰 때는 조심하자... 저 if(time_pending)으로 체크를 안해주면 바로 뻗음.

 

3. 2번과 동일한 맥락으로, 프로그램이 항상 커널타이머가 정지되어 있는 상태에서 정상적으로 종료된다는 보장이 없기 때문에 응용프로그램 종료할 때 동작하는 release()함수에 TIMER_STOP에 있는 부분과 마찬가지로 timer_pending이 되어 있다면 타이머를 삭제하는 코드를 넣어줘야한다. 안하면 또 뻗어요...

 

반응형