1
0
mirror of https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git synced 2026-10-24 00:48:53 +03:00
Files
libgpiod/dbus/client/common.c
Bartosz Golaszewski a5ab76da1e dbus: add the D-Bus daemon, command-line client and tests
Add the D-Bus API definition and its implementation in the form of a GPIO
manager daemon and a companion command-line client as well as some
additional configuration and data files (systemd service, example udev
configuration, etc.) and test suites.

Tested-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
Link: https://lore.kernel.org/r/20240812-dbus-v5-4-ead288509217@linaro.org
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
2024-08-13 10:45:25 +02:00

647 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2022-2023 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
#include <glib/gprintf.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "common.h"
static void print_err_msg(GError *err, const gchar *fmt, va_list va)
{
g_printerr("%s: ", g_get_prgname());
g_vfprintf(stderr, fmt, va);
if (err)
g_printerr(": %s", err->message);
g_printerr("\n");
}
void die(const gchar *fmt, ...)
{
va_list va;
va_start(va, fmt);
print_err_msg(NULL, fmt, va);
va_end(va);
exit(EXIT_FAILURE);
}
void die_gerror(GError *err, const gchar *fmt, ...)
{
va_list va;
va_start(va, fmt);
print_err_msg(err, fmt, va);
va_end(va);
exit(EXIT_FAILURE);
}
void die_parsing_opts(const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
print_err_msg(NULL, fmt, va);
va_end(va);
g_printerr("\nSee %s --help\n", g_get_prgname());
exit(EXIT_FAILURE);
}
void parse_options(const GOptionEntry *opts, const gchar *summary,
const gchar *description, int *argc, char ***argv)
{
g_autoptr(GOptionContext) ctx = NULL;
g_autoptr(GError) err = NULL;
gboolean ret;
ctx = g_option_context_new(NULL);
g_option_context_set_summary(ctx, summary);
g_option_context_set_description(ctx, description);
g_option_context_add_main_entries(ctx, opts, NULL);
g_option_context_set_strict_posix(ctx, TRUE);
ret = g_option_context_parse(ctx, argc, argv, &err);
if (!ret) {
g_printerr("%s: Option parsing failed: %s\nSee %s --help\n",
g_get_prgname(), err->message, g_get_prgname());
exit(EXIT_FAILURE);
}
}
void check_manager(void)
{
g_autoptr(GDBusProxy) proxy = NULL;
g_autoptr(GVariant) result = NULL;
g_autoptr(GError) err = NULL;
proxy = g_dbus_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL,
"io.gpiod1", "/io/gpiod1", "org.freedesktop.DBus.Peer",
NULL, &err);
if (!proxy)
die_gerror(err, "Unable to create a proxy to '/io/gpiod1'");
result = g_dbus_proxy_call_sync(proxy, "Ping", NULL,
G_DBUS_CALL_FLAGS_NONE, -1, NULL,
&err);
if (!result) {
if (err->domain == G_DBUS_ERROR) {
switch (err->code) {
case G_DBUS_ERROR_ACCESS_DENIED:
die("Access to gpio-manager denied, check your permissions");
case G_DBUS_ERROR_SERVICE_UNKNOWN:
die("gpio-manager not running");
}
}
die_gerror(err, "Failed trying to contect the gpio manager");
}
}
gboolean quit_main_loop_on_signal(gpointer user_data)
{
GMainLoop *loop = user_data;
g_main_loop_quit(loop);
return G_SOURCE_REMOVE;
}
void die_on_name_vanished(GDBusConnection *con G_GNUC_UNUSED,
const gchar *name G_GNUC_UNUSED,
gpointer user_data G_GNUC_UNUSED)
{
die("gpio-manager exited unexpectedly");
}
GList *strv_to_gstring_list(GStrv lines)
{
gsize llen = g_strv_length(lines);
GList *list = NULL;
guint i;
for (i = 0; i < llen; i++)
list = g_list_append(list, g_string_new(lines[i]));
return list;
}
gint output_value_from_str(const gchar *value_str)
{
if ((g_strcmp0(value_str, "active") == 0) ||
(g_strcmp0(value_str, "1") == 0))
return 1;
else if ((g_strcmp0(value_str, "inactive") == 0) ||
(g_strcmp0(value_str, "0") == 0))
return 0;
die_parsing_opts("invalid output value: '%s'", value_str);
}
static gboolean str_is_all_digits(const gchar *str)
{
for (; *str; str++) {
if (!g_ascii_isdigit(*str))
return FALSE;
}
return TRUE;
}
static gint compare_objs_by_path(GDBusObject *a, GDBusObject *b)
{
return strverscmp(g_dbus_object_get_object_path(a),
g_dbus_object_get_object_path(b));
}
GDBusObjectManager *get_object_manager_client(const gchar *obj_path)
{
g_autoptr(GDBusObjectManager) manager = NULL;
g_autoptr(GError) err = NULL;
manager = gpiodbus_object_manager_client_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
"io.gpiod1", obj_path, NULL, &err);
if (!manager)
die_gerror(err,
"failed to create the object manager client for %s",
obj_path);
return g_object_ref(manager);
}
static gchar *make_chip_obj_path(const gchar *chip)
{
return g_strdup_printf(
str_is_all_digits(chip) ?
"/io/gpiod1/chips/gpiochip%s" :
"/io/gpiod1/chips/%s",
chip);
}
GpiodbusObject *get_chip_obj_by_path(const gchar *obj_path)
{
g_autoptr(GDBusObjectManager) manager = NULL;
g_autoptr(GpiodbusObject) chip_obj = NULL;
manager = get_object_manager_client("/io/gpiod1/chips");
chip_obj = GPIODBUS_OBJECT(g_dbus_object_manager_get_object(manager,
obj_path));
if (!chip_obj)
die("No such chip object: '%s'", obj_path);
return g_object_ref(chip_obj);
}
GpiodbusObject *get_chip_obj(const gchar *chip_name)
{
g_autofree gchar *chip_path = make_chip_obj_path(chip_name);
return get_chip_obj_by_path(chip_path);
}
GList *get_chip_objs(GStrv chip_names)
{
g_autoptr(GDBusObjectManager) manager = NULL;
GList *objs = NULL;
gint i;
manager = get_object_manager_client("/io/gpiod1/chips");
if (!chip_names)
return g_list_sort(g_dbus_object_manager_get_objects(manager),
(GCompareFunc)compare_objs_by_path);
for (i = 0; chip_names[i]; i++) {
g_autofree gchar *obj_path = make_chip_obj_path(chip_names[i]);
g_autoptr(GpiodbusObject) obj = NULL;
obj = GPIODBUS_OBJECT(
g_dbus_object_manager_get_object(manager, obj_path));
if (!obj)
die("No such chip: '%s'", chip_names[i]);
objs = g_list_insert_sorted(objs, g_object_ref(obj),
(GCompareFunc)compare_objs_by_path);
}
return objs;
}
gchar *make_request_obj_path(const gchar *request)
{
return g_strdup_printf(
str_is_all_digits(request) ?
"/io/gpiod1/requests/request%s" :
"/io/gpiod1/requests/%s",
request);
}
GpiodbusObject *get_request_obj(const gchar *request_name)
{
g_autoptr(GDBusObjectManager) manager = NULL;
g_autoptr(GpiodbusObject) req_obj = NULL;
g_autofree gchar *obj_path = NULL;
manager = get_object_manager_client("/io/gpiod1/requests");
obj_path = make_request_obj_path(request_name);
req_obj = GPIODBUS_OBJECT(g_dbus_object_manager_get_object(manager,
obj_path));
if (!req_obj)
die("No such request: '%s'", request_name);
return g_object_ref(req_obj);
}
GList *get_request_objs(void)
{
g_autoptr(GDBusObjectManager) manager = NULL;
GList *objs = NULL;
manager = get_object_manager_client("/io/gpiod1/requests");
objs = g_dbus_object_manager_get_objects(manager);
return g_list_sort(objs, (GCompareFunc)compare_objs_by_path);
}
GArray *get_request_offsets(GpiodbusRequest *request)
{
const gchar *chip_path, *line_path, *const *line_paths;
g_autoptr(GDBusObjectManager) manager = NULL;
g_autoptr(GArray) offsets = NULL;
GpiodbusLine *line;
guint i, offset;
chip_path = gpiodbus_request_get_chip_path(request);
line_paths = gpiodbus_request_get_line_paths(request);
offsets = g_array_new(FALSE, TRUE, sizeof(guint));
manager = get_object_manager_client(chip_path);
for (i = 0, line_path = line_paths[i];
line_path;
line_path = line_paths[++i]) {
g_autoptr(GDBusObject) line_obj = NULL;
line_obj = g_dbus_object_manager_get_object(manager, line_path);
line = gpiodbus_object_peek_line(GPIODBUS_OBJECT(line_obj));
offset = gpiodbus_line_get_offset(line);
g_array_append_val(offsets, offset);
}
return g_array_ref(offsets);
}
gboolean get_line_obj_by_name(const gchar *name, GpiodbusObject **line_obj,
GpiodbusObject **chip_obj)
{
g_autolist(GpiodbusObject) chip_objs = NULL;
GList *pos;
if (str_is_all_digits(name))
die("Refusing to use line offsets if chip is not specified");
chip_objs = get_chip_objs(NULL);
for (pos = g_list_first(chip_objs); pos; pos = g_list_next(pos)) {
*line_obj = get_line_obj_by_name_for_chip(pos->data, name);
if (*line_obj) {
if (chip_obj)
*chip_obj = g_object_ref(pos->data);
return TRUE;
}
}
return FALSE;
}
GpiodbusObject *
get_line_obj_by_name_for_chip(GpiodbusObject *chip_obj, const gchar *line_name)
{
g_autoptr(GDBusObjectManager) manager = NULL;
g_autolist(GpiodbusObject) line_objs = NULL;
const gchar *chip_path;
GpiodbusLine *line;
guint64 offset;
GList *pos;
chip_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(chip_obj));
manager = get_object_manager_client(chip_path);
line_objs = g_dbus_object_manager_get_objects(manager);
for (pos = g_list_first(line_objs); pos; pos = g_list_next(pos)) {
line = gpiodbus_object_peek_line(pos->data);
if (g_strcmp0(gpiodbus_line_get_name(line), line_name) == 0)
return g_object_ref(pos->data);
if (str_is_all_digits(line_name)) {
offset = g_ascii_strtoull(line_name, NULL, 10);
if (offset == gpiodbus_line_get_offset(line))
return g_object_ref(pos->data);
}
}
return NULL;
}
GList *get_all_line_objs_for_chip(GpiodbusObject *chip_obj)
{
g_autoptr(GDBusObjectManager) manager = NULL;
const gchar *chip_path;
chip_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(chip_obj));
manager = get_object_manager_client(chip_path);
return g_list_sort(g_dbus_object_manager_get_objects(manager),
(GCompareFunc)compare_objs_by_path);
}
static gchar *sanitize_str(const gchar *str)
{
if (!strlen(str))
return NULL;
return g_strdup(str);
}
static const gchar *sanitize_direction(const gchar *direction)
{
if ((g_strcmp0(direction, "input") == 0) ||
(g_strcmp0(direction, "output") == 0))
return direction;
die("invalid direction value received from manager: '%s'", direction);
}
static const gchar *sanitize_drive(const gchar *drive)
{
if ((g_strcmp0(drive, "push-pull") == 0) ||
(g_strcmp0(drive, "open-source") == 0) ||
(g_strcmp0(drive, "open-drain") == 0))
return drive;
die("invalid drive value received from manager: '%s'", drive);
}
static const gchar *sanitize_bias(const gchar *bias)
{
if ((g_strcmp0(bias, "pull-up") == 0) ||
(g_strcmp0(bias, "pull-down") == 0) ||
(g_strcmp0(bias, "disabled") == 0))
return bias;
if (g_strcmp0(bias, "unknown") == 0)
return NULL;
die("invalid bias value received from manager: '%s'", bias);
}
static const gchar *sanitize_edge(const gchar *edge)
{
if ((g_strcmp0(edge, "rising") == 0) ||
(g_strcmp0(edge, "falling") == 0) ||
(g_strcmp0(edge, "both") == 0))
return edge;
if (g_strcmp0(edge, "none") == 0)
return NULL;
die("invalid edge value received from manager: '%s'", edge);
}
static const gchar *sanitize_clock(const gchar *event_clock)
{
if ((g_strcmp0(event_clock, "monotonic") == 0) ||
(g_strcmp0(event_clock, "realtime") == 0) ||
(g_strcmp0(event_clock, "hte") == 0))
return event_clock;
die("invalid clock value received from manager: '%s'", event_clock);
}
gchar *sanitize_object_path(const gchar *path)
{
if (g_strcmp0(path, "/") == 0)
return g_strdup("N/A");
return g_path_get_basename(path);
}
LineProperties *get_line_properties(GpiodbusLine *line)
{
LineProperties *props;
props = g_malloc0(sizeof(*props));
props->name = sanitize_str(gpiodbus_line_get_name(line));
props->offset = gpiodbus_line_get_offset(line);
props->used = gpiodbus_line_get_used(line);
props->consumer = sanitize_str(gpiodbus_line_get_consumer(line));
props->direction = sanitize_direction(
gpiodbus_line_get_direction(line));
props->drive = sanitize_drive(gpiodbus_line_get_drive(line));
props->bias = sanitize_bias(gpiodbus_line_get_bias(line));
props->active_low = gpiodbus_line_get_active_low(line);
props->edge = sanitize_edge(gpiodbus_line_get_edge_detection(line));
props->debounced = gpiodbus_line_get_debounced(line);
props->debounce_period = gpiodbus_line_get_debounce_period_us(line);
props->event_clock = sanitize_clock(
gpiodbus_line_get_event_clock(line));
props->managed = gpiodbus_line_get_managed(line);
props->request_name = sanitize_object_path(
gpiodbus_line_get_request_path(line));
return props;
}
void free_line_properties(LineProperties *props)
{
g_free(props->name);
g_free(props->consumer);
g_free(props->request_name);
g_free(props);
}
void validate_line_config_opts(LineConfigOpts *opts)
{
gint counter;
if (opts->input && opts->output)
die_parsing_opts("--input and --output are mutually exclusive");
if (opts->both_edges)
opts->rising_edge = opts->falling_edge = TRUE;
if (!opts->input && (opts->rising_edge || opts->falling_edge))
die_parsing_opts("monitoring edges is only possible in input mode");
counter = 0;
if (opts->push_pull)
counter++;
if (opts->open_drain)
counter++;
if (opts->open_source)
counter++;
if (counter > 1)
die_parsing_opts("--push-pull, --open-drain and --open-source are mutually exclusive");
if (!opts->output && (counter > 0))
die_parsing_opts("--push-pull, --open-drain and --open-source are only available in output mode");
counter = 0;
if (opts->pull_up)
counter++;
if (opts->pull_down)
counter++;
if (opts->bias_disabled)
counter++;
if (counter > 1)
die_parsing_opts("--pull-up, --pull-down and --bias-disabled are mutually exclusive");
counter = 0;
if (opts->clock_monotonic)
counter++;
if (opts->clock_realtime)
counter++;
if (opts->clock_hte)
counter++;
if (counter > 1)
die_parsing_opts("--clock-monotonic, --clock-realtime and --clock-hte are mutually exclusive");
if (counter > 0 && (!opts->rising_edge && !opts->falling_edge))
die_parsing_opts("--clock-monotonic, --clock-realtime and --clock-hte can only be used with edge detection enabled");
if (opts->debounce_period && (!opts->rising_edge && !opts->falling_edge))
die_parsing_opts("--debounce-period can only be used with edge-detection enabled");
}
GVariant *make_line_config(GArray *offsets, LineConfigOpts *opts)
{
const char *direction, *edge = NULL, *bias = NULL, *drive = NULL,
*clock = NULL;
g_autoptr(GVariant) output_values = NULL;
g_autoptr(GVariant) line_settings = NULL;
g_autoptr(GVariant) line_offsets = NULL;
g_autoptr(GVariant) line_configs = NULL;
g_autoptr(GVariant) line_config = NULL;
GVariantBuilder builder;
guint i;
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
for (i = 0; i < offsets->len; i++)
g_variant_builder_add_value(&builder,
g_variant_new_uint32(g_array_index(offsets, guint, i)));
line_offsets = g_variant_builder_end(&builder);
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
if (opts->input)
direction = "input";
else if (opts->output)
direction = "output";
else
direction = "as-is";
if (direction)
g_variant_builder_add_value(&builder,
g_variant_new("{sv}", "direction",
g_variant_new_string(direction)));
if (opts->rising_edge && opts->falling_edge)
edge = "both";
else if (opts->falling_edge)
edge = "falling";
else if (opts->rising_edge)
edge = "rising";
if (edge)
g_variant_builder_add_value(&builder,
g_variant_new("{sv}", "edge",
g_variant_new_string(edge)));
if (opts->pull_up)
bias = "pull-up";
else if (opts->pull_down)
bias = "pull-down";
else if (opts->bias_disabled)
bias = "disabled";
if (bias)
g_variant_builder_add_value(&builder,
g_variant_new("{sv}", "bias",
g_variant_new_string(bias)));
if (opts->push_pull)
drive = "push-pull";
else if (opts->open_drain)
drive = "open-drain";
else if (opts->open_source)
drive = "open-source";
if (drive)
g_variant_builder_add_value(&builder,
g_variant_new("{sv}", "drive",
g_variant_new_string(drive)));
if (opts->active_low)
g_variant_builder_add_value(&builder,
g_variant_new("{sv}", "active-low",
g_variant_new_boolean(TRUE)));
if (opts->debounce_period)
g_variant_builder_add_value(&builder,
g_variant_new("{sv}", "debounce-period",
g_variant_new_int64(opts->debounce_period)));
if (opts->clock_monotonic)
clock = "monotonic";
else if (opts->clock_realtime)
clock = "realtime";
else if (opts->clock_hte)
clock = "hte";
if (clock)
g_variant_builder_add_value(&builder,
g_variant_new("{sv}", "event-clock",
g_variant_new_string(clock)));
line_settings = g_variant_builder_end(&builder);
g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
g_variant_builder_add_value(&builder, g_variant_ref(line_offsets));
g_variant_builder_add_value(&builder, g_variant_ref(line_settings));
line_config = g_variant_builder_end(&builder);
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
g_variant_builder_add_value(&builder, g_variant_ref(line_config));
line_configs = g_variant_builder_end(&builder);
if (opts->output_values) {
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
for (i = 0; i < opts->output_values->len; i++) {
g_variant_builder_add(&builder, "i",
g_array_index(opts->output_values,
gint, i));
}
output_values = g_variant_builder_end(&builder);
} else {
output_values = g_variant_new("ai", opts->output_values);
}
g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
g_variant_builder_add_value(&builder, g_variant_ref(line_configs));
g_variant_builder_add_value(&builder, g_variant_ref(output_values));
return g_variant_ref_sink(g_variant_builder_end(&builder));
}