devtmpfs & kdevtmpfs 스레드

 

devtmpfs

 

기존 커널이 사용했던 devfs를 대신하여 커널 2.6에 도입된 udev 데몬을 통해 디바이스 드라이버의 로딩이 이루어진다. udev 데몬이 유저 프로세스에서 동작하므로 커널 부팅 후 루트 파일시스템을 마운트한 후에야 디바이스 드라이버의 로딩이 가능해진다. 대부분의 디바이스 드라이버들은 커널 부트업 과정에서 로딩이 이루어지므로 루트 파일 시스템을 로딩하기 전에 initrd/initramfs 등에 존재하는 디바이스 드라이버를 임시 파일 시스템인 devtmpfs에 로딩시켜 부팅 시간을 단축할 목적으로 사용한다.

 

다음 그림과 같이 루트 파일 시스템을 마운트하기 전에 디바이스 드라이버들이 devtmpfs에 로딩되어 부팅 시간을 단축시키는 과정을 보여준다.

 

devtmpfs_init()

drivers/base/devtmpfs.c

/*                                                                              
 * Create devtmpfs instance, driver-core devices will add their device          
 * nodes here.                                                                  
 */                                                                             
int __init devtmpfs_init(void)                                                  
{                                                                               
    int err = register_filesystem(&dev_fs_type);                                
    if (err) {                                                                  
        printk(KERN_ERR "devtmpfs: unable to register devtmpfs "                
               "type %i\n", err);                                               
        return err;                                                             
    }                                                                           
                                                                                
    thread = kthread_run(devtmpfsd, &err, "kdevtmpfs");                         
    if (!IS_ERR(thread)) {                                                      
        wait_for_completion(&setup_done);                                       
    } else {                                                                    
        err = PTR_ERR(thread);                                                  
        thread = NULL;                                                          
    }                                                                           
                                                                                
    if (err) {                                                                  
        printk(KERN_ERR "devtmpfs: unable to create devtmpfs %i\n", err);       
        unregister_filesystem(&dev_fs_type);                                    
        return err;                                                             
    }                                                                           
                                                                                
    printk(KERN_INFO "devtmpfs: initialized\n");                                
    return 0;                                                                   
}

다음 devtmpfs라는 이름을 가진 파일 시스템을 등록한다. 그런 후 “kdevtmpfs” 스레드를 동작시킨다.

static struct file_system_type dev_fs_type = {                                  
    .name = "devtmpfs",                                                         
    .mount = dev_mount,                                                         
    .kill_sb = kill_litter_super,                                               
};

 

kdevtmpfs 스레드

다음 그림은 디바이스 드라이버들의 로딩/언로딩 요청에 대해 devtmpfs에 디바이스 노드를 생성 및 삭제하는 과정을 보여준다.

 

devtmpfsd()

drivers/base/devtmpfs.c

static int devtmpfsd(void *p)                                                   
{                                                                               
    char options[] = "mode=0755";                                               
    int *err = p;                                                               
    *err = sys_unshare(CLONE_NEWNS);                                            
    if (*err)                                                                   
        goto out;                                                               
    *err = sys_mount("devtmpfs", "/", "devtmpfs", MS_SILENT, options);          
    if (*err)                                                                   
        goto out;                                                               
    sys_chdir("/.."); /* will traverse into overmounted root */                 
    sys_chroot(".");                                                            
    complete(&setup_done);                                                      
    while (1) {                                                                 
        spin_lock(&req_lock);                                                   
        while (requests) {                                                      
            struct req *req = requests;                                         
            requests = NULL;                                                    
            spin_unlock(&req_lock);                                             
            while (req) {                                                       
                struct req *next = req->next;                                   
                req->err = handle(req->name, req->mode,                         
                          req->uid, req->gid, req->dev);                        
                complete(&req->done);                                           
                req = next;                                                     
            }                                                                   
            spin_lock(&req_lock);                                               
        }                                                                       
        __set_current_state(TASK_INTERRUPTIBLE);                                
        spin_unlock(&req_lock);                                                 
        schedule();                                                             
    }                                                                           
    return 0;                                                                   
out:                                                                            
    complete(&setup_done);                                                      
    return *err;                                                                
}

kdevtmpfs 스레드가 처음 동작하고 무한 루프를 돌며 디바이스 노드 생성 및 삭제 요청을 처리하는 함수이다.

  • 코드 라인 5~7에서 현재 동작중인 파일 시스템 정보를 복사하여 새 스레드에서 사용할 수 있게 한다.
  • 코드 라인 8~10에서 “devtmpfs” 파일 시스템을 마운트한다.
  • 코드 라인 11~13에서 루트 디렉토리로 이동시킨 후 setup_done 변수에 complete 신호를 보내 싱크 대기중인 부모 태스크를 동작하게 한다.
  • 코드 라인 14~33에서 반복 루프를 돌며 요청이 있는 경우 요청에 대한 처리를 수행한다.

 

handle()

base/devtmpfs.c

static int handle(const char *name, umode_t mode, kuid_t uid, kgid_t gid,       
          struct device *dev)                                                   
{                                                                               
    if (mode)                                                                   
        return handle_create(name, mode, uid, gid, dev);                        
    else                                                                        
        return handle_remove(name, dev);                                        
}

mode에 따라 핸들 생성 및 삭제 함수를 호출한다.

 

handle_create()

drivers/base/devtmpfs.c

static int handle_create(const char *nodename, umode_t mode, kuid_t uid,        
             kgid_t gid, struct device *dev)                                    
{                                                                               
    struct dentry *dentry;                                                      
    struct path path;                                                           
    int err;                                                                    
                                                                                
    dentry = kern_path_create(AT_FDCWD, nodename, &path, 0);                    
    if (dentry == ERR_PTR(-ENOENT)) {                                           
        create_path(nodename);                                                  
        dentry = kern_path_create(AT_FDCWD, nodename, &path, 0);                
    }                                                                           
    if (IS_ERR(dentry))                                                         
        return PTR_ERR(dentry);                                                 
                                                                                
    err = vfs_mknod(d_inode(path.dentry), dentry, mode, dev->devt);             
    if (!err) {                                                                 
        struct iattr newattrs;                                                  
                                                                                
        newattrs.ia_mode = mode;                                                
        newattrs.ia_uid = uid;                                                  
        newattrs.ia_gid = gid;                                                  
        newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;                        
        inode_lock(d_inode(dentry));                                            
        notify_change(dentry, &newattrs, NULL);                                 
        inode_unlock(d_inode(dentry));                                          
                                                                                
        /* mark as kernel-created inode */                                      
        d_inode(dentry)->i_private = &thread;                                   
    }                                                                           
    done_path_create(&path, dentry);                                            
    return err;                                                                 
}

디바이스 파일을 생성하고 디바이스 노드를 생성한다.

 

handle_remove()

drivers/base/devtmpfs.c

static int handle_remove(const char *nodename, struct device *dev)              
{                                                                               
    struct path parent;                                                         
    struct dentry *dentry;                                                      
    int deleted = 0;                                                            
    int err;                                                                    
                                                                                
    dentry = kern_path_locked(nodename, &parent);                               
    if (IS_ERR(dentry))                                                         
        return PTR_ERR(dentry);                                                 
                                                                                
    if (d_really_is_positive(dentry)) {                                         
        struct kstat stat;                                                      
        struct path p = {.mnt = parent.mnt, .dentry = dentry};                  
        err = vfs_getattr(&p, &stat, STATX_TYPE | STATX_MODE,                   
                  AT_STATX_SYNC_AS_STAT);                                       
        if (!err && dev_mynode(dev, d_inode(dentry), &stat)) {                  
            struct iattr newattrs;                                              
            /*                                                                  
             * before unlinking this node, reset permissions                    
             * of possible references like hardlinks                            
             */                                                                 
            newattrs.ia_uid = GLOBAL_ROOT_UID;                                  
            newattrs.ia_gid = GLOBAL_ROOT_GID;                                  
            newattrs.ia_mode = stat.mode & ~0777;                               
            newattrs.ia_valid =                                                 
                ATTR_UID|ATTR_GID|ATTR_MODE;                                    
            inode_lock(d_inode(dentry));                                        
            notify_change(dentry, &newattrs, NULL);                             
            inode_unlock(d_inode(dentry));                                      
            err = vfs_unlink(d_inode(parent.dentry), dentry, NULL);             
            if (!err || err == -ENOENT)                                         
                deleted = 1;                                                    
        }                                                                       
    } else {                                                                    
        err = -ENOENT;                                                          
    }                                                                           
    dput(dentry);                                                               
    inode_unlock(d_inode(parent.dentry));                                       
                                                                                
    path_put(&parent);                                                          
    if (deleted && strchr(nodename, '/'))                                       
        delete_path(nodename);                                                  
    return err;                                                                 
}

디바이스 노드를 삭제하고, 디바이스 파일도 삭제한다.

 

참고

 

driver_init()

 

리눅스 디바이스 드라이버 모델의 초기화

 

리눅스 디바이스 드라이버 모델을 표현하기위해 kobjectkset등이 사용된다.

  • sysfs는 kobject 및 kset 등으로 이루어진 하이라키 관계를 sysfs 가상 파일 시스템을 통해 표현되고 관리된다.

 

다음 그림은 디바이스 드라이버 모델의 초기화 호출 과정을 보여준다.

 

driver_init()

drivers/base/init.c

/**                                                                             
 * driver_init - initialize driver model.                                       
 *                                                                              
 * Call the driver model init functions to initialize their                     
 * subsystems. Called early from init/main.c.                                   
 */                                                                             
void __init driver_init(void)                                                   
{                                                                               
    /* These are the core pieces */                                             
    devtmpfs_init();                                                            
    devices_init();                                                             
    buses_init();                                                               
    classes_init();                                                             
    firmware_init();                                                            
    hypervisor_init();                                                          
                                                                                
    /* These are also core pieces, but must come after the                      
     * core core pieces.                                                        
     */                                                                         
    platform_bus_init();                                                        
    cpu_dev_init();                                                             
    memory_dev_init();                                                          
    container_dev_init();                                                       
    of_core_init();                                                             
}

리눅스 커널이 사용하는 드라이버 모델의 초기화를 수행한다.

  • 코드 라인 10에서 devtmpfs 파일 시스템을 마운트하고 kdevtmpfs 스레드를 생성하고 동작시킨다.
  • 코드 라인 11에서 디바이스를 표현 및 관리할 “/sys/devices” 및 “/sys/dev” 디렉토리를 생성한다. 또한 캐릭터 디바이스를 표현 및 관리할 “/sys/dev/block” 디렉토리와 블럭 디바이스를 표현 및 관리할 “/sys/dev/char” 디렉토리를 생성한다.
  • 코드 라인 12에서 버스 디바이스를 표현 및 관리할 “/sys/bus” 디렉토리를 생성한다. 또한 서브 시스템들이 등록될 “/sys/devices/system” 디렉토리도 생성한다.
  • 코드 라인 13에서 클래스 디바이스를 표현 및 관리할 “/sys/class” 디렉토리를 생성한다.
  • 코드 라인 14에서 펌웨어를 표현 및 관리할 “/sys/firmware” 디렉토리를 생성한다.
  • 코드 라인 15에서 하이퍼바이저를 표현 및 관리할 “/sys/hypervisor” 디렉토리를 생성한다.
  • 코드 라인 20에서 플랫폼 디바이스를 표현 및 관리할 “/sys/devices/platform” 디렉토리와 “/sys/bus/platform” 디렉토리를 생성한다.
  • 코드 라인 21에서 cpu 서브시스템의 cpu들을 표현 및 관리할  “/sys/devices/system/cpu” 디렉토리와 그 아래에 속성 파일들을 생성한다.
  • 코드 라인 22에서 hotplug 허용된 시스템에서 memory 서브시스템의 memory 블럭들을 표현 및 관리할  “/sys/devices/system/memory” 디렉토리와 그 아래에 속성 파일들을 생성한다.
  • 코드 라인 23에서 컨테이너 서브시스템의 컨테이너들을 표현 및 관리할 “/sys/devices/system/container” 디렉토리를 생성한다.
  • 코드 라인 24에서 디바이스 트리를 표현 및 관리할 “/sys/firmware/devicetree” 디렉토리를 생성하고, 그 이하에 모든 노드에 대한 디렉토리를 만든다. 그리고 각 노드의 속성들도 해당 노드 디렉토리에 각각 파일로 추가한다.

 

devices_init()

drivers/base/core.c

int __init devices_init(void)                                                   
{                                                                               
    devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);    
    if (!devices_kset)                                                          
        return -ENOMEM;                                                         
    dev_kobj = kobject_create_and_add("dev", NULL);                             
    if (!dev_kobj)                                                              
        goto dev_kobj_err;                                                      
    sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);           
    if (!sysfs_dev_block_kobj)                                                  
        goto block_kobj_err;                                                    
    sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);             
    if (!sysfs_dev_char_kobj)                                                   
        goto char_kobj_err;                                                     
                                                                                
    return 0;                                                                   
                                                                                
 char_kobj_err:                                                                 
    kobject_put(sysfs_dev_block_kobj);                                          
 block_kobj_err:                                                                
    kobject_put(dev_kobj);                                                      
 dev_kobj_err:                                                                  
    kset_unregister(devices_kset);                                              
    return -ENOMEM;                                                             
}

디바이스를 표현 및 관리할 “/sys/devices” 및 “/sys/dev” 디렉토리를 생성한다. 또한 캐릭터 디바이스를 표현 및 관리할 “/sys/dev/block” 디렉토리와 블럭 디바이스를 표현 및 관리할 “/sys/dev/char” 디렉토리를 생성한다.

  • 코드 라인 1~3에서 “devices” 이름을 갖는 kset를 생성하고 sysfs 루트에 추가한다.
  • 코드 라인 4~6에서 “dev” 이름을 갖는 kobject를 생성하고 sysfs 루트에 생성한다.
  • 코드 라인 7~9에서 “block” 이름을 갖는 kobject를 생성하고 “/sys/dev” 뒤에 추가한다.
  • 코드 라인 10~12에서 “char” 이름을 갖는 kobject를 생성하고 “/sys/dev” 뒤에 추가한다.

 

buses_init()

drivers/base/bus.c

int __init buses_init(void)                                                     
{                                                                               
    bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);               
    if (!bus_kset)                                                              
        return -ENOMEM;                                                         
                                                                                
    system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);     
    if (!system_kset)                                                           
        return -ENOMEM;                                                         
                                                                                
    return 0;                                                                   
}

버스 디바이스를 표현 및 관리할 “/sys/bus” 디렉토리를 생성한다. 또한 서브 시스템들이 등록될 “/sys/devices/system” 디렉토리도 생성한다.

  • 코드 라인 3~5에서 “bus” 이름을 갖는 kset를 생성하고 sysfs 루트에 추가한다.
  • 코드 라인 7~9에서 “system” 이름을 갖는 kset를 생성하고 “/sys/devices” 뒤에 추가한다.

 

classes_init()

drivers/base/class.c

int __init classes_init(void)                                                   
{                                                                               
    class_kset = kset_create_and_add("class", NULL, NULL);                      
    if (!class_kset)                                                            
        return -ENOMEM;                                                         
    return 0;                                                                   
}

클래스 디바이스를 표현 및 관리할 “/sys/class” 디렉토리를 생성한다.

  • 코드 라인 3~5에서 “class” 이름을 갖는 kset를 생성하고 sysfs 루트에 추가한다.

 

firmware_init()

drivers/base/firmware.c

int __init firmware_init(void)                                                  
{                                                                               
    firmware_kobj = kobject_create_and_add("firmware", NULL);                   
    if (!firmware_kobj)                                                         
        return -ENOMEM;                                                         
    return 0;                                                                   
}

펌웨어를 표현 및 관리할 “/sys/firmware” 디렉토리를 생성한다.

  • 코드 라인 3~5에서 “firmware” 이름을 갖는 kobject를 생성하고 sysfs 루트에 추가한다.

 

 

hypervisor_init()

drivers/base/hypervisor.c

int __init hypervisor_init(void)                                                
{                                                                               
    hypervisor_kobj = kobject_create_and_add("hypervisor", NULL);               
    if (!hypervisor_kobj)                                                       
        return -ENOMEM;                                                         
    return 0;                                                                   
}

하이퍼바이저를 표현 및 관리할 “/sys/hypervisor” 디렉토리를 생성한다.

  • 코드 라인 3~5에서 “hypervisor” 이름을 갖는 kobject를 생성하고 sysfs 루트에 추가한다.

 

platform_bus_init()

drivers/base/platform.c

int __init platform_bus_init(void)                                              
{                                                                               
    int error;                                                                  
                                                                                
    early_platform_cleanup();                                                   
                                                                                
    error = device_register(&platform_bus);                                     
    if (error)                                                                  
        return error;                                                           
    error =  bus_register(&platform_bus_type);                                  
    if (error)                                                                  
        device_unregister(&platform_bus);                                       
    of_platform_register_reconfig_notifier();                                   
    return error;                                                               
}

플랫폼 디바이스를 표현 및 관리할 “/sys/devices/platform” 디렉토리와 “/sys/bus/platform” 디렉토리를 생성한다.

  • 코드 라인 5에서 early 플랫폼 디바이스를 리스트에서 제거한다.
  • 코드 라인 7~9에서 플랫폼 버스 디바이스를 등록한다.
    • 플랫폼 버스 디바이스를 표현하고 관리할 “/sys/devices/platform” 디렉토리를 생성한다.
  • 코드 라인 10~12에서 플랫폼 버스를 등록한다.
    • 플랫폼 버스를 표현하고 관리할 “/sys/bus/platform” 디렉토리를 생성한다.
  • 코드 라인 13에서 디바이스 트리를 통해 플랫폼 디바이스가 추가/삭제될 때 마다 호출되도록 notifier block에 of_platform_notify() 함수를 등록한다.

 

등록될 디바이스

struct device platform_bus = {                                                  
    .init_name  = "platform",                                                   
};                                                                              
EXPORT_SYMBOL_GPL(platform_bus);

 

등록될 버스

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

 

예) 다음은 platform 버스 디바이스 아래에 등록된 플랫폼 디바이스들이다.

$ ls /sys/devices/platform -la
drwxr-xr-x    3 root     root             0 May 16 15:00 20020000.pcie
drwxr-xr-x    3 root     root             0 Mar 15 09:55 50020000.pcie
drwxr-xr-x    3 root     root             0 May 16 15:00 60c00000.pcie
drwxr-xr-x    3 root     root             0 May 28 16:17 Fixed MDIO bus.0
drwxr-xr-x    2 root     root             0 May 28 16:17 alarmtimer
drwxr-xr-x    2 root     root             0 May 28 16:17 pmu
drwxr-xr-x    2 root     root             0 May 28 16:17 psci
drwxr-xr-x    2 root     root             0 May 28 16:17 secure_rtc
drwxr-xr-x    2 root     root             0 May 28 16:17 serial8250
drwxr-xr-x   70 root     root             0 Mar 30 15:41 soc
drwxr-xr-x    2 root     root             0 May 28 16:17 timer
-rw-r--r--    1 root     root          4096 May 28 16:17 uevent

 

early_platform_cleanup()

drivers/base/platform.c

/**                                                                             
 * early_platform_cleanup - clean up early platform code                        
 */                                                                             
void __init early_platform_cleanup(void)                                        
{                                                                               
    struct platform_device *pd, *pd2;                                           
                                                                                
    /* clean up the devres list used to chain devices */                        
    list_for_each_entry_safe(pd, pd2, &early_platform_device_list,              
                 dev.devres_head) {                                             
        list_del(&pd->dev.devres_head);                                         
        memset(&pd->dev.devres_head, 0, sizeof(pd->dev.devres_head));           
    }                                                                           
}

등록된 early 플랫폼 디바이스들을 리스트에서 제거한다.

  • early_platform_add_devices() 함수로 추가한 플랫폼 디바이스이다.
  • arm64에서는 early 플랫폼 디바이스를 등록하지 않는다.
  • 디바이스 트리를 통해 플랫폼 디바이스가 추가/삭제될 때 마다 of_platform_notify() 후크 함수가 호출되도록 등록한다.

 

of_platform_register_reconfig_notifier()

drivers/of/platform.c

void of_platform_register_reconfig_notifier(void)                               
{                                                                               
    WARN_ON(of_reconfig_notifier_register(&platform_of_notifier));              
}

디바이스 트리를 통해 플랫폼 디바이스가 추가/삭제될 때 마다 호출되도록 notifier block에 of_platform_notify() 함수를 등록한다.

  • CONFIG_OF_DYNAMIC 및 CONFIG_OF_ADDRESS 두 커널 옵션이 사용되어야 동작한다.
  • 참고: Platform Device | 문c

 

cpu_dev_init()

drivers/base/cpu.c

void __init cpu_dev_init(void)                                                  
{                                                                               
    if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups))              
        panic("Failed to register CPU subsystem");                              
                                                                                
    cpu_dev_register_generic();                                                 
    cpu_register_vulnerabilities();                                             
}

cpu 서브시스템의 cpu들을 표현 및 관리할  “/sys/devices/system/cpu” 디렉토리와 그 아래에 속성 파일들을 생성한다.

  • 코드 라인 3~4에서 cpu 서브 시스템을 등록하고 그 밑에 속성들을 추가한다.
    • “/sys/devices/system/cpu” 디렉토리를 만들고 그 밑에 다음 속성들을 추가한다.
      • probe, release, online, possible, present, offline, isolated, nohz_full, modalias
  • 코드 라인 6~7에서 CONFIG_GENERIC_CPU_DEVICES 커널 옵션을 사용하는 경우 호출되지만 arm64 아키텍처는 관련 없다.

 

다음은 cpu 서브 시스템에 해당하는 버스 타입이다.

struct bus_type cpu_subsys = {                                                  
    .name = "cpu",                                                              
    .dev_name = "cpu",                                                          
    .match = cpu_subsys_match,                                                  
#ifdef CONFIG_HOTPLUG_CPU                                                       
    .online = cpu_subsys_online,                                                
    .offline = cpu_subsys_offline,                                              
#endif                                                                          
};                                                                              
EXPORT_SYMBOL_GPL(cpu_subsys);

 

다음은 cpu 서브 시스템에 추가될 속성들이다.

static struct attribute *cpu_root_attrs[] = {                                   
#ifdef CONFIG_ARCH_CPU_PROBE_RELEASE                                            
    &dev_attr_probe.attr,                                                       
    &dev_attr_release.attr,                                                     
#endif                                                                          
    &cpu_attrs[0].attr.attr,                                                    
    &cpu_attrs[1].attr.attr,                                                    
    &cpu_attrs[2].attr.attr,                                                    
    &dev_attr_kernel_max.attr,                                                  
    &dev_attr_offline.attr,                                                     
    &dev_attr_isolated.attr,                                                    
#ifdef CONFIG_NO_HZ_FULL                                                        
    &dev_attr_nohz_full.attr,                                                   
#endif                                                                          
#ifdef CONFIG_GENERIC_CPU_AUTOPROBE                                             
    &dev_attr_modalias.attr,                                                    
#endif                                                                          
    NULL                                                                        
};

 

cpu 서브 시스템에 등록된 속성들을 간단히 알아본다.

  • probe
    • CONFIG_ARCH_CPU_PROBE_RELEASE 커널 옵션이 필요하다.
  • release
    • CONFIG_ARCH_CPU_PROBE_RELEASE 커널 옵션이 필요하다.
  • online
    • 현재 online 된 cpu를 보여준다. (장착되고 online된 상태의 cpu)
    • 예) 0-1
  • possible
    • possible cpu를 보여준다. (장착 되었거나 추가 장착 가능한 상태의 cpu)
    • 예) 0~3
  • present
    • preseent cpu를 보여준다. (장착된 상태의 cpu)
    • 예) 0~1
  • kernel_max
    • 현재 부팅된 커널이 지원하는 최고 cpu 번호 (NR_CPUS -1)
      • 예) 3
  • offline
    • offline된 cpu를 보여준다.
      • 예) 2~3
  • isolated
    • isolate된 cpu를 보여준다. (스케줄링에서 제외된 cpu)
      • 예) 기본적으로는 없으므로 아무것도 보여주지 않는다.
  • nohz_full
    • nohz full된 상태의 cpu를 보여준다.
    • CONFIG_NO_HZ_FULL 커널 옵션이 필요하다.
  • modalias
    • cpu 타입과 feature들을 보여준다.
    • 예) cpu:type:aarch64_be:feature:,0000,0001,0003,0004,0005,0006,0007
    • CONFIG_GENERIC_CPU_AUTOPROBE 커널 옵션이 필요하다.
    • arm, arm64, mips, s390, x86 등 많은 아키텍처들이 이 커널 옵션을 사용한다.

 

예) 다음은 cpu 서브 시스템에 등록된 cpu들과 속성들이다.

  • cpu0 ~ cpu3과 같은 cpu 디렉토리들은 cpu가 online/offline될 때마다 추가/제거된다.
$ ls /sys/devices/system/cpu -la
drwxr-xr-x    4 root     root             0 May 28 16:50 cpu0
drwxr-xr-x    4 root     root             0 May 28 16:50 cpu1
drwxr-xr-x    2 root     root             0 May 28 16:50 cpu2
drwxr-xr-x    2 root     root             0 May 28 16:50 cpu3
drwxr-xr-x    2 root     root             0 May 28 16:50 cpufreq
drwxr-xr-x    2 root     root             0 May 28 16:50 cpuidle
-r--r--r--    1 root     root          4096 May 28 16:50 isolated
-r--r--r--    1 root     root          4096 May 28 16:50 kernel_max
-r--r--r--    1 root     root          4096 May 28 16:50 modalias
-r--r--r--    1 root     root          4096 May 28 16:50 offline
-r--r--r--    1 root     root          4096 Mar 15 09:55 online
-r--r--r--    1 root     root          4096 May 28 16:50 possible
-r--r--r--    1 root     root          4096 May 28 16:50 present
-rw-r--r--    1 root     root          4096 May 28 16:50 uevent

 

예) 다음은 cpu0에 대한 속성들이다.

<pre “>$ ls /sys/devices/system/cpu/cpu0 -la drwxr-xr-x 5 root root 0 May 28 17:12 cache -r——– 1 root root 4096 May 28 17:12 frequency lrwxrwxrwx 1 root root 0 May 28 17:12 of_node -> ../../../../firmware/devicetree/base/cpus/cpu@0 -rw-r–r– 1 root root 4096 May 28 17:12 online lrwxrwxrwx 1 root root 0 May 28 17:12 subsystem -> ../../../../bus/cpu drwxr-xr-x 2 root root 0 May 28 17:12 topology -rw-r–r– 1 root root 4096 May 28 17:12 uevent

 

memory_dev_init()

drivers/base/memory.c

/*                                                                              
 * Initialize the sysfs support for memory devices...                           
 */                                                                             
int __init memory_dev_init(void)                                                
{                                                                               
    unsigned int i;                                                             
    int ret;                                                                    
    int err;                                                                    
    unsigned long block_sz;                                                     
                                                                                
    ret = subsys_system_register(&memory_subsys, memory_root_attr_groups);      
    if (ret)                                                                    
        goto out;                                                               
                                                                                
    block_sz = get_memory_block_size();                                         
    sections_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE;                      
                                                                                
    /*                                                                          
     * Create entries for memory sections that were found                       
     * during boot and have been initialized                                    
     */                                                                         
    mutex_lock(&mem_sysfs_mutex);                                               
    for (i = 0; i < NR_MEM_SECTIONS; i += sections_per_block) {                 
        /* Don't iterate over sections we know are !present: */                 
        if (i > __highest_present_section_nr)                                   
            break;                                                              
                                                                                
        err = add_memory_block(i);                                              
        if (!ret)                                                               
            ret = err;                                                          
    }                                                                           
    mutex_unlock(&mem_sysfs_mutex);                                             
                                                                                
out:                                                                            
    if (ret)                                                                    
        printk(KERN_ERR "%s() failed: %d\n", __func__, ret);                    
    return ret;                                                                 
}

hotplug 허용된 시스템(CONFIG_MEMORY_HOTPLUG_SPARSE 커널 옵션)에서 memory 서브시스템의 memory 블럭들을 표현 및 관리할  “/sys/devices/system/memory” 디렉토리와 그 아래에 속성 파일들을 생성한다.

  • 코드 라인 11~13에서 memory 서브시스템을 등록하고 관련 속성을 추가한다.
    • “/sys/devices/system/memory” 디렉토리를 만들고 그 밑에 다음 속성들을 추가한다.
      • probe, soft_offline_page, block_size_bytes, auto_online_blocks
  • 코드 라인에서 15~16에서 블럭당 섹션 수를 산출한다.
    • arm64는 블럭과 섹션 크기가 같으므로 항상 1이다.
  • 코드 라인 22~32에서 전체 섹션에 대해서 블럭 크기만큼 증가시키며 memory 서브시스템 밑에 메모리 블럭(디렉토리 및 파일들)을 추가한다.

 

다음은 memory 서브 시스템에 해당하는 버스 타입이다.

static struct bus_type memory_subsys = {                                        
    .name = MEMORY_CLASS_NAME,                                                  
    .dev_name = MEMORY_CLASS_NAME,                                              
    .online = memory_subsys_online,                                             
    .offline = memory_subsys_offline,                                           
};

 

다음은 memory 서브 시스템에 추가될 속성들이다.

static struct attribute *memory_root_attrs[] = {                                
#ifdef CONFIG_ARCH_MEMORY_PROBE                                                 
    &dev_attr_probe.attr,                                                       
#endif                                                                          
                                                                                
#ifdef CONFIG_MEMORY_FAILURE                                                    
    &dev_attr_soft_offline_page.attr,                                           
    &dev_attr_hard_offline_page.attr,                                           
#endif                                                                          
                                                                                
    &dev_attr_block_size_bytes.attr,                                            
    &dev_attr_auto_online_blocks.attr,                                          
    NULL                                                                        
};                                                                              
                                                                                
static struct attribute_group memory_root_attr_group = {                        
    .attrs = memory_root_attrs,                                                 
};                                                                              
                                                                                
static const struct attribute_group *memory_root_attr_groups[] = {              
    &memory_root_attr_group,                                                    
    NULL,                                                                       
};

 

예) 다음은 memory 서브 시스템에 등록된 메모리 블럭들과 속성들이다.

  • memory0 ~ memory27과 같은 memory 블럭 디렉토리들은 memory가 online/offline될 때마다 추가/제거된다.
$ ls -la
-r--r--r--  1 root root 4096  5월 29 14:14 block_size_bytes
--w-------  1 root root 4096  5월 29 14:14 hard_offline_page
drwxr-xr-x  3 root root    0  5월 29 14:18 memory0
drwxr-xr-x  3 root root    0  5월 29 13:36 memory1
...
drwxr-xr-x  3 root root    0  5월 29 13:36 memory27
drwxr-xr-x  2 root root    0  5월 29 14:14 power
--w-------  1 root root 4096  5월 29 14:14 probe
--w-------  1 root root 4096  5월 29 14:14 soft_offline_page
-rw-r--r--  1 root root 4096  5월 29 13:36 uevent

 

예) 다음은 memory0 블럭에 대한 속성들이다.

$ ls /sys/devices/system/memory/memory0 -la
lrwxrwxrwx 1 root root 0 5월 29 14:18 node0 -> ../../node/node0
-rw-r--r-- 1 root root 4096 5월 29 14:18 online
-r--r--r-- 1 root root 4096 5월 29 14:18 phys_device
-r--r--r-- 1 root root 4096 5월 29 14:18 phys_index
drwxr-xr-x 2 root root 0 5월 29 14:18 power
-r--r--r-- 1 root root 4096 5월 29 14:18 removable
-rw-r--r-- 1 root root 4096 5월 29 14:18 state
lrwxrwxrwx 1 root root 0 5월 29 14:18 subsystem -> ../../../../bus/memory
-rw-r--r-- 1 root root 4096 5월 29 14:18 uevent
-r--r--r-- 1 root root 4096 5월 29 14:18 valid_zones

 

get_memory_block_size()

drivers/base/memory.c

static unsigned long get_memory_block_size(void)                                
{                                                                               
    unsigned long block_sz;                                                     
                                                                                
    block_sz = memory_block_size_bytes();                                       
                                                                                
    /* Validate blk_sz is a power of 2 and not less than section size */        
    if ((block_sz & (block_sz - 1)) || (block_sz < MIN_MEMORY_BLOCK_SIZE)) {    
        WARN_ON(1);                                                             
        block_sz = MIN_MEMORY_BLOCK_SIZE;                                       
    }                                                                           
                                                                                
    return block_sz;                                                            
}

메모리 블럭 사이즈를 반환한다.

  • 아키텍처에 별도의 구현이 없는 디폴트: 섹션 사이즈(arm64)

 

container_dev_init()

drivers/base/container.c

void __init container_dev_init(void)                                            
{                                                                               
    int ret;                                                                    
                                                                                
    ret = subsys_system_register(&container_subsys, NULL);                      
    if (ret)                                                                    
        pr_err("%s() failed: %d\n", __func__, ret);                             
}

컨테이너 서브시스템의 컨테이너들을 표현 및 관리할 “/sys/devices/system/container” 디렉토리를 생성한다.  (추가할 속성들은 없다)

 

다음은 container 서브 시스템에 해당하는 버스 타입이다.

struct bus_type container_subsys = {                                            
    .name = CONTAINER_BUS_NAME,                                                 
    .dev_name = CONTAINER_BUS_NAME,                                             
    .online = trivial_online,                                                   
    .offline = container_offline,                                               
};

 

of_core_init()

drivers/of/base.c

void __init of_core_init(void)                                                  
{                                                                               
    struct device_node *np;                                                     
                                                                                
    /* Create the kset, and register existing nodes */                          
    mutex_lock(&of_mutex);                                                      
    of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);           
    if (!of_kset) {                                                             
        mutex_unlock(&of_mutex);                                                
        pr_err("failed to register existing nodes\n");                          
        return;                                                                 
    }                                                                           
    for_each_of_allnodes(np)                                                    
        __of_attach_node_sysfs(np);                                             
    mutex_unlock(&of_mutex);                                                    
                                                                                
    /* Symlink in /proc as required by userspace ABI */                         
    if (of_root)                                                                
        proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base");     
}

디바이스 트리를 표현 및 관리할 “/sys/firmware/devicetree” 디렉토리를 생성하고, 그 이하에 모든 노드에 대한 디렉토리를 만든다. 그리고 각 노드의 속성들도 해당 노드 디렉토리에 각각 파일로 추가한다.

  • 코드 라인 8~12에서 “devicetree” kset을 생성하고 “/sys/firmware”에 추가한다.
    • “/sys/firmware/devicetree” 디렉토리를 만든다.
  • 코드 라인 13~14에서 “/sys/firmware/devicetree” 디렉토리 밑에 읽어온 디바이스 트리 노드들과 속성들을 추가한다.
    • 첫 번째 루트 노드는 base 라는 이름의 디렉토리에 존재한다.
  • 코드 라인 18~19에서 “/sys/firmware/devicetree/base”를 가리키는 심볼릭 링크를 proc 루트 디렉토리에 만든다.
    • “/proc/device-tree”  심볼릭 링크 파일을 생성한다.

 

예) 루트 디렉토리인 base 아래에 생긴 노드들과 속성들이다.

$ ls /sys/firmware/devicetree/base -la
-r--r--r--    1 root     root             4 Mar 30 15:45 #address-cells
-r--r--r--    1 root     root             4 Mar 30 15:45 #size-cells
drwxr-xr-x    2 root     root             0 Mar 30 15:45 aliases
drwxr-xr-x    2 root     root             0 Mar 30 15:45 bootst
drwxr-xr-x    2 root     root             0 Mar 30 15:45 chosen
-r--r--r--    1 root     root            22 Mar 30 15:45 compatible
drwxr-xr-x    7 root     root             0 Mar 30 15:45 cpus
-r--r--r--    1 root     root             4 Mar 30 15:45 interrupt-parent
drwxr-xr-x    2 root     root             0 Mar 30 15:45 memory
-r--r--r--    1 root     root            14 Mar 30 15:45 model
-r--r--r--    1 root     root             1 Mar 30 15:45 name
drwxr-xr-x    2 root     root             0 Mar 30 15:45 pcie@20020000
drwxr-xr-x    2 root     root             0 Mar 30 15:45 pcie@50020000
drwxr-xr-x    2 root     root             0 Mar 30 15:45 pcie@60c00000
drwxr-xr-x    2 root     root             0 Mar 30 15:45 pmu
drwxr-xr-x    2 root     root             0 Mar 30 15:45 psci
drwxr-xr-x    2 root     root             0 Mar 30 15:45 secure_rtc
drwxr-xr-x   70 root     root             0 Mar 30 15:45 soc
drwxr-xr-x    2 root     root             0 Mar 30 15:45 timer

 

참고

 

Sysfs (kobject & kset)

디바이스 드라이버 모델에서 디바이스들은 하이라키 토플로지 형태로 표현되고 있다.  이렇게 하이라키로 표현된 디바이스 드라이버들은 마운트된 sysfs라는 파일 시스템을 통해 디렉토리 구조로 보여주고 관리된다. 이러한 디바이스 드라이버들의 하이라키 표현을 도와주기 위해 다음 구조체들을 사용한다.

  • kobject
  • kset
  • ktype
  • kref

 

kobject(kernel object)

디바이스들은 여러 가지 객체(구조체)로 표현되는데 이들이 기본적으로 가져야 할 이름과 참조 카운터 및 하이 라키 관계를 표현하기 위해 여러 개의 필드가 필요하다. 리눅스 커널은 이러한 정보 및 하이라키 구성관계를 쉽게 사용할 수 있도록 kobject 구조체를 사용한다.

  • kobject 구조체는 그 자체로는 사용되지 않고 다른 객체(구조체) 내부에 임베드되는 형태로 사용된다.
  • kobject는 이름(name)과 참조 카운터(kref)를 가진다.
  • kobject는 하이라키를 표현하기 위해 부모 kobject 포인터 및 ktype, kset 등을 가진다.
  • kobject는 sysfs 가상 파일 시스템에 디렉토리와 연동되므로 모든 디바이스의 구조를 디렉토리 구조로 표현하고 제어할 수 있다.
    • sysfs 구조에서 하나의 디렉토리는 kobject이다.
  • 관련 API
    • kobject_init()
    • kobject_init_and_add()
    • kobject_register()
    • kobject_add()
    • kobject_rename()
    • kobject_move()
    • kobject_get_path()
    • kobject_set_name()
    • kobject_del()
    • kobject_get()
    • kobject_get_unless_zero()
    • kobject_put()
    • kobject_create_and_add()

 

다음 그림의 좌측을 보면 3개의 객체가 하이라키 구조를 갖는 것을 표현하기 위해 각각 name, kref, parent 등의 멤버가 필요함을 알 수 있다. 이러한 정보를 쉽게 처리하기 위해 화살표 우측과 같이 kobject 구조체를 각 객체에 임베드하여 손쉽게 연동할 수 있음을 알 수 있다.

 

kset(kernel object set)

kobject를 여러개 담을 수 있는 컨테이너이다.

  • kset는 기본적으로 하나의 kobject를 포함한다.
  • kset의 컨테이너 구현은 kobject들이 연결된 리스트이다.
    • 이 리스트에 child kobject들이 연결된다.
  • sysfs에서 하나의 디렉토리 아래에 여러 개의 디렉토리가 필요한 경우 kset을 사용한다.
  • 관련 API
    • kset_register()
    • kset_unregister()
    • kset_find_obj()
    • kset_create_and_add()

 

다음 그림과 같은 디렉토리 구조를 같기 위해서는 디렉토리 밑에 2 개 이상의 child 디렉토리가 포함되어야 한다. 이러한 컨테이너를 구현하기 위해 kobject이외에도 list_lock, list, entry 등의 추가 필드들이 필요함을 알 수 있다. 화살표 우측과 같이 kobject를 포함한 컨테이너를 쉽게 구현할 수 있도록 kset 구조체가 사용됨을 알 수 있다.

 

ktype(kernel object type)

kobject가 제거될 때 호출될 함수가 지정된다. 또한 sysfs를 통해 kobject에 대한 정보를 보여주거나 수정될 때 처리할 오퍼레이션이 지정된다.

  • kobj_type 구조체를 사용한다.
  • kobject의 참조 카운터가 0이되어 더 이상 사용하지 않게될 경우 처리 방법이 지정된다.
    • 디폴트로 kfree() 함수가 호출되어 kobject가 제거된다.
  • sysfs에서 기본적으로 표현되고 수정될 방법이 지정된다.
    • 디폴트로 어트리뷰트에 지정된 show() 후크 함수 및 store() 함수가 지정된다.

 

다음 그림은 kobject가 생성될 때 디폴트 ktype으로 dynamic_kobj_ktype이 가리키는 kobj_type 구조체가 지정되는 것을 보여준다.

 

kref(kernel object reference)

참조 카운터가 kobject에 임베드되어 사용된다.  참조 카운터를 관리하는 표준 API인 kref_*() 함수들을 사용할 수 있다.

  • kobject_get() 함수 호출 시 참조 카운터를 1 증가시키는 kref_get() 함수가 호출된다.
  • kobject_put() 함수 호출 시 참조 카운터를 1 감소시키는 kref_put() 함수가 호출된다.
  • kobject_init() 함수 호출 시 참조 카운터를 1로 초기화시키는 kref_init() 함수가 호출된다.
  • 기타 함수
    • kref_read()
    • kref_get_unless_zero()
    • kref_put_mutex()
    • kref_put_lock()

 

다음 그림은 kobject가 처음 초기화되고 참조 카운터가 1이된 것을 보여준다. 그리고 kobject가 추가된 후 sysfs에 연결된 상태의 kobject 상태 값들을 보여준다.

 

Sysfs 파일시스템

sysfs 파일시스템은 유저가 커널 자료 구조와 인터페이스를 하기 위한 가상파일시스템이다. 디렉토리 및 파일들은 kset, kobject로 표현된다. sysfs는 보통 다음과 같이 마운트된다.

  • mount -t sysfs sysfs /sys

 

sysfs는 최근의 Device Driver Model을 따라 하이라키 관계를 사용하여 관리되는 방법이며 다음 방법들을 대체하는 것을 권고하고 있다.

  • procfs
    • 프로세스 정보를 유저 application과의 인터페이스를 위해 심플하게 readable text 출력 포맷으로 출력하게 고안된 파일 시스템이다.
    • 과도하게 이 디렉토리에 파일들이 모여있는 것을 피하기 위해 sysfs가 사용된다.
  • character 디바이스
    • ioctl()을 포함한 file operation 방식으로 유저 application과의 인터페이스를 수행한다.
    • major 및 minor 넘버링이 필요하다.

 

최신 버전 sysfs

  • sysfs는 앞으로 구조가 대폭 변경된다. sys 디렉토리에 직접 접근해야 하는 low-level 유저 application은 앞으로 몇 가지 rule을 따라야 한다. 앞으로 표준화될 새로운 sysfs의 인터페이스 방법은 다음을 참고한다.

 

sysfs 디렉토리 구조

sysfs의 구조는 대략 다음 그림과 같다.

 

Sysfs 관련 API들

디렉토리
  • sysfs_create_mount_point()
  • sysfs_remove_mount_point()
파일
  • sysfs_notify()
  • sysfs_create_file()
  • sysfs_create_files()
  • sysfs_create_file_ns()
  • sysfs_add_file()
  • sysfs_chmod_file()
  • sysfs_add_file_to_group()
  • sysfs_add_file_mode_ns()
  • sysfs_remove_file_ns()
  • sysfs_remove_files()
  • sysfs_remove_file_from_group()
  • sysfs_create_bin_file()
  • sysfs_remove_bin_file()
그룹
  • sysfs_create_group()
  • sysfs_create_groups()
  • sysfs_update_group()
  • sysfs_remove_group()
  • sysfs_remove_groups()
  • sysfs_merge_group()
  • sysfs_unmerge_group()
  • sysfs_add_link_to_group()
  • sysfs_remove_link_from_group()
  • __compat_only_sysfs_link_entry_to_kobj()
링크
  • sysfs_create_link()
  • sysfs_create_link_nowarn()
  • sysfs_create_link_sd()
  • sysfs_remove_link()
  • sysfs_rename_link_ns()

 

Sysfs 샘플

샘플을 통해 속성을 사용하는 방법과 유저 application으로 이벤트를 전달하는 방법을 알아본다.

 

속성 구조체와 매크로 함수

샘플들을 사용하면서 attribute 및 device_attribute 에 대한 2가지 속성들을 알아보기로한다. bus_attribute, class_attribute 및 driver_attribute 등의 샘플은 사용방법이 유사하므로 생략한다. 다음은 5 가지 속성에 대한 구조체와 관련 매크로 함수들을 보여준다.

  • attribute 구조체
    • __ATTR(_name, _mode, _show, _store)
    • __ATTR_RW(_name)
    • __ATTR_RO(_name)
    • __ATTR_WO(_name)
  • device_attribute 구조체
    • DEVICE_ATTR(_name, _mode, _show, _store)
    • DEVICE_ATTR_RW(_name)
    • DEVICE_ATTR_RO(_name)
    • DEVICE_ATTR_WO(_name)
  •  bus_attribute 구조체
    • BUS_ATTR(_name, _mode, _show, _store)
    • BUS_ATTR_RW(_name)
    • BUS_ATTR_RO(_name)
  • class_attribute 구조체
    • CLASS_ATTR_RW(_name)
    • CLASS_ATTR_RO(_name)
    • CLASS_ATTR_WO(_name)
  • driver_attribute 구조체
    • DRIVER_ATTR_RW(_name)
    • DRIVER_ATTR_RO(_name)
    • DRIVER_ATTR_WO(_name)

 

1) kobject_init() & kobject_add() 사용

Makefile

obj-m := foo1.o foo2.o foo3.o
KER_DIR := /lib/modules/$(shell uname -r)/build

all:
	make -C ${KER_DIR} M=$(PWD) modules
	gcc test.c -o test

clean:
	make -C ${KER_DIR} M=$(PWD) clean
	rm -f test.o
	rm -f test

 

foo.1 샘플은 attribute 구조체를 사용하고 kobject API를 사용하여 /sys/foo/foo_value 및 /sys/foo/foo_notify 속성을 만든다.

foo1.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/slab.h>

static struct kobject *foo_kobj;

struct foo_attr{
	struct attribute attr;
	int value;
};

static struct foo_attr foo_value = {
	.attr.name="foo_value",
	.attr.mode = 0644,
	.value = 0,
};

static struct foo_attr foo_notify = {
	.attr.name="foo_notify",
	.attr.mode = 0644,
	.value = 0,
};

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

static ssize_t foo_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);
	return scnprintf(buf, PAGE_SIZE, "%d\n", foo->value);
}

static ssize_t foo_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);

	sscanf(buf, "%d", &foo->value);
	foo_value.value = foo->value;
	sysfs_notify(foo_kobj, NULL, "foo_notify");
	return len;
}

static struct sysfs_ops foo_ops = {
	.show = foo_show,
	.store = foo_store,
};

static struct kobj_type foo_type = {
	.sysfs_ops = &foo_ops,
	.default_attrs = foo_attrs,
};

static int __init foo_init(void)
{
	int ret = 0;

	printk("%s\n", __func__);

	foo_kobj = kzalloc(sizeof(*foo_kobj), GFP_KERNEL);
	if (!foo_kobj)
	{
		printk("%s: kzalloc() failed\n", __func__);
		return -1;
	}

	kobject_init(foo_kobj, &foo_type);
	ret = kobject_add(foo_kobj, NULL, "%s", "foo");
	if (ret) {
		printk("kobject_add() failed. ret=%d\n", ret);
		kobject_put(foo_kobj);
		foo_kobj = NULL;
	}

	return ret;	/* 0=success */
}

static void __exit foo_exit(void)
{
	if (foo_kobj) {
		kobject_put(foo_kobj);
	}

	printk("%s\n", __func__);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("GPL");

 

위의 샘플을 동작시키면 다음과 같은 속성 파일이 생성된다.

$ su -
$ insmod foo1.ko
$ cd /sys/foo
$ ls
foo_notify   foo_value
$ echo 123 > foo_value
$ cat foo_value
123
$ rmmod foo1

 

2) kobject_create_and_add() &  sysfs_create_group() 사용

 

foo2.c 샘플은 kobj_attribute 구조체를 사용하고 kobject 및 sysfs API를 사용하여 /sys/foo/foo_value 및 /sys/foo/foo_notify 속성을 만든다. foo2.c의 사용법은 foo1.c와 동일하다.

foo2.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/slab.h>

static struct kobject *foo_kobj;

struct foo_attr{
	struct kobj_attribute attr;
	int value;
};

static struct foo_attr foo_value;
static struct foo_attr foo_notify;

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

static struct attribute_group foo_group = {
	.attrs = foo_attrs,
};

static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr, 
		char *buf)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);
	return scnprintf(buf, PAGE_SIZE, "%d\n", foo->value);
}

static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, 
		const char *buf, size_t len)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);

	sscanf(buf, "%d", &foo->value);
	sysfs_notify(foo_kobj, NULL, "foo_notify");
	return len;
}

static struct foo_attr foo_value = {
	.attr = __ATTR(foo_value, 0644,	foo_show, foo_store),
	.value = 0,
};

static struct foo_attr foo_notify = {
	.attr = __ATTR(foo_notify, 0644, foo_show, foo_store),
	.value = 0,
};

static int __init foo_init(void)
{
	int ret = 0;

	printk("%s\n", __func__);

	foo_kobj = kobject_create_and_add("foo", NULL);
	if (!foo_kobj) {
		printk("%s: kobject_create_and_add() failed\n", __func__);
		return -1;
	}

	ret = sysfs_create_group(foo_kobj, &foo_group);
	if (ret) 
		printk("%s: sysfs_create_group() failed. ret=%d\n", __func__, ret);

	return ret;	/* 0=success */
}

static void __exit foo_exit(void)
{
	if (foo_kobj) {
		kobject_put(foo_kobj);
	}

	printk("%s\n", __func__);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("GPL");

 

3) platform_device_register() 사용

 

foo3.c 샘플은 플랫폼 디바이스를 등록할 때 device_attribute 구조체를 사용한 디바이스 속성을 등록한다.

foo3.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/platform_device.h>                                              

struct foo_attr{
	struct device_attribute attr;
	int value;
};

static struct foo_attr foo_value;
static struct foo_attr foo_notify;

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

ATTRIBUTE_GROUPS(foo);

static ssize_t foo_show(struct device *dev, struct device_attribute *attr, 
		char *buf)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);
	return scnprintf(buf, PAGE_SIZE, "%d\n", foo->value);
}

static ssize_t foo_store(struct device *dev, struct device_attribute *attr, 
		const char *buf, size_t len)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);

	sscanf(buf, "%d", &foo->value);
	sysfs_notify(&dev->kobj, NULL, "foo_notify");
	return len;
}

static struct foo_attr foo_value = {
	.attr = __ATTR(foo_value, 0644, foo_show, foo_store),
	.value = 0,
};

static struct foo_attr foo_notify = {
	.attr = __ATTR(foo_notify, 0644, foo_show, foo_store),
	.value = 0,
};

static struct platform_device foo_device = {                                   
	.name = "foo",
	.id = -1,
	.dev.groups = foo_groups,
};                                                                              

static int __init foo_init(void)
{
	int ret = 0;

	printk("%s\n", __func__);

	ret = platform_device_register(&foo_device);
	if (ret < 0) {
		printk("%s: platform_device_register() failed. ret=%d\n",
				__func__, ret);
		ret = -1;
	}

	return ret;	/* 0=success */
}

static void __exit foo_exit(void)
{
	platform_device_unregister(&foo_device);

	printk("%s\n", __func__);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("GPL");

 

위의 foo3.c 샘플을 동작시키면 다음과 같이 플랫폼 디비이스 디렉토리 뒤에 foo 플랫폼 디바이스가 만들어지고 그 안에 속성 파일이 생성된다.

$ su -
$ insmod foo3.ko
$ cd /sys/devices/platform/foo
$ ls
driver_override foo_notify foo_value modalias power subsystem uevent
$ echo 123 > foo_value
$ cat foo_value
123
$ rmmod foo3

 

이벤트 테스트를 위한 유저 application

위에서 작성한 3가지의 샘플 모듈을 사용하여 유저 application에서 poll() API를 사용하여 이벤트를 받는 방법을 보여주기 위한 샘플이다.

  • 참고로 커널 모듈에서 속성에 이벤트를 전달하는 방법은 sysfs_notify() 함수를 사용한다.
  • 주의: foo3.c 샘플과 연동하여 테스트하려면 아래 FOO_VALUE 및 FOO_NOTIFY의 디렉토리 경로를 플랫폼 디바이스 위치로 변경해야 한다.
    • #define FOO_VALUE “/sys/devices/platform/foo/foo_value”
    • #define FOO_NOTIFY “/sys/devices/platform/foo/foo_notify”

test.c

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h> 
#include <sys/stat.h> 
#include <poll.h>

#define FOO_VALUE "/sys/foo/foo_value"
#define FOO_NOTIFY "/sys/foo/foo_notify"

int main(int argc, char **argv)
{
	int len, value_fd, notify_fd, recv;
	char attr_data[100];
	struct pollfd fds[1];

	if ((value_fd = open("/sys/foo/foo_value", O_RDWR)) < 0)
	{
		printf("Unable to open %s\n", FOO_VALUE);
		exit(1);
	}

	if ((notify_fd = open("/sys/foo/foo_notify", O_RDWR)) < 0)
	{
		printf("Unable to open %s\n", FOO_NOTIFY);
		exit(1);
	}

	fds[0].fd = notify_fd;
	fds[0].events = POLLPRI | POLLERR;

	/* read garvage data once */
	len = read(notify_fd, attr_data, 100);

	fds[0].revents = 0;

	/* wait until 60 sec */
	recv = poll(fds, 1, 60000);
	if (recv < 0)
	{
		perror("poll error");
	}
	else if (recv == 0)
	{
		printf("Timeout!\n");
	}
	else
	{
		memset(attr_data, 0, 100);
		len = lseek(value_fd, 0, SEEK_SET);
		len = read(value_fd, attr_data, 100);
		printf( "foo_value=%s (len=%d)\n", attr_data, len);
	}

	close(notify_fd);
	close(value_fd);
}

 

다음과 같이 두 개의 터미널을 열고 각각에서 다음과 같이 동작시킨다.

터미널 1
$ su -                 <- 루트 계정으로 접근한다.
$ ./test3              <- 60초 동안 이벤트를 대기한다.
foo_value=456          <- 이벤트가 발생하는 순간 출력된다.
 (len=4)

 

터미널 2
$ su -                         <- 루트 계정으로 접근한다.
$ cd /sys/devices/platform/foo
$ echo 456 > foo_value         <- 이 속성을 변경하는 순간 이벤트가 발생한다.

 

참고

 

GPIO Subsystem -4- (new Interface)

 

새로운 GPIO character 디바이스 for User-space

기존 Sysfs 클래스 디바이스 인터페이스

기존 커널의 gpio 디바이스는 클래스 디바이스에 등록되어 관리되었다.

  • “/sys/class/gpio” 디렉토리의 virtual 디렉토리와 파일을 통해 유저 스페이스에서 인터페이스 할 수 있도록 제공한다.
  • 이 인터페이스는 GPIO subsystem core에서 제공한다.
  • gpio 핀 하나 하나에 대응하는 exprot된 가상 디렉토리 및 제어용 파일들이 만들어진다.
  • 추후 사용되지 않을 인터페이스이다. (deprecated)

 

새 character 디바이스 인터페이스

기존 sysfs 클래스 디바이스 기반의 ABI를 사용하는 user-space 인터페이스는 앞으로 character 디바이스로 대체될 예정이다.

  • 매직 넘버를 사용하지 않는 디스커버리(Discovery) 메커니즘을 적용하였다.
  • character 디바이스를 open하여 ioctl()을 통해 gpio 핀 제어가 가능하다.
    • 디바이스 트리에 정의된 line 명에 해당하는 gpio 핀을 ioctl()로 접근하여 제어할 수 있다.
  • 이에 대한 접근 방법은 gpio 유저 application 툴인 커널 디렉토리의 tools/lsgpio.c 파일을 보면 알 수 있다.
  • “/sys/bus/gpio”, “/dev/gpiochipN”
  • 이 새로운 방식은 커널 v4.8부터 사용된다.
    • 관련 유틸리티
      • lsgpio
      • gpio-hammer
      • gpio-event-mon
    • 관련 User 라이브러리 (option)
      • gpiodlib

 

lsgpio

“gpiochip0″명을 가진 gpio controller 디바이스를 open한 후 소속된 gpio 라인명과 사용 상태를 보여준다.

GPIO chip: gpiochip0, "pinctrl-bcm2835", 54 GPIO lines
 line 0: "[SDA0]" unused
 line 1: "[SCL0]" unused
 (...)
 line 16: "STATUS_LED_N" unused
 line 17: "GPIO_GEN0" unused
 line 18: "GPIO_GEN1" unused
 line 19: "NC" unused
 line 20: "NC" unused
 line 21: "CAM_GPIO" unused
 line 22: "GPIO_GEN3" unused
 line 23: "GPIO_GEN4" unused
 line 24: "GPIO_GEN5" unused
 line 25: "GPIO_GEN6" unused
 line 26: "NC" unused
 line 27: "GPIO_GEN2" unused

 

gpio-hammer
$ gpio-hammer --help
gpio-hammer: invalid option -- '-'
Usage: gpio-hammer [options]...
Hammer GPIO lines, 0->1->0->1...
  -n <name>  Hammer GPIOs on a named device (must be stated)
  -o <n>     Offset[s] to hammer, at least one, several can be stated
 [-c <n>]    Do <n> loops (optional, infinite loop if not stated)
  -?         This helptext

Example:
gpio-hammer -n gpiochip0 -o 4
$ gpio-hammer -n gpiochip0 -o 5 -c 2
Hammer lines [5] on gpiochip0, initial states: [1]
[\] [5: 1]

 

gpio-event-mon
$ gpio-event-mon --help
gpio-event-mon: invalid option -- '-'
Usage: gpio-event-mon [options]...
Listen to events on GPIO lines, 0->1 1->0
  -n <name>  Listen on GPIOs on a named device (must be stated)
  -o <n>     Offset to monitor
  -d         Set line as open drain
  -s         Set line as open source
  -r         Listen for rising edges
  -f         Listen for falling edges
 [-c <n>]    Do <n> loops (optional, infinite loop if not stated)
  -?         This helptext

Example:
gpio-event-mon -n gpiochip0 -o 4 -r -f
$ gpio-event-mon -n gpiochip0 -o 6 -r -c 2
Monitoring line 6 on gpiochip0
Initial line value: 1
GPIO EVENT 1527053280817909942: rising edge
GPIO EVENT 1527053282195388044: rising edge

 

New GPIO character 디바이스에 대한 유저 application 샘플 -1-

GPIO character 디바이스 Open 메인

/dev/gpiochipN gpio 디바이스로 open한 후 요청한 gpio 핀 3개를 input, output, input+event 모드로 설정하고 이벤트 입력을 기다린다.

  • 예)  new-gpio-api /dev/gpiochip0 4 5 6

 

new-gpio-api.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/gpio.h>
#include <poll.h>
#include <inttypes.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
        int gpio1, gpio2, gpio3;
        int fd;
        int req_fd;
        int value;
        char * file;

        if (argc < 5) {
                printf("Usage: new-gpio-api /dev/<gpiochipN> "
                                "<in-gpio> <out-gpio> <event-gpio>\n");
                return -1;
        }

        file = argv[1];
        fd = open(file, O_RDWR);
        if (fd < 0) {
                printf("open \"%s\" failed\n", file);
                return -1;
        }

        gpio1 = atoi(argv[2]);
        gpio2 = atoi(argv[3]);
        gpio3 = atoi(argv[4]);

        if (chip_info(fd) < 0)
                goto err;

        if (line_info(fd, gpio1) < 0)
                goto err;

        if (request_gpio(fd, gpio1, GPIOHANDLE_REQUEST_INPUT,
                "foo_input", &req_fd) < 0)
                goto err;

        if (get_value(req_fd, gpio1, &value) < 0)
                goto err;

        if (request_gpio(fd, gpio2, GPIOHANDLE_REQUEST_OUTPUT,
                "foo_output", &req_fd) < 0)
                goto err;

        if (set_value(req_fd, gpio2, 1) < 0)
                goto err;

        if (request_event(fd, gpio3, "foo_event", &req_fd) < 0)
                goto err;

        while (1)
                if (recv_event(req_fd, gpio3) < 0)
                        break;

err:
        close(fd);
        return -1;
}

 

GPIO chip 정보 알아오기

gpio chip 디바이스명, 라벨명, gpio 라인 수를 알아와서 출력한다.

int chip_info(int fd)
{
        int ret;
        struct gpiochip_info cinfo;

        ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo);
        if (ret < 0) {
                printf("GPIO_GET_CHIPINFO_IOCTL failed.\n");
                return -1;
        }

        printf("GPIO chip: %s, \"%s\", %u GPIO lines\n",
                cinfo.name, cinfo.label, cinfo.lines);

        return 0;
}

 

GPIO line 정보 알아오기

요청한 gpio 번호로 지정되어 있는 라인명 등을 알아온다.

int line_info(int fd, int gpio)
{
        int ret;
        struct gpioline_info linfo;

        memset(&linfo, 0, sizeof(linfo));
        linfo.line_offset = gpio;

        ret = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &linfo);
        if (ret < 0) {
                printf("GPIO_GET_LINEINFO_IOCTL failed.\n");
                return -1;
        }

        printf("line %2d: %s\n", linfo.line_offset, linfo.name);

        return fd;
}

 

GPIO line 사용 요청

요청한 gpio 번호에 해당하는 라인을 플래그 값에 해당하는 모드로 설정한다. 라벨명도 지정할 수 있다.

  • flags
    • GPIOHANDLE_REQUEST_INPUT
    • GPIOHANDLE_REQUEST_OUTPUT
int request_gpio(int fd, int gpio, int flags, const char *label, int *req_fd)
{
        struct gpiohandle_request req;
        int ret;

        req.flags = flags;
        req.lines = 1;
        req.lineoffsets[0] = gpio;
        req.default_values[0] = 0;
        strcpy(req.consumer_label, label);

        ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
        if (ret < 0) {
                printf("GPIO_GET_LINEHANDLE_IOCTL failed.\n");
                return -1;
        }

        *req_fd = req.fd;

        return 0;
}

 

GPIO line 읽기(Read)

요청한 gpio 번호에 해당하는 라인에서 값(high/low)을 읽어 출력한다.

int get_value(int req_fd, int gpio, int *value)
{
        struct gpiohandle_data data;
        int ret;

        memset(&data, 0, sizeof(data));

        ret = ioctl(req_fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
                printf("GPIO_GET_LINE_VALUES_IOCTL failed.\n");
                return -1;
        }
        printf("line %d is %s\n", gpio, data.values[0] ? "high" : "low");

        *value = data.values[0];

        return 0;
}

 

GPIO line 쓰기(Write)

요청한 gpio 번호에 해당하는 라인에 값(high/low)을 출력한다.

int set_value(int req_fd, int gpio, int value)
{
        struct gpiohandle_data data;
        int ret;

        memset(&data, 0, sizeof(data));
        data.values[0] = value;

        ret = ioctl(req_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
                printf("GPIO_SET_LINE_VALUES_IOCTL failed.\n");
                return -1;
        }
        printf("line %d is %s\n", gpio, data.values[0] ? "high" : "low");

        return 0;
}

 

GPIO line 이벤트 요청

요청한 gpio 번호에 해당하는 라인을 입력 모드로 설정하고 이벤트를 수신할 수 있도록 준비한다.

  • 아래 예에서는 핀에 open drain 설정을 하였고, rising edge 및 falling edge 이벤트에 반응하도록 설정하였다.
int request_event(int fd, int gpio, const char *label, int *req_fd)
{
        struct gpioevent_request req;
        int ret;

        req.lineoffset = gpio;
        strcpy(req.consumer_label, label);
        req.handleflags = GPIOHANDLE_REQUEST_OPEN_DRAIN;
        req.eventflags = GPIOEVENT_REQUEST_RISING_EDGE |
                GPIOEVENT_REQUEST_FALLING_EDGE;

        ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
        if (ret < 0) {
                printf("GPIO_GET_LINEEVENT_IOCTL failed.\n");
                return -1;
        }

        *req_fd = req.fd;

        return 0;
}

 

GPIO line 이벤트 대기

요청한 gpio 번호에 해당하는 라인에서 이벤트를 수신 대기한다.

#define USE_POLL

int recv_event(int req_fd, int gpio)
{
        struct gpioevent_data event;
        struct pollfd pfd;
        ssize_t rd;
        int ret;

#ifdef USE_POLL
        pfd.fd = req_fd;
        pfd.events = POLLIN | POLLPRI;

        rd = poll(&pfd, 1, 1000);
        if (rd < 0)
        if (ret < 0) {
                printf("poll failed.\n");
                return -1;
        }
#endif
        ret = read(req_fd, &event, sizeof(event));
        if (ret < 0) {
                printf("read failed.\n");
                return -1;
        }

        printf( "GPIO EVENT @%" PRIu64 ": ", event.timestamp);

        if (event.id == GPIOEVENT_EVENT_RISING_EDGE)
                printf("RISING EDGE");
        else
                printf("FALLING EDGE");
        printf ("\n");

        return 0;
}

 

동작 확인

gpio 4, 5, 6번에 대해 다음과 같은 동작을 하는 것을 확인한다.

  • gpio 4번을 입력 모드로 설정하고 low 갑을 읽었다.
  • gpio 5번을 출력 모드로 설정하고 high 출력하였다.
  • gpio 6번을 입력 모드 및 이벤트 수신 대기를 한다.
$ ./new-gpio-api /dev/gpiochip0 4 5 6
GPIO chip: gpiochip0, "0000:00:01.0", 32 GPIO lines
line  4: 
line 4 is low
line 5 is high
GPIO EVENT @1527042559726500338: RISING EDGE                        <- 6번 gpio에 연결된 버튼(high 시그널)을 눌렀을 때

 

아래와 같이 gpio line 4~6 번이 설정되어 동작하는 것을 lsgpio 툴로 확인할 수 있다.

$ lsgpio
GPIO chip: gpiochip0, "0000:00:01.0", 32 GPIO lines
	line  0: unnamed "sysfs" [kernel]
	line  1: unnamed "sysfs" [kernel]
	line  2: unnamed "sysfs" [kernel]
	line  3: unnamed "sysfs" [kernel]
	line  4: unnamed "foo_input" [kernel]
	line  5: unnamed "foo_output" [kernel output]
	line  6: unnamed "foo_event" [kernel open-drain]
	line  7: unnamed unused
	line  8: unnamed unused
	line  9: unnamed unused

 

New GPIO character 디바이스에 대한 유저 application sample -2-

gpiodlib를 사용하는 유저 application 샘플

이 샘플 소스 역시 샘플 1 소스와 동일한 결과를 수행한다.

  • 이 샘플을 빌드하기 위해서는 libgpiod의 설치가 필요하다.

 

libgpiod 다운 및 설치 (for debian)

debian 계열을 사용하는 임베디드에서 테스트하기 위해 root 계정에서 다음과 같이 libgpiod를 설치한다.

$ cd ~
$ apt-get install libtool pkg-config autoconf autoconf-archive
$ git clone https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
$ cd libgpiod
$ mkdir -p include/linux
$ cp /usr/src/linux-headers-$(uname -r)/include/linux/compiler_types.h include/linux/.
$ ./autogen.sh --enable-tools=yes --prefix=/usr/local CFLAGS="-I/usr/src/linux-headers-$(uname -r)/include/uapi -Iinclude"
$ make
$ make install
$ ldconfig

 

Utility

libgpiod 설치시 같이 제공되는 유틸리티는 다음과 같다.

  • gpiofind
  • gpiodetect
  • gpioinfo
  • gpioget
  • gpiomon
  • gpioset

 

gpiodetect
$ gpiodetect --help
Usage: gpiodetect [OPTIONS]
List all GPIO chips, print their labels and number of GPIO lines.
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit
$ gpiodetect
gpiochip0 [0000:00:01.0] (32 lines)

 

gpioinfo
$ gpioinfo --help
Usage: gpioinfo [OPTIONS] <gpiochip1> ...
Print information about all lines of the specified GPIO chip(s) (or all gpiochips if none are specified).
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit

$ gpioinfo gpiochip0
gpiochip0 - 32 lines:
	line   0:      unnamed      "sysfs"   input  active-high [used]
	line   1:      unnamed      "sysfs"   input  active-high [used]
	line   2:      unnamed      "sysfs"   input  active-high [used]
	line   3:      unnamed      "sysfs"   input  active-high [used]
	line   4:      unnamed       unused   input  active-high 
	line   5:      unnamed       unused  output  active-high 
	line   6:      unnamed       unused   input  active-high 
        ...

 

gpioget
$ gpioget --help
Usage: gpioget [OPTIONS] <chip name/number> <offset 1> <offset 2> ...
Read line value(s) from a GPIO chip
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit
  -l, --active-low:	set the line active state to low

$ gpioget gpiochip0 4
0

 

gpioset
$ gpioset --help
Usage: gpioset [OPTIONS] <chip name/number> <offset1>=<value1> <offset2>=<value2> ...
Set GPIO line values of a GPIO chip
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit
  -l, --active-low:	set the line active state to low
  -m, --mode=[exit|wait|time|signal] (defaults to 'exit'):
		tell the program what to do after setting values
  -s, --sec=SEC:	specify the number of seconds to wait (only valid for --mode=time)
  -u, --usec=USEC:	specify the number of microseconds to wait (only valid for --mode=time)
  -b, --background:	after setting values: detach from the controlling terminal

Modes:
  exit:		set values and exit immediately
  wait:		set values and wait for user to press ENTER
  time:		set values and sleep for a specified amount of time
  signal:	set values and wait for SIGINT or SIGTERM
$ gpioset gpiochip0 5=1

 

gpiomon
$ gpiomon --help
Usage: gpiomon [OPTIONS] <chip name/number> <offset 1> <offset 2> ...
Wait for events on GPIO lines
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit
  -l, --active-low:	set the line active state to low
  -n, --num-events=NUM:	exit after processing NUM events
  -s, --silent:		don't print event info
  -r, --rising-edge:	only process rising edge events
  -f, --falling-edge:	only process falling edge events
  -F, --format=FMT	specify custom output format

Format specifiers:
  %o:  GPIO line offset
  %e:  event type (0 - falling edge, 1 rising edge)
  %s:  seconds part of the event timestamp
  %n:  nanoseconds part of the event timestamp
$ gpiomon gpiochip0 6
event:  RISING EDGE offset: 6 timestamp: [1527052928.061198582]

 

샘플 소스 파일

Makefile

LIBGPIOD_DIR=/root/libgpiod/include

all:
	gcc -I. -I$LIBGPIOD_DIR -lgpiod new-gpio-api2.c -o new-gpio-api2 

clean:
	rm -f *.o
	rm -f new-gpio-api2

 

new-gpio-api2.c

#include <stdio.h>
#include "gpiod.h"

int main(int argc, char *argv[])
{
        int gpio1, gpio2, gpio3;
        int fd;
        int req_fd;
        int value;
        char * file;
	int rv;

	struct gpiod_chip *chip;
	struct gpiod_line *line;

	struct timespec ts = { 0, 1000000 };
	struct gpiod_line_event event;
	struct gpiod_line_request_config config;

        if (argc < 5) {
                printf("Usage: new-gpio-api2 /dev/<gpiochipN> "
                                "<in-gpio> <out-gpio> <event-gpio>\n");
                return -1;
        }

        file = argv[1];

        gpio1 = atoi(argv[2]);
        gpio2 = atoi(argv[3]);
        gpio3 = atoi(argv[4]);

	chip = gpiod_chip_open(file);
	if (!chip)
		return -1;

	/* foo-input */
	line = gpiod_chip_get_line(chip, gpio1);
	if (!line) 
		goto err;

	rv = gpiod_line_request_input(line, "foo-input2");
	if (rv)
		goto err;

	value = gpiod_line_get_value(line);

	/* foo-output */
	line = gpiod_chip_get_line(chip, gpio2);
	if (!line)
		goto err;

	rv = gpiod_line_request_output(line, "foo-output2", 0);
	if (rv)
		goto err;

	gpiod_line_set_value(line, 1);

	line = gpiod_chip_get_line(chip, gpio3);
	if (!line)
		goto err;

	rv = gpiod_line_request_rising_edge_events_flags(line, "foo-event2", 
			GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);
	if (rv) 
		goto err;

	while (1)
	{
		do {
			rv = gpiod_line_event_wait(line, &ts);
		} while (rv <= 0);

		rv = gpiod_line_event_read(line, &event);
	}

err:
	gpiod_chip_close(chip);

	return -1;
}

 

참고