@@ -16,3 +16,5 @@ common-obj-$(call land,$(CONFIG_VHOST_USER),$(CONFIG_LINUX)) += \
endif
common-obj-$(CONFIG_LINUX) += hostmem-memfd.o
+
+common-obj-$(CONFIG_GPIO) += gpiodev.o
new file mode 100644
@@ -0,0 +1,183 @@
+/*
+ * QEMU GPIO Backend
+ *
+ * Copyright (C) 2018 Glider bvba
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <errno.h>
+#include <gpiod.h>
+
+#include "qemu/osdep.h"
+#include "qemu/config-file.h"
+#include "qemu/cutils.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+
+#include "sysemu/gpiodev.h"
+
+#include "hw/irq.h"
+#include "hw/qdev-core.h"
+
+DeviceState *the_pl061_dev;
+
+static void gpio_irq_handler(void *opaque, int n, int level)
+{
+ struct gpiod_line *line = opaque;
+ int status;
+
+ status = gpiod_line_set_value(line, level);
+ if (status < 0) {
+ struct gpiod_chip *chip = gpiod_line_get_chip(line);
+
+ error_report("%s/%s: Cannot set GPIO line %u: %s",
+ gpiod_chip_name(chip), gpiod_chip_label(chip),
+ gpiod_line_offset(line), strerror(errno));
+ }
+}
+
+static int gpio_connect_line(unsigned int vgpio, struct gpiod_chip *chip,
+ unsigned int gpio)
+{
+ const char *name = gpiod_chip_name(chip);
+ const char *label = gpiod_chip_label(chip);
+ struct gpiod_line *line;
+ qemu_irq irq;
+ int status;
+
+ if (!the_pl061_dev) {
+ error_report("PL061 GPIO controller not available");
+ return -1;
+ }
+
+ line = gpiod_chip_get_line(chip, gpio);
+ if (!line) {
+ error_report("%s/%s: Cannot obtain GPIO line %u: %s", name, label,
+ gpio, strerror(errno));
+ return -1;
+ }
+
+ status = gpiod_line_request_output(line, "qemu", 0);
+ if (status < 0) {
+ error_report("%s/%s: Cannot request GPIO line %u for output: %s", name,
+ label, gpio, strerror(errno));
+ return -1;
+ }
+
+ irq = qemu_allocate_irq(gpio_irq_handler, line, 0);
+ qdev_connect_gpio_out(the_pl061_dev, vgpio, irq);
+
+ info_report("%s/%s: Connected PL061 GPIO %u to GPIO line %u", name, label,
+ vgpio, gpio);
+ return 0;
+}
+
+static int gpio_count_gpios(const char *opt)
+{
+ unsigned int len = 0;
+ unsigned int n = 0;
+
+ do {
+ switch (*opt) {
+ case '0' ... '9':
+ len++;
+ break;
+
+ case ':':
+ case '\0':
+ if (!len) {
+ return -1;
+ }
+
+ n++;
+ len = 0;
+ break;
+
+ default:
+ return -1;
+ }
+ } while (*opt++);
+
+ return n;
+}
+
+int qemu_gpiodev_add(QemuOpts *opts)
+{
+ const char *name = qemu_opt_get(opts, "name");
+ const char *vgpios = qemu_opt_get(opts, "vgpios");
+ const char *gpios = qemu_opt_get(opts, "gpios");
+ unsigned int vgpio, gpio;
+ struct gpiod_chip *chip;
+ int n1, n2, i, status;
+
+ if (!name || !vgpios || !gpios) {
+ error_report("Missing parameters");
+ return -1;
+ }
+
+ n1 = gpio_count_gpios(vgpios);
+ if (n1 < 0) {
+ error_report("Invalid vgpios parameter");
+ return n1;
+ }
+
+ n2 = gpio_count_gpios(gpios);
+ if (n2 < 0) {
+ error_report("Invalid gpios parameter");
+ return n2;
+ }
+
+ if (n1 != n2) {
+ error_report("Number of vgpios and gpios do not match");
+ return -1;
+ }
+
+ chip = gpiod_chip_open_lookup(name);
+ if (!chip) {
+ error_report("Cannot open GPIO chip %s: %s", name, strerror(errno));
+ return -1;
+ }
+
+ for (i = 0; i < n1; i++, vgpios++, gpios++) {
+ qemu_strtoui(vgpios, &vgpios, 10, &vgpio);
+ qemu_strtoui(gpios, &gpios, 10, &gpio);
+
+ status = gpio_connect_line(vgpio, chip, gpio);
+ if (status) {
+ return status;
+ }
+ }
+
+ return 0;
+}
+
+static QemuOptsList qemu_gpiodev_opts = {
+ .name = "gpiodev",
+ .implied_opt_name = "name",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_gpiodev_opts.head),
+ .desc = {
+ {
+ .name = "name",
+ .type = QEMU_OPT_STRING,
+ .help = "Sets the GPIO chip specifier",
+ }, {
+ .name = "vgpios",
+ .type = QEMU_OPT_STRING,
+ .help = "Sets the list of virtual GPIO offsets",
+ }, {
+ .name = "gpios",
+ .type = QEMU_OPT_STRING,
+ .help = "Sets the list of physical GPIO offsets",
+ },
+ { /* end of list */ }
+ },
+};
+
+static void gpiodev_register_config(void)
+{
+ qemu_add_opts(&qemu_gpiodev_opts);
+}
+
+opts_init(gpiodev_register_config);
@@ -476,6 +476,7 @@ libxml2=""
docker="no"
debug_mutex="no"
libpmem=""
+gpio=""
# cross compilers defaults, can be overridden with --cross-cc-ARCH
cross_cc_aarch64="aarch64-linux-gnu-gcc"
@@ -1444,6 +1445,10 @@ for opt do
;;
--disable-libpmem) libpmem=no
;;
+ --disable-gpio) gpio="no"
+ ;;
+ --enable-gpio) gpio="yes"
+ ;;
*)
echo "ERROR: unknown option $opt"
echo "Try '$0 --help' for more information"
@@ -1721,6 +1726,7 @@ disabled with --disable-FEATURE, default is enabled if available:
capstone capstone disassembler support
debug-mutex mutex debugging support
libpmem libpmem support
+ gpio gpio support
NOTE: The object files are built at the place where configure is launched
EOF
@@ -5632,6 +5638,24 @@ if test "$libpmem" != "no"; then
fi
fi
+##########################################
+# check for libgpiod
+
+if test "$gpio" != "no"; then
+ if $pkg_config --exists "libgpiod"; then
+ gpio="yes"
+ libgpiod_libs=$($pkg_config --libs libgpiod)
+ libgpiod_cflags=$($pkg_config --cflags libgpiod)
+ libs_softmmu="$libs_softmmu $libgpiod_libs"
+ QEMU_CFLAGS="$QEMU_CFLAGS $libgpiod_cflags"
+ else
+ if test "$gpio" = "yes" ; then
+ feature_not_found "gpio" "Install libgpiod"
+ fi
+ gpio="no"
+ fi
+fi
+
##########################################
# End of CC checks
# After here, no more $cc or $ld runs
@@ -6102,6 +6126,7 @@ echo "VxHS block device $vxhs"
echo "capstone $capstone"
echo "docker $docker"
echo "libpmem support $libpmem"
+echo "gpio support $gpio"
if test "$sdl_too_old" = "yes"; then
echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -6863,6 +6888,10 @@ if test "$libpmem" = "yes" ; then
echo "CONFIG_LIBPMEM=y" >> $config_host_mak
fi
+if test "$gpio" = "yes" ; then
+ echo "CONFIG_GPIO=y" >> $config_host_mak
+fi
+
if test "$tcg_interpreter" = "yes"; then
QEMU_INCLUDES="-iquote \$(SRC_PATH)/tcg/tci $QEMU_INCLUDES"
elif test "$ARCH" = "sparc64" ; then
@@ -40,6 +40,7 @@
#include "hw/devices.h"
#include "net/net.h"
#include "sysemu/device_tree.h"
+#include "sysemu/gpiodev.h"
#include "sysemu/numa.h"
#include "sysemu/sysemu.h"
#include "sysemu/kvm.h"
@@ -761,6 +762,11 @@ static void create_gpio(const VirtMachineState *vms, qemu_irq *pic)
const char compat[] = "arm,pl061\0arm,primecell";
pl061_dev = sysbus_create_simple("pl061", base, pic[irq]);
+#ifdef CONFIG_GPIO
+ if (!the_pl061_dev) {
+ the_pl061_dev = pl061_dev;
+ }
+#endif
uint32_t phandle = qemu_fdt_alloc_phandle(vms->fdt);
nodename = g_strdup_printf("/pl061@%" PRIx64, base);
new file mode 100644
@@ -0,0 +1,11 @@
+/*
+ * QEMU GPIO Backend
+ *
+ * Copyright (C) 2018 Glider bvba
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+extern DeviceState *the_pl061_dev;
+
+int qemu_gpiodev_add(QemuOpts *opts);
@@ -2903,6 +2903,26 @@ DEFHEADING()
#endif
+#ifdef CONFIG_GPIO
+DEFHEADING(GPIO device options:)
+
+DEF("gpiodev", HAS_ARG, QEMU_OPTION_gpiodev,
+ "-gpiodev gpiochip,vgpios=x:y:...:z,gpios=x:y:...:z\n", QEMU_ARCH_ALL)
+STEXI
+@item -gpiodev @var{gpiochip},vgpios=@var{vgpios},gpios=@var{gpios}
+@findef -gpiodev
+Define a new GPIO device. Valid options are:
+@table @option
+@item @var{gpiochip}
+This option specifies the GPIO chip to map virtual gpios to.
+@item vgpios=@var{vgpios}
+Specifies an array of virtual GPIOs to be mapped to physical GPIOs.
+@item gpios=@var{gpios}
+Specifies an array of physical GPIOs to be used as mapping targets.
+@end table
+ETEXI
+#endif
+
DEFHEADING(Linux/Multiboot boot specific:)
STEXI
@@ -102,6 +102,9 @@ int main(int argc, char **argv)
#ifdef CONFIG_VIRTFS
#include "fsdev/qemu-fsdev.h"
#endif
+#ifdef CONFIG_GPIO
+#include "sysemu/gpiodev.h"
+#endif
#include "sysemu/qtest.h"
#include "disas/disas.h"
@@ -2259,6 +2262,14 @@ static int fsdev_init_func(void *opaque, QemuOpts *opts, Error **errp)
}
#endif
+#ifdef CONFIG_GPIO
+static int gpiodev_init_func(void *opaque, QemuOpts *opts, Error **errp)
+{
+
+ return qemu_gpiodev_add(opts);
+}
+#endif
+
static int mon_init_func(void *opaque, QemuOpts *opts, Error **errp)
{
Chardev *chr;
@@ -3333,6 +3344,15 @@ int main(int argc, char **argv, char **envp)
exit(1);
}
break;
+#ifdef CONFIG_GPIO
+ case QEMU_OPTION_gpiodev:
+ opts = qemu_opts_parse_noisily(qemu_find_opts("gpiodev"),
+ optarg, true);
+ if (!opts) {
+ exit(1);
+ }
+ break;
+#endif
case QEMU_OPTION_virtfs: {
QemuOpts *fsdev;
QemuOpts *device;
@@ -4470,6 +4490,13 @@ int main(int argc, char **argv, char **envp)
exit(1);
}
+#ifdef CONFIG_GPIO
+ if (qemu_opts_foreach(qemu_find_opts("gpiodev"),
+ gpiodev_init_func, NULL, NULL)) {
+ exit(1);
+ }
+#endif
+
cpu_synchronize_all_post_init();
rom_reset_order_override();
This is a Proof-of-Concept for a GPIO backend, which allows to connect virtual GPIOs on the guest to physical GPIOs on the host. This allows the guest to control any external device connected to the physical GPIOs. Features and limitations: - The backend uses libgpiod on Linux, - For now only GPIO outputs are supported, - The frontend is currently hardcoded to be the PL061 GPIO controller on the arm virtual machine. As the generic qdev_connect_gpio_out() API call is used, any virtual GPIO controller could do, though. Future work: - Adding a user-instantiable GPIO frontend (or is any of the existing virtualized GPIO controllers already user-instantiable?), - Proper frontend/backend interface using IDs, - Adding a QEMU internal API for controlling multiple GPIOs at once, - Defining an API for GPIO paravirtualization, - ... Example: To connect the first three GPIOs of the virtual PL061 GPIO controller to the GPIOs controlling the three LEDs on the Renesas Salvator-X(S) board, add the following to your qemu command invocation: -gpiodev e6055400.gpio,vgpios=0:1:2,gpios=11:12:13 After that, the guest can cycle through the three LEDs using: for i in $(seq 504 506); do echo $i > /sys/class/gpio/export; done while /bin/true; do for i in $(seq 504 506); do echo high > /sys/class/gpio/gpio$i/direction sleep 1 echo low > /sys/class/gpio/gpio$i/direction done done Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be> --- Thanks for your comments! --- backends/Makefile.objs | 2 + backends/gpiodev.c | 183 +++++++++++++++++++++++++++++++++++++++ configure | 29 +++++++ hw/arm/virt.c | 6 ++ include/sysemu/gpiodev.h | 11 +++ qemu-options.hx | 20 +++++ vl.c | 27 ++++++ 7 files changed, 278 insertions(+) create mode 100644 backends/gpiodev.c create mode 100644 include/sysemu/gpiodev.h