강좌 & 팁
타겟보드를 이용하여 디바이스 드라이버를 배워보자!(12)
안녕하세요 이우영 입니다.
오늘은 간단한 소스코드를 작성해 보도록 하겠습니다.
1. 복습!
일단 소스코드를 작성하기 전에 그림을 보면서 한번 더 확인해보고 넘어 가겠습니다.
그림은 유영창저 디바이스 드라이버에서 나온 그림입니다.
그림처럼 타이머 장치를 커널에서 제어하고 있습니다. 1/HZ 초 간격으로 인터럽트를 발생 시킵니다.
(우리는 HZ가 1000이였습니다.)
디바이스 드라이버를 통해 타이머를 등록 시키면 커널에서 설정한 틱만큼 타이머 인터럽트가 발생했을 때
등록한 함수를 호출 해 줍니다. 함수가 호출되면 등록한 타이머는 삭제됩니다.
그래서 주기적으로 호출하기 위해서는 호출된 함수가 종료하기전 다시 타이머를 등록해 주어야 합니다.
그럼 소스코드를 만들어 보도록 하겠습니다.
2. 소스 코드 작성
timer_dev.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>
#include <asm/unistd.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/gpio.h>
#include <linux/irq.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <asm/mach/arch.h>
#include <plat/gpio-cfg.h>
#define
INT_DEV_NAME "timer_dev"
#define TIME_STEP HZ //타이머 속도 지금 시스템은 1000
#define LED_GPIO_DATA_REGISTER 0x7F008184 //LED의 물리 주소
#define LED_ON (7<<2)
#define PAGE_SIZES 0x1000
volatile u32 *led_data;
u32 irq;
typedef struct //타이머를 등록하면서 넘겨줄 데이터
{ //이런식도 가능한 것을 보여주고 있습니다.
struct timer_list timer; //이 변수는 필수
unsigned long led;
u8 flag;
} __attribute__ ((packed)) KERNEL_TIMER_MANAGER;
KERNEL_TIMER_MANAGER *ptrmng = NULL;
void kerneltimer_timeover(unsigned long arg); //등록한 타이머가 동작할 때 호출하는 함수
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)); //타이머 등록
}
void kerneltimer_timeover(unsigned long arg)
{
KERNEL_TIMER_MANAGER *pdata = NULL;
if(arg)
{
pdata
= (KERNEL_TIMER_MANAGER *) arg;
*led_data = ~(*led_data); //LED 제어
printk("timer!!\n");
kerneltimer_registertimer(pdata, TIME_STEP); //타이머를 다시 등록해준다.
}
}
int timer_init()
{
printk("timer_dev_init!\n");
led_data =
ioremap(LED_GPIO_DATA_REGISTER, PAGE_SIZES);
*led_data =
LED_ON;
ptrmng =
kmalloc(sizeof(KERNEL_TIMER_MANAGER), GFP_KERNEL);
if(ptrmng == NULL) return -ENOMEM;
memset(ptrmng, 0, sizeof(KERNEL_TIMER_MANAGER)); //0으로 초기화
ptrmng->led = 0;
ptrmng->flag = 0;
kerneltimer_registertimer(ptrmng, TIME_STEP); //타이머 등록
return 0;
}
void timer_exit()
{
printk("timer_dev_exit!\n");
if(ptrmng != NULL)
{
del_timer(&(ptrmng->timer));
kfree(ptrmng);
}
iounmap(
led_data );
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("Dual BSD/GPL");
Makefile
3. 소스 코드 실행
모두 작성 하셨나요? 그럼 make 명령어를 이용해서 컴파일을 해 봅시다.
기존에 따라 오신 분들은 /nfs 폴더에 timer_dev.ko 파일이 있을 겁니다.
그럼 minicom 을 이용해서 보드에서 실행해 보겠습니다.
보드의 /mnt/nfs 폴더에 가보시면 위와 같이 보이시죠?
그럼 등록하겠습니다.
위와 같이 timer!!라는 메시지가 1초마다 발생 할 것입니다.
여기서 끝이 아닙니다.
그림의 LED 또한 1초마다 불이 들어왔다 안들어왔다 하실 겁니다.
소스코드의 주석을 보셨다면 어떤 소스 코드에서 위 작업을 등록 해주었는지 아실 수 있습니다.
그럼 몇 가지 중요한 부분만 집어 보겠습니다.
kerneltimer_registertimer 함수에서 add_timer를 호출하기 전에 세팅 잡업을 합니다.
그리고 타이머가 다 되었을 경우 동작하는 함수로 kerneltimer_timeover 를 등록했습니다.
그리고 add_timer 함수를 통해 등록했습니다.
하지만 이상하게도 kerneltimer_timeover 함수를 보면
마지막에 add_timer를 다시해주죠?
저번시간에 잠깐 언급을 했었는데요. 한번 호출된 타이머 인터럽트는 등록된 부분에서 제거가 됩니다.
그래서 반복적으로 동작하게 하려면 마지막에 저런식으로 다시 등록을 해주어야 합니다.
그리고 디바이스 드라이버가 제거될때는 당연히 사용한 자원을 모두 해제해주고 끝나게 됩니다.
(등록된 타이머가 있다면 그것도 제거해주어야겠죠?)
이로써 오늘도 간단하게 끝났습니다.
그럼 다음시간에 만나요~
빠른만남을 원하시면 http://ms-osek.org/ 여기로 찾아오세요~