Device Driver API

GAON PARK·2023년 11월 14일
0
post-custom-banner

linux/module.h

모듈 헤더. 커널 모듈형식으로 제작한 디바이스 드라이버 코드로 framework와 같다.

linux/prink.h

커널 로그에 메시지를 찍을 때 사용하는 헤더. prink()는 이전 방식이며, 요즘은 pr_로그레벨로 사용한다.

log levmethodmean
0pr_emerg()시스템이 동작하지 않음
1pr_alert()즉시 출력 메시지
2pr_crit()치명적 에러 메시지
3pr_err()에러 메시지
4pr_warn()경고 메시지
5pr_notice()정상 메시지
6pr_info()시스템 정보 메시지
7pf_debug()디버깅 정보

linux/init.h

__init, __exit매크로를 갖는다.

  • __init: 초기화 함수 앞에 붙이며, 호출된 뒤 메모리를 해제한다.
  • __exit: 모듈 언로드시 메모리 해제한다.

linux/fs.h

file system 접근을 위한 헤더이다.

  • fops 구조체를 사용하기 위해 작성한다.
  • fops 구조체에 user가 만든 함수를 등록해 동작시킨다.

linux/device.h

device file 관리용 헤더이다. device_create(), class_create() 등 device file을 생성 및 관리하는 API를 모아놨다.

struct file_operations

  • <linux/fs.h>에 정의되어 있다
  • 요청된 작업(syscall)에 대응할 수 있는 함수들의 포인터를 갖는다
  • inode: Device File의 inode 정보(주번호, 부번호 등)
  • filp: 파일 포인터
//devicefile open 시 호출
static int deviceFile_open(struct inode *inode, struct file *filp){ 
    pr_info("Open Device\n");
    return 0;
}

//devicefile close 시 호출
static int deviceFile_release(struct inode *inode, struct file *filp){ 
    pr_info("Close Device\n");
    return 0;
}

//구조체 지정초기화를 이용한 fops 구조체 초기화
static struct file_operations fops = {  
    .owner = THIS_MODULE, // device driver 모듈의 host, THIS_MODULE 매크로로 지정(linux/module.h)
    .open = deviceFile_open, // deviceFile_open() 으로 등록
    .release = deviceFile_release, // deviceFile_release() 로 등록
};

fops 동작 방법

deviceFile에 특정 API로 접근하면, 등록된 함수가 호출된다. 이때 특정 API란 System Call이다.

  • open() syscall -> .open -> deviceFile_open()
  • close() syscall -> .release -> deviceFile_release()

register_chrdev()

int register_chrdev(unsigned int major, const char name, const struct file_operations fops)

커널이 관리하는 chrdev의 배열에 캐릭터 디바이스 드라이버를 등록하는 API.

  • unsigned int major: 장치 파일의 주번호
  • const char* name: char 장치 파일 이름
  • const struct file_operations* fops: fops
static int __init deviceFile_init(void)
{
    int ret = register_chrdev(NOD_MAJOR, NOD_NAME, &fops);  //chrdev를 모듈에 등록
    if( ret < 0 ){
        pr_alert("Register File\n");
        return ret;
    }

    pr_info("Insmod Module\n");
    return 0;
}

unregister_chrdev()

void unregister_chrdev(unsigned int major, const char* name)

캐릭터 디바이스 드라이버를 해제하는 API.

  • unsigned int major: 장치 파일의 주번호
  • const char* name: char 장치 파일 이름
static void __exit deviceFile_exit(void)
{
    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    pr_info("Unload Module\n");
}

class_create()

struct class class_create(owner, char name)

device file들을 그룹화하고 관리하는 class를 생성하는 API.

  • owner: THIS_MODULE 매크로를 이용해 현재 모듈을 가리킴
  • name: 생성할 클래스 이름
  • return 값: struct class 포인터 반환
struct class *cls = class_create(THIS_MODULE, NOD_NAME);

device_create()

device_create(struct class class, struct device parent, dev_t devt, fmt)

device file 생성

  • struct class* class: class 명
  • struct device* parent: 부모 장치 이름, NULL
  • dev_t devt: 디바이스 구분을 위한 번호, MKDEV()의 return 값으로 생성
  • fmt: device file 이름
device_create(cls, NULL, MKDEV(NOD_MAJOR, 0), NULL, NOD_NAME);

device_destroy()

device file 해제

  • struct class* class: device file의 class
  • dev_t devt: device file의 주번호
  • return 값: MKDEV(주번호, 부번호)를 이용해 주번호 값을 return
    - MKDEV(1, 5) -> 0x0105 return
device_destroy(cls, MKDEV(NOD_MAJOR, 0));

class_destroy()

class_destroy(struct class* class)

device file의 class 삭제

class_destroy(cls);

ioctl()

  • <sys/ioctl.h>에 정의
  • 하드웨어를 제어하기 위한 함수
  • 예약된 동작 번호 지정

int ioctl(int fd, unsigned long request, unsigned long arg)

  • fd: 파일 디스크립터
  • request: cmd parameter 규칙에 맞춰 작성
  • arg: 포인터 전달
//ioctl로 /dev/deviceFile 에 _IO() 매크로로 arg 값 전달
ioctl(fd, _IO(0,3), 16);        //16
ioctl(fd, _IO(0,4), 0xf);       //15
ioctl(fd, _IO(0,5), 0b1111);    //15
int ret = ioctl(fd, _IO(0,6), 0);

cmd parameter

kernel에서의 안전한 명령 코드 작성을 위해 만들어진 "명령 코드 작성 규격"이다. 약속된 cmd 변수의 비트 단위 Format은 32bit이다.

  • Direction: R/W
  • Size: 데이터 크기
  • Type: 매직 넘버
  • Number: 구분 번호

cmd parameter를 다 지키며 개발하는 것은 매우 많은 시간이 걸리기 때문에 매크로가 제공된다.

  • _IO(type, number): 단순한 타입
  • _IOR(type, number, 전송 받을 데이터 타입): read 용
  • _IOW(type, number, 전송 보낼 데이터 타입): write 용
  • _IOWR(type, number, 전송 주고 받을 데이터 타입): read + write 용
    -> _IO(0, 1), (0, 2)은 시스템에서 사용하고 있기 때문에 _IO(0, 3)부터 사용한다.

Sample Code

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>

#define NOD_NAME "/dev/deviceFile"

void btn_interrupt(int signum){
    for(int i=0; i<3; i++){
	printf("PRESSED!\n");
    }
}

int main(){
    signal(SIGIO, btn_interrupt);
    
    int fd = open(NOD_NAME, O_RDWR);
    if( fd<0 ){
        printf("ERROR\n");
        exit(1);
    }	

    int pid = getpid();
    ioctl(fd, _IO(0,3), pid);
    printf("Start! pid : %d\n", pid);
    
    while(1){	
	printf("====HI====\n");
	usleep(300*1000);	
    }

    printf("End!\n");
    close(fd);
    return 0;
}

devicedriver.c

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>

#define NOD_NAME "deviceFile"

MODULE_LICENSE("GPL");

static int NOD_MAJOR;
static struct class *cls;

#define BTN 2
#define DEV_NAME "BTN"

static int irq_num;
static int app_pid;
static struct kernel_siginfo sig_info;
static struct task_struct *task;

static irqreturn_t btn_handler(int irq, void* data){
    pr_info("%s pressed!\n", DEV_NAME);

    if( app_pid>0 ){
        task = pid_task(find_vpid(app_pid), PIDTYPE_PID);
        if( task ){
            pr_info("Find app!\n");
            send_sig_info(SIGIO, &sig_info, task);
            pr_info("Send Signal\n");
        }
    }

    return IRQ_HANDLED;
}

static int deviceFile_open(struct inode *inode, struct file *filp){
    pr_info("Open Device\n");
    return 0;
}

static int deviceFile_release(struct inode *inode, struct file *filp){
    pr_info("Close Device\n");
    return 0;
}

static ssize_t deviceFile_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
    switch(cmd){
        case _IO(0,3):
            app_pid = (int)arg;
            pr_info("PID : %d\n", app_pid);
            break;
        default :
            return -EINVAL;
    }
    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = deviceFile_open,
    .release = deviceFile_release,
    .unlocked_ioctl = deviceFile_ioctl,
};

static int __init deviceFile_init(void)
{
    NOD_MAJOR = register_chrdev(0, NOD_NAME, &fops);
    if( NOD_MAJOR < 0 ){
        pr_alert("Register File\n");
        return NOD_MAJOR;
    }

    pr_info("Insmod Module\n");

    cls = class_create(THIS_MODULE, NOD_NAME);
    device_create(cls, NULL, MKDEV(NOD_MAJOR, 0), NULL, NOD_NAME);

    pr_info("Major number %d\n", NOD_MAJOR);
    pr_info("Device file : /dev/%s\n", NOD_NAME);
	
    gpio_request(BTN, "BTN");
    irq_num = gpio_to_irq(BTN);
    gpio_direction_input(BTN);
    int ret = request_irq(irq_num, btn_handler, IRQF_TRIGGER_FALLING, DEV_NAME, NULL);

    return 0;
}

static void __exit deviceFile_exit(void)
{
    free_irq(irq_num, DEV_NAME);
    gpio_free(BTN);

    device_destroy(cls, MKDEV(NOD_MAJOR, 0));
    class_destroy(cls);

    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    pr_info("Unload Module\n");
}

module_init(deviceFile_init);
module_exit(deviceFile_exit);

Makefile

KERNEL_HEADERS=/lib/modules/$(shell uname -r)/build
CC = gcc

TARGET := app
obj-m += devicedriver.o

PWD := $(CURDIR)

#make 시 make all 실행
all: driver app

#driver build
driver:
    make -C $(KERNEL_HEADERS) M=$(PWD) modules

#app build
app:
    $(CC) -o $@ $@.c

#driver, app 모두 제거
clean:
    make -C $(KERNEL_HEADERS) M=$(PWD) clean
    rm -f *.o $(TARGET)
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 11월 14일

좋은 정보 감사합니다

답글 달기