From a05d4c892dbf7418abce4bdfb0fb34e3568467bc Mon Sep 17 00:00:00 2001 From: Adrian Szyndela Date: Thu, 22 Jul 2021 12:17:58 +0200 Subject: [PATCH] gpio: switch to new kernel API Change-Id: I628b7e88578f079d21d24513a1ecdfb3ee6ee255 --- CMakeLists.txt | 1 + src/peripheral_gpio.c | 31 +++-- src/peripheral_gpio_modern_api.c | 284 +++++++++++++++++++++++++++++++++++++++ src/peripheral_gpio_modern_api.h | 41 ++++++ 4 files changed, 349 insertions(+), 8 deletions(-) create mode 100644 src/peripheral_gpio_modern_api.c create mode 100644 src/peripheral_gpio_modern_api.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ec2ba1a..ddae94a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ ENDIF() SET(SOURCES src/peripheral_gpio.c src/peripheral_gpio_legacy_api.c + src/peripheral_gpio_modern_api.c src/peripheral_i2c.c src/peripheral_pwm.c src/peripheral_adc.c diff --git a/src/peripheral_gpio.c b/src/peripheral_gpio.c index c451594..0e8fd54 100644 --- a/src/peripheral_gpio.c +++ b/src/peripheral_gpio.c @@ -24,6 +24,7 @@ #include "peripheral_io.h" #include "peripheral_gpio_legacy_api.h" +#include "peripheral_gpio_modern_api.h" #include "common.h" #include "log.h" @@ -59,7 +60,11 @@ struct _peripheral_gpio_s { // otherwise update csapi accordingly int vermagic; interrupted_cb_info_s cb_info; - struct gpio_legacy_data legacy_data; + bool is_legacy; + union { + struct gpio_legacy_data legacy_data; + struct gpio_modern_data modern_data; + }; peripheral_gpio_direction_e direction; peripheral_gpio_edge_e edge; }; @@ -96,7 +101,17 @@ int peripheral_gpio_open(int gpio_pin, peripheral_gpio_h *gpio) handle->cb_info.thread = NULL; - ret = gpio_legacy_open(&handle->legacy_data, gpio_pin, &handle->direction, &handle->edge); + g_autoptr(GHashTable) chip_to_base = gpio_modern_read_chip_to_base_config(); + + if (!chip_to_base) { + _W("HAL chip config missing, legacy kernel API will be used.\n" + "If you have at least 4.8 kernel, please strongly consider using the modern kernel API instead."); + handle->is_legacy = true; + ret = gpio_legacy_open(&handle->legacy_data, gpio_pin, &handle->direction, &handle->edge); + } else { + handle->is_legacy = false; + ret = gpio_modern_open(&handle->modern_data, gpio_pin, chip_to_base, &handle->direction, &handle->edge); + } CHECK_ERROR(ret != PERIPHERAL_ERROR_NONE); *gpio = handle; @@ -116,7 +131,7 @@ int peripheral_gpio_close(peripheral_gpio_h gpio) peripheral_gpio_unset_interrupted_cb(gpio); - int ret = gpio_legacy_cleanup(&gpio->legacy_data); + int ret = gpio->is_legacy ? gpio_legacy_cleanup(&gpio->legacy_data) : gpio_modern_cleanup(&gpio->modern_data); free(gpio); @@ -147,7 +162,7 @@ int peripheral_gpio_set_direction(peripheral_gpio_h gpio, peripheral_gpio_direct RETVM_IF(gpio->cb_info.status != GPIO_INTERRUPTED_CALLBACK_UNSET, PERIPHERAL_ERROR_IO_ERROR, "Can't change direction to OUT while 'interrupted_cb' is set"); - int ret = gpio_legacy_set_direction(&gpio->legacy_data, direction); + int ret = gpio->is_legacy ? gpio_legacy_set_direction(&gpio->legacy_data, direction) : gpio_modern_set_direction_xor_edge_mode(&gpio->modern_data, direction, gpio->edge); if (ret != PERIPHERAL_ERROR_NONE) return ret; @@ -176,7 +191,7 @@ int peripheral_gpio_set_edge_mode(peripheral_gpio_h gpio, peripheral_gpio_edge_e RETV_IF(gpio->edge == edge, PERIPHERAL_ERROR_NONE); RETV_IF(gpio->direction != PERIPHERAL_GPIO_DIRECTION_IN, PERIPHERAL_ERROR_IO_ERROR); - int ret = gpio_legacy_set_edge_mode(&gpio->legacy_data, edge); + int ret = gpio->is_legacy ? gpio_legacy_set_edge_mode(&gpio->legacy_data, edge) : gpio_modern_set_direction_xor_edge_mode(&gpio->modern_data, gpio->direction, edge); if (ret != PERIPHERAL_ERROR_NONE) return ret; @@ -199,7 +214,7 @@ static gpointer __peripheral_gpio_poll(void *data) struct pollfd poll_fd; - poll_fd.fd = gpio_legacy_get_poll_fd(&gpio->legacy_data); + poll_fd.fd = gpio->is_legacy ? gpio_legacy_get_poll_fd(&gpio->legacy_data) : gpio_modern_get_poll_fd(&gpio->modern_data); poll_fd.events = POLLPRI; uint32_t value; @@ -305,7 +320,7 @@ int peripheral_gpio_read(peripheral_gpio_h gpio, uint32_t *value) * out --------> read (O) */ - return gpio_legacy_read(&gpio->legacy_data, value); + return gpio->is_legacy ? gpio_legacy_read(&gpio->legacy_data, value) : gpio_modern_read(&gpio->modern_data, value); } /** @@ -324,5 +339,5 @@ int peripheral_gpio_write(peripheral_gpio_h gpio, uint32_t value) */ RETV_IF(gpio->direction == PERIPHERAL_GPIO_DIRECTION_IN, PERIPHERAL_ERROR_IO_ERROR); - return gpio_legacy_write(&gpio->legacy_data, value); + return gpio->is_legacy ? gpio_legacy_write(&gpio->legacy_data, value) : gpio_modern_write(&gpio->modern_data, value); } diff --git a/src/peripheral_gpio_modern_api.c b/src/peripheral_gpio_modern_api.c new file mode 100644 index 0000000..21e68f9 --- /dev/null +++ b/src/peripheral_gpio_modern_api.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2016-2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat/linux/gpio.h" +#include "peripheral_io.h" +#include "peripheral_gpio_modern_api.h" +#include "common.h" +#include "log.h" + +/* Path format for device file */ +#define DEV_PATH_BASE(chip) ("/dev/gpiochip" chip) +#define DEV_PATH_FMT_MAX_SIZE sizeof(DEV_PATH_BASE(MAX_d_FMT)) +#define DEV_PATH_FMT DEV_PATH_BASE("%d") + +/* Format for gpio name */ +#define GPIO_NAME_BASE "gpio" +#define GPIO_NAME_FMT_MAX_SIZE sizeof(GPIO_NAME_BASE MAX_d_FMT) +#define GPIO_NAME_FMT (GPIO_NAME_BASE "%d") + +#define GPIO_BUFFER_MAX 64 + +#define GPIO_BASE "gpio" +#define GPIO_CHIP_CONF_PATH "/hal/etc/peripheral-io/gpio.ini" + +GHashTable *gpio_modern_read_chip_to_base_config() +{ + g_autoptr(GHashTable) tmp = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); + if (!tmp) { + /* glib seems to use a malloc wrapper that aborts on + * allocation failure, but as far as I can tell this + * may be configurable and also static analysis tools + * often complain. Better be safe and expect NULLs. */ + return NULL; + } + + g_autoptr(GKeyFile) conf = g_key_file_new(); + if (!conf) + return NULL; + if (!g_key_file_load_from_file(conf, GPIO_CHIP_CONF_PATH, G_KEY_FILE_NONE, NULL)) + return NULL; + + gsize len; + g_auto(GStrv) groups = g_key_file_get_groups(conf, &len); + if (!groups) + return NULL; + + for (gsize i = 0; i < len; ++i) { + gchar *name = g_key_file_get_string(conf, groups[i], "name", NULL); + if (name == NULL) + return NULL; + + g_autoptr(GError) gerror = NULL; + gint base = g_key_file_get_integer(conf, groups[i], "base", &gerror); + if (gerror != NULL) { + free(name); + return NULL; + } + + g_hash_table_insert(tmp, name, GINT_TO_POINTER(base)); + // NB: above means name will be freed when tmp is destroyed + } + + GHashTable *ret = tmp; + tmp = NULL; + return ret; +} + +int gpio_modern_cleanup(struct gpio_modern_data *mdata) +{ + assert(mdata); + + close_fd(mdata->fd_line); + close_fd(mdata->fd_chip); + + return PERIPHERAL_ERROR_NONE; +} + +static unsigned __eventflags_from_edge(peripheral_gpio_edge_e edge) { + switch (edge) { + case PERIPHERAL_GPIO_EDGE_RISING: + return GPIOEVENT_REQUEST_RISING_EDGE; + case PERIPHERAL_GPIO_EDGE_FALLING: + return GPIOEVENT_REQUEST_FALLING_EDGE; + case PERIPHERAL_GPIO_EDGE_BOTH: + return GPIOEVENT_REQUEST_BOTH_EDGES; + case PERIPHERAL_GPIO_EDGE_NONE: + default: + return 0; + } +} + +static int __create_lineevent(struct gpio_modern_data *mdata, peripheral_gpio_edge_e edge) +{ + assert(mdata); + + cleanup_fd(&mdata->fd_line); + + struct gpioevent_request req; + req.lineoffset = mdata->line; + req.handleflags = GPIOHANDLE_REQUEST_INPUT; + req.eventflags = __eventflags_from_edge(edge); + req.consumer_label[0] = 0; + + int ret = ioctl(mdata->fd_chip, GPIO_GET_LINEEVENT_IOCTL, &req); + CHECK_ERROR(ret == -1); + + mdata->fd_line = req.fd; + + return PERIPHERAL_ERROR_NONE; +} + +static int __create_linehandle(struct gpio_modern_data *mdata, peripheral_gpio_direction_e direction) +{ + assert(mdata); + + struct gpiohandle_request req; + req.lineoffsets[0] = mdata->line; + req.lines = 1; + req.flags = GPIOHANDLE_REQUEST_OUTPUT; + req.default_values[0] = direction == PERIPHERAL_GPIO_DIRECTION_OUT_INITIALLY_HIGH; + req.consumer_label[0] = 0; + + int ret = ioctl(mdata->fd_chip, GPIO_GET_LINEHANDLE_IOCTL, &req); + CHECK_ERROR(ret == -1); + + mdata->fd_line = req.fd; + + return PERIPHERAL_ERROR_NONE; +} + +/* This closes currently open fd_line to create a new one, which will have desired settings. + * To be called after changing direction or edge. + * It selects lineevent or linehandle, depending on the settings. + * They can't both exist at the same time - that's why we're juggling it here. + */ +int gpio_modern_set_direction_xor_edge_mode(struct gpio_modern_data *mdata, peripheral_gpio_direction_e direction, peripheral_gpio_edge_e edge) +{ + assert(mdata); + + /* We take advisory lock here to ensure that no other client takes the pin + between close_fd() and one of __create_line{event,handle} functions. */ + int ret = flock(mdata->fd_chip, LOCK_EX); + CHECK_ERROR(ret != 0); + + cleanup_fd(&mdata->fd_line); + + if (direction == PERIPHERAL_GPIO_DIRECTION_IN) + ret = __create_lineevent(mdata, edge); + else + ret = __create_linehandle(mdata, direction); + + /* Unlock at the end */ + flock(mdata->fd_chip, LOCK_UN); + return ret; +} + +int gpio_modern_open(struct gpio_modern_data *mdata, int gpio_pin, GHashTable *chip_to_base, peripheral_gpio_direction_e *direction_out, peripheral_gpio_edge_e *edge_out) +{ + assert(mdata); + assert(chip_to_base); + assert(direction_out); + assert(edge_out); + + __attribute__((cleanup(cleanup_fd))) int fd_line = -1; + __attribute__((cleanup(cleanup_fd))) int fd_chip = -1; + int line = -1; + + for (int id = 0; ; ++id) { + char path[DEV_PATH_FMT_MAX_SIZE] = {}; + snprintf(path, sizeof(path), DEV_PATH_FMT, id); + + /* NB: It's actually very important that we set the fd in the structure. + * This way, it gets automagically closed when it should be. */ + fd_chip = open(path, 0); + if (fd_chip < 0 && errno == ENOENT) { + _E("Pin not found"); + return PERIPHERAL_ERROR_INVALID_PARAMETER; + } + CHECK_ERROR(fd_chip < 0); + + struct gpiochip_info chip_info; + int r = ioctl(fd_chip, GPIO_GET_CHIPINFO_IOCTL, &chip_info); + CHECK_ERROR(r < 0); + + gpointer base_as_ptr; + bool found = g_hash_table_lookup_extended(chip_to_base, chip_info.label, NULL, &base_as_ptr); + if (!found) + continue; + int base = GPOINTER_TO_INT(base_as_ptr); + + if (gpio_pin >= base && gpio_pin < base + chip_info.lines) { + line = gpio_pin - base; + break; + } else { + cleanup_fd(&fd_chip); + } + } + + struct gpioline_info line_info; + memset(&line_info, 0, sizeof(line_info)); + line_info.line_offset = line; + + int ret = ioctl(fd_chip, GPIO_GET_LINEINFO_IOCTL, &line_info); + CHECK_ERROR(ret == -1); + + if (line_info.flags & GPIOLINE_FLAG_IS_OUT) + *direction_out = PERIPHERAL_GPIO_DIRECTION_OUT_INITIALLY_LOW; + else + *direction_out = PERIPHERAL_GPIO_DIRECTION_IN; + *edge_out = PERIPHERAL_GPIO_EDGE_NONE; + + mdata->fd_line = fd_line; + mdata->fd_chip = fd_chip; + mdata->line = line; + + fd_line = -1; + fd_chip = -1; + + ret = gpio_modern_set_direction_xor_edge_mode(mdata, *direction_out, *edge_out); + if (ret != PERIPHERAL_ERROR_NONE) { + cleanup_fd(&mdata->fd_line); + cleanup_fd(&mdata->fd_chip); + return ret; + } + + return PERIPHERAL_ERROR_NONE; +} + +int gpio_modern_read(struct gpio_modern_data *mdata, uint32_t *value) +{ + assert(mdata); + assert(value); + + struct gpiohandle_data data; + int ret = ioctl(mdata->fd_line, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); + CHECK_ERROR(ret == -1); + + assert(data.values[0] == 0 || data.values[0] == 1); + *value = data.values[0]; + + return PERIPHERAL_ERROR_NONE; +} + +int gpio_modern_write(struct gpio_modern_data *mdata, uint32_t value) +{ + assert(mdata); + + struct gpiohandle_data data; + data.values[0] = value; + int ret = ioctl(mdata->fd_line, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); + CHECK_ERROR(ret == -1); + + return PERIPHERAL_ERROR_NONE; +} + +int gpio_modern_get_poll_fd(struct gpio_modern_data *mdata) +{ + assert(mdata); + + return mdata->fd_line; +} diff --git a/src/peripheral_gpio_modern_api.h b/src/peripheral_gpio_modern_api.h new file mode 100644 index 0000000..1fd99dd --- /dev/null +++ b/src/peripheral_gpio_modern_api.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016-2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "peripheral_io.h" + +/* NB: This struct should really be an opaque one, i.e. should be defined + * in the corresponding .c file instead and should be only used elsewhere + * via the functions defined below. Alas, in _peripheral_gpio_s, we want + * to have an instance of this struct as a field. If this struct were opaque, + * we could only put an ugly pointer there. */ +struct gpio_modern_data { + int line; + int fd_line; + int fd_chip; +}; + +int gpio_modern_open(struct gpio_modern_data *mdata, int gpio_pin, GHashTable *chip_to_base, peripheral_gpio_direction_e *direction_out, peripheral_gpio_edge_e *edge_out); +int gpio_modern_read(struct gpio_modern_data *mdata, uint32_t *value); +int gpio_modern_write(struct gpio_modern_data *mdata, uint32_t value); +int gpio_modern_set_direction_xor_edge_mode(struct gpio_modern_data *mdata, peripheral_gpio_direction_e direction, peripheral_gpio_edge_e edge); +int gpio_modern_cleanup(struct gpio_modern_data *mdata); +int gpio_modern_get_poll_fd(struct gpio_modern_data *mdata); + +GHashTable *gpio_modern_read_chip_to_base_config(); -- 2.7.4