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