| // 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 <linux/virtio-uml.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; |
| int sock, req_fd, irq; |
| u64 features; |
| u64 protocol_features; |
| u8 status; |
| u8 registered:1; |
| u8 suspended:1; |
| u8 no_vq_suspend:1; |
| |
| u8 config_changed_irq:1; |
| uint64_t vq_irq_vq_map; |
| }; |
| |
| struct virtio_uml_vq_info { |
| int kick_fd, call_fd; |
| char name[32]; |
| bool suspended; |
| }; |
| |
| 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; |
| |
| if (!len) |
| return 0; |
| |
| 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) |
| { |
| size_t size; |
| 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; |
| |
| if ((msg->header.flags & ~VHOST_USER_FLAG_NEED_REPLY) != |
| VHOST_USER_VERSION) |
| return -EPROTO; |
| |
| return 0; |
| } |
| |
| static int vhost_user_send(struct virtio_uml_device *vu_dev, |
| bool need_response, struct vhost_user_msg *msg, |
| int *fds, size_t num_fds) |
| { |
| size_t size = sizeof(msg->header) + msg->header.size; |
| unsigned long flags; |
| bool request_ack; |
| int rc; |
| |
| msg->header.flags |= VHOST_USER_VERSION; |
| |
| /* |
| * The need_response flag indicates that we already need a response, |
| * e.g. to read the features. In these cases, don't request an ACK as |
| * it is meaningless. Also request an ACK only if supported. |
| */ |
| request_ack = !need_response; |
| if (!(vu_dev->protocol_features & |
| BIT_ULL(VHOST_USER_PROTOCOL_F_REPLY_ACK))) |
| request_ack = false; |
| |
| if (request_ack) |
| msg->header.flags |= VHOST_USER_FLAG_NEED_REPLY; |
| |
| spin_lock_irqsave(&vu_dev->sock_lock, flags); |
| rc = full_sendmsg_fds(vu_dev->sock, msg, size, fds, num_fds); |
| if (rc < 0) |
| goto out; |
| |
| if (request_ack) { |
| uint64_t status; |
| |
| rc = vhost_user_recv_u64(vu_dev, &status); |
| if (rc) |
| goto out; |
| |
| if (status) { |
| vu_err(vu_dev, "slave reports error: %llu\n", status); |
| rc = -EIO; |
| goto out; |
| } |
| } |
| |
| out: |
| spin_unlock_irqrestore(&vu_dev->sock_lock, flags); |
| return rc; |
| } |
| |
| static int vhost_user_send_no_payload(struct virtio_uml_device *vu_dev, |
| bool need_response, u32 request) |
| { |
| struct vhost_user_msg msg = { |
| .header.request = request, |
| }; |
| |
| return vhost_user_send(vu_dev, need_response, &msg, NULL, 0); |
| } |
| |
| static int vhost_user_send_no_payload_fd(struct virtio_uml_device *vu_dev, |
| u32 request, int fd) |
| { |
| struct vhost_user_msg msg = { |
| .header.request = request, |
| }; |
| |
| return vhost_user_send(vu_dev, false, &msg, &fd, 1); |
| } |
| |
| static int vhost_user_send_u64(struct virtio_uml_device *vu_dev, |
| u32 request, u64 value) |
| { |
| struct vhost_user_msg msg = { |
| .header.request = request, |
| .header.size = sizeof(msg.payload.integer), |
| .payload.integer = value, |
| }; |
| |
| return vhost_user_send(vu_dev, false, &msg, NULL, 0); |
| } |
| |
| static int vhost_user_set_owner(struct virtio_uml_device *vu_dev) |
| { |
| return vhost_user_send_no_payload(vu_dev, false, VHOST_USER_SET_OWNER); |
| } |
| |
| static int vhost_user_get_features(struct virtio_uml_device *vu_dev, |
| u64 *features) |
| { |
| int rc = vhost_user_send_no_payload(vu_dev, true, |
| VHOST_USER_GET_FEATURES); |
| |
| if (rc) |
| return rc; |
| return vhost_user_recv_u64(vu_dev, features); |
| } |
| |
| static int vhost_user_set_features(struct virtio_uml_device *vu_dev, |
| u64 features) |
| { |
| return vhost_user_send_u64(vu_dev, VHOST_USER_SET_FEATURES, features); |
| } |
| |
| static int vhost_user_get_protocol_features(struct virtio_uml_device *vu_dev, |
| u64 *protocol_features) |
| { |
| int rc = vhost_user_send_no_payload(vu_dev, true, |
| VHOST_USER_GET_PROTOCOL_FEATURES); |
| |
| if (rc) |
| return rc; |
| return vhost_user_recv_u64(vu_dev, protocol_features); |
| } |
| |
| static int vhost_user_set_protocol_features(struct virtio_uml_device *vu_dev, |
| u64 protocol_features) |
| { |
| return vhost_user_send_u64(vu_dev, VHOST_USER_SET_PROTOCOL_FEATURES, |
| protocol_features); |
| } |
| |
| static void vhost_user_reply(struct virtio_uml_device *vu_dev, |
| struct vhost_user_msg *msg, int response) |
| { |
| struct vhost_user_msg reply = { |
| .payload.integer = response, |
| }; |
| size_t size = sizeof(reply.header) + sizeof(reply.payload.integer); |
| int rc; |
| |
| reply.header = msg->header; |
| reply.header.flags &= ~VHOST_USER_FLAG_NEED_REPLY; |
| reply.header.flags |= VHOST_USER_FLAG_REPLY; |
| reply.header.size = sizeof(reply.payload.integer); |
| |
| rc = full_sendmsg_fds(vu_dev->req_fd, &reply, size, NULL, 0); |
| |
| if (rc) |
| vu_err(vu_dev, |
| "sending reply to slave request failed: %d (size %zu)\n", |
| rc, size); |
| } |
| |
| static irqreturn_t vu_req_read_message(struct virtio_uml_device *vu_dev, |
| struct time_travel_event *ev) |
| { |
| struct virtqueue *vq; |
| int response = 1; |
| struct { |
| struct vhost_user_msg msg; |
| u8 extra_payload[512]; |
| } msg; |
| int rc; |
| |
| rc = vhost_user_recv_req(vu_dev, &msg.msg, |
| sizeof(msg.msg.payload) + |
| sizeof(msg.extra_payload)); |
| |
| if (rc) |
| return IRQ_NONE; |
| |
| switch (msg.msg.header.request) { |
| case VHOST_USER_SLAVE_CONFIG_CHANGE_MSG: |
| vu_dev->config_changed_irq = true; |
| response = 0; |
| break; |
| case VHOST_USER_SLAVE_VRING_CALL: |
| virtio_device_for_each_vq((&vu_dev->vdev), vq) { |
| if (vq->index == msg.msg.payload.vring_state.index) { |
| response = 0; |
| vu_dev->vq_irq_vq_map |= BIT_ULL(vq->index); |
| break; |
| } |
| } |
| break; |
| case VHOST_USER_SLAVE_IOTLB_MSG: |
| /* not supported - VIRTIO_F_ACCESS_PLATFORM */ |
| case VHOST_USER_SLAVE_VRING_HOST_NOTIFIER_MSG: |
| /* not supported - VHOST_USER_PROTOCOL_F_HOST_NOTIFIER */ |
| default: |
| vu_err(vu_dev, "unexpected slave request %d\n", |
| msg.msg.header.request); |
| } |
| |
| if (ev && !vu_dev->suspended) |
| time_travel_add_irq_event(ev); |
| |
| if (msg.msg.header.flags & VHOST_USER_FLAG_NEED_REPLY) |
| vhost_user_reply(vu_dev, &msg.msg, response); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t vu_req_interrupt(int irq, void *data) |
| { |
| struct virtio_uml_device *vu_dev = data; |
| irqreturn_t ret = IRQ_HANDLED; |
| |
| if (!um_irq_timetravel_handler_used()) |
| ret = vu_req_read_message(vu_dev, NULL); |
| |
| if (vu_dev->vq_irq_vq_map) { |
| struct virtqueue *vq; |
| |
| virtio_device_for_each_vq((&vu_dev->vdev), vq) { |
| if (vu_dev->vq_irq_vq_map & BIT_ULL(vq->index)) |
| vring_interrupt(0 /* ignored */, vq); |
| } |
| vu_dev->vq_irq_vq_map = 0; |
| } else if (vu_dev->config_changed_irq) { |
| virtio_config_changed(&vu_dev->vdev); |
| vu_dev->config_changed_irq = false; |
| } |
| |
| return ret; |
| } |
| |
| static void vu_req_interrupt_comm_handler(int irq, int fd, void *data, |
| struct time_travel_event *ev) |
| { |
| vu_req_read_message(data, ev); |
| } |
| |
| static int vhost_user_init_slave_req(struct virtio_uml_device *vu_dev) |
| { |
| int rc, req_fds[2]; |
| |
| /* Use a pipe for slave req fd, SIGIO is not supported for eventfd */ |
| rc = os_pipe(req_fds, true, true); |
| if (rc < 0) |
| return rc; |
| vu_dev->req_fd = req_fds[0]; |
| |
| rc = um_request_irq_tt(UM_IRQ_ALLOC, vu_dev->req_fd, IRQ_READ, |
| vu_req_interrupt, IRQF_SHARED, |
| vu_dev->pdev->name, vu_dev, |
| vu_req_interrupt_comm_handler); |
| if (rc < 0) |
| goto err_close; |
| |
| vu_dev->irq = rc; |
| |
| rc = vhost_user_send_no_payload_fd(vu_dev, VHOST_USER_SET_SLAVE_REQ_FD, |
| req_fds[1]); |
| if (rc) |
| goto err_free_irq; |
| |
| goto out; |
| |
| err_free_irq: |
| um_free_irq(vu_dev->irq, vu_dev); |
| err_close: |
| os_close_file(req_fds[0]); |
| out: |
| /* Close unused write end of request fds */ |
| os_close_file(req_fds[1]); |
| return rc; |
| } |
| |
| static int vhost_user_init(struct virtio_uml_device *vu_dev) |
| { |
| int rc = vhost_user_set_owner(vu_dev); |
| |
| if (rc) |
| return rc; |
| rc = vhost_user_get_features(vu_dev, &vu_dev->features); |
| if (rc) |
| return rc; |
| |
| if (vu_dev->features & BIT_ULL(VHOST_USER_F_PROTOCOL_FEATURES)) { |
| rc = vhost_user_get_protocol_features(vu_dev, |
| &vu_dev->protocol_features); |
| if (rc) |
| return rc; |
| vu_dev->protocol_features &= VHOST_USER_SUPPORTED_PROTOCOL_F; |
| rc = vhost_user_set_protocol_features(vu_dev, |
| vu_dev->protocol_features); |
| if (rc) |
| return rc; |
| } |
| |
| if (vu_dev->protocol_features & |
| BIT_ULL(VHOST_USER_PROTOCOL_F_SLAVE_REQ)) { |
| rc = vhost_user_init_slave_req(vu_dev); |
| if (rc) |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static void vhost_user_get_config(struct virtio_uml_device *vu_dev, |
| u32 offset, void *buf, u32 len) |
| { |
| u32 cfg_size = offset + len; |
| struct vhost_user_msg *msg; |
| size_t payload_size = sizeof(msg->payload.config) + cfg_size; |
| size_t msg_size = sizeof(msg->header) + payload_size; |
| int rc; |
| |
| if (!(vu_dev->protocol_features & |
| BIT_ULL(VHOST_USER_PROTOCOL_F_CONFIG))) |
| return; |
| |
| msg = kzalloc(msg_size, GFP_KERNEL); |
| if (!msg) |
| return; |
| msg->header.request = VHOST_USER_GET_CONFIG; |
| msg->header.size = payload_size; |
| msg->payload.config.offset = 0; |
| msg->payload.config.size = cfg_size; |
| |
| rc = vhost_user_send(vu_dev, true, msg, NULL, 0); |
| if (rc) { |
| vu_err(vu_dev, "sending VHOST_USER_GET_CONFIG failed: %d\n", |
| rc); |
| goto free; |
| } |
| |
| rc = vhost_user_recv_resp(vu_dev, msg, msg_size); |
| if (rc) { |
| vu_err(vu_dev, |
| "receiving VHOST_USER_GET_CONFIG response failed: %d\n", |
| rc); |
| goto free; |
| } |
| |
| if (msg->header.size != payload_size || |
| msg->payload.config.size != cfg_size) { |
| rc = -EPROTO; |
| vu_err(vu_dev, |
| "Invalid VHOST_USER_GET_CONFIG sizes (payload %d expected %zu, config %u expected %u)\n", |
| msg->header.size, payload_size, |
| msg->payload.config.size, cfg_size); |
| goto free; |
| } |
| memcpy(buf, msg->payload.config.payload + offset, len); |
| |
| free: |
| kfree(msg); |
| } |
| |
| static void vhost_user_set_config(struct virtio_uml_device *vu_dev, |
| u32 offset, const void *buf, u32 len) |
| { |
| struct vhost_user_msg *msg; |
| size_t payload_size = sizeof(msg->payload.config) + len; |
| size_t msg_size = sizeof(msg->header) + payload_size; |
| int rc; |
| |
| if (!(vu_dev->protocol_features & |
| BIT_ULL(VHOST_USER_PROTOCOL_F_CONFIG))) |
| return; |
| |
| msg = kzalloc(msg_size, GFP_KERNEL); |
| if (!msg) |
| return; |
| msg->header.request = VHOST_USER_SET_CONFIG; |
| msg->header.size = payload_size; |
| msg->payload.config.offset = offset; |
| msg->payload.config.size = len; |
| memcpy(msg->payload.config.payload, buf, len); |
| |
| rc = vhost_user_send(vu_dev, false, msg, NULL, 0); |
| if (rc) |
| vu_err(vu_dev, "sending VHOST_USER_SET_CONFIG failed: %d\n", |
| rc); |
| |
| kfree(msg); |
| } |
| |
| static int vhost_user_init_mem_region(u64 addr, u64 size, int *fd_out, |
| struct vhost_user_mem_region *region_out) |
| { |
| unsigned long long mem_offset; |
| int rc = phys_mapping(addr, &mem_offset); |
| |
| if (WARN(rc < 0, "phys_mapping of 0x%llx returned %d\n", addr, rc)) |
| return -EFAULT; |
| *fd_out = rc; |
| region_out->guest_addr = addr; |
| region_out->user_addr = addr; |
| region_out->size = size; |
| region_out->mmap_offset = mem_offset; |
| |
| /* Ensure mapping is valid for the entire region */ |
| rc = phys_mapping(addr + size - 1, &mem_offset); |
| if (WARN(rc != *fd_out, "phys_mapping of 0x%llx failed: %d != %d\n", |
| addr + size - 1, rc, *fd_out)) |
| return -EFAULT; |
| return 0; |
| } |
| |
| static int vhost_user_set_mem_table(struct virtio_uml_device *vu_dev) |
| { |
| struct vhost_user_msg msg = { |
| .header.request = VHOST_USER_SET_MEM_TABLE, |
| .header.size = sizeof(msg.payload.mem_regions), |
| .payload.mem_regions.num = 1, |
| }; |
| unsigned long reserved = uml_reserved - uml_physmem; |
| int fds[2]; |
| int rc; |
| |
| /* |
| * This is a bit tricky, see also the comment with setup_physmem(). |
| * |
| * Essentially, setup_physmem() uses a file to mmap() our physmem, |
| * but the code and data we *already* have is omitted. To us, this |
| * is no difference, since they both become part of our address |
| * space and memory consumption. To somebody looking in from the |
| * outside, however, it is different because the part of our memory |
| * consumption that's already part of the binary (code/data) is not |
| * mapped from the file, so it's not visible to another mmap from |
| * the file descriptor. |
| * |
| * Thus, don't advertise this space to the vhost-user slave. This |
| * means that the slave will likely abort or similar when we give |
| * it an address from the hidden range, since it's not marked as |
| * a valid address, but at least that way we detect the issue and |
| * don't just have the slave read an all-zeroes buffer from the |
| * shared memory file, or write something there that we can never |
| * see (depending on the direction of the virtqueue traffic.) |
| * |
| * Since we usually don't want to use .text for virtio buffers, |
| * this effectively means that you cannot use |
| * 1) global variables, which are in the .bss and not in the shm |
| * file-backed memory |
| * 2) the stack in some processes, depending on where they have |
| * their stack (or maybe only no interrupt stack?) |
| * |
| * The stack is already not typically valid for DMA, so this isn't |
| * much of a restriction, but global variables might be encountered. |
| * |
| * It might be possible to fix it by copying around the data that's |
| * between bss_start and where we map the file now, but it's not |
| * something that you typically encounter with virtio drivers, so |
| * it didn't seem worthwhile. |
| */ |
| rc = vhost_user_init_mem_region(reserved, physmem_size - reserved, |
| &fds[0], |
| &msg.payload.mem_regions.regions[0]); |
| |
| if (rc < 0) |
| return rc; |
| if (highmem) { |
| msg.payload.mem_regions.num++; |
| rc = vhost_user_init_mem_region(__pa(end_iomem), highmem, |
| &fds[1], &msg.payload.mem_regions.regions[1]); |
| if (rc < 0) |
| return rc; |
| } |
| |
| return vhost_user_send(vu_dev, false, &msg, fds, |
| msg.payload.mem_regions.num); |
| } |
| |
| static int vhost_user_set_vring_state(struct virtio_uml_device *vu_dev, |
| u32 request, u32 index, u32 num) |
| { |
| struct vhost_user_msg msg = { |
| .header.request = request, |
| .header.size = sizeof(msg.payload.vring_state), |
| .payload.vring_state.index = index, |
| .payload.vring_state.num = num, |
| }; |
| |
| return vhost_user_send(vu_dev, false, &msg, NULL, 0); |
| } |
| |
| static int vhost_user_set_vring_num(struct virtio_uml_device *vu_dev, |
| u32 index, u32 num) |
| { |
| return vhost_user_set_vring_state(vu_dev, VHOST_USER_SET_VRING_NUM, |
| index, num); |
| } |
| |
| static int vhost_user_set_vring_base(struct virtio_uml_device *vu_dev, |
| u32 index, u32 offset) |
| { |
| return vhost_user_set_vring_state(vu_dev, VHOST_USER_SET_VRING_BASE, |
| index, offset); |
| } |
| |
| static int vhost_user_set_vring_addr(struct virtio_uml_device *vu_dev, |
| u32 index, u64 desc, u64 used, u64 avail, |
| u64 log) |
| { |
| struct vhost_user_msg msg = { |
| .header.request = VHOST_USER_SET_VRING_ADDR, |
| .header.size = sizeof(msg.payload.vring_addr), |
| .payload.vring_addr.index = index, |
| .payload.vring_addr.desc = desc, |
| .payload.vring_addr.used = used, |
| .payload.vring_addr.avail = avail, |
| .payload.vring_addr.log = log, |
| }; |
| |
| return vhost_user_send(vu_dev, false, &msg, NULL, 0); |
| } |
| |
| static int vhost_user_set_vring_fd(struct virtio_uml_device *vu_dev, |
| u32 request, int index, int fd) |
| { |
| struct vhost_user_msg msg = { |
| .header.request = request, |
| .header.size = sizeof(msg.payload.integer), |
| .payload.integer = index, |
| }; |
| |
| if (index & ~VHOST_USER_VRING_INDEX_MASK) |
| return -EINVAL; |
| if (fd < 0) { |
| msg.payload.integer |= VHOST_USER_VRING_POLL_MASK; |
| return vhost_user_send(vu_dev, false, &msg, NULL, 0); |
| } |
| return vhost_user_send(vu_dev, false, &msg, &fd, 1); |
| } |
| |
| static int vhost_user_set_vring_call(struct virtio_uml_device *vu_dev, |
| int index, int fd) |
| { |
| return vhost_user_set_vring_fd(vu_dev, VHOST_USER_SET_VRING_CALL, |
| index, fd); |
| } |
| |
| static int vhost_user_set_vring_kick(struct virtio_uml_device *vu_dev, |
| int index, int fd) |
| { |
| return vhost_user_set_vring_fd(vu_dev, VHOST_USER_SET_VRING_KICK, |
| index, fd); |
| } |
| |
| static int vhost_user_set_vring_enable(struct virtio_uml_device *vu_dev, |
| u32 index, bool enable) |
| { |
| if (!(vu_dev->features & BIT_ULL(VHOST_USER_F_PROTOCOL_FEATURES))) |
| return 0; |
| |
| return vhost_user_set_vring_state(vu_dev, VHOST_USER_SET_VRING_ENABLE, |
| index, enable); |
| } |
| |
| |
| /* Virtio interface */ |
| |
| static bool vu_notify(struct virtqueue *vq) |
| { |
| struct virtio_uml_vq_info *info = vq->priv; |
| const uint64_t n = 1; |
| int rc; |
| |
| if (info->suspended) |
| return true; |
| |
| time_travel_propagate_time(); |
| |
| if (info->kick_fd < 0) { |
| struct virtio_uml_device *vu_dev; |
| |
| vu_dev = to_virtio_uml_device(vq->vdev); |
| |
| return vhost_user_set_vring_state(vu_dev, VHOST_USER_VRING_KICK, |
| vq->index, 0) == 0; |
| } |
| |
| do { |
| rc = os_write_file(info->kick_fd, &n, sizeof(n)); |
| } while (rc == -EINTR); |
| return !WARN(rc != sizeof(n), "write returned %d\n", rc); |
| } |
| |
| static irqreturn_t vu_interrupt(int irq, void *opaque) |
| { |
| struct virtqueue *vq = opaque; |
| struct virtio_uml_vq_info *info = vq->priv; |
| uint64_t n; |
| int rc; |
| irqreturn_t ret = IRQ_NONE; |
| |
| do { |
| rc = os_read_file(info->call_fd, &n, sizeof(n)); |
| if (rc == sizeof(n)) |
| ret |= vring_interrupt(irq, vq); |
| } while (rc == sizeof(n) || rc == -EINTR); |
| WARN(rc != -EAGAIN, "read returned %d\n", rc); |
| return ret; |
| } |
| |
| |
| static void vu_get(struct virtio_device *vdev, unsigned offset, |
| void *buf, unsigned len) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| vhost_user_get_config(vu_dev, offset, buf, len); |
| } |
| |
| static void vu_set(struct virtio_device *vdev, unsigned offset, |
| const void *buf, unsigned len) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| vhost_user_set_config(vu_dev, offset, buf, len); |
| } |
| |
| static u8 vu_get_status(struct virtio_device *vdev) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| return vu_dev->status; |
| } |
| |
| static void vu_set_status(struct virtio_device *vdev, u8 status) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| vu_dev->status = status; |
| } |
| |
| static void vu_reset(struct virtio_device *vdev) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| vu_dev->status = 0; |
| } |
| |
| static void vu_del_vq(struct virtqueue *vq) |
| { |
| struct virtio_uml_vq_info *info = vq->priv; |
| |
| if (info->call_fd >= 0) { |
| struct virtio_uml_device *vu_dev; |
| |
| vu_dev = to_virtio_uml_device(vq->vdev); |
| |
| um_free_irq(vu_dev->irq, vq); |
| os_close_file(info->call_fd); |
| } |
| |
| if (info->kick_fd >= 0) |
| os_close_file(info->kick_fd); |
| |
| vring_del_virtqueue(vq); |
| kfree(info); |
| } |
| |
| static void vu_del_vqs(struct virtio_device *vdev) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| struct virtqueue *vq, *n; |
| u64 features; |
| |
| /* Note: reverse order as a workaround to a decoding bug in snabb */ |
| list_for_each_entry_reverse(vq, &vdev->vqs, list) |
| WARN_ON(vhost_user_set_vring_enable(vu_dev, vq->index, false)); |
| |
| /* Ensure previous messages have been processed */ |
| WARN_ON(vhost_user_get_features(vu_dev, &features)); |
| |
| list_for_each_entry_safe(vq, n, &vdev->vqs, list) |
| vu_del_vq(vq); |
| } |
| |
| static int vu_setup_vq_call_fd(struct virtio_uml_device *vu_dev, |
| struct virtqueue *vq) |
| { |
| struct virtio_uml_vq_info *info = vq->priv; |
| int call_fds[2]; |
| int rc; |
| |
| /* no call FD needed/desired in this case */ |
| if (vu_dev->protocol_features & |
| BIT_ULL(VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS) && |
| vu_dev->protocol_features & |
| BIT_ULL(VHOST_USER_PROTOCOL_F_SLAVE_REQ)) { |
| info->call_fd = -1; |
| return 0; |
| } |
| |
| /* Use a pipe for call fd, since SIGIO is not supported for eventfd */ |
| rc = os_pipe(call_fds, true, true); |
| if (rc < 0) |
| return rc; |
| |
| info->call_fd = call_fds[0]; |
| rc = um_request_irq(vu_dev->irq, info->call_fd, IRQ_READ, |
| vu_interrupt, IRQF_SHARED, info->name, vq); |
| if (rc < 0) |
| goto close_both; |
| |
| rc = vhost_user_set_vring_call(vu_dev, vq->index, call_fds[1]); |
| if (rc) |
| goto release_irq; |
| |
| goto out; |
| |
| release_irq: |
| um_free_irq(vu_dev->irq, vq); |
| close_both: |
| os_close_file(call_fds[0]); |
| out: |
| /* Close (unused) write end of call fds */ |
| os_close_file(call_fds[1]); |
| |
| return rc; |
| } |
| |
| static struct virtqueue *vu_setup_vq(struct virtio_device *vdev, |
| unsigned index, vq_callback_t *callback, |
| const char *name, bool ctx) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| struct platform_device *pdev = vu_dev->pdev; |
| struct virtio_uml_vq_info *info; |
| struct virtqueue *vq; |
| int num = MAX_SUPPORTED_QUEUE_SIZE; |
| int rc; |
| |
| info = kzalloc(sizeof(*info), GFP_KERNEL); |
| if (!info) { |
| rc = -ENOMEM; |
| goto error_kzalloc; |
| } |
| snprintf(info->name, sizeof(info->name), "%s.%d-%s", pdev->name, |
| pdev->id, name); |
| |
| vq = vring_create_virtqueue(index, num, PAGE_SIZE, vdev, true, true, |
| ctx, vu_notify, callback, info->name); |
| if (!vq) { |
| rc = -ENOMEM; |
| goto error_create; |
| } |
| vq->priv = info; |
| num = virtqueue_get_vring_size(vq); |
| |
| if (vu_dev->protocol_features & |
| BIT_ULL(VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS)) { |
| info->kick_fd = -1; |
| } else { |
| rc = os_eventfd(0, 0); |
| if (rc < 0) |
| goto error_kick; |
| info->kick_fd = rc; |
| } |
| |
| rc = vu_setup_vq_call_fd(vu_dev, vq); |
| if (rc) |
| goto error_call; |
| |
| rc = vhost_user_set_vring_num(vu_dev, index, num); |
| if (rc) |
| goto error_setup; |
| |
| rc = vhost_user_set_vring_base(vu_dev, index, 0); |
| if (rc) |
| goto error_setup; |
| |
| rc = vhost_user_set_vring_addr(vu_dev, index, |
| virtqueue_get_desc_addr(vq), |
| virtqueue_get_used_addr(vq), |
| virtqueue_get_avail_addr(vq), |
| (u64) -1); |
| if (rc) |
| goto error_setup; |
| |
| return vq; |
| |
| error_setup: |
| if (info->call_fd >= 0) { |
| um_free_irq(vu_dev->irq, vq); |
| os_close_file(info->call_fd); |
| } |
| error_call: |
| if (info->kick_fd >= 0) |
| os_close_file(info->kick_fd); |
| error_kick: |
| vring_del_virtqueue(vq); |
| error_create: |
| kfree(info); |
| error_kzalloc: |
| return ERR_PTR(rc); |
| } |
| |
| static int vu_find_vqs(struct virtio_device *vdev, unsigned nvqs, |
| struct virtqueue *vqs[], vq_callback_t *callbacks[], |
| const char * const names[], const bool *ctx, |
| struct irq_affinity *desc) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| int i, queue_idx = 0, rc; |
| struct virtqueue *vq; |
| |
| /* not supported for now */ |
| if (WARN_ON(nvqs > 64)) |
| return -EINVAL; |
| |
| rc = vhost_user_set_mem_table(vu_dev); |
| if (rc) |
| return rc; |
| |
| for (i = 0; i < nvqs; ++i) { |
| if (!names[i]) { |
| vqs[i] = NULL; |
| continue; |
| } |
| |
| vqs[i] = vu_setup_vq(vdev, queue_idx++, callbacks[i], names[i], |
| ctx ? ctx[i] : false); |
| if (IS_ERR(vqs[i])) { |
| rc = PTR_ERR(vqs[i]); |
| goto error_setup; |
| } |
| } |
| |
| list_for_each_entry(vq, &vdev->vqs, list) { |
| struct virtio_uml_vq_info *info = vq->priv; |
| |
| if (info->kick_fd >= 0) { |
| rc = vhost_user_set_vring_kick(vu_dev, vq->index, |
| info->kick_fd); |
| if (rc) |
| goto error_setup; |
| } |
| |
| rc = vhost_user_set_vring_enable(vu_dev, vq->index, true); |
| if (rc) |
| goto error_setup; |
| } |
| |
| return 0; |
| |
| error_setup: |
| vu_del_vqs(vdev); |
| return rc; |
| } |
| |
| static u64 vu_get_features(struct virtio_device *vdev) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| return vu_dev->features; |
| } |
| |
| static int vu_finalize_features(struct virtio_device *vdev) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| u64 supported = vdev->features & VHOST_USER_SUPPORTED_F; |
| |
| vring_transport_features(vdev); |
| vu_dev->features = vdev->features | supported; |
| |
| return vhost_user_set_features(vu_dev, vu_dev->features); |
| } |
| |
| static const char *vu_bus_name(struct virtio_device *vdev) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| return vu_dev->pdev->name; |
| } |
| |
| static const struct virtio_config_ops virtio_uml_config_ops = { |
| .get = vu_get, |
| .set = vu_set, |
| .get_status = vu_get_status, |
| .set_status = vu_set_status, |
| .reset = vu_reset, |
| .find_vqs = vu_find_vqs, |
| .del_vqs = vu_del_vqs, |
| .get_features = vu_get_features, |
| .finalize_features = vu_finalize_features, |
| .bus_name = vu_bus_name, |
| }; |
| |
| static void virtio_uml_release_dev(struct device *d) |
| { |
| struct virtio_device *vdev = |
| container_of(d, struct virtio_device, dev); |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| /* might not have been opened due to not negotiating the feature */ |
| if (vu_dev->req_fd >= 0) { |
| um_free_irq(vu_dev->irq, vu_dev); |
| os_close_file(vu_dev->req_fd); |
| } |
| |
| os_close_file(vu_dev->sock); |
| kfree(vu_dev); |
| } |
| |
| void virtio_uml_set_no_vq_suspend(struct virtio_device *vdev, |
| bool no_vq_suspend) |
| { |
| struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev); |
| |
| if (WARN_ON(vdev->config != &virtio_uml_config_ops)) |
| return; |
| |
| vu_dev->no_vq_suspend = no_vq_suspend; |
| dev_info(&vdev->dev, "%sabled VQ suspend\n", |
| no_vq_suspend ? "dis" : "en"); |
| } |
| |
| /* Platform device */ |
| |
| static int virtio_uml_probe(struct platform_device *pdev) |
| { |
| struct virtio_uml_platform_data *pdata = pdev->dev.platform_data; |
| struct virtio_uml_device *vu_dev; |
| int rc; |
| |
| if (!pdata) |
| return -EINVAL; |
| |
| vu_dev = kzalloc(sizeof(*vu_dev), GFP_KERNEL); |
| if (!vu_dev) |
| return -ENOMEM; |
| |
| vu_dev->vdev.dev.parent = &pdev->dev; |
| vu_dev->vdev.dev.release = virtio_uml_release_dev; |
| vu_dev->vdev.config = &virtio_uml_config_ops; |
| vu_dev->vdev.id.device = pdata->virtio_device_id; |
| vu_dev->vdev.id.vendor = VIRTIO_DEV_ANY_ID; |
| vu_dev->pdev = pdev; |
| vu_dev->req_fd = -1; |
| |
| do { |
| rc = os_connect_socket(pdata->socket_path); |
| } while (rc == -EINTR); |
| if (rc < 0) |
| goto error_free; |
| vu_dev->sock = rc; |
| |
| spin_lock_init(&vu_dev->sock_lock); |
| |
| rc = vhost_user_init(vu_dev); |
| if (rc) |
| goto error_init; |
| |
| platform_set_drvdata(pdev, vu_dev); |
| |
| device_set_wakeup_capable(&vu_dev->vdev.dev, true); |
| |
| rc = register_virtio_device(&vu_dev->vdev); |
| if (rc) |
| put_device(&vu_dev->vdev.dev); |
| vu_dev->registered = 1; |
| return rc; |
| |
| error_init: |
| os_close_file(vu_dev->sock); |
| error_free: |
| kfree(vu_dev); |
| return rc; |
| } |
| |
| static int virtio_uml_remove(struct platform_device *pdev) |
| { |
| struct virtio_uml_device *vu_dev = platform_get_drvdata(pdev); |
| |
| unregister_virtio_device(&vu_dev->vdev); |
| return 0; |
| } |
| |
| /* Command line device list */ |
| |
| static void vu_cmdline_release_dev(struct device *d) |
| { |
| } |
| |
| static struct device vu_cmdline_parent = { |
| .init_name = "virtio-uml-cmdline", |
| .release = vu_cmdline_release_dev, |
| }; |
| |
| static bool vu_cmdline_parent_registered; |
| static int vu_cmdline_id; |
| |
| static int vu_unregister_cmdline_device(struct device *dev, void *data) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct virtio_uml_platform_data *pdata = pdev->dev.platform_data; |
| |
| kfree(pdata->socket_path); |
| platform_device_unregister(pdev); |
| return 0; |
| } |
| |
| static void vu_conn_broken(struct work_struct *wk) |
| { |
| struct virtio_uml_platform_data *pdata; |
| |
| pdata = container_of(wk, struct virtio_uml_platform_data, conn_broken_wk); |
| vu_unregister_cmdline_device(&pdata->pdev->dev, NULL); |
| } |
| |
| static int vu_cmdline_set(const char *device, const struct kernel_param *kp) |
| { |
| const char *ids = strchr(device, ':'); |
| unsigned int virtio_device_id; |
| int processed, consumed, err; |
| char *socket_path; |
| struct virtio_uml_platform_data pdata, *ppdata; |
| struct platform_device *pdev; |
| |
| if (!ids || ids == device) |
| return -EINVAL; |
| |
| processed = sscanf(ids, ":%u%n:%d%n", |
| &virtio_device_id, &consumed, |
| &vu_cmdline_id, &consumed); |
| |
| if (processed < 1 || ids[consumed]) |
| return -EINVAL; |
| |
| if (!vu_cmdline_parent_registered) { |
| err = device_register(&vu_cmdline_parent); |
| if (err) { |
| pr_err("Failed to register parent device!\n"); |
| put_device(&vu_cmdline_parent); |
| return err; |
| } |
| vu_cmdline_parent_registered = true; |
| } |
| |
| socket_path = kmemdup_nul(device, ids - device, GFP_KERNEL); |
| if (!socket_path) |
| return -ENOMEM; |
| |
| pdata.virtio_device_id = (u32) virtio_device_id; |
| pdata.socket_path = socket_path; |
| |
| pr_info("Registering device virtio-uml.%d id=%d at %s\n", |
| vu_cmdline_id, virtio_device_id, socket_path); |
| |
| pdev = platform_device_register_data(&vu_cmdline_parent, "virtio-uml", |
| vu_cmdline_id++, &pdata, |
| sizeof(pdata)); |
| err = PTR_ERR_OR_ZERO(pdev); |
| if (err) |
| goto free; |
| |
| ppdata = pdev->dev.platform_data; |
| ppdata->pdev = pdev; |
| INIT_WORK(&ppdata->conn_broken_wk, vu_conn_broken); |
| |
| return 0; |
| |
| free: |
| kfree(socket_path); |
| return err; |
| } |
| |
| static int vu_cmdline_get_device(struct device *dev, void *data) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct virtio_uml_platform_data *pdata = pdev->dev.platform_data; |
| char *buffer = data; |
| unsigned int len = strlen(buffer); |
| |
| snprintf(buffer + len, PAGE_SIZE - len, "%s:%d:%d\n", |
| pdata->socket_path, pdata->virtio_device_id, pdev->id); |
| return 0; |
| } |
| |
| static int vu_cmdline_get(char *buffer, const struct kernel_param *kp) |
| { |
| buffer[0] = '\0'; |
| if (vu_cmdline_parent_registered) |
| device_for_each_child(&vu_cmdline_parent, buffer, |
| vu_cmdline_get_device); |
| return strlen(buffer) + 1; |
| } |
| |
| static const struct kernel_param_ops vu_cmdline_param_ops = { |
| .set = vu_cmdline_set, |
| .get = vu_cmdline_get, |
| }; |
| |
| device_param_cb(device, &vu_cmdline_param_ops, NULL, S_IRUSR); |
| __uml_help(vu_cmdline_param_ops, |
| "virtio_uml.device=<socket>:<virtio_id>[:<platform_id>]\n" |
| " Configure a virtio device over a vhost-user socket.\n" |
| " See virtio_ids.h for a list of possible virtio device id values.\n" |
| " Optionally use a specific platform_device id.\n\n" |
| ); |
| |
| |
| static void vu_unregister_cmdline_devices(void) |
| { |
| if (vu_cmdline_parent_registered) { |
| device_for_each_child(&vu_cmdline_parent, NULL, |
| vu_unregister_cmdline_device); |
| device_unregister(&vu_cmdline_parent); |
| vu_cmdline_parent_registered = false; |
| } |
| } |
| |
| /* Platform driver */ |
| |
| static const struct of_device_id virtio_uml_match[] = { |
| { .compatible = "virtio,uml", }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, virtio_uml_match); |
| |
| static int virtio_uml_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct virtio_uml_device *vu_dev = platform_get_drvdata(pdev); |
| |
| if (!vu_dev->no_vq_suspend) { |
| struct virtqueue *vq; |
| |
| virtio_device_for_each_vq((&vu_dev->vdev), vq) { |
| struct virtio_uml_vq_info *info = vq->priv; |
| |
| info->suspended = true; |
| vhost_user_set_vring_enable(vu_dev, vq->index, false); |
| } |
| } |
| |
| if (!device_may_wakeup(&vu_dev->vdev.dev)) { |
| vu_dev->suspended = true; |
| return 0; |
| } |
| |
| return irq_set_irq_wake(vu_dev->irq, 1); |
| } |
| |
| static int virtio_uml_resume(struct platform_device *pdev) |
| { |
| struct virtio_uml_device *vu_dev = platform_get_drvdata(pdev); |
| |
| if (!vu_dev->no_vq_suspend) { |
| struct virtqueue *vq; |
| |
| virtio_device_for_each_vq((&vu_dev->vdev), vq) { |
| struct virtio_uml_vq_info *info = vq->priv; |
| |
| info->suspended = false; |
| vhost_user_set_vring_enable(vu_dev, vq->index, true); |
| } |
| } |
| |
| vu_dev->suspended = false; |
| |
| if (!device_may_wakeup(&vu_dev->vdev.dev)) |
| return 0; |
| |
| return irq_set_irq_wake(vu_dev->irq, 0); |
| } |
| |
| static struct platform_driver virtio_uml_driver = { |
| .probe = virtio_uml_probe, |
| .remove = virtio_uml_remove, |
| .driver = { |
| .name = "virtio-uml", |
| .of_match_table = virtio_uml_match, |
| }, |
| .suspend = virtio_uml_suspend, |
| .resume = virtio_uml_resume, |
| }; |
| |
| static int __init virtio_uml_init(void) |
| { |
| return platform_driver_register(&virtio_uml_driver); |
| } |
| |
| static void __exit virtio_uml_exit(void) |
| { |
| platform_driver_unregister(&virtio_uml_driver); |
| vu_unregister_cmdline_devices(); |
| } |
| |
| module_init(virtio_uml_init); |
| module_exit(virtio_uml_exit); |
| __uml_exitcall(virtio_uml_exit); |
| |
| MODULE_DESCRIPTION("UML driver for vhost-user virtio devices"); |
| MODULE_LICENSE("GPL"); |