/*
 * QEMU Xen backend support: Operations for true Xen
 *
 * Copyright © 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Authors: David Woodhouse <dwmw2@infradead.org>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

#include "qemu/osdep.h"
#include "qemu/uuid.h"
#include "qapi/error.h"

#include "hw/xen/xen_native.h"
#include "hw/xen/xen_backend_ops.h"

/*
 * If we have new enough libxenctrl then we do not want/need these compat
 * interfaces, despite what the user supplied cflags might say. They
 * must be undefined before including xenctrl.h
 */
#undef XC_WANT_COMPAT_EVTCHN_API
#undef XC_WANT_COMPAT_GNTTAB_API
#undef XC_WANT_COMPAT_MAP_FOREIGN_API

#include <xenctrl.h>

/*
 * We don't support Xen prior to 4.7.1.
 */

#include <xenevtchn.h>
#include <xengnttab.h>
#include <xenforeignmemory.h>

/* Xen before 4.8 */

static int libxengnttab_fallback_grant_copy(xengnttab_handle *xgt,
                                            bool to_domain, uint32_t domid,
                                            XenGrantCopySegment segs[],
                                            unsigned int nr_segs, Error **errp)
{
    uint32_t *refs = g_new(uint32_t, nr_segs);
    int prot = to_domain ? PROT_WRITE : PROT_READ;
    void *map;
    unsigned int i;
    int rc = 0;

    for (i = 0; i < nr_segs; i++) {
        XenGrantCopySegment *seg = &segs[i];

        refs[i] = to_domain ? seg->dest.foreign.ref :
            seg->source.foreign.ref;
    }
    map = xengnttab_map_domain_grant_refs(xgt, nr_segs, domid, refs, prot);
    if (!map) {
        if (errp) {
            error_setg_errno(errp, errno,
                             "xengnttab_map_domain_grant_refs failed");
        }
        rc = -errno;
        goto done;
    }

    for (i = 0; i < nr_segs; i++) {
        XenGrantCopySegment *seg = &segs[i];
        void *page = map + (i * XEN_PAGE_SIZE);

        if (to_domain) {
            memcpy(page + seg->dest.foreign.offset, seg->source.virt,
                   seg->len);
        } else {
            memcpy(seg->dest.virt, page + seg->source.foreign.offset,
                   seg->len);
        }
    }

    if (xengnttab_unmap(xgt, map, nr_segs)) {
        if (errp) {
            error_setg_errno(errp, errno, "xengnttab_unmap failed");
        }
        rc = -errno;
    }

done:
    g_free(refs);
    return rc;
}

#if CONFIG_XEN_CTRL_INTERFACE_VERSION >= 40800

static int libxengnttab_backend_grant_copy(xengnttab_handle *xgt,
                                           bool to_domain, uint32_t domid,
                                           XenGrantCopySegment *segs,
                                           uint32_t nr_segs, Error **errp)
{
    xengnttab_grant_copy_segment_t *xengnttab_segs;
    unsigned int i;
    int rc;

    xengnttab_segs = g_new0(xengnttab_grant_copy_segment_t, nr_segs);

    for (i = 0; i < nr_segs; i++) {
        XenGrantCopySegment *seg = &segs[i];
        xengnttab_grant_copy_segment_t *xengnttab_seg = &xengnttab_segs[i];

        if (to_domain) {
            xengnttab_seg->flags = GNTCOPY_dest_gref;
            xengnttab_seg->dest.foreign.domid = domid;
            xengnttab_seg->dest.foreign.ref = seg->dest.foreign.ref;
            xengnttab_seg->dest.foreign.offset = seg->dest.foreign.offset;
            xengnttab_seg->source.virt = seg->source.virt;
        } else {
            xengnttab_seg->flags = GNTCOPY_source_gref;
            xengnttab_seg->source.foreign.domid = domid;
            xengnttab_seg->source.foreign.ref = seg->source.foreign.ref;
            xengnttab_seg->source.foreign.offset =
                seg->source.foreign.offset;
            xengnttab_seg->dest.virt = seg->dest.virt;
        }

        xengnttab_seg->len = seg->len;
    }

    if (xengnttab_grant_copy(xgt, nr_segs, xengnttab_segs)) {
        if (errp) {
            error_setg_errno(errp, errno, "xengnttab_grant_copy failed");
        }
        rc = -errno;
        goto done;
    }

    rc = 0;
    for (i = 0; i < nr_segs; i++) {
        xengnttab_grant_copy_segment_t *xengnttab_seg = &xengnttab_segs[i];

        if (xengnttab_seg->status != GNTST_okay) {
            if (errp) {
                error_setg(errp, "xengnttab_grant_copy seg[%u] failed", i);
            }
            rc = -EIO;
            break;
        }
    }

done:
    g_free(xengnttab_segs);
    return rc;
}
#endif

static xenevtchn_handle *libxenevtchn_backend_open(void)
{
    return xenevtchn_open(NULL, 0);
}

struct evtchn_backend_ops libxenevtchn_backend_ops = {
    .open = libxenevtchn_backend_open,
    .close = xenevtchn_close,
    .bind_interdomain = xenevtchn_bind_interdomain,
    .unbind = xenevtchn_unbind,
    .get_fd = xenevtchn_fd,
    .notify = xenevtchn_notify,
    .unmask = xenevtchn_unmask,
    .pending = xenevtchn_pending,
};

static xengnttab_handle *libxengnttab_backend_open(void)
{
    return xengnttab_open(NULL, 0);
}

static int libxengnttab_backend_unmap(xengnttab_handle *xgt,
                                      void *start_address, uint32_t *refs,
                                      uint32_t count)
{
    return xengnttab_unmap(xgt, start_address, count);
}


static struct gnttab_backend_ops libxengnttab_backend_ops = {
    .features = XEN_GNTTAB_OP_FEATURE_MAP_MULTIPLE,
    .open = libxengnttab_backend_open,
    .close = xengnttab_close,
    .grant_copy = libxengnttab_fallback_grant_copy,
    .set_max_grants = xengnttab_set_max_grants,
    .map_refs = xengnttab_map_domain_grant_refs,
    .unmap = libxengnttab_backend_unmap,
};

static void *libxenforeignmem_backend_map(uint32_t dom, void *addr, int prot,
                                          size_t pages, xen_pfn_t *pfns,
                                          int *errs)
{
    return xenforeignmemory_map2(xen_fmem, dom, addr, prot, 0, pages, pfns,
                                 errs);
}

static int libxenforeignmem_backend_unmap(void *addr, size_t pages)
{
    return xenforeignmemory_unmap(xen_fmem, addr, pages);
}

struct foreignmem_backend_ops libxenforeignmem_backend_ops = {
    .map = libxenforeignmem_backend_map,
    .unmap = libxenforeignmem_backend_unmap,
};

struct qemu_xs_handle {
    struct xs_handle *xsh;
    NotifierList notifiers;
};

static void watch_event(void *opaque)
{
    struct qemu_xs_handle *h = opaque;

    for (;;) {
        char **v = xs_check_watch(h->xsh);

        if (!v) {
            break;
        }

        notifier_list_notify(&h->notifiers, v);
        free(v);
    }
}

static struct qemu_xs_handle *libxenstore_open(void)
{
    struct xs_handle *xsh = xs_open(0);
    struct qemu_xs_handle *h;

    if (!xsh) {
        return NULL;
    }

    h = g_new0(struct qemu_xs_handle, 1);
    h->xsh = xsh;

    notifier_list_init(&h->notifiers);
    qemu_set_fd_handler(xs_fileno(h->xsh), watch_event, NULL, h);

    return h;
}

static void libxenstore_close(struct qemu_xs_handle *h)
{
    g_assert(notifier_list_empty(&h->notifiers));
    qemu_set_fd_handler(xs_fileno(h->xsh), NULL, NULL, NULL);
    xs_close(h->xsh);
    g_free(h);
}

static char *libxenstore_get_domain_path(struct qemu_xs_handle *h,
                                         unsigned int domid)
{
    return xs_get_domain_path(h->xsh, domid);
}

static char **libxenstore_directory(struct qemu_xs_handle *h,
                                    xs_transaction_t t, const char *path,
                                    unsigned int *num)
{
    return xs_directory(h->xsh, t, path, num);
}

static void *libxenstore_read(struct qemu_xs_handle *h, xs_transaction_t t,
                              const char *path, unsigned int *len)
{
    return xs_read(h->xsh, t, path, len);
}

static bool libxenstore_write(struct qemu_xs_handle *h, xs_transaction_t t,
                              const char *path, const void *data,
                              unsigned int len)
{
    return xs_write(h->xsh, t, path, data, len);
}

static bool libxenstore_create(struct qemu_xs_handle *h, xs_transaction_t t,
                               unsigned int owner, unsigned int domid,
                               unsigned int perms, const char *path)
{
    struct xs_permissions perms_list[] = {
        {
            .id    = owner,
            .perms = XS_PERM_NONE,
        },
        {
            .id    = domid,
            .perms = perms,
        },
    };

    if (!xs_mkdir(h->xsh, t, path)) {
        return false;
    }

    return xs_set_permissions(h->xsh, t, path, perms_list,
                              ARRAY_SIZE(perms_list));
}

static bool libxenstore_destroy(struct qemu_xs_handle *h, xs_transaction_t t,
                                const char *path)
{
    return xs_rm(h->xsh, t, path);
}

struct qemu_xs_watch {
    char *path;
    char *token;
    xs_watch_fn fn;
    void *opaque;
    Notifier notifier;
};

static void watch_notify(Notifier *n, void *data)
{
    struct qemu_xs_watch *w = container_of(n, struct qemu_xs_watch, notifier);
    const char **v = data;

    if (!strcmp(w->token, v[XS_WATCH_TOKEN])) {
        w->fn(w->opaque, v[XS_WATCH_PATH]);
    }
}

static struct qemu_xs_watch *new_watch(const char *path, xs_watch_fn fn,
                                       void *opaque)
{
    struct qemu_xs_watch *w = g_new0(struct qemu_xs_watch, 1);
    QemuUUID uuid;

    qemu_uuid_generate(&uuid);

    w->token = qemu_uuid_unparse_strdup(&uuid);
    w->path = g_strdup(path);
    w->fn = fn;
    w->opaque = opaque;
    w->notifier.notify = watch_notify;

    return w;
}

static void free_watch(struct qemu_xs_watch *w)
{
    g_free(w->token);
    g_free(w->path);

    g_free(w);
}

static struct qemu_xs_watch *libxenstore_watch(struct qemu_xs_handle *h,
                                               const char *path, xs_watch_fn fn,
                                               void *opaque)
{
    struct qemu_xs_watch *w = new_watch(path, fn, opaque);

    notifier_list_add(&h->notifiers, &w->notifier);

    if (!xs_watch(h->xsh, path, w->token)) {
        notifier_remove(&w->notifier);
        free_watch(w);
        return NULL;
    }

    return w;
}

static void libxenstore_unwatch(struct qemu_xs_handle *h,
                                struct qemu_xs_watch *w)
{
    xs_unwatch(h->xsh, w->path, w->token);
    notifier_remove(&w->notifier);
    free_watch(w);
}

static xs_transaction_t libxenstore_transaction_start(struct qemu_xs_handle *h)
{
    return xs_transaction_start(h->xsh);
}

static bool libxenstore_transaction_end(struct qemu_xs_handle *h,
                                        xs_transaction_t t, bool abort)
{
    return xs_transaction_end(h->xsh, t, abort);
}

struct xenstore_backend_ops libxenstore_backend_ops = {
    .open = libxenstore_open,
    .close = libxenstore_close,
    .get_domain_path = libxenstore_get_domain_path,
    .directory = libxenstore_directory,
    .read = libxenstore_read,
    .write = libxenstore_write,
    .create = libxenstore_create,
    .destroy = libxenstore_destroy,
    .watch = libxenstore_watch,
    .unwatch = libxenstore_unwatch,
    .transaction_start = libxenstore_transaction_start,
    .transaction_end = libxenstore_transaction_end,
};

void setup_xen_backend_ops(void)
{
#if CONFIG_XEN_CTRL_INTERFACE_VERSION >= 40800
    xengnttab_handle *xgt = xengnttab_open(NULL, 0);

    if (xgt) {
        if (xengnttab_grant_copy(xgt, 0, NULL) == 0) {
            libxengnttab_backend_ops.grant_copy = libxengnttab_backend_grant_copy;
        }
        xengnttab_close(xgt);
    }
#endif
    xen_evtchn_ops = &libxenevtchn_backend_ops;
    xen_gnttab_ops = &libxengnttab_backend_ops;
    xen_foreignmem_ops = &libxenforeignmem_backend_ops;
    xen_xenstore_ops = &libxenstore_backend_ops;
}
