CrossCompile

Linux Device Driver (LED)

sh1mj1 2022. 11. 11. 15:46

Linux Device Driver Overview

  • Device
    • Hardware, I/O
  • Driver
    • 하드웨어를 구동시키는 Software
  • Device driver
    • privileged mode 에서 drive the device(hardware)
    • OS의 한 부분이다. user application 이 아님!
    • module: kernel object의 또 다른 이름. 런타임에서 추가되거나 삭제될 수 있다.

어떻게 드라이버가 디바이스를 컨트롤할까?

  • 디바이스를 컨트롤한다는 것은 디바이스로 command 와 value을 작성하는 것, 혹은 디바이스로부터 상태나 값을 읽는 것을 의미한다!
  • 메모리와 I/O 는 매핑되어 있는 매커니즘이다.
    • 소프트웨어는 I/O 로부터 값을 읽을 수 있다. 혹은 I/O로 값을 write 할 수 있다. 이것은 마치 메모리로부터 값을 읽거나 메모리로부터 값을 쓰는 것과 같다.
    • 메모리는 항상 주소로 접근된다.
  • 디바이스 내에 있는 command register 나 data buffer 가 메모리와 매핑되어 있다.
    #define LED_buffer_addr 0x80000000
    unsigned int *led_buffer_addr = LED_buffer_addr;
    
    // writing values to LED
    *led_buffer_addr = 0x1010;
    
  • 예를 들어서 아래와 같다.

Linux Device Driver Interface

File operations/APIs 는 LDD(Linux Device Driver)에서 사용된다.

어플리케이션은 오직 파일 API 만 호출할수 있다.

디바이스 드라이버 개발자는 오직 디바이스의 동작을 정의만 하면 된다!!

LED 는 output 디바이스이므로 read() API 는 필요하지 않는다.

device driver

특정 하드웨어나 장치를 제어하기 위한 커널의 일부분으로 동작하는 프로그램이다. privileged mode(권한레벨 15)에서 동작한다.

그림1. linux device driver

그림 1에서 볼 수 있듯이 사용자와 리눅스의 관점에서 디바이스 드라이버는 다르게 취급된다. 사용자는 디바이스에 대한 정보를 알 필요없이 드라이버를 통하여 디바이스에 접근 가능하다.

우리는 Target board 를 한백전자의 보드를 사용한다.

arm-linux-gnueabi-gcc 와 Android Debug Bridge (ADB) 가 필요하다.

LED Device Driver.c 전체 코드

#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <mach/regs-gpio.h>
#include <linux/of_gpio.h>
#include <hanback/gpios.h>
#include <linux/delay.h>
	
static int sm9s5422_led_open(struct inode * inode, struct file * file){
	printk(KERN_CRIT"sm9s5422_led_open, \\n");

	printk("Student name: Shim Jihoon \\n");
	printk("Student number: 2017440073 \\n");

	int err,i;

	for (i=0; i<8; i++)
	{
		err = gpio_request(gpy7(i), "Led");

		if (err){
			printk("led.c failed to request gpy7(%d) \\n", i);
		}
		s3c_gpio_setpull(gpy7(i), S3C_GPIO_PULL_NONE);
		gpio_direction_output(gpy7(i),0);
	}

	return 0;
}

static int sm9s5422_led_release(struct inode * inode, struct file * file){
	printk("sm9s5422_led_release, \\n");
	int i;
	for (i =0; i<8; i++){
		gpio_free(gpy7(i));
	}
	return 0;
}

static ssize_t sm9s5422_led_write(struct file * file, const char * buf, size_t length, loff_t * ofs){
	printk("sm9s5422_led_write, \\n");

	int ret;
	unsigned char cbuf[8];
	ret = copy_from_user(cbuf, buf, length);

	gpio_direction_output(gpy7(0), (unsigned int) cbuf[0]);
	gpio_direction_output(gpy7(1), (unsigned int) cbuf[1]);
	gpio_direction_output(gpy7(2), (unsigned int) cbuf[2]);
	gpio_direction_output(gpy7(3), (unsigned int) cbuf[3]);
	gpio_direction_output(gpy7(4), (unsigned int) cbuf[4]);
	gpio_direction_output(gpy7(5), (unsigned int) cbuf[5]);
	gpio_direction_output(gpy7(6), (unsigned int) cbuf[6]);
	gpio_direction_output(gpy7(7), (unsigned int) cbuf[7]);
	return 0;
}

static struct file_operations sm9s5422_led_fops = {
	.owner = THIS_MODULE,
	.open = sm9s5422_led_open,
	.release = sm9s5422_led_release,
	.write = sm9s5422_led_write
};

static struct miscdevice sm9s5422_led_driver = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "sm9s5422_led",
	.fops = &sm9s5422_led_fops,
};

static int sm9s5422_led_init(void){
	printk("sm9s5422_led_init, \\n");
	return misc_register(&sm9s5422_led_driver);
}

static void sm9s5422_led_exit(void){
	printk("sm9s5422_led_exit, \\n");
	misc_deregister(&sm9s5422_led_driver);
}

module_init(sm9s5422_led_init);
module_exit(sm9s5422_led_exit);

MODULE_AUTHOR("MPCLASS");
MODULE_DESCRIPTION("Led Control");
MODULE_LICENSE("Dual BSD/GPL");

file_operations 구조체

디바이스 드라이버 (Device Driver) - 3. file_operations 구조체

먼저 LED Device Driver 코드 중에서 file_operatioins 을 봅시다.

static struct file_operations sm9s5422_led_fops = {
	.owner = THIS_MODULE,
	.open = sm9s5422_led_open,
	.release = sm9s5422_led_release,
	.write = sm9s5422_led_write
};

문자 디바이스 드라이버와 응용 프로그램을 연결하는 고리이다.

linux/fs.h 에서 정의하는 이 구조체는 함수 포인터 집합이다. 특정 동작 함수를 구현하여 가리켜야 한다.

file_operations 구조체 설명

struct file_operations{

       struct moduel *owner;
       ssize_t (*read) (struct file *, loff_t, int);
       ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
       int (*open) (struct inode *, struct file *);
       int (*release) (struct inode *, struct file *);
       int (*mmap) (struct inode *, struct file *);
...
  • struct module *owner;
    • File operation의 소유자를 나타낸다. 보통 <linux/module.h> 에 정의되어 있는 THIS_MODULE 매크로를 사용해서 초기화한다.
  • ssize_t (*read) (struct file *, char *, size_t, loff_t *);
    • 디바이스에서 자료를 읽는데 사용한다. NULL 이면 -EINVAL 반환
  • ssize_t (*write) (struct file *, const char *, size_t, loff_t * );
    • 자료를 디바이스로 보낸다. NULL이면 -EINVAL 반환
  • unsigned int (*poll) (struct file *, struct poll_table_struct * );
    • 다중 입출력 처리를 가능하게 해주는 poll, epoll, select의 백엔드이다.
  • int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    • 디바이스 관련 명령들을 제어할 수 있다.
  • int (*unlocked_ioctl) (struct file *flip, unsigned int cmd, unsigned long arg);
    • 모든 CPU에서 lock을 걸던 것을 개별적인 lock을 걸 수 있도록 ioctl에서 바꾼 함수.
    • 함수에 진입 시 커널에 대한 락이 처리되어지지 않기 때문에 함수 내에서 별도의 동기화 기법을 사용해야 한다. 이 함수를 부르면 된다.

연결되는 함수

  • open 함수
    • 디바이스 드라이버가 처음 열렸을 때 하드웨어를 초기화한다.
    • 디바이스 드라이버의 동작에 필요한 에러를 체크한다.
      • ENODEV 하드웨어가 존재하지 않는다.
      • ENOMEM 커널 메모리가 부족하다.
      • EBUSY 디바이스가 이미 사용 중이다.
      • ⇒ 위 두가지 것은 아래에 우리가 sm9s5422_led_open 함수에서 반복문을 통해 구현했다.
  • release 함수
    • device_close 로 부르는 경우도 있다.
    • open 이 flip→private data 에 데이터를 할당했었는데 이 데이터의 할당을 삭제한다.
    • ⇒ 우리는 아래에 gpio_free 함수로 이것을 구현했다.
    • 마지막 close 호출 시 디바이스를 종료한다.
  • write 함수
    • 데이터 전달 (copy_from_user)
      • 아래에서 설명할 것이다. kernel 영역과 user 영역은 서로 접근하지 못하는 메모리 영역이기 때문에 pointer 을 이용하지 못하고 데이터를 전달하는 함수를 이용한다.
  • ssize_t_xxx_write(struct file *flip, char *buf, size_t count, loff_t *offp) /* 사용자 영역인 buff 에서 count 바이트만큼 읽은 후 디바이스의 offp 위치로 저장한다. struct file *flip: 읽기와 쓰기에 전달되는 flip 은 디바이스 파일이 어떤 형식으로 열렸는가에 대한 정보를 저장한다. loff_t *offp: offp 필드 변수에는 현재의 읽기/쓰기 위치를 저장한다.
  • read 함수
    • 이 디바이스 드라이버에서는 구현하지 않아도 되어서 구현하지 않았다.

이제 open 함수, release 함수, write 함수를 봅시다.

함수sm9s5422_led_open in LED Device Driver 코드

static int sm9s5422_led_open(struct inode * inode, struct file * file){
		printk("sm9s5422_led_open, \\n")
	
		int err,i;
		for (i=0; i<8; i++)
		{
			err = gpio_request(gpy7(i), "Led");
	
			if (err){
				printk("led.c failed to request gpy7(%d) \\n", i);
			}
			**s3c_gpio_setpull**(gpy7(i), S3C_GPIO_PULL_NONE);
			**gpio_direction_output**(gpy7(i),0);
		}
	
		return 0;
}

GPIO 는 General Purpose Input Output 의 약자이다.

디지털 I/O 함수를 수행하는 데 쓰이는 통합 회로나 보드에 있는 신호 pin이다. 모든 마이크로컨트롤러들은 적은 양의 레지스터를 가지고 gpio function 을 컨트롤한다.

gpio_request(gpy7(i), “Led”) 을 반복문으로 돌려서 현재 Led 가 다른 설정으로 이미 사용되고 있는지 여부를 판단한다. 만약 이미 어디선가 설정한 pin 이면 실행하지 않게 되고 어디에서도 설정한 흔적이 없다면 0을 리턴한다. 즉, gpio pin을 사용할 때 다른 driver 와 중복해서 사용하는 위험을 피하고자 할 때 사용한다.

int s3c_gpio_setpull(unsigned Int pin, 
										samsung_gpio_pull_t pull)

이 함수는 gpio pin pull 레지스터의 상태를 설정한다.

이 함수는 특정한 핀의 pull(up/down) 레지스터의 상태를 설정한다. 성공하면 0 을 리턴하고 요청된 pull 세팅을 지원하지 않는다면 negative error 코드를 리턴한다.

pull up/down 레지스터는 input이 연결되어 있는 것이 없는 상태 혹은 연결된 부분이 high impedance 상태일 때 input 핀이 상태를 정의하는데 사용된다. 정의되지 않은 input은 input 값이 0이나 1 어떤 것도 될 수 있는 문제(floating)를 가진다.

즉, gpy7[i] 가 아무것도 연결되어 있지 않다는 뜻.

gpio_direction_output(unsigned gpio, int value)

gpy7[i] 의 모든 출력값을 0 으로 설정한다. → led 가 모두 꺼져있게 설정한다.

함수sm9s5422_led_release in LED Device Driver 코드

static int sm9s5422_led_release(struct inode * inode, struct file * file){
	printk("sm9s5422_led_release, \\n");
	int i;
	for (i =0; i<8; i++){
		gpio_free(gpy7(i));
	}
	return 0;
}
void gpio_free (unsigned gpio) 
// 반복문을 통해서 gpy[i] 들의 pin 을 모두 free 시킨다.

함수sm9s5422_led_write in LED Device Driver 코드

static ssize_t sm9s5422_led_write(struct file * file, const char * buf, size_t length, loff_t * ofs){
	printk("sm9s5422_led_write, \\n");

	int ret;
	unsigned char cbuf[8];
	ret = **copy_from_user**(cbuf, buf, length);

	gpio_direction_output(gpy7(0), (unsigned int) cbuf[0]);
	gpio_direction_output(gpy7(1), (unsigned int) cbuf[1]);
	gpio_direction_output(gpy7(2), (unsigned int) cbuf[2]);
	gpio_direction_output(gpy7(3), (unsigned int) cbuf[3]);
	gpio_direction_output(gpy7(4), (unsigned int) cbuf[4]);
	gpio_direction_output(gpy7(5), (unsigned int) cbuf[5]);
	gpio_direction_output(gpy7(6), (unsigned int) cbuf[6]);
	gpio_direction_output(gpy7(7), (unsigned int) cbuf[7]);
	return 0;
}

copy_from_user?

int copy_from_user(void* to, const void __user*from, unsigned long n)

사용자 메모리 블록 데이터를 커널 메모리 블록 데이터에 써넣는다.

from 이 가리키는 주소의 사용자 메모리 블록 데이터를 to가 가리키는 커널 메모리 블록 데이터에 바이트 크기 단위인 n 만큼 써넣는다. 이 함수는 읽어올 공간의 유효성 검사를 수행한다.

PARAMETER:

from: user space 메모리 블록 선두 주소

to: kernel space 메모리 블록 선두 주소

n: 써넣을 바이트 단위의 크기

리턴: 복사되지 않은 바이트 수를 리턴한다. 정상적으로 수행되었다면 0을 리턴한다.

여기서는 buf 가 user space 에 있는 메모리 블록 선두 주소이다.(sm9s5422_led_write의 매개 변수의 buf)

cbuf 라는 array 을 만들어서 여기에 복사한다.

gpio_direction_output 함수 매개변수를 보면 gpy[i] 을 kernel space 에 있는 cbuf 로 설정한다.

즉, sm9s5422_led_write 함수를 사용하여 gpio pin 에 내가 led 에 불을 켜고 싶은 자리에 불을 켠다. sm9s5422_led_write 함수는 user space 에서 사용되기 때문에 함수의 인수로 buf 을 사용한다. 이것이 함수 내에서 kernel space 에 있는 cbuf 로 바뀌어서( copy_from_user 함수로) gpio_direction_output 함수로 led에 불 켜기.

Misc 디바이스 드라이버1

miscdevice 구조체

디바이스 드라이버는 kernel module 의 한 종류이다.

file_operations 구조체를 기반으로 가상 파일 시스템과 연계한다. 사용자는 기존 파일 접근 시스템 호출을 재사용한다.

물리적인 저장 장치나 파일 시스템 설계가 아니라면 캐릭터 디바이스 드라이버를 구현한다. (일반적인 하드웨어 제어에 적용된다. 최근에는 기존 캐릭터 디바이스 드라이버 대신 기타 디바이스 드라이버로 구현한다.)

기타 디바이스 드라이버는 캐릭터 디바이스 드라이버의 일종이다. (노드 파일 관리를 커널이 담당한다.)

static struct miscdevice sm9s5422_led_driver = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "sm9s5422_led",
	.fops = &sm9s5422_led_fops,
};
  • 보조번호를 나타낸 minor 에는 커널이 자동 할당되도록 MISC_DYNAMIC_MINOR 을 할당한다.
    • 기타 디바이스 드라이버의 주 번호는 10번임.
  • 디바이스 드라이버가 로드될 때 /dev 경로에 name 노드 파일이 자동 생성된다.
  • fops 는 앞서 정의한 file_operations 구조체 포인터가 할당된다.
// 디바이스 드라이버 등록. 
// 디바이스 드라이버가 적재될 때 misc_register() 을 호출해서 등록한다.
static int sm9s5422_led_init(void){
	printk("sm9s5422_led_init, \\n");
	return misc_register(&sm9s5422_led_driver);
} 

// 디바이스 드라이버 해제 misc_deregister() 을 호출해서 해제한다.
static void sm9s5422_led_exit(void){
	printk("sm9s5422_led_exit, \\n");
	misc_deregister(&sm9s5422_led_driver);
}

module_init(sm9s5422_led_init);
module_exit(sm9s5422_led_exit);

MODULE_AUTHOR("MPCLASS");
MODULE_DESCRIPTION("Led Control");
MODULE_LICENSE("Dual BSD/GPL");

리눅스 커널의 많은 부분은 모듈 형태로 제공된다. 디바이스 드라이버 는

  • module_init

디바이스 드라이버의 커널을 등록.

‘insmod’ 가 요청될 때마다 관련 함수가 호출된다. 이 때 insmod 는 insert a module 이다.

  • module_exit

디바이스 드라이버의 커널의 등록 해제

‘rmmod’가 요청될 때마다 관련 함수가 호출된다. 이 때 rmmod 는 remove a module 이다.

Device driver: sm9s5422_led.c

Understanding the Microcontroller GPIO - GPIO Working Explained

Makefile for device driver

리눅스(우분투) 환경에서 Makefile 을 만들어서 디바이스 드라이버를 타겟보드에 크로스 컴파일 한 후 생성한 kernel object 파일을 ADB 에 push 한다.

export ARCH=arm
export CROSS_COMPILE=arm-eabi-

CC = arm-eabi-gcc
ADB = adb

ifneq ($(KERNELRELEASE),)
obj-m := sm9s5422_led.o
else
KDIR := /code/linux

all:
	$(MAKE) -C $(KDIR) M=$(shell pwd) modules
	echo
	echo "**** Install:" /system/lib/modules/sm9s5422_led.ko "*****"
	$(ADB) push sm9s5422_led.ko /system/lib/modules/
	$(ADB) shell chmode 644 /system/lib/modules/sm9s5422_led.ko
	echo
	echo "**** Load Module:" /system/lib/modules/sm9s5422_led.ko "*****"
	$(ADB) shell toolbox rmmode sm9s5422_led > /dev/null
	$(ADB) shell insmod /system/lib/modules/sm9s5422_led.ko
	$(ADB) shell lsmod | grep sm9s5422_led
	echo

endif

clean:
	rm -f *.symvers
	rm -f *.ko
	rm -f *.o
	rm -f *.mod.c
	rm -f .*.cmd
	rm -rf .tmp_versions

참고 chmod

파일의 permission 은 read, write, excute 로 rwx 로 많이 표현한다.

이 때 owner, groups, others 순서로 rwx , rwx, rwx 가 표시되고 이는 권한이 있을 떄 1이 된다.

만약 111 110 100 이렇게 되면 퍼미션은 764 인 것이다.

sm9s5422_led_test.c Application

우리 LED device에 대한 어플리케이션은 led bitmap 을 위해 argument 로 실행된다. 이 argument 는 16진수 이다. Ex) 0x77

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char * argv[]) {
	int dev,i;
	char temp,buf[8]={0};
	unsigned char t = 0;

	if(argc <= 1) {
		printf("please input the parameter! ex)./test 0xff\\n");
		return -1;
	}
	dev = open("/dev/sm9s5422_led", O_WRONLY);

// 입력은 0x[16진수) 로 받는다. 예) 0xF0
	if (dev != -1) {
			if(argv[1][0] == '0' && (argv[1][1] == 'x' || argv[1][1] == 'X'))
			{
				// temp 에 0x 다음 문자부터 나온 문자를 정수 타입으로 바꿔서 저장한다. 
				temp = (unsigned short)strtol(&argv[1][2], NULL, 16);
			}
			else
			{
				// 16진수로 입력하지 않고 10진수로 입력했을 때, 정수형으로 바꿈
				temp = atoi(argv[1]); // atoi(=char to int):  문자열을 정수 타입으로 
			}
			
			// 반복문을 통해 입력한 temp을 buf 에 저장한다.
			for (i = 0; i<8; i++) {
					if(( temp >> i) & 1 == 1){
							buf[i] = 1;
						}
			}

	}
	else {
		printf( "Device Open ERROR!\\n");
		return -1;
	}

	write(dev, buf, 8); 
	close(dev);

	return 0;
}

int main(int argc, char * argv[])?

argc, argv[] 가 뭐냐

 

int argc 는 메인함수에 전달되는 정보의 개수이다.

char* argv[] 는 메인함수에 전달되는 실질적인 정보. 문자의 배열이다. 첫번째 문자열은 프로그램의 실행경로로 고정이 되어 있다.

만약 아무것도 전달하지 않는다면 argv[] 에 오직 프로그램의 실행경로로 고정되어 있기 때문에 argc 는 1이 된다.

Makefile For LED Application

CC = arm-linux-gnueabi-gcc
ADB = adb
TARGET = sm9s5422_led_test
TARGET_SRC = $(TARGET).c

all: $(TARGET)
	$(CC) $(TARGET_SRC) -static -o $(TARGET)

	echo 
	echo "**** Install:" /data/local/tmp/sm9s5422_led_test "****"
	$(ADB) push sm9s5422_led_test /data/local/tmp
	$(ADB) shell chmod 755 /data/local/tmp/sm9s5422_led_test

clean: 
	rm -f *.o
	rm -f *.i
	rm -f *.s
	rm -f sm9s5422_led_test