character device를 tasklet으로 테스트

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

코멘트

댓글 남기기

이 사이트는 Akismet을 사용하여 스팸을 줄입니다. 댓글 데이터가 어떻게 처리되는지 알아보세요.