[라즈베리파이] Device Driver

HEEJOON MOON·2022년 6월 11일
0

Device Driver

  • 특정 디바이스를 제어하는 커널 Software
  • 응용프로그램은 특정 하드웨어에 대한 제어를 커널에 요청하고, 커널은 해당 디바이스 드라이버를 호출하여 처리
  • 응용프로그램은 file I/O system call을 통하여 디바이스 드라이버에 요청
  • 응용프로그램은 special device file을 통하여 특정 디바이스를 지정. 리눅스는 디바이스를 파일로 표현하며, 이런 파일들은 /dev/ 디렉토리에 존재하며, special device file이라 합니다. 즉, 디바이스 파일 하나하나는 실질적인 하드웨어를 표현합니다. 이 파일들의 목적은 디바이스에 대한 정보 제공. => 디바이스 타입정보, 주번호, 부번호
    Ex) Mouse -> /dev/mouse라는 디바이스 파일로 표현
  • 주번호 : 커널에서 디바이스를 구분하고 연결하는데 사용. 제어하려는 디바이스를 구분하기 위한 디바이스의 ID
  • 부번호 : 디바이스 드라이버 내에서 장치를 구분하기 위해 사용. 같은 종류의 디바이스가 여러개 있을때 그중 하나를 선택하기 위해 사용

File I/O system calls

  • open()
  • close()
  • read()
  • write()
  • lseek()
  • ioctl()

Special Device file 생성

  • mknod를 이용
    # mknod /dev/ttyS4 c 4 68
    : /dev/라는 디렉토리에 디바이스 파일명은 ttyS4이며 c(문자 디바이스 드라이버 타입)이며, 주번호는 4, 부번호는 68입니다.

모듈 프로그래밍

  • 모듈은 커널 프로그램의 특징을 갖고 있으면서 커널에 동적으로 적재되고 제거되므로 일반 프로그램과는 다른 형식을 갖추어야 함
  • 디바이스 드라이버의 경우, 리눅스 커널의 디바이스 드라이버 인터페이스에 준하여 작성되어야 함.

모듈 관련 명령어

  • insmod : 모듈을 커널에 적재
  • rmmod : 커널에서 모듈을 제거
  • lsmod : 커널에 적재된 모듈 목록을 출력
  • depmod : 모듈간 의존성 정보를 생성
  • modprobe : 모듈을 커널에 적재하거나 제거

< Device Driver Application -> digit_app.c >

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <linux/kdev_t.h>

#define _LED_PATH_ "dev/led_dd"

int main(int argc, char **argv){
	int fd = 0;
    
    // parameter 개수가 안맞을때
    if(argc!=2){
    	printf("Usage: %s [LED binary]\n", argv[0]);
        exit(1);
    }
    
    // fd 번호가 음수인 경우 error 발생
    if((fd = open(_LED_PATH_, _O_RDWR | O_NONBLOCK)) < 0){
    	perror("open()");
        exit(1);
    }
    
    // fd에 argv[1] write연산 수행
    write(fd, argv[1], strlen(argv[1])));
    
    // fd close
    close(fd);
    
    return 0;
}

< Device Driver -> led_dd.c >

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h>

// Special Device File을 만들기 위한 파리미터 정의
#define DRIVER_AUTHOR "KHU"
#define DEVICE_DESC "Led Driver"
#define DEVICE_NAME "led_dd"
#define MAJOR_NUMBER 222 -> 주번호

#define GPIO_BASE 0x3F200000 // physical address
#define GPIO_SIZE 0xc0

#define INPUT 0
#define OUTPUT 1
#define LOW 0
#define HIGH 1

void __iomem *gpioAddr;
volatile unsigned int gpioToGPFSEL[] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
};

volatile unsigned int gpioToShift[] = {
        0, 3, 6, 9, 12, 15, 18, 21, 24, 27,
        0, 3, 6, 9, 12, 15, 18, 21, 24, 27,
        0, 3, 6, 9, 12, 15, 18, 21, 24, 27,
};

volatile unsigned int gpioToGPSET = 0x1C; // Set bit
volatile unsigned int gpioToGPCLR = 0x28; // CLEAR bit

const int Led[16] = {4, 17, 18, 27, 22, 23, 24, 25, 6, 12, 13, 16, 19, 20, 26, 21};

// Gpio의 주소값을 초기화하는 함수
// 기존에는 mmap이라는 systemcall을 이용했으나, kernel에서는 다음과 같이 한다.
static volatile void initGpioAddr(void){
	if(gpioAddr == NULL) // gpioAddr가 Null인경우 BASE의 pointer를 가져온다
    	gpioAddr = ioremap(GPIO_BASE, GPIO_SIZE); // Physical주소를 이용하여 virtual-memory로 ioremap이 mapping해준다
}

// wiringPi는 application library 때문이므로 kernel에서는 사용할 수 없다
// 따라서 kernel에서 동작하도록 직접 구현해준다.
static void pinMode(int pin, int mode){
	volatile unsigned int *gpio = (volatile unsigned int *)gpioAddr;
    
    unsigned int fsel = gpioToGPFSEL[pin] / sizeof(unsigned int); // GPSEL을 고른다
    unsigned int shift = gpioToShift[pin];
    
    if(mode) // HIGH이면
    	gpio[fsel] |= (1<<shift); // 1비트를 index위치로 shift해준다 
    else // LOW 이면 
    	gpio[fsel] |= (0<<shift); // 0비트를 index위치로 shift해준다
}

static void digitalWrite(int pin, int value){
	volatile unsigned int* gpio = (volatile unsigned int *)gpioAddr;
    
    unsigned int set = gpioToGPSET/sizeof(unsigned int);
    unsigned int clr = gpioToGPCLR/sizeof(unsigned int);
    
    if(value)
    	gpio[set] = (1<<pin); // pin 위치에 1 bit을 줌으로써 on
    else // pin 위치에 clear 비트를 줌으로써 off
    	gpio[clr] = (1<<pin):
}


// 구조체를 이용하여 open/close/write에 해당하는 함수를 매핑해준다
static struct file_operations_fops={
	.owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .write = led_write
};


// led 전체를 다 끄게 초기화하는 함수
static int led_open(struct inode *inode, struct file *flip){
	int i;
    
    initGpioAddr(); // gpioaddr 초기화
    for(i=0; i<16; i++){
    	pinMode(Led[i], OUTPUT);
        digitalWrite(Led[i], LOW);
    }
    printk("[led_dd] led_open\n");
    
    return 0;
}

// led 파일을 닫을때 실행되는 함수
static int led_release(struct inode *inode, struct file *flip){
	iounmap(gpioAddr); // gpio 초기화를 해제
    
    printk("[led_dd] led_release\n");
    return 0;
}

// led special file에 쓰는 함수. 즉 LED 동작을 하도록 하는 함수
static int led_write(struct file *flip, const char *buf, size_t len, loft_t *fops){
	int i;
    char state;
    
    for(i=0; i<len; i++){
    	copy_from_user(&state, &buf[i], 1); // 현재 led의 상태를 읽어와 buffer에 저장
        
        if (state=='0')
        	digitalWrite(Led[3-i], LOW);
        else
        	digitalWrite(Led[3-i], HIGH);
    }
    
    printk("[led_dd] led_write\n");
    return len;
}

// 모듈이 시작할 때와 종료될 떄 실행되는 함수 정의
static int led_init(void){
	printk("[led_dd] led_init() \n");
    // 주번호에 해당하는 장치와 디바이스 장치를 /dev에 등록
    // 구조체에 해당하는 연산에 작동하는 레지스터를 등록
    register_chrdev(MAJOR_NUMBER, DEVICE_NAME, &fops); 
    
   	return 0;
}
static void led_exit(void){
	printk("[led_dd] led_exit()\n");
    unregister_chrdev(MAJOR_NUMBER, DEVICE_NAME); // /dev에서 등록을 삭제
}

module_init(led_init); // 모듈 초기화시 led_init이 실행
module_exit(led_exit); // 모듈 종료시 led_exit이 실행

MODULE_LICENSE("Dual BSD/GPL"); // 라이센스 관련
profile
Robotics, 3D-Vision, Deep-Learning에 관심이 있습니다

0개의 댓글