디바이스 드라이버4 - read(), write()를 통한 스위치 & LED 제어

kenGwon·2024년 2월 23일
0

[Embedded Linux] BSP

목록 보기
27/36

초기화와 종료처리


디바이스 드라이버 소스파일 내의 init(), exit(), open(), release() 함수에 대해서 말하는 것이다.


I/O 포트 영역 race condition 처리 함수

먼저 접근 가능한 메모리인지 확인을 해보고 접근을 해야한다. 안그러면 segmentation fault가 나버린다. 가장 fatal한 상황이다. 그래서 커널에서 pointer를 사용할 때는 정말 신중하게 사용해야 한다.

#include <linux/ioport.h>하면 아래 함수를 통해서 체크해볼 수 있다.

이거 말고도 다양하게 더 있다.

I/O mapped I/O 방식에서 I/O 포트의 주소를 확인

우분투 가장머신에서 아래 명령어를 쳐보자.

memory mapped I/O 방식에서 I/O 포트의 주소를 확인

라즈베리파이 커널에서 아래 명령어를 쳐보자.


디바이스 드라이버 읽기 쓰기의 구조

정말 중요한 개념도이다. 여기서도 핵심이 되는 것은 put_user(), copy_to_user(), get_user(), copy_from_user() 이 4개의 함수이다.

사용자 어플리케이션은 그 프로세스 주소 공간에서만 제어 가능하고 커널 영역의 메모리 공간에 접근할 수 없다. 마찬가지로 커널에서도 함부로 사용자 어플리케이션 주소공간에 맘대로 접근하면 안된다.

즉 서로 자기 영역의 메모리 공간이 아니기 때문에, 그 메모리 공간이 가르키는 정보에 직접 접근하여 읽거나 쓰지 않고, 중간에 버퍼를 두고 서로의 주소 공간을 복사하여 사용하는 식으로 운영하게 되는 것이다.

그래서 필요한 함수들이 put_user(), copy_to_user(), get_user(), copy_from_user()이다. 꼭 이 4개의 함수를 가지고 어플리케이션과 디바이스드라이버가 서로 읽기/쓰기가 이루어져야 한다.

교제 페이지 232쪽부터 읽어보면 왜 그런지 그 이유를 알 수 있다.





코드 실습

우리는 우선 open(), close(), read(), write()만 가지고 해보겠다.

목표

  • 디바이스 드라이버 파일은 /dev/ledkey c 230 0이라고 만들면 되도록
  • 저수준 입출력함수 read(dev, buf, size)로 key값 읽기
  • 저수준 입출력함수 write(dev, buf, size)로 key값 쓰기

ledkey_dev.c (디바이스 드라이버)

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/gpio.h> // gpio

#define LEDKEY_DEV_NAME			"ledkey_dev"
#define LEDKEY_DEV_MAJOR		230 // 디바이스 주번호

#define OFF 0
#define ON 1
#define GPIOLEDCNT 8
#define GPIOKEYCNT 8
static int gpioLed[GPIOLEDCNT] = {6,7,8,9,10,11,12,13};
static int gpioKey[GPIOKEYCNT] = {16,17,18,19,20,21,22,23};

static int gpioLedInit(void);
static void gpioLedSet(long);
static void gpioLedFree(void);
static int gpioKeyInit(void);
static int gpioKeyGet(void);
static void gpioKeyFree(void);

static int gpioLedInit(void)
{
	int i;
	int ret=0;
	char gpioName[10];
	for(i=0;i<GPIOLEDCNT;i++)
	{
		sprintf(gpioName,"led%d",i);
		ret = gpio_request(gpioLed[i],gpioName);
		if(ret < 0) {
			printk("Failed gpio_request() gpio%d error \n",i);
			return ret;
		}

		ret = gpio_direction_output(gpioLed[i],OFF);
		if(ret < 0) {
			printk("Failed gpio_direction_output() gpio%d error \n",i);
			return ret;
		}
	}
	return ret;
}

static void gpioLedSet(long val)
{
	int i;
	for(i=0;i<GPIOLEDCNT;i++)
	{
		gpio_set_value(gpioLed[i],(val>>i) & 0x1);
	}
}
static void gpioLedFree(void)
{
	int i;
	for(i=0;i<GPIOLEDCNT;i++)
	{
		gpio_free(gpioLed[i]);
	}
}
static int gpioKeyInit(void)
{
	int i;
	int ret=0;
	char gpioName[10];
	for(i=0;i<GPIOKEYCNT;i++)
	{
		sprintf(gpioName,"key%d",gpioKey[i]);
		ret = gpio_request(gpioKey[i], gpioName);
		if(ret < 0) {
			printk("Failed Request gpio%d error\n", 6);
			return ret;
		}
	}
	for(i=0;i<GPIOKEYCNT;i++)
	{
		ret = gpio_direction_input(gpioKey[i]);
		if(ret < 0) {
			printk("Failed direction_output gpio%d error\n", 6);
       	 	return ret;
		}
	}
	return ret;
}
static int gpioKeyGet(void)
{
	int i;
	int ret;
	int keyData=0;
	for(i=0;i<GPIOKEYCNT;i++)
	{
//		ret=gpio_get_value(gpioKey[i]) << i;
//		keyData |= ret;
		ret=gpio_get_value(gpioKey[i]);
		keyData = keyData | ( ret << i );
	}
	return keyData;
}
static void gpioKeyFree(void)
{
	int i;
	for(i=0;i<GPIOKEYCNT;i++)
	{
		gpio_free(gpioKey[i]);
	}
}

/////////////////////////////////////////////////////////////////////////////////////////////////

static int ledkey_open(struct inode *inode, struct file *filp)
{
	int num0 = MINOR(inode->i_rdev); 
	int num1 = MAJOR(inode->i_rdev);
	printk("ledkey open-> minor : %d\n", num0);
	printk("ledkey open-> major : %d\n", num1);

	try_module_get(THIS_MODULE);
	return 0;
}

static loff_t ledkey_llseek(struct file *filp, loff_t off, int whence)
{
	printk("ledkey llseek -> off : %08X, whence : %08X\n", (unsigned int)off, whence);
	return 0x23;
}

static ssize_t ledkey_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
	printk("ledkey read -> buf : %08X, count : %08X \n", (unsigned int)buf, count);

	char kval;
	kval = gpioKeyGet();
	printk("%d\n", kval);
	put_user(kval, buf);

	// result = copy_to_user(buf, &kval, count);

	return count;
}

static ssize_t ledkey_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
	// 밖에서 저수준 입출력 함수로 write()할때는 void* 형으로 buf를 전달했는데, 이 함수와 연결되면서 buf가 const char* 형으로 강제 형변환되었다.
	// const char *buf라고 선언을 해둬야 여기 코드 안에서 실수로 *buf가 가리키는 공간을 바꿔버리는 것을 방지하기 위함이다.
	// 즉, 함수의 파라미터를 구성할 때 원본데이터가 되는 src를 const로 선언하여 수정되는 것을 방지해주는 것은 일반적인 방법이다. 아래의 strcpy() 함수를 보면 제대로 알 수 있다.
	/*
	char * strcpy(char * dest,const char *src)
	{
		char *tmp = dest;

		while ((*dest++ = *src++) != '\0')
			;
		return tmp;
	}
	*/
	// 이러한 개념 원리가 이 코드에도 적용되어 있는 것이다.
	// const로 선언 안하고 Readonly 영역에 write할려고 하면 그 순간 segmentation fault가 일어나면서 시스템이 죽어버린다. 너무나 fatal한 상황인 것이다. 하지만 코드상에 Readonly 영역 포인터를 const로 선언해놓고 거기에 무언가 값을 write하려고 하면 컴파일 순간에 컴파일 에러가 난다. 즉 프로그램이 돌면서 segmentaion fault가 나기 전에 컴파일 타임에 error를 발생시켜서 좀더 graceful하게 실수를 방지할 수 있다는 것이다.

	printk("ledkey write -> buf : %08X, count : %08X \n", (unsigned int)buf, count);

#if 0
	gpioLedSet(*buf); // 어플리케이션에서 할당한 값을 참조하여 불을 켬
	// buf라는 포인터 변수는 커널 공간에 할당된 것이 맞다. 
	// 하지만 buf라는 포인터 변수 안에 담긴 주소 값은 사용자 공간이라는 것이 핵심이다.
	// 근데 이렇게 직접접근 하지 말라고 했다. 반드시 값을 복사 받아서 사용하라고 했다.
	// 물론 그냥 이렇게 해도 실행이 되긴 한다. 근데 이러면 안된다는 것이다. 그 이유는 교재232쪽을 보면 알수 있다.
#else
	// 버퍼가 단 1바이트라면...
	char kbuff;
	get_user(kbuff, buf); // kbuff는 '일반 변수'이고, buf는 '포인터'이다.

	// 만약 버퍼가 1바이트가 아니라 여러 바이트였다면...
	/*  char kbuff[10];
		int i;
		for(i=0;i<count;i++)
			get_user(kbuff[i],buf++);
	 */
	/*  char kbuff[10];
		copy_from_user(kbuff,buf,count);
	 */
	/*  int result;
		result = copy_from_user(&kbuff, buf, count);
	 */

	gpioLedSet(kbuff); // gpioLedSet은 long을 받기를 기대하고 있는데 char을 넣어줬다. 이건 문제가 안된다.
#endif
	return count; // 실제로 읽은 바이트 수를 리턴해야 한다. 
}

static long ledkey_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	printk("ledkey ioctl -> cmd : %08X, arg : %08X\n", cmd, (unsigned int)arg);
	return 0x53;
}

static int ledkey_release(struct inode *inode, struct file *filp)
{
	printk("ledkey release \n");

	module_put(THIS_MODULE);
	return 0;
}

// 구조체다
struct file_operations ledkey_fops = {
	//.owner			= THIS_MODULE,
	.llseek			= ledkey_llseek,
	.read			= ledkey_read,
	.write			= ledkey_write,
	.unlocked_ioctl	= ledkey_ioctl,
	.open			= ledkey_open,
	.release		= ledkey_release, // 저수준 입출력함수에서 close()가 .release에 대응한다.
};

static int ledkey_init(void)
{
	int result;

	printk("ledkey ledkey_init \n");

	result = gpioLedInit();
	if(result < 0)
		return result;
	result = gpioKeyInit();
	if(result < 0)
		return result;

	// 캐릭터 디바이스를 메모리에 적재시키는 명령
	result = register_chrdev(LEDKEY_DEV_MAJOR, LEDKEY_DEV_NAME, &ledkey_fops); // ledkey_fops의 주소값을 등록
	if (result < 0)
		return result;

	return 0;
}

static void ledkey_exit(void)
{
	printk("ledkey ledkey_exit \n");

	unregister_chrdev(LEDKEY_DEV_MAJOR, LEDKEY_DEV_NAME);

	gpioLedFree();
	gpioKeyFree();
}

module_init(ledkey_init);
module_exit(ledkey_exit);
MODULE_AUTHOR("KCCI");
MODULE_DESCRIPTION("Device Driver pracite~!");
MODULE_LICENSE("Dual BSD/GPL");

ledkey_app.c (어플리케이션 프로그램)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#pragma GCC diagnostic ignored "-Wunused-result" // scanf()쓸 때 리턴값 안받는걸로 경고 띄위지 말라는 것

int main(int argc, char *argv[])
{
	int i;
	int ledkey_fd;
    int key_data, key_data_old = 0;
	unsigned long val = 0;
	char buff;

	// 예외처리1
	if(argc < 2)
	{
		printf("USAGE : %s ledVal[0x00~0xff]\n", argv[0]);
		return 1;
	}

	// 예외처리2
	val = strtoul(argv[1], NULL,16);
	if(val < 0 || 0xff < val)
	{
		printf("Usage : %s ledValue[0x00~0xff]\n", argv[0]);
		return 2;
	}

	// 실제 프로그램 실행 후 예외처리3
	ledkey_fd = open("/dev/ledkey", O_RDWR | O_NONBLOCK);
	if(ledkey_fd < 0)
	{
		perror("open()");
		return 3;
	}

	// 우선 LED 켜보기
	buff = (char)val; // 버퍼에 val값을 담아서 led를 켜는 디바이스 드라이버 호출
	write(ledkey_fd, &buff, sizeof(buff)); // 여기서 &buff는 void* 파라미터에 대응하는 것이다.

	do 
	{
		usleep(100000);

		// 이 함수를 읽어오는거랑 쓰는걸로 찢어야 한다.
		// key_data = syscall(__NR_mysyscall, val);
		read(ledkey_fd, &buff, sizeof(buff));
		key_data = buff;

		if(key_data != key_data_old)
		{
			key_data_old = key_data;
			if(key_data)
			{
				write(ledkey_fd, &buff, sizeof(buff)); // 여기서 &buff는 void* 파라미터에 대응하는 것이다.
				puts("0:1:2:3:4:5:6:7");
				for(i=0;i<8;i++)
				{
					if(key_data & (0x01 << i))
						putchar('O');
					else
						putchar('X');
					if(i != 7 )
						putchar(':');
					else
						putchar('\n');
				}
				putchar('\n');
			}
			if(key_data == 0x80)
				break;
		}
	} while(1);

	printf("mysyscall return value = %#04x\n",key_data);

	return 0;
}

폴링방식 프로그램의 문제점


지금까지의 어플리케이션 프로그램은 안에서 do~while()문이 매우 빠른 속도로 무한루프 돌면서 폴링하면서 하다보니까 $top으로 CPU 점유율을 찍어보면, 우리의 프로그램이 하나의 코어를 거의 100% 점유하고 있는 상황을 볼 수 있었다.

인터럽트로 처리하면 해결

그래서 blocking I/O를 통해서 프로세스를 sleep모드로 바꿔버릴 것이다.
그리고 HW interrupt가 들어왔을 때만 sleep모드에서 탈출하도록 하는 것이 올바른 구현 방향이다.

profile
스펀지맨

0개의 댓글