[Linux] M1/S 커널 드라이버 gpiomem

11 minute read

드라이버 코드 리뷰를 한번도 안해본 것 같아서 시작해 보려고 합니다.
타겟은 작년 말 하드커널에서 출시한 M1S 커널 중에서,
간단한 gpiomem을 해보려고 합니다.

개요

gpiomem 드라이버는 메모리 중에서 gpio영역만 따로 구분해 놓은 디바이스 드라이버 입니다.
하드웨어를 userspace에서 제어하게 되면 보통 root 권한을 가지고 컨트롤합니다.

그런데, /dev/mem 노드를 통해 gpio를 제어할 수 있을까요?
물론 가능은 합니다. 하지만 실수로 gpio가 아닌 다른 주소에 접근해서 레지스터값을 변경하게 되면..
root 권한으로 write 작업한 것은 바로 적용이 됩니다.
큰 문제가 없을 수도 있지만, 시스템에 큰 영향을 줄 수도 있습니다.

그런 상황이 나타나지 않기 위해 노드 자체에 gpio 주소만 열어두는 겁니다.
대표적으로 wiringPi/dev/gpiomem을,
gpiod/dev/gpiochip[n] 노드를 사용하고 있습니다.

외에 다른 케이스들도 있겠지만, 우선 gpiomem 드라이버 부터 알아보겠습니다.

코드 리뷰

코드 리뷰는 커널 버전 5.10.y 입니다.
리뷰를 보기 전에 디바이스 드라이버에 관한 문서를 읽어보는것도 좋습니다.
필수적으로 포함되어야 하는 것들이 정리되어 있습니다.

gpiomem.c

소스코드는 rk3568-gpiomem.c 입니다.

헤더파일, 전처리기 입니다.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/pagemap.h>
#include <linux/io.h>

#define DEVICE_NAME "rk3568-gpiomem"
#define DRIVER_NAME "gpiomem-rk3568"
#define DEVICE_MINOR 0

<linux/kernel.h> : 커널 API 지원
<linux/module.h> : 커널 모듈 관련 API/MACRO 지원
<linux/of.h> : 커널 매치테이블 지원
<linux/platform_device.h> : 커널 platform device 지원
<linux/mm.h> : 커널 메모리 type 지원
<linux/slab.h> : 커널 kzalloc, kfree 등 메모리 작업 지원
<linux/cdev.h> : 커널 캐릭터 디바이스 관련 API 지원
<linux/pagemap.h> : 커널 페이지 관련 지원
<linux/io.h> : 커널 I/O 관련 API 지원

static 전역 변수/구조체 선언입니다.

struct rk3568_gpiomem_instance {
	unsigned long gpio_regs_phys;
	struct device *dev;
};

static struct cdev rk3568_gpiomem_cdev;
static dev_t rk3568_gpiomem_devid;
static struct class *rk3568_gpiomem_class;
static struct device *rk3568_gpiomem_dev;
static struct rk3568_gpiomem_instance *inst;

struct rk3568_gpiomem_instance
드라이버의 proberemove 를 위해 사용됩니다.
레지스터의 물리주소와 하드웨어 상호작용을 위한 device 구조체를 포함하고 있습니다.
static struct rk3568_gpiomem_instance *inst 으로 활용되고 있습니다.

static struct cdev rk3568_gpiomem_cdev
char device 노드를 나타냅니다.

static struct dev_t rk3568_gpiomem_devid
device의 major minor 번호를 지정하기 위한 것입니다.

static struct class *rk3568_gpiomem_class
sysfs 아래에서 device 노드를 생성합니다.
그 디바이스 노드를 만들기 위한 static struct device *rk3568_gpiomem_dev 가 있습니다.

먼저 드라이버의 file_operations 을 구현해야 합니다.
왜냐하면 UNIX 시스템에서 하드웨어는 대부분 파일로 존재하기 때문입니다.

static int rk3568_gpiomem_open(struct inode *inode, struct file *file)
{
	int dev = iminor(inode);
	int ret = 0;

	dev_info(inst->dev, "gpiomem device opened.");

	if (dev != DEVICE_MINOR) {
		dev_err(inst->dev, "Unknown minor device: %d", dev);
		ret = -ENXIO;
	}
	return ret;
}

static int rk3568_gpiomem_release(struct inode *inode, struct file *file)
{
	int dev = iminor(inode);
	int ret = 0;

	if (dev != DEVICE_MINOR) {
		dev_err(inst->dev, "Unknown minor device %d", dev);
		ret = -ENXIO;
	}
	return ret;
}

static const struct vm_operations_struct rk3568_gpiomem_vm_ops = {
#ifdef CONFIG_HAVE_IOREMAP_PROT
	.access = generic_access_phys
#endif
};

static int address_is_allowed(unsigned long pfn, unsigned long size)
{
	unsigned long address = pfn << PAGE_SHIFT;
	
	dev_info(inst->dev, "address_is_allowed.pfn: 0x%08lx", address);
	
	switch(address) {
		case 0xfdd00000:
		case 0xfdd20000:
		case 0xfdc20000:
		case 0xfdd60000:
		case 0xfdd70000:
		case 0xfe6f0000:
		case 0xfe740000:
		case 0xfe750000:
		case 0xfe760000:
		case 0xfe770000:
		case 0xfdc60000:
			dev_info(inst->dev, "address_is_allowed.return 1");
			return 1;
			break;
		default :
			dev_info(inst->dev, "address_is_allowed.return 0");
				return 0;
    }
}

static int rk3568_gpiomem_mmap(struct file *file, struct vm_area_struct *vma)
{

	size_t size;

	size = vma->vm_end - vma->vm_start;


	if (!address_is_allowed(vma->vm_pgoff, size))
		return -EPERM;

	vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
						 size,
						 vma->vm_page_prot);

	vma->vm_ops =  &rk3568_gpiomem_vm_ops;

	/* Remap-pfn-range will mark the range VM_IO */
	if (remap_pfn_range(vma,
			    vma->vm_start,
			    vma->vm_pgoff,
			    size,
			    vma->vm_page_prot)) {
		return -EAGAIN;
	}

	return 0;
}

static const struct file_operations
rk3568_gpiomem_fops = {
	.owner = THIS_MODULE,
	.open = rk3568_gpiomem_open,
	.release = rk3568_gpiomem_release,
	.mmap = rk3568_gpiomem_mmap,
};

file_operations 은 디바이스 드라이버 초기화할때 필요합니다.
드라이버 코드는 file_operations의 owner, open, release, mmap 속성을 정의했습니다.

file_operations 를 따라가 보겠습니다.

먼저 owner 에서 THIS_MODULE 은 커널에서 제공하는 모듈 매크로 입니다.
특별한 경우가 아니면 보통 기본값으로 사용됩니다.


open 입니다.

static int rk3568_gpiomem_open(struct inode *inode, struct file *file)
{
	int dev = iminor(inode);
	int ret = 0;

	dev_info(inst->dev, "gpiomem device opened.");

	if (dev != DEVICE_MINOR) {
		dev_err(inst->dev, "Unknown minor device: %d", dev);
		ret = -ENXIO;
	}
	return ret;
}

iminor 함수는 디바이스 노드의 minor 번호를 가져오는 함수입니다.
드라이버의 마이너 번호와 다르면 dev_err를 호출합니다.
에러 로그( ENXIO ) 는 “No such device or address” 입니다.


release 입니다.

static int rk3568_gpiomem_release(struct inode *inode, struct file *file)
{
	int dev = iminor(inode);
	int ret = 0;

	if (dev != DEVICE_MINOR) {
		dev_err(inst->dev, "Unknown minor device %d", dev);
		ret = -ENXIO;
	}
	return ret;
}

open 함수와 별 차이는 없습니다.
둘 다 어떤 하드웨어 동작을 하지는 않습니다. 메세지 출력(즉, open/release 시점을 알려주는 것)이 목적입니다.


mmap 입니다.

static int rk3568_gpiomem_mmap(struct file *file, struct vm_area_struct *vma)
{

	size_t size;

	size = vma->vm_end - vma->vm_start;


	if (!address_is_allowed(vma->vm_pgoff, size))
		return -EPERM;

	vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
						 size,
						 vma->vm_page_prot);

	vma->vm_ops =  &rk3568_gpiomem_vm_ops;

	/* Remap-pfn-range will mark the range VM_IO */
	if (remap_pfn_range(vma,
			    vma->vm_start,
			    vma->vm_pgoff,
			    size,
			    vma->vm_page_prot)) {
		return -EAGAIN;
	}

	return 0;
}

함수의 매개변수 중에 vm_area_struct 이 있습니다.
가상메모리 구조체인데, 먼저 mmap이 무엇인지 이해해야 합니다.

mmap은 memory mapping을 뜻하는 말입니다.
커널은 메모리 매핑을 가상 주소를 따로 만들어서 매핑하는데,
이 가상주소 영역을 vm_area_struct 를 사용해서 정의합니다.
가상메모리와 페이징 기법을 알고 있어야 이해할 수 있습니다.
주제가 메모리가 아니기 때문에 코드 리뷰로 넘어가겠습니다.

vma의 사이즈는 vma->vm_end - vma->vm_start 입니다.
각각 vma의 끝주소와 시작주소를 나타냅니다.
당연히 끝주소값에서 시작 주소값을 빼면 그 차이만큼의 크기가 나옵니다.

그리고 중간에 사용자 정의 함수가 하나 있습니다.

	if (!address_is_allowed(vma->vm_pgoff, size))
		return -EPERM;

gpiomem은 gpio영역만 따로 구분한다고 했습니다.
영역을 확인하는 작업입니다.
vma->mv_pgoff 값이 gpio 영역 안에 있는지 확인합니다.
vma->mv_pgoff 은 vma의 page offset을 나타냅니다.
실제 파일의 시작 위치입니다.
가상메모리 영역에는 여러개의 페이지가 존재하고 페이지 단위는 보통 4K ~ 8K 입니다.
페이징을 알고 있다면 실제 파일의 시작은 가상메모리 시작주소가 아닌것 쯤은 알고 있을 것입니다.
이것을 page frame number 라고 하고, 줄여서 pfn 이라고도 합니다.


이 부분은 gpiomem을 사용할 때, file discriptor 가 가리키는 주소가 gpio영역이 맞는지 검사하는 부분입니다.

static int address_is_allowed(unsigned long pfn, unsigned long size)
{
	unsigned long address = pfn << PAGE_SHIFT;
	
	dev_info(inst->dev, "address_is_allowed.pfn: 0x%08lx", address);
	
	switch(address) {
		case 0xfdd00000:
		case 0xfdd20000:
		...
		case 0xfdc20000:
				case 0xfdc60000:
			dev_info(inst->dev, "address_is_allowed.return 1");
			return 1;
			break;
		default :
			dev_info(inst->dev, "address_is_allowed.return 0");
				return 0;
    }
}

실제로 pfn을 매개변수로 받고 있습니다.
address 는 물리주소 입니다.
보통 pfn « PAGE_SHIFT 연산을 하면 물리주소로 변환되고
반대로 phy » PAGE_SHIFT 연산을 하면 pfn 값이 나옵니다.
PAGE_SHIFT 는 보통 12~16의 값을 가지게 됩니다.
아키텍처에 따라 값이 상이합니다.

이렇게 변환된 주소값으로 gpio영역의 주소와 비교합니다.
switch-case 문을 사용해서 mmap을 진행할지 종료할지 리턴값에 따라 결정됩니다.
gpio 영역은 chip의 datasheet을 확인하면 알 수 있습니다.

gpiomem-datasheet


실제 함수에서는 몇가지 주소가 더 있지만, 이 포스팅주제와 맞지 않아서 넘어갑니다.
아무튼 관련주소가 모두 포함되어 있는것을 확인할 수 있습니다.


다시 mmap 코드로 돌아오겠습니다.

static int rk3568_gpiomem_mmap(struct file *file, struct vm_area_struct *vma)
{

	size_t size;

	size = vma->vm_end - vma->vm_start;


	if (!address_is_allowed(vma->vm_pgoff, size))
		return -EPERM;

	vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
						 size,
						 vma->vm_page_prot);

	vma->vm_ops =  &rk3568_gpiomem_vm_ops;

	/* Remap-pfn-range will mark the range VM_IO */
	if (remap_pfn_range(vma,
			    vma->vm_start,
			    vma->vm_pgoff,
			    size,
			    vma->vm_page_prot)) {
		return -EAGAIN;
	}

	return 0;
}

vma->vm_page_prot 은 가상메모리 영역의 권한(플래그)를 의미합니다.

prot 은 “protection”의 줄임말 입니다.
위에서 pgoff도 그렇고, 커널에서 사용되는 변수는 무엇의 줄임말인지 알고 있어야 보기 쉬워지는 것 같습니다.
아무튼 phys_mem_access_prot 함수를 통해 메모리 접근 권한을 받습니다.

가상메모리 vma 역시 file 처럼 operations가 정의 되어야 합니다
vma->vm_ops = &rk3568_gpiomem_vm_ops; 구문으로 정의 되었습니다.


해당 ops은 코드 내에 정의 되어 있습니다.

static const struct vm_operations_struct rk3568_gpiomem_vm_ops = {
#ifdef CONFIG_HAVE_IOREMAP_PROT
	.access = generic_access_phys
#endif
};


마지막으로 가상 메모리 영역에 해당 파일을 매핑 해줘야 합니다.
계속 언급하지만, 가상메모리 구조는 따로 설명하지 않습니다.

	if (remap_pfn_range(vma,
			    vma->vm_start,
			    vma->vm_pgoff,
			    size,
			    vma->vm_page_prot)) {
		return -EAGAIN;
	}

mmap 함수에서 가장 핵심적인 부분입니다.
remap_pfn_range 함수는 메모리를 매핑하는 함수입니다.
가상 주소를 위한 페이지 테이블을 만듭니다.
결국 vma->pgoff (가상주소) 와 실제 하드웨어 물리주소 간의 매핑되게 한다.
매핑을 vma 영역(vm_start ~ size) 안에서 한다.
정도로 이해하면 됩니다.
에러로그( EAGAIN ) 는 “No more processes” 입니다.


file_operations 모두 정의했으면 이제 device driver 를 정의해야 합니다.
driver 정의 부분입니다.

static int rk3568_gpiomem_probe(struct platform_device *pdev)
{
	int err;
	void *ptr_err;
	struct device *dev = &pdev->dev;
	struct resource *ioresource;

	/* Allocate buffers and instance data */

	inst = kzalloc(sizeof(struct rk3568_gpiomem_instance), GFP_KERNEL);

	if (!inst) {
		err = -ENOMEM;
		goto failed_inst_alloc;
	}

	inst->dev = dev;

	ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (ioresource) {
		inst->gpio_regs_phys = ioresource->start;
	} else {
		dev_err(inst->dev, "failed to get IO resource");
		err = -ENOENT;
		goto failed_get_resource;
	}

	/* Create character device entries */

	err = alloc_chrdev_region(&rk3568_gpiomem_devid,
				  DEVICE_MINOR, 1, DEVICE_NAME);
	if (err != 0) {
		dev_err(inst->dev, "unable to allocate device number");
		goto failed_alloc_chrdev;
	}
	cdev_init(&rk3568_gpiomem_cdev, &rk3568_gpiomem_fops);
	rk3568_gpiomem_cdev.owner = THIS_MODULE;
	err = cdev_add(&rk3568_gpiomem_cdev, rk3568_gpiomem_devid, 1);
	if (err != 0) {
		dev_err(inst->dev, "unable to register device");
		goto failed_cdev_add;
	}

	/* Create sysfs entries */

	rk3568_gpiomem_class = class_create(THIS_MODULE, DEVICE_NAME);
	ptr_err = rk3568_gpiomem_class;
	if (IS_ERR(ptr_err))
		goto failed_class_create;

	rk3568_gpiomem_dev = device_create(rk3568_gpiomem_class, NULL,
					rk3568_gpiomem_devid, NULL,
					"gpiomem");
	ptr_err = rk3568_gpiomem_dev;
	if (IS_ERR(ptr_err))
		goto failed_device_create;

	dev_info(inst->dev, "Initialised: Registers at 0x%08lx",
		inst->gpio_regs_phys);

	return 0;

failed_device_create:
	class_destroy(rk3568_gpiomem_class);
failed_class_create:
	cdev_del(&rk3568_gpiomem_cdev);
	err = PTR_ERR(ptr_err);
failed_cdev_add:
	unregister_chrdev_region(rk3568_gpiomem_devid, 1);
failed_alloc_chrdev:
failed_get_resource:
	kfree(inst);
failed_inst_alloc:
	dev_err(inst->dev, "could not load rk3568_gpiomem");
	return err;
}

static int rk3568_gpiomem_remove(struct platform_device *pdev)
{
	struct device *dev = inst->dev;

	kfree(inst);
	device_destroy(rk3568_gpiomem_class, rk3568_gpiomem_devid);
	class_destroy(rk3568_gpiomem_class);
	cdev_del(&rk3568_gpiomem_cdev);
	unregister_chrdev_region(rk3568_gpiomem_devid, 1);

	dev_info(dev, "GPIO mem driver removed - OK");
	return 0;
}

static const struct of_device_id rk3568_gpiomem_of_match[] = {
	{.compatible = "rockchip,rk3568-gpiomem",},
	{ /* sentinel */ },
};

MODULE_DEVICE_TABLE(of, rk3568_gpiomem_of_match);

static struct platform_driver rk3568_gpiomem_driver = {
	.probe = rk3568_gpiomem_probe,
	.remove = rk3568_gpiomem_remove,
	.driver = {
		   .name = DRIVER_NAME,
		   .owner = THIS_MODULE,
		   .of_match_table = rk3568_gpiomem_of_match,
		   },
};

module_platform_driver(rk3568_gpiomem_driver);

MODULE_ALIAS("platform:gpiomem-rk3568");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("gpiomem driver for accessing GPIO from userspace");
MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>");


file_operations 처럼 핵심부분이 어떤 것인지 살펴보고 하나씩 순차적으로 보겠습니다.

static struct platform_driver rk3568_gpiomem_driver = {
	.probe = rk3568_gpiomem_probe,
	.remove = rk3568_gpiomem_remove,
	.driver = {
		   .name = DRIVER_NAME,
		   .owner = THIS_MODULE,
		   .of_match_table = rk3568_gpiomem_of_match,
		   },
};

드라이버 등록/해제하는 probe, remove 그리고 driver 구성요소인 name, owner, of_match_table 이 있습니다.

dirver의 name과 owner는 스트링/매크로를 사용했습니다.


probe 부분입니다.

static int rk3568_gpiomem_probe(struct platform_device *pdev)
{
	int err;
	void *ptr_err;
	struct device *dev = &pdev->dev;
	struct resource *ioresource;

	/* Allocate buffers and instance data */

	inst = kzalloc(sizeof(struct rk3568_gpiomem_instance), GFP_KERNEL);

	if (!inst) {
		err = -ENOMEM;
		goto failed_inst_alloc;
	}

	inst->dev = dev;

	ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (ioresource) {
		inst->gpio_regs_phys = ioresource->start;
	} else {
		dev_err(inst->dev, "failed to get IO resource");
		err = -ENOENT;
		goto failed_get_resource;
	}

	/* Create character device entries */

	err = alloc_chrdev_region(&rk3568_gpiomem_devid,
				  DEVICE_MINOR, 1, DEVICE_NAME);
	if (err != 0) {
		dev_err(inst->dev, "unable to allocate device number");
		goto failed_alloc_chrdev;
	}
	cdev_init(&rk3568_gpiomem_cdev, &rk3568_gpiomem_fops);
	rk3568_gpiomem_cdev.owner = THIS_MODULE;
	err = cdev_add(&rk3568_gpiomem_cdev, rk3568_gpiomem_devid, 1);
	if (err != 0) {
		dev_err(inst->dev, "unable to register device");
		goto failed_cdev_add;
	}

	/* Create sysfs entries */

	rk3568_gpiomem_class = class_create(THIS_MODULE, DEVICE_NAME);
	ptr_err = rk3568_gpiomem_class;
	if (IS_ERR(ptr_err))
		goto failed_class_create;

	rk3568_gpiomem_dev = device_create(rk3568_gpiomem_class, NULL,
					rk3568_gpiomem_devid, NULL,
					"gpiomem");
	ptr_err = rk3568_gpiomem_dev;
	if (IS_ERR(ptr_err))
		goto failed_device_create;

	dev_info(inst->dev, "Initialised: Registers at 0x%08lx",
		inst->gpio_regs_phys);

	return 0;

failed_device_create:
	class_destroy(rk3568_gpiomem_class);
failed_class_create:
	cdev_del(&rk3568_gpiomem_cdev);
	err = PTR_ERR(ptr_err);
failed_cdev_add:
	unregister_chrdev_region(rk3568_gpiomem_devid, 1);
failed_alloc_chrdev:
failed_get_resource:
	kfree(inst);
failed_inst_alloc:
	dev_err(inst->dev, "could not load rk3568_gpiomem");
	return err;
}

먼저, inst = kzalloc(sizeof(struct rk3568_gpiomem_instance), GFP_KERNEL); 부분 입니다.
kzalloc 함수는 malloc 함수와 비슷합니다.
동적 메모리 할당의 커널 버전이라고 보면 되는데, kzalloc 은 메모리 값을 0으로 초기화까지 합니다.
GFP_KERNEL 은 할당할 수 있는 메모리가 있을때만 할당하겠다는 플래그 입니다.

inst->dev = dev; 구문으로 코드 제일 처음부분에서 정의했던 instance 의 dev를 pdev의 dev와 연결합니다.


하드웨어의 리소스를 instance로 넘겨받는 부분입니다.

	ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (ioresource) {
		inst->gpio_regs_phys = ioresource->start;
	} else {
		dev_err(inst->dev, "failed to get IO resource");
		err = -ENOENT;
		goto failed_get_resource;
	}

레지스터 물리주소를 리소스 시작주소로 매핑합니다.
에러로그( ENOENT ) 는 “No such file or directory” 입니다.


다음은 디바이스 드라이버의 초기화 부분입니다.
char device driver 이기 때문에 초기화는 cdev_init 을 사용합니다.
alloc_chrdev_region 함수를 통해 major/minor 번호도 할당합니다.

	/* Create character device entries */

	err = alloc_chrdev_region(&rk3568_gpiomem_devid,
				  DEVICE_MINOR, 1, DEVICE_NAME);
	if (err != 0) {
		dev_err(inst->dev, "unable to allocate device number");
		goto failed_alloc_chrdev;
	}
	cdev_init(&rk3568_gpiomem_cdev, &rk3568_gpiomem_fops);
	rk3568_gpiomem_cdev.owner = THIS_MODULE;
	err = cdev_add(&rk3568_gpiomem_cdev, rk3568_gpiomem_devid, 1);
	if (err != 0) {
		dev_err(inst->dev, "unable to register device");
		goto failed_cdev_add;
	}

cdev_init 함수는 타겟이 되는 cdev와 타겟의 ops를 매개변수로 받습니다.
앞에서 정의했던 file_operations 를 사용합니다.
goto 은 에러 핸들링 부분입니다.
따로 다루지는 않겠습니다.


클래스 디바이스 부분입니다.
/sys/class/ 아래에 device를 연결합니다.
위에서 할당한 major/minor 번호가 매개가 됩니다.

	/* Create sysfs entries */

	rk3568_gpiomem_class = class_create(THIS_MODULE, DEVICE_NAME);
	ptr_err = rk3568_gpiomem_class;
	if (IS_ERR(ptr_err))
		goto failed_class_create;

	rk3568_gpiomem_dev = device_create(rk3568_gpiomem_class, NULL,
					rk3568_gpiomem_devid, NULL,
					"gpiomem");
	ptr_err = rk3568_gpiomem_dev;
	if (IS_ERR(ptr_err))
		goto failed_device_create;

	dev_info(inst->dev, "Initialised: Registers at 0x%08lx",
		inst->gpio_regs_phys);

	return 0;

uevent 노드를 통해 디바이스 이름과 MAJOR, MINOR 번호를 알 수 있습니다.
goto 은 에러 핸들링 부분입니다.
역시 따로 다루지는 않겠습니다.


remove 부분입니다.

static int rk3568_gpiomem_remove(struct platform_device *pdev)
{
	struct device *dev = inst->dev;

	kfree(inst);
	device_destroy(rk3568_gpiomem_class, rk3568_gpiomem_devid);
	class_destroy(rk3568_gpiomem_class);
	cdev_del(&rk3568_gpiomem_cdev);
	unregister_chrdev_region(rk3568_gpiomem_devid, 1);

	dev_info(dev, "GPIO mem driver removed - OK");
	return 0;
}

만들었던 메모리, 디바이스, 클래스, 캐릭터 디바이스를 제거하고
디바이스 major, minor 넘버를 지웁니다.

이제 거의 끝났습니다.


다시 돌아와서 driver 등록하는 부분입니다.

static struct platform_driver rk3568_gpiomem_driver = {
	.probe = rk3568_gpiomem_probe,
	.remove = rk3568_gpiomem_remove,
	.driver = {
		   .name = DRIVER_NAME,
		   .owner = THIS_MODULE,
		   .of_match_table = rk3568_gpiomem_of_match,
		   },
};

module_platform_driver(rk3568_gpiomem_driver);

남은 부분은 of_match_table 입니다.
매칭 테이블은 device-tree 에서 디바이스를 관리할 수 있도록
드라이버에서 제공하는 테이블 입니다.
트리의 속성은 compatible 을 사용합니다.
트리의 compatible 값과 드라이버 매칭테이블의 compatible 값을 비교해서 드라이버를 load 합니다.
값은 string 입니다.

왜 이렇게 관리가 되냐하면,
이것도 주제를 살짝 벗어난 이야기인데 원래 커널 2.*.y 버전에서는 드라이버가 추가 될 때마다
register 함수를 호출했습니다.
그런데 점점 device의 수와 종류가 늘어감에 따라 관리가 힘들어 지게 됩니다.
그래서 리눅스 커널 major 버전이 3으로 올라갈 때, device-tree 이 도입 되었습니다.
트리의 compatible 값으로 디바이스 드라이버를 관리하게 되면서 매칭 테이블 개념도 생겨났습니다.


아무튼 이렇게 매칭 테이블을 정의하고, 매크로를 통해 매칭테이블을 등록합니다.

static const struct of_device_id rk3568_gpiomem_of_match[] = {
	{.compatible = "rockchip,rk3568-gpiomem",},
	{ /* sentinel */ },
};

MODULE_DEVICE_TABLE(of, rk3568_gpiomem_of_match);

MODULE_DEVICE_TABLE 매크로로 매핑 테이블을 등록하고
등록을 위해서 <linux/of.h> 가 필요합니다.


이렇게 코드가 완성되었습니다.
코드 마지막 부분은 드라이버 모듈 이름, 라이센스, 설명, 저자를 남겨주면 됩니다.

MODULE_ALIAS("platform:gpiomem-rk3568");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("gpiomem driver for accessing GPIO from userspace");
MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>");

이상입니다.

Leave a comment