8. 디바이스 드라이버
8.2. EMPOSⅡ에서 디바이스 드라이버 작성
8.2.1 LED I/O 출력
z get_user(void *x. const void *addr)
*addr의 값은 커널 영역인 x로 sizeof(addr) 만큼 복사한다
z put_user(void *x, const void *addr)
*x의 값을 user영역인 addr로 sizeof(addr)만큼 복사한다.
z copy_to_user(void *to, void *from, unsigned long size)
커널영역의 어드레스 from부터 size만큼 유저 영역의 어드레스 to로 데이터를 복사한 다.
z copy_from_user(void *to, void *from, unsigned long size)
user영역의 어드레스 from으로부터 size만큼 커널영역의 어드레스 to로 데이터를 복 put_user( *add, (unsigned short *)(gdata) );
위에서 사용자 영역에서 커널영역으로 데이터를 전송하는 방법을 보았고 다음은 프로그램 에서 I/O를 제어하는 방법에 관하여 살펴보자.
앞선 챕터의 응용 프로그램에서 보았듯이 I/O제어의 가장 기본적인 방법은 포인터를 직접 이용하는 방법이다 “포인터는 곧 번지이다” 라는 말과 같이 변수의 내용이 보관되어 있는 곳의 어드레스를 말한다.
unsigned char *addr;
addr = (unsigned char *)(0xf1600000);
*addr = 0xa5;
위와 같이 프로그램을 작성하였을 경우 포인터 변수로 addr을 선언했고 이 변수를 0xf1600000으로 초기화 하여 위의 주소에 0xa5값을 쓰라는 의미를 가진다.
또한 c 라이브러리에서 I/O 포트 제어를 위한 여러 함수들을 만들어 놓고 일반 프로그래 머들이 사용할 수 있게 했다. 다음은 이러한 함수들의 예이다.
- I/O로 부터 값을 읽은 함수 __u8 inb(unsigned int port) __u16 inw(unsigned int port) __u32 inl(unsigned int port) - I/O로 부터 값을 쓰는 함수
void outb(__u8 data, unsigned int port);
void outw(__u16 data, unsigned int port);
void outl(__u32 data, unsigned int port);
EMPOSⅡ에 있는 led를 제어하는 프로그램을 작성하기 위해선 먼저 ‘__initdata’의 구조체 에서 정의된 led의 해당 가상 어드레스 주소인 0xf1600000의 값을 변화 시켜 led를 제어 할 수 있다. 관련 소스는 다음과 같다.
- ledioport.c
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>
#define LEDIOPORT_MAJOR 0
#define LEDIOPORT_NAME "LED IO PORT"
#define LEDIOPORT_MODULE_VERSION "LED IO PORT V0.1"
#define LEDIOPORT_ADDRESS 0xf1600000
#define LEDIOPORT_ADDRESS_RANGE 1
//Global variable
static int ledioport_usage = 0;
if(ledioport_usage != 0) return -EBUSY;
MOD_INC_USE_COUNT;
ledioport_usage = 1;
return 0;
int ledioport_release(struct inode *minode, struct file *mfile) {
MOD_DEC_USE_COUNT;
ledioport_usage = 0;
return 0;
ssize_t ledioport_write_byte(struct file *inode, const char *gdata, size_t length, loff_t *off_what) {
unsigned char *addr;
unsigned char c;
get_user(c,gdata);
addr = (unsigned char *)(LEDIOPORT_ADDRESS);
*addr = c;
return length;
static struct file_operations led_fops = { write : ledioport_write_byte, open : ledioport_open,
release : ledioport_release,
;
int init_module(void) {
&led_fops);
if(result < 0) {
printk(KERN_WARNING"Can't get any major n");
return result;
ledioport_major = result;
if(!check_region(LEDIOPORT_ADDRESS,LEDIOPORT_ADDRESS_
RANGE))
request_region(LEDIOPORT_ADDRESS,LEDIOPORT_ADDRESS_
RANGE,LEDIOPORT_NAME);
else printk(KERN_WARNING"Can't get IO Region 0x%x n", EDIOPORT_ADDRESS);
printk("init module, ledioport major number : %d n",result);
return 0;
void cleanup_module(void) {
release_region(LEDIOPORT_ADDRESS,LEDIOPORT_ADDRESS_RANGE);
if(unregister_chrdev(ledioport_major,LEDIOPORT_NAME)) printk(KERN_WARNING"%s DRIVER CLEANUP FALLED n", LEDIOPORT_NAME);
- test.c
include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv) {
int dev;
char buff;
if(argc <= 1) {
printf("please input the parameter! ex)./test 0xa1 n");
return -1;
dev = open("/dev/ledioport", O_WRONLY);
if (dev != -1){
if(argv[1][0] == '0' && (argv[1][1] =='x'||argv[1][1] == 'X')) buff = (unsigned char)strtol(&argv[1][2], NULL,16);
else
buff = atoi(argv[1]);
write(dev,&buff,1);
close(dev);
else{
printf( "Device Open ERROR! n");
exit(-1);
return(0);
- Makefile
# led Device Driver Makefile
CC = arm-linux-gcc
KERNELDIR = /usr/local/linux-2.4.19-rmk4-pxa2-empx1 INCLUDEDIR = -I$(KERNELDIR)/include -I./
MODULE_OBJS = ledioport.o MODULE_SRCS = ledioport.c
TEST_TARGET = test TEST_OBJS = test.o TEST_SRCS = test.c
all: $(MODULE_OBJS) $(TEST_TARGET)
$(MODULE_OBJS) :
$(CC) $(CFLAGS) -c $(MODULE_SRCS)
$(TEST_TARGET) : $(TEST_OBJS)
$(CC) $(CFLAGS) $(TEST_OBJS) -o $@
clean:
rm -f *.o
rm -f $(TEST_TARGET)
소스 파일을 이상 없이 생성하였다면 모든 파일을 하나의 디렉토리에 옮겨 놓고 다음과 같이 컴파일을 실행한다.
$ make
아무런 문제가 발생하지 않으면 디렉토리 안에 ledioport.o, test.o 와 test라는 파일들이 생길 것이다. 여기서 ledioport.o는 led를 제어하는 디바이스 드라이버이고 test는 ledioport.o라는 디바이스 드라이버를 열어 사용할 테스트용 어플리케이션이다.
다음으로 위의 ledioport.o와 test파일을 타겟 보드로 전송한 후 insmod를 사용하여 디바 이스로 컴파일 된 ledioport.o를 커널에 삽입한다.
[root@EMPOS/root]$ insmod ledioport.o
디바이스의 커널 삽입과정이 이상 없이 진행되었다면 다음과 같은 메시지가 minicom을 통해 출력이 된다.
[root@EMPOS/root]$ insmod ledioport.o
[root@EMPOS/root]$ mknod /dev/ledioport c 253 0 [root@EMPOS/root]$
위의 내용을 보면 insmod를 통해 ledioport.c의 init_module()이 실행되고 ‘0’으로 설정한 메이저 번호를 커널에서 동적으로 설정하여 돌려준다. 그러므로 반환된 메이저 번호를 가 지고 mknod를 사용하여 다음과 같이 특수 장치 파일을 만들 수가 있다.
[root@EMPOS/root]$ mknod /dev/ledioport c 253 0
커널에 디바이스 드라이버가 정상적으로 삽입되고 mknod를 통해 특수 장치 파일을 만들 었으면 test 프로그램을 실행 시켜 제작된 드라이버를 테스트 할 수 있다.
[root@EMPOS/root]$ ./test 0xaf
삽입된 디바이스와 test 프로그램에 이상이 없을 경우 EMPOSⅡ 의 LED에 “10101111”의 형태로 불이 들어 오는 것을 확인 할 수 있다.
※ 동작원리
사용자 응용 프로그램에서는 제일 먼저 특수 장치 파일을 다음과 같이 오픈 하여 사용하 게 된다.
dev = open("/dev/ledioport", O_WRONLY);
위의 명령을 통해 디바이스 드라이버의 ledioport_open()이 성공적으로 실행되면 다른 시 스템 호출에서 사용 할 수 있는 파일 기술자를 반환한다. 이 파일 기술자와 사용자 프로 그램의 인수 값을 사용하여 시스템 콜인 write()를 호출한다. 이 함수는 file_operations의 구조체 안에 write로 정의 되어 있는 디바이스 드라이버의 ledioport_write_byte()를 호출 한다. ledioport_write_byte()안에서는 get_user()로 사용자 영역에 있는 값을 커널 영역으 로 복사하고 led의 가상 주소에 사용자 영역으로부터 받을 값을 쓰고 리턴 하면 디바이스 드라이버의 작업은 끝나고 사용자 응용프로그램으로 돌아오게 된다. 그 다음에 close()로 특수 장치 파일을 닫으면 디바이스 드라이버의 ledioport_release()가 실행 된 후 프로그 램을 종료하게 된다.