#include "peripheral_io.h"
#include "peripheral_gpio_legacy_api.h"
+#include "peripheral_gpio_modern_api.h"
#include "common.h"
#include "log.h"
// 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;
};
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;
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);
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;
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;
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;
* 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);
}
/**
*/
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);
}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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();