common/recipes-lib/gpio-ctrl/files/gpio.c (622 lines of code) (raw):

/* * Copyright 2019-present Facebook. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <poll.h> #include <pthread.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <assert.h> #include <libgen.h> #include <sys/stat.h> #include <sys/types.h> #include <linux/limits.h> #include "gpio_int.h" #define GPIO_SHADOW_PATH_MAX 128 /* * Global variables. */ #define GPIO_DEF(type, str) [type] = str static const char* g_value_str[GPIO_VALUE_MAX] = { GPIO_VALUE_TYPES }; static const char* g_direction_str[GPIO_DIRECTION_MAX] = { GPIO_DIRECTION_TYPES, }; static const char* g_edge_str[GPIO_EDGE_MAX] = { GPIO_EDGE_TYPES, }; #undef GPIO_DEF const char* gpio_value_type_to_str(gpio_value_t val) { if (IS_VALID_GPIO_VALUE(val)) return g_value_str[val]; return ""; } gpio_value_t gpio_value_str_to_type(const char *val_str) { int i; if (val_str == NULL) return GPIO_VALUE_INVALID; for (i = 0; i < ARRAY_SIZE(g_value_str); i++) if (strcmp(val_str, g_value_str[i]) == 0) return i; return GPIO_VALUE_INVALID; } const char* gpio_direction_type_to_str(gpio_direction_t dir) { if (IS_VALID_GPIO_DIRECTION(dir)) return g_direction_str[dir]; return ""; } gpio_direction_t gpio_direction_str_to_type(const char *dir_str) { int i; if (dir_str == NULL) return GPIO_DIRECTION_INVALID; for (i = 0; i < ARRAY_SIZE(g_direction_str); i++) if (strcmp(dir_str, g_direction_str[i]) == 0) return i; return GPIO_DIRECTION_INVALID; } const char* gpio_edge_type_to_str(gpio_edge_t edge) { if (IS_VALID_GPIO_EDGE(edge)) return g_edge_str[edge]; return ""; } gpio_edge_t gpio_edge_str_to_type(const char *edge_str) { int i; if (edge_str == NULL) return GPIO_EDGE_INVALID; for (i = 0; i < ARRAY_SIZE(g_edge_str); i++) if (strcmp(edge_str, g_edge_str[i]) == 0) return i; return GPIO_EDGE_INVALID; } /* * Internal helper functions. */ static int create_shadow_dir(void) { if (!path_isdir(GPIO_SHADOW_ROOT)) { GLOG_DEBUG("create <%s> direction", GPIO_SHADOW_ROOT); if (mkdir(GPIO_SHADOW_ROOT, 0755) != 0) { GLOG_ERR("failed to create <%s>: %s\n", GPIO_SHADOW_ROOT, strerror(errno)); return -1; } } return 0; } static char* gpio_shadow_abspath(char *buf, size_t size, const char* shadow) { return path_join(buf, size, GPIO_SHADOW_ROOT, shadow, NULL); } static int gpio_shadow_to_num(const char *shadow_path) { int len, pin_num; char *base_name; char target_path[GPIO_SHADOW_PATH_MAX]; len = readlink(shadow_path, target_path, sizeof(target_path)); if (len < 0) { GLOG_ERR("failed to read target from symlink <%s>: %s\n", shadow_path, strerror(errno)); return -1; } else if (len >= sizeof(target_path)) { GLOG_ERR("target path of symlink <%s> is truncated\n", shadow_path); return -1; } target_path[len] = '\0'; base_name = basename(target_path); if (!str_startswith(base_name, "gpio")) { GLOG_ERR("unable to extract gpio number from path <%s>", target_path); return -1; } pin_num = (int)strtol(&base_name[strlen("gpio")], NULL, 10); GLOG_DEBUG("shadow <%s> is mapped to gpio pin %d\n", shadow_path, pin_num); return pin_num; } static int gpio_name_to_num(const char *chip, const char *pin_name) { int base, offset, pin_num; gpiochip_desc_t *gcdesc; gcdesc = gpiochip_lookup(chip); if (gcdesc == NULL) { GLOG_ERR("failed to find gpio chip <%s>\n", chip); return -1; } offset = gpiochip_pin_name_to_offset(gcdesc, pin_name); if (offset < 0) return -1; base = gpiochip_get_base(gcdesc); pin_num = base + offset; GLOG_DEBUG("(%s, %s) is mapped to gpio pin %d (%d + %d)\n", chip, pin_name, pin_num, base, offset); return pin_num; } static int gpio_offset_to_num(const char *chip, int offset) { int base, pin_num; gpiochip_desc_t *gcdesc; gcdesc = gpiochip_lookup(chip); if (gcdesc == NULL) { GLOG_ERR("failed to find gpio chip <%s>\n", chip); return -1; } base = gpiochip_get_base(gcdesc); pin_num = base + offset; GLOG_DEBUG("(%s, %d) is mapped to gpio pin %d (%d + %d)\n", chip, offset, pin_num, base, offset); return pin_num; } static gpio_desc_t* gpio_desc_alloc(int pin_num, const char *shadow_path) { gpio_desc_t *gdesc; gdesc = malloc(sizeof(*gdesc)); if (gdesc == NULL) { return NULL; } memset(gdesc, 0, sizeof(*gdesc)); gdesc->pin_num = pin_num; if (shadow_path != NULL) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic ignored "-Wstringop-truncation" strncpy(gdesc->shadow_path, shadow_path, sizeof(gdesc->shadow_path) - 1); #pragma GCC diagnostic pop } return gdesc; } static void gpio_desc_free(gpio_desc_t *gdesc) { if (gdesc != NULL) free(gdesc); } /* * Public functions to export/unexport control of gpio pins to userspace. */ int gpio_export_by_name(const char *chip, const char *name, const char *shadow) { int pin_num; char shadow_path[GPIO_SHADOW_PATH_MAX]; if (chip == NULL || name == NULL || shadow == NULL) { errno = EINVAL; return -1; } if (create_shadow_dir() != 0) { return -1; } gpio_shadow_abspath(shadow_path, sizeof(shadow_path), shadow); if (path_exists(shadow_path)) { errno = EEXIST; return -1; } pin_num = gpio_name_to_num(chip, name); if (pin_num < 0) { errno = ENXIO; return -1; } return GPIO_OPS()->export_pin(pin_num, shadow_path); } int gpio_export_by_offset(const char *chip, int offset, const char *shadow) { int pin_num; char shadow_path[GPIO_SHADOW_PATH_MAX]; if (chip == NULL || offset < 0 || shadow == NULL) { errno = EINVAL; return -1; } if (create_shadow_dir() != 0) { return -1; } gpio_shadow_abspath(shadow_path, sizeof(shadow_path), shadow); if (path_exists(shadow_path)) { errno = EEXIST; return -1; } pin_num = gpio_offset_to_num(chip, offset); if (pin_num < 0) { errno = ENXIO; return -1; } return GPIO_OPS()->export_pin(pin_num, shadow_path); } int gpio_unexport(const char *shadow) { int pin_num; char shadow_path[GPIO_SHADOW_PATH_MAX]; if (shadow == NULL) { errno = EINVAL; return -1; } gpio_shadow_abspath(shadow_path, sizeof(shadow_path), shadow); if (!path_islink(shadow_path)) { errno = ENOENT; return -1; } pin_num = gpio_shadow_to_num(shadow_path); if (pin_num < 0) { errno = ENXIO; return -1; } return GPIO_OPS()->unexport_pin(pin_num, shadow_path); } bool gpio_is_exported(const char * shadow) { char path[64] = {0}; snprintf(path, sizeof(path), "%s/%s", GPIO_SHADOW_ROOT, shadow); return (access(path, F_OK) == 0); } /* * Public functions to open/close a specific gpio pin. */ gpio_desc_t* gpio_open_by_shadow(const char *shadow) { int pin_num; gpio_desc_t *gdesc; char shadow_path[GPIO_SHADOW_PATH_MAX]; gpio_shadow_abspath(shadow_path, sizeof(shadow_path), shadow); if (!path_islink(shadow_path)) { errno = ENOENT; return NULL; } pin_num = gpio_shadow_to_num(shadow_path); if (pin_num < 0) { errno = ENXIO; return NULL; } gdesc = gpio_desc_alloc(pin_num, shadow_path); if (gdesc == NULL) { errno = ENOMEM; return NULL; } if (GPIO_OPS()->open_pin(gdesc) != 0) { gpio_desc_free(gdesc); return NULL; } return gdesc; } gpio_desc_t* gpio_open_by_name(const char *chip, const char *name) { int pin_num; gpio_desc_t *gdesc; if (chip == NULL || name == NULL) { errno = EINVAL; return NULL; } pin_num = gpio_name_to_num(chip, name); if (pin_num < 0) { errno = ENXIO; return NULL; } gdesc = gpio_desc_alloc(pin_num, NULL); if (gdesc == NULL) { errno = ENOMEM; return NULL; } if (GPIO_OPS()->open_pin(gdesc) != 0) { gpio_desc_free(gdesc); return NULL; } return gdesc; } gpio_desc_t* gpio_open_by_offset(const char *chip, int offset) { int pin_num; gpio_desc_t *gdesc; if (chip == NULL || offset < 0) { errno = EINVAL; return NULL; } pin_num = gpio_offset_to_num(chip, offset); if (pin_num < 0) { errno = ENXIO; return NULL; } gdesc = gpio_desc_alloc(pin_num, NULL); if (gdesc == NULL) { errno = ENOMEM; return NULL; } if (GPIO_OPS()->open_pin(gdesc) != 0) { gpio_desc_free(gdesc); return NULL; } return gdesc; } int gpio_close(gpio_desc_t *gdesc) { if (gdesc != NULL) { GPIO_OPS()->close_pin(gdesc); gpio_desc_free(gdesc); } return 0; } /* * Public functions to get/set a gpio pin's attributes, such as value, * direction and etc. */ int gpio_get_value(gpio_desc_t *gdesc, gpio_value_t *value) { if (!IS_VALID_GPIO_DESC(gdesc) || value == NULL) { errno = EINVAL; return -1; } return GPIO_OPS()->get_pin_value(gdesc, value); } int gpio_set_value(gpio_desc_t *gdesc, gpio_value_t value) { if (!IS_VALID_GPIO_DESC(gdesc) || !IS_VALID_GPIO_VALUE(value)) { errno = EINVAL; return -1; } return GPIO_OPS()->set_pin_value(gdesc, value); } int gpio_get_direction(gpio_desc_t *gdesc, gpio_direction_t *dir) { if (!IS_VALID_GPIO_DESC(gdesc) || dir == NULL) { errno = EINVAL; return -1; } return GPIO_OPS()->get_pin_direction(gdesc, dir); } int gpio_set_direction(gpio_desc_t *gdesc, gpio_direction_t dir) { if (!IS_VALID_GPIO_DESC(gdesc) || !IS_VALID_GPIO_DIRECTION(dir)) { errno = EINVAL; return -1; } return GPIO_OPS()->set_pin_direction(gdesc, dir); } int gpio_get_edge(gpio_desc_t *gdesc, gpio_edge_t *edge) { if (!IS_VALID_GPIO_DESC(gdesc) || edge == NULL) { errno = EINVAL; return -1; } return GPIO_OPS()->get_pin_edge(gdesc, edge); } int gpio_set_edge(gpio_desc_t *gdesc, gpio_edge_t edge) { if (!IS_VALID_GPIO_DESC(gdesc) || !IS_VALID_GPIO_EDGE(edge)) { errno = EINVAL; return -1; } return GPIO_OPS()->set_pin_edge(gdesc, edge); } int gpio_set_init_value(gpio_desc_t *gdesc, gpio_value_t value) { if (!IS_VALID_GPIO_DESC(gdesc) || !IS_VALID_GPIO_VALUE(value)) { errno = EINVAL; return -1; } return GPIO_OPS()->set_pin_init_value(gdesc, value); } gpio_value_t gpio_get_value_by_shadow(const char *shadow) { gpio_value_t value = GPIO_VALUE_INVALID; gpio_desc_t *desc = gpio_open_by_shadow(shadow); if (!desc) { return GPIO_VALUE_INVALID; } if (gpio_get_value(desc, &value)) { value = GPIO_VALUE_INVALID; } gpio_close(desc); return value; } int gpio_set_value_by_shadow(const char *shadow, gpio_value_t value) { gpio_desc_t *desc = gpio_open_by_shadow(shadow); if (!desc) { return -1; } if (gpio_set_value(desc, value)) { return -1; } gpio_close(desc); return 0; } int gpio_set_init_value_by_shadow(const char *shadow, gpio_value_t value) { gpio_desc_t *desc = gpio_open_by_shadow(shadow); if (!desc) { return -1; } if (gpio_set_init_value(desc, value)) { gpio_close(desc); return -1; } gpio_close(desc); return 0; } gpiopoll_desc_t* gpio_poll_open(struct gpiopoll_config *config, size_t num_config) { int i; gpiopoll_desc_t *ret; ret = calloc(1, sizeof(gpiopoll_desc_t)); if (!ret) { return NULL; } ret->num_pins = num_config; ret->pins = calloc(num_config, sizeof(ret->pins[0])); if (!ret->pins) { goto err_pins_alloc_bail; } for (i = 0; i < num_config; i++) { gpiopoll_pin_t *desc = &ret->pins[i]; desc->handler_started = false; desc->cfg = config[i]; if (config[i].handler == NULL || config[i].shadow[0] == '\0') { GLOG_ERR("Incorrect configuration at index: %d\n", i); goto err_bail; } desc->gpio = gpio_open_by_shadow(desc->cfg.shadow); if (!desc->gpio) { GLOG_ERR("Failed to open GPIO by shadow: %s <%s>\n", desc->cfg.shadow, strerror(errno)); goto err_bail; } if (gpio_set_direction(desc->gpio, GPIO_DIRECTION_IN)) { GLOG_ERR("Failed to set direction of GPIO: %s <%s>\n", desc->cfg.shadow, strerror(errno)); goto err_bail; } if (gpio_set_edge(desc->gpio, desc->cfg.edge)) { GLOG_ERR("Failed to set edge on GPIO: %s <%s>\n", desc->cfg.shadow, strerror(errno)); goto err_bail; } if (gpio_get_value(desc->gpio, &desc->last_value)) { GLOG_ERR("Failed to get value of GPIO: %s <%s>\n", desc->cfg.shadow, strerror(errno)); goto err_bail; } desc->curr_value = desc->last_value; if (desc->cfg.init_value) { desc->cfg.init_value(desc, desc->curr_value); } } return ret; err_bail: for (i = 0; i < num_config; i++) { gpiopoll_pin_t *desc = &ret->pins[i]; if (desc->gpio) gpio_close(desc->gpio); } free(ret->pins); err_pins_alloc_bail: free(ret); return NULL; } int gpio_poll_close(gpiopoll_desc_t *gpdesc) { int i; if (!gpdesc || !gpdesc->pins) { return -1; } for (i = 0; i < gpdesc->num_pins; i++) { gpiopoll_pin_t *desc = &gpdesc->pins[i]; if (desc->handler_started) { int rc = pthread_cancel(desc->tid); if(rc != 0) { GLOG_ERR("Kill of thread failed for GPIO: %s <%s>\n", desc->cfg.shadow, strerror(rc)); } desc->handler_started = false; } if (gpio_close(desc->gpio)) { GLOG_ERR("Close failed for GPIO: %s <%s>\n", desc->cfg.shadow, strerror(errno)); } } free(gpdesc->pins); free(gpdesc); return 0; } static void *gpio_poll_handler(void *priv) { gpiopoll_pin_t *desc = (gpiopoll_pin_t *)priv; int rc; assert(GPIO_OPS()->poll_pin != NULL); while (1) { rc = GPIO_OPS()->poll_pin(desc->gpio, desc->timeout); if (rc < 0) { GLOG_ERR("Wait failed with rc=%d for GPIO: %s <%s>\n", rc, desc->cfg.shadow, strerror(errno)); break; } desc->last_value = desc->curr_value; if (gpio_get_value(desc->gpio, &desc->curr_value)) { GLOG_ERR("Getting current value failed for GPIO: %s <%s>\n", desc->cfg.shadow, strerror(errno)); break; } desc->cfg.handler(desc, desc->last_value, desc->curr_value); } return NULL; } int gpio_poll(gpiopoll_desc_t *gpdesc, int timeout) { int i; if (!gpdesc || !gpdesc->pins) { return -1; } for (i = 0; i < gpdesc->num_pins; i++) { int rc; gpiopoll_pin_t *desc = &gpdesc->pins[i]; assert(desc->handler_started == false); desc->timeout = timeout; rc = pthread_create(&desc->tid, NULL, gpio_poll_handler, desc); if (rc) { GLOG_ERR("Create of handler thread failed for GPIO: %s <%s>\n", desc->cfg.shadow, strerror(rc)); if (gpio_close(desc->gpio)) { GLOG_ERR("Close failed for GPIO: %s <%s>\n", desc->cfg.shadow, strerror(errno)); } continue; } desc->handler_started = true; } for (i = 0; i < gpdesc->num_pins; i++) { int rc; void *res; gpiopoll_pin_t *desc = &gpdesc->pins[i]; if (!desc->handler_started) { continue; } rc = pthread_join(desc->tid, &res); if (rc != 0) { GLOG_ERR("Pthread_join fialed for %s <%s>\n", desc->cfg.shadow, strerror(rc)); } else if (res == PTHREAD_CANCELED) { GLOG_ERR("Potential race condition between close and poll"); } } return 0; } const struct gpiopoll_config *gpio_poll_get_config(gpiopoll_pin_t *gpdesc) { if (!gpdesc) { return NULL; } return &gpdesc->cfg; } gpio_desc_t *gpio_poll_get_descriptor(gpiopoll_pin_t *gpdesc) { if (!gpdesc) { return NULL; } return gpdesc->gpio; } int gpio_get_value_by_shadow_list(const char *const *shadows, size_t num, unsigned int *mask) { size_t i; if (num > sizeof(*mask) * 8 || !shadows || !mask) { errno = EINVAL; return -1; } *mask = 0; for (i = 0; i < num; i++) { gpio_value_t value = gpio_get_value_by_shadow(shadows[i]); if (value == GPIO_VALUE_INVALID) { return -1; } *mask |= (value == GPIO_VALUE_HIGH ? 1 : 0) << i; } return 0; } int gpio_set_value_by_shadow_list(const char *const *shadows, size_t num, unsigned int mask) { size_t i; if (num > sizeof(mask) * 8 || !shadows) { errno = EINVAL; return -1; } for (i = 0; i < num; i++) { gpio_value_t value = (mask & (1 << i)) ? GPIO_VALUE_HIGH : GPIO_VALUE_LOW; if (gpio_set_value_by_shadow(shadows[i], value)) { return -1; } } return 0; }