tasklet을 배우고 실습하다 보니, 캐릭터 디바이스를 활용한 예제를 찾았다. 하다보니 강제로 character device를 등록, 열고, 닫고, 읽고, 쓰는 방법을 배웠다. 대충 이해한 다음 아래 동작을 구성했다.
- read: 앞쪽부터 읽음.
- write: 데이터가 있다면 뒤로 채워 넣음.
- 인터럽트: gpio를 시물레이션하여 앞에서 한 글자씩 지움.
- 버퍼 용량은 char 256개 사용.
전에 사용한 파일을 수정했고, 모듈을 만들기 위한 makefile을 그대로 사용했다.
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/irq.h> #include <linux/interrupt.h> #include <linux/gpio.h> //GPIO #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/fcntl.h> #define GPIO_10_OUT (10) #define DEVICE_DATA_MAX 256 unsigned int GPIO_irqNumber; void my_tasklet_fn(unsigned long); /*\uc0ac\uc6a9\uc790 \ub370\uc774\ud130 \ubd80\ubd84*/ static struct my_device_data{ struct cdev cdev; int index; char my_string[DEVICE_DATA_MAX]; }my_data_global; void my_tasklet_fn(unsigned long data) { /*taskelet \ubc1c\uc0dd\ud558\uba74 \uc55e\uc5d0\uc11c\ubd80\ud130 1\uac1c \ubb38\uc790 \uc0ad\uc81c*/ struct my_device_data *my_data; int i; /*address\ub85c data\uac00 \ub118\uc5b4\uc624\uae30 \ub54c\ubb38\uc5d0 \ud3ec\uc778\ud130\ub85c casting*/ my_data = (struct my_device_data*)data; //\uc55e\uc5d0\uc11c\ubd80\ud130 \ud55c \ubb38\uc790 \uc0ad\uc81c for (i=0;i<DEVICE_DATA_MAX;i++) { my_data->my_string[i]=my_data->my_string[i+1]; } my_data->my_string[(DEVICE_DATA_MAX-1)]=(char)0; pr_info("taskelet: deleted 1st character.\n"); } /* Init the Tasklet by Static Method */ DECLARE_TASKLET(my_tasklet,my_tasklet_fn, (unsigned long)&my_data_global); static irqreturn_t gpio_irq_handler(int irq, void *dev_id) { /*Scheduling Task to Tasklet*/ tasklet_schedule(&my_tasklet); //pr_info("interrupt occured\n"); return IRQ_HANDLED; } //device driver \uc791\uc131 \ubd80\ubd84. /*************\ub4dc\ub77c\uc774\ubc84 \ud568\uc218 ******************/ static int mydriver_open(struct inode *inode, struct file *file); static int mydriver_release(struct inode *inode, struct file *file); static ssize_t mydriver_read(struct file *flip, char *buf, size_t len, loff_t *off); static ssize_t mydriver_write(struct file *flip, const char *buf, size_t len, loff_t *off); /********************************************/ //file operation structure static struct file_operations fops = { .owner = THIS_MODULE, .read = mydriver_read, .write = mydriver_write, .open = mydriver_open, .release = mydriver_release, }; static int mydriver_open(struct inode *inode, struct file *file) { pr_info("Deviced file was opend.\n"); return 0; } static int mydriver_release(struct inode *inode, struct file *file) { pr_info("Deviced file was closed.\n"); return 0; } static int mydriver_read(struct file *file, char *buf, size_t len, loff_t *off) { struct my_device_data *my_data; size_t datalen, read_len; my_data = (struct my_device_data*)&my_data_global; datalen = strlen(my_data->my_string); //\ucd5c\ub300\uac12\uc73c\ub85c \uac15\uc81c \uc124\uc815 if(len > datalen) { len = datalen; } read_len = len - *off; if (read_len <= 0) return 0; pr_info("start %p, offset is %lld, read_len is %d\n", my_data->my_string, *off, read_len); if(copy_to_user(buf, my_data->my_string+*off, read_len)) return -EFAULT; //read_len = datalen-*off; *off += read_len; pr_info("kernel has %d, read %d characters from kernel\n", datalen, read_len); //zero\ub97c \ubc18\ud658\ud560 \ub54c\uae4c\uc9c0 \ubc18\ubcf5. return read_len; } static int mydriver_write(struct file *flip, const char *buf, size_t len, loff_t *off) { struct my_device_data *my_data; size_t datalen, write_len, start_pos; my_data = &my_data_global; start_pos = strlen(my_data->my_string); datalen = strlen(my_data->my_string); if(len > datalen) { len = DEVICE_DATA_MAX; } write_len = len - *off; if(copy_from_user(my_data->my_string + start_pos + *off, buf, write_len)) return -EFAULT; *off += write_len; return write_len; } dev_t dev = 0; static struct cdev my_cdev; static struct class *dev_class; static int __init init_hw(void) { //\ub514\ubc14\uc774\uc2a4 \ub4f1\ub85d if(( alloc_chrdev_region(&dev, 0, 1, "test_device") < 0)) { pr_err("[!]character device was not allocated\n"); goto r_unreg; } pr_info("[=]%d-%d, was allocated\n", MAJOR(dev), MINOR(dev)); //\ucd08\uae30\ud654 cdev_init(&my_cdev, &fops); pr_info("[=]driver was initialized\n"); //\uc2dc\uc2a4\ud15c\uc5d0 \ucd94\uac00 if((cdev_add(&my_cdev, dev, 1)) < 0) { pr_err("[!]cannot add device to kernel\n"); goto r_del; } //class \ub9cc\ub4e6. if((dev_class=class_create(THIS_MODULE, "my_class")) == NULL) { pr_err("[!]cannot add class\n"); goto r_class; } if((device_create(dev_class, NULL, dev, NULL, "my_device")) == NULL) { pr_err("[!]cannot create device\n"); goto r_device; } //gpio 10\ubc88\uc744 \uc0ac\uc6a9. //export\ud558\uc5ec \uac04\ub2e8\ud788 \uc0ac\uc6a9. //\uc785\ub825\uc740 \uac12\uc744 \uc368 \ub123\uc744 \uc218 \uc5c6\uc74c. \ucd9c\ub825\uc73c\ub85c \uc124\uc815. GPIO_irqNumber = gpio_to_irq(GPIO_10_OUT); pr_info("[=]irq %d was assinged\n",GPIO_irqNumber); //interrupt \ub4f1\ub85d \ud544\uc694 if (request_irq(GPIO_irqNumber, (void*)gpio_irq_handler, IRQF_TRIGGER_RISING, "my_device", NULL)) { pr_err("[!]my_device: cannot register IRQ\n"); goto r_gpio; } pr_info("[=]module was installed\n"); return 0; r_gpio: gpio_free(GPIO_10_OUT); r_device: device_destroy(dev_class,dev); r_class: class_destroy(dev_class); r_del: cdev_del(&my_cdev); r_unreg: unregister_chrdev_region(dev,1); return -1; } static void __exit exit_hw(void) { free_irq(GPIO_irqNumber, NULL); gpio_free(GPIO_10_OUT); device_destroy(dev_class,dev); //class_unregister(dev_class); class_destroy(dev_class); cdev_del(&my_cdev); unregister_chrdev_region(dev,1); printk(KERN_INFO "module was removed\n"); } module_init(init_hw); module_exit(exit_hw); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Eunseong Park (esp-ark.com)"); MODULE_DESCRIPTION("Hello, world!");
make 후 모듈을 로딩한다. 캐릭터 디바이스가 236, 0으로 등록되었다.
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ sudo insmod my_driver.ko pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ lsmod | grep my_driver my_driver 16384 0 pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ dmesg | tail -4 [42918.717112] [=]236-0, was allocated [42918.717123] [=]driver was initialized [42918.717332] [=]irq 54 was assinged [42918.717363] [=]module was installed pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ ls /dev/my_device -l crw------- 1 root root 236, 0 7\uc6d4 14 12:45 /dev/my_device
쓰기를 위해 권한을 부여했다.
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ sudo chmod 666 /dev/my_device ;ls -l /dev/my_device crw-rw-rw- 1 root root 236, 0 7\uc6d4 14 12:45 /dev/my_device
로딩 후 읽어보면 아무런 데이터를 읽을 수 없다.
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ head -c10 /dev/my_device pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ dmesg | tail -2 [43123.405028] Deviced file was opend. [43123.405091] Deviced file was closed.
원하는 데이터를 디바이스에 기록한다.
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ echo "I need more space to delete characters" > /dev/my_device pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ echo "I need more space to delete characters" > /dev/my_device
내용을 확인한다. 최대 길이를 넘기면 잘못 읽는데 어떻게 해결할 지 모르겠다.
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ head -c100 /dev/my_device I need more space to delete characters I need more space to delete characters head: '/dev/my_device를 읽는 도중 오류 발생: 주소가 잘못됨 pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ dmesg | tail -10 [43264.763161] Deviced file was closed. [43309.683908] Deviced file was opend. [43309.683961] start 18f123ee, offset is 0, read_len is 10 [43309.683972] kernel has 78, read 10 characters from kernel [43309.684006] Deviced file was closed. [43316.934115] Deviced file was opend. [43316.934165] start 18f123ee, offset is 0, read_len is 78 [43316.934183] kernel has 78, read 78 characters from kernel [43316.934260] start 18f123ee, offset is 78, read_len is -56 [43316.934813] Deviced file was closed.
인터럽트를 설정 한 다음 발생시킨다.
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ sudo ./config_gpio.sh 1 pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ ./make_gpio.sh pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ cat config_gpio.sh #!/bin/bash echo "10" > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio10/direction gpio read 10 pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ cat make_gpio.sh #!/bin/bash #echo "10" > /sys/class/gpio/export #sleep(1) #echo "out" > /sys/class/gpio/gpio10/direction #sleep(1) #gpio read 10 #sleep(1) echo 0 > /sys/class/gpio/gpio10/value ; echo 1 > /sys/class/gpio/gpio10/value; sleep 1 echo 0 > /sys/class/gpio/gpio10/value ; echo 1 > /sys/class/gpio/gpio10/value; sleep 1 echo 0 > /sys/class/gpio/gpio10/value ; echo 1 > /sys/class/gpio/gpio10/value; pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ dmesg | tail -10 [43309.683972] kernel has 78, read 10 characters from kernel [43309.684006] Deviced file was closed. [43316.934115] Deviced file was opend. [43316.934165] start 18f123ee, offset is 0, read_len is 78 [43316.934183] kernel has 78, read 78 characters from kernel [43316.934260] start 18f123ee, offset is 78, read_len is -56 [43316.934813] Deviced file was closed. [43694.677107] taskelet: deleted 1st character. [43695.686488] taskelet: deleted 1st character. [43696.693794] taskelet: deleted 1st character.
다시 읽어본다. 앞 캐릭터 3개가 지워졌다.
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ head -c100 /dev/my_device eed more space to delete characters I need more space to delete characters head: '/dev/my_device를 읽는 도중 오류 발생: 주소가 잘못됨
https://olegkutkov.me/2018/03/14/simple-linux-character-device-driver/
https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html
https://stackoverflow.com/questions/1330284/how-might-i-learn-to-write-char-device-drivers-for-linux
https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch03s04.html
https://pr0gr4m.tistory.com/entry/Linux-Kernel-5-Character-Device-Driver
http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/
https://stackoverflow.com/questions/12124628/endlessly-looping-when-reading-from-character-device