// SPDX-License-Identifier: GPL-2.0-only /* * virtio transport for vsock * * Copyright (C) 2013-2015 Red Hat, Inc. * Author: Asias He <asias@redhat.com> * Stefan Hajnoczi <stefanha@redhat.com> * * Some of the code is take from Gerd Hoffmann <kraxel@redhat.com>'s * early virtio-vsock proof-of-concept bits.
*/ #include <linux/spinlock.h> #include <linux/module.h> #include <linux/list.h> #include <linux/atomic.h> #include <linux/virtio.h> #include <linux/virtio_ids.h> #include <linux/virtio_config.h> #include <linux/virtio_vsock.h> #include <net/sock.h> #include <linux/mutex.h> #include <net/af_vsock.h>
/* The following fields are protected by rx_lock. vqs[VSOCK_VQ_RX] * must be accessed with rx_lock held.
*/ struct mutex rx_lock; bool rx_run; int rx_buf_nr; int rx_buf_max_nr;
/* The following fields are protected by event_lock. * vqs[VSOCK_VQ_EVENT] must be accessed with event_lock held.
*/ struct mutex event_lock; bool event_run; struct virtio_vsock_event event_list[8];
u32 guest_cid; bool seqpacket_allow;
/* These fields are used only in tx path in function * 'virtio_transport_send_pkt_work()', so to save * stack space in it, place both of them here. Each * pointer from 'out_sgs' points to the corresponding * element in 'out_bufs' - this is initialized in * 'virtio_vsock_probe()'. Both fields are protected * by 'tx_lock'. +1 is needed for packet header.
*/ struct scatterlist *out_sgs[MAX_SKB_FRAGS + 1]; struct scatterlist out_bufs[MAX_SKB_FRAGS + 1];
};
if (!skb_is_nonlinear(skb)) { if (skb->len > 0) {
sg_init_one(sgs[out_sg], skb->data, skb->len);
out_sg++;
}
} else { struct skb_shared_info *si; int i;
/* If skb is nonlinear, then its buffer must contain * only header and nothing more. Data is stored in * the fragged part.
*/
WARN_ON_ONCE(skb_headroom(skb) != sizeof(*virtio_vsock_hdr(skb)));
si = skb_shinfo(skb);
for (i = 0; i < si->nr_frags; i++) {
skb_frag_t *skb_frag = &si->frags[i]; void *va;
/* We will use 'page_to_virt()' for the userspace page * here, because virtio or dma-mapping layers will call * 'virt_to_phys()' later to fill the buffer descriptor. * We don't touch memory at "virtual" address of this page.
*/
va = page_to_virt(skb_frag_page(skb_frag));
sg_init_one(sgs[out_sg],
va + skb_frag_off(skb_frag),
skb_frag_size(skb_frag));
out_sg++;
}
}
ret = virtqueue_add_sgs(vq, sgs, out_sg, in_sg, skb, gfp); /* Usually this means that there is no more space available in * the vq
*/ if (ret < 0) return ret;
for (;;) { struct sk_buff *skb; bool reply; int ret;
skb = virtio_vsock_skb_dequeue(&vsock->send_pkt_queue); if (!skb) break;
reply = virtio_vsock_skb_reply(skb);
ret = virtio_transport_send_skb(skb, vq, vsock, GFP_KERNEL); if (ret < 0) {
virtio_vsock_skb_queue_head(&vsock->send_pkt_queue, skb); break;
}
if (reply) { struct virtqueue *rx_vq = vsock->vqs[VSOCK_VQ_RX]; int val;
val = atomic_dec_return(&vsock->queued_replies);
/* Do we now have resources to resume rx processing? */ if (val + 1 == virtqueue_get_vring_size(rx_vq))
restart_rx = true;
}
added = true;
}
if (added)
virtqueue_kick(vq);
out:
mutex_unlock(&vsock->tx_lock);
if (restart_rx)
queue_work(virtio_vsock_workqueue, &vsock->rx_work);
}
/* Caller need to hold RCU for vsock. * Returns 0 if the packet is successfully put on the vq.
*/ staticint virtio_transport_send_skb_fast_path(struct virtio_vsock *vsock, struct sk_buff *skb)
{ struct virtqueue *vq = vsock->vqs[VSOCK_VQ_TX]; int ret;
/* Inside RCU, can't sleep! */
ret = mutex_trylock(&vsock->tx_lock); if (unlikely(ret == 0)) return -EBUSY;
ret = virtio_transport_send_skb(skb, vq, vsock, GFP_ATOMIC); if (ret == 0)
virtqueue_kick(vq);
mutex_unlock(&vsock->tx_lock);
return ret;
}
staticint
virtio_transport_send_pkt(struct sk_buff *skb)
{ struct virtio_vsock_hdr *hdr; struct virtio_vsock *vsock; int len = skb->len;
hdr = virtio_vsock_hdr(skb);
rcu_read_lock();
vsock = rcu_dereference(the_virtio_vsock); if (!vsock) {
kfree_skb(skb);
len = -ENODEV; goto out_rcu;
}
if (le64_to_cpu(hdr->dst_cid) == vsock->guest_cid) {
kfree_skb(skb);
len = -ENODEV; goto out_rcu;
}
/* If send_pkt_queue is empty, we can safely bypass this queue * because packet order is maintained and (try) to put the packet * on the virtqueue using virtio_transport_send_skb_fast_path. * If this fails we simply put the packet on the intermediate * queue and schedule the worker.
*/ if (!skb_queue_empty_lockless(&vsock->send_pkt_queue) ||
virtio_transport_send_skb_fast_path(vsock, skb)) { if (virtio_vsock_skb_reply(skb))
atomic_inc(&vsock->queued_replies);
virtqueue_disable_cb(vq); while ((skb = virtqueue_get_buf(vq, &len)) != NULL) {
virtio_transport_consume_skb_sent(skb, true);
added = true;
}
} while (!virtqueue_enable_cb(vq));
out:
mutex_unlock(&vsock->tx_lock);
if (added)
queue_work(virtio_vsock_workqueue, &vsock->send_pkt_work);
}
/* Is there space left for replies to rx packets? */ staticbool virtio_transport_more_replies(struct virtio_vsock *vsock)
{ struct virtqueue *vq = vsock->vqs[VSOCK_VQ_RX]; int val;
smp_rmb(); /* paired with atomic_inc() and atomic_dec_return() */
val = atomic_read(&vsock->queued_replies);
return val < virtqueue_get_vring_size(vq);
}
/* event_lock must be held */ staticint virtio_vsock_event_fill_one(struct virtio_vsock *vsock, struct virtio_vsock_event *event)
{ struct scatterlist sg; struct virtqueue *vq;
/* event_lock must be held */ staticvoid virtio_vsock_event_fill(struct virtio_vsock *vsock)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(vsock->event_list); i++) { struct virtio_vsock_event *event = &vsock->event_list[i];
virtio_vsock_event_fill_one(vsock, event);
}
virtqueue_kick(vsock->vqs[VSOCK_VQ_EVENT]);
}
staticvoid virtio_vsock_reset_sock(struct sock *sk)
{ /* vmci_transport.c doesn't take sk_lock here either. At least we're * under vsock_table_lock so the sock cannot disappear while we're * executing.
*/
/* Check that tx queue is large enough to keep whole * data to send. This is needed, because when there is * not enough free space in the queue, current skb to * send will be reinserted to the head of tx list of * the socket to retry transmission later, so if skb * is bigger than whole queue, it will be reinserted * again and again, thus blocking other skbs to be sent. * Each page of the user provided buffer will be added * as a single buffer to the tx virtqueue, so compare * number of pages against maximum capacity of the queue.
*/ if (bufs_num <= vq->num_max)
res = true;
}
/* virtio_transport_send_pkt() can queue packets once * the_virtio_vsock is set, but they won't be processed until * vsock->tx_run is set to true. We queue vsock->send_pkt_work * when initialization finishes to send those packets queued * earlier. * We don't need to queue the other workers (rx, event) because * as long as we don't fill the queues with empty buffers, the * host can't send us any notification.
*/
queue_work(virtio_vsock_workqueue, &vsock->send_pkt_work);
}
/* Reset all connected sockets when the VQs disappear */
vsock_for_each_connected_socket(&virtio_transport.transport,
virtio_vsock_reset_sock);
/* Stop all work handlers to make sure no one is accessing the device, * so we can safely call virtio_reset_device().
*/
mutex_lock(&vsock->rx_lock);
vsock->rx_run = false;
mutex_unlock(&vsock->rx_lock);
/* Delete virtqueues and flush outstanding callbacks if any */
vdev->config->del_vqs(vdev);
}
staticint virtio_vsock_probe(struct virtio_device *vdev)
{ struct virtio_vsock *vsock = NULL; int ret; int i;
ret = mutex_lock_interruptible(&the_virtio_vsock_mutex); if (ret) return ret;
/* Only one virtio-vsock device per guest is supported */ if (rcu_dereference_protected(the_virtio_vsock,
lockdep_is_held(&the_virtio_vsock_mutex))) {
ret = -EBUSY; goto out;
}
vsock = kzalloc(sizeof(*vsock), GFP_KERNEL); if (!vsock) {
ret = -ENOMEM; goto out;
}
/* Other works can be queued before 'config->del_vqs()', so we flush * all works before to free the vsock object to avoid use after free.
*/
flush_work(&vsock->rx_work);
flush_work(&vsock->tx_work);
flush_work(&vsock->event_work);
flush_work(&vsock->send_pkt_work);
/* Only one virtio-vsock device per guest is supported */ if (rcu_dereference_protected(the_virtio_vsock,
lockdep_is_held(&the_virtio_vsock_mutex))) {
ret = -EBUSY; goto out;
}
ret = virtio_vsock_vqs_init(vsock); if (ret < 0) goto out;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.