드라이버 동작 형태

1. IOCTL 형태로 동작되며 사용자가 시간을 입력한다. (application 실행)

2. 드라이버는 해당 명령어를 받아서 유저 영역의 시간 데이터를 커널 영역으로 복사

3. 해당 GPIO 를 SET(high level) 시키고

4. 타이머를 기동 시킨다.

5. 지정된 시간 이후에 타이머 등록시 지정한 콜백함수를 호출하여 해당 GPIO를 Reset (LOW)을 시킨다.


ODROID XU4/XU3 의 핀 중에서 15번핀을 출력으로 사용한다.



하기 경로의 레지스터 맵을 참조 하여 해당 GPIO의 Base Address를 알아냄

http://odroid.com/dokuwiki/doku.php?id=en:xu3_gpio_register



1.  커널 타이머를 이용한 GPIO 타이밍 제어


정밀제어가 불가함

아무래도 스케쥴링 에 따른 시간차로 인해 발생되는 문제로 보이며 하기 사항으로 인하여 발생되는 현상 같다.

“ 커널타이머는 프로세스 스케줄러를 동작시키기 위해 발생되는 타이머 인터럽트를 통해 구현되는데 커널 타이머 인터럽트가 발생되면 스케쥴링에 대한 처리후 커널타이머 목록의 데이터를 검사하게 된다 “[출처 : 리눅스 디바이스 드라이버 – 유영창]


2. hrtimer 이용한 GPIO 타이밍 제어

hrtimer는 고분해능 타이머 기능으로 나노초 단위로 동작된다.

hrtimer 구조체 선언후  초기화 시킨뒤에 (구조체 등록,클록정의,타이머모드설정) 콜백함수를 등록한다.

이후에 타이머를 스타트 시키면 hrtimrt가 기동 된다.

hrtimer 는 hardkernel의 ioboard-keyled.c 파일에도 작성 되어있었으며 예제 구현은 하기의 경로를 참조 하였다.

http://xenostudy.tistory.com/306

http://lxr.free-electrons.com/source/include/linux/hrtimer.h#L33

결과적으로 msec로 제어 결과 hrtimer는 정확하게 제어 되었다.




드라이버 코드와 어플리케이션 공통 헤더파일은 하기와 같다.

코드상에는 커널 타이머 관련된 내용이 혼재 되어 있으나 실질적 IOCTL 함수 내부에서 사용하지 않고 있다(주석처리).

#mknod /dev/gpio_ioctl c 240 0

#insmod gpio_ioctl.ko

#lsmod (드라이버 적재 상태 확인)


하기 부분은 해당 파일을 XU4로 옮겨서 XU4에서 직접 컴파일

#gcc –o app_gpio_ioctl app_gpio_ioctl.c

#./app_gpio_ioctl

실행하여 동작 확인


4번눌러서 원하는 타이밍 입력후(msec 단위) 엔터 치면 해당 시간만큼 포트 출력됨.




#ifndef _GPIO_IOCTL_H_ 
#define _GPIO_IOCTL_H_

#define GPIO_IOCTL_MAGIC        't'

typedef struct 
{ 
        unsigned int msec; 
}__attribute__((packed)) gpio_parameter;

#define GPIO_LED_OFF        _IO(GPIO_IOCTL_MAGIC,0) 
#define GPIO_LED_ON        _IO(GPIO_IOCTL_MAGIC,1) 
#define GPIO_ON_OFF        _IOW(GPIO_IOCTL_MAGIC,2,gpio_parameter) 
#define GPIO_TIME_CTRL    _IOW(GPIO_IOCTL_MAGIC,3,gpio_parameter)

#define GPIO_IOCTL_MAXNR        4



#endif



#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include 

#include  
#include 

#include  
#include 

#include "gpio_ioctl.h"

#define GPIO_MMAP_DEV_NAME        "gpio_ioctl"

#define GPIO_DEV_MAJOR        240


//hardware gpio memory map define 
#define GPX1_BASE            0x13400000 
#define GPX1_CONFIG        (*(volatile unsigned int *)(0x13400000+(0x0c20 >> 2))) 
#define GPX1_2_SETTING    (*(volatile unsigned int *)(0x13400000+(0x0c24 >> 2)))

// 
typedef struct 
{ 
    struct timer_list timer; 
    unsigned long data ; 
}__attribute__((packed)) KERNEL_TIMER_MANAGER;

static KERNEL_TIMER_MANAGER *ptrmng = NULL;

static struct hrtimer         test_hrtimer;

volatile unsigned int *virt_gpio_addr;

// function

void kerneltimer_expire(unsigned long arg); 
void kerneltimer_timeover(unsigned long arg); 
static enum hrtimer_restart gpio_hrtimer_callback(struct hrtimer *timer);

//////////////////////////////////////////////////////// 
//      Device Driver Open 
//////////////////////////////////////////////////////// 
static int gpio_open(struct inode *inode, struct file *filp) 
{ 
        //kerner timer initialize 
        ptrmng = kmalloc(sizeof(KERNEL_TIMER_MANAGER),GFP_KERNEL); 
        if(ptrmng == NULL) return -ENOMEM;

        memset(ptrmng,0, sizeof(KERNEL_TIMER_MANAGER)); 
        ptrmng->data = 0;

        //hrtimer initialize 
        hrtimer_init(&test_hrtimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL); 
        test_hrtimer.function = gpio_hrtimer_callback; 
    
        //gpio initialize 
        //가상 주소를 획득한다. 물리적 주소 접근시 segmentaion fault 발생 
        //레지스터 address 참조 http://odroid.com/dokuwiki/doku.php?id=en:xu3_gpio_register 
        virt_gpio_addr = ioremap(GPX1_BASE,0x10000); 
        if (virt_gpio_addr != NULL) 
        { 
            printk("virture adress mapping success@\r\n"); 
        } 
        *(virt_gpio_addr + (0x0c20 >> 2)) |= (0x1 << 8);

        printk("gpio open & GPX1 Configration Complete!\n"); 
        return 0; 
}

//////////////////////////////////////////////////////// 
//      Device Driver Release 
////////////////////////////////////////////////////////

static int gpio_released(struct inode *inode, struct file *filp) 
{ 
        //gpio shutdown 
        return 0; 
}

//////////////////////////////////////////////////////// 
//      Device Driver kernel timer register 
//////////////////////////////////////////////////////// 
void kerneltimer_registertimer(KERNEL_TIMER_MANAGER *pdata, unsigned long timeover) 
{ 
    init_timer(&(pdata->timer)); 
    pdata->timer.expires  = get_jiffies_64() + timeover; 
    pdata->timer.data     = (unsigned long) pdata; 
    pdata->timer.function = kerneltimer_timeover; 
    add_timer(&(pdata->timer)); 
}

//////////////////////////////////////////////////////// 
//      Device Driver IOCTL Functioin 
//////////////////////////////////////////////////////// 
static long gpio_ioctl(struct file *filp,unsigned int cmd, unsigned long arg) 
{ 
    gpio_parameter gpio_info; 
    int err, size; 
    long ret;


    if(_IOC_TYPE(cmd) != GPIO_IOCTL_MAGIC ) return -EINVAL; 
    if(_IOC_NR(cmd)    >= GPIO_IOCTL_MAXNR ) return -EINVAL;

    size = _IOC_SIZE(cmd); 
    printk("%d_IOC_SIZE\r\n",size); 
    
    if(size) 
    { 
        err = 0; 
        if (_IOC_DIR(cmd) & _IOC_READ) 
        { 
            err = !access_ok(VERIFY_WRITE, (void __user *)arg,size); 
            printk("IOC_READ\r\n"); 
        } 
        else if (_IOC_DIR(cmd) & _IOC_WRITE) 
        { 
            err = !access_ok(VERIFY_READ,(void __user *)arg,size); 
            printk("IOC_WRITE\r\n %d :err_number\r\n",err); 
        }

        if (err) return err; 
    } 
    
    switch (cmd) 
    { 
        case GPIO_LED_ON: 
        *(virt_gpio_addr + (0x0c24 >> 2)) |= (1 << 2); 
        printk("GPIO LED ON\n"); 
        break;

        case GPIO_LED_OFF: 
        *(virt_gpio_addr + (0x0c24 >> 2)) &= ~(1 << 2); 
        printk("GPIO LED OFF\n"); 
        break;

        case GPIO_ON_OFF: 
        *(virt_gpio_addr + (0x0c24 >> 2)) |= (1 << 2); 
        printk("GPIO LED ON OFF\n"); 
        kerneltimer_registertimer(ptrmng,gpio_info.msec); 
        break;

        case GPIO_TIME_CTRL: 
        printk("GPIO TIME_CTRL\n"); 
        ret = copy_from_user((void *)&gpio_info,(const void *)arg,size); //user appllication 에서 data 가져옴 
        //kerneltimer_registertimer(ptrmng,gpio_info.msec); 
        hrtimer_start(&test_hrtimer,ktime_set(0,gpio_info.msec*1000000),HRTIMER_MODE_REL); 
        gpio_info.msec = 0; 
        *(virt_gpio_addr + (0x0c24 >> 2)) |= (1 << 2); 
        printk("ioctl timer %d\r\n",(unsigned int )gpio_info.msec); 
        break;

        default : 
        break; 
        
    } 
    return 0; 
}

//////////////////////////////////////////////////////// 
//      Device Driver filr operation fild 
//////////////////////////////////////////////////////// 
static struct file_operations gpioioctl_fops = 
{ 
        .owner = THIS_MODULE, 
        .unlocked_ioctl = gpio_ioctl, 
        .open = gpio_open, 
        .release = gpio_released,

};

//////////////////////////////////////////////////////// 
//        Device Driver hrtimer expire excute 
/////////////////////////////////////////////////////// 
static enum hrtimer_restart gpio_hrtimer_callback(struct hrtimer *timer) 
{   
    //0으로 포트 lowlevel 변경 
    *(virt_gpio_addr + (0x0c24 >> 2)) &= ~(1 << 2);

    return HRTIMER_NORESTART; 
}

//////////////////////////////////////////////////////// 
//      Device Driver kernel timer expire excute 
//////////////////////////////////////////////////////// 
void kerneltimer_timeover(unsigned long arg) 
{ 
    KERNEL_TIMER_MANAGER *pdata = NULL;

    pdata = (KERNEL_TIMER_MANAGER *) arg;

    //0으로 포트 초기화 
    *(virt_gpio_addr + (0x0c24 >> 2)) &= ~(1 << 2);

}



//////////////////////////////////////////////////////// 
//      Device Driver init & exit 
//////////////////////////////////////////////////////// 
static void gpio_ioctl_exit(void) 
{ 
    int ret; 
    if (ptrmng != NULL) 
    { 
        del_timer(&(ptrmng->timer)); 
        kfree(ptrmng); 
    }

    //hrtimer 해제 
    ret = hrtimer_cancel(&test_hrtimer);

    unregister_chrdev(GPIO_DEV_MAJOR,GPIO_MMAP_DEV_NAME);

}

static int gpio_ioctl_init(void) 
{

    int result;

    result = register_chrdev(GPIO_DEV_MAJOR,GPIO_MMAP_DEV_NAME,&gpioioctl_fops);

    if (result < 0 ) 
    { 
        printk(" drv register fail\r\n"); 
        return result; 
    } 
    else 
    { 
        printk(" drv register success\r\n"); 
    } 
    
    return 0; 
    }

module_init(gpio_ioctl_init); 
module_exit(gpio_ioctl_exit);

MODULE_DESCRIPTION("GPIO Control Driver for ODROID_BOARDXU3/4"); 
MODULE_AUTHOR("S.Jung/sjung8432@pnstech.ner/www.pnstech.co.kr"); 
MODULE_LICENSE("Dual BSD/GPL");

application Code 는 하기와 같다.

#include 
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include 

#include  
#include 

#include  
#include 

#include "gpio_ioctl.h"

#define GPIO_MMAP_DEV_NAME        "gpio_ioctl"

#define GPIO_DEV_MAJOR        240


//hardware gpio memory map define 
#define GPX1_BASE            0x13400000 
#define GPX1_CONFIG        (*(volatile unsigned int *)(0x13400000+(0x0c20 >> 2))) 
#define GPX1_2_SETTING    (*(volatile unsigned int *)(0x13400000+(0x0c24 >> 2)))

// 
typedef struct 
{ 
    struct timer_list timer; 
    unsigned long data ; 
}__attribute__((packed)) KERNEL_TIMER_MANAGER;

static KERNEL_TIMER_MANAGER *ptrmng = NULL;

static struct hrtimer         test_hrtimer;

volatile unsigned int *virt_gpio_addr;

// function

void kerneltimer_expire(unsigned long arg); 
void kerneltimer_timeover(unsigned long arg); 
static enum hrtimer_restart gpio_hrtimer_callback(struct hrtimer *timer);

//////////////////////////////////////////////////////// 
//      Device Driver Open 
//////////////////////////////////////////////////////// 
static int gpio_open(struct inode *inode, struct file *filp) 
{ 
        //kerner timer initialize 
        ptrmng = kmalloc(sizeof(KERNEL_TIMER_MANAGER),GFP_KERNEL); 
        if(ptrmng == NULL) return -ENOMEM;

        memset(ptrmng,0, sizeof(KERNEL_TIMER_MANAGER)); 
        ptrmng->data = 0;

        //hrtimer initialize 
        hrtimer_init(&test_hrtimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL); 
        test_hrtimer.function = gpio_hrtimer_callback; 
    
        //gpio initialize 
        //가상 주소를 획득한다. 물리적 주소 접근시 segmentaion fault 발생 
        //레지스터 address 참조 http://odroid.com/dokuwiki/doku.php?id=en:xu3_gpio_register 
        virt_gpio_addr = ioremap(GPX1_BASE,0x10000); 
        if (virt_gpio_addr != NULL) 
        { 
            printk("virture adress mapping success@\r\n"); 
        } 
        *(virt_gpio_addr + (0x0c20 >> 2)) |= (0x1 << 8);

        printk("gpio open & GPX1 Configration Complete!\n"); 
        return 0; 
}

//////////////////////////////////////////////////////// 
//      Device Driver Release 
////////////////////////////////////////////////////////

static int gpio_released(struct inode *inode, struct file *filp) 
{ 
        //gpio shutdown 
        return 0; 
}

//////////////////////////////////////////////////////// 
//      Device Driver kernel timer register 
//////////////////////////////////////////////////////// 
void kerneltimer_registertimer(KERNEL_TIMER_MANAGER *pdata, unsigned long timeover) 
{ 
    init_timer(&(pdata->timer)); 
    pdata->timer.expires  = get_jiffies_64() + timeover; 
    pdata->timer.data     = (unsigned long) pdata; 
    pdata->timer.function = kerneltimer_timeover; 
    add_timer(&(pdata->timer)); 
}

//////////////////////////////////////////////////////// 
//      Device Driver IOCTL Functioin 
//////////////////////////////////////////////////////// 
static long gpio_ioctl(struct file *filp,unsigned int cmd, unsigned long arg) 
{ 
    gpio_parameter gpio_info; 
    int err, size; 
    long ret;


    if(_IOC_TYPE(cmd) != GPIO_IOCTL_MAGIC ) return -EINVAL; 
    if(_IOC_NR(cmd)    >= GPIO_IOCTL_MAXNR ) return -EINVAL;

    size = _IOC_SIZE(cmd); 
    printk("%d_IOC_SIZE\r\n",size); 
    
    if(size) 
    { 
        err = 0; 
        if (_IOC_DIR(cmd) & _IOC_READ) 
        { 
            err = !access_ok(VERIFY_WRITE, (void __user *)arg,size); 
            printk("IOC_READ\r\n"); 
        } 
        else if (_IOC_DIR(cmd) & _IOC_WRITE) 
        { 
            err = !access_ok(VERIFY_READ,(void __user *)arg,size); 
            printk("IOC_WRITE\r\n %d :err_number\r\n",err); 
        }

        if (err) return err; 
    } 
    
    switch (cmd) 
    { 
        case GPIO_LED_ON: 
        *(virt_gpio_addr + (0x0c24 >> 2)) |= (1 << 2); 
        printk("GPIO LED ON\n"); 
        break;

        case GPIO_LED_OFF: 
        *(virt_gpio_addr + (0x0c24 >> 2)) &= ~(1 << 2); 
        printk("GPIO LED OFF\n"); 
        break;

        case GPIO_ON_OFF: 
        *(virt_gpio_addr + (0x0c24 >> 2)) |= (1 << 2); 
        printk("GPIO LED ON OFF\n"); 
        kerneltimer_registertimer(ptrmng,gpio_info.msec); 
        break;

        case GPIO_TIME_CTRL: 
        printk("GPIO TIME_CTRL\n"); 
        ret = copy_from_user((void *)&gpio_info,(const void *)arg,size); //user appllication 에서 data 가져옴 
        //kerneltimer_registertimer(ptrmng,gpio_info.msec); 
        hrtimer_start(&test_hrtimer,ktime_set(0,gpio_info.msec*1000000),HRTIMER_MODE_REL); 
        gpio_info.msec = 0; 
        *(virt_gpio_addr + (0x0c24 >> 2)) |= (1 << 2); 
        printk("ioctl timer %d\r\n",(unsigned int )gpio_info.msec); 
        break;

        default : 
        break; 
        
    } 
    return 0; 
}

//////////////////////////////////////////////////////// 
//      Device Driver filr operation fild 
//////////////////////////////////////////////////////// 
static struct file_operations gpioioctl_fops = 
{ 
        .owner = THIS_MODULE, 
        .unlocked_ioctl = gpio_ioctl, 
        .open = gpio_open, 
        .release = gpio_released,

};

//////////////////////////////////////////////////////// 
//        Device Driver hrtimer expire excute 
/////////////////////////////////////////////////////// 
static enum hrtimer_restart gpio_hrtimer_callback(struct hrtimer *timer) 
{   
    //0으로 포트 lowlevel 변경 
    *(virt_gpio_addr + (0x0c24 >> 2)) &= ~(1 << 2);

    return HRTIMER_NORESTART; 
}

//////////////////////////////////////////////////////// 
//      Device Driver kernel timer expire excute 
//////////////////////////////////////////////////////// 
void kerneltimer_timeover(unsigned long arg) 
{ 
    KERNEL_TIMER_MANAGER *pdata = NULL;

    pdata = (KERNEL_TIMER_MANAGER *) arg;

    //0으로 포트 초기화 
    *(virt_gpio_addr + (0x0c24 >> 2)) &= ~(1 << 2);

}



//////////////////////////////////////////////////////// 
//      Device Driver init & exit 
//////////////////////////////////////////////////////// 
static void gpio_ioctl_exit(void) 
{ 
    int ret; 
    if (ptrmng != NULL) 
    { 
        del_timer(&(ptrmng->timer)); 
        kfree(ptrmng); 
    }

    //hrtimer 해제 
    ret = hrtimer_cancel(&test_hrtimer);

    unregister_chrdev(GPIO_DEV_MAJOR,GPIO_MMAP_DEV_NAME);

}

static int gpio_ioctl_init(void) 
{

    int result;

    result = register_chrdev(GPIO_DEV_MAJOR,GPIO_MMAP_DEV_NAME,&gpioioctl_fops);

    if (result < 0 ) 
    { 
        printk(" drv register fail\r\n"); 
        return result; 
    } 
    else 
    { 
        printk(" drv register success\r\n"); 
    } 
    
    return 0; 
    }

module_init(gpio_ioctl_init); 
module_exit(gpio_ioctl_exit);

MODULE_DESCRIPTION("GPIO Control Driver for ODROID_BOARDXU3/4"); 
MODULE_AUTHOR("S.Jung/sjung8432@pnstech.ner/www.pnstech.co.kr"); 
MODULE_LICENSE("Dual BSD/GPL");

크리에이티브 커먼즈 라이센스
Creative Commons License
2015/12/11 13:50 2015/12/11 13:50

Trackback Address >> 이 글에는 트랙백을 보낼 수 없습니다

댓글을 달아 주세요

  1. 이은영 2018/08/13 02:30  address  modify / delete  reply

    얼마나 걸릴까요?

  2. 솔루션 2018/08/13 18:51  address  modify / delete  reply

    스카이프 pvaltrade 텔레그램@pvalrade