// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+

/*
 * Ioctls implementations for the virtio-media driver.
 *
 * Copyright (c) 2023-2024 Google LLC.
 */

#include <linux/virtio_config.h>
#include <linux/vmalloc.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h>

#include "scatterlist_filler.h"
#include "virtio_media.h"

/**
 * Send an ioctl that has no driver payload, but expects a reponse from the host (i.e. an
 * ioctl specified with _IOR).
 *
 * Returns 0 in case of success, or a negative error code.
 */
static int virtio_media_send_r_ioctl(struct v4l2_fh *fh, u32 ioctl,
				     void *ioctl_data, size_t ioctl_data_len)
{
	struct video_device *video_dev = fh->vdev;
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct virtio_media_session *session = fh_to_session(fh);
	struct scatterlist *sgs[3];
	struct scatterlist_filler filler = {
		.descs = session->command_sgs.sgl,
		.num_descs = DESC_CHAIN_MAX_LEN,
		.cur_desc = 0,
		.shadow_buffer = session->shadow_buf,
		.shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
		.shadow_buffer_pos = 0,
		.sgs = sgs,
		.num_sgs = ARRAY_SIZE(sgs),
		.cur_sg = 0,
	};
	int ret;

	/* Command descriptor */
	ret = scatterlist_filler_add_ioctl_cmd(&filler, session, ioctl);
	if (ret)
		return ret;

	/* Response descriptor */
	ret = scatterlist_filler_add_ioctl_resp(&filler, session);
	if (ret)
		return ret;

	/* Response payload */
	ret = scatterlist_filler_add_data(&filler, ioctl_data, ioctl_data_len);
	if (ret) {
		v4l2_err(&vv->v4l2_dev,
			 "failed to prepare command descriptor chain\n");
		return ret;
	}

	ret = virtio_media_send_command(
		vv, sgs, 1, 2,
		sizeof(struct virtio_media_resp_ioctl) + ioctl_data_len, NULL);
	if (ret < 0)
		return ret;

	ret = scatterlist_filler_retrieve_data(session, filler.sgs[2],
					       ioctl_data, ioctl_data_len);
	if (ret) {
		v4l2_err(&vv->v4l2_dev,
			 "failed to retrieve response descriptor chain\n");
		return ret;
	}

	return 0;
}
/**
 * Send an ioctl that does not expect a reply beyond an error status (i.e. an
 * ioctl specified with _IOW) to the host.
 *
 * Returns 0 in case of success, or a negative error code.
 */
static int virtio_media_send_w_ioctl(struct v4l2_fh *fh, u32 ioctl,
				     const void *ioctl_data,
				     size_t ioctl_data_len)
{
	struct video_device *video_dev = fh->vdev;
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct virtio_media_session *session = fh_to_session(fh);
	struct scatterlist *sgs[3];
	struct scatterlist_filler filler = {
		.descs = session->command_sgs.sgl,
		.num_descs = DESC_CHAIN_MAX_LEN,
		.cur_desc = 0,
		.shadow_buffer = session->shadow_buf,
		.shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
		.shadow_buffer_pos = 0,
		.sgs = sgs,
		.num_sgs = ARRAY_SIZE(sgs),
		.cur_sg = 0,
	};
	int ret;

	/* Command descriptor */
	ret = scatterlist_filler_add_ioctl_cmd(&filler, session, ioctl);
	if (ret)
		return ret;

	/* Command payload */
	ret = scatterlist_filler_add_data(&filler, (void *)ioctl_data,
					  ioctl_data_len);
	if (ret) {
		v4l2_err(&vv->v4l2_dev,
			 "failed to prepare command descriptor chain\n");
		return ret;
	}

	/* Response descriptor */
	ret = scatterlist_filler_add_ioctl_resp(&filler, session);
	if (ret)
		return ret;

	ret = virtio_media_send_command(
		vv, sgs, 2, 1, sizeof(struct virtio_media_resp_ioctl), NULL);
	if (ret < 0)
		return ret;

	return 0;
}

/**
 * Sends an ioctl that expects a response of exactly the same size as the
 * input (i.e. an ioctl specified with _IOWR) to the host.
 *
 * This corresponds to what most V4L2 ioctls do. For instance VIDIOC_ENUM_FMT
 * takes a partially-initialized struct v4l2_fmtdesc and returns its filled
 * version.
 *
 * Ioctls specified with _IOR can also use this, since the host will simply
 * ignore the extra input data provided.
 *
 * Returns 0 in case of success, or a negative error code.
 */
static int virtio_media_send_wr_ioctl(struct v4l2_fh *fh, u32 ioctl,
				      void *ioctl_data, size_t ioctl_data_len,
				      size_t min_resp_payload)
{
	struct video_device *video_dev = fh->vdev;
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct virtio_media_session *session = fh_to_session(fh);
	struct scatterlist *sgs[4];
	struct scatterlist_filler filler = {
		.descs = session->command_sgs.sgl,
		.num_descs = DESC_CHAIN_MAX_LEN,
		.cur_desc = 0,
		.shadow_buffer = session->shadow_buf,
		.shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
		.shadow_buffer_pos = 0,
		.sgs = sgs,
		.num_sgs = ARRAY_SIZE(sgs),
		.cur_sg = 0,
	};
	int ret;

	/* Command descriptor */
	ret = scatterlist_filler_add_ioctl_cmd(&filler, session, ioctl);
	if (ret)
		return ret;

	/* Command payload */
	ret = scatterlist_filler_add_data(&filler, ioctl_data, ioctl_data_len);
	if (ret) {
		v4l2_err(&vv->v4l2_dev,
			 "failed to prepare command descriptor chain\n");
		return ret;
	}

	/* Response descriptor */
	ret = scatterlist_filler_add_ioctl_resp(&filler, session);
	if (ret)
		return ret;

	/* Response payload, same as command */
	ret = scatterlist_filler_add_sg(&filler, filler.sgs[1]);
	if (ret)
		return ret;

	ret = virtio_media_send_command(vv, sgs, 2, 2,
					sizeof(struct virtio_media_resp_ioctl) +
						min_resp_payload,
					NULL);
	if (ret < 0)
		return ret;

	ret = scatterlist_filler_retrieve_data(session, filler.sgs[3],
					       ioctl_data, ioctl_data_len);
	if (ret) {
		v4l2_err(&vv->v4l2_dev,
			 "failed to retrieve response descriptor chain\n");
		return ret;
	}

	return 0;
}

static int virtio_media_send_buffer_ioctl(struct v4l2_fh *fh, u32 ioctl_code,
					  struct v4l2_buffer *b)
{
	struct video_device *video_dev = fh->vdev;
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct virtio_media_session *session = fh_to_session(fh);
	struct v4l2_plane *planes_backup = NULL;
	u32 length_backup = 0;
	struct scatterlist *sgs[64];
	/* End of the device-readable buffer SGs, to reuse in device-writable section. */
	size_t num_cmd_sgs;
	size_t end_buf_sg;
	struct scatterlist_filler filler = {
		.descs = session->command_sgs.sgl,
		.num_descs = DESC_CHAIN_MAX_LEN,
		.cur_desc = 0,
		.shadow_buffer = session->shadow_buf,
		.shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
		.shadow_buffer_pos = 0,
		.sgs = sgs,
		.num_sgs = ARRAY_SIZE(sgs),
		.cur_sg = 0,
	};
	size_t resp_len;
	int ret;
	int i;

	if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
		return -EINVAL;

	if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
		planes_backup = b->m.planes;
		length_backup = b->length;
	}

	/* Command descriptor */
	ret = scatterlist_filler_add_ioctl_cmd(&filler, session, ioctl_code);
	if (ret)
		return ret;

	/* Command payload (struct v4l2_buffer) */
	ret = scatterlist_filler_add_buffer(&filler, b);
	if (ret < 0)
		return ret;

	end_buf_sg = filler.cur_sg;

	/* Payload of USERPTR buffers, if relevant */
	ret = scatterlist_filler_add_buffer_userptr(&filler, b);
	if (ret < 0)
		return ret;

	num_cmd_sgs = filler.cur_sg;

	/* Response descriptor */
	ret = scatterlist_filler_add_ioctl_resp(&filler, session);
	if (ret)
		return ret;

	/* Response payload (same as input, but no userptr mapping) */
	for (i = 1; i < end_buf_sg; i++) {
		ret = scatterlist_filler_add_sg(&filler, filler.sgs[i]);
		if (ret < 0)
			return ret;
	}

	ret = virtio_media_send_command(
		vv, filler.sgs, num_cmd_sgs, filler.cur_sg - num_cmd_sgs,
		sizeof(struct virtio_media_resp_ioctl) + sizeof(*b), &resp_len);

	if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
		b->m.planes = planes_backup;
		if (b->length > length_backup)
			return -ENOSPC;
	}

	if (ret < 0)
		return ret;

	resp_len -= sizeof(struct virtio_media_resp_ioctl);

	/* Make sure that the reply's length covers our v4l2_buffer */
	if (resp_len < sizeof(*b))
		return -EINVAL;

	ret = scatterlist_filler_retrieve_buffer(session, &sgs[num_cmd_sgs + 1],
						 b, length_backup);
	if (ret) {
		v4l2_err(&vv->v4l2_dev,
			 "failed to retrieve response descriptor chain\n");
		return ret;
	}

	/* TODO ideally we should not be doing this twice, but the scatterlist may screw us up here? */
	if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
		b->m.planes = planes_backup;
		if (b->length > length_backup)
			return -ENOSPC;
	}

	return 0;
}

/**
 * Queues an ioctl that sends a v4l2_ext_controls to the host and receives an updated version.
 *
 * v4l2_ext_controls has a pointer to an array of v4l2_ext_control, and also
 * potentially pointers to user-space memory that we need to map properly,
 * hence the dedicated function.
 *
 */
static int virtio_media_send_ext_controls_ioctl(struct v4l2_fh *fh,
						u32 ioctl_code,
						struct v4l2_ext_controls *ctrls)
{
	struct video_device *video_dev = fh->vdev;
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct virtio_media_session *session = fh_to_session(fh);
	size_t num_cmd_sgs;
	struct v4l2_ext_control *controls_backup = ctrls->controls;
	const u32 num_ctrls = ctrls->count;
	struct scatterlist *sgs[64];
	struct scatterlist_filler filler = {
		.descs = session->command_sgs.sgl,
		.num_descs = DESC_CHAIN_MAX_LEN,
		.cur_desc = 0,
		.shadow_buffer = session->shadow_buf,
		.shadow_buffer_size = VIRTIO_SHADOW_BUF_SIZE,
		.shadow_buffer_pos = 0,
		.sgs = sgs,
		.num_sgs = ARRAY_SIZE(sgs),
		.cur_sg = 0,
	};
	size_t resp_len = 0;
	int ret;

	/* Command descriptor */
	ret = scatterlist_filler_add_ioctl_cmd(&filler, session, ioctl_code);
	if (ret)
		return ret;

	/* v4l2_controls and its pointees */
	ret = scatterlist_filler_add_ext_ctrls(&filler, ctrls, true);
	if (ret)
		return ret;

	num_cmd_sgs = filler.cur_sg;

	/* Response descriptor */
	ret = scatterlist_filler_add_ioctl_resp(&filler, session);
	if (ret)
		return ret;

	/*
	 * Response payload (same as input but without userptrs)
	 */
	ret = scatterlist_filler_add_ext_ctrls(&filler, ctrls, false);
	if (ret)
		return ret;

	ret = virtio_media_send_command(
		vv, filler.sgs, num_cmd_sgs, filler.cur_sg - num_cmd_sgs,
		sizeof(struct virtio_media_resp_ioctl) + sizeof(*ctrls),
		&resp_len);

	/* Just in case the host touched these. */
	ctrls->controls = controls_backup;
	if (ctrls->count != num_ctrls) {
		v4l2_err(
			&vv->v4l2_dev,
			"device returned a number of extended controls different than submitted\n");
	}
	if (ctrls->count > num_ctrls)
		return -ENOSPC;

	/* Event if we have received an error, we may need to read our payload back */
	if (ret < 0 && resp_len >= sizeof(struct virtio_media_resp_ioctl) +
					   sizeof(*ctrls)) {
		/* Deliberately ignore the error here as we want to return the previous one */
		scatterlist_filler_retrieve_ext_ctrls(
			session, &sgs[num_cmd_sgs + 1],
			filler.cur_sg - (num_cmd_sgs + 1), ctrls);
		return ret;
	}

	resp_len -= sizeof(struct virtio_media_resp_ioctl);

	/* Make sure that the reply's length covers our v4l2_ext_controls */
	if (resp_len < sizeof(*ctrls))
		return -EINVAL;

	ret = scatterlist_filler_retrieve_ext_ctrls(
		session, &sgs[num_cmd_sgs + 1],
		filler.cur_sg - (num_cmd_sgs + 1), ctrls);
	if (ret)
		return ret;

	return 0;
}

/**
 * Helper function to clear the list of buffers waiting to be dequeued on a
 * queue that has just been streamed off.
 */
static void virtio_media_clear_queue(struct virtio_media *vv,
				     struct virtio_media_session *session,
				     struct virtio_media_queue_state *queue)
{
	struct list_head *p, *n;
	int i;

	mutex_lock(&session->dqbufs_lock);

	list_for_each_safe(p, n, &queue->pending_dqbufs) {
		struct virtio_media_buffer *dqbuf =
			list_entry(p, struct virtio_media_buffer, list);

		list_del(&dqbuf->list);
	}

	mutex_unlock(&session->dqbufs_lock);

	/* All buffers are now dequeued. */
	for (i = 0; i < queue->allocated_bufs; i++) {
		queue->buffers[i].buffer.flags = 0;
	}

	queue->queued_bufs = 0;
	queue->streaming = false;
	queue->is_capture_last = false;
}

/*
 * Macros suitable for defining ioctls with a constant size payload.
 */

#define SIMPLE_WR_IOCTL(name, ioctl, type)                            \
	static int virtio_media_##name(struct file *file, void *fh,   \
				       type *payload)                 \
	{                                                             \
		return virtio_media_send_wr_ioctl(fh, ioctl, payload, \
						  sizeof(*payload),   \
						  sizeof(*payload));  \
	}
#define SIMPLE_R_IOCTL(name, ioctl, type)                            \
	static int virtio_media_##name(struct file *file, void *fh,  \
				       type *payload)                \
	{                                                            \
		return virtio_media_send_r_ioctl(fh, ioctl, payload, \
						 sizeof(*payload));  \
	}
#define SIMPLE_W_IOCTL(name, ioctl, type)                            \
	static int virtio_media_##name(struct file *file, void *fh,  \
				       type *payload)                \
	{                                                            \
		return virtio_media_send_w_ioctl(fh, ioctl, payload, \
						 sizeof(*payload));  \
	}

/*
 * V4L2 ioctl handlers.
 *
 * Most of these functions just forward the ioctl to the host, for these we can
 * use one of the SIMPLE_*_IOCTL macros. Exceptions that have their own
 * standalone function follow.
 */

SIMPLE_WR_IOCTL(enum_fmt, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc)
SIMPLE_WR_IOCTL(g_fmt, VIDIOC_G_FMT, struct v4l2_format)
SIMPLE_WR_IOCTL(s_fmt, VIDIOC_S_FMT, struct v4l2_format)
SIMPLE_WR_IOCTL(try_fmt, VIDIOC_TRY_FMT, struct v4l2_format)
SIMPLE_WR_IOCTL(enum_framesizes, VIDIOC_ENUM_FRAMESIZES,
		struct v4l2_frmsizeenum)
SIMPLE_WR_IOCTL(enum_frameintervals, VIDIOC_ENUM_FRAMEINTERVALS,
		struct v4l2_frmivalenum)
SIMPLE_WR_IOCTL(queryctrl, VIDIOC_QUERYCTRL, struct v4l2_queryctrl)
SIMPLE_WR_IOCTL(g_ctrl, VIDIOC_G_CTRL, struct v4l2_control)
SIMPLE_WR_IOCTL(s_ctrl, VIDIOC_S_CTRL, struct v4l2_control)
SIMPLE_WR_IOCTL(query_ext_ctrl, VIDIOC_QUERY_EXT_CTRL,
		struct v4l2_query_ext_ctrl)
SIMPLE_WR_IOCTL(s_dv_timings, VIDIOC_S_DV_TIMINGS, struct v4l2_dv_timings)
SIMPLE_WR_IOCTL(g_dv_timings, VIDIOC_G_DV_TIMINGS, struct v4l2_dv_timings)
SIMPLE_R_IOCTL(query_dv_timings, VIDIOC_QUERY_DV_TIMINGS,
	       struct v4l2_dv_timings)
SIMPLE_WR_IOCTL(enum_dv_timings, VIDIOC_ENUM_DV_TIMINGS,
		struct v4l2_enum_dv_timings)
SIMPLE_WR_IOCTL(dv_timings_cap, VIDIOC_DV_TIMINGS_CAP,
		struct v4l2_dv_timings_cap)
SIMPLE_WR_IOCTL(enuminput, VIDIOC_ENUMINPUT, struct v4l2_input)
SIMPLE_WR_IOCTL(querymenu, VIDIOC_QUERYMENU, struct v4l2_querymenu)
SIMPLE_WR_IOCTL(enumoutput, VIDIOC_ENUMOUTPUT, struct v4l2_output)
SIMPLE_WR_IOCTL(enumaudio, VIDIOC_ENUMAUDIO, struct v4l2_audio)
SIMPLE_R_IOCTL(g_audio, VIDIOC_G_AUDIO, struct v4l2_audio)
SIMPLE_W_IOCTL(s_audio, VIDIOC_S_AUDIO, const struct v4l2_audio)
SIMPLE_WR_IOCTL(enumaudout, VIDIOC_ENUMAUDOUT, struct v4l2_audioout)
SIMPLE_R_IOCTL(g_audout, VIDIOC_G_AUDOUT, struct v4l2_audioout)
SIMPLE_W_IOCTL(s_audout, VIDIOC_S_AUDOUT, const struct v4l2_audioout)
SIMPLE_WR_IOCTL(g_modulator, VIDIOC_G_MODULATOR, struct v4l2_modulator)
SIMPLE_W_IOCTL(s_modulator, VIDIOC_S_MODULATOR, const struct v4l2_modulator)
SIMPLE_WR_IOCTL(g_selection, VIDIOC_G_SELECTION, struct v4l2_selection)
SIMPLE_WR_IOCTL(s_selection, VIDIOC_S_SELECTION, struct v4l2_selection)
SIMPLE_R_IOCTL(g_enc_index, VIDIOC_G_ENC_INDEX, struct v4l2_enc_idx)
SIMPLE_WR_IOCTL(encoder_cmd, VIDIOC_ENCODER_CMD, struct v4l2_encoder_cmd)
SIMPLE_WR_IOCTL(try_encoder_cmd, VIDIOC_TRY_ENCODER_CMD,
		struct v4l2_encoder_cmd)
SIMPLE_WR_IOCTL(try_decoder_cmd, VIDIOC_TRY_DECODER_CMD,
		struct v4l2_decoder_cmd)
SIMPLE_WR_IOCTL(g_parm, VIDIOC_G_PARM, struct v4l2_streamparm)
SIMPLE_WR_IOCTL(s_parm, VIDIOC_S_PARM, struct v4l2_streamparm)
SIMPLE_R_IOCTL(g_std, VIDIOC_G_STD, v4l2_std_id)
SIMPLE_R_IOCTL(querystd, VIDIOC_QUERYSTD, v4l2_std_id)
SIMPLE_WR_IOCTL(enumstd, VIDIOC_ENUMSTD, struct v4l2_standard)
SIMPLE_WR_IOCTL(g_tuner, VIDIOC_G_TUNER, struct v4l2_tuner)
SIMPLE_W_IOCTL(s_tuner, VIDIOC_S_TUNER, const struct v4l2_tuner)
SIMPLE_WR_IOCTL(g_frequency, VIDIOC_G_FREQUENCY, struct v4l2_frequency)
SIMPLE_W_IOCTL(s_frequency, VIDIOC_S_FREQUENCY, const struct v4l2_frequency)
SIMPLE_WR_IOCTL(enum_freq_bands, VIDIOC_ENUM_FREQ_BANDS,
		struct v4l2_frequency_band)
SIMPLE_WR_IOCTL(g_sliced_vbi_cap, VIDIOC_G_SLICED_VBI_CAP,
		struct v4l2_sliced_vbi_cap)
SIMPLE_W_IOCTL(s_hw_freq_seek, VIDIOC_S_HW_FREQ_SEEK,
	       const struct v4l2_hw_freq_seek)

/*
 * QUERYCAP is handled by reading the configuration area.
 *
 */

static int virtio_media_querycap(struct file *file, void *fh,
				 struct v4l2_capability *cap)
{
	struct video_device *video_dev = video_devdata(file);
	struct virtio_media *vv = to_virtio_media(video_dev);

	/* TODO add proper number? */
	strncpy(cap->bus_info, "platform:virtio-media0", sizeof(cap->bus_info));

	if (!driver_name) {
		strncpy(cap->driver, VIRTIO_MEDIA_DEFAULT_DRIVER_NAME,
			sizeof(cap->driver));
	} else {
		strncpy(cap->driver, driver_name, sizeof(cap->driver));
	}
	virtio_cread_bytes(vv->virtio_dev, 8, cap->card, sizeof(cap->card));

	cap->capabilities = video_dev->device_caps | V4L2_CAP_DEVICE_CAPS;
	cap->device_caps = video_dev->device_caps;

	return 0;
}

/*
 * Extended control ioctls are handled mostly identically.
 */

static int virtio_media_g_ext_ctrls(struct file *file, void *fh,
				    struct v4l2_ext_controls *ctrls)
{
	return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_G_EXT_CTRLS,
						    ctrls);
}

static int virtio_media_s_ext_ctrls(struct file *file, void *fh,
				    struct v4l2_ext_controls *ctrls)
{
	return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_S_EXT_CTRLS,
						    ctrls);
}

static int virtio_media_try_ext_ctrls(struct file *file, void *fh,
				      struct v4l2_ext_controls *ctrls)
{
	return virtio_media_send_ext_controls_ioctl(fh, VIDIOC_TRY_EXT_CTRLS,
						    ctrls);
}

/*
 * Subscribe/unsubscribe from an event.
 */

static int
virtio_media_subscribe_event(struct v4l2_fh *fh,
			     const struct v4l2_event_subscription *sub)
{
	struct video_device *video_dev = fh->vdev;
	struct virtio_media *vv = to_virtio_media(video_dev);
	int ret;

	/* First subscribe to the event in the guest. */
	switch (sub->type) {
	case V4L2_EVENT_SOURCE_CHANGE:
		ret = v4l2_src_change_event_subscribe(fh, sub);
		break;
	default:
		ret = v4l2_event_subscribe(fh, sub, 1, NULL);
		break;
	}
	if (ret)
		return ret;

	/* Then ask the host to signal us these events. */
	ret = virtio_media_send_w_ioctl(fh, VIDIOC_SUBSCRIBE_EVENT, sub,
					sizeof(*sub));
	if (ret < 0) {
		v4l2_event_unsubscribe(fh, sub);
		return ret;
	}

	/*
	 * Subscribing to an event may result in that event being signaled
	 * immediately. Process all pending events to make sure we don't miss it.
	 */
	if (sub->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL) {
		virtio_media_process_events(vv);
	}

	return 0;
}

static int
virtio_media_unsubscribe_event(struct v4l2_fh *fh,
			       const struct v4l2_event_subscription *sub)
{
	int ret;

	ret = virtio_media_send_w_ioctl(fh, VIDIOC_UNSUBSCRIBE_EVENT, sub,
					sizeof(*sub));
	if (ret < 0)
		return ret;

	ret = v4l2_event_unsubscribe(fh, sub);
	if (ret)
		return ret;

	return 0;
}

/*
 * Streamon/off affect the local queue state.
 */

static int virtio_media_streamon(struct file *file, void *fh,
				 enum v4l2_buf_type i)
{
	struct virtio_media_session *session = fh_to_session(fh);
	int ret;

	if (i > VIRTIO_MEDIA_LAST_QUEUE)
		return -EINVAL;

	ret = virtio_media_send_w_ioctl(fh, VIDIOC_STREAMON, &i, sizeof(i));
	if (ret < 0)
		return ret;

	session->queues[i].streaming = true;

	return 0;
}

static int virtio_media_streamoff(struct file *file, void *fh,
				  enum v4l2_buf_type i)
{
	struct video_device *video_dev = video_devdata(file);
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct virtio_media_session *session = fh_to_session(fh);
	int ret;

	if (i > VIRTIO_MEDIA_LAST_QUEUE) {
		return -EINVAL;
	}

	ret = virtio_media_send_w_ioctl(fh, VIDIOC_STREAMOFF, &i, sizeof(i));
	if (ret < 0)
		return ret;

	virtio_media_clear_queue(vv, session, &session->queues[i]);

	return 0;
}

/*
 * Buffer creation/queuing functions deal with the local driver state.
 */

static int virtio_media_reqbufs(struct file *file, void *fh,
				struct v4l2_requestbuffers *b)
{
	struct video_device *video_dev = video_devdata(file);
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct virtio_media_session *session = fh_to_session(fh);
	struct virtio_media_queue_state *queue;
	int ret;

	if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
		return -EINVAL;

	ret = virtio_media_send_wr_ioctl(fh, VIDIOC_REQBUFS, b, sizeof(*b),
					 sizeof(*b));
	if (ret)
		return ret;

	queue = &session->queues[b->type];

	/* REQBUFS(0) is an implicit STREAMOFF. */
	if (b->count == 0) {
		virtio_media_clear_queue(vv, session, queue);
	}

	vfree(queue->buffers);
	queue->buffers = NULL;

	if (b->count > 0) {
		queue->buffers =
			vzalloc(sizeof(struct virtio_media_buffer) * b->count);
		if (!queue->buffers) {
			return -ENOMEM;
		}
	}

	queue->allocated_bufs = b->count;

	/*
	 * If a multiplanar queue is successfully used here, this means
	 * we are using the multiplanar interface.
	 */
	if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
		session->uses_mplane = true;
	}

	/* TODO remove once we support DMABUFs */
	b->capabilities &= ~V4L2_BUF_CAP_SUPPORTS_DMABUF;

	return 0;
}

static int virtio_media_querybuf(struct file *file, void *fh,
				 struct v4l2_buffer *b)
{
	struct virtio_media_session *session = fh_to_session(fh);
	struct virtio_media_queue_state *queue;
	struct virtio_media_buffer *buffer;
	int ret;

	ret = virtio_media_send_buffer_ioctl(fh, VIDIOC_QUERYBUF, b);
	if (ret)
		return ret;

	if (b->type > VIRTIO_MEDIA_LAST_QUEUE) {
		return -EINVAL;
	}
	queue = &session->queues[b->type];
	if (b->index >= queue->allocated_bufs) {
		return -EINVAL;
	}
	buffer = &queue->buffers[b->index];
	/* Set the DONE flag if the buffer is waiting in our own dequeue queue. */
	b->flags |= (buffer->buffer.flags & V4L2_BUF_FLAG_DONE);

	return 0;
}

static int virtio_media_create_bufs(struct file *file, void *fh,
				    struct v4l2_create_buffers *b)
{
	struct virtio_media_session *session = fh_to_session(fh);
	struct virtio_media_queue_state *queue;
	struct virtio_media_buffer *buffers;
	u32 type = b->format.type;
	int ret;

	if (type > VIRTIO_MEDIA_LAST_QUEUE)
		return -EINVAL;

	queue = &session->queues[type];

	ret = virtio_media_send_wr_ioctl(fh, VIDIOC_CREATE_BUFS, b, sizeof(*b),
					 sizeof(*b));
	if (ret)
		return ret;

	/* If count is zero, we were just checking for format. */
	if (b->count == 0)
		return 0;

	buffers = queue->buffers;

	queue->buffers = vzalloc(sizeof(struct virtio_media_buffer) *
				 (b->index + b->count));
	if (!queue->buffers) {
		queue->buffers = buffers;
		return -ENOMEM;
	}

	memcpy(queue->buffers, buffers,
	       sizeof(*buffers) * queue->allocated_bufs);
	vfree(buffers);

	queue->allocated_bufs = b->index + b->count;

	return 0;
}

static int virtio_media_prepare_buf(struct file *file, void *fh,
				    struct v4l2_buffer *b)
{
	struct virtio_media_session *session = fh_to_session(fh);
	struct virtio_media_queue_state *queue;
	struct virtio_media_buffer *buffer;
	int i, ret;

	if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
		return -EINVAL;
	queue = &session->queues[b->type];
	if (b->index >= queue->allocated_bufs)
		return -EINVAL;
	buffer = &queue->buffers[b->index];

	buffer->buffer.m = b->m;
	if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
		if (b->length > VIDEO_MAX_PLANES)
			return -EINVAL;
		for (i = 0; i < b->length; i++)
			buffer->planes[i].m = b->m.planes[i].m;
	}

	ret = virtio_media_send_buffer_ioctl(fh, VIDIOC_PREPARE_BUF, b);
	if (ret)
		return ret;

	buffer->buffer.flags = V4L2_BUF_FLAG_PREPARED;

	return 0;
}

static int virtio_media_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
{
	struct virtio_media_session *session = fh_to_session(fh);
	struct virtio_media_queue_state *queue;
	struct virtio_media_buffer *buffer;
	bool prepared;
	u32 old_flags;
	int i, ret;

	if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
		return -EINVAL;
	queue = &session->queues[b->type];
	if (b->index >= queue->allocated_bufs)
		return -EINVAL;
	buffer = &queue->buffers[b->index];
	prepared = buffer->buffer.flags & V4L2_BUF_FLAG_PREPARED;

	/*
	 * Store the buffer and plane `m` information so we can retrieve it again
	 * when DQBUF occurs.
	 */
	if (!prepared) {
		buffer->buffer.m = b->m;
		if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
			if (b->length > VIDEO_MAX_PLANES)
				return -EINVAL;
			for (i = 0; i < b->length; i++)
				buffer->planes[i].m = b->m.planes[i].m;
		}
	}
	old_flags = buffer->buffer.flags;
	buffer->buffer.flags = V4L2_BUF_FLAG_QUEUED;

	ret = virtio_media_send_buffer_ioctl(fh, VIDIOC_QBUF, b);
	if (ret) {
		/* Rollback the previous flags as the buffer is not queued. */
		buffer->buffer.flags = old_flags;
		return ret;
	}

	queue->queued_bufs += 1;

	return 0;
}

static int virtio_media_dqbuf(struct file *file, void *fh,
			      struct v4l2_buffer *b)
{
	struct virtio_media_session *session =
		fh_to_session(file->private_data);
	struct video_device *video_dev = video_devdata(file);
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct virtio_media_buffer *dqbuf;
	struct virtio_media_queue_state *queue;
	struct list_head *buffer_queue;
	struct v4l2_plane *planes_backup = NULL;
	const bool is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(b->type);
	int ret;

	if (b->type > VIRTIO_MEDIA_LAST_QUEUE)
		return -EINVAL;

	queue = &session->queues[b->type];

	/*
	 * If a buffer with the LAST flag has been returned, subsequent calls to DQBUF
	 * must return -EPIPE until the queue is cleared.
	 */
	if (queue->is_capture_last)
		return -EPIPE;

	buffer_queue = &queue->pending_dqbufs;

	if (session->nonblocking_dequeue) {
		if (list_empty(buffer_queue))
			return -EAGAIN;
	} else if (queue->allocated_bufs == 0) {
		return -EINVAL;
	} else if (!queue->streaming) {
		return -EINVAL;
	} else {
		mutex_unlock(&vv->vlock);
		ret = wait_event_interruptible(session->dqbufs_wait,
					       !list_empty(buffer_queue));
		mutex_lock(&vv->vlock);
		if (ret)
			return -EINTR;
	}

	mutex_lock(&session->dqbufs_lock);
	dqbuf = list_first_entry(buffer_queue, struct virtio_media_buffer,
				 list);
	list_del(&dqbuf->list);
	mutex_unlock(&session->dqbufs_lock);

	/* Clear the DONE flag as the buffer is now being dequeued. */
	dqbuf->buffer.flags &= ~V4L2_BUF_FLAG_DONE;

	if (is_multiplanar) {
		size_t nb_planes = min(b->length, (u32)VIDEO_MAX_PLANES);
		memcpy(b->m.planes, dqbuf->planes,
		       nb_planes * sizeof(struct v4l2_plane));
		planes_backup = b->m.planes;
	}

	memcpy(b, &dqbuf->buffer, sizeof(*b));

	if (is_multiplanar) {
		b->m.planes = planes_backup;
	}

	if (V4L2_TYPE_IS_CAPTURE(b->type) && b->flags & V4L2_BUF_FLAG_LAST) {
		queue->is_capture_last = true;
	}

	return 0;
}

/*
 * s/g_input/output work with an unsigned int - recast this to a u32 so the
 * size is unambiguous.
 */

static int virtio_media_g_input(struct file *file, void *fh, unsigned int *i)
{
	u32 input;
	int ret;

	ret = virtio_media_send_wr_ioctl(fh, VIDIOC_G_INPUT, &input,
					 sizeof(input), sizeof(input));
	if (ret)
		return ret;

	*i = input;

	return 0;
}

static int virtio_media_s_input(struct file *file, void *fh, unsigned int i)
{
	u32 input = i;

	return virtio_media_send_wr_ioctl(fh, VIDIOC_S_INPUT, &input,
					  sizeof(input), sizeof(input));
}

static int virtio_media_g_output(struct file *file, void *fh, unsigned int *o)
{
	u32 output;
	int ret;

	ret = virtio_media_send_wr_ioctl(fh, VIDIOC_G_OUTPUT, &output,
					 sizeof(output), sizeof(output));
	if (ret)
		return ret;

	*o = output;

	return 0;
}

static int virtio_media_s_output(struct file *file, void *fh, unsigned int o)
{
	u32 output = o;

	return virtio_media_send_wr_ioctl(fh, VIDIOC_S_OUTPUT, &output,
					  sizeof(output), sizeof(output));
}

/*
 * decoder_cmd can affect the state of the CAPTURE queue.
 */

static int virtio_media_decoder_cmd(struct file *file, void *fh,
				    struct v4l2_decoder_cmd *cmd)
{
	struct virtio_media_session *session = fh_to_session(fh);
	int ret;

	ret = virtio_media_send_wr_ioctl(fh, VIDIOC_DECODER_CMD, cmd,
					 sizeof(*cmd), sizeof(*cmd));
	if (ret)
		return ret;

	/* A START command makes the CAPTURE queue able to dequeue again. */
	if (cmd->cmd == V4L2_DEC_CMD_START) {
		session->queues[V4L2_BUF_TYPE_VIDEO_CAPTURE].is_capture_last =
			false;
		session->queues[V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE]
			.is_capture_last = false;
	}

	return 0;
}

/*
 * s_std doesn't work with a pointer, so we cannot use SIMPLE_W_IOCTL.
 */

static int virtio_media_s_std(struct file *file, void *fh, v4l2_std_id s)
{
	int ret;

	ret = virtio_media_send_w_ioctl(fh, VIDIOC_S_STD, &s, sizeof(s));
	if (ret)
		return ret;

	return 0;
}

const struct v4l2_ioctl_ops virtio_media_ioctl_ops = {
	/* VIDIOC_QUERYCAP handler */
	.vidioc_querycap = virtio_media_querycap,

	/* VIDIOC_ENUM_FMT handlers */
	.vidioc_enum_fmt_vid_cap = virtio_media_enum_fmt,
	.vidioc_enum_fmt_vid_overlay = virtio_media_enum_fmt,
	.vidioc_enum_fmt_vid_out = virtio_media_enum_fmt,
	.vidioc_enum_fmt_sdr_cap = virtio_media_enum_fmt,
	.vidioc_enum_fmt_sdr_out = virtio_media_enum_fmt,
	.vidioc_enum_fmt_meta_cap = virtio_media_enum_fmt,
	.vidioc_enum_fmt_meta_out = virtio_media_enum_fmt,

	/* VIDIOC_G_FMT handlers */
	.vidioc_g_fmt_vid_cap = virtio_media_g_fmt,
	.vidioc_g_fmt_vid_overlay = virtio_media_g_fmt,
	.vidioc_g_fmt_vid_out = virtio_media_g_fmt,
	.vidioc_g_fmt_vid_out_overlay = virtio_media_g_fmt,
	.vidioc_g_fmt_vbi_cap = virtio_media_g_fmt,
	.vidioc_g_fmt_vbi_out = virtio_media_g_fmt,
	.vidioc_g_fmt_sliced_vbi_cap = virtio_media_g_fmt,
	.vidioc_g_fmt_sliced_vbi_out = virtio_media_g_fmt,
	.vidioc_g_fmt_vid_cap_mplane = virtio_media_g_fmt,
	.vidioc_g_fmt_vid_out_mplane = virtio_media_g_fmt,
	.vidioc_g_fmt_sdr_cap = virtio_media_g_fmt,
	.vidioc_g_fmt_sdr_out = virtio_media_g_fmt,
	.vidioc_g_fmt_meta_cap = virtio_media_g_fmt,
	.vidioc_g_fmt_meta_out = virtio_media_g_fmt,

	/* VIDIOC_S_FMT handlers */
	.vidioc_s_fmt_vid_cap = virtio_media_s_fmt,
	.vidioc_s_fmt_vid_overlay = virtio_media_s_fmt,
	.vidioc_s_fmt_vid_out = virtio_media_s_fmt,
	.vidioc_s_fmt_vid_out_overlay = virtio_media_s_fmt,
	.vidioc_s_fmt_vbi_cap = virtio_media_s_fmt,
	.vidioc_s_fmt_vbi_out = virtio_media_s_fmt,
	.vidioc_s_fmt_sliced_vbi_cap = virtio_media_s_fmt,
	.vidioc_s_fmt_sliced_vbi_out = virtio_media_s_fmt,
	.vidioc_s_fmt_vid_cap_mplane = virtio_media_s_fmt,
	.vidioc_s_fmt_vid_out_mplane = virtio_media_s_fmt,
	.vidioc_s_fmt_sdr_cap = virtio_media_s_fmt,
	.vidioc_s_fmt_sdr_out = virtio_media_s_fmt,
	.vidioc_s_fmt_meta_cap = virtio_media_s_fmt,
	.vidioc_s_fmt_meta_out = virtio_media_s_fmt,

	/* VIDIOC_TRY_FMT handlers */
	.vidioc_try_fmt_vid_cap = virtio_media_try_fmt,
	.vidioc_try_fmt_vid_overlay = virtio_media_try_fmt,
	.vidioc_try_fmt_vid_out = virtio_media_try_fmt,
	.vidioc_try_fmt_vid_out_overlay = virtio_media_try_fmt,
	.vidioc_try_fmt_vbi_cap = virtio_media_try_fmt,
	.vidioc_try_fmt_vbi_out = virtio_media_try_fmt,
	.vidioc_try_fmt_sliced_vbi_cap = virtio_media_try_fmt,
	.vidioc_try_fmt_sliced_vbi_out = virtio_media_try_fmt,
	.vidioc_try_fmt_vid_cap_mplane = virtio_media_try_fmt,
	.vidioc_try_fmt_vid_out_mplane = virtio_media_try_fmt,
	.vidioc_try_fmt_sdr_cap = virtio_media_try_fmt,
	.vidioc_try_fmt_sdr_out = virtio_media_try_fmt,
	.vidioc_try_fmt_meta_cap = virtio_media_try_fmt,
	.vidioc_try_fmt_meta_out = virtio_media_try_fmt,

	/* Buffer handlers */
	.vidioc_reqbufs = virtio_media_reqbufs,
	.vidioc_querybuf = virtio_media_querybuf,
	.vidioc_qbuf = virtio_media_qbuf,
	.vidioc_expbuf = NULL,
	.vidioc_dqbuf = virtio_media_dqbuf,
	.vidioc_create_bufs = virtio_media_create_bufs,
	.vidioc_prepare_buf = virtio_media_prepare_buf,
	/* Overlay interface not supported yet */
	.vidioc_overlay = NULL,
	/* Overlay interface not supported yet */
	.vidioc_g_fbuf = NULL,
	/* Overlay interface not supported yet */
	.vidioc_s_fbuf = NULL,

	/* Stream on/off */
	.vidioc_streamon = virtio_media_streamon,
	.vidioc_streamoff = virtio_media_streamoff,

	/* Standard handling */
	.vidioc_g_std = virtio_media_g_std,
	.vidioc_s_std = virtio_media_s_std,
	.vidioc_querystd = virtio_media_querystd,

	/* Input handling */
	.vidioc_enum_input = virtio_media_enuminput,
	.vidioc_g_input = virtio_media_g_input,
	.vidioc_s_input = virtio_media_s_input,

	/* Output handling */
	.vidioc_enum_output = virtio_media_enumoutput,
	.vidioc_g_output = virtio_media_g_output,
	.vidioc_s_output = virtio_media_s_output,

	/* Control handling */
	.vidioc_queryctrl = virtio_media_queryctrl,
	.vidioc_query_ext_ctrl = virtio_media_query_ext_ctrl,
	.vidioc_g_ctrl = virtio_media_g_ctrl,
	.vidioc_s_ctrl = virtio_media_s_ctrl,
	.vidioc_g_ext_ctrls = virtio_media_g_ext_ctrls,
	.vidioc_s_ext_ctrls = virtio_media_s_ext_ctrls,
	.vidioc_try_ext_ctrls = virtio_media_try_ext_ctrls,
	.vidioc_querymenu = virtio_media_querymenu,

	/* Audio ioctls */
	.vidioc_enumaudio = virtio_media_enumaudio,
	.vidioc_g_audio = virtio_media_g_audio,
	.vidioc_s_audio = virtio_media_s_audio,

	/* Audio out ioctls */
	.vidioc_enumaudout = virtio_media_enumaudout,
	.vidioc_g_audout = virtio_media_g_audout,
	.vidioc_s_audout = virtio_media_s_audout,
	.vidioc_g_modulator = virtio_media_g_modulator,
	.vidioc_s_modulator = virtio_media_s_modulator,

	/* Crop ioctls */
	/* Not directly an ioctl (part of VIDIOC_CROPCAP), so no need to implement */
	.vidioc_g_pixelaspect = NULL,
	.vidioc_g_selection = virtio_media_g_selection,
	.vidioc_s_selection = virtio_media_s_selection,

	/* Compression ioctls */
	/* Deprecated in V4L2. */
	.vidioc_g_jpegcomp = NULL,
	/* Deprecated in V4L2. */
	.vidioc_s_jpegcomp = NULL,
	.vidioc_g_enc_index = virtio_media_g_enc_index,
	.vidioc_encoder_cmd = virtio_media_encoder_cmd,
	.vidioc_try_encoder_cmd = virtio_media_try_encoder_cmd,
	.vidioc_decoder_cmd = virtio_media_decoder_cmd,
	.vidioc_try_decoder_cmd = virtio_media_try_decoder_cmd,

	/* Stream type-dependent parameter ioctls */
	.vidioc_g_parm = virtio_media_g_parm,
	.vidioc_s_parm = virtio_media_s_parm,

	/* Tuner ioctls */
	.vidioc_g_tuner = virtio_media_g_tuner,
	.vidioc_s_tuner = virtio_media_s_tuner,
	.vidioc_g_frequency = virtio_media_g_frequency,
	.vidioc_s_frequency = virtio_media_s_frequency,
	.vidioc_enum_freq_bands = virtio_media_enum_freq_bands,

	/* Sliced VBI cap */
	.vidioc_g_sliced_vbi_cap = virtio_media_g_sliced_vbi_cap,

	/* Log status ioctl */
	/* Guest-only operation */
	.vidioc_log_status = NULL,

	.vidioc_s_hw_freq_seek = virtio_media_s_hw_freq_seek,

	.vidioc_enum_framesizes = virtio_media_enum_framesizes,
	.vidioc_enum_frameintervals = virtio_media_enum_frameintervals,

	/* DV Timings IOCTLs */
	.vidioc_s_dv_timings = virtio_media_s_dv_timings,
	.vidioc_g_dv_timings = virtio_media_g_dv_timings,
	.vidioc_query_dv_timings = virtio_media_query_dv_timings,
	.vidioc_enum_dv_timings = virtio_media_enum_dv_timings,
	.vidioc_dv_timings_cap = virtio_media_dv_timings_cap,
	.vidioc_g_edid = NULL,
	.vidioc_s_edid = NULL,

	.vidioc_subscribe_event = virtio_media_subscribe_event,
	.vidioc_unsubscribe_event = virtio_media_unsubscribe_event,

	/* For other private ioctls */
	.vidioc_default = NULL,
};

long virtio_media_device_ioctl(struct file *file, unsigned int cmd,
			       unsigned long arg)
{
	struct video_device *video_dev = video_devdata(file);
	struct virtio_media *vv = to_virtio_media(video_dev);
	struct v4l2_fh *vfh = NULL;
	struct v4l2_standard standard;
	v4l2_std_id std_id = 0;
	int ret;

	if (test_bit(V4L2_FL_USES_V4L2_FH, &video_dev->flags))
		vfh = file->private_data;

	mutex_lock(&vv->vlock);

	/*
	 * We need to handle a few ioctls manually because their result rely on
	 * vfd->tvnorms, which is normally updated by the driver as S_INPUT is
	 * called. Since we want to just pass these ioctls through, we have to hijack
	 * them from here.
	 */
	switch (cmd) {
	case VIDIOC_S_STD:
		ret = copy_from_user(&std_id, (void __user *)arg,
				     sizeof(std_id));
		if (ret) {
			ret = -EINVAL;
			break;
		}
		ret = virtio_media_s_std(file, vfh, std_id);
		break;
	case VIDIOC_ENUMSTD:
		ret = copy_from_user(&standard, (void __user *)arg,
				     sizeof(standard));
		if (ret) {
			ret = -EINVAL;
			break;
		}
		ret = virtio_media_enumstd(file, vfh, &standard);
		if (ret)
			break;
		ret = copy_to_user((void __user *)arg, &standard,
				   sizeof(standard));
		if (ret)
			ret = -EINVAL;
		break;
	case VIDIOC_QUERYSTD:
		ret = virtio_media_querystd(file, vfh, &std_id);
		if (ret)
			break;
		ret = copy_to_user((void __user *)arg, &std_id, sizeof(std_id));
		if (ret)
			ret = -EINVAL;
		break;
	default:
		ret = video_ioctl2(file, cmd, arg);
		break;
	}

	mutex_unlock(&vv->vlock);

	return ret;
}
