Skip to content
virtio_uml.c 32.4 KiB
Newer Older
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Virtio vhost-user driver
 *
 * Copyright(c) 2019 Intel Corporation
 *
 * This driver allows virtio devices to be used over a vhost-user socket.
 *
 * Guest devices can be instantiated by kernel module or command line
 * parameters. One device will be created for each parameter. Syntax:
 *
 *		virtio_uml.device=<socket>:<virtio_id>[:<platform_id>]
 * where:
 *		<socket>	:= vhost-user socket path to connect
 *		<virtio_id>	:= virtio device id (as in virtio_ids.h)
 *		<platform_id>	:= (optional) platform device id
 *
 * example:
 *		virtio_uml.device=/var/uml.socket:1
 *
 * Based on Virtio MMIO driver by Pawel Moll, copyright 2011-2014, ARM Ltd.
 */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ring.h>
#include <linux/time-internal.h>
#include <shared/as-layout.h>
#include <irq_kern.h>
#include <init.h>
#include <os.h>
#include "vhost_user.h"

#define MAX_SUPPORTED_QUEUE_SIZE	256

#define to_virtio_uml_device(_vdev) \
	container_of(_vdev, struct virtio_uml_device, vdev)

struct virtio_uml_platform_data {
	u32 virtio_device_id;
	const char *socket_path;
	struct work_struct conn_broken_wk;
	struct platform_device *pdev;
};

struct virtio_uml_device {
	struct virtio_device vdev;
	struct platform_device *pdev;

	int sock, req_fd, irq;
	u64 features;
	u64 protocol_features;
	u8 status;
	u8 registered:1;
};

struct virtio_uml_vq_info {
	int kick_fd, call_fd;
	char name[32];
#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
	struct virtqueue *vq;
	vq_callback_t *callback;
	struct time_travel_event defer;
#endif
};

extern unsigned long long physmem_size, highmem;

#define vu_err(vu_dev, ...)	dev_err(&(vu_dev)->pdev->dev, ##__VA_ARGS__)

/* Vhost-user protocol */

static int full_sendmsg_fds(int fd, const void *buf, unsigned int len,
			    const int *fds, unsigned int fds_num)
{
	int rc;

	do {
		rc = os_sendmsg_fds(fd, buf, len, fds, fds_num);
		if (rc > 0) {
			buf += rc;
			len -= rc;
			fds = NULL;
			fds_num = 0;
		}
	} while (len && (rc >= 0 || rc == -EINTR));

	if (rc < 0)
		return rc;
	return 0;
}

static int full_read(int fd, void *buf, int len, bool abortable)
{
	int rc;

	do {
		rc = os_read_file(fd, buf, len);
		if (rc > 0) {
			buf += rc;
			len -= rc;
		}
	} while (len && (rc > 0 || rc == -EINTR || (!abortable && rc == -EAGAIN)));

	if (rc < 0)
		return rc;
	if (rc == 0)
		return -ECONNRESET;
	return 0;
}

static int vhost_user_recv_header(int fd, struct vhost_user_msg *msg)
	return full_read(fd, msg, sizeof(msg->header), true);
static int vhost_user_recv(struct virtio_uml_device *vu_dev,
			   int fd, struct vhost_user_msg *msg,
			   size_t max_payload_size, bool wait)
	int rc;

	/*
	 * In virtio time-travel mode, we're handling all the vhost-user
	 * FDs by polling them whenever appropriate. However, we may get
	 * into a situation where we're sending out an interrupt message
	 * to a device (e.g. a net device) and need to handle a simulation
	 * time message while doing so, e.g. one that tells us to update
	 * our idea of how long we can run without scheduling.
	 *
	 * Thus, we need to not just read() from the given fd, but need
	 * to also handle messages for the simulation time - this function
	 * does that for us while waiting for the given fd to be readable.
	 */
	if (wait)
		time_travel_wait_readable(fd);

	rc = vhost_user_recv_header(fd, msg);
	if (rc == -ECONNRESET && vu_dev->registered) {
		struct virtio_uml_platform_data *pdata;

		pdata = vu_dev->pdev->dev.platform_data;

		virtio_break_device(&vu_dev->vdev);
		schedule_work(&pdata->conn_broken_wk);
	}
	if (rc)
		return rc;
	size = msg->header.size;
	if (size > max_payload_size)
		return -EPROTO;
	return full_read(fd, &msg->payload, size, false);
}

static int vhost_user_recv_resp(struct virtio_uml_device *vu_dev,
				struct vhost_user_msg *msg,
				size_t max_payload_size)
{
	int rc = vhost_user_recv(vu_dev, vu_dev->sock, msg,
				 max_payload_size, true);

	if (rc)
		return rc;

	if (msg->header.flags != (VHOST_USER_FLAG_REPLY | VHOST_USER_VERSION))
		return -EPROTO;

	return 0;
}

static int vhost_user_recv_u64(struct virtio_uml_device *vu_dev,
			       u64 *value)
{
	struct vhost_user_msg msg;
	int rc = vhost_user_recv_resp(vu_dev, &msg,
				      sizeof(msg.payload.integer));

	if (rc)
		return rc;
	if (msg.header.size != sizeof(msg.payload.integer))
		return -EPROTO;
	*value = msg.payload.integer;
	return 0;
}

static int vhost_user_recv_req(struct virtio_uml_device *vu_dev,
			       struct vhost_user_msg *msg,
			       size_t max_payload_size)
{
	int rc = vhost_user_recv(vu_dev, vu_dev->req_fd, msg,
				 max_payload_size, false);
Loading
Loading full blame...