gpio: switch to new kernel API 25/261025/12
authorAdrian Szyndela <adrian.s@samsung.com>
Thu, 22 Jul 2021 10:17:58 +0000 (12:17 +0200)
committerMateusz Majewski <m.majewski2@samsung.com>
Wed, 11 Aug 2021 10:14:00 +0000 (12:14 +0200)
Change-Id: I628b7e88578f079d21d24513a1ecdfb3ee6ee255

CMakeLists.txt
src/peripheral_gpio.c
src/peripheral_gpio_modern_api.c [new file with mode: 0644]
src/peripheral_gpio_modern_api.h [new file with mode: 0644]

index ec2ba1a..ddae94a 100644 (file)
@@ -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
index c451594..0e8fd54 100644 (file)
@@ -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 (file)
index 0000000..21e68f9
--- /dev/null
@@ -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 <assert.h>
+#include <glib.h>
+#include <libudev.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..1fd99dd
--- /dev/null
@@ -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 <glib.h>
+
+#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();