diff options
Diffstat (limited to 'vhost_user_gpio.c')
-rw-r--r-- | vhost_user_gpio.c | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/vhost_user_gpio.c b/vhost_user_gpio.c new file mode 100644 index 0000000..6049bd5 --- /dev/null +++ b/vhost_user_gpio.c @@ -0,0 +1,381 @@ +/* + * Based on vhost-user-gpio.c of QEMU project + * + * Copyright (c) 2022 Viresh Kumar <viresh.kumar@linaro.org> + * + * Copyright (c) 2023 Virtual Open Systems SAS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdbool.h> +#include <sys/param.h> +#include <errno.h> + +/* Project header files */ +#include "vhost_user_gpio.h" + +#ifdef DEBUG +#define DBG(...) printf("vhost-user-gpio: " __VA_ARGS__) +#else +#define DBG(...) +#endif /* DEBUG */ + +#define REALIZE_CONNECTION_RETRIES 3 +#define VHOST_NVQS 2 + +static const int feature_bits[] = { + VIRTIO_F_VERSION_1, + VIRTIO_F_NOTIFY_ON_EMPTY, + VIRTIO_RING_F_INDIRECT_DESC, + VIRTIO_RING_F_EVENT_IDX, + VIRTIO_GPIO_F_IRQ, + VIRTIO_F_RING_RESET, + VHOST_INVALID_FEATURE_BIT +}; + +static void vu_gpio_get_config(VirtIODevice *vdev, uint8_t *config) +{ + VHostUserGPIO *gpio = dev->vdev->vhugpio; + + DBG("vu_gpio_get_config()\n"); + memcpy(config, &gpio->config, sizeof(gpio->config)); +} + +static int vu_gpio_config_notifier(struct vhost_dev *dev) +{ + VHostUserGPIO *gpio = dev->vdev->vhugpio; + + DBG("vu_gpio_config_notifier\n"); + + memcpy(dev->vdev->config, &gpio->config, sizeof(gpio->config)); + virtio_notify_config(dev->vdev); + + return 0; +} + +const VhostDevConfigOps gpio_ops = { + .vhost_dev_config_notifier = vu_gpio_config_notifier, +}; + +static int vu_gpio_start(VirtIODevice *vdev) +{ + VirtioBus *k = vdev->vbus; + VHostUserGPIO *gpio = vdev->vhugpio; + int ret, i; + + DBG("vu_gpio_start()\n"); + + if (!k->set_guest_notifiers) { + DBG("binding does not support guest notifiers"); + return -ENOSYS; + } + + ret = vhost_dev_enable_notifiers(gpio->vhost_dev, vdev); + if (ret < 0) { + DBG("Error enabling host notifiers: %d", ret); + return ret; + } + + ret = k->set_guest_notifiers(k->vdev, gpio->vhost_dev->nvqs, true); + if (ret < 0) { + DBG("Error binding guest notifier: %d", ret); + goto out_with_err_host_notifiers; + } + + vhost_ack_features(gpio->vhost_dev, feature_bits, vdev->guest_features); + + ret = vhost_dev_start(gpio->vhost_dev, vdev, true); + if (ret < 0) { + DBG("Error starting vhost-user-gpio: %d", ret); + goto out_with_err_guest_notifiers; + } + gpio->started_vu = true; + + for (i = 0; i < gpio->vhost_dev->nvqs; i++) { + vhost_virtqueue_mask(gpio->vhost_dev, vdev, i, false); + } + + /* + * TODO: check if we need the following is needed + * ret = gpio->vhost_dev->vhost_ops->vhost_set_vring_enable(gpio->vhost_dev, + * true); + */ + + return 0; + +out_with_err_guest_notifiers: + k->set_guest_notifiers(k->vdev, gpio->vhost_dev->nvqs, false); +out_with_err_host_notifiers: + /* + * TODO: implement the following functions: + * vhost_dev_disable_notifiers(&gpio->vhost_dev, vdev); + */ + + return ret; +} + +static void vu_gpio_stop(VirtIODevice *vdev) +{ + DBG("vu_gpio_stop() not yet implemented\n"); +} + +static void vu_gpio_set_status(VirtIODevice *vdev, uint8_t status) +{ + VHostUserGPIO *gpio = vdev->vhugpio; + bool should_start = virtio_device_started(vdev, status); + + DBG("vu_gpio_set_status()\n"); + + if (!gpio->connected) { + return; + } + +printf("should_start: %d\n", should_start); + if (gpio->vhost_dev->started) { + return; + } + + if (should_start) { + if (vu_gpio_start(vdev)) { + DBG("vu_gpio_start() failed\n"); + } + } else { + vu_gpio_stop(vdev); + } +} + +static uint64_t vu_gpio_get_features(VirtIODevice *vdev, uint64_t features) +{ + VHostUserGPIO *gpio = vdev->vhugpio; + + DBG("vu_gpio_get_features()\n"); + return vhost_get_features(gpio->vhost_dev, feature_bits, features); +} + +static void vu_gpio_handle_output(VirtIODevice *vdev, VirtQueue *vq) +{ + /* + * Not normally called; it's the daemon that handles the queue; + * however virtio's cleanup path can call this. + */ + DBG("vu_gpio_handle_output not yet implemented\n"); +} + +static void vu_gpio_guest_notifier_mask(VirtIODevice *vdev, int idx, bool mask) +{ + VHostUserGPIO *gpio = vdev->vhugpio; + + DBG("vu_gpio_guest_notifier_mask() not yet implemented\n"); + + vhost_virtqueue_mask(gpio->vhost_dev, vdev, idx, mask); +} + +static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserGPIO *gpio) +{ + DBG("do_vhost_user_cleanup() not yet implemented\n"); +} + +static int vu_gpio_connect(VirtIODevice *vdev) +{ + VHostUserGPIO *gpio = vdev->vhugpio; + int ret; + + DBG("vu_gpio_connect()\n"); + + if (gpio->connected) { + return 0; + } + gpio->connected = true; + + vhost_dev_set_config_notifier(gpio->vhost_dev, &gpio_ops); + /* + * TODO: Investigate if the following is needed + * gpio->vhost_user.supports_config = true; + */ + + gpio->vhost_dev->nvqs = VHOST_NVQS; + gpio->vhost_dev->vqs = gpio->vhost_vqs; + + vhost_dev_init(gpio->vhost_dev); + /* + * TODO: Add error handling + * if (ret < 0) { + * return ret; + * } + */ + + /* restore vhost state */ + if (virtio_device_started(vdev, vdev->status)) { + vu_gpio_start(vdev); + } + + return 0; +} + +static int vu_gpio_realize_connect(VHostUserGPIO *gpio) +{ + int ret; + + DBG("vu_gpio_realize_connect()\n"); + + ret = vu_gpio_connect(gpio->parent); + if (ret < 0) { + return ret; + } + + ret = vhost_dev_get_config(gpio->vhost_dev, (uint8_t *)&gpio->config, + sizeof(gpio->config)); + + if (ret < 0) { + DBG("vhost-user-gpio: get config failed\n"); + /* + * TODO: Add cleanup function + * vhost_dev_cleanup(vhost_dev); + */ + return ret; + } + + return 0; +} + +static void vu_gpio_device_unrealize(VirtIODevice *vdev) +{ + DBG("vu_gpio_device_unrealize() not yet implemented\n"); +} + +static void print_config_gpio(uint8_t *config_data) +{ + struct virtio_gpio_config *config = + (struct virtio_gpio_config *)config_data; + + DBG("ngpio: %hu\n", config->ngpio); + DBG("gpio_names_size: %u\n", config->gpio_names_size); +} + +static void vu_gpio_class_init(VirtIODevice *vdev) +{ + DBG("vu_gpio_class_init()\n"); + + vdev->vdev_class = (VirtioDeviceClass *)malloc(sizeof(VirtioDeviceClass)); + if (!vdev->vdev_class) { + DBG("vdev_class memory allocation failed\n"); + return; + } + vdev->vdev_class->realize = vu_gpio_device_realize; + vdev->vdev_class->unrealize = vu_gpio_device_unrealize; + vdev->vdev_class->get_features = vu_gpio_get_features; + vdev->vdev_class->get_config = vu_gpio_get_config; + vdev->vdev_class->set_status = vu_gpio_set_status; + vdev->vdev_class->guest_notifier_mask = vu_gpio_guest_notifier_mask; +} + +void vu_gpio_init(VirtIODevice *vdev) +{ + DBG("vu_gpio_init()\n"); + + VHostUserGPIO *vhugpio = (VHostUserGPIO *)malloc(sizeof(VHostUserGPIO)); + if (!proxy) { + DBG("proxy memory allocation failed\n"); + goto out; + } + + vdev->vhugpio = vhugpio; + vdev->nvqs = &dev->nvqs; + vhugpio->parent = vdev; + vhugpio->vhost_dev = dev; + + vu_gpio_class_init(vdev); + virtio_loopback_bus_init(vdev->vbus); + +out: + return; +} + +/* TODO: Add queue_num, queue_size as parameters */ +void vu_gpio_device_realize() +{ + int retries, ret; + int i; + + DBG("vu_gpio_device_realize()\n"); + + /* This needs to be added */ + proxy = (VirtIOMMIOProxy *)malloc(sizeof(VirtIOMMIOProxy)); + if (!proxy) { + DBG("proxy memory allocation failed\n"); + goto out_with_error; + } + + *proxy = (VirtIOMMIOProxy) { + .legacy = 1, + }; + + /* VIRTIO_ID_GPIO is 41, check virtio_ids.h in linux */ + virtio_dev_init(global_vdev, "virtio-gpio", 41, + sizeof(struct virtio_gpio_config)); + + vu_gpio_init(global_vdev); + if (!global_vdev->vhugpio) { + DBG("vhugpio memory allocation failed\n"); + goto out_with_proxy; + } + + global_vdev->vhugpio->command_vq = virtio_add_queue(global_vdev, 64, + vu_gpio_handle_output); + global_vdev->vhugpio->interrupt_vq = virtio_add_queue(global_vdev, 64, + vu_gpio_handle_output); + + global_vdev->vhugpio->vhost_vqs = (struct vhost_virtqueue *) + malloc(sizeof(struct vhost_virtqueue *)); + if (!global_vdev->vhugpio->vhost_vqs) { + DBG("vhost_vqs memory allocation failed\n"); + goto out_with_dev; + } + + global_vdev->vhugpio->connected = false; + + retries = REALIZE_CONNECTION_RETRIES; + + do { + ret = vu_gpio_realize_connect(global_vdev->vhugpio); + } while (ret < 0 && retries--); + + if (ret < 0) { + DBG("vu_gpio_realize_connect(): -EPROTO\n"); + do_vhost_user_cleanup(global_vdev, global_vdev->vhugpio); + } + + print_config_gpio((uint8_t *)(&global_vdev->vhugpio->config)); + DBG("(realize completed)\n"); + + return; + + /* TODO: Fix the following considering also do_vhost_user_cleanup() */ +out_with_cmd_vq: + /* free(global_vdev->vhugpio->command_vq); */ +out_with_dev: + free(global_vdev->vhugpio); +out_with_proxy: + free(proxy); +out_with_error: + DBG("Realize funciton return error\n"); + return; +} |