Device & Driver -1- (Basic)

<kernel v4.14>

디바이스와 드라이버 -1-

리눅스 커널에서 디바이스 드라이버를 표현하자면 디바이스를 구동하는 프로그램이 디바이스 드라이버이다.  리눅스 커널의 디바이스 드라이버 모델에 따라 이들을 구분하여 표현할 때 디바이스 정보를 먼저 등록하고, 디바이스 core에서 hotplug 기반으로 id 매치되는 디바이스 드라이버를 probe하는 것으로 나누어 디바이스 드라이버를 가동하게 된다. 디바이스 드라이버를 가동(probe)할 때, 미리 등록된 디바이스 정보를 사용하여 디바이스 드라이버의 HW 조작을 위한 설정등을 할 수 있다. 다음과 같이 드라이버 모델에 따른 분류를 더 자세히 살펴보자.

  • 디바이스
    • 한국말로 그대로 장치라고한다. 장치에 대한 정보를 등록하면 나중에 디바이스 드라이버가 호출(probe)될 때 등록된 디바이스 정보를 사용할 수 있게된다.
    • 디바이스를 등록 시 리눅스 커널은 버스 및 클래스(option)로 분류한다.
      • bus
        • 버스 컨트롤러 또는 호스트 컨트롤러 기능이 있는 디바이스
        • 예) pci, i2c, usb, scsi, spi, acpi, mdio_bus, platform
      • class
        • scsi 디스크나 ata 디스크는 클래스 단계로 볼 때 동일한 디스크이다. 이렇게 디바이스를 추상화 시켜 상위 단계에서 보았을 때 분류하는 방법이다.
        • 예) input, block, dma, gpio, leds, net, phy, rtc, tty, watchdog, mem, bdi, …
  • 디바이스 드라이버
    • 디바이스 HW의 식별, 구동/해제 및 절전 기능 등에 대한 구현이 담긴 프로그램이다.
    • 디바이스 드라이버는 특정 디바이스 HW가 detect되어 hotplug 할 수 있도록 디바이스 부분과 디바이스 드라이버 부분이 분리되어있다.
    • 디바이스 드라이버가 HW 정보를 설정할 때 등록된 디바이스 정보를 알아와서 사용할 수 있다.
      • 레지스터, irq, dma 정보 등…

 

유저 스페이스를 위한 또 다른 디바이스 유형 분류

유저 스페이스에서 각 유형별 디바이스에 접근하기 위해 사용되는 분류는 다음과 같이 3가지가 있다. 이러한 디바이스 유형들은 별도의 글에서 자세히 알아보기로 한다.

  • char
    • 캐릭터 디바이스
  • block
    • 블럭 디바이스
  • net
    • 네트웍 디바이스

 

임베드된 device와 device_driver

디바이스를 정의하기 위해 device 구조체가 그대로 사용되는 경우는 많지 않으며 다음과 같이 보통 사용자 디바이스 구조체안에 임베드되어 사용된다.

struct foo_device {
        struct device;
        ...
}

 

다음 그림은 임베드된 device 및 device_driver 구조체를 보여준다.

 

다음 그림은 디바이스 드라이버가 호출되는 과정을 간략히 보여준다. 최근의 디바이스 드라이버 구현 부분은 hotplug 기반으로 probe되어 호출되는 구조로되어있다.

  • hotplug 인지를 할 수 없는 플랫폼 디바이스 드라이버일지라도 디바이스 드라이버 모델에 맞추어 probe 후크가 구현되어야 한다.

 

버스

대부분의 디바이스는 반드시 자신이 속한 버스(부모 버스)가 있다. 아래 그림을 보고 디바이스 수, 버스 컨트롤러 수를 확인해보자.

  • 참고로 버스 없이 사용되는 custom 디바이스 및 클래스 디바이스로만 등록되어 사용되는 디바이스 드라이버도 있다.
  • 주의: 버스 컨트롤러 자신도 디바이스이다.
    • 디바이스 수: 6개
    • 버스 컨트롤러 수: 2개
  • bus Controller B의 소속 버스는 bus A이다.
  • bus Controller A의 소속 버스는 아래 그림에 나와 있지 않지만 보통 platform, system, virtual 등 중 하나이다.

 

아래의 그림을 보면 다음과 같은 특징을 가지고 있음을 알 수 있다.

  • pci 고속(high speed) 버스에 i2c 저속(low speed) 버스가 연결되었다.
  • gpio 및 cpld 디바이스가 pci 버스에도 연결되어 있는 것이 있고, i2c 버스에 연결되어 있는 것도 있다.

 

device 구조체

include/linux/device.h

/**                                                                             
 * struct device - The basic device structure                                   
 * @parent: The device's "parent" device, the device to which it is attached.   
 *      In most cases, a parent device is some sort of bus or host              
 *      controller. If parent is NULL, the device, is a top-level device,       
 *      which is not usually what you want.                                     
 * @p:      Holds the private data of the driver core portions of the device.   
 *      See the comment of the struct device_private for detail.                
 * @kobj:   A top-level, abstract class from which other classes are derived.   
 * @init_name:  Initial name of the device.                                     
 * @type:   The type of device.                                                 
 *      This identifies the device type and carries type-specific               
 *      information.                                                            
 * @mutex:  Mutex to synchronize calls to its driver.                           
 * @bus:    Type of bus device is on.                                           
 * @driver: Which driver has allocated this                                     
 * @platform_data: Platform data specific to the device.                        
 *      Example: For devices on custom boards, as typical of embedded           
 *      and SOC based hardware, Linux often uses platform_data to point         
 *      to board-specific structures describing devices and how they            
 *      are wired.  That can include what ports are available, chip             
 *      variants, which GPIO pins act in what additional roles, and so          
 *      on.  This shrinks the "Board Support Packages" (BSPs) and               
 *      minimizes board-specific #ifdefs in drivers.                            
 * @driver_data: Private pointer for driver specific info.                      
 * @links:  Links to suppliers and consumers of this device.                    
 * @power:  For device power management.                                        
 *      See Documentation/driver-api/pm/devices.rst for details.                
 * @pm_domain:  Provide callbacks that are executed during system suspend,      
 *      hibernation, system resume and during runtime PM transitions            
 *      along with subsystem-level and driver-level callbacks.                  
 * @pins:   For device pin management.                                          
 *      See Documentation/driver-api/pinctl.rst for details.                    
 * @msi_list:   Hosts MSI descriptors                                           
 * @msi_domain: The generic MSI domain this device is using.                    
 * @numa_node:  NUMA node this device is close to.                              
 * @dma_ops:    DMA mapping operations for this device.                         
 * @dma_mask:   Dma mask (if dma'ble device).                                   
 * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all 
 *      hardware supports 64-bit addresses for consistent allocations           
 *      such descriptors.                                                       
 * @dma_pfn_offset: offset of DMA memory range relatively of RAM                
 * @dma_parms:  A low level driver may set these to teach IOMMU code about      
 *      segment limitations.                                                    
 * @dma_pools:  Dma pools (if dma'ble device).                                  
 * @dma_mem:    Internal for coherent mem override.                             
 * @cma_area:   Contiguous memory area for dma allocations                      
 * @archdata:   For arch-specific additions.                                    
 * @of_node:    Associated device tree node.                                    
 * @fwnode: Associated device node supplied by platform firmware.               
 * @devt:   For creating the sysfs "dev".                                       
 * @id:     device instance                                                     
 * @devres_lock: Spinlock to protect the resource of the device.                
 * @devres_head: The resources list of the device.                              
 * @knode_class: The node used to add the device to the class list.             
 * @class:  The class of the device.                                            
 * @groups: Optional attribute groups.                                     
 * @release:    Callback to free the device after all references have           
 *      gone away. This should be set by the allocator of the                   
 *      device (i.e. the bus driver that discovered the device).                
 * @iommu_group: IOMMU group the device belongs to.                             
 * @iommu_fwspec: IOMMU-specific properties supplied by firmware.               
 *                                                                              
 * @offline_disabled: If set, the device is permanently online.                 
 * @offline:    Set after successful invocation of bus type's .offline().       
 * @of_node_reused: Set if the device-tree node is shared with an ancestor      
 *              device.                                                         
 *                                                                              
 * At the lowest level, every device in a Linux system is represented by an     
 * instance of struct device. The device structure contains the information     
 * that the device model core needs to model the system. Most subsystems,       
 * however, track additional information about the devices they host. As a      
 * result, it is rare for devices to be represented by bare device structures;  
 * instead, that structure, like kobject structures, is usually embedded within 
 * a higher-level representation of the device.                                 
 */
struct device {                                                                 
    struct device       *parent;                                                
                                                                                
    struct device_private   *p;                                                 
                                                                                
    struct kobject kobj;                                                        
    const char      *init_name; /* initial name of the device */                
    const struct device_type *type;                                             
                                                                                
    struct mutex        mutex;  /* mutex to synchronize calls to                
                     * its driver.                                              
                     */                                                         
                                                                                
    struct bus_type *bus;       /* type of bus device is on */                  
    struct device_driver *driver;   /* which driver has allocated this          
                       device */                                                
    void        *platform_data; /* Platform specific data, device               
                       core doesn't touch it */                                 
    void        *driver_data;   /* Driver data, set and get with                
                       dev_set/get_drvdata */                                   
    struct dev_links_info   links;                                              
    struct dev_pm_info  power;                                                  
    struct dev_pm_domain    *pm_domain;                                         
                                                                                
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN                                            
    struct irq_domain   *msi_domain;                                            
#endif                                                                          
#ifdef CONFIG_PINCTRL                                                           
    struct dev_pin_info *pins;                                                  
#endif                                                                          
#ifdef CONFIG_GENERIC_MSI_IRQ                                                   
    struct list_head    msi_list;                                               
#endif                                                                          
                                                                                
#ifdef CONFIG_NUMA                                                              
    int     numa_node;  /* NUMA node this device is close to */                 
#endif                                                                          
    const struct dma_map_ops *dma_ops;                                          
    u64     *dma_mask;  /* dma mask (if dma'able device) */                     
    u64     coherent_dma_mask;/* Like dma_mask, but for                         
                         alloc_coherent mappings as                             
                         not all hardware supports                              
                         64 bit addresses for consistent                        
                         allocations such descriptors. */                       
    unsigned long   dma_pfn_offset;                                             
                                                                                
    struct device_dma_parameters *dma_parms;             

    struct list_head    dma_pools;  /* dma pools (if dma'ble) */                
                                                                                
    struct dma_coherent_mem *dma_mem; /* internal for coherent mem              
                         override */                                            
#ifdef CONFIG_DMA_CMA                                                           
    struct cma *cma_area;       /* contiguous memory area for dma               
                       allocations */                                           
#endif                                                                          
    /* arch specific additions */                                               
    struct dev_archdata archdata;                                               
                                                                                
    struct device_node  *of_node; /* associated device tree node */             
    struct fwnode_handle    *fwnode; /* firmware device node */                 
                                                                                
    dev_t           devt;   /* dev_t, creates the sysfs "dev" */                
    u32         id; /* device instance */                                       
                                                                                
    spinlock_t      devres_lock;                                                
    struct list_head    devres_head;                                            
                                                                                
    struct klist_node   knode_class;                                            
    struct class        *class;                                                 
    const struct attribute_group **groups;  /* optional groups */               
                                                                                
    void    (*release)(struct device *dev);                                     
    struct iommu_group  *iommu_group;                                           
    struct iommu_fwspec *iommu_fwspec;                                          
                                                                                
    bool            offline_disabled:1;                                         
    bool            offline:1;                                                  
    bool            of_node_reused:1;                                           
};
  • *parent
    • 부모 디바이스를 가리킨다.
  • *p
    • 디바이스 연관 관계를 위해 디바이스 코어가 관리한다.
  • kobj
    • 상속 관계에 대한 추상클래스 역할을 하는 kobject이다.
  • *init_name
    • 초기 이름
  • *type
    • 디바이스 타입(device_type)이 지정된다.
  • *mutex
    • 드라이버의 호출 동기화를 위해 사용한다.
  • *bus
    • 버스 디바이스인 경우 버스 타입(bus_type)이 지정된다.
  • *driver
    • 디바이스 드라이버(device_driver)가 지정된다.
  • *platform_data
    • 개별 플랫폼 디바이스 전용 데이터로 HW 연결 정보등을 드라이버에게 제공하기 위해 사용한다.
  • *driver_data
    • 개별 디바이스 드라이버 전용 데이터로 dev_set() 및 get_drvdat() 함수를 사용하여 설정 및 알아올 수 있다.
  • links
    • 디바이스의 supplier들과 consumer들에 대한 연결 정보(dev_links_info)가 지정된다.
  • power
    • 디바이스의 절전 관리
  • *pm_domain
    • 서브 시스템 레벨과 드라이버 레벨의 콜백을 통해 디바이스의 절전 관리를 수행하는 핸들러이다.
  • msi_list
    • 호스트의 msi 디스크립터 정보가 지정된다.
  • *msi_domain
    • MSI 방식을 사용하는 irq 도메인이 지정된다.
  • *pins
    • pin control 정보(dev_pin_info)가 지정된다.
  • numa_node
    • NUMA 시스템인 경우 디바이스가 붙어있는 노드 번호이다.
  • *dma_ops
    • dma를 사용하는 장치인 경우 dma 오페레이션이 지정된다.
  • *dma_mask
    • dma를 사용하는 장치인 경우 dma 마스크가 지정된다.
  • coherent_dma_mask
    • coherent dma를 지원하는 시스템에서 사용하는 coherent dma 마스크가 지정된다.
  • dma_pfn_offset
    • dma 페이지의 pfn offset이 지정된다.
    • 물리 메모리로부터 DMA 페이지가 위치한 offset이다.
  • *dma_parms
    • dma 파라메터가 지정된다.
  • dma_pools
    • dma 장치인 경우 사용하는 dma pool을 가리킨다.
  • *dma_mem
    • dma 장치가 사용하는 coherent 메모리를 가리킨다.
  • *cma_area
    • dma 장치가 사용할 커널의 연속된 메모리인 cma 영역을 가리킨다.
  • archdata
    • 아키텍처 추가 데이터
  • *of_node
    • 디바이스 트리에서 해당 디바이스 노드를 가리킨다.
  • *fwnode
    • 펌웨어 디바이스 노드 핸들러를 가리킨다.
  • devt
    • sysfs의 /dev를 생성 시 사용한다.
  • id
    • 디바이스 인스턴트가 담긴다.
    • -1은 인스턴스가 하나일 때, 복수개인 경우 0부터 시작
  • devres_lock
    • 디바이스 리소스의 조작 시 protect를 위해 사용할 spinlock이다.
  • devres_head
    • 디바이스 리소스들이 담길 리스트이다.
  • knode_class
    • 디바이스 클래스 분류 집합
  • *class
    • 디바이스 클래스를 지정한다.
  • **groups
    • 디바이스 속성 그룹(attribute_group)이 옵션으로 지정된다.
  • (*release)
    • 디바이스 참조가 0이 될 때 디바이스 해제 시 사용할 후크 함수가 지정된다.
  • *iommu_group
    • 디바이스가 소속될 iommu 그룹(iommu_group을 지정한다.
  • *iommu_fwspec
    • iommu용 펌웨어 핸들러(iommu_fwspec)가 지정된다.
  • offline_disabled:1
    • 디바이스가 영구적으로 online인 경우 설정된다.
  • offline:1
    • 버스 타입의 (*offline) 후크 함수의 호출 결과가 성공인 경우 1로 설정된다.
  • of_node_reused:1
    • 디바이스 트리노드가 상위 디바이스 트리노드와 공유되는 경우 1로 설정된다.

 

device_driver 구조체

include/linux/device.h

/**                                                                             
 * struct device_driver - The basic device driver structure                     
 * @name:   Name of the device driver.                                          
 * @bus:    The bus which the device of this driver belongs to.                 
 * @owner:  The module owner.                                                   
 * @mod_name:   Used for built-in modules.                                      
 * @suppress_bind_attrs: Disables bind/unbind via sysfs.                        
 * @probe_type: Type of the probe (synchronous or asynchronous) to use.         
 * @of_match_table: The open firmware table.                                    
 * @acpi_match_table: The ACPI match table.                                     
 * @probe:  Called to query the existence of a specific device,                 
 *      whether this driver can work with it, and bind the driver               
 *      to a specific device.                                                   
 * @remove: Called when the device is removed from the system to                
 *      unbind a device from this driver.                                       
 * @shutdown:   Called at shut-down time to quiesce the device.                 
 * @suspend:    Called to put the device to sleep mode. Usually to a            
 *      low power state.                                                        
 * @resume: Called to bring a device from sleep mode.                           
 * @groups: Default attributes that get created by the driver core              
 *      automatically.                                                          
 * @pm:     Power management operations of the device which matched             
 *      this driver.                                                            
 * @p:      Driver core's private data, no one other than the driver            
 *      core can touch this.                                                    
 *                                                                              
 * The device driver-model tracks all of the drivers known to the system.       
 * The main reason for this tracking is to enable the driver core to match      
 * up drivers with new devices. Once drivers are known objects within the       
 * system, however, a number of other things become possible. Device drivers    
 * can export information and configuration variables that are independent      
 * of any specific device.                                                      
 */
struct device_driver {                                                          
    const char      *name;                                                      
    struct bus_type     *bus;                                                   
                                                                                
    struct module       *owner;                                                 
    const char      *mod_name;  /* used for built-in modules */                 
                                                                                
    bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */            
    enum probe_type probe_type;                                                 
                                                                                
    const struct of_device_id   *of_match_table;                                
    const struct acpi_device_id *acpi_match_table;                              
                                                                                
    int (*probe) (struct device *dev);                                          
    int (*remove) (struct device *dev);                                         
    void (*shutdown) (struct device *dev);                                      
    int (*suspend) (struct device *dev, pm_message_t state);                    
    int (*resume) (struct device *dev);                                         
    const struct attribute_group **groups;                                      
                                                                                
    const struct dev_pm_ops *pm;                                                
                                                                                
    struct driver_private *p;                                                   
};
  • *name
    • 디바이스 드라이버명
  • *bus
    • 버스 타입(bus_type)이 지정된다.
  • *owner
    • 모듈 오너
  • *mod_name
    • 내장 모듈에서 사용하는 모듈명
  • suppress_bind_attrs
    • sysfs를 통한 bind/unbind를 사용하지 못하게할 때 1로 설정된다.
  • probe_type
    • probe 타입이 지정된다.
  • *of_match_table
    • 디바이스 트리 식별를 위한 매치 테이블이 지정된다.
  • *acpi_match_table
    • ACPI 식별을 위한 매치 테이블이 지정된다.
  • (*probe)
    • HW 디바이스 존재 유무를 판단하기 위해 호출되고 해당 디바이스를 사용하기 위해 디바이스 드라이버와 바인드한다.
  • (*remove)
    • 디바이스의 사용을 완료시킨 경우 호출된다.
  • (*shutdown)
    • 디바이스의 전원을 끄려고할 때 호출된다.
  • (*suspend)
    • 디바이스가 절전 모드로 진입할 때 호출된다.
  • (*resume)
    • 디바이스가 절전 모드로부터 정상 모드로 돌아올 때 호출된다.
  • **groups
    • 디바이스 속성 그룹(attribute_group)이 옵션으로 지정된다.
  • *pm
    • 드라이버 레벨의 콜백을 통해 디바이스의 절전 관리를 수행하는 핸들러이다.
  • *p
    • 드라이버의 하이라키 관계 정보가 관리된다.
    • 이 정보는 드라이버 코어가 업데이트하고 관리한다.

 

디바이스 등록

다음 API를 사용하여 디바이스를 요청한 버스에 등록할 때 버스 타입에 따라 리스트로 관리된다. 등록된 디바이스 정보들은 추후 디바이스 드라이버와의 매치 여부를 판단할 때 사용한다.

  • int device_register(struct device * dev);

 

디바이스가 등록될 때 다음 매크로 함수를 사용하여 디바이스 속성을 추가할 수 있다. 속성에 대해서는 조금 후에 자세히 알아본다.

  • #define DEVICE_ATTR(name,mode,show,store)

 

device_register()

drivers/base/core.c

/**                                                                             
 * device_register - register a device with the system.                         
 * @dev: pointer to the device structure                                        
 *                                                                              
 * This happens in two clean steps - initialize the device                      
 * and add it to the system. The two steps can be called                        
 * separately, but this is the easiest and most common.                         
 * I.e. you should only call the two helpers separately if                      
 * have a clearly defined need to use and refcount the device                   
 * before it is added to the hierarchy.                                         
 *                                                                              
 * For more information, see the kerneldoc for device_initialize()              
 * and device_add().                                                            
 *                                                                              
 * NOTE: _Never_ directly free @dev after calling this function, even           
 * if it returned an error! Always use put_device() to give up the              
 * reference initialized in this function instead.                              
 */
int device_register(struct device *dev)                                         
{                                                                               
    device_initialize(dev);                                                     
    return device_add(dev);                                                     
}                                                                               
EXPORT_SYMBOL_GPL(device_register);

요청한 디바이스를 추가한다.

  • 코드 라인 3에서 인자로 받은 디바이스 구조체를 초기화한다.
  • 코드 라인 4에서 인자로 받은 디바이스를 초기화한다.

 

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);

인자로 받은 디바이스 구조체를 초기화한다. (kobject, dma pools, mutex, resource, pm, node, msi_list, links)

 

device_add()

drivers/base/core.c -1/3-

/**                                                                             
 * device_add - add device to device hierarchy.                                 
 * @dev: device.                                                                
 *                                                                              
 * This is part 2 of device_register(), though may be called                    
 * separately _iff_ device_initialize() has been called separately.             
 *                                                                              
 * This adds @dev to the kobject hierarchy via kobject_add(), adds it           
 * to the global and sibling lists for the device, then                         
 * adds it to the other relevant subsystems of the driver model.                
 *                                                                              
 * Do not call this routine or device_register() more than once for             
 * any device structure.  The driver model core is not designed to work         
 * with devices that get unregistered and then spring back to life.             
 * (Among other things, it's very hard to guarantee that all references         
 * to the previous incarnation of @dev have been dropped.)  Allocate            
 * and register a fresh new struct device instead.                              
 *                                                                              
 * NOTE: _Never_ directly free @dev after calling this function, even           
 * if it returned an error! Always use put_device() to give up your             
 * reference instead.                                                           
 */
int device_add(struct device *dev)                                              
{                                                                               
    struct device *parent;                                                      
    struct kobject *kobj;                                                       
    struct class_interface *class_intf;                                         
    int error = -EINVAL;                                                        
    struct kobject *glue_dir = NULL;                                            
                                                                                
    dev = get_device(dev);                                                      
    if (!dev)                                                                   
        goto done;                                                              
                                                                                
    if (!dev->p) {                                                              
        error = device_private_init(dev);                                       
        if (error)                                                              
            goto done;                                                          
    }                                                                           
                                                                                
    /*                                                                          
     * for statically allocated devices, which should all be converted          
     * some day, we need to initialize the name. We prevent reading back        
     * the name, and force the use of dev_name()                                
     */                                                                         
    if (dev->init_name) {                                                       
        dev_set_name(dev, "%s", dev->init_name);                                
        dev->init_name = NULL;                                                  
    }                                                                           
                                                                                
    /* subsystems can specify simple device enumeration */                      
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)                       
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);                 
                                                                                
    if (!dev_name(dev)) {                                                       
        error = -EINVAL;                                                        
        goto name_error;                                                        
    }                                                                           
                                                                                
    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

디바이스 하이라키에 디바이스를 추가한다. (sysfs에 연동된다)

  • 코드 라인 9~11에서 디바이스 참조 카운터를 1 증가시킨다.
  • 코드 라인 13~17에서 디바이스에 device_private 구조체가 할당되어 있지 않은 경우 할당한다.
  • 코드 라인 24~27에서 디바이스의 초기 이름(init_name)이 주어진 경우 이를 사용하여 디바이스 명(kobject의 이름)을 지정한다. 그런 후 init_name은 null로 변경한다.
  • 코드 라인 30~31에서 디바이스 명이 주어지지 않지만 버스 이름이 있는 경우 디바이스명으로 버스 이름을 사용한다.
  • 코드 라인 33~36에서 여전히 디바이스명이 없는 경우 에러로 함수를 빠져나간다.

 

drivers/base/core.c -2/3-

.   parent = get_device(dev->parent);                                           
    kobj = get_device_parent(dev, parent);                                      
    if (kobj)                                                                   
        dev->kobj.parent = kobj;                                                
                                                                                
    /* use parent numa_node */                                                  
    if (parent && (dev_to_node(dev) == NUMA_NO_NODE))                           
        set_dev_node(dev, dev_to_node(parent));                                 
                                                                                
    /* first, register with generic layer. */                                   
    /* we require the name to be set before, and pass NULL */                   
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);                    
    if (error) {                                                                
        glue_dir = get_glue_dir(dev);                                           
        goto Error;                                                             
    }                                                                           
                                                                                
    /* notify platform of device entry */                                       
    if (platform_notify)                                                        
        platform_notify(dev);                                                   
                                                                                
    error = device_create_file(dev, &dev_attr_uevent);                          
    if (error)                                                                  
        goto attrError;                                                         
                                                                                
    error = device_add_class_symlinks(dev);                                     
    if (error)                                                                  
        goto SymlinkError;                                                      
    error = device_add_attrs(dev);                                              
    if (error)                                                                  
        goto AttrsError;                                                        
    error = bus_add_device(dev);                                                
    if (error)                                                                  
        goto BusError;                                                          
    error = dpm_sysfs_add(dev);                                                 
    if (error)                                                                  
        goto DPMError;                                                          
    device_pm_add(dev);                                                         
                                                                                
    if (MAJOR(dev->devt)) {                                                     
        error = device_create_file(dev, &dev_attr_dev);                         
        if (error)                                                              
            goto DevAttrError;                                                  
                                                                                
        error = device_create_sys_dev_entry(dev);                               
        if (error)                                                              
            goto SysEntryError;                                                 
                                                                                
        devtmpfs_create_node(dev);                                              
    }
  • 코드 라인 1~4에서 부모 디바이스의 참조 카운터를 1 증가시켜 알아오고 현재 디바이스의 부모를 지정한다.
  • 코드 라인 7~8에서 부모 디바이스가 존재하고 현재 디바이스가 모든 노드의 사용이 가능하면 현재 디바이스가 부모 디바이스의 노드번호를 사용하도록 한다.
  • 코드 라인 12~16에서 현재 디바이스를 부모 디바이스에 추가한다.
  • 코드 라인 19~20에서 플랫폼 notify 후크 함수가 지정된 경우 호출한다.
    • of_platform_notify() 또는 acpi_platform_notify() 함수가 사용된다.
  • 코드 라인 22~24에서 uevent 속성에 대한 파일을 생성하고 속성 핸들러를 연동한다.
    • 속성과 관련하여 연동되는 함수는 다음과 같다.
      • uevent_show()
      • uevent_store()
    • kset과 관련하여 연동되는 함수는 다음과 같다. 이들 함수들은 devices_init() 함수를 통해 “/sys/devices” 디렉토리에 연동된다.
      • dev_uevent_filter()
      • dev_uevent_name()
      • dev_uevent()
  • 코드 라인 26~28에서 요청한 디바이스에 대해 of_node, subsystem 및 device 심볼 링크를 생성한다. 부모 디바이스에서는 <요청한 디바이스명>으로 심볼 링크를 생성한다.
  • 코드 라인 29~31에서 디바이스, 디바이스 타입, 클래스에 등록된 속성 그룹들을 추가하여 sysfs에 반영한다. 디바이스의 hot-removal이 지원되는 경우 디바이스에 online 속성도 추가하여 sysfs에 반영한다.
  • 코드 라인 32~34에서 버스에 추가될 디바이스인 경우 디바이스에 버스 타입 속성들을 추가한다. 이들은 그대로 sysfs에 반영된다. 그리고 디바이스와 버스 타입을 서로 심볼 링크로 연결한다
  • 코드 라인 35~37에서 디바이스 디렉토리에 pm 디렉토리와 pm 관련 속성 그룹들을 머지(merge)한 후 추가한다.
  • 코드 라인 38에서 dpm_list에 현재 디바이스를 추가한다.
    • 부모 디바이스가 pm core에 의해 관리되면 부모 디바이스가 슬립하면 안된다는 경고 메시지를 출력한다.
  • 코드 라인 40~50에서 “/sys/dev”에 등록되는 디바이스의 경우 디바이스 디렉토리에 dev 속성을 생성한다. 그리고 “/sys/dev/<dev type>” 디렉토리에 <major>:<minor> 번호로 심볼 링크를 만들어 디바이스와 연결한다.
    • 예) /sys/devices/platform/soc/66090000.watchdog/misc/watchdog/dev
    • 예) /sys/dev/char/10:130 -> /sys/devices/platform/soc/66090000.watchdog/misc/watchdog

 

drivers/base/core.c -3/3-

    /* Notify clients of device addition.  This call must come                  
     * after dpm_sysfs_add() and before kobject_uevent().                       
     */                                                                         
    if (dev->bus)                                                               
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,                
                         BUS_NOTIFY_ADD_DEVICE, dev);                           
                                                                                
    kobject_uevent(&dev->kobj, KOBJ_ADD);                                       
    bus_probe_device(dev);                                                      
    if (parent)                                                                 
        klist_add_tail(&dev->p->knode_parent,                                   
                   &parent->p->klist_children);                                 
                                                                                
    if (dev->class) {                                                           
        mutex_lock(&dev->class->p->mutex);                                      
        /* tie the class to the device */                                       
        klist_add_tail(&dev->knode_class,                                       
                   &dev->class->p->klist_devices);                              
                                                                                
        /* notify any interfaces that the device is here */                     
        list_for_each_entry(class_intf,                                         
                    &dev->class->p->interfaces, node)                           
            if (class_intf->add_dev)                                            
                class_intf->add_dev(dev, class_intf);                           
        mutex_unlock(&dev->class->p->mutex);                                    
    }                                                                           
done:                                                                           
    put_device(dev);                                                            
    return error;                                                               
 SysEntryError:                                                                 
    if (MAJOR(dev->devt))                                                       
        device_remove_file(dev, &dev_attr_dev);                                 
 DevAttrError:                                                                  
    device_pm_remove(dev);                                                      
    dpm_sysfs_remove(dev);                                                      
 DPMError:                                                                      
    bus_remove_device(dev);                                                     
 BusError:                                                                      
    device_remove_attrs(dev);                                                   
 AttrsError:                                                                    
    device_remove_class_symlinks(dev);                                          
 SymlinkError:                                                                  
    device_remove_file(dev, &dev_attr_uevent);                                  
 attrError:                                                                     
    kobject_uevent(&dev->kobj, KOBJ_REMOVE);                                    
    glue_dir = get_glue_dir(dev);                                               
    kobject_del(&dev->kobj);                                                    
 Error:                                                                         
    cleanup_glue_dir(dev, glue_dir);                                            
    put_device(parent);                                                         
name_error:                                                                     
    kfree(dev->p);                                                              
    dev->p = NULL;                                                              
    goto done;                                                                  
}                                                                               
EXPORT_SYMBOL_GPL(device_add);
  • 코드 라인 4~6에서 버스에 디바이스가 추가된 경우 버스가 알도록 bus notifier 호출 체인에 등록된 함수를 호출한다.
  • 코드 라인 8에서 userspace에서 알 수 있도록 uevent를 발생시킨다.
  • 코드 라인 9에서 버스에 연결될 autoprobe가 가능한 디바이스의 경우 곧바로 디바이스 드라이버의 probe 함수를 호출한다.
  • 코드 라인 10~12에서 부모 디바이스가 있는 경우 현재 디바이스를 children으로 등록한다.
  • 코드 라인 14~18에서 클래스가 있는 디바이스의 경우 클래스의 소속되도록 리스트에 디바이스를 추가하고, 클래스 인터페이스의 후크함수 (*add_dev)를 호출한다.

 

디바이스 하이라키 관리 정보

device_private 구조체

drivers/base/base.h

/**
 * struct device_private - structure to hold the private to the driver core portions of the device structure.
 *
 * @klist_children - klist containing all children of this device
 * @knode_parent - node in sibling list
 * @knode_driver - node in driver list
 * @knode_bus - node in bus list
 * @deferred_probe - entry in deferred_probe_list which is used to retry the
 *      binding of drivers which were unable to get all the resources needed by
 *      the device; typically because it depends on another driver getting
 *      probed first.
 * @device - pointer back to the struct device that this structure is
 * associated with.
 *
 * Nothing outside of the driver core should ever touch these fields.
 */
struct device_private {
        struct klist klist_children;
        struct klist_node knode_parent;
        struct klist_node knode_driver;
        struct klist_node knode_bus;
        struct list_head deferred_probe;
        struct device *device;
};
  • klist_children
    • child 디바이스가 추가될 리스트
  • knode_parent
    • child 디바이스가 위의 리스트에 추가시킬 때 사용하는 노드
  • knode_driver
    • 이 디바이스가 드라이버의 디바이스 리스트에 포함될 때 사용되는 노드
      • driver->p->klist_devices
  • knode_bus
    • 이 디바이스가 버스의 디바이스 리스트에 포함될 때 사용되는 노드
      • bus_type->p->klist_devices
  • deferred_probe
    • 전역 deferred probe 리스트에 추가시킬 때 사용되는 노드
      • 전역 deferred_probe_pending_list
  • *device
    • 자신 디바이스를 가리키는 포인터

 

속성(Attribute) & 속성 그룹(Attribute Group)

 

다음 그림과 같이 속성과 속성 그룹간의 관계를 4 단계로 표현해보았다.

 

attribute 구조체

include/linux/sysfs.h

struct attribute {                                                              
    const char      *name;                                                      
    umode_t         mode;                                                       
#ifdef CONFIG_DEBUG_LOCK_ALLOC                                                  
    bool            ignore_lockdep:1;                                           
    struct lock_class_key   *key;                                               
    struct lock_class_key   skey;                                               
#endif                                                                          
};

속성은 attribute 구조체를 사용하여 표현된다. 이 구조체는 여러 가지 타입의 속성 구조체에 임베드되는 기본 골격이다.

  • *name
    • 속성명
  • mode
    • 파일 권한(permission)
      • 예) 0664 = -rw-rw-r–

 

다양한 속성 타입과 속성 정의 매크로

위의 attribute 구조체를 임베드하고 (*show) 및 (*store) 두 가지 후크를 추가하여 다음과 같이 4개의 속성 구조체가 사용된다.

  • device_attribute 구조체
  • bus_attribute 구조체
  • class_attribute 구조체
  • driver_attribute 구조체

 

4가지 구조체 소스를 확인해보면 (*show) 및 (*store) 후크의 인자만 조금씩 다른 것을 확인할 수 있다.

device_attribute()
struct device_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                        char *buf);
        ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                         const char *buf, size_t count);
};

디바이스 속성을 생성하는 경우 /sys/devices/foo에 속성들이 생성된다. 속성 값을 보거나 변경하기 위해 device_attribute 구조체가 지원하는 후크 함수의 기능은 다음과 같다.

  • (*show)
    • 디바이스 속성을 출력
  • (*store)
    • 디바이스 속성을 변경

 

bus_attribute()
struct bus_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct bus_type *bus, char *buf);
        ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};

버스 속성을 생성하는 경우 /sys/bus/foo에 속성들이 생성된다.

 

class_attribute()
struct class_attribute {
        struct attribute attr;
        ssize_t (*show)(struct class *class, struct class_attribute *attr,
                        char *buf);
        ssize_t (*store)(struct class *class, struct class_attribute *attr,
                        const char *buf, size_t count);
};

클래스 속성을 생성하는 경우 /sys/class/foo에 속성들이 생성된다.

 

driver_attribute()
struct driver_attribute {
        struct attribute attr;
        ssize_t (*show)(struct device_driver *driver, char *buf);
        ssize_t (*store)(struct device_driver *driver, const char *buf,
                         size_t count);
};

드라이버 속성을 생성하는 경우 /sys/bus/drivers/foo에 속성들이 생성된다.

 

각 속성 타입별로 속성 구조체를 심플하게 작성해주는 매크로들이 준비되어 있다.

device_attribute 구조체 정의 매크로
#define DEVICE_ATTR(_name, _mode, _show, _store) \
        struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DEVICE_ATTR_RW(_name) \
        struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
#define DEVICE_ATTR_RO(_name) \
        struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \
        struct device_attribute dev_attr_##_name = __ATTR_WO(_name)

 

예) 다음과 같이 디바이스 속성을 하나 정의할 때 생성되는 구조체와 연결된 후크 함수들을 알아본다.

static DEVICE_ATTR_RW(a1);

 

생성되는 구조체 함수는 다음과 같다. a1_show() 함수와 a1_store() 함수는 사용자가 직접 추가 구현해야 한다.

struct device_attribute dev_attr_a1 = {
	.attr = {
		.name = "a1",
		.mode = 644
	}
	.show = a1_show,
	.store = a1_store,
}

 

bus_attribute 구조체 정의 매크로
#define BUS_ATTR(_name, _mode, _show, _store)   \
        struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define BUS_ATTR_RW(_name) \
        struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)
#define BUS_ATTR_RO(_name) \
        struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)

 

class_attribute 구조체 정의 매크로
#define CLASS_ATTR_RW(_name) \
        struct class_attribute class_attr_##_name = __ATTR_RW(_name)
#define CLASS_ATTR_RO(_name) \
        struct class_attribute class_attr_##_name = __ATTR_RO(_name)
#define CLASS_ATTR_WO(_name) \
        struct class_attribute class_attr_##_name = __ATTR_WO(_name)

 

driver_attribute 구조체 정의 매크로
#define DRIVER_ATTR_RW(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)

 

각 속성 구조체의 멤버 설정

위의 4가지 매크로에서 공통으로 사용되며, 구조체의 멤버들에 값을 부여하는 매크로이다.

__ATTR()

include/linux/sysfs.h

#define __ATTR(_name, _mode, _show, _store) {               \                   
    .attr = {.name = __stringify(_name),                \                       
         .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },     \                       
    .show   = _show,                        \                                   
    .store  = _store,                       \                                   
}

__ATTR() 매크로에서 사용하는 VERIFY_OCTAL_PERMISSIONS() 매크로는 컴파일 타임에 파일 권한 값이 다음의 조건을 만족하는지 체크한다.

  • 0000 ~ 0777 범위 이내
  • USER_READABLE >= GROUP_READABLE >= OTHER_READABLE
    • 0444 (ok)
    • 0440 (ok)
    • 0400 (ok)
    • 0000 (ok)
    • 0404 (X)
    • 0004 (X)
    • 0040 (X)
    • 가장 많이 실수하는 값은 예전에는 허용했던 0666 값이 최근에는 OTHER_WRITABLE 때문에 문제가 발생한다. 이를 0644 또는 664등으로 수정해야 한다.
  • USER_WRITABLE >= GROUP_WRITABLE
    • 0220 (ok)
    • 0200 (ok)
    • 0000 (ok)
    • 0020 (X)
    • OTHER_WRITABLE 안됨!
      • 0XX0 (ok)
      • 0XX2 (X)

 

__ATTR_WO()
#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO),             \
                         _name##_show, _name##_store)

파일 권한 값으로 0644를 사용한다.

  • user, group, other 모두 읽기 전용

 

__ATTR_RO()
#define __ATTR_RO(_name) { \
.attr = { .name = __stringify(_name), .mode = S_IRUGO }, \
.show = _name##_show, \
}

파일 권한 값으로 0444를 사용한다.

  • user, group, other 모두 읽기 전용

 

__ATTR_WO()
#define __ATTR_WO(_name) { \
.attr = { .name = __stringify(_name), .mode = S_IWUSR }, \
.store = _name##_store, \
}

파일 권한 값으로 0200을 사용한다.

  • user만 쓰기 전용
  • group, other는 금지

 

속성 그룹 및 속성 그룹들

속성 그룹들을 정의하기 위해 다음 단계와 같이 준비한다.

  • 첫 번째, 다수의 속성을 갖는 속성들을 정의한다.
  • 두 번째 ATTRIBUTE_GROUPS()를 사용하여 다음과 같이 2개의 구조체를 생성한다.
    • 하나의 속성 그룹을 만들고 지정한 속성들을 포함시킨다.
    • 속성 그룹들을 만들고 속성 그룹을 포함시킨다.

 

첫 번째, 다수의 속성이 포함된 속성들 정의

다음과 같이 foo 디바이스를 위해 속성들을 정의한다.

static struct attribute *foo_attrs[] = {
        &dev_attr_a1.attr,
        &dev_attr_a2.attr,
        NULL,
};

 

두 번째, 속성 그룹들 정의

이어서 foo 디바이스를 위해 위에서 정의한 속성들을 포함하는 속성 그룹과 속성 그룹들을 정의한다.

ATTRIBUTE_GROUPS(foo);

 

ATTRIBUTE_GROUPS() 매크로 함수
#define ATTRIBUTE_GROUPS(_name)                                 \
static const struct attribute_group _name##_group = {           \
        .attrs = _name##_attrs,                                 \
};                                                              \
__ATTRIBUTE_GROUPS(_name)

 

__ATTRIBUTE_GROUPS() 매크로 함수
#define __ATTRIBUTE_GROUPS(_name)                               \
static const struct attribute_group *_name##_groups[] = {       \
        &_name##_group,                                         \
        NULL,                                                   \
}

 

위의 매크로를 사용하여 생성되는 두 개의 구조체는 다음과 같다.

  • a1과 a2 속성이 foo 속성 그룹이 생성된다.
  • foo 속성 그룹이 담긴 foo 속성 그룹들이 생성된다.
static const struct attribute_group foo_group = {
	.attrs = foo_attrs,
}

static const struct attribute_group *foo_groups[] = {
        &foo_group,
        NULL,
}

 

다음 그림은 플랫폼 디바이스로 등록한 디바이스 속성들을 보여준다.

 

다음 그림에서 foo 디바이스에 대한 디바이스 속성 및 속성 그룹들을 알아보자.

 

속성 그룹들 동적 추가

device_add_attrs()

drivers/base/core.c

static int device_add_attrs(struct device *dev)
{
        struct class *class = dev->class;
        const struct device_type *type = dev->type;
        int error;

        if (class) {
                error = device_add_groups(dev, class->dev_groups);
                if (error)
                        return error;
        }

        if (type) {
                error = device_add_groups(dev, type->groups);
                if (error)
                        goto err_remove_class_groups;
        }

        error = device_add_groups(dev, dev->groups);
        if (error)
                goto err_remove_type_groups;

        if (device_supports_offline(dev) && !dev->offline_disabled) {
                error = device_create_file(dev, &dev_attr_online);
                if (error)
                        goto err_remove_dev_groups;
        }

        return 0;

 err_remove_dev_groups:
        device_remove_groups(dev, dev->groups);
 err_remove_type_groups:
        if (type)
                device_remove_groups(dev, type->groups);
 err_remove_class_groups:
        if (class)
                device_remove_groups(dev, class->dev_groups);

        return error;
}

디바이스, 디바이스 타입, 클래스에 등록된 속성 그룹들을 추가하여 sysfs에 반영한다. 디바이스의 hot-removal이 지원되는 경우 디바이스에 online 속성도 추가하여 sysfs에 반영한다.

  • 코드 라인 7~11에서 디바이스의 클래스에 소속된 그룹들 속성을 추가하여 sysfs에 반영한다.
  • 코드 라인 13~17에서 디바이스의 타입에 소속된 그룹들 속성을 추가하여 sysfs에 반영한다.
  • 코드 라인 19~21에서 디바이스에 소속된 그룹들 속성을 추가하여 sysfs에 반영한다.
  • 코드 라인 23~27에서 디바이스의 hot-removal을 지원하고 현재 offline 기능을 동작하게 할 수 있는 경우 디바이스에 online 속성도 추가하여 sysfs에 반영한다.

 

device_add_groups()

drivers/base/core.c

int device_add_groups(struct device *dev, const struct attribute_group **groups)
{
        return sysfs_create_groups(&dev->kobj, groups);
}
EXPORT_SYMBOL_GPL(device_add_groups);

속성이 담겨있는는 그룹들을 디바이스에 추가하여 sysfs에 반영한다.

 

디바이스 등록 시 생성되는 속성들

아래와 같이 디바이스가 등록되면 디바이스 공통 또는 디바이스 타입, 클래스 타입 및 버스 타입 등에 따라 속성들이 각각 추가된다.

  • 예) gpio 컨트롤러 디바이스에 gpio 0번이 등록된 경우

 

아래 그림을 보면 gpio 디바이스, gpio 컨트롤러 디바이스, gpio 포트 디바이스들이 생성된 경우 아래와 같은 속성들이 등록되는 것을 보여준다.

 

sysfs 반영

device_create_file()

drivers/base/core.c

/**
 * device_create_file - create sysfs attribute file for device.
 * @dev: device.
 * @attr: device attribute descriptor.
 */
int device_create_file(struct device *dev,
                       const struct device_attribute *attr)
{
        int error = 0;

        if (dev) {
                WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
                        "Attribute %s: write permission without 'store'\n",
                        attr->attr.name);
                WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
                        "Attribute %s: read permission without 'show'\n",
                        attr->attr.name);
                error = sysfs_create_file(&dev->kobj, &attr->attr);
        }

        return error;
}
EXPORT_SYMBOL_GPL(device_create_file);

sysfs에 디바이스명에 해당하는 디렉토리와 속성들을 생성한다. 만일 속성에 해당하는 (*store) 및 (*show) 후크 핸들러가 없는 경우 경고 메시지가 출력된다.

 

다음 그림은 foo 디바이스에 준비된 a1 속성이 추가되는 모습을 보여준다.

  • 참고로 대부분의 디바이스 드라이버를 등록할 때 개별 디바이스에 대한 custom 속성들은 대부분 보이지 않을 것이다. 보통 디바이스 타입,  버스 타입 및 클래스 타입에 등록되어 있는 속성들을 사용하므로 개별 드라이버의 custom 속성을 사용할 일이 거의 없다.

 

device_add_class_symlinks()

drivers/base/core.c

static int device_add_class_symlinks(struct device *dev)
{
        struct device_node *of_node = dev_of_node(dev);
        int error;

        if (of_node) {
                error = sysfs_create_link(&dev->kobj, &of_node->kobj,"of_node");
                if (error)
                        dev_warn(dev, "Error %d creating of_node link\n",error);
                /* An error here doesn't warrant bringing down the device */
        }

        if (!dev->class)
                return 0;

        error = sysfs_create_link(&dev->kobj,
                                  &dev->class->p->subsys.kobj,
                                  "subsystem");
        if (error)
                goto out_devnode;

        if (dev->parent && device_is_not_partition(dev)) {
                error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
                                          "device");
                if (error)
                        goto out_subsys;
        }

#ifdef CONFIG_BLOCK
        /* /sys/block has directories and does not need symlinks */
        if (sysfs_deprecated && dev->class == &block_class)
                return 0;
#endif

        /* link in the class directory pointing to the device */
        error = sysfs_create_link(&dev->class->p->subsys.kobj,
                                  &dev->kobj, dev_name(dev));
        if (error)
                goto out_device;

        return 0;

out_device:
        sysfs_remove_link(&dev->kobj, "device");

out_subsys:
        sysfs_remove_link(&dev->kobj, "subsystem");
out_devnode:
        sysfs_remove_link(&dev->kobj, "of_node");
        return error;
}

요청한 디바이스에 대해 of_node, subsystem 및 device 심볼 링크를 생성한다. 부모 디바이스에서는 <요청한 디바이스명>으로 심볼 링크를 생성한다.

  • 코드 라인 6~14에서 디바이스가 있는 디렉토리에서 디바이스 트리 노드로 “of_node” 파일명으로 심볼 링크를 연결한다.
    • 예) /sys/devices/platform/soc/foo/foo0/of_node -> /sys/firmware/devicetree/base/soc/foo/foo0@660a0000
  • 코드 라인 16~20에서 디바이스가 있는 디렉토리에서 클래스의 타입이 있는 디렉토리를 향하도록 “subsystem” 파일명으로 심볼 링크를 연결한다.
    • 예) /sys/devices/platform/soc/foo/foo0/subsystem -> /sys/class/foo타입
  • 코드 라인 22~27에서 디바이스가 파티션 타입이 아닌 경우 부모 디바이스가 있는 디렉토리로 연동되도록 “device” 파일명으로 심볼 링크를 연결한다.
    • 예) /sys/devices/platform/soc/foo/foo0/device -> /sys/devices/platform/soc/foo/foo0
  • 코드 라인 29~33에서 deprecated된 block 클래스 디바이스인 경우 성공(0) 결과로 그냥 함수를 빠져나간다.
    • CONFIG_SYSFS_DEPRECATED 또는 CONFIG_SYSFS_DEPRECATED_V2 커널 옵션을 사용하면 “/dev/block”을 사용하지 않게 한다.
  • 코드 라인 36~39에서 부모 디바이스에 해당하는 디렉토리에 현재 디바이스 디렉토리를 향하도록 <디바이스명> 파일명으로 심볼 링크를 연결한다.
    • 예) /sys/class/foo타입/foo0 -> /sys/devices/platform/soc/foo/foo0

 

다음 그림은 foo0디바이스와 클래스 및 디바이스에 해당하는 디바이스 트리 노드를 심볼 링크로 연결한 모습을 보여준다.

 

uevent 속성

커널에서 유저 application(udev)에게 디바이스의 hotplug 등의 변경 정보를 전달하기 위해 uevent 속성을 사용한다.

 

드라이버

동작 유형이 비슷한 디바이스들은 벤더에서 드라이버 코드를 작성할 때 하나의 드라이버에서 동작시킨다. 따라서 드라이버는 1개 이상의 디바이스들을 대상으로 동작한다.

 

driver_register()

drivers/base/driver.c

/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
        int ret;
        struct device_driver *other;

        BUG_ON(!drv->bus->p);

        if ((drv->bus->probe && drv->probe) ||
            (drv->bus->remove && drv->remove) ||
            (drv->bus->shutdown && drv->shutdown))
                printk(KERN_WARNING "Driver '%s' needs updating - please use "
                        "bus_type methods\n", drv->name);

        other = driver_find(drv->name, drv->bus);
        if (other) {
                printk(KERN_ERR "Error: Driver '%s' is already registered, "
                        "aborting...\n", drv->name);
                return -EBUSY;
        }

        ret = bus_add_driver(drv);
        if (ret)
                return ret;
        ret = driver_add_groups(drv, drv->groups);
        if (ret) {
                bus_remove_driver(drv);
                return ret;
        }
        kobject_uevent(&drv->p->kobj, KOBJ_ADD);

        return ret;
}
EXPORT_SYMBOL_GPL(driver_register);

드라이버를 등록한다.

  • 코드 라인 16~20에서 드라이버 또는 버스에 probe, remove 또는 shutdown 후크 함수가 이미 구현된 경우 경고 메시지를 출력한다.
  • 코드 라인 22~27에서 버스에서 인자로 요청한 드라이버에 해당하는 이름으로 검색하여 발견되는 경우 드라이버가 이미 등록되었다는 에러 메시지를 출력하고 -EBUSY 에러를 반환한다.
  • 코드 라인 29~31에서 버스에 드라이버를 등록한다.
    • sysfs의 드라이버 디렉토리에 관련 속성들을 생성한다.
    • sysfs의 모듈 디렉토리에 관련 파라메터들을 생성한다.
  • 코드 라인 32~36에서 sysfs의 드라이버 디렉토리와 드라이버에 해당하는 속성들을 생성한다.
    • /sys/bus/<bus_name>/drivers/foo 디렉토리
  • 코드 라인 37에서 sysfs 드라이버 디렉토리에 uevent 속성도 추가한다.

 

아래 그림은 드라이버를 등록하는 과정에서  sysfs에 생성되는 디렉토리와 속성 파일들을 보여준다.

 

드라이버 하이라키 관리 정보

driver_private 구조체

drivers/base/base.h

struct driver_private {
        struct kobject kobj;
        struct klist klist_devices;
        struct klist_node knode_bus;
        struct module_kobject *mkobj;
        struct device_driver *driver;
};
  • kobj
    • 드라이버를 관리하는 kobject
  • klist_devices
    • 드라이버에 속한 디바이스들
  • knode_bus
    • 소속 버스 klist에 포함될 때 사용되는 klist_node
  • *mkobj
    • module_kobject를 가리킨다.
  • *driver
    • 자신 드라이버를 가리키는 포인터

 

드라이버 관련 API들

  • driver_for_each_device()
    • 드라이버에 소속된 디바이스드을 순회한다.
  • driver_find_device()
    • 드라이버에서 start 디바이스부터 시작하여 매치된 디바이스를 검색한다.
  • driver_create_file()
    • 드라이버 디렉토리를 생성하고 그 디렉토리 내부에 드라이버 속성 파일을 생성한다.
  • driver_remove_file()
    • 드라이버 디렉토리를 삭제한다.
  • driver_unregister()
    • 드라이버를 할당 해제한다.
  • driver_find()
    • 버스에서 요청 드라이버명으로 드라이버를 검색한다.

 

참고

 

15 thoughts to “Device & Driver -1- (Basic)”

  1. 안녕하세요 질문이 있습니다. 위에 모든 디바이스는 반드시 자신이 속한 버스가 있다고 말씀하셨는데 이게 커널이 bus, class로 분류한 bus 디바이스들만 해당되는건지 class 디바이스들도 같이 해당되는 건지 궁금합니다.

    1. gpio도 pci버스에 속하는 걸 보면 bus 디바이스 class 디바이스 상관없이 모든 디바이스는 버스에 속한다고 보면 되는건가요?

    2. 안녕하세요? 문c 블로그의 문영일입니다.

      제 글 중 오해의 소지가 있는 문장이므로 고쳐야겠습니다.

      실제 HW 디바이스들은 대부분 자신이 속한 버스가 있고, 클래스로도 등록될 수 있습니다.
      그러나 버스 없이 클래스 디바이스로만 동작할 수도 있습니다.
      또한 고유한 디바이스 드라이버를 제작하는 경우에는 버스 지정 없이 사용할 수도 있습니다.

      감사합니다.

  2. sysfs반영을 하는 이유가 무엇인가요?? device_add_class_symlinks()함수로 4개의 심볼링크로 연결했는데 왜 이런구성을 하는지 모르겠습니다.. sysfs_create_link()함수가 심볼링크를 연결하는 것 같은데.. 함수 내부는 이해안되서 넘어갔는데 결과적으로 왜 저렇게 연결하는지 이유를 알 수 있을까요??

  3. 안녕하세요.
    device driver 관련 해서 문의 드립니다~!
    char 디바이스 드라이버와 platform 디바이스 드라이버에서
    char 디바이스 드라이버는 fileoperation에서 ioctl을 제공해서 제가 원하는 기능을 application에서 불러서 사용 할 수 있는데요
    platform 디바이스 드라이버는 어떻게 ioctl같은 기능을 구현해서 사용 할 수 있는지 궁금합니다.
    misc driver를 등록해서 ioctl을 만들어서 사용해야 하는건가요? 아니면 sysfs로 구현해서 사용하나요~~
    궁금합니다~~~

    1. 안녕하세요?

      결론을 말씀드리면 캐릭터 디바이스로 만들어도 되고,
      디바이스 드라이버 모델을 따르는 플랫폼 디바이스 드라이버로 만들어도 됩니다.

      캐릭터 디바이스의 장점은 ioctl()을 통한 사용자 인터페이스를 제공하므로
      프로그래머에게 유리한 방법입니다.

      디바이스 드라이버 모델의 장점은 sysfs를 통한 사용자 인터페이스를 제공하므로
      프로그램 없이 bash shell 상태에서 접근이 쉽습니다.

      필요하면 두 가지를 모두 섞어 구현해도 됩니다.
      예를 들어 gpio 서브 시스템은 gpio 디바이스로 등록하면 sysfs로 제공되는
      사용자 인터페이스와 캐릭터 디바이스 드라이버 양쪽을 모두 지원합니다.

      다음 URL을 통해 디바이스 드라이버 모델에 대한 샘플 코드를 참고하시기 바랍니다.
      (설명은 자세히 되어 있지 않음으로 디바이스 드라이버 모델을 공부하면서 보시면 유리합니다)

      https://github.com/jakeisname/moon_c

      감사합니다.

  4. 안녕하세요. 내용이 이해가 잘 안되서 질문드립니다.
    Device를 추가 할 경우, 해당 device를 bus_create_file, class_create_file, device_create_file, driver_create_file를 통해 sysfs에 등록해야 하나요?
    bus, class, device, driver의 관계가 이해가 잘 안되네요.

    1. 안녕하세요?

      디바이스 드라이버는 sysfs에 등록해도 되고 안해도 됩니다.
      등록한다는 말은 특정 속성을 sysfs를 통해 사용자에게 노출하겠다는 뜻입니다. (편리 사항)

      bus, class, device, driver의 관계를 sysfs 입장에서 설명드리면 각각은 다음과 같습니다.

      • bus는 /sys/bus < - 이 버스를 사용하는 모든 디바이스들에 대해 이 버스용 속성을 제공
      • class는 /sys/class < - 이 클래스를 사용하는 모든 디바이스들에 대해 이 클래스용 속성을 제공
      • device는 /sys/devices < - 이 디바이스 전용의 속성을 제공
      • driver는 /sys/devices/../driver < - 이 드라이버 전용의 속성을 제공

      예) i2c 장치를 개발한 분이 전송한 횟수를 출력하는 속성 파일을 개발하고 sysfs에 등록하고 싶을 때 (cat nr_tx)

      • i2c 버스 쪽에 이 속성을 추가하면 i2c 버스를 사용하는 모든 i2c 장치들이 이 명령을 공유할 수 있습니다.
      • i2c device 쪽에 이 속성을 추가하면 해당 i2c 디바이스 들은 이 명령을 공유할 수 있습니다.
      • i2c driver 쪽에 이 속성을 추가하면 해당 코드를 사용하는 i2c 디바이스 시리즈들은 이 명령을 공유할 수 있습니다.

      감사합니다.

      1. 안녕하세요. 만약에 어떠한 속성도 노출하고 싶지 않는다면 bus,class,device,driver를 등록하지 않으면 되는것인가요?
        말씀해주신대로 bus,class,device,driver는 속성을 공유(노출)할때 사용하기 위한 옵션/기능 이라고 생각하면 될까요?
        감사합니다.

        1. 맞습니다.
          sysfs에 노출 시킬 속성이 없으면 등록하지 않아도 됩니다.
          (bus_create_file, class_create_file, device_create_file, driver_create_file …)

          다만 커널이 제공하는 표준 디바이스 등록 명령어(device_register, driver_register, class_register, …)를 사용하는 경우,
          디바이스 종류에 따라 몇 가지 기본적인 속성(uevent, driver_override, modalias)들은 자동으로 등록됩니다.

          감사합니다.

          1. 디바이스 추가 시에 device_register와 driver_register는 함께 호출이 되어야 하는건가요? 커널 코드나 커널 모듈를 구현한 예제들을 보면 driver_register는 호출하나 device_register를 호출하는 경우를 못 봐서요… device_register는 버스에 해당 디바이스를 추가하는 것으로 이해하고 있는데요. device_register를 호출 안 하게 되면 bus에 등록이 안 되는거 아닌가요..
            위에 그림에서도 보면 1번 동작으로 device_register 후에 2번에 driver_register를 수행하는 것처럼 보여서요.
            또한 그림에서 1번 호출 시에는 foo1이었는데 driver_register 호출 후에는 foo2로 표현이 되어 있는데 특별한 이유가 있을까요?

            1. 디바이스와 드라이버는 둘 다 등록이 되어야 매치되어 수행이됩니다.

              예를 들어 i2c 버스를 담당하는 i2c 디바이스 및 드라이버 코드가 있습니다. 이들은 i2c 칩을 개발한 제조사가 만들었습니다.
              그리고 사용자는 이 i2c 버스를 사용하는 온도계 디바이스를 사용하고자, 온도계 드라이버 코드를 작성하였습니다.
              i2c 입장에서는 i2c 클리이언트이자 온도계로 사용됩니다.

              사용자의 온도계 드라이버 코드에는 온도를 측정하고 표시하는 루틴이 있다고 가정합니다.
              이 드라이버 코드는 처음 모듈이 동작할 때 i2c 버스에 driver를 등록해야 합니다.
              이 때 module_i2c_driver(), i2c_add_driver() 또는 i2c_register_driver() 함수를 사용합니다.

              위에서 온도계 드라이버가 i2c 드라이버로 등록될 때 i2c 디바이스 정보를 매치시켜 동작시키기 위해 i2c_driver 구조체의 id_table 멤버를 사용하여 디바이스 정보를 제공합니다. 그러면 i2c 드라이버를 등록하는 과정에서 커널 내부에서 id 테이블과 비교하여 매치되면 자동으로 i2c 디바이스로 등록시키고, 그 후 사용자의 probe 함수가 호출됩니다.

              디바이스를 등록하는 방법도 은 매우 다양합니다.
              – 코드에서 테이블을 제공해 매치되는 디바이스를 커널이 등록하게 할 수도 있고,
              – 코드에서 사용자가 직접 디바이스를 등록 할 수도 있고,
              – 디바이스 트리를 통해 커널이 등록하게 할 수 있고,
              – 서버의 경우 커널이 ACPI 펌웨어를 읽어 등록할 수 있습니다.

              I2C의 디바이스 및 드라이버의 등록 과정을 간단히 말씀드렸는데(참고: http://jake.dothome.co.kr/i2c-2/),
              각 버스마다 조금씩 다르게 구현되고, 방법도 매우 다양합니다.
              그러니깐 디바이스 드라이버 관련 책자들이 수백페이지를 넘겨도 그걸로는 부족하죠 ^^;

              디바이스 드라이버에 관심이 많으시면 두 가지 방법으로 공부를 해는 것이 좋습니다.
              1) 커널에 있는 디바이스 드라이버 코드를 쉬운 것 부터 분석해봅니다.
              2) 디바이스 드라이버 관련 커널 코드를 게속 분석해나갑니다. 이 때에도 쉬운 것 부터 해야 합니다.

              foo1과 foo2에 큰 의미없습니다. 둘 다 foo1이라고 해도 상관없습니다.

              1. 만약 device_register 를 호출하지 않게 되면 어떤 문제가 있을까요? 찾아본 예제들 중 driver_register만 수행하고 device_register를 수행 하지 않는 경우가 있는데요.

                1. 대부분이 그런 코드일 것입니다.
                  디바이스 정보를 이미 커널이 얻어내(id 테이블, 디바이스 트리) 사용자가 모르게 대신 등록을 해준 상황입니다.

문영일에게 댓글 남기기 댓글 취소

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