<kernel v4.14>
플랫폼 디바이스
리눅스 커널에서 기능 별로 분류한 디바이스와 전혀 다르게 플랫폼 디바이스라고 불리는 디바이스가 있다. 플랫폼은 anonymous(virtual) 버스와 비슷한 개념이다. 이러한 플랫폼에 연결되는 디바이스가 플랫폼 디바이스인데 일반 디바이스와 다른 것이 무엇인지 알아보자.
플랫폼 버스와 연결된 플랫폼 디바이스와 해당 드라이버들이 있다. 플랫폼 디바이스가 일반 디바이스와 다르게 불리는 이유를 알아보자.
- 플랫폼 디바이스는 SoC 내부에 임베드된 디바이스 또는 버스 디바이스로 cpu로 부터 직접 주소 지정이 가능한 특성을 가진다.
- 플랫폼 디바이스의 레지스터를 가상 주소 공간에 매핑하여 직접 조작할 수 있다.
- 플랫폼 디바이스는 디바이스의 시작(probe) 시점이 정해지지 않은 디바이스이다.
- 자동 발견되지 않으므로 누군가 지정해줘야 하는 디바이스가 플랫폼 디바이스이다.
- 누가 플랫폼 디바이스를 등록시킬까?
- 디바이스 트리를 사용하기 전에는 보드 특정(board, machine 또는 architect specific) 셋업 코드에서 플랫폼 디바이스를 등록시켰다.
- 디바이스 트리를 사용하는 최신 리눅스 커널은 대부분 디바이스 트리를 통해서 등록된다.
- 플러그 가능한 PCI 및 USB 버스에 연결되는 디바이스는 플랫폼 디바이스가 아니다.
- 참고로 pci 디바이스의 인식 및 가동 정보는 pci 자체적으로 파악할 수 있으므로 반드시 디바이스 트리를 사용할 필요가 없다.
- 플랫폼 디바이스의 등록은 커널 부팅 시 매우 빠른(early) 시간에 해야한다.
- 플랫폼 드라이버의 등록은 In-kernel 드라이버인 경우 커널 부팅 후에 하고 모듈 방식인 경우 모듈 로딩 시에 수행된다.
다음 그림과 같이 4개의 플랫폼 디바이스의 위치를 확인할 수 있다.
- 다음과 같이 가정할 때 두 개의 i2c bus controller 중 A는 플랫폼 디바이스로, 그리고 C는 pci 디바이스로 등록해야한다.
- bus controller A가 ic2 bus controller
- bus controller B가 pci bus controller
- bus controller C가 i2c bus controller
platform_device 구조체
플랫폼 디바이스를 등록하기 위해 아래의 platform_device 구조체를 등록한다. 디바이스 구조체를 임베드한 platform_device 구조체를 알아보자.
include/linux/platform_device.h
struct platform_device { const char *name; int id; bool id_auto; struct device dev; u32 num_resources; struct resource *resource; const struct platform_device_id *id_entry; char *driver_override; /* Driver name to force a match */ /* MFD cell pointer */ struct mfd_cell *mfd_cell; /* arch specific additions */ struct pdev_archdata archdata; };
플랫폼 디바이스를 정의하기 위해 기본적으로 이름과 id 및 리소스들과 리소스의 수가 주어질 수 있다.
- *name
- 플랫폼 디바이스 이름
- id
- 전체 플랫폼이 아닌 해당 플랫폼 디바이스에서 유니크한 값
- -1인 경우 하나밖에 없는 경우이다.
- 두 개 이상의 디바이스 인스턴스가 주어진 경우 0부터 시작한다.
- 예) serial/0 또는 serial.0
- id_auto
- id 발급을 자동으로할지 여부
- dev
- 디바이스 구조체가 임베드된다.
- num_resources
- 포함된 리소스의 수
- *resource
- 플랫폼 디바이스 고유의 리소스 데이터로 irq 또는 시작 주소와 사이즈를 갖는 리소스 값들
플랫폼 디바이스 등록
디바이스 드라이버 모듈을 따른 플랫폼 디바이스의 등록은 이전 32bit arm에서 많은 수의 보드 특정(board, machine 또는 architect specific) 셋업 코드에 아래의 플랫폼 디바이스 등록 API 코드들을 사용해왔다. 현재 디바이스 트리를 사용하는 arm64 커널은 arch-specific 셋업 코드를 사용하지 않고 디바이스 트리가 제공하는 정보를 파싱하여 각 성격에 맞는 디바이스로 등록된다. 대부분의 플랫폼 디바이스들은 일부 특수 노드를 제외하고 루트 노드의 자식 노드들이다. 그리고 플랫폼 디바이스 노드를 확장할 수 있는데, compatible=”simple-bus” 속성이 추가된 노드들(예: soc)에 포함된 자식 디렉토리들도 플랫폼 디바이스로 등록하여 사용한다.
- int platform_device_register(struct platform_device *pdev);
- 플랫폼 디바이스를 등록한다.
- int platform_add_devices(struct platform_device **pdevs, int ndev);
- 다수의 플랫폼 디바이스를 등록한다.
Legacy 플랫폼 디바이스 등록
디바이스 드라이버 모듈을 따르지 않는 기존 플랫폼 디바이스는 모듈(핫플러그)로 등록될 수 없다. Legacy 드라이버에서 다음 API의 사용을 피하고 두 번째 API를 사용하는 것을 권장한다.
-
- 직접 사용을 피해야 할 API
- platform_device_alloc()
- 직접 사용을 피해야 할 API
- 권장 API
- platform_device_register_simple()
다음과 같이 시스템에 등록된 플랫폼 디바이스들을 알아보려면 “/sys/devices/platform” 디렉토리를 확인한다.
$ ls /sys/devices/platform -la drwxr-xr-x 3 root root 0 May 29 13:07 0.flash drwxr-xr-x 3 root root 0 May 29 13:07 0.gpio drwxr-xr-x 4 root root 0 May 29 13:07 3f000000.pcie drwxr-xr-x 4 root root 0 May 29 13:07 9000000.pl011 drwxr-xr-x 4 root root 0 May 29 13:07 9010000.pl031 drwxr-xr-x 3 root root 0 May 29 13:07 9020000.fw-cfg drwxr-xr-x 4 root root 0 May 29 13:07 Fixed MDIO bus.0 drwxr-xr-x 3 root root 0 May 29 13:07 a000000.virtio_mmio drwxr-xr-x 3 root root 0 May 29 13:07 a000200.virtio_mmio ... drwxr-xr-x 4 root root 0 May 29 13:07 a003e00.virtio_mmio drwxr-xr-x 3 root root 0 May 29 13:07 alarmtimer drwxr-xr-x 3 root root 0 May 29 13:07 gpio-keys drwxr-xr-x 3 root root 0 May 29 13:07 platform@c000000 drwxr-xr-x 3 root root 0 May 29 13:07 pmu drwxr-xr-x 2 root root 0 May 29 13:16 power drwxr-xr-x 3 root root 0 May 29 13:07 psci drwxr-xr-x 4 root root 0 May 29 13:07 reg-dummy drwxr-xr-x 4 root root 0 May 29 13:07 serial8250 drwxr-xr-x 3 root root 0 May 29 13:07 snd-soc-dummy drwxr-xr-x 3 root root 0 May 29 13:07 timer
플랫폼 리소스 정보
플랫폼 디바이스를 등록할 때 저장할 플랫폼 리소스 데이터가 주어질 수 있다. 추후 플랫폼 드라이버가 probe되어 호출될 때 이 정보를 사용하여 HW 설정을 한다. 플랫폼 디바이스를 위해 저장될 수 있는 리소스 정보는 다음과 같다.
- io
- io 시작 포트 및 끝 포트
- 디바이스 트리 노드의 reg 속성에서 읽는 값들
- mem
- memory 시작 주소 및 끝 주소
- irq
- irq 번호
- 디바이스 트리 노드의 interrupts 및 interrupt-names 속성에서 읽는 값들
- dma
- dma 채널 번호
- bus
- 버스 시작 주소 및 끝 주소
플랫폼 드라이버
표준 드라이버 모델은 드라이버 외부로부터 발견(discovery) 및 열거(enumeration)가 가능하므로 플랫폼 드라이버 역시 (*probe) 후크 함수와 (*remove) 후크 함수등이 구현되어야 한다. 추가로 디바이스에 절전 기능이 있는 경우 해당 후크업 함수들도 구현되어야 한다.
platfrom_driver 구조체
include/linux/platform_device.h
struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; const struct platform_device_id *id_table; bool prevent_deferred_probe; };
- (*probe)
- HW 디바이스 존재 유무를 판단하기 위해 호출되고 해당 디바이스를 사용하기 위해 디바이스 드라이버와 바인드한다.
- 디바이스 리소스를 사용하여 irq, 레지스터 매핑등을 할 수 있다.
- (*remove)
- 디바이스의 사용을 완료시킨 경우 호출된다.
- (*shutdown)
- 디바이스의 전원을 끄려고할 때 호출된다.
- (*suspend)
- 디바이스가 절전 모드로 진입할 때 호출된다.
- (*resume)
- 디바이스가 절전 모드로부터 정상 모드로 돌아올 때 호출된다.
- driver
- 디바이스 드라이버 구조체가 임베드된다.
- *id_table
- 식별용 디바이스 id
- prevent_deferred_probe
- probe 유예 금지
플랫폼 드라이버의 등록
플랫폼 드라이버를 등록하는 방법은 다음과 같이 몇 가지가 있다.
- int platform_driver_register(struct platform_driver *drv);
- 일반적으로 사용하는 API이다.
- int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *))
- 모듈(핫플러그) 형태의 디바이스 드라이버가 아닌 경우 이 API를 사용하면 메모리 foot-print를 줄일 수 있다.
- 이 함수 코드가 .init 섹션에 등록시키므로 커널이 부트업한 후 .init 섹션을 모두 할당해제하여 메모리 사용량을 줄인다.
- #define platform_register_drivers(drivers, count)
- int __platform_register_drivers(struct platform_driver * const *drivers, unsigned int count, struct module *owner);
- 복수의 디바이스 드라이버들을 등록하는 API이다. 만일 하나라도 디바이스 드라이버의 등록이 실패하면 한꺼번에 해제시킨다.
Early 플랫폼 디바이스 및 드라이버
예를 들어 “earlyprintk”를 지원하기 위해 early 플랫폼 디바이스로 early 시리얼 콘솔을 사용할 수 있도록 커널은 6 단계의 스텝을 수행하였다.
- eraly 플랫폼 디바이스 데이터 등록
- 커널 커멘드 라인 파싱
- 해당 클래스로의 early 플랫픔 드라이버 설치
- early 플랫폼 드라이버 등록
- 해당 클래스에서 early 플랫폼 드라이버의 probing
- early 플랫폼 드라이버 probe 내부
플랫폼 디바이스 등록
다음 그림은 플랫폼 디바이스를 등록하는 과정을 보여준다.
- 커널 부트업 시 몇 개의 등록된 initcall 함수를 통해 플랫폼 디바이스가 등록된다.
- of_platform_default_populate_init() 함수를 통해 디바이스 트리를 통해서 등록되는 플랫폼 디바이스가 더 많다.
다음 그림은 플랫폼 디바이스와 플랫폼 드라이버의 등록 및 바인딩하는 모습을 보여준다.
platform_device_register()
drivers/base/platform.c
/** * platform_device_register - add a platform-level device * @pdev: platform device we're adding */ int platform_device_register(struct platform_device *pdev) { device_initialize(&pdev->dev); arch_setup_pdev_archdata(pdev); return platform_device_add(pdev); } EXPORT_SYMBOL_GPL(platform_device_register)
인자로 주어진 플랫폼 디바이스를 등록한다.
- 코드 라인 7에서 플랫폼 디바이스 내부의 디바이스 구조체의 멤버들을 초기화한다.
- 코드 라인 8에서 플랫폼 디바이스의 archdata 조작이 필요한 경우 호출하는 함수이다.
- 현재는 ppc 아키텍처에서만 사용하고 있다.
- 코드 라인 9에서 플랫폼 디바이스를 추가한다.
device_initialize()
drivers/base/core.c
/** * device_initialize - init device structure. * @dev: device. * * This prepares the device for use by other layers by initializing * its fields. * It is the first half of device_register(), if called by * that function, though it can also be called separately, so one * may use @dev's fields. In particular, get_device()/put_device() * may be used for reference counting of @dev after calling this * function. * * All fields in @dev must be initialized by the caller to 0, except * for those explicitly set to some other value. The simplest * approach is to use kzalloc() to allocate the structure containing * @dev. * * NOTE: Use put_device() to give up your reference instead of freeing * @dev directly once you have called this function. */ void device_initialize(struct device *dev) { dev->kobj.kset = devices_kset; kobject_init(&dev->kobj, &device_ktype); INIT_LIST_HEAD(&dev->dma_pools); mutex_init(&dev->mutex); lockdep_set_novalidate_class(&dev->mutex); spin_lock_init(&dev->devres_lock); INIT_LIST_HEAD(&dev->devres_head); device_pm_init(dev); set_dev_node(dev, -1); #ifdef CONFIG_GENERIC_MSI_IRQ INIT_LIST_HEAD(&dev->msi_list); #endif INIT_LIST_HEAD(&dev->links.consumers); INIT_LIST_HEAD(&dev->links.suppliers); dev->links.status = DL_DEV_NO_DRIVER; } EXPORT_SYMBOL_GPL(device_initialize);
디바이스 구조체의 멤버들을 초기화한다.
platform_device_add()
drivers/base/platform.c
/** * platform_device_add - add a platform device to device hierarchy * @pdev: platform device we're adding * * This is part 2 of platform_device_register(), though may be called * separately _iff_ pdev was allocated by platform_device_alloc(). */ int platform_device_add(struct platform_device *pdev) { int i, ret; if (!pdev) return -EINVAL; if (!pdev->dev.parent) pdev->dev.parent = &platform_bus; pdev->dev.bus = &platform_bus_type; switch (pdev->id) { default: dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); break; case PLATFORM_DEVID_NONE: dev_set_name(&pdev->dev, "%s", pdev->name); break; case PLATFORM_DEVID_AUTO: /* * Automatically allocated device ID. We mark it as such so * that we remember it must be freed, and we append a suffix * to avoid namespace collision with explicit IDs. */ ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL); if (ret < 0) goto err_out; pdev->id = ret; pdev->id_auto = true; dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id); break; }
플랫폼 디바이스를 등록한다.
- 코드 라인 15~16에서 부모 디바이스가 지정되지 않은 경우 플랫폼 버스를 지정한다.
- 코드 라인 18에서 디바이스에 플랫폼 버스타입을 지정한다.
- 코드 라인 20~40에서 디바이스 id를 포함하여 디바이스명을 지정한다.
- pdev->id가 주어진 경우
- 예) foo.1
- id가 없는 경우, pdev->id = PLATFORM_DEVID_NONE
- 예) foo
- id를 자동 생성하는 경우, pdev->id = PLATFORM_DEVID_AUTO
- 예) foo.1.auto
- pdev->id가 주어진 경우
for (i = 0; i < pdev->num_resources; i++) { struct resource *p, *r = &pdev->resource[i]; if (r->name == NULL) r->name = dev_name(&pdev->dev); p = r->parent; if (!p) { if (resource_type(r) == IORESOURCE_MEM) p = &iomem_resource; else if (resource_type(r) == IORESOURCE_IO) p = &ioport_resource; } if (p && insert_resource(p, r)) { dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r); ret = -EBUSY; goto failed; } } pr_debug("Registering platform device '%s'. Parent at %s\n", dev_name(&pdev->dev), dev_name(pdev->dev.parent)); ret = device_add(&pdev->dev); if (ret == 0) return ret; failed: if (pdev->id_auto) { ida_simple_remove(&platform_devid_ida, pdev->id); pdev->id = PLATFORM_DEVID_AUTO; } while (--i >= 0) { struct resource *r = &pdev->resource[i]; if (r->parent) release_resource(r); } err_out: return ret; } EXPORT_SYMBOL_GPL(platform_device_add);
- 코드 라인 1~5에서 플랫폼 디바이스에 지정된 리소스를 대상으로 손회하며 리소스 명이 없는 경우 플랫폼 디바이스 명을 사용한다.
- 코드 라인 7~20에서 부모 리소스의 현재 리소스 정보의 추가를 시도한다. 만일 부모 리소스가 없으면 리소스의 타입에 따라 최상위 리소스 트리인 iomem_resource 또는 ioport_resource를 사용한다.
- 코드 라인 25~27에서 디바이스를 추가한다.
디바이스 트리를 사용한 플랫폼 디바이스 등록
아래 그림은 디바이스 트리에서 플랫폼 디바이스로 등록되는 노드와 그렇지 않은 노드를 보여준다.
- 플랫폼 디바이스로 등록 가능한 노드
- 부모 노드가 루트 노드인 경우
- 부모 노드가 플랫폼 버스인 경우
다음 그림은 디바이스 트리 노드를 읽어 플랫폼 디바이스로 등록할 때 플랫폼 리소스 정보도 같이 수집하여 등록되는 과정을 보여준다.
- 아래 예와 같이 메모리 정보와 irq 정보를 플랫폼 디바이스의 리소스로 등록한다.
다음 그림은 Legacy irq 대신 MSI를 사용하는 플랫폼 디바이스를 등록하는 과정을 보여준다.
- 대부분의 MSI는 PCI 디바이스가 사용한다. 플랫폼 디바이스가 사용하는 매우 드물다.
of_platform_default_populate_init()
drivers/of/platform.c
static int __init of_platform_default_populate_init(void) { struct device_node *node; if (!of_have_populated_dt()) return -ENODEV; /* * Handle ramoops explicitly, since it is inside /reserved-memory, * which lacks a "compatible" property. */ node = of_find_node_by_path("/reserved-memory"); if (node) { node = of_find_compatible_node(node, NULL, "ramoops"); if (node) of_platform_device_create(node, NULL, NULL); } /* Populate everything else. */ of_platform_default_populate(NULL, NULL, NULL); return 0; } arch_initcall_sync(of_platform_default_populate_init);
다비이스 트리를 파싱하여 플랫폼 디바이스들을 생성하여 등록한다.
- 코드 라인 5~6에서 디바이스 트리가 없는 경우 -ENODEV 결과를 반환한다.
- 코드 라인 12~17에서 “/reserved-memory” 노드가 발견된 경우 그 노드 안에 “ramoops” 디바이스명이 있으면 “ramoops” 디바이스를 생성하여 등록한다.
- 코드 라인 20에서 디바이스 트리를 모두 파싱하여 플랫폼 디바이스를 생성하고 등록한다.
of_platform_default_populate()
drivers/of/platform.c
int of_platform_default_populate(struct device_node *root, const struct of_dev_auxdata *lookup, struct device *parent) { return of_platform_populate(root, of_default_bus_match_table, lookup, parent); } EXPORT_SYMBOL_GPL(of_platform_default_populate);
디바이스 트리를 루트부터 모두 파싱하여 아래 4개의 기본 버스 타입에 연결된 디바이스를 플랫폼 디바이스로 인식하여 생성하고 등록한다.
- “simple-bus”
- “simple-mfd”
- “isa”
- “arm,amba-bus”
of_default_bus_match_table[]
drivers/of/platform.c
const struct of_device_id of_default_bus_match_table[] = { { .compatible = "simple-bus", }, { .compatible = "simple-mfd", }, { .compatible = "isa", }, #ifdef CONFIG_ARM_AMBA { .compatible = "arm,amba-bus", }, #endif /* CONFIG_ARM_AMBA */ {} /* Empty terminated list */ };
of_platform_populate()
drivers/of/platform.c
/** * of_platform_populate() - Populate platform_devices from device tree data * @root: parent of the first level to probe or NULL for the root of the tree * @matches: match table, NULL to use the default * @lookup: auxdata table for matching id and platform_data with device nodes * @parent: parent to hook devices from, NULL for toplevel * * Similar to of_platform_bus_probe(), this function walks the device tree * and creates devices from nodes. It differs in that it follows the modern * convention of requiring all device nodes to have a 'compatible' property, * and it is suitable for creating devices which are children of the root * node (of_platform_bus_probe will only create children of the root which * are selected by the @matches argument). * * New board support should be using this function instead of * of_platform_bus_probe(). * * Returns 0 on success, < 0 on failure. */
int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent) { struct device_node *child; int rc = 0; root = root ? of_node_get(root) : of_find_node_by_path("/"); if (!root) return -EINVAL; pr_debug("%s()\n", __func__); pr_debug(" starting at: %pOF\n", root); for_each_child_of_node(root, child) { rc = of_platform_bus_create(child, matches, lookup, parent, true); if (rc) { of_node_put(child); break; } } of_node_set_flag(root, OF_POPULATED_BUS); of_node_put(root); return rc; } EXPORT_SYMBOL_GPL(of_platform_populate);
디바이스 트리를 첫 번째 인자로 요청한 노드부터 시작하여 그 이하 노드를 파싱한다. 두 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.
- 코드 라인 9~11에서 첫 번째 인자가 정상적으로 주어지지 않은 경우 -EINVAL 결과를 반환한다.
- 코드 라인 16~22에서 두 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.
- 코드 라인 23에서 첫 번째 인자로 요청한 노드에 OF_POPULATED_BUS 플래그를 설정하여 버스를 이미 커널에 등록하였음을 나타낸다.
of_platform_bus_create()
drivers/of/platform.c
/** * of_platform_bus_create() - Create a device for a node and its children. * @bus: device node of the bus to instantiate * @matches: match table for bus nodes * @lookup: auxdata table for matching id and platform_data with device nodes * @parent: parent for new device, or NULL for top level. * @strict: require compatible property * * Creates a platform_device for the provided device_node, and optionally * recursively create devices for all the child nodes. */ static int of_platform_bus_create(struct device_node *bus, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent, bool strict) { const struct of_dev_auxdata *auxdata; struct device_node *child; struct platform_device *dev; const char *bus_id = NULL; void *platform_data = NULL; int rc = 0; /* Make sure it has a compatible property */ if (strict && (!of_get_property(bus, "compatible", NULL))) { pr_debug("%s() - skipping %pOF, no compatible prop\n", __func__, bus); return 0; } if (of_node_check_flag(bus, OF_POPULATED_BUS)) { pr_debug("%s() - skipping %pOF, already populated\n", __func__, bus); return 0; } auxdata = of_dev_lookup(lookup, bus); if (auxdata) { bus_id = auxdata->name; platform_data = auxdata->platform_data; } if (of_device_is_compatible(bus, "arm,primecell")) { /* * Don't return an error here to keep compatibility with older * device tree files. */ of_amba_device_create(bus, bus_id, platform_data, parent); return 0; } dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); if (!dev || !of_match_node(matches, bus)) return 0; for_each_child_of_node(bus, child) { pr_debug(" create child: %pOF\n", child); rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); if (rc) { of_node_put(child); break; } } of_node_set_flag(bus, OF_POPULATED_BUS); return rc; }
첫 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.
- 코드 라인 25~29에서 다섯 번째 인자가 true로 주어진 경우 “compatible” 속성이 발견되지 않으면 skip 하기 위해 성공(0)을 반환한다.
- 코드 라인 31~35에서 이 버스가 이미 커널에 등록된 경우 skip 하기 위해 성공(0)을 반환한다.
- 코드 라인 37~41에서 lookup 테이블에서 검색하여 플랫폼 데이터를 알아온다. lookup이 지정되지 않은 경우 아무런 일도 하지 않는다.
- lookup table은 machine-specific 코드 셋업 루틴을 사용하는 디바이스 드라이버에서 사용되었지만 지금은 거의 사용하지 않는다.
- 코드 라인 43~50에서 버스 노드의 compatible명이 “arm,primecell”인 경우 amba 버스 디바이스를 생성하기 위해 별도의 처리 함수를 사용한다. 그런 후 정상(0) 결과로 빠져나간다.
- 코드 라인 52~54에서 플랫폼 디바이스를 생성하고 등록한다. 만일 생성이 실패하거나 버스가 매치되지 않은 경우 서브 디렉토리를 skip하기 위해 성공(0)을 반환한다.
- 결국 부모 노드가 루트 노드인 경우 플랫폼 디바이스로 등록된다.
- 부모 노드가 플랫폼 버스가 아닌 경우 그 이하의 서브 디렉토리들은 플랫폼 디바이스로 등록되지 않는다.
- 코드 라인 56~63에서 버스 노드 아래 child 노드를 대상으로 이 함수를 재귀호출한다.
- 코드 라인 64에서 요청한 노드에 OF_POPULATED_BUS 플래그를 설정하여 버스를 이미 커널에 등록하였음을 나타낸다.
auxdata lookup 테이블 예)
static struct of_dev_auxdata foo_auxdata_lookup[] __initdata = { OF_DEV_AUXDATA("foo", 0xf1010600, "foo.0", NULL), {}, };
- of_dev_auxdata 구조체에 사용되는 데이터는 compatible 디바이스명, 물리주소, 노드명, 플랫폼 데이터
of_platform_device_create_pdata()
drivers/of/platform.c
static struct platform_device *of_platform_device_create_pdata( struct device_node *np, const char *bus_id, void *platform_data, struct device *parent) { struct platform_device *dev; if (!of_device_is_available(np) || of_node_test_and_set_flag(np, OF_POPULATED)) return NULL; dev = of_device_alloc(np, bus_id, parent); if (!dev) goto err_clear_flag; dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data; of_msi_configure(&dev->dev, dev->dev.of_node); if (of_device_add(dev) != 0) { platform_device_put(dev); goto err_clear_flag; } return dev; err_clear_flag: of_node_clear_flag(np, OF_POPULATED); return NULL; }
플랫폼 디바이스를 생성하고 디바이스 트리로부터 msi 정보 및 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.
- 코드 라인 8~11에서 디바이스가 준비되지 않은 경우 또는 노드가 이미 등록된 경우 null을 반환한다.
- 코드 라인 13~15에서 디바이스를 생성한다.
- 코드 라인 17~19에서 플랫폼 버스 타입과, 플랫폼 데이터를 지정한다. 그리고 디바이스 트리에서 msi 정보를 읽어 디바이스에 설정한다.
- 코드 라인 21~24에서 플랫폼 디바이스를 생성하고 디바이스 트리로부터 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.
of_device_alloc()
drivers/of/platform.c
/** * of_device_alloc - Allocate and initialize an of_device * @np: device node to assign to device * @bus_id: Name to assign to the device. May be null to use default name. * @parent: Parent device. */ struct platform_device *of_device_alloc(struct device_node *np, const char *bus_id, struct device *parent) { struct platform_device *dev; int rc, i, num_reg = 0, num_irq; struct resource *res, temp_res; dev = platform_device_alloc("", PLATFORM_DEVID_NONE); if (!dev) return NULL; /* count the io and irq resources */ while (of_address_to_resource(np, num_reg, &temp_res) == 0) num_reg++; num_irq = of_irq_count(np); /* Populate the resource table */ if (num_irq || num_reg) { res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL); if (!res) { platform_device_put(dev); return NULL; } dev->num_resources = num_reg + num_irq; dev->resource = res; for (i = 0; i < num_reg; i++, res++) { rc = of_address_to_resource(np, i, res); WARN_ON(rc); } if (of_irq_to_resource_table(np, res, num_irq) != num_irq) pr_debug("not all legacy IRQ resources mapped for %s\n", np->name); } dev->dev.of_node = of_node_get(np); dev->dev.fwnode = &np->fwnode; dev->dev.parent = parent ? : &platform_bus; if (bus_id) dev_set_name(&dev->dev, "%s", bus_id); else of_device_make_bus_id(&dev->dev); return dev; } EXPORT_SYMBOL(of_device_alloc);
플랫폼 디바이스를 생성하고 디바이스 트리로부터 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.
- 코드 라인 15~17에서 플랫폼 디바이스를 할당하되 id는 주어지지 않게 한다.
- 코드 라인 20~22에서 io 및 irq 리소스의 수를 파악하기 위해 임시로 읽어온다.
- num_reg에는 노드에서 읽어온 io 주소 수가 담긴다.
- num_irq에는 노드에서 읽어온 irq 수가 담긴다.
- 코드 라인 25~41에서 io 및 irq 리소스가 존재하는 경우 그 수 만큼 리소스를 생성하고 노드에서 다시 리소스 정보를 정식으로 읽어 저장한다.
- 코드 라인 43~45에서 디바이스에 노드 정보와 부모 디바이스 정보를 대입한다. 부모 디바이스가 없는 경우 플랫폼 버스를 지정한다.
- 코드 라인 47~50에서 디바이스 이름을 bus_id로 설정한다. bus_id가 없는 경우 디바이스 트리 노드를 읽어 지정한다.
- 디바이스 트리 노드를 읽어 사용한 예) 3f000000.pcie, platform@c000000, psci
디바이스명 생성 규칙 (for 디바이스 트리)
of_device_make_bus_id()
drivers/of/platform.c
/** * of_device_make_bus_id - Use the device node data to assign a unique name * @dev: pointer to device structure that is linked to a device tree node * * This routine will first try using the translated bus address to * derive a unique name. If it cannot, then it will prepend names from * parent nodes until a unique name can be derived. */ static void of_device_make_bus_id(struct device *dev) { struct device_node *node = dev->of_node; const __be32 *reg; u64 addr; /* Construct the name, using parent nodes if necessary to ensure uniqueness */ while (node->parent) { /* * If the address can be translated, then that is as much * uniqueness as we need. Make it the first component and return */ reg = of_get_property(node, "reg", NULL); if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) { dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s", (unsigned long long)addr, node->name, dev_name(dev)); return; } /* format arguments only used if dev_name() resolves to NULL */ dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s", kbasename(node->full_name), dev_name(dev)); node = node->parent; } }
디바이스 이름을 다음 규칙에 의해 이름을 만들어 지정한다.
1) 노드에 reg 속성이 있는 경우
- 주어진 디바이스 명 존재 시: <주소>.<노드명>:<주어진 디바이스명>
- 주어진 디바이스 명 없을 때: <주소>.<노드명>
다음 예와 같이 reg 속성이 있고, 주어진 디바이스명이 없는 경우 최종 디바이스명은 “9020000.fw-cfg”가 된다.
fw-cfg@9020000 { dma-coherent; reg = <0x0 0x9020000 0x0 0x18>; compatible = "qemu,fw-cfg-mmio"; };
2) 노드에 reg 속성이 없는 경우
- 주어진 디바이스 명 존재 시: <full_노드명>:<주어진 디바이스명>
- 주어진 디바이스 명 없을 때: <full_노드명>
다음 예와 같이 reg 속성이 없고, 주어진 디바이스명이 없는 경우 최종 디바이스명은 “platform@c000000″가 된다.
platform@c000000 { interrupt-parent = <0x8001>; ranges = <0x0 0x0 0xc000000 0x2000000>; #address-cells = <0x1>; #size-cells = <0x1>; compatible = "qemu,platform", "simple-bus"; };
Dynamic 디바이스 트리
Dynamic하게 디바이스 트리의 정보를 변경 시키고 플랫폼 디바이스의 등록 및 해제 시 호출되는 후크 함수이다. 보통 특정 아키텍처의 테스트 환경을 지원하기 위해 사용된다.
of_platform_notify()
drivers/of/platform.c
static int of_platform_notify(struct notifier_block *nb, unsigned long action, void *arg) { struct of_reconfig_data *rd = arg; struct platform_device *pdev_parent, *pdev; bool children_left; switch (of_reconfig_get_state_change(action, rd)) { case OF_RECONFIG_CHANGE_ADD: /* verify that the parent is a bus */ if (!of_node_check_flag(rd->dn->parent, OF_POPULATED_BUS)) return NOTIFY_OK; /* not for us */ /* already populated? (driver using of_populate manually) */ if (of_node_check_flag(rd->dn, OF_POPULATED)) return NOTIFY_OK; /* pdev_parent may be NULL when no bus platform device */ pdev_parent = of_find_device_by_node(rd->dn->parent); pdev = of_platform_device_create(rd->dn, NULL, pdev_parent ? &pdev_parent->dev : NULL); of_dev_put(pdev_parent); if (pdev == NULL) { pr_err("%s: failed to create for '%pOF'\n", __func__, rd->dn); /* of_platform_device_create tosses the error code */ return notifier_from_errno(-EINVAL); } break; case OF_RECONFIG_CHANGE_REMOVE: /* already depopulated? */ if (!of_node_check_flag(rd->dn, OF_POPULATED)) return NOTIFY_OK; /* find our device by node */ pdev = of_find_device_by_node(rd->dn); if (pdev == NULL) return NOTIFY_OK; /* no? not meant for us */ /* unregister takes one ref away */ of_platform_device_destroy(&pdev->dev, &children_left); /* and put the reference of the find */ of_dev_put(pdev); break; } return NOTIFY_OK; }
디바이스 트리를 통해 플랫폼 디바이스가 등록되거나 호출될 때 사용되는 후크 함수이다.
- 코드 라인 9~30에서 디바이스 트리에 플랫폼 디바이스가 발견되는 경우 등록한다.
- 코드 라인 32~48에서 디바이스 트리로부터 등록된 플랫폼 디바이스를 등록 해제한다.
of_platform_device_create()
drivers/of/platform.c
/** * of_platform_device_create - Alloc, initialize and register an of_device * @np: pointer to node to create device for * @bus_id: name to assign device * @parent: Linux device model parent device. * * Returns pointer to created platform device, or NULL if a device was not * registered. Unavailable devices will not get registered. */ struct platform_device *of_platform_device_create(struct device_node *np, const char *bus_id, struct device *parent) { return of_platform_device_create_pdata(np, bus_id, NULL, parent); } EXPORT_SYMBOL(of_platform_device_create);
플랫폼 디바이스를 생성하고 디바이스 트리로부터 msi 정보 및 플랫폼 리소스를 읽어 저장한 후 플랫폼 디바이스로 등록한다.
- platform_data만 null로 지정하여 호출한다.
플랫폼 드라이버 등록
드라이버 모듈 진입부
플랫폼 드라이버 모듈의 마지막에 다음과 같은 매크로 함수 중 하나를 사용하여 플랫폼 드라이버의 등록부 코드를 준비한다.
- 모든 디바이스 및 드라이버 공통 진입부
- device_initcall(foo_init)
- 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 모든 디바이스 및 드라이버의 진입부에 사용된다.
- 커널에 임베드하지 않고 모듈로 빌드한 경우 module_init()과 동일하게 동작한다.
- module_init(foo_init) & module_exit(foo_exit)
- insmod를 사용한 모듈 방식으로 모든 디바이스 및 드라이버의 진입부에 사용된다.
- device_initcall(foo_init)
- 플랫폼 드라이버용 진입부
- builtin_platform_driver(foo_driver)
- 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 플랫폼 드라이버 진입부에 사용된다.
- 커널에 임베드하지 않고 모듈로 빌드한 경우 module_platform_driver()와 동일하게 동작한다.
- module_platform_driver(foo_driver)
- insmod를 사용한 모듈 방식으로 플랫폼 드라이버용 진입부에 사용된다.
- builtin_platform_driver(foo_driver)
- 플랫폼 디바이스 생성 및 플랫폼 드라이버용 진입부
- builtin_platform_driver_probe(foo_driver, foo_probe)
- 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 플랫폼 디바이스를 생성하면서 플랫폼 드라이버 진입에 사용된다.
- 커널에 임베드하지 않고 모듈로 빌드한 경우 module_platform_driver_probe()와 동일하게 동작한다.
- module_platform_driver_probe(foo_driver, foo_probe)
- insmod를 사용한 모듈 방식으로 플랫폼 디바이스를 생성하면서 플랫폼 드라이버 진입에 사용된다.
- builtin_platform_driver_probe(foo_driver, foo_probe)
플랫폼 드라이버를 등록하기 위해 다음을 준비한다.
- probe() 함수
- platform_device_id 구조체
- platform_driver 구조체
#include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/platform_device.h> static int foo_probe(struct platform_device *pdev) { ... return 0; } static const struct platform_device_id foo_id_table[] = { { "foo", (unsigned long) &foo_id }, { }, }; static const struct of_device_id foo_of_match_table[] = { { .compatible = "foo,foo", }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, foo_of_match_table); static struct platform_driver foo_driver = { .probe = foo_probe, .driver = { .name = "foo", .of_match_table = foo_of_match_table, }, .id_table = foo_id_table, };
1) device_initcall() 매크로 함수 사용 시
device_initcall() 함수의 동작은 다음과 같이 내부적으로 두 가지 상황으로 처리된다.
- 이 드라이버가 커널에 임베드되어(커널의 menuconfig에서 ‘*’ 선택) 로드하는 경우
- 커널의 부트업 시 initcall 호출 과정에서 foo_driver를 등록한다.
- 그리고 이에 대한 디바이스도 이미 로드되어 매치 가능한 경우 bind하여 곧바로 foo_probe() 함수를 probe한다.
- 커널에 임베드되지 않고 모듈 형태로 빌드한(커널의 menuconfig에서 ‘m’ 선택) 후 사용자 영역에서 insmod 명령에 의해 로드하는 경우
- insmod 명령에 의해 드라이버 모듈을 로드하고 foo_init() 함수를 호출하여 foo_driver를 등록한다.
- 역시 이에 대한 디바이스도 이미 로드되어 매치 가능한 경우 bind하여 곧바로 foo_probe() 함수를 probe한다.
static int __init foo_init(void) { return platform_driver_register(&foo_driver); } device_initcall(foo_init);
2) module_init() & module_exit() 매크로 함수 사용 시
드라이버를 항상 모듈로 만들어 insmod에 의해서만 드라이버를 로드하려면 device_initcall() 대신 module_init()을 사용한다.
static int __init foo_init(void) { return platform_driver_register(&foo_driver); } static void __exit foo_exit(void) { platform_driver_unregister(&foo_driver); } module_init(foo_init); module_exit(foo_exit);
3) builtin_platform_driver() 매크로 함수 사용 시
커널 부트업 시 사용할 드라이버를 등록한다. device_initcall()보다 더 심플하게 코딩할 수 있다.
builtin_platform_driver(&foo_driver);
/* builtin_platform_driver() - Helper macro for builtin drivers that * don't do anything special in driver init. This eliminates some * boilerplate. Each driver may only use this macro once, and * calling it replaces device_initcall(). Note this is meant to be * a parallel of module_platform_driver() above, but w/o _exit stuff. */ #define builtin_platform_driver(__platform_driver) \ builtin_driver(__platform_driver, platform_driver_register)
/* * builtin_driver() - Helper macro for drivers that don't do anything * special in init and have no exit. This eliminates some boilerplate. * Each driver may only use this macro once, and calling it replaces * device_initcall (or in some cases, the legacy __initcall). This is * meant to be a direct parallel of module_driver() above but without * the __exit stuff that is not used for builtin cases. * * @__driver: driver name * @__register: register function for this driver type * @...: Additional arguments to be passed to __register * * Use this macro to construct bus specific macros for registering * drivers, and do not use it on its own. */ #define builtin_driver(__driver, __register, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ device_initcall(__driver##_init);
4) module_platform_driver() 매크로 함수 사용 시
위의 module_init() 및 module_exit() 함수를 아래 매크로로 유사하게 만들어 사용하는 방법으로 매우 심플하게 코딩할 수 있다.
module_platform_driver(&foo_driver);
include/linux/platform_device.h
/* module_platform_driver() - Helper macro for drivers that don't do * anything special in module init/exit. This eliminates a lot of * boilerplate. Each module may only use this macro once, and * calling it replaces module_init() and module_exit() */ #define module_platform_driver(__platform_driver) \ module_driver(__platform_driver, platform_driver_register, \ platform_driver_unregister)
module_driver() 매크로 함수
/** * module_driver() - Helper macro for drivers that don't do anything * special in module init/exit. This eliminates a lot of boilerplate. * Each module may only use this macro once, and calling it replaces * module_init() and module_exit(). * * @__driver: driver name * @__register: register function for this driver type * @__unregister: unregister function for this driver type * @...: Additional arguments to be passed to __register and __unregister. * * Use this macro to construct bus specific macros for registering * drivers, and do not use it on its own. */ #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);
5) builtin_platform_driver_probe() 매크로 함수 사용 시
builtin_platform_driver() 매크로 함수와 유사한 방식이다. device_driver 구조체에서 probe 함수 및 id_table을 지정하지 않은 것이 다른 점이다. 역시 아래와 같이 매우 심플하게 코딩할 수 있다.
static struct platform_driver foo_driver = { .remove = __exit_p(foo_remove), .driver = { .name = "foo", }, }; builtin_platform_driver_probe(foo_driver, foo_probe);
/* builtin_platform_driver_probe() - Helper macro for drivers that don't do * anything special in device init. This eliminates some boilerplate. Each * driver may only use this macro once, and using it replaces device_initcall. * This is meant to be a parallel of module_platform_driver_probe above, but * without the __exit parts. */ #define builtin_platform_driver_probe(__platform_driver, __platform_probe) \ static int __init __platform_driver##_init(void) \ { \ return platform_driver_probe(&(__platform_driver), \ __platform_probe); \ } \ device_initcall(__platform_driver##_init);
6) module_platform_driver_probe() 매크로 함수 사용 시
module_platform_driver() 매크로 함수와 유사한 방식이다. device_driver 구조체에서 probe 함수 및 id_table을 지정하지 않은 것이 다른 점이다. 역시 아래와 같이 매우 심플하게 코딩할 수 있다.
static struct platform_driver foo_driver = { .remove = __exit_p(foo_remove), .driver = { .name = "foo", }, }; module_platform_driver_probe(foo_driver, foo_probe);
/* module_platform_driver_probe() - Helper macro for drivers that don't do * anything special in module init/exit. This eliminates a lot of * boilerplate. Each module may only use this macro once, and * calling it replaces module_init() and module_exit() */ #define module_platform_driver_probe(__platform_driver, __platform_probe) \ static int __init __platform_driver##_init(void) \ { \ return platform_driver_probe(&(__platform_driver), \ __platform_probe); \ } \ module_init(__platform_driver##_init); \ static void __exit __platform_driver##_exit(void) \ { \ platform_driver_unregister(&(__platform_driver)); \ } \ module_exit(__platform_driver##_exit);
플랫폼 드라이버 등록부
__platform_driver_register()
include/linux/platform_device.h
/* * use a macro to avoid include chaining to get THIS_MODULE */ #define platform_driver_register(drv) \ __platform_driver_register(drv, THIS_MODULE)
플랫폼 드라이버를 등록한다.
__platform_driver_register()
drivers/base/platform.c
/** * __platform_driver_register - register a driver for platform-level devices * @drv: platform driver structure * @owner: owning module/driver */ int __platform_driver_register(struct platform_driver *drv, struct module *owner) { drv->driver.owner = owner; drv->driver.bus = &platform_bus_type; drv->driver.probe = platform_drv_probe; drv->driver.remove = platform_drv_remove; drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } EXPORT_SYMBOL_GPL(__platform_driver_register);
플랫폼 드라이버를 등록한다.
- 버스 타입 및 probe, remove, shutdown 함수는 플랫폼 드라이버 core가 제공하는 함수를 사용한다.
플랫폼 버스 타입
struct bus_type platform_bus_type = { .name = "platform", .dev_groups = platform_dev_groups, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, }; EXPORT_SYMBOL_GPL(platform_bus_type);
플랫폼 디바이스 속성
플랫폼 디바이스에 부여되는 속성들은 다음과 같다.
- drriver_override
- modalias
플랫폼 디바이스 및 드라이버 매치 확인
platform_match()
drivers/base/platform.c
/** * platform_match - bind platform device to platform driver. * @dev: device. * @drv: driver. * * Platform device IDs are assumed to be encoded like this: * "<name><instance>", where <name> is a short description of the type of * device, like "pci" or "floppy", and <instance> is the enumerated * instance of the device, like '0' or '42'. Driver IDs are simply * "<name>". So, extract the <name> from the platform_device structure, * and compare it against the name of the driver. Return whether they match * or not. */ static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv); /* When driver_override is set, only bind to the matching driver */ if (pdev->driver_override) return !strcmp(pdev->driver_override, drv->name); /* Attempt an OF style match first */ if (of_driver_match_device(dev, drv)) return 1; /* Then try ACPI style match */ if (acpi_driver_match_device(dev, drv)) return 1; /* Then try to match against the id table */ if (pdrv->id_table) return platform_match_id(pdrv->id_table, pdev) != NULL; /* fall-back to driver name match */ return (strcmp(pdev->name, drv->name) == 0); }
플랫폼 디바이스와 플랫폼 드라이버의 매치여부를 반환한다. (1=매치, 0=매치되지 않음)
- 코드 라인 20~21에서 플랫폼 디바이스의 driver_override가 드라이버 명으로 설정된 경우 함수를 성공(1)을 반환한다.
- 코드 라인 24~25에서 디바이스와 드라이버가 매치된 경우 성공(1)을 반환한다.
- 코드 라인 28~29에서 ACPI 스타일로 디바이스와 드라이버가 매치되는 경우 성공(1)을 반환한다.
- 코드 라인 32~33에서 플랫폼 디바이스에 있는 id_table과 플랫폼 드라이버에 있는 id_table이 매치되는 경우 성공(1)을 반환한다.
- 코드 라인 36에서 마지막으로 플랫폼 디바이스명과 드라이버의 이름이 동일한 경우 성공(1)을 반환한다. 그렇지 않은 경우 0을 반환한다.
플랫폼 드라이버 등록과 동기 방법으로 probe
platform_driver_probe()
include/linux/platform_device.h
/* non-hotpluggable platform devices may use this so that probe() and * its support may live in __init sections, conserving runtime memory. */ #define platform_driver_probe(drv, probe) \ __platform_driver_probe(drv, probe, THIS_MODULE)
플랫폼 디바이스 드라이버를 probe한다.
__platform_driver_probe()
drivers/base/platform.c
/** * __platform_driver_probe - register driver for non-hotpluggable device * @drv: platform driver structure * @probe: the driver probe routine, probably from an __init section * @module: module which will be the owner of the driver * * Use this instead of platform_driver_register() when you know the device * is not hotpluggable and has already been registered, and you want to * remove its run-once probe() infrastructure from memory after the driver * has bound to the device. * * One typical use for this would be with drivers for controllers integrated * into system-on-chip processors, where the controller devices have been * configured as part of board setup. * * Note that this is incompatible with deferred probing. * * Returns zero if the driver registered and bound to a device, else returns * a negative error code and with the driver not registered. */
int __init_or_module __platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *), struct module *module) { int retval, code; if (drv->driver.probe_type == PROBE_PREFER_ASYNCHRONOUS) { pr_err("%s: drivers registered with %s can not be probed asynchronously\n", drv->driver.name, __func__); return -EINVAL; } /* * We have to run our probes synchronously because we check if * we find any devices to bind to and exit with error if there * are any. */ drv->driver.probe_type = PROBE_FORCE_SYNCHRONOUS; /* * Prevent driver from requesting probe deferral to avoid further * futile probe attempts. */ drv->prevent_deferred_probe = true; /* make sure driver won't have bind/unbind attributes */ drv->driver.suppress_bind_attrs = true; /* temporary section violation during probe() */ drv->probe = probe; retval = code = __platform_driver_register(drv, module); /* * Fixup that section violation, being paranoid about code scanning * the list of drivers in order to probe new devices. Check to see * if the probe was successful, and make sure any forced probes of * new devices fail. */ spin_lock(&drv->driver.bus->p->klist_drivers.k_lock); drv->probe = NULL; if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list)) retval = -ENODEV; drv->driver.probe = platform_drv_probe_fail; spin_unlock(&drv->driver.bus->p->klist_drivers.k_lock); if (code != retval) platform_driver_unregister(drv); return retval; } EXPORT_SYMBOL_GPL(__platform_driver_probe);
플랫폼 드라이버를 등록하고 동기 방법으로 probe한다.
- 코드 라인 6~10에서 드라이버의 probe 타입이 PROBE_PREFER_ASYNCHRONOUS인 경우 에러를 출력하고 -EINVAL 에러를 반환한다. 이 함수는 비동기 probe를 지원하지 않는다.
- PROBE_PREFER_ASYNCHRONOUS는 mtd 드라이버나 i2c 등에서 probing이 저속으로 동작하는 장치등에서 사용한다.
- 코드 라인 17에서 드라이버의 probe 타입을 PROBE_FORCE_SYNCHRONOUS로 고정한다.
- 코드 라인 23에서 deferred probe가 동작하지 못하게 금지시킨다.
- 코드 라인 26에서 드라이버에 suppress_bind_attrs가 설정된 경우 sysfs에서 bind/unbind 속성 파일을 생성하지 못하게 한다.
- 코드 라인 29~30에서 드라이버에 인자로 전달 받은 probe 함수를 지정하고 플랫폼 드라이버를 등록한다.
subsystem 등록
subsys_system_register()
/** * subsys_system_register - register a subsystem at /sys/devices/system/ * @subsys: system subsystem * @groups: default attributes for the root device * * All 'system' subsystems have a /sys/devices/system/<name> root device * with the name of the subsystem. The root device can carry subsystem- * wide attributes. All registered devices are below this single root * device and are named after the subsystem with a simple enumeration * number appended. The registered devices are not explicitly named; * only 'id' in the device needs to be set. * * Do not use this interface for anything new, it exists for compatibility * with bad ideas only. New subsystems should use plain subsystems; and * add the subsystem-wide attributes should be added to the subsystem * directory itself and not some create fake root-device placed in * /sys/devices/system/<name>. */ int subsys_system_register(struct bus_type *subsys, const struct attribute_group **groups) { return subsys_register(subsys, groups, &system_kset->kobj); } EXPORT_SYMBOL_GPL(subsys_system_register);
서브 시스템을 “/sys/devices/system” 디렉토리 아래에 등록한다. (이하 코드는 생략)
기타 플랫폼 디바이스 & 드라이버 관련 API들
- platform_get_resource()
- 플랫폼 디바이스로부터 리소스를 가져온다.
- platform_get_irq()
- 플랫폼 디바이스로부터 irq 번호를 가져온다.
- platform_irq_count()
- 플랫폼 디바이스에있는 irq 수를 알아온다.
- platform_get_resource_byname()
- 플랫폼 디바이스에서 요청한 이름과 타입의 리소스를 알아온다.
- platform_get_irq_byname()
- 플랫폼 디바이스에서 요청한 irq 이름에 해당하는 irq 번호를 가져온다.
- platform_device_put()
- 플랫폼 디바이스를 destroy한다.
- platform_device_add_resources()
- 플랫폼 디바이스에 리소스들을 추가한다.
- platform_device_add_data()
- 플랫폼 디바이스에 플랫폼 데이터를 지정한다.
- platform_device_add_properties()
- 내장 속성을 플랫폼 디바이스에 추가한다.
- platform_device_del()
- 플랫폼 디바이스를 제거한다.
- platform_device_unregister()
- 플랫폼 디바이스를 등록 해제한다.
- platform_driver_unregister()
- 플랫폼 드라이버를 등록 해제한다.
- __platform_create_bundle()
- 플랫폼 드라이버를 등록하고 이에 대응하는 플랫폼 디바이스를 생성한다.
- platform_unregister_drivers()
- 복수의 플랫폼 드라이버들을 등록 해제한다.
- platform_get_device_id()
- to_platform_device()
- 디바이스를 임베드한 플랫폼 디바이스를 반환한다.
- to_platform_driver()
- 드라이버를 임베드한 플랫폼 드라이버를 반환한다.
참고
- Device & Driver -1- (Device Driver Model) | 문c
- Device & Driver -2- (Bus & Class) | 문c
- Device & Driver -3- (Platform Device) | 문c – 현재 글
- Sysfs (kobject & kset) | 문c
- driver_init() | 문c
- devtmpfs & kdevtmpfs 스레드 | 문c
- do_initcalls() | 문c
- Linux Device Model Part 1 | Sarah Diesburg – 다운로드 pdf
- Linux Device Model Part 2 | Sarah Diesburg – 다운로드 pdf
- Linux Device Drivers | OPERSYS – 다운로드 pdf
- Embedded data structures and lifetime management | Shuah Khan, Samsung Open Source Group – 다운로드 pdf
- The Sysfs Virtual Filesystem Exploring the Linux Device Model | Bill Gatliff – 다운로드 pdf
- Linux Device Drivers | Jie Liao – 다운로드 pdf
- The zen of kobjects | LWN.net
- Linux Device Drivers, Third Edition | LWN.net
- kobject/kset/ktype documentation and example code updated | LWN.net
- Documentation/driver-model/ | kernel.org
- Brief sysfs Tutorial | Ben Marks – 다운로드 pdf
- Rules on how to access information in sysfs | kernel.org
안녕하세요, 좋은 자료 감사합니다.
위에서 “디바이스 드라이버 모듈을 따른 플랫폼 디바이스의 등록은 이전 32bit arm에서 많은 수의 보드 특정(board, machine 또는 architect specific) 셋업 코드에 아래의 플랫폼 디바이스 등록 API 코드들을 사용해왔다. 현재 디바이스 트리를 사용하는 arm64 커널은 arch-specific 셋업 코드를 사용하지 않고 디바이스 트리가 제공하는 정보를 파싱하여 각 성격에 맞는 디바이스로 등록된다. 참고로 이 과정에서 soc 디렉토리 뒤에 붙은 첫 디바이스들은 당연히 플랫폼 디바이스로 등록된다.”라고 하셨는데요,
“soc 디렉토리 뒤에 붙은 첫 디바이스들은 당연히 플랫폼 디바이스로 등록된다.”가 무슨 뜻인지 모르겠네요. 혹시 좀 더 설명해 주시면 감사하겠습니다.
안녕하세요?
gpu 없이 arm cpu로만 SoC가 만들어진 경우 곧바로 arm이 부팅하며 이 경우 플랫폼 디바이스 들이 위치한 디렉토리는 /sys/devices/platform 디렉토리입니다. 그리고 요즈음 gpu가 내장된 보드 들(예: 라즈베리 파이)의 경우 gpu로 펌웨어가 부팅되고, 그 후 이어서 arm cpu에 부트로더와 커널이 부팅되는데 이렇게 두 번째로 연결되는 arm cpu의 플랫폼 디바이스들은 디바이스 드라이버 작성자의 의도에 따라 /sys/devices/platform/soc 등의 디렉토리 명으로 구분되어 위치하기도 합니다. soc라는 디렉토리명은 변경될 수도 있고, soc 디렉토리 없이 그냥 /sys/devices/platform에 모두 포함되어 있을 수도 있습니다
다음은 라즈베리 파이 4의 예입니다.
$ ls -la /sys/devices/platform/
total 0
drwxr-xr-x 21 root root 0 Nov 19 19:35 .
drwxr-xr-x 12 root root 0 Nov 19 19:35 ..
drwxr-xr-x 3 root root 0 Nov 19 19:35 0.framebuffer
drwxr-xr-x 4 root root 0 Nov 19 19:35 'Fixed MDIO bus.0'
drwxr-xr-x 3 root root 0 Nov 19 19:35 alarmtimer
drwxr-xr-x 3 root root 0 Nov 19 19:35 arm-pmu
drwxr-xr-x 3 root root 0 Nov 19 19:35 chosen
drwxr-xr-x 3 root root 0 Nov 19 19:35 clocks
drwxr-xr-x 4 root root 0 Nov 19 19:35 fixedregulator_3v3
drwxr-xr-x 4 root root 0 Nov 19 19:35 fixedregulator_5v0
drwxr-xr-x 4 root root 0 Nov 19 19:35 leds
drwxr-xr-x 3 root root 0 Nov 19 19:35 phy
drwxr-xr-x 2 root root 0 Nov 19 19:35 power
drwxr-xr-x 4 root root 0 Nov 19 19:35 reg-dummy
drwxr-xr-x 3 root root 0 Nov 19 19:35 regulatory.0
drwxr-xr-x 11 root root 0 Nov 19 19:35 scb
drwxr-xr-x 4 root root 0 Nov 19 19:35 sd_io_1v8_reg
drwxr-xr-x 3 root root 0 Nov 19 19:35 snd-soc-dummy
drwxr-xr-x 24 root root 0 Nov 19 19:35 soc
drwxr-xr-x 3 root root 0 Nov 19 19:35 timer
-rw-r--r-- 1 root root 4096 Nov 19 19:35 uevent
drwxr-xr-x 4 root root 0 Nov 19 19:35 v3dbus
$ ls -la /sys/devices/platform/soc
total 0
drwxr-xr-x 24 root root 0 Nov 19 19:35 .
drwxr-xr-x 21 root root 0 Nov 19 19:35 ..
-rw-r--r-- 1 root root 4096 Nov 19 19:22 driver_override
drwxr-xr-x 3 root root 0 Jan 29 2018 fd5d2200.thermal
drwxr-xr-x 4 root root 0 Jan 29 2018 fe007000.dma
drwxr-xr-x 3 root root 0 Jan 29 2018 fe00b880.mailbox
drwxr-xr-x 5 root root 0 Jan 29 2018 fe100000.watchdog
drwxr-xr-x 3 root root 0 Jan 29 2018 fe101000.cprman
drwxr-xr-x 3 root root 0 Jan 29 2018 fe104000.rng
drwxr-xr-x 5 root root 0 Jan 29 2018 fe200000.gpio
drwxr-xr-x 3 root root 0 Jan 29 2018 fe200000.gpiomem
drwxr-xr-x 4 root root 0 Jan 29 2018 fe201000.serial
drwxr-xr-x 3 root root 0 Jan 29 2018 fe209000.dsi
drwxr-xr-x 3 root root 0 Jan 29 2018 fe215000.aux
drwxr-xr-x 4 root root 0 Jan 29 2018 fe300000.mmcnr
drwxr-xr-x 5 root root 0 Jan 29 2018 fe340000.emmc2
drwxr-xr-x 3 root root 0 Jan 29 2018 fe600000.firmwarekms
drwxr-xr-x 3 root root 0 Jan 29 2018 ff800000.local_intc
-r--r--r-- 1 root root 4096 Nov 19 19:22 modalias
lrwxrwxrwx 1 root root 0 Nov 19 19:22 of_node -> ../../../firmware/devicetree/base/soc
drwxr-xr-x 2 root root 0 Nov 19 19:22 power
drwxr-xr-x 4 root root 0 Jan 29 2018 soc:audio
drwxr-xr-x 5 root root 0 Jan 29 2018 soc:firmware
drwxr-xr-x 4 root root 0 Jan 29 2018 soc:gpu
drwxr-xr-x 3 root root 0 Jan 29 2018 soc:power
drwxr-xr-x 3 root root 0 Jan 29 2018 soc:vcsm
drwxr-xr-x 3 root root 0 Jan 29 2018 soc:virtgpio
lrwxrwxrwx 1 root root 0 Jan 29 2018 subsystem -> ../../../bus/platform
-rw-r--r-- 1 root root 4096 Jan 29 2018 uevent
위와 같이 라즈베리 파이 4의 경우 soc 디렉토리를 따로 만들었고, soc 디렉토리 다음에 위치한 디바이스들도 플랫폼 디바이스로 인식합니다.
감사합니다.
아 그렇군요. rasberry pi에서 gpu로 firmware 초기화가 끝난 후 cpu로 boot loading한다는 것을 지금 알았네요. 그런데 /sys 밑에 devie들이 생기는 것은 device들이 등록되면서 kernel이 sysfs 밑에 그런 file들을 만드는 것으로 알고 있는데요, 그렇다면 platform device의 경우 /sys/devices/platform[/soc] 밑에 생기는 것으로 알겠습니다. 그래도 “soc 디렉토리 뒤에 붙은 첫 디바이스들은 당연히 플랫폼 디바이스로 등록된다” 는 말이 좀 이상한 것 같습니다. “soc에 포함된 device들은 /sys/devices/platform/soc 에 만들어진다” 정도가 더 자연수러울 거 같네요.
네. 정확히 말씀하셨습니다. 디바이스 트리에서 루트 노드에 포함된 특수 노드를 제외한 첫 번째 노드들이 플랫폼 디바이스들입니다.
이들은 /sys/devices/platform 디렉토리 뒤에 생기는 디렉토리들입니다.
그리고 arm + gpu와 같이 특수한 경우에는 soc(개발자가 아무 이름이나 사용할 수 있지만, 보통 이 이름을 사용합니다) 노드와 같이 별도의 추가 노드를 사용하고, 그 노드안에 compatible = “simple-bus”를 사용하여 이 다음 노드들도 플랫폼 디바이스로 확장 인식할 수 있게 합니다. 그러한 경우 /sys/devices/platform/soc 디렉토리 뒤에 생성되는 디바이스들도 플랫폼 디바이스들이 됩니다.
조금 길게 쓰려니 표현이 어렵군요. 김찬님 표현이 정말 더 자연스러운 것 같습니다. ^^
갑자기 디바이스 드라이버(플렛폼 디바이스 타입)를 올리라는 미션을 받았는데, 여러 시행 착오를 겪었는데요.
이 콘텐츠가 많은 도움이 됐습니다. 정말 감사합니다.
안녕하세요 김동현님.
잘 해결이 되었다하니 좋은 소식입니다.
즐거운 하루되세요. ^^
요즘 여기 내용들 정독 중인데
정말 많은 도움이 되는 거 같습니다.
감사합니다.
도움이 되신다니 매우 기쁩니다.
오늘도 즐거운 하루되세요. ^^
덕분에 여러 글들 감사히 잘 보고있습니다. RTOS에서 리눅스로 넘어오면서 디바이스 드라이버 관점에서 스터디중인데요, 아직 해결하지 못한 부분이, 플랫폼디바이스와 일반디바이스의 사용상 차이점인데,,, 많은 자료와 코드를 봐도 이해가 안가네요. 일반디바이스는 fops로 제어가 가능한데, 플랫폼 디바이스는 내부제어용 ops만 있고, 응용프로그램에서 제어하는 fops는 없네요. 예를들어 LCD 콘트롤러 드라이버는 플랫폼에 올렸지만, 정작 LCD 제어할때는 fb 디바이스의 fops를 사용하더군요.(MDS 디바이스 드라이버 강의 책자 내용)
플랫폼 디바이스로 등록되면, 응용프로그램에서 제어가 안되는것인지, 커널 내부에서만 제어되는것인지, 다른 디바이스에 맵핑되어 제어되는것인지….. I2C, SOUND 디바이스 관련하여 분석중인데, 일주일째 되돌이표네요. 괜찮으시다면 도움 부탁드려도 될런지요.
안녕하세요? 이석준 님.
디바이스가 연결되는 상위 버스는 다음과 같습니다.
– 플랫폼 버스(주로 SoC 내부에 위치)
– PCI 버스
– I2C 버스
– 기타 각종 버스
드라이버의 구현 형태는 다음과 같습니다.
– 캐릭터 드라이버
– 블럭 드라이버
– 네트워크 드라이버
– TTY 드라이버
– 기타 Native 드라이버
위와 같이 구현된 드라이버는 기본적으로 각각의 방법으로 유저 모드와 소통합니다.
그 외에 방법은 다음과 같은 인터페이스를 사용하여 추가할 수 있습니다.
– debugfs 인터페이스
– proc 인터페이스
– sysfs 인터페이스
– socket 인터페이스
– netlink 인터페이스
물론 추가로 클래스에 등록하여 해당 클래스가 제공하는 속성을 통해 유저와 인터페이스하는 방법도 있다.
—————————-
질문하신 플랫폼 디바이스는 보통 SoC 내부에 위치하며, 유저가 특별히 몰라도 되는 high speed 버스에 연결된 시스템 레지스터를 통해 제어할 수 있는 특징이 있습니다.
이석준 님이 보시는 플랫픔 버스에 연결된 드라이버는 보통 1개의 구현 형태를 채택하였으며,
필요한 경우 유저와의 인터페이스를 얼마든지 추가할 수 있습니다.
예를 들어 플랫폼 버스를 사용하는 온도계가 있는 경우 다음과 같은 코드 블럭들을 사용하여 구현할 수 있습니다.
– 플랫폼 버스에 연결된 온도계의 구현을 Native 하게 구현합니다. (이 때 유저 모드와 소통할 수 있는 방법이 없습니다.)
– 캐릭터 드라이버 추가 구현하여 유저 application이 /dev/foo 파일을 통해 ioctl API를 사용할 수 있는 장점을 줍니다.
– proc 인터페이스를 추가 구현하여 /proc/foo 파일을 통해 간단한 디바이스 정보를 확인할 수 있게 합니다.
– sysfs 인터페이스를 추가 구현하여 /sys/…./foo 파일을 통해 설정 및 정보를 확인할 수 있게 합니다.
– debugfs 인터페이스를 추가 구현하여 /proc/sys/debug/foo 파일을 통해 설정 및 디버그 정보를 확인할 수 있게 합니다.
끝으로 드라이버 코드들은 사용자 입맛에 맞게 사용하기 위해서는 각종 인터페이스를 추가하여 사용해야 합니다.
감사합니다.
친절한 답변 고맙습니다. 안그래도 오늘 sysfs 이용한 GPIO 제어방법을 봤습니다. /sys/class아래 등록하여 화일로 제어를 하더군요. 말씀하신대로 여러방법이 있는것같은데, 아직 리알못이라 개념잡는데 좀더 시간이 필요할것 같습니다.
다시한번 고멉다는 말씀드리며, 편한 저녁시간 보내십시요~
gpio를 제어하는 경우 커널이 유저 모드에 기본제공하는 2 가지 인터페이스가 있습니다.
1) 클래스 인터페이스(/sys/class)를 사용하는 방법으로 쉘등에서 편하게 사용할 수 있습니다.
2) 캐릭터 디바이스를 사용하는 방법으로 프로그래머가 ioctl API를 통해 제어할 수 있습니다.
감사합니다.
안녕하세요! 대학 졸업 후 BPS 인턴을 하며 처음 리눅스를 접한 초보자입니다..
리눅스를 배운지 2개월 정도가 지났습니다. gpio를 디바이스 트리를 이용하여 제어하라는 명을 받았습니다.
지금까지는 캐릭터 디바이스 드라이버로 모듈을 장착하여 제어했습니다.
디바이스 트리에 새로운 자식 노드를 만들어서 제어를 하려고 시도했습니다. 그 후 디바이스 드라이버와 연결해주려고 했는데 많은 코드들이 플랫폼 디바이스를 등록해서 사용하던구요.. 혼란이 와서 여기저기 떠돌다가 문c 블로그까지 오게 되었습니다. ( 많은 도움 받고 있습니다. 감사합니다. )
여기저기 보다가 내린 결론이 캐릭터 디바이스로는 디바이스 트리를 사용 못하고 플랫폼 디바이스가 필수적이다. 라는 결론이 나왔는데 올바른 생각인지 궁금합니다..
H/W 장치가 리눅스가 제공하는 다음과 같이 3가지 방법의 내장 코드 집합(서브 시스템)을 이용할 수 있습니다.
1) 네트워크 디바이스 드라이버
– 내 장치가 리눅스 커널에 있는 네트워크 관련 API 들과 스택을 사용하면 좋을 때
2) 블럭 디바이스 드라이버
– 내 장치가 리눅스 커널에 있는 mount 시스템 및 파일과 디렉토리등에 사용하는 권한 등을 동일하게 사용할 수 있을 때
3) 캐릭터 디바이스 드라이버
– 1)번과, 2)번에 해당하지 않는 기타 장치가 이 유형을 사용할 수 있습니다.
– 유저 application과 커널에서 운용중인 캐릭터 디바이스 드라이버와 ioctl() syscall로 소통하기가 편리하기에 사용합니다.
물론 드라이버가 위의 1)~3)번 모두 해당하지 않는 경우도 있습니다.
procfs, sysfs 및 클래스를 사용하는 “디바이스 드라이버 모델”로 구성할 수도 있고 위의 모델과 같이 사용할 수도 있습니다.
예를 들어 온도 센서의 경우 보통 1)번이나 2)번 형태와는 다르기 때문에 3)번으로 개발할 수도 있고, 아니면 “디바이스 드라이버 모델”만을 사용하여
드라이버를 개발할 수도 있으며, 3)번과 “디바이스 드라이버 모델”을 모두 적용하여 개발할 수도 있습니다.
– – – – –
버스에 대해서도 이야기해 보겠습니다.
각 장치가 연결된 버스의 종류에 따라 다음과 같이 분류할 수 있습니다. (무수히 많습니다)
a) PCI 디바이스 드라이버
b) I2C 디바이스 드라이버
c) SPI 디바이스 드라이버
d) USB 디바이스 드라이버
e) 플랫폼 디바이스 드라이버
f) …
어떤 네트워크 디바이스가 PCI 버스에 연결되어 제공될 수도 있고, USB 버스에 연결되어 제공할 수도 있고, 플랫폼 디바이스 드라이버에 연결되어 제공될 수도 있습니다.
결국 캐릭터 디바이스와 플랫폼 디바이스의 유형은 전혀 다릅니다.
GPIO 장치에 대해 설명을 드리면 GPIO가 SoC내부에서 제공하는 경우도 있고,
I2C 버스에 연결되어 사용하는 GPIO chip도 있습니다. 따라서 질문자가 사용하는 GPIO 하드웨어가 플랫폼 버스에 연결되는지를 확인해야 합니다.
이미 SoC 업체가 제공한 GPIO 드라이버가 플랫폼 버스를 사용했으면, 반드시 이에 따라야 합니다.
그런 후 application에서 제어를 하려할 때 캐릭터 디바이스 드라이버를 이용하여 제어를 할지 또는 별도의 sysfs 속성 파일을 만들어
제어를 할 지, 그것도 아니면 자동으로 만들어주는 gpio 클래스에서 제공하는 속성 파일을 이용할지 결정해야 합니다.
이것은 디바이스 트리를 사용하는 것과는 또 전혀 다른 내용입니다.
현재의 드라이버에 디바이스 트리를 사용하는 것은 드라이버를 조금 변경하면 할 수 있는 일입니다.
raspberry pi도 GPIO에 대해 처음엔 디바이스 트리가 없었던 시절도 있었고,
이를 수정하여 디바이스 트리가 적용되었습니다. 그런 사례를 찾아서 학습을 해보시는 것이 좋은것 같습니다.
마지막 최종 답변:
Q) 캐릭터 디바이스로는 디바이스 트리를 사용 못하고 플랫폼 디바이스가 필수적이다. 라는 결론이 나왔는데 올바른 생각인지 궁금합니다..
A) GPIO 디바이스 드라이버는 대부분 디바이스 트리를 지원하게 개발되었고, 캐릭터 디바이스 드라이버에서 사용하는 것도 전혀 상관 없습니다.
결국 질문자께서는 플랫폼 디바이스 드리이버 + GPIO 디바이스 드라이버 + 캐릭터 디바이스 드라이버 + 디바이스 트리 지원 형태로 개발하시면 됩니다.
최근 커널에서는 GPIO 드라이버들은 캐릭터 디바이스를 자동으로 지원합니다. 물론 별도의 custom 캐릭터 디바이스를 여러 개 만들어 붙여 사용하는 것도 문제 없습니다.
감사합니다.