Device & Driver -3- (Platform Device)

<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 셋업 코드를 사용하지 않고 디바이스 트리가 제공하는 정보를 파싱하여 각 성격에 맞는 디바이스로 등록된다. 참고로 이 과정에서 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
    • 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 단계의 스텝을 수행하였다.

  1. eraly 플랫폼 디바이스 데이터 등록
  2. 커널 커멘드 라인 파싱
  3. 해당 클래스로의 early 플랫픔 드라이버 설치
  4. early 플랫폼 드라이버 등록
  5. 해당 클래스에서 early 플랫폼 드라이버의 probing
  6. 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

 

    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를 사용한 모듈 방식으로 모든 디바이스 및 드라이버의 진입부에 사용된다.
  • 플랫폼 드라이버용 진입부
    • builtin_platform_driver(foo_driver)
      • 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 플랫폼 드라이버 진입부에 사용된다.
      • 커널에 임베드하지 않고 모듈로 빌드한 경우 module_platform_driver()와 동일하게 동작한다.
    • module_platform_driver(foo_driver)
      • insmod를 사용한 모듈 방식으로 플랫폼 드라이버용 진입부에 사용된다.
  • 플랫폼 디바이스 생성 및 플랫폼 드라이버용 진입부
    • builtin_platform_driver_probe(foo_driver, foo_probe)
      • 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 플랫폼 디바이스를 생성하면서 플랫폼 드라이버 진입에 사용된다.
      • 커널에 임베드하지 않고 모듈로 빌드한 경우 module_platform_driver_probe()와 동일하게 동작한다.
    • module_platform_driver_probe(foo_driver, foo_probe)
      • insmod를 사용한 모듈 방식으로 플랫폼 디바이스를 생성하면서 플랫폼 드라이버 진입에 사용된다.

 

플랫폼 드라이버를 등록하기 위해 다음을 준비한다.

  • 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()
    • 드라이버를 임베드한 플랫폼 드라이버를 반환한다.

 

참고

 

 

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다