text: /*
   * Virtio PMEM device
   *
   * Copyright (C) 2018-2019 Red Hat, Inc.
   *
   * Authors:
   *  Pankaj Gupta <pagupta@redhat.com>
   *  David Hildenbrand <david@redhat.com>
   *
   * This work is licensed under the terms of the GNU GPL, version 2.
   * See the COPYING file in the top-level directory.
   */
  
  #include "qemu/osdep.h"
  #include "qapi/error.h"
  #include "qemu-common.h"
  #include "qemu/error-report.h"
  #include "qemu/main-loop.h"
  #include "hw/virtio/virtio-pmem.h"
  #include "hw/qdev-properties.h"
  #include "hw/virtio/virtio-access.h"
  #include "standard-headers/linux/virtio_ids.h"
  #include "standard-headers/linux/virtio_pmem.h"
  #include "sysemu/hostmem.h"
  #include "block/aio.h"
  #include "block/thread-pool.h"
  #include "trace.h"
  
  typedef struct VirtIODeviceRequest {
      VirtQueueElement elem;
      int fd;
      VirtIOPMEM *pmem;
      VirtIODevice *vdev;
      struct virtio_pmem_req req;
      struct virtio_pmem_resp resp;
  } VirtIODeviceRequest;
  
  static int worker_cb(void *opaque)
  {
      VirtIODeviceRequest *req_data = opaque;
      int err = 0;
  
      /* flush raw backing image */
      err = fsync(req_data->fd);
      trace_virtio_pmem_flush_done(err);
      if (err != 0) {
          err = 1;
      }
  
      virtio_stl_p(req_data->vdev, &req_data->resp.ret, err);
  
      return 0;
  }
  
  static void done_cb(void *opaque, int ret)
  {
      VirtIODeviceRequest *req_data = opaque;
      int len = iov_from_buf(req_data->elem.in_sg, req_data->elem.in_num, 0,
                                &req_data->resp, sizeof(struct virtio_pmem_resp));
  
      /* Callbacks are serialized, so no need to use atomic ops. */
      virtqueue_push(req_data->pmem->rq_vq, &req_data->elem, len);
      virtio_notify((VirtIODevice *)req_data->pmem, req_data->pmem->rq_vq);
      trace_virtio_pmem_response();
      g_free(req_data);
  }
  
  static void virtio_pmem_flush(VirtIODevice *vdev, VirtQueue *vq)
  {
      VirtIODeviceRequest *req_data;
      VirtIOPMEM *pmem = VIRTIO_PMEM(vdev);
      HostMemoryBackend *backend = MEMORY_BACKEND(pmem->memdev);
      ThreadPool *pool = aio_get_thread_pool(qemu_get_aio_context());
  
      trace_virtio_pmem_flush_request();
      req_data = virtqueue_pop(vq, sizeof(VirtIODeviceRequest));
      if (!req_data) {
          virtio_error(vdev, "virtio-pmem missing request data");
          return;
      }
  
      if (req_data->elem.out_num < 1 || req_data->elem.in_num < 1) {
          virtio_error(vdev, "virtio-pmem request not proper");
          virtqueue_detach_element(vq, (VirtQueueElement *)req_data, 0);
          g_free(req_data);
          return;
      }
      req_data->fd   = memory_region_get_fd(&backend->mr);
      req_data->pmem = pmem;
      req_data->vdev = vdev;
      thread_pool_submit_aio(pool, worker_cb, req_data, done_cb, req_data);
  }
  
  static void virtio_pmem_get_config(VirtIODevice *vdev, uint8_t *config)
  {
      VirtIOPMEM *pmem = VIRTIO_PMEM(vdev);
      struct virtio_pmem_config *pmemcfg = (struct virtio_pmem_config *) config;
  
      virtio_stq_p(vdev, &pmemcfg->start, pmem->start);
      virtio_stq_p(vdev, &pmemcfg->size, memory_region_size(&pmem->memdev->mr));
  }
  
  static uint64_t virtio_pmem_get_features(VirtIODevice *vdev, uint64_t features,
                                          Error **errp)
  {
      return features;
  }
  
  static void virtio_pmem_realize(DeviceState *dev, Error **errp)
  {
      VirtIODevice *vdev = VIRTIO_DEVICE(dev);
      VirtIOPMEM *pmem = VIRTIO_PMEM(dev);
  
      if (!pmem->memdev) {
          error_setg(errp, "virtio-pmem memdev not set");
          return;
      }
  
      if (host_memory_backend_is_mapped(pmem->memdev)) {
          error_setg(errp, "can't use already busy memdev: %s",
                     object_get_canonical_path_component(OBJECT(pmem->memdev)));
          return;
      }
  
      host_memory_backend_set_mapped(pmem->memdev, true);
      virtio_init(vdev, TYPE_VIRTIO_PMEM, VIRTIO_ID_PMEM,
                  sizeof(struct virtio_pmem_config));
      pmem->rq_vq = virtio_add_queue(vdev, 128, virtio_pmem_flush);
  }
  
  static void virtio_pmem_unrealize(DeviceState *dev)
  {
      VirtIODevice *vdev = VIRTIO_DEVICE(dev);
      VirtIOPMEM *pmem = VIRTIO_PMEM(dev);
  
      host_memory_backend_set_mapped(pmem->memdev, false);
      virtio_delete_queue(pmem->rq_vq);
      virtio_cleanup(vdev);
  }
  
  static void virtio_pmem_fill_device_info(const VirtIOPMEM *pmem,
                                           VirtioPMEMDeviceInfo *vi)
  {
      vi->memaddr = pmem->start;
      vi->size    = memory_region_size(&pmem->memdev->mr);
      vi->memdev  = object_get_canonical_path(OBJECT(pmem->memdev));
  }
  
  static MemoryRegion *virtio_pmem_get_memory_region(VirtIOPMEM *pmem,
                                                     Error **errp)
  {
      if (!pmem->memdev) {
          error_setg(errp, "'%s' property must be set", VIRTIO_PMEM_MEMDEV_PROP);
          return NULL;
      }
  
      return &pmem->memdev->mr;
  }
  
  static Property virtio_pmem_properties[] = {
      DEFINE_PROP_UINT64(VIRTIO_PMEM_ADDR_PROP, VirtIOPMEM, start, 0),
      DEFINE_PROP_LINK(VIRTIO_PMEM_MEMDEV_PROP, VirtIOPMEM, memdev,
                       TYPE_MEMORY_BACKEND, HostMemoryBackend *),
      DEFINE_PROP_END_OF_LIST(),
  };
  
  static void virtio_pmem_class_init(ObjectClass *klass, void *data)
  {
      DeviceClass *dc = DEVICE_CLASS(klass);
      VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
      VirtIOPMEMClass *vpc = VIRTIO_PMEM_CLASS(klass);
  
      device_class_set_props(dc, virtio_pmem_properties);
  
      vdc->realize = virtio_pmem_realize;
      vdc->unrealize = virtio_pmem_unrealize;
      vdc->get_config = virtio_pmem_get_config;
      vdc->get_features = virtio_pmem_get_features;
  
      vpc->fill_device_info = virtio_pmem_fill_device_info;
      vpc->get_memory_region = virtio_pmem_get_memory_region;
      set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
  }
  
  static TypeInfo virtio_pmem_info = {
      .name          = TYPE_VIRTIO_PMEM,
      .parent        = TYPE_VIRTIO_DEVICE,
      .class_size    = sizeof(VirtIOPMEMClass),
      .class_init    = virtio_pmem_class_init,
      .instance_size = sizeof(VirtIOPMEM),
  };
  
  static void virtio_register_types(void)
  {
      type_register_static(&virtio_pmem_info);
  }
  
  type_init(virtio_register_types)
  