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 <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;
spinlock_t sock_lock;
u64 features;
u64 protocol_features;
u8 status;
};
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,
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);
if (rc)
return rc;
Loading
Loading full blame...