diff mbox series

[2/5] backends: introduce gpio backend subsystem

Message ID 20201127182917.2387-2-info@metux.net
State New
Headers show
Series [1/5] scripts: minikconf: support config titles | expand

Commit Message

Enrico Weigelt, metux IT consult Nov. 27, 2020, 6:29 p.m. UTC
Introducing a backend subsys for hardware GPIOs, so we can now
let simulated GPIO devices actually do something, by talking to
pluggable GPIO backends (eg. speaking to host's gpio subsystem,
gpiod, custom simulation, etc).

This patch does not implement any actual gpio backend, nor any
any any hw simulation drivers yet - just the generic infrastructure.

Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
---
 MAINTAINERS           |   6 ++
 backends/Kconfig      |   4 +
 backends/gpio.c       | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++
 backends/meson.build  |   1 +
 include/sysemu/gpio.h |  76 +++++++++++++++
 5 files changed, 342 insertions(+)
 create mode 100644 backends/gpio.c
 create mode 100644 include/sysemu/gpio.h
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 68bc160f41..bfa29a4560 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2288,6 +2288,12 @@  S: Maintained
 F: gdbstub*
 F: gdb-xml/
 
+GPIO Backend API
+M: Enrico Weigelt, metux IT consult <info@metux.net>
+S: Supported
+F: backends/gpio.c
+F: include/sysemu/gpio.h
+
 Memory API
 M: Paolo Bonzini <pbonzini@redhat.com>
 S: Supported
diff --git a/backends/Kconfig b/backends/Kconfig
index f35abc1609..2f17189472 100644
--- a/backends/Kconfig
+++ b/backends/Kconfig
@@ -1 +1,5 @@ 
 source tpm/Kconfig
+
+config BACKEND_GPIO
+    bool "Enable GPIO backends"
+    default y
diff --git a/backends/gpio.c b/backends/gpio.c
new file mode 100644
index 0000000000..dc539b0791
--- /dev/null
+++ b/backends/gpio.c
@@ -0,0 +1,255 @@ 
+/*
+ * QEMU GPIO Backend
+ *
+ * Copyright 2020 Enrico Weigelt, metux IT consult <info@metux.net>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <errno.h>
+#include "qemu/osdep.h"
+#include "sysemu/gpio.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qerror.h"
+#include "qemu/module.h"
+#include "qom/object_interfaces.h"
+#include "qemu/error-report.h"
+#include "sysemu/gpio.h"
+
+#define GPIO_FORMAT_VALUE       "gpio%d.value"
+#define GPIO_FORMAT_DIRECTION   "gpio%d.direction"
+
+#define BACKEND_OP_HEAD \
+    if (!gpio) \
+        return -EFAULT;
+
+#define BACKEND_CLASSOP_HEAD(op) \
+    GpioBackendClass *klass; \
+    BACKEND_OP_HEAD \
+    klass = GPIO_BACKEND_GET_CLASS(gpio); \
+    if (!klass) \
+        return -EFAULT; \
+    if (!klass->op) \
+        return -EUNATCH;
+
+int gpio_backend_set_notify(GpioBackend *gpio,
+                            gpio_backend_notify_t proc,
+                            void *consumer)
+{
+    BACKEND_OP_HEAD
+
+    gpio->notify_proc = proc;
+    gpio->notify_consumer = consumer;
+
+    return 0;
+}
+
+int gpio_backend_send_notify(GpioBackend *gpio, int pin, int event, int value)
+{
+    BACKEND_OP_HEAD
+
+    if (gpio->notify_proc) {
+        return gpio->notify_proc(gpio->notify_consumer, pin, event, value);
+    }
+
+    return 0;
+}
+
+int gpio_backend_request(GpioBackend *gpio, int pin)
+{
+    BACKEND_CLASSOP_HEAD(request);
+    return klass->request(gpio, pin);
+}
+
+int gpio_backend_set_value(GpioBackend *gpio, int pin, int state)
+{
+    BACKEND_CLASSOP_HEAD(set_value);
+    return klass->set_value(gpio, pin, state);
+}
+
+int gpio_backend_get_value(GpioBackend *gpio, int pin)
+{
+    BACKEND_CLASSOP_HEAD(get_value);
+    return klass->get_value(gpio, pin);
+}
+
+int gpio_backend_direction_output(GpioBackend *gpio, int pin, int state)
+{
+    BACKEND_CLASSOP_HEAD(direction_output);
+    return klass->direction_output(gpio, pin, state);
+}
+
+int gpio_backend_direction_input(GpioBackend *gpio, int pin)
+{
+    BACKEND_CLASSOP_HEAD(direction_input);
+    return klass->direction_input(gpio, pin);
+}
+
+int gpio_backend_get_direction(GpioBackend *gpio, int pin)
+{
+    BACKEND_CLASSOP_HEAD(get_direction);
+    return klass->get_direction(gpio, pin);
+}
+
+int gpio_backend_get_ngpio(GpioBackend *gpio)
+{
+    BACKEND_CLASSOP_HEAD(get_ngpio);
+    return klass->get_ngpio(gpio);
+}
+
+static void getattr_value(Object *obj, Visitor *v, const char *name,
+                          void *opaque, Error **errp)
+{
+    int pin;
+    int64_t val = 0;
+    GpioBackend *gpio = GPIO_BACKEND(obj);
+
+    if (sscanf(name, GPIO_FORMAT_VALUE, &pin) != 1) {
+        error_setg(errp,
+                  "gpio: getattr_value() illegal property: \"%s\"",
+                   name);
+        return;
+    }
+
+    val = gpio_backend_get_value(gpio, pin);
+    visit_type_int(v, name, &val, errp);
+}
+
+static void setattr_value(Object *obj, Visitor *v, const char *name,
+                          void *opaque, Error **errp)
+{
+    int pin;
+    int64_t val = 0;
+    GpioBackend *gpio = GPIO_BACKEND(obj);
+
+    if (!visit_type_int(v, name, &val, errp)) {
+        return;
+    }
+
+    if (sscanf(name, GPIO_FORMAT_VALUE, &pin) != 1) {
+        error_setg(errp,
+                   "gpio: setattr_value() illegal property: \"%s\"",
+                   name);
+        return;
+    }
+
+    gpio_backend_set_value(gpio, pin, val);
+    gpio_backend_send_notify(gpio, pin, GPIO_EVENT_LEVEL, val);
+}
+
+static void getattr_direction(Object *obj, Visitor *v, const char *name,
+                              void *opaque, Error **errp)
+{
+    int pin;
+    GpioBackend *gpio = GPIO_BACKEND(obj);
+    char str[16] = { 0 };
+    char *val = str;
+
+    if (sscanf(name, GPIO_FORMAT_DIRECTION, &pin) != 1) {
+        error_setg(errp,
+                   "gpio: getattr_direction() illegal property: \"%s\"",
+                   name);
+        return;
+    }
+
+    strcpy(str, (gpio_backend_get_direction(gpio, pin)
+                    == QEMU_GPIO_DIRECTION_INPUT) ? "in" : "out");
+    visit_type_str(v, name, &val, errp);
+}
+
+static void setattr_direction(Object *obj, Visitor *v, const char *name,
+                              void *opaque, Error **errp)
+{
+    int pin;
+    GpioBackend *gpio = GPIO_BACKEND(obj);
+    char *val;
+
+    if (!visit_type_str(v, name, &val, errp)) {
+        return;
+    }
+
+    if (sscanf(name, GPIO_FORMAT_DIRECTION, &pin) != 1) {
+        error_setg(errp, "gpio: setattr_direction() illegal property: \"%s\"",
+                   name);
+        return;
+    }
+
+    if (strcmp(val, "in") == 0) {
+        gpio_backend_direction_input(gpio, pin);
+        gpio_backend_send_notify(gpio, pin, GPIO_EVENT_INPUT, 0);
+        return;
+    }
+
+    if (strcmp(val, "out") == 0) {
+        gpio_backend_direction_output(gpio, pin, QEMU_GPIO_LINE_INACTIVE);
+        gpio_backend_send_notify(gpio, pin, GPIO_EVENT_OUTPUT, 0);
+        return;
+    }
+
+    error_setg(errp, "gpio: setattr_direction() illegal value: \"%s\"", val);
+    return;
+}
+
+int gpio_backend_register(GpioBackend *gpio)
+{
+    int pin;
+    int ngpio = gpio_backend_get_ngpio(gpio);
+
+    if (ngpio < 1) {
+        error_report("gpio_backend_register() illegal number of gpios: %d",
+                     ngpio);
+        return -EINVAL;
+    }
+
+    for (pin = 0; pin < ngpio; pin++) {
+        char name[64];
+        snprintf(name, sizeof(name), GPIO_FORMAT_VALUE, pin);
+        object_property_add(OBJECT(gpio), name, "bool", getattr_value,
+                            setattr_value, NULL, NULL);
+        snprintf(name, sizeof(name), GPIO_FORMAT_DIRECTION, pin);
+        object_property_add(OBJECT(gpio), name, "string", getattr_direction,
+                            setattr_direction, NULL, NULL);
+    }
+
+    return 0;
+}
+
+int gpio_backend_unregister(GpioBackend *s)
+{
+    /* nothing to do for now */
+    return 0;
+}
+
+static void gpio_backend_init(Object *obj)
+{
+}
+
+static void gpio_backend_finalize(Object *obj)
+{
+}
+
+static void gpio_backend_class_init(ObjectClass *oc, void *data)
+{
+}
+
+static const TypeInfo gpio_backend_info = {
+    .name = TYPE_GPIO_BACKEND,
+    .parent = TYPE_OBJECT,
+    .instance_size = sizeof(GpioBackend),
+    .instance_init = gpio_backend_init,
+    .instance_finalize = gpio_backend_finalize,
+    .class_size = sizeof(GpioBackendClass),
+    .class_init = gpio_backend_class_init,
+    .abstract = true,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+static void register_types(void)
+{
+    type_register_static(&gpio_backend_info);
+}
+
+type_init(register_types);
diff --git a/backends/meson.build b/backends/meson.build
index 484456ece7..332ad7379a 100644
--- a/backends/meson.build
+++ b/backends/meson.build
@@ -15,5 +15,6 @@  softmmu_ss.add(when: ['CONFIG_VHOST_USER', 'CONFIG_VIRTIO'], if_true: files('vho
 softmmu_ss.add(when: 'CONFIG_VIRTIO_CRYPTO', if_true: files('cryptodev-vhost.c'))
 softmmu_ss.add(when: ['CONFIG_VIRTIO_CRYPTO', 'CONFIG_VHOST_CRYPTO'], if_true: files('cryptodev-vhost-user.c'))
 softmmu_ss.add(when: 'CONFIG_GIO', if_true: [files('dbus-vmstate.c'), gio])
+softmmu_ss.add(when: 'CONFIG_BACKEND_GPIO', if_true: files('gpio.c'))
 
 subdir('tpm')
diff --git a/include/sysemu/gpio.h b/include/sysemu/gpio.h
new file mode 100644
index 0000000000..0cfd62b192
--- /dev/null
+++ b/include/sysemu/gpio.h
@@ -0,0 +1,76 @@ 
+/*
+ * QEMU GPIO backend
+ *
+ * Copyright 2020 Enrico Weigelt, metux IT consult <info@metux.net>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef QEMU_GPIO_H
+#define QEMU_GPIO_H
+
+#include "qemu/queue.h"
+#include "qom/object.h"
+
+#define TYPE_GPIO_BACKEND "gpio-backend"
+OBJECT_DECLARE_TYPE(GpioBackend, GpioBackendClass, GPIO_BACKEND)
+
+/* dont change them - drivers rely on these values */
+#define QEMU_GPIO_DIRECTION_OUTPUT  0
+#define QEMU_GPIO_DIRECTION_INPUT   1
+
+#define QEMU_GPIO_LINE_INACTIVE     0
+#define QEMU_GPIO_LINE_ACTIVE       1
+
+/*
+ * notification callback from gpio backend to consumer/frontend
+ *
+ * consumer:    pointer to/for the consumer object
+ * gpio_id:     id of the gpio (-1 = all at once)
+ * event:       whats happened
+ */
+typedef int (*gpio_backend_notify_t)(void *consumer, int gpio, int event,
+                                     int value);
+
+#define GPIO_EVENT_INPUT  1
+#define GPIO_EVENT_OUTPUT 2
+#define GPIO_EVENT_LEVEL  3
+
+struct GpioBackendClass {
+    ObjectClass parent_class;
+    bool opened;
+
+    char *name;
+
+    int (*request)(GpioBackend *s, int gpio);
+    int (*direction_input)(GpioBackend *s, int gpio);
+    int (*direction_output)(GpioBackend *s, int gpio, int state);
+    int (*get_direction)(GpioBackend *s, int gpio);
+    int (*set_value)(GpioBackend *s, int gpio, int state);
+    int (*get_value)(GpioBackend *s, int gpio);
+    int (*get_ngpio)(GpioBackend *s);
+};
+
+struct GpioBackend {
+    Object parent;
+    gpio_backend_notify_t notify_proc;
+    void *notify_consumer;
+};
+
+/* call wrappers to gpio backend */
+int gpio_backend_request(GpioBackend *s, int gpio);
+int gpio_backend_direction_input(GpioBackend *s, int gpio);
+int gpio_backend_direction_output(GpioBackend *s, int gpio, int state);
+int gpio_backend_get_direction(GpioBackend *s, int gpio);
+int gpio_backend_set_value(GpioBackend *s, int gpio, int state);
+int gpio_backend_get_value(GpioBackend *s, int gpio);
+int gpio_backend_set_notify(GpioBackend *s, gpio_backend_notify_t proc,
+                            void *consumer);
+int gpio_backend_send_notify(GpioBackend *s, int gpio, int event, int value);
+int gpio_backend_get_ngpio(GpioBackend *s);
+
+/* used by backend drivers for common initializations */
+int gpio_backend_register(GpioBackend *s);
+int gpio_backend_unregister(GpioBackend *s);
+
+#endif /* QEMU_GPIO_H */