[태그:] tasklet

  • 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