mirror of
https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
synced 2026-10-24 00:48:53 +03:00
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>
251 lines
6.6 KiB
C
251 lines
6.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// SPDX-FileCopyrightText: 2023-2024 Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "common.h"
|
|
|
|
typedef struct {
|
|
LineConfigOpts line_cfg_opts;
|
|
const gchar *consumer;
|
|
} RequestOpts;
|
|
|
|
typedef struct {
|
|
const gchar *request_path;
|
|
gboolean done;
|
|
} RequestWaitData;
|
|
|
|
static GVariant *make_request_config(RequestOpts *opts)
|
|
{
|
|
GVariantBuilder builder;
|
|
|
|
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
|
|
g_variant_builder_add_value(&builder,
|
|
g_variant_new("{sv}", "consumer",
|
|
g_variant_new_string(opts->consumer)));
|
|
|
|
return g_variant_ref_sink(g_variant_builder_end(&builder));
|
|
}
|
|
|
|
static gboolean on_timeout(gpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
die("wait for request to appear timed out!");
|
|
}
|
|
|
|
static void obj_match_request_path(GpiodbusObject *obj, RequestWaitData *data)
|
|
{
|
|
if (g_strcmp0(g_dbus_object_get_object_path(G_DBUS_OBJECT(obj)),
|
|
data->request_path) == 0)
|
|
data->done = TRUE;
|
|
}
|
|
|
|
static void match_request_path(gpointer elem, gpointer user_data)
|
|
{
|
|
RequestWaitData *data = user_data;
|
|
GpiodbusObject *obj = elem;
|
|
|
|
obj_match_request_path(obj, data);
|
|
}
|
|
|
|
static void on_object_added(GDBusObjectManager *manager G_GNUC_UNUSED,
|
|
GpiodbusObject *obj, gpointer user_data)
|
|
{
|
|
RequestWaitData *data = user_data;
|
|
|
|
obj_match_request_path(GPIODBUS_OBJECT(obj), data);
|
|
}
|
|
|
|
static void wait_for_request(const gchar *request_path)
|
|
{
|
|
RequestWaitData data = { .request_path = request_path };
|
|
g_autoptr(GDBusObjectManager) manager = NULL;
|
|
g_autolist(GpiodbusObject) objs = NULL;
|
|
|
|
manager = get_object_manager_client("/io/gpiod1/requests");
|
|
|
|
g_signal_connect(manager, "object-added",
|
|
G_CALLBACK(on_object_added), &data);
|
|
|
|
objs = g_dbus_object_manager_get_objects(manager);
|
|
g_list_foreach(objs, match_request_path, &data);
|
|
|
|
g_timeout_add(5000, on_timeout, NULL);
|
|
|
|
while (!data.done)
|
|
g_main_context_iteration(NULL, TRUE);
|
|
}
|
|
|
|
static int
|
|
request_lines(GList *line_names, const gchar *chip_name, RequestOpts *req_opts)
|
|
{
|
|
g_autoptr(GpiodbusObject) chip_obj = NULL;
|
|
g_autoptr(GVariant) request_config = NULL;
|
|
g_autoptr(GVariant) line_config = NULL;
|
|
g_autofree gchar *request_path = NULL;
|
|
g_autofree gchar *request_name = NULL;
|
|
g_autofree gchar *dyn_name = NULL;
|
|
g_autoptr(GArray) offsets = NULL;
|
|
g_autoptr(GError) err = NULL;
|
|
GpiodbusLine *line;
|
|
GpiodbusChip *chip;
|
|
GString *line_name;
|
|
guint i, *offset;
|
|
gboolean ret;
|
|
GList *pos;
|
|
gsize llen;
|
|
|
|
llen = g_list_length(line_names);
|
|
offsets = g_array_sized_new(FALSE, TRUE, sizeof(guint), llen);
|
|
g_array_set_size(offsets, llen);
|
|
|
|
if (chip_name)
|
|
chip_obj = get_chip_obj(chip_name);
|
|
|
|
for (i = 0, pos = g_list_first(line_names);
|
|
i < llen;
|
|
i++, pos = g_list_next(pos)) {
|
|
g_autoptr(GpiodbusObject) line_obj = NULL;
|
|
|
|
line_name = pos->data;
|
|
|
|
if (chip_obj) {
|
|
line_obj = get_line_obj_by_name_for_chip(chip_obj,
|
|
line_name->str);
|
|
if (!line_obj) {
|
|
if (dyn_name) {
|
|
ret = get_line_obj_by_name(
|
|
line_name->str,
|
|
&line_obj, NULL);
|
|
if (ret)
|
|
/*
|
|
* This means the line exists
|
|
* but on a different chip.
|
|
*/
|
|
die("all requested lines must belong to the same chip");
|
|
}
|
|
|
|
die("no line '%s' on chip '%s'",
|
|
line_name->str, chip_name);
|
|
}
|
|
} else {
|
|
ret = get_line_obj_by_name(line_name->str, &line_obj,
|
|
&chip_obj);
|
|
if (!ret)
|
|
die("line '%s' not found", line_name->str);
|
|
|
|
dyn_name = g_path_get_basename(
|
|
g_dbus_object_get_object_path(
|
|
G_DBUS_OBJECT(chip_obj)));
|
|
chip_name = dyn_name;
|
|
}
|
|
|
|
line = gpiodbus_object_peek_line(line_obj);
|
|
offset = &g_array_index(offsets, guint, i);
|
|
*offset = gpiodbus_line_get_offset(line);
|
|
}
|
|
|
|
chip = gpiodbus_object_peek_chip(chip_obj);
|
|
line_config = make_line_config(offsets, &req_opts->line_cfg_opts);
|
|
request_config = make_request_config(req_opts);
|
|
|
|
ret = gpiodbus_chip_call_request_lines_sync(chip, line_config,
|
|
request_config,
|
|
G_DBUS_CALL_FLAGS_NONE, -1,
|
|
&request_path, NULL, &err);
|
|
if (err)
|
|
die_gerror(err, "failed to request lines from chip '%s'",
|
|
chip_name);
|
|
|
|
wait_for_request(request_path);
|
|
|
|
request_name = g_path_get_basename(request_path);
|
|
g_print("%s\n", request_name);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int gpiocli_request_main(int argc, char **argv)
|
|
{
|
|
static const gchar *const summary =
|
|
"Request a set of GPIO lines for exclusive usage by the gpio-manager.";
|
|
|
|
g_autoptr(GArray) output_values = NULL;
|
|
g_autolist(GString) line_names = NULL;
|
|
const gchar *chip_name = NULL;
|
|
g_auto(GStrv) lines = NULL;
|
|
RequestOpts req_opts = {};
|
|
gsize llen;
|
|
gint val;
|
|
guint i;
|
|
|
|
const GOptionEntry opts[] = {
|
|
{
|
|
.long_name = "chip",
|
|
.short_name = 'c',
|
|
.flags = G_OPTION_FLAG_NONE,
|
|
.arg = G_OPTION_ARG_STRING,
|
|
.arg_data = &chip_name,
|
|
.description =
|
|
"Explicitly specify the chip_name on which to resolve the lines which allows to use raw offsets instead of line names.",
|
|
.arg_description = "<chip name>",
|
|
},
|
|
{
|
|
.long_name = "consumer",
|
|
.flags = G_OPTION_FLAG_NONE,
|
|
.arg = G_OPTION_ARG_STRING,
|
|
.arg_data = &req_opts.consumer,
|
|
.description = "Consumer string (defaults to program name)",
|
|
.arg_description = "<consumer name>",
|
|
},
|
|
{
|
|
.long_name = G_OPTION_REMAINING,
|
|
.flags = G_OPTION_FLAG_NONE,
|
|
.arg = G_OPTION_ARG_STRING_ARRAY,
|
|
.arg_data = &lines,
|
|
.arg_description = "<line1>[=value1] [line2[=value2]] ...",
|
|
},
|
|
LINE_CONFIG_OPTIONS(&req_opts.line_cfg_opts),
|
|
{ }
|
|
};
|
|
|
|
parse_options(opts, summary, NULL, &argc, &argv);
|
|
validate_line_config_opts(&req_opts.line_cfg_opts);
|
|
|
|
if (!lines)
|
|
die_parsing_opts("At least one line must be specified");
|
|
|
|
if (!req_opts.consumer)
|
|
req_opts.consumer = "gpio-manager";
|
|
|
|
for (i = 0, llen = g_strv_length(lines); i < llen; i++) {
|
|
g_auto(GStrv) tokens = NULL;
|
|
|
|
tokens = g_strsplit(lines[i], "=", 2);
|
|
line_names = g_list_append(line_names, g_string_new(tokens[0]));
|
|
if (g_strv_length(tokens) == 2) {
|
|
if (!req_opts.line_cfg_opts.output)
|
|
die_parsing_opts("Output values can only be set in output mode");
|
|
|
|
if (!output_values)
|
|
output_values = g_array_sized_new(FALSE, TRUE,
|
|
sizeof(gint),
|
|
llen);
|
|
val = output_value_from_str(tokens[1]);
|
|
g_array_append_val(output_values, val);
|
|
}
|
|
}
|
|
|
|
if (output_values && req_opts.line_cfg_opts.input)
|
|
die_parsing_opts("cannot set output values in input mode");
|
|
|
|
if (output_values &&
|
|
(g_list_length(line_names) != output_values->len))
|
|
die_parsing_opts("if values are set, they must be set for all lines");
|
|
|
|
req_opts.line_cfg_opts.output_values = output_values;
|
|
|
|
check_manager();
|
|
|
|
return request_lines(line_names, chip_name, &req_opts);
|
|
}
|