Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

꾸준히

리눅스 디바이스 모델 본문

Device Driver

리눅스 디바이스 모델

S210530 2023. 8. 8. 20:47

리눅스 디바이스 모델을 정리하는 글입니다.

 

리눅스 디바이스 모델

 

목차

 

1. 디바이스 모델 구성

2. 플랫폼 디바이스와 드라이버

3. 디바이스 트리

 

 

디바이스 모델 구성

디바이스 모델

디바이스 모델

 

커널에서는 기본적인 형식 위에 다양한 형태의 하드웨어를 쉽게 다룰 수 있도록, 디바이스 드라이버의 구성을 체계화 했다. 즉, 처리 방식을 통합하기 위함이다.

위 디바이스 모델에 따라, HW(위 그림에서 keypad)를 등록하기 위해서,  bus, device, class, driver를 등록해야 한다.
가상 버스 생성 후 driver를 등록하는 순서는 아래와 같다.

  1. 가상 버스를 등록
  2. 가상버스를 디바이스로 등록
  3. 위 디바이스 타입을 사용하여 driver를 등록
  4. 클래스를 등록

 

참고로 디바이스 드라이버 연결 관계를 출력하기 위해 sysfs를 이용할 수 있다.

  • /sys/block : 블록 디바이스 드라이버에 대한 정보 표현
  • /sys/bus : 시스템에 존재하는 버스와 연관된 계층 구조
  • /sys/class : 디바이스 드라이버의 종류에 대한 분류를 표현
  • /sys/devices : 시스템에 존재하는 모든 디바이스에 대한 정보를 표현
  • /sys/firmware : 펌웨어가 제공하는 정보 중 커널에서 사용하는 정보 표현
  • /sys/power : 전원 관리와 관련된 정보 제공

 

bus

버스는 프로세서와 여러 디바이스 사이에 놓인 채널이다.
디바이스 모델이라는 목적을 달성하기 위해, 모든 디바이스는 버스를 경유하여 연결한다. (가상버스에도 적용 됨)
대표적인 버스 형식에는 PCI, IDE, USB ,I2C, PLATFORM등이 있으며, bus_type 구조체로 표현된다. (linux/device.h에 정의 됨)

버스를 생성하여 등록하는 코드를 살펴보면, 아래와 같이 작성할 수 있다.

struct bus_type ldd_bus_type = {
	.name = "ldd",
};

...

result = bus_register(&ldd_bus_type);
	if(result)
		return result;

 

 

코드에서 bus_type 구조체에 버스 name을 정의하고, bus_register 함수를 통해 버스를 등록한다.

struct bus_type {
	const char *name; // bus 하위 시스템 아래에 생성된다.
	struct subsystem subsys;
	struct kset drivers;
	struct kset devices;

	int (*match)(struct device *dev, struct device_driver * drv);
	int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
	....
};

bus_register(struct bus_type *bus)    // 등록
bus_unregister(struct bus_type *bus)  // 해제

 

이 부분은 대다수 드라이버 제작자에게는 필요하지 않지만, 새로운 버스 타입을 추가할 경우나 platform, pci, usb와 같은 버스 계층 내부에서 일어나는 일을 이해하는데 유용하다.

 

device

디바이스란 커널에서 동작하는 하드웨어 개체이며, 실제로 다루는 디바이스가 여러 개가 될 수 있는데, 이런 경우 디바이스와 드라이버를 별도로 관리해야 한다. 저 수준 단계에서 모든 디바이스는 struct device의 인스턴스로 표현하며, 최소한 bus_id, release, parent, bus 필드는 device 구조체를 등록하기에 앞서 반드시 설정해 놓아야 한다.
device 구조체는 <linux/device.h>에 정의되어있다.

struct device{
	struct device *parent;
	struct kobject kobj;
	char bus_id[BUS_ID_SIZE];
	struct bus_type *bus;
	struct device_driver *driver;
	void (*release)(struct device *dev);
	...
}

device_register(strcut bus_type *bus)    // 등록
device_unregister(struct bus_type *bus)  // 해제

 

디바이스를 등록하는 코드를 보자.

static void ldd_bus_release(struct device *dev)
{
	printk(KERN_DEBUG "ldd_bus release\n");
}

struct device ldd_bus = {
	.bus_id = "ldd",
	.release = ldd_bus_release,
};

...

result = device_register(&ldd_bus);
printk("1. platform_device_result = %d \n", result);

 

최상위 단계 버스이므로 parent와 bus필드를 NULL로 남겨 놓는다. 호출이 성공하면 /sys/devices 아래에 새로운 버스(ldd)가 보인다. 이 버스에 디바이스를 추가하면 /sys/devices/ldd 아래 나타난다.

순수한 device 구조체 만으로 디바이스를 표현하는 경우는 드물며, kobject 구조체와 같은 구조체를 더 높은 수준의 디바이스 내부에 내장하는 방법을 사용한다. ex) struct pci_dev, struct usb_device, struct platform_device
struct ldd_device라는 독자적인 디바이스 타입을 생성하며, 이 타입을 사용해서 등록해보자.

struct ldd_device{
	char *name;
	unsigned short vendor;
	struct device dev;
};

static struct ldd_device pdev =
{
	.name = "my_device",
	.vendor = 100,
	.dev = {
	},
}

int ldd_device_register(struct ldd_device *ldddev)
{
	ldddev->dev.bus = &ldd_bus_type;
	ldddev->dev.parent = &ldd_bus;
	dev->dev.release = pdev_release;
	strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);
	return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(ldd_device_register);

void ldd_device_unregister(struct ldd_device *ldddev)
{
	device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(ldd_device_unregister);

....

result = ldd_device_register(&pdev);

호출이 성공하면 /sys/devices/ldd 아래에 새로운 디바이스(my_device)가 보인다.


참고로, 매크로를 정의해서 ldd_device 포인터에 내장된 device 구조체를 가리키는 포인터를 쉽게 제출할 수 있도록 만들 수 있다.

#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);

 

driver

드라이버란 디바이스를 다루는 실질적인 소프트웨어이며, 디바이스 모델은 시스템에 알려진 모든 드라이버를 추적 관리한다. 드라이버 코어가 새로운 디바이스와 드라이버를 matching 시키는 것을 활성화 하기 때문이다. device_driver 구조체로 표현하며, <linux/device.h>에 정의 된다.

struct device_driver {
	char *name; // 드라이버 이름
	struct bus_type *bus; // 드라이버가 동작하는 버스 종류
	struct kobject kobj; // 필수 불가결한 kobject
	struct list_head devices;
	int (*probe)(struct device *dev); // 시스템 초기화시 불리는 함수
	int (*remove)(struct device *dev); // 시스템 제거시 불리는 함수
	void (*shutdown)(struct device *dev);
};

driver_register(struct device_driver *drv)   // 등록
driver_unregister(struct device_driver *drv) // 제거

probe와 remove는 platform device driver에서 사용 된다.

드라이버를 등록하는 코드를 보자.

static struct device_driver my_driver = {
	.name = "my_driver",
	.owner = THIS_MODULE,
	.bus = &ldd_bus_type,
	.probe = my_device_probe,
	.remove = my_device_remove,
};
...
result = driver_register(my_driver);

호출이 성공하면 /sys/bus/ldd/drivers/ 아래에 새로운 드라이버(my_device)가 보인다.

 

class

클래스란 디바이스를 상위 단계에서 보는 시각이다. 연결 방법이나 동작 방법보다는 기능적인 측면에서 디바이스를 다룬다. 거의 모든 클래스는 /sys/class 아래 나타난다. (예외 : /sys/block)
class 구조체로 표현하며, <linux/device.h>에 정의된다.

struct class {
	char *name;
	struct class_attribute *class_attrs;
	struct class_device_attribute *class_dev_attrs;
	void (*release)(struct class_device *dev);
	void (*class_release)(struct class *class);
	...
};

class_register(struct class *cls)   // 등록
class_unregister(struct class *cls) // 해제

 

클래스를 등록하는 코드를 보자.

static void my_class_release(struct class_device *dev)
{
	printk("my class release\n");
}

static struct class my_class = {
	.name = "my_class",
	.release = my_class_release,
};
class_register(&my_class);

호출이 성공하면 /sys/class 아래에 새로운 인터페이스(my_class)가 보인다.

 

클래스 디바이스도 존재하는데, 이는 디바이스의 분류 관리를 위한 모델이다.
class_device 구조체로 표현하며, <linux/device.h>에 정의된다.

struct class_device {
	struct kobject kobj;
	struct class *class; // 디바이스를 쥐고 있는 클래스
	struct device *dev; // /sys/devices 아래 대응하는 항목으로 심볼릭 링크 생성

	void *class_data;
	char class_id[BUS_ID_SIZE]; // sysfs에 나타날 디바이스 이름
};

device_register(struct class_device *cd)    // 등록
device_unregister(struct class_device *cd)  // 해제

 

클래스 디바이스를 등록하는 코드를 보자.

static struct class_device my_cdevice = {
	.class = &my_class,
	.class_id = "my_device0",
};
...

my_cdevice.devt = my_dev;
device_register(&my_cdevice);

호출이 성공하면 /sys/class/my_class 아래에 새로운 인터페이스(my_device0)가 보인다.

 

플랫폼 디바이스와 드라이버

플랫폼 디바이스에 대한 드라이버 등록을 위한 구조체이며, 플랫폼 디바이스는 struct platform_device에 의해 표현된다.
이 디바이스는 가상의 platform bus에 연결되어 다음과 같은 platform_driver의 함수 포인터를 구현해야 한다.
<linux/platform_device.h>에 정의 된다.


플랫폼 디바이스와 드라이버를 등록하는 방법은 아래와 같다.

  • platform_device와 platform_driver를 모두 모듈로 등록하는 방법
  • platform_device는 커널에서 platform_driver는 모듈로 등록하는 방법
  • platform_device와 platform_driver를 모두 커널에 등록하는 방법
  • 사용 빈도가 높은 device는 커널에 등록한다 (커널 사이즈를 줄이기 위해 사용빈도가 낮은 device는 모듈로 등록하는 경우가 많다)

 

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	int (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
};

int platform_driver_register(struct platform_driver *drv);     // 등록
void platform_driver_unregister(struct platform_driver *drv);  // 해제
  • probe : 디바이스의 초기화 루틴(필수)
  • remove : 디바이스가 제거될 때 (필수)
  • shutdown : 디바이스의 종료
  • suspend : 절전 모드로 들어갈 때 호출(필수)
  • resume : 절전 모드에서 빠져나올 때 호출(필수)

 

플랫폼 드라이버를 등록하는 코드를 보자

static struct platform_driver xxx_driver = {
	.probe = xxx_probe,
	.remove = xxx_remove,
	.suspend  xxx_suspend,
	.resume = xxx_resume,
	.driver = {
		.name = "xxx",
		.owner = THIS_MODULE,
	},
};

static int __init xxx_init(void)
{
	if(platform_driver_register(&xxx_driver))
	{
		printk("failed to register xxx driver\n");
		return -ENODEV;
	}
}

 

 

디바이스 트리

다양한 디바이스의 정보를 명시하기 위한 자료구조이며, 리눅스 커널 3.1 버전부터 적용되어 사용된다.
프로그램 코드를 제공하지 않고, 장치를 설명하는 자료구조를 작성하여 os가 이를 해석하고 디바이스를 로드한다.
텍스트파일(.dts)에 의해 정보를 기술하고, 이 파일을 device tree compiler(dtc)로 부르는 툴로 컴파일해서 바이너리(.dtb)로 변환하여 커널에 전달한다.

로드하는 과정은 아래와 같다.

  • dts 파일을 바이너리파일(.dbt)로 컴파일하는 device tree compiler 소스는 커널의 scripts/dtc 경로에 있다
  • dtv 파일은 부팅할 때 부트로더에 의해 적재되고 커널이 파싱한다
  • u-boot에서 부팅 명령어인 bootz에 dtv가 있는 메모리 주소를 알려준다
    • bootz kernel_addr ramdisk_addr dtb_addr
  • 커널에서는 kernel_entry()함수에 dtb_addr을 다음과 같이 전달한다
    • kernel_entry(0, match_id, dtb_addr)

 

arch/arm/boot/dts 경로에서 스크립트를 확인할수 있으며, /proc/device-tree 경로에서 장치 설정 정보를 확인할 수 있다.

다음은 dts의 일부이다

...
		apbx@80040000 {
			compatible = "simple-bus";
			#address-cells = <1>;
			#size-cells = <1>;
			reg = <0x80040000 0x40000>;
			ranges;
...
			auart0: serial@8006a000 {
				compatible = "fsl,imx28-auart", "fsl,imx23-auart";
				reg = <0x8006a000 0x2000>;
				interrupts = <112>;
				dmas = <&dma_apbx 8>, <&dma_apbx 9>;
				dma-names = "rx", "tx";
				clocks = <&clks 45>;
				status = "disabled";
			};
...

 

 

'Device Driver' 카테고리의 다른 글

Virtual Memory  (0) 2023.07.16
디바이스 드라이버에서 변수와 메모리 할당  (0) 2023.07.09
디바이스 드라이버 구현  (0) 2023.07.01
디바이스 드라이버 개요  (0) 2023.07.01