새로운 문자 장치 드라이버 원리
register_chrdev 및 unregister_chrdev 함수는 구형 드라이버에서 사용되던 방식입니다. 현재는 새롭게 개선된 문자 장치 드라이버 API를 활용해야 합니다.
장치 번호 할당 및 해제
register_chrdev 함수는 단일 주 장치 번호만 제공받을 수 있었으나, 이는 다음과 같은 문제점을 초래했습니다:
- 사용 중인 주 장치 번호를 사전에 확인해야 했습니다.
- 주 장치 번호 아래 모든 부 장치 번호를 차지하게 되었습니다.
이러한 문제를 해결하기 위해 Linux 커널에 장치 번호를 요청하는 방식을 권장합니다. 필요 없는 경우 다음 함수를 사용할 수 있습니다:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
사용자가 주/부 장치 번호를 직접 지정하는 경우 다음 함수를 사용할 수 있습니다:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
모든 방식에서 동일한 해제 함수를 사용합니다:
void unregister_chrdev_region(dev_t from, unsigned count);
장치 번호 할당 예시
int device_major; /* 주 장치 번호 */
int device_minor; /* 부 장치 번호 */
dev_t device_id; /* 장치 ID */
/* 장치 번호 할당 */
if (device_major) {
device_id = MKDEV(device_major, 0);
register_chrdev_region(device_id, 1, "test");
} else {
alloc_chrdev_region(&device_id, 0, 1, "test");
device_major = MAJOR(device_id);
device_minor = MINOR(device_id);
}
/* 장치 번호 해제 */
unregister_chrdev_region(device_id, 1);
자동 장치 노드 생성
modprobe로 드라이버 모듈을 로드하면 /dev 디렉토리에 자동으로 장치 파일이 생성됩니다.
mdev 사용
mdev를 통해 장치 노드의 생성 및 제거를 처리할 수 있으며, Buildroot에서 이를 자동으로 관리합니다.
클래스 및 장치 생성
자동 장치 노드 생성은 드라이버 진입 함수에서 수행됩니다:
struct class *class_create(struct module *owner, const char *name);
void class_destroy(struct class *cls);
장치 생성 시 다음 함수를 사용합니다:
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
삭제 시:
void device_destroy(struct class *cls, dev_t devt);
예시
struct class *device_class;
struct device *device_node;
dev_t device_id;
static int __init led_init(void) {
device_class = class_create(THIS_MODULE, "xxx");
device_node = device_create(device_class, NULL, device_id, NULL, "xxx");
return 0;
}
static void __exit led_exit(void) {
device_destroy(device_class, device_id);
class_destroy(device_class);
}
파일 기반 데이터 설정
하드웨어 장치의 속성을 구조체로 정리하는 것이 좋습니다:
struct test_dev {
dev_t device_id;
struct cdev cdev;
struct class *device_class;
struct device *device_node;
int device_major;
int device_minor;
};
open 함수에서 파일 기반 데이터를 설정합니다:
static int test_open(struct inode *inode, struct file *filp) {
filp->private_data = &testdev;
return 0;
}
드라이버 작성
LED 드라이버 예시
#include
#include
#include
#include
#include
#define DEVICE_COUNT 1
#define DEVICE_NAME "led_driver"
#define LED_ON 1
#define LED_OFF 0
struct led_dev {
dev_t device_id;
struct cdev cdev;
struct class *device_class;
struct device *device_node;
int device_major;
int device_minor;
};
struct led_dev led_dev;
static int led_open(struct inode *inode, struct file *filp) {
filp->private_data = &led_dev;
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
unsigned char data;
copy_from_user(&data, buf, cnt);
if (data == LED_ON) {
// LED 켜기 로직
} else if (data == LED_OFF) {
// LED 끄기 로직
}
return cnt;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
테스트 앱 작성
#include
#include
#include
int main(int argc, char **argv) {
int fd;
int value;
if (argc != 3) {
printf("Usage: %s <value>\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("open failed");
return -1;
}
value = atoi(argv[2]);
write(fd, &value, sizeof(value));
close(fd);
return 0;
}
실행 및 테스트
Makefile 예시:
KERNELDIR := /path/to/kernel
CURRENT_PATH := $(shell pwd)
obj-m := led_driver.o
build:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
명령어:
make
arm-none-linux-gnueabihf-gcc test_app.c -o test_app
depmod
modprobe led_driver
./test_app /dev/led_device 1
./test_app /dev/led_device 0
rmmod led_driver