새로운 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; }
참고
- GPIO Subsystem -1- | 문c
- GPIO Subsystem -2- | 문c
- GPIO Subsystem -3- (Device Tree) | 문c
- GPIO Subsystem -4- (new Interface) | 문c – 현재 글
- Pin Control Subsystem -1- | 문c
- Pin Control Subsystem -2- | 문c
- Interrupts -1- (Interrupt Controller) | 문c
- Interrupts -2- (irq chip) | 문c
- Interrupts -3- (irq domain) | 문c
- Documentation/gpio – kernel.org
- Specifying GPIO information for devices – Documentation/devicetree/bindings/gpio/gpio.txt | kernel.org
- GPIO in the kernel: an introduction | LWN.net
- GPIO in the kernel: future directions | LWN.net
- GPIO for Engineers and Makers | Linus Walleij, Linaro – 다운로드 pdf
- The Linux input driver subsystem | The kernel development community
- 리눅스 어플리케이션에서 GPIO 사용하기 | 방바닥 디자인
- Beaglebone으로 알아보는 linux kernel(3.10.x) programming | SLOWBOOT
- [Linux kernel] GPIO 정리 | 다솜돌이
- [번역] Linux의 새로운 GPIO 사용자 공간 하위 시스템 및 Libgpiod에 대해 자세히 알아보십시오. | Eddy Lab
- Build libgpiod the “New GPIO Interface for User Space | Armbian