안녕하세요?
4명의 저자들이 모여 arm64 아키텍처에 대한 분석을 완료하고 아래 책을 출판하였습니다.
저도 저자 중 한 명입니다 ^^
안녕하세요?
4명의 저자들이 모여 arm64 아키텍처에 대한 분석을 완료하고 아래 책을 출판하였습니다.
저도 저자 중 한 명입니다 ^^
기존 커널이 사용했던 devfs를 대신하여 커널 2.6에 도입된 udev 데몬을 통해 디바이스 드라이버의 로딩이 이루어진다. udev 데몬이 유저 프로세스에서 동작하므로 커널 부팅 후 루트 파일시스템을 마운트한 후에야 디바이스 드라이버의 로딩이 가능해진다. 대부분의 디바이스 드라이버들은 커널 부트업 과정에서 로딩이 이루어지므로 루트 파일 시스템을 로딩하기 전에 initrd/initramfs 등에 존재하는 디바이스 드라이버를 임시 파일 시스템인 devtmpfs에 로딩시켜 부팅 시간을 단축할 목적으로 사용한다.
다음 그림과 같이 루트 파일 시스템을 마운트하기 전에 디바이스 드라이버들이 devtmpfs에 로딩되어 부팅 시간을 단축시키는 과정을 보여준다.
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, };
다음 그림은 디바이스 드라이버들의 로딩/언로딩 요청에 대해 devtmpfs에 디바이스 노드를 생성 및 삭제하는 과정을 보여준다.
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 스레드가 처음 동작하고 무한 루프를 돌며 디바이스 노드 생성 및 삭제 요청을 처리하는 함수이다.
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에 따라 핸들 생성 및 삭제 함수를 호출한다.
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; }
디바이스 파일을 생성하고 디바이스 노드를 생성한다.
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; }
디바이스 노드를 삭제하고, 디바이스 파일도 삭제한다.
리눅스 디바이스 드라이버 모델을 표현하기위해 kobject 및 kset등이 사용된다.
다음 그림은 디바이스 드라이버 모델의 초기화 호출 과정을 보여준다.
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(); }
리눅스 커널이 사용하는 드라이버 모델의 초기화를 수행한다.
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” 디렉토리를 생성한다.
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” 디렉토리도 생성한다.
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” 디렉토리를 생성한다.
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” 디렉토리를 생성한다.
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” 디렉토리를 생성한다.
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” 디렉토리를 생성한다.
등록될 디바이스
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
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 플랫폼 디바이스들을 리스트에서 제거한다.
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() 함수를 등록한다.
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” 디렉토리와 그 아래에 속성 파일들을 생성한다.
다음은 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 서브 시스템에 등록된 속성들을 간단히 알아본다.
예) 다음은 cpu 서브 시스템에 등록된 cpu들과 속성들이다.
$ 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
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” 디렉토리와 그 아래에 속성 파일들을 생성한다.
다음은 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 서브 시스템에 등록된 메모리 블럭들과 속성들이다.
$ 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
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; }
메모리 블럭 사이즈를 반환한다.
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, };
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” 디렉토리를 생성하고, 그 이하에 모든 노드에 대한 디렉토리를 만든다. 그리고 각 노드의 속성들도 해당 노드 디렉토리에 각각 파일로 추가한다.
예) 루트 디렉토리인 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 구조체를 사용한다.
다음 그림의 좌측을 보면 3개의 객체가 하이라키 구조를 갖는 것을 표현하기 위해 각각 name, kref, parent 등의 멤버가 필요함을 알 수 있다. 이러한 정보를 쉽게 처리하기 위해 화살표 우측과 같이 kobject 구조체를 각 객체에 임베드하여 손쉽게 연동할 수 있음을 알 수 있다.
kobject를 여러개 담을 수 있는 컨테이너이다.
다음 그림과 같은 디렉토리 구조를 같기 위해서는 디렉토리 밑에 2 개 이상의 child 디렉토리가 포함되어야 한다. 이러한 컨테이너를 구현하기 위해 kobject이외에도 list_lock, list, entry 등의 추가 필드들이 필요함을 알 수 있다. 화살표 우측과 같이 kobject를 포함한 컨테이너를 쉽게 구현할 수 있도록 kset 구조체가 사용됨을 알 수 있다.
kobject가 제거될 때 호출될 함수가 지정된다. 또한 sysfs를 통해 kobject에 대한 정보를 보여주거나 수정될 때 처리할 오퍼레이션이 지정된다.
다음 그림은 kobject가 생성될 때 디폴트 ktype으로 dynamic_kobj_ktype이 가리키는 kobj_type 구조체가 지정되는 것을 보여준다.
참조 카운터가 kobject에 임베드되어 사용된다. 참조 카운터를 관리하는 표준 API인 kref_*() 함수들을 사용할 수 있다.
다음 그림은 kobject가 처음 초기화되고 참조 카운터가 1이된 것을 보여준다. 그리고 kobject가 추가된 후 sysfs에 연결된 상태의 kobject 상태 값들을 보여준다.
sysfs 파일시스템은 유저가 커널 자료 구조와 인터페이스를 하기 위한 가상파일시스템이다. 디렉토리 및 파일들은 kset, kobject로 표현된다. sysfs는 보통 다음과 같이 마운트된다.
sysfs는 최근의 Device Driver Model을 따라 하이라키 관계를 사용하여 관리되는 방법이며 다음 방법들을 대체하는 것을 권고하고 있다.
sysfs의 구조는 대략 다음 그림과 같다.
샘플을 통해 속성을 사용하는 방법과 유저 application으로 이벤트를 전달하는 방법을 알아본다.
샘플들을 사용하면서 attribute 및 device_attribute 에 대한 2가지 속성들을 알아보기로한다. bus_attribute, class_attribute 및 driver_attribute 등의 샘플은 사용방법이 유사하므로 생략한다. 다음은 5 가지 속성에 대한 구조체와 관련 매크로 함수들을 보여준다.
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 속성을 만든다.
#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
foo2.c 샘플은 kobj_attribute 구조체를 사용하고 kobject 및 sysfs API를 사용하여 /sys/foo/foo_value 및 /sys/foo/foo_notify 속성을 만든다. foo2.c의 사용법은 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 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");
foo3.c 샘플은 플랫폼 디바이스를 등록할 때 device_attribute 구조체를 사용한 디바이스 속성을 등록한다.
#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
위에서 작성한 3가지의 샘플 모듈을 사용하여 유저 application에서 poll() API를 사용하여 이벤트를 받는 방법을 보여주기 위한 샘플이다.
#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); }
다음과 같이 두 개의 터미널을 열고 각각에서 다음과 같이 동작시킨다.
$ su - <- 루트 계정으로 접근한다. $ ./test3 <- 60초 동안 이벤트를 대기한다. foo_value=456 <- 이벤트가 발생하는 순간 출력된다. (len=4)
$ su - <- 루트 계정으로 접근한다. $ cd /sys/devices/platform/foo $ echo 456 > foo_value <- 이 속성을 변경하는 순간 이벤트가 발생한다.
기존 커널의 gpio 디바이스는 클래스 디바이스에 등록되어 관리되었다.
기존 sysfs 클래스 디바이스 기반의 ABI를 사용하는 user-space 인터페이스는 앞으로 character 디바이스로 대체될 예정이다.
“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 --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 --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
/dev/gpiochipN gpio 디바이스로 open한 후 요청한 gpio 핀 3개를 input, output, input+event 모드로 설정하고 이벤트 입력을 기다린다.
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 라인 수를 알아와서 출력한다.
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 번호로 지정되어 있는 라인명 등을 알아온다.
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 번호에 해당하는 라인을 플래그 값에 해당하는 모드로 설정한다. 라벨명도 지정할 수 있다.
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 번호에 해당하는 라인에서 값(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 번호에 해당하는 라인에 값(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 번호에 해당하는 라인을 입력 모드로 설정하고 이벤트를 수신할 수 있도록 준비한다.
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 번호에 해당하는 라인에서 이벤트를 수신 대기한다.
#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번에 대해 다음과 같은 동작을 하는 것을 확인한다.
$ ./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
이 샘플 소스 역시 샘플 1 소스와 동일한 결과를 수행한다.
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
libgpiod 설치시 같이 제공되는 유틸리티는 다음과 같다.
$ 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 --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 --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 --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 --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; }