ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 디바이스 드라이버 / 인터럽트 처리
    Linux/Linux Device Driver 2020. 9. 1. 12:46

    /dev/mem 등 디바이스 파일을 이용하면 root 권한을 갖는 응용 프로그램에서도 하드웨어를 제어할 수 있지만, 인터럽트 만큼은 디바이스 드라이버를 이용해야 한다.

     

     

     

    인터럽트 서비스 함수의 형식

    #include <linux/interrupt.h> // 커널 디렉터리/include/linux/interrupt.h
    
    irqreturn_t (*irq_handler_t)(int, void *) // 인터럽트 서비스 함수 

     

    인터럽트 서비스 함수의 반환값

    #include <linux/irqreturn.h> // 커널 디렉터리/include/linux/irqreturn.h
    
    enum irqreturn{
    IRQ_NONE // intterupt was not from this device or was not handled
    IRQ_HANDLED // interrupt was handled by this device
    IRQ_WAKE_THREAD // handler requests to wake the handler thread
    }
    
    #define IRQ_RETVAL(x) // x가 true면 IRQ_HANDLED 반환 false면 IRQ_NONE 반환

     

    인터럽트 서비스 함수 내의 메모리 할당

    kmalloc() 함수와 kfree() 함수를 사용한다. kmalloc() 함수는 GFP_ATOMIC 인자를 사용한 방식만 사용해야 한다.

    vmalloc() 함수나 vfree() 함수 그리고 ioremap() 같은 함수를 인터럽트 서비스 함수에서 사용하면 안된다.

    인터럽트가 발생하기 전에 미리 할당한 메모리는 아무런 제약없이 사용할 수 있다.

     

     

    인터럽트 서비스 등록 함수

    #include <linux/interrupt.h> // 커널 디렉터리/include/linux/interrupt.h
    
    int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
    int __must_check request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev)

    uns

    ※ request_irq() 함수와 request_threaded_irq() 함수의 차이

    request_irq() 함수를 사용하면 인터럽트가 발생했을 때 request_irq() 함수의 인자값으로 넘긴 handler를 실행하고 프로세스 컨택스트로 돌아간다.

    request_threaded_irq() 함수를 사용하면 인터럽트가 발생했을 때 request_threaded_irq() 함수의 인자값으로 넘긴 handler를 실행하고 irq_wakeup_thread() 함수를 호출해 thread_fn로 등록된 IRQ 스레드를 깨운다. 그 후 프로세스 컨택스트로 돌아가 스케줄링되어 thread_fn가실행된다.(후반부 처리다.)

     

    인터럽트 서비스 등록 함수의 인터럽트 플래그(unsigned long flags)

    #include <linux/interrupt.h> // 커널 디렉터리/include/linux/interrupt.h
    
    #define IRQF_TRIGGER_NONE      0x00000000
    #define IRQF_TRIGGER_RISING    0x00000001
    #define IRQF_TRIGGER_FALLING   0x00000002
    #define IRQF_TRIGGER_HIGH      0x00000004
    #define IRQF_TRIGGER_LOW       0x00000008
    #define IRQF_TRIGGER_MASK      (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING) 
    #define IRQF_TRIGGER_PROBE     0x00000010
    /*
     * These flags used only by the kernel as part of the
     * irq handling routines.
     *
     * IRQF_SHARED - allow sharing the irq among several devices
     * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
     * IRQF_TIMER - Flag to mark this interrupt as timer interrupt
     * IRQF_PERCPU - Interrupt is per cpu
     * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
     * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
     *                registered first in an shared interrupt is considered for
     *                performance reasons)
     * IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
     *                Used by threaded interrupts which need to keep the
     *                irq line disabled until the threaded handler has been run.
     * IRQF_NO_SUSPEND - Do not disable this IRQ during suspend.  Does not guarantee
     *                   that this interrupt will wake the system from a suspended
     *                   state.  See Documentation/power/suspend-and-interrupts.txt
     * IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
     * IRQF_NO_THREAD - Interrupt cannot be threaded
     * IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
     *                resume time.
     * IRQF_COND_SUSPEND - If the IRQ is shared with a NO_SUSPEND user, execute this
     *                interrupt handler after suspending interrupts. For system
     *                wakeup devices users need to implement wakeup detection in
     *                their interrupt handlers.
     */
    #define IRQF_SHARED		0x00000080
    #define IRQF_PROBE_SHARED	0x00000100
    #define __IRQF_TIMER		0x00000200
    #define IRQF_PERCPU		0x00000400
    #define IRQF_NOBALANCING	0x00000800
    #define IRQF_IRQPOLL		0x00001000
    #define IRQF_ONESHOT		0x00002000
    #define IRQF_NO_SUSPEND		0x00004000
    #define IRQF_FORCE_RESUME	0x00008000
    #define IRQF_NO_THREAD		0x00010000
    #define IRQF_EARLY_RESUME	0x00020000
    #define IRQF_COND_SUSPEND	0x00040000
    #define IRQF_TIMER		(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
    

     

    egloos.zum.com/rousalome/v/9990654

     

    [리눅스커널][인터럽트] 인터럽트 핸들러 등록 시 플래그 설정

    이 내용을 다루기 전에 인터럽트 신호에 대해서 조금 살펴보겠습니다. 인터럽트 속성 플래그를 소개하기 전 이 내용을 다루기 전에 인터럽트 신호에 대해 살펴보겠습니다. 가끔 인터럽트가 제��

    egloos.zum.com

     

    인터럽트 서비스 해제 함수

    #include <linux/interrupt.h> // 커널 디렉터리/include/linux/interrupt.h
    
    void *free_irq(unsigned int, void *)

     

    인터럽트 함수와 디바이스 드라이버간의 데이터 공유

    전역 변수를 이용하여 데이터를 공유하는 경우

    전역변수를 쓴다..;;

    매개 변수를 이용하여 데이터를 공유하는 경우

    공유하고 싶은 데이터의 주소를 인터럽트 서비스 등록 함수의 void *dev인자에 넣는다.

    인터럽트 서비스 함수의 void *인자로 전달받은 주소에 데이터를 쓰거나 읽는다.

     

    인터럽트 서비스 등록과 해제 시점

    특정 목적이 있는 디바이스의 경우라면 모듈의 등록과 해제 시에 처리하는것이 좋고

    USB로 마우스, 키보드, 저장소, 충전 등 하는것과 같이 범용적 목적이 있는 디바이스의 경우라면 open() 함수와 close() 함수에서 처리하는 것이 좋다. (IRQF_SHARED 사용)

     

     

    인터럽트 공유

    위에서 설명한 인터럽트 서비스 등록과 해제 시점에서 범용목적이 있는 디바이스의 경우 여러 디바이스 드라이버가 하나의 인터럽트를 공유한다. 그럼 각각의 디바이스 드라이버를 구별하기 위해 위에서 설명한 매개 변수를 이용하여 데이터를 공유하는 경우의 void *dev인자를 사용해야 된다.

     

    ex)

    irqreturn_t USB_interrupt(int irq, void *dev)
    {
       if(dev)
       {
          switch(dev)
          {
          case 마우스:
             인터럽트 처리;
             break;
          case 키보드:
             인터럽트 처리;
             break;
          case 저장소:
             인터럽트 처리;
             break;
          }
    
          return IRQ_HANDLED;
       }
    
       return IRQ_NONE;
    }

    인터럽트 금지와 해제

    인터럽트 서비스 함수가 동작중에 다른 인터럽트가 발생하지 못하게 막는 경우와 프로세스가 실행중에 데이터 처리를 보호하기 위해 인터럽트를 강제로 막는 경우다.

     

     

    다른 인터럽트를 허용하는 경우나 인터럽트와 상관없는 일반적인 루틴에서도 특정 처리 구간에서의 특정 인터럽트를 금지하거나 허가하고 싶은 경우가 종종 발생한다. 이런 경우를 위해 커널은 다음과 같은 함수를 제공한다.

    #include <linux/interrupt.h> // 커널 디렉터리/include/linux/interrupt.h
    
    void disable_irq(unsigned int irq);
    void enable_irq(unsigned int irq);
    void disable_percpu_irq(unsigned int irq);
    void enable_percpu_irq(unsigned int irq);

     

    다른 인터럽트를 허용하는 경우나 인터럽트와 상관없는 일반적인 루틴에서도 특정 처리 구간에서의 인터럽트 발생을 금지하고 싶은 경우가 종종 발생한다. 이런 경우를 위해 커널은 다음과 같은 함수를 제공한다.

    #include <linux/irqflags.h> // 커널 디렉터리/include/linux/irqflags.h
    
    #define local_irq_enable() // 프로세서의 인터럽트 처리를 허가한다.
    #define local_irq_disable() // 프로세서의 인터럽트 처리를 금지한다.
    #define local_irq_save(flags) // 현재의 프로세스의 인터럽트 상태를 저장하고 인터럽트 처리가 허가 되어있으면 인터럽트 처리를 금지한다.
    #define local_irq_restore(flags) // 저장된 프로세스의 인터럽트 상태가 금지되어있으면 인터럽트 처리를 허가한다.

    왠만하면 local_irq_save() 함수와 local_irq_restore() 함수를 사용하는 것이 좋은것 같다.

     

     

    인터럽트 발생 횟수 확인

    보통 디바이스 드라이버가 제대로 동작하는지 확인하려면 인터럽트가 제대로 동작하는지를 살펴보면 된다. 왜냐하면 대부분의 디바이스들은 인터럽트 핸들러가 필수적이기 때문에 디바이스 드라이버에 포함된 인터럽트 서비스 호출 횟수를 보면 동적 유뮤를 판별할 수 있기 때문이다.

    cat /proc/interrupts

     

     

     

     

     

    수정중

     

    코드

    실행하기전 점퍼케이블을 한쪽에는 3.3v에 연결한 상태로 둔다.

     

    이 예제는 다음과 같은 순서로 동작한다.

    1. gpio.c 실행 파일은 write() 함수를 이용하여 LED를 켠다. 그리고 read() 함수를 이용하여 인터럽트가 발생했는지 무한 루프를 돌면서 확인한다.
    2. 점퍼케이블의 나머지 핀을 GPIO 12번핀에 접촉시켜서 입력을 발생시키면 인터럽트가 발생하고, 이때 read() 함수는 인터럽트가 발생한 횟수를 반환한다. 이 반환값을 이용하여 무한루프를 탈출하고 인터럽트 발생 시간을 출력한다. 이때 인터럽트의 잔여 발생 횟수를 얻기 위해 1초의 지연을 준다.
    3. LED를 write() 함수를 이용하여 1초 간격으로 5회 점멸시킨후 종료한다.

    ※ 점퍼케이블의 나머지핀을 GPIO 12번핀에 접촉시키면 채터링이 발생되어 인터럽트 발생 횟수가 다를 수도 있다.

     

     

     

    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/module.h>
    #include <linux/io.h>
    #include <linux/uaccess.h>
    #include <linux/gpio.h>
    #include <linux/interrupt.h>
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("KOOK");
    MODULE_DESCRIPTION("Raspberry Pi GPIO LED Device Module");
    
    #define BCM_IO_BASE 0x3F000000
    #define GPIO_BASE (BCM_IO_BASE + 0x200000)
    #define GPIO_SIZE (256)
    #define GPIO_IN(g) (*(gpio+((g)/10)) &= ~(7<<(((g)%10)*3)))
    #define GPIO_OUT(g) (*(gpio+((g)/10)) |= (1<<(((g)%10)*3)))
    #define GPIO_SET(g) (*(gpio+7) = 1<<g)
    #define GPIO_CLR(g) (*(gpio+10) = 1<<g)
    #define GPIO_GET(g) (*(gpio+13) & (1<<g))
    #define GPIO_MAJOR 200
    #define GPIO_DEVICE "GPIO_IRQ"
    #define GPIO_LED 18
    #define GPIO_SW 12
    #define INT_BUFF_MAX 64
    
    volatile unsigned *gpio;
    
    static int gpio_open(struct inode*, struct file*);
    static ssize_t gpio_read(struct file*, char*, size_t, loff_t*);
    static ssize_t gpio_write(struct file*, const char*, size_t, loff_t*);
    static int gpio_close(struct inode*, struct file*);
    
    static struct file_operations gpio_fops = {
    .owner = THIS_MODULE,
    .read = gpio_read,
    .write = gpio_write,
    .open = gpio_open,
    .release = gpio_close,
    };
    
    typedef struct{
    unsigned long time;
    } __attribute__ ((packed)) R_INT_INFO;
    
    R_INT_INFO intbuffer[INT_BUFF_MAX];
    int intcount;
    static int switch_irq;
    
    void int_clear(void)
    {
        int lp;
        for(lp = 0; lp < INT_BUFF_MAX; lp++)
        {
            intbuffer[lp].time = 0;
        }
        
        intcount = 0;
    }
    
    static irqreturn_t isr_func(int irq, void *data)
    {
        if(intcount < INT_BUFF_MAX)
        {
            intbuffer[intcount].time = get_jiffies_64();
            intcount++;
        }
        
        return IRQ_HANDLED;
    }
    
    int gpio_init(void)
    {
        static void *map;
        int result =-1;
    	
        printk(KERN_INFO "Hello module!\n");
    	
        result = register_chrdev(GPIO_MAJOR, GPIO_DEVICE, &gpio_fops);
        
        if(result < 0)
        {
            return result;
        }
    	
        map = ioremap(GPIO_BASE, GPIO_SIZE);
    	
        if(!map)
        {
            printk("Error : mapping GPIO Memory\n");
            iounmap(map);
            return -EBUSY;
        }
        
        gpio = (volatile unsigned int*)map;
        GPIO_IN(GPIO_LED);
        GPIO_OUT(GPIO_LED);
        gpio_request(GPIO_SW, "SWITCH");
        switch_irq = gpio_to_irq(GPIO_SW);
    	
        return 0;
    }
    
    void gpio_exit(void)
    {
        unregister_chrdev(GPIO_MAJOR, GPIO_DEVICE);
        gpio_free(GPIO_SW);
    	
        if(gpio)
        {
            iounmap(gpio);
        }
    
        printk(KERN_INFO "Good-bye module!\n");
    }
    
    static int gpio_open(struct inode *inod, struct file* fil)
    {
        printk("GPIO Device Opened(%d/%d)\n", imajor(inod), iminor(inod));
        try_module_get(THIS_MODULE);
    
        if(request_irq(switch_irq, isr_func, IRQF_TRIGGER_RISING, "switch", NULL) < 0)
        {
            return -EBUSY;
        }
    
        int_clear();
    	
        return 0;
    }
    
    static int gpio_close(struct inode* inod, struct file* fil)
    {
        printk("GPIO Device closed(%d)\n", MAJOR(fil->f_inode->i_rdev));
        module_put(THIS_MODULE);
        free_irq(switch_irq, NULL);
        
        return 0;
    }
    
    static ssize_t gpio_read(struct file* inode, char* buff, size_t len, loff_t* off)
    {
        int readcount;
        char* ptrdata;
        int loop;
        readcount = len / sizeof(R_INT_INFO);
    
        if(readcount > intcount)
        {
            readcount = intcount;
        }
    
        ptrdata = (char*) &intbuffer[0];
    
        for(loop = 0; loop < readcount * sizeof(R_INT_INFO); loop++)
        {
            copy_to_user(&buff[loop], &ptrdata[loop], sizeof(R_INT_INFO));
        }
    
        return readcount * sizeof(R_INT_INFO);
    }
    
    static ssize_t gpio_write(struct file* inode, const char* buff, size_t len, loff_t* off)
    {
        static char status[5] ={0};
        int loop;
        short count=0;
        //const char* cmpvalue = "0";
        int_clear();
        
        for(loop = 0; loop < len; loop++)
        {
            count = copy_from_user(status, buff, len);
            (!strcmp(status, "0"))?GPIO_CLR(GPIO_LED):GPIO_SET(GPIO_LED);
        }
        
        return count;
    }
    
    module_init(gpio_init);
    module_exit(gpio_exit);

     

     

     

    gpio.c
    
    
    
    #include <stdio.h>
    #include <fcntl.h>
    #include <string.h>
    
    #define INT_BUFF_MAX 64
    
    typedef struct
    {
    unsigned long time;
    } __attribute__ ((packed)) R_INT_INFO;
    
    
    int main(int argc, char** argv)
    {
    char buf[5];
    int loop;
    int fd = -1;
    
    R_INT_INFO intbuffer[INT_BUFF_MAX];
    int intcount;
    
    
    fd = open("/dev/LED_IRQ", O_RDWR | O_NDELAY);
    
    if(fd >= 0)
    {
    printf("start..\n");
    buf[0] = '1';
    write(fd, buf, 1, NULL);
    
    printf("wait.. input\n");
    
    while(1)
    {
    memset(intbuffer, 0, sizeof(intbuffer));
    intcount = read(fd, intbuffer, sizeof(R_INT_INFO), NULL) / sizeof(R_INT_INFO);
    if(intcount)
    {
    break;
    }
    }
    
    printf("input ok..\n");
    
    sleep(1);
    
    memset(intbuffer, 0, sizeof(intbuffer));
    
    printf("read interrupt time\n");
    intcount = read(fd, intbuffer,sizeof(intbuffer),NULL) / sizeof(R_INT_INFO);
    
    for(loop = 0; loop < intcount; loop++)
    {
    printf("index = %d time = %1d\n", loop, intbuffer[loop].time);
    }
    
    printf("led flashing..\n");
    
    for(loop = 0; loop < 5; loop++)
    {
    buf[0] = '1';
    write(fd, buf, 1, NULL);
    sleep(1);
    buf[0] = '0';
    write(fd, buf, 1, NULL);
    sleep(1);
    }
    
    
    close(fd);
    }
    
    return 0;
    }

     

     

     

     

    make

    sudo insmod gpio_module.ko

    sudo mknod /dev/LED_IRQ c 200 0

    sudo chmod 666 /dev/LED_IRQ

    sudo ./gpio

     

    3.3v에 끼워진 점퍼케이블의 반대편 핀을 GPIO12핀에 접촉시킨다.

     

    인터럽트 발생횟수와 발생시간이 출력되고

    LED가 1초간격으로 5회 점멸한다.

     

    성공..

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    시간처리

    짧은 지연의 처리

    mdelay(), udelay(), ndelay() 를 사용하면 된다 하지만 이 함수들을 사용하면 이 시간 동안 CPU는 아무런 일도 하지 않기 때문에 시스템이 정지 된다

     

    긴 지연의 처리

    디바이스 드라이버에서 1밀리초 이상의 시간을 지연시키는 루틴을 사용하는 것은 다른 방법을 이용해서라도 가급적 피해야 한다.  그러나 어쩔 수 없이 비교적 긴 시간 동안 지연시켜야 한다면 jiffies 변수와 while문을 이용해 지연시켜야 된다. 여기서 주의할 젓은 정확한 지연 시간을 보장하지 않는다는 점이다. 비선점형 커널의 경우 schedule()함수를 직접적으로 적어줘야되고 선점형은 루프문 수행중에 스케줄이 전환되므로 schedule() 함수를 호출할 필요가 없다.

     

    커널 타이머의 제거

    커널 타이머가 등록된 이후에 정해진 시간이 초과하여 터널 타이머에 등록된 함수가 호출되면 등록된 커널 타이머는 자동으로 제거된다. 그렇다고 해서 커널 타이머의 임의 제거가 필요없다는 뜻은 아니다. 커널 타이머에 등록된 함수가 모듈에 의해 삽입된 디바이스 드라이버의 함수라고 한다면 모듈이 제거되었을 때 제거된 번지에 속해있던 함수가 호출될 경우도 발생할 수 있다. 왜냐하면 커널은 호출되는 함수의 주소가 유효한지 아닌지를 해당 함수를 호출하기 전에 검사하지 않기 때문이다. 그래서 디바이스 드라이버는 등록된 커널 타이머가 확실하게 호출되어 제거될 것이라는 확신이 있더라도 모듈 형식으로 디바이스 드라이버를 작성했다면 디바이스 드라이버의 종료 루틴에 del_time() 함수를 호출하여 등록된 커널 타이머르 제거해야 한다.

     

    인터럽트 서비스 함수 내의 메모리 할당

    인터럽트 서비스 함수의 처리 루틴을 작성할 때는 메모리 할당에 주의해야 한다. 주로 사용되는 메모리 할당으로는 kmalloc() 함수와 kfree() 함수를 사용한다. vmalloc() 함수나 vfree() 그리고 ioremap() 같은 함수를 인터럽트에서 사용하면 안된다. kmalloc() 함수 역시 GFP_ATOMIC 인자를 사용한 방식만 사용해야 한다는 제약이 있다. 그러나 vmalloc() 함수나 kmalloc() 함수를 사용해 인터럽트가 발생하기 전에 미리 할당한 메모리는 아무런 제약없이 사용할 수있다.

    메모리를 할당할때 메모리가 부족하면 공간이 생길때까지 sleep을 할 수 있기 때문이다.

    댓글

Designed by Tistory.