'led 장치 디바이스 드라이버'에 해당되는 글 1건

문자 디바이스 드라이버 프로그래밍의 예로써 LED 장치 디바이스 드라이버를 설명한다.

 

 

#include "linux/kernel.h"
#include "LINUX/module.h"
#include "LINUX/init.h"
#include "LINUX/types.h"
#include "LINUX/ioport.h"
#include "UNISTD.H"
#include "LINUX/slab.h"
#include "LINUX/mm.h"
#include "ASM/hardware.h"
......

#define LED_ADDR0xF3000C00  // 베이스 주소+LED offset 값 0x0C00

static int led_major = 63;

 

 

 

led-drv.h에는 linux/kernel.h, linux/module.h 등 디바이스 드라이버 구현에 필요한 각종 헤더 파일들이 포함되어 있다. LED 디바이스 파일의 디바이스 주 번호로 63이 정의되어 있다.

 

참고로 linux/module.h는 커널 모듈 프로그래밍에서 필요한 정의, 함수 등이 선언되어 있다.

 

linux/init.h는 module_init()과 module_exit(), 매크로등이 선언되어 있다. 

 

 

디바이스 드라이버 실제 구현부분인 led-drv.c 의 소스는 다음과 같다

#include "led-drv.h"

int led_open(struct inode *inode, struct file *filp)
{
MOD_INC_USE_COUNT;
printk("LED device open \n");

return 0;
}

static ssize_t led_write(struct file *file, const char *buffer, size_t length, loff_t *offset){

unsigned char *led_port;
size_t len = length;
int value;

get_user(value, (int *)buffer);
printk("data from application program : %d\n",value);

led_port = (unsigned char *)(LED_ADDR);
*led_port = value;
return len;
}

int led_release(struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
return 0;
}

struct file_operations led_fops = {
open : led_open,
write : led_write,
release : led_release,
};

static int __init led_init(void)
{
int result;

result = register_chrdev(led_major,"led_device",&led_fops);
if(result<0)
{
printk(KERN_WARNING "register_chrdev() FAIL!\n");
return result;
}

if(!check_region(LED_ADDR,2))
  request_region(LED_ADDR,2,"led_device");
else
printk(KERN_WARNING "check_region() FAIL!\n");

return result;
}

static void __exit led_cleanup(void)
{
   release_region(LED_ADDR, 2);
if(unregister_chrdev(led_major, "led_device"))
printk(KERN_WARNING "unregister_chrdev() FAIL!\n");

}

module_init(led_init);
module_exit(led_cleanup);


 

 

 

 

 

 

위 소스에서 마지막 줄의 module_init(led_init)와 module_exit(led_cleanup) 문에서는 커널 모듈이 로드될 때 호출되는 함수가 led_init()이고 언로드 될 때 호출되는 함수가 led_cleanup()임을 나타낸다.

 

led_init 시에 check_region(LED_ADDR,2) 문을 사용해 LED_ADDR이 가리키는 영역이 현재 입출력(I/O) 포트로 사용할 수 있는 영역인지 확인한다. check_region() 함수의 포맷은 다음과 같다.

 

int check_region(unsigned int from,unsigned int extent)

 

위에서 from 인수는 입출력 포트에 해당하는 영역의 시작 주소이고, extent는 그영역의 범위를 나타낸다.

 

check_region() 함수가 성공하면, request_region(LED_ADDR, 2, "led_device") 문을 사용해 LED_ADDR이 가리키는 메모리 영역을 입출력 포트로 사용하기 위해 할당한다.

 

할당 후에는 LED_ADDR을 사용해 디바이스에 대한 접근을 할 수 있다.

 

 

request_region() 함수의 포맷은 다음과 같다.

 

void request_region(unsigned int from, unsigned int extent, const char *name);

 

위에서 from 인수는 입출력 포트에 해당하는 메모리 영역의 시작 주소이고, extent는 그 영역의 범위를 나타내며, name은 디바이스의 이름이다. 즉 request_region() 함수는 디바이스의 입출력 포트를 위해 특정 주소의 메모리 영역을 커널에서 확보하는데 사용한다.

 

led_write() 함수는 실제 LED로 출력을 하는데 다음 문에서는 get_user()함수를 이용해 사용자 메모리 영역(buffer)의 데이터를 커널 영역의 메모리(value)로 옮기고 있다. copy_from_user() 함수를 사용해도 동일한 작업을 할 수 있다.

 

get_user(value, (int *)buffer);

 

다음 문에서는 request_region() 함수로 확보한 메모리 영역에 접근하여 LED_ADDR을 사용하여 이를 통해 사용자로 부터 받은 값을 LED 디바이스로 직접 출력하고 있다.

 

led_ port = (unsigned char*)(LED_ADDR)

*led_port = value;

 

led_release() 함수는 사용자 응용 프로그램에서 close() 함수를 사용했을 때 호출되는 함수이며, 여기서는 단지 MOD_DEC_USE_COUNT 매크로를 이용하여 이 디바이스를 이용하는 프로그램 개수(usage count)를 줄이는 기능만 수행한다.

 

사용자 응용 프로그램 led-drv-app.c 소스는 다음과 같다

 

 

#include 
#include 
#include 

int main() {

int fd;
int value;

if((fd = open("/dev/led_device",O_RDWR))<0){
printf(" open() FAIL! \n");
exit(-1);
}

for(value=0; value<64; value++){
write(fd,&value,sizeof(int));
usleep(8000);
}

close(fd);
return 0;
}



 

 

 

 

 

다음 Makefile은 위에서 설명한 디바이스 드라이버 소스 led-drv.c와 사용자 응용 프로그램 led-drv-app.c를 각각 컴파일 하여 커널 모듈인 led-drv.o와 사용자 실행 프로그램 led-drv-app를 생성하기 위한 것이다.

CC = arm-linux-gcc
STRIP = arm-linux-strip

INCLUDEDIR = /src/linux-2.4.21/include

CFLAGS = -D__KERNEL__ -DMODULE -02 -Wall -I$(INCLUDEDIR)

EXECS = led-drv-app
MODULE_OBJ = led-drv.o
SRC = led-drv.c
ARRP_SRC = led-drv-app.c
HDR = led-drv.h

all : $(MODULE_OBJ) $(EXECS)

$(MODULE_OBJ) : $(SRC) $(HDR)
 $(CC) $(CFLAGS)  -c $(SRC)

$(EXECS) : $(APP_SRC)
$(CC)  -o $@   $(APP_SRC)
$(STRIP) $@

clean : 
rm -f *.o  $(EXECS)

 

 

 

 

위에서 CFLAGS는 커널 모듈을 컴파일 하기 위해 필요한 컴파일 옵션들은 선언하고 있다. 여기서 옵션 -Wall은 'Warning All'의 의미로 모든 경고 메시지를 표시하라는 것으로 이는 커널 모듈은 커널 레벨에서 동작하므로 응용 레벨 프로그램과는 달리 에러 발생 시 시스템 전체에 영향을 미칠 수 있는 상황을 막기 위한 보호 장치가 없기 때문에 조심하기 위한 것이다.

 

컴파일 옵션 -O2는 최적화 레벨을 나타낸다. 이 옵션은 컴파일 시 최적화하지 않는 경우 인라인 함수를 외부로 공개하는 심벌로 간주해 에러가 발생할 수 있기 때문에 사용한다. 옵션 -O를 사용해도 된다.

 

컴파일 옵션 -D__KERNEL__은 코드가 커널 모드에서 실행됨을 알려주며, 옵션 -DMODULE은 헤더파일에게 소스가 모듈임을 알려 준다. INCLUDEDIR은 커널 모듈을 생성하기 위해서 필요로 하는 헤더 파일 위치를 나타낸다.

 

커널 모듈 컴파일 시는 링크 과정이 없으므로 옵션 -c를 사용하였고, 사용자 프로그램 컴파일 시는 링크 과정을 통해 실행파일이 생성되어야 하므로 옵션 -o가 사용되었다. 위 Makefile 파일에 대해 make 명령을 실행하면 커널 모듈 led-drv.o와 사용자 실행프로그램 led-drv-app가 생성된다.

 

디바이스 드라이버 실행 준비를 위해 먼저 위에서 얻은 디바이스 드라이버 파일 led-drv.o와 실행파일 led-drv-app을 임베디드 보드로 전송해야 한다. 이를 위해서 이 책에서는 임베디드 보드가 부팅 과정에서 NFS를 사용하여 마운트하는 루트파일 시스템이 호스트 컴퓨터의 /test/nfs 디렉터리라고 설정하였으므로 다음과 같이 cp 명령을 사용하여 임베디드 보드의 '/root' 디렉터리, 즉 root 계정의 홈디렉터리로 보낸다.

 

cp led-drv.o /test/nfs/root

cp led-drv-app /test/nfs/root

 

이후 호스트에서 시리얼 통신 프로그램을 실행시켜 임베디드 보드와 연결 후 사용자 계정 root로 임베디드 보드에 로그인 하면 위에서 cp명령으로 전송했던 파일이 root의 홈 디렉터리에 있는 것이 보인다.

  LED 디바이스 드라이버 실행을 위해 먼저 LED 디바이스 파일을 만들어야 한다. 이를 위해 다음 명령을 수행하여 디바이스 주(major) 번호 63으로 LED디바이스 파일을 생성한다.

 

mknod /dev/led_device c 63 0

 

다음 명령을 수행하여 LED 디바이스 드라이버 모듈인 led-drv.o를 메모리에 로드한다.

 

insmod led-drv.o

 

insmod 명령을 사용하여 LED 디바이스 드라이버를 메모리에 로드한 후에는 다음과 같이 하여 사용자 응용 프로그램 led-drv-app을 실행할 수 있다.

 

/led-drv-app

 

실행 결과로 임베디드 보드의 8개 LED에 0~64에 해당 하는 2진수 값이 차례로 표시된다.

 

 

블로그 이미지

종환 Revolutionist-JongHwan

github.com/alciakng 항상 겸손하자.

댓글을 달아 주세요