/*
 * Copyright 2022 Google LLC
 * SPDX-License-Identifier: MIT
 */

#ifndef MSM_PROTO_H_
#define MSM_PROTO_H_

/**
 * General protocol notes:
 * 1) Request (req) messages are generally sent over DRM_VIRTGPU_EXECBUFFER
 *    but can also be sent via DRM_VIRTGPU_RESOURCE_CREATE_BLOB (in which
 *    case they are processed by the host before ctx->get_blob())
 * 2) Response (rsp) messages are returned via shmem->rsp_mem, at an offset
 *    specified by the guest in the req message.  Not all req messages have
 *    a rsp.
 * 3) Host and guest could have different pointer sizes, ie. 32b guest and
 *    64b host, or visa versa, so similar to kernel uabi, req and rsp msgs
 *    should be explicitly padded to avoid 32b vs 64b struct padding issues
 */

/**
 * Defines the layout of shmem buffer used for host->guest communication.
 */
struct msm_shmem {
   /**
    * The sequence # of last cmd processed by the host
    */
   uint32_t seqno;

   /**
    * Offset to the start of rsp memory region in the shmem buffer.  This
    * is set by the host when the shmem buffer is allocated, to allow for
    * extending the shmem buffer with new fields.  The size of the rsp
    * memory region is the size of the shmem buffer (controlled by the
    * guest) minus rsp_mem_offset.
    *
    * The guest should use the msm_shmem_has_field() macro to determine
    * if the host supports a given field, ie. to handle compatibility of
    * newer guest vs older host.
    *
    * Making the guest userspace responsible for backwards compatibility
    * simplifies the host VMM.
    */
   uint32_t rsp_mem_offset;

#define msm_shmem_has_field(shmem, field) ({                         \
      struct msm_shmem *_shmem = (shmem);                            \
      (_shmem->rsp_mem_offset > offsetof(struct msm_shmem, field));  \
   })

   /**
    * Counter that is incremented on asynchronous errors, like SUBMIT
    * or GEM_NEW failures.  The guest should treat errors as context-
    * lost.
    */
   uint32_t async_error;

   /**
    * Counter that is incremented on global fault (see MSM_PARAM_FAULTS)
    */
   uint32_t global_faults;
};

#define DEFINE_CAST(parent, child)                                             \
   static inline struct child *to_##child(const struct parent *x)              \
   {                                                                           \
      return (struct child *)x;                                                \
   }

/*
 * Possible cmd types for "command stream", ie. payload of EXECBUF ioctl:
 */
enum msm_ccmd {
   MSM_CCMD_NOP = 1,         /* No payload, can be used to sync with host */
   MSM_CCMD_IOCTL_SIMPLE,
   MSM_CCMD_GEM_NEW,
   MSM_CCMD_GEM_SET_IOVA,
   MSM_CCMD_GEM_CPU_PREP,
   MSM_CCMD_GEM_SET_NAME,
   MSM_CCMD_GEM_SUBMIT,
   MSM_CCMD_GEM_UPLOAD,
   MSM_CCMD_SUBMITQUEUE_QUERY,
   MSM_CCMD_WAIT_FENCE,
   MSM_CCMD_SET_DEBUGINFO,
   MSM_CCMD_LAST,
};

struct msm_ccmd_req {
   uint32_t cmd;
   uint32_t len;
   uint32_t seqno;

   /* Offset into shmem ctrl buffer to write response.  The host ensures
    * that it doesn't write outside the bounds of the ctrl buffer, but
    * otherwise it is up to the guest to manage allocation of where responses
    * should be written in the ctrl buf.
    */
   uint32_t rsp_off;
};

struct msm_ccmd_rsp {
   uint32_t len;
};

#ifdef __cplusplus
#define MSM_CCMD(_cmd, _len) {                      \
       .cmd = MSM_CCMD_##_cmd,                      \
       .len = (_len),                               \
   }
#else
#define MSM_CCMD(_cmd, _len) (struct msm_ccmd_req){ \
       .cmd = MSM_CCMD_##_cmd,                      \
       .len = (_len),                               \
   }
#endif

/*
 * MSM_CCMD_NOP
 */
struct msm_ccmd_nop_req {
   struct msm_ccmd_req hdr;
};

/*
 * MSM_CCMD_IOCTL_SIMPLE
 *
 * Forward simple/flat IOC_RW or IOC_W ioctls.  Limited ioctls are supported.
 */
struct msm_ccmd_ioctl_simple_req {
   struct msm_ccmd_req hdr;

   uint32_t cmd;
   uint8_t payload[];
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_ioctl_simple_req)

struct msm_ccmd_ioctl_simple_rsp {
   struct msm_ccmd_rsp hdr;

   /* ioctl return value, interrupted syscalls are handled on the host without
    * returning to the guest.
    */
   int32_t ret;

   /* The output payload for IOC_RW ioctls, the payload is the same size as
    * msm_context_cmd_ioctl_simple_req.
    *
    * For IOC_W ioctls (userspace writes, kernel reads) this is zero length.
    */
   uint8_t payload[];
};

/*
 * MSM_CCMD_GEM_NEW
 *
 * GEM buffer allocation, maps to DRM_MSM_GEM_NEW plus DRM_MSM_GEM_INFO to
 * set the BO's iova (to avoid extra guest -> host trip)
 *
 * No response.
 */
struct msm_ccmd_gem_new_req {
   struct msm_ccmd_req hdr;

   uint64_t iova;
   uint64_t size;
   uint32_t flags;
   uint32_t blob_id;
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_gem_new_req)

/*
 * MSM_CCMD_GEM_SET_IOVA
 *
 * Set the buffer iova (for imported BOs).  Also used to release the iova
 * (by setting it to zero) when a BO is freed.
 */
struct msm_ccmd_gem_set_iova_req {
   struct msm_ccmd_req hdr;

   uint64_t iova;
   uint32_t res_id;
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_gem_set_iova_req)

/*
 * MSM_CCMD_GEM_CPU_PREP
 *
 * Maps to DRM_MSM_GEM_CPU_PREP
 *
 * Note: Since we don't want to block the single threaded host, this returns
 * immediately with -EBUSY if the fence is not yet signaled.  The guest
 * should poll if needed.
 */
struct msm_ccmd_gem_cpu_prep_req {
   struct msm_ccmd_req hdr;

   uint32_t res_id;
   uint32_t op;
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_gem_cpu_prep_req)

struct msm_ccmd_gem_cpu_prep_rsp {
   struct msm_ccmd_rsp hdr;

   int32_t ret;
};

/*
 * MSM_CCMD_GEM_SET_NAME
 *
 * Maps to DRM_MSM_GEM_INFO:MSM_INFO_SET_NAME
 *
 * No response.
 */
struct msm_ccmd_gem_set_name_req {
   struct msm_ccmd_req hdr;

   uint32_t res_id;
   /* Note: packet size aligned to 4 bytes, so the string name may
    * be shorter than the packet header indicates.
    */
   uint32_t len;
   uint8_t  payload[];
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_gem_set_name_req)

/*
 * MSM_CCMD_GEM_SUBMIT
 *
 * Maps to DRM_MSM_GEM_SUBMIT
 *
 * The actual for-reals cmdstream submission.  Note this intentionally
 * does not support relocs, since we already require a non-ancient
 * kernel.
 *
 * Note, no in/out fence-fd, that synchronization is handled on guest
 * kernel side (ugg).. need to come up with a better story for fencing.
 * We probably need to sort something out for that to handle syncobjs.
 *
 * No response.
 */
struct msm_ccmd_gem_submit_req {
   struct msm_ccmd_req hdr;

   uint32_t flags;
   uint32_t queue_id;
   uint32_t nr_bos;
   uint32_t nr_cmds;

   /**
    * The fence "seqno" assigned by the guest userspace.  The host SUBMIT
    * ioctl uses the MSM_SUBMIT_FENCE_SN_IN flag to let the guest assign
    * the sequence #, to avoid the guest needing to wait for a response
    * from the host.
    */
   uint32_t fence;

   /**
    * Payload is first an array of 'struct drm_msm_gem_submit_bo' of
    * length determined by nr_bos (note that handles are guest resource
    * ids which are translated to host GEM handles by the host VMM),
    * followed by an array of 'struct drm_msm_gem_submit_cmd' of length
    * determined by nr_cmds
    */
   int8_t   payload[];
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_gem_submit_req)

/*
 * MSM_CCMD_GEM_UPLOAD
 *
 * Upload data to a GEM buffer
 *
 * No response.
 */
struct msm_ccmd_gem_upload_req {
   struct msm_ccmd_req hdr;

   uint32_t res_id;
   uint32_t pad;
   uint32_t off;

   /* Note: packet size aligned to 4 bytes, so the payload may
    * be shorter than the packet header indicates.
    */
   uint32_t len;
   uint8_t  payload[];
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_gem_upload_req)

/*
 * MSM_CCMD_SUBMITQUEUE_QUERY
 *
 * Maps to DRM_MSM_SUBMITQUEUE_QUERY
 */
struct msm_ccmd_submitqueue_query_req {
   struct msm_ccmd_req hdr;

   uint32_t queue_id;
   uint32_t param;
   uint32_t len;   /* size of payload in rsp */
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_submitqueue_query_req)

struct msm_ccmd_submitqueue_query_rsp {
   struct msm_ccmd_rsp hdr;

   int32_t  ret;
   uint32_t out_len;
   uint8_t  payload[];
};

/*
 * MSM_CCMD_WAIT_FENCE
 *
 * Maps to DRM_MSM_WAIT_FENCE
 *
 * Note: Since we don't want to block the single threaded host, this returns
 * immediately with -ETIMEDOUT if the fence is not yet signaled.  The guest
 * should poll if needed.
 */
struct msm_ccmd_wait_fence_req {
   struct msm_ccmd_req hdr;

   uint32_t queue_id;
   uint32_t fence;
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_wait_fence_req)

struct msm_ccmd_wait_fence_rsp {
   struct msm_ccmd_rsp hdr;

   int32_t ret;
};

/*
 * MSM_CCMD_SET_DEBUGINFO
 *
 * Set per-guest-process debug info (comm and cmdline).  For GPU faults/
 * crashes, it isn't too useful to see the crosvm (for ex.) comm/cmdline,
 * since the host process is only a proxy.  This allows the guest to
 * pass through the guest process comm and commandline for debugging
 * purposes.
 *
 * No response.
 */
struct msm_ccmd_set_debuginfo_req {
   struct msm_ccmd_req hdr;

   uint32_t comm_len;
   uint32_t cmdline_len;

   /**
    * Payload is first the comm string followed by cmdline string, padded
    * out to a multiple of 4.
    */
   int8_t   payload[];
};
DEFINE_CAST(msm_ccmd_req, msm_ccmd_set_debuginfo_req)

#endif /* MSM_PROTO_H_ */
