xref: /openbmc/qemu/net/vmnet-common.m (revision 29318db1)
1/*
2 * vmnet-common.m - network client wrapper for Apple vmnet.framework
3 *
4 * Copyright(c) 2022 Vladislav Yaroshchuk <vladislav.yaroshchuk@jetbrains.com>
5 * Copyright(c) 2021 Phillip Tennen <phillip@axleos.com>
6 *
7 * This work is licensed under the terms of the GNU GPL, version 2 or later.
8 * See the COPYING file in the top-level directory.
9 *
10 */
11
12#include "qemu/osdep.h"
13#include "qemu/main-loop.h"
14#include "qemu/log.h"
15#include "qapi/qapi-types-net.h"
16#include "vmnet_int.h"
17#include "clients.h"
18#include "qemu/error-report.h"
19#include "qapi/error.h"
20#include "sysemu/runstate.h"
21
22#include <vmnet/vmnet.h>
23#include <dispatch/dispatch.h>
24
25
26static void vmnet_send_completed(NetClientState *nc, ssize_t len);
27
28
29const char *vmnet_status_map_str(vmnet_return_t status)
30{
31    switch (status) {
32    case VMNET_SUCCESS:
33        return "success";
34    case VMNET_FAILURE:
35        return "general failure (possibly not enough privileges)";
36    case VMNET_MEM_FAILURE:
37        return "memory allocation failure";
38    case VMNET_INVALID_ARGUMENT:
39        return "invalid argument specified";
40    case VMNET_SETUP_INCOMPLETE:
41        return "interface setup is not complete";
42    case VMNET_INVALID_ACCESS:
43        return "invalid access, permission denied";
44    case VMNET_PACKET_TOO_BIG:
45        return "packet size is larger than MTU";
46    case VMNET_BUFFER_EXHAUSTED:
47        return "buffers exhausted in kernel";
48    case VMNET_TOO_MANY_PACKETS:
49        return "packet count exceeds limit";
50    case VMNET_SHARING_SERVICE_BUSY:
51        return "conflict, sharing service is in use";
52    default:
53        return "unknown vmnet error";
54    }
55}
56
57
58/**
59 * Write packets from QEMU to vmnet interface.
60 *
61 * vmnet.framework supports iov, but writing more than
62 * one iov into vmnet interface fails with
63 * 'VMNET_INVALID_ARGUMENT'. Collecting provided iovs into
64 * one and passing it to vmnet works fine. That's the
65 * reason why receive_iov() left unimplemented. But it still
66 * works with good performance having .receive() only.
67 */
68ssize_t vmnet_receive_common(NetClientState *nc,
69                             const uint8_t *buf,
70                             size_t size)
71{
72    VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
73    struct vmpktdesc packet;
74    struct iovec iov;
75    int pkt_cnt;
76    vmnet_return_t if_status;
77
78    if (size > s->max_packet_size) {
79        warn_report("vmnet: packet is too big, %zu > %" PRIu64,
80            packet.vm_pkt_size,
81            s->max_packet_size);
82        return -1;
83    }
84
85    iov.iov_base = (char *) buf;
86    iov.iov_len = size;
87
88    packet.vm_pkt_iovcnt = 1;
89    packet.vm_flags = 0;
90    packet.vm_pkt_size = size;
91    packet.vm_pkt_iov = &iov;
92    pkt_cnt = 1;
93
94    if_status = vmnet_write(s->vmnet_if, &packet, &pkt_cnt);
95    if (if_status != VMNET_SUCCESS) {
96        error_report("vmnet: write error: %s\n",
97                     vmnet_status_map_str(if_status));
98        return -1;
99    }
100
101    if (pkt_cnt) {
102        return size;
103    }
104    return 0;
105}
106
107
108/**
109 * Read packets from vmnet interface and write them
110 * to temporary buffers in VmnetState.
111 *
112 * Returns read packets number (may be 0) on success,
113 * -1 on error
114 */
115static int vmnet_read_packets(VmnetState *s)
116{
117    assert(s->packets_send_current_pos == s->packets_send_end_pos);
118
119    struct vmpktdesc *packets = s->packets_buf;
120    vmnet_return_t status;
121    int i;
122
123    /* Read as many packets as present */
124    s->packets_send_current_pos = 0;
125    s->packets_send_end_pos = VMNET_PACKETS_LIMIT;
126    for (i = 0; i < s->packets_send_end_pos; ++i) {
127        packets[i].vm_pkt_size = s->max_packet_size;
128        packets[i].vm_pkt_iovcnt = 1;
129        packets[i].vm_flags = 0;
130    }
131
132    status = vmnet_read(s->vmnet_if, packets, &s->packets_send_end_pos);
133    if (status != VMNET_SUCCESS) {
134        error_printf("vmnet: read failed: %s\n",
135                     vmnet_status_map_str(status));
136        s->packets_send_current_pos = 0;
137        s->packets_send_end_pos = 0;
138        return -1;
139    }
140    return s->packets_send_end_pos;
141}
142
143
144/**
145 * Write packets from temporary buffers in VmnetState
146 * to QEMU.
147 */
148static void vmnet_write_packets_to_qemu(VmnetState *s)
149{
150    while (s->packets_send_current_pos < s->packets_send_end_pos) {
151        ssize_t size = qemu_send_packet_async(&s->nc,
152                                      s->iov_buf[s->packets_send_current_pos].iov_base,
153                                      s->packets_buf[s->packets_send_current_pos].vm_pkt_size,
154                                      vmnet_send_completed);
155
156        if (size == 0) {
157            /* QEMU is not ready to consume more packets -
158             * stop and wait for completion callback call */
159            return;
160        }
161        ++s->packets_send_current_pos;
162    }
163}
164
165
166/**
167 * Bottom half callback that transfers packets from vmnet interface
168 * to QEMU.
169 *
170 * The process of transferring packets is three-staged:
171 * 1. Handle vmnet event;
172 * 2. Read packets from vmnet interface into temporary buffer;
173 * 3. Write packets from temporary buffer to QEMU.
174 *
175 * QEMU may suspend this process on the last stage, returning 0 from
176 * qemu_send_packet_async function. If this happens, we should
177 * respectfully wait until it is ready to consume more packets,
178 * write left ones in temporary buffer and only after this
179 * continue reading more packets from vmnet interface.
180 *
181 * Packets to be transferred are stored into packets_buf,
182 * in the window [packets_send_current_pos..packets_send_end_pos)
183 * including current_pos, excluding end_pos.
184 *
185 * Thus, if QEMU is not ready, buffer is not read and
186 * packets_send_current_pos < packets_send_end_pos.
187 */
188static void vmnet_send_bh(void *opaque)
189{
190    NetClientState *nc = (NetClientState *) opaque;
191    VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
192
193    /*
194     * Do nothing if QEMU is not ready - wait
195     * for completion callback invocation
196     */
197    if (s->packets_send_current_pos < s->packets_send_end_pos) {
198        return;
199    }
200
201    /* Read packets from vmnet interface */
202    if (vmnet_read_packets(s) > 0) {
203        /* Send them to QEMU */
204        vmnet_write_packets_to_qemu(s);
205    }
206}
207
208
209/**
210 * Completion callback to be invoked by QEMU when it becomes
211 * ready to consume more packets.
212 */
213static void vmnet_send_completed(NetClientState *nc, ssize_t len)
214{
215    VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
216
217    /* Callback is invoked eq queued packet is sent */
218    ++s->packets_send_current_pos;
219
220    /* Complete sending packets left in VmnetState buffers */
221    vmnet_write_packets_to_qemu(s);
222
223    /* And read new ones from vmnet if VmnetState buffer is ready */
224    if (s->packets_send_current_pos < s->packets_send_end_pos) {
225        qemu_bh_schedule(s->send_bh);
226    }
227}
228
229
230static void vmnet_bufs_init(VmnetState *s)
231{
232    struct vmpktdesc *packets = s->packets_buf;
233    struct iovec *iov = s->iov_buf;
234    int i;
235
236    for (i = 0; i < VMNET_PACKETS_LIMIT; ++i) {
237        iov[i].iov_len = s->max_packet_size;
238        iov[i].iov_base = g_malloc0(iov[i].iov_len);
239        packets[i].vm_pkt_iov = iov + i;
240    }
241}
242
243/**
244 * Called on state change to un-register/re-register handlers
245 */
246static void vmnet_vm_state_change_cb(void *opaque, bool running, RunState state)
247{
248    VmnetState *s = opaque;
249
250    if (running) {
251        vmnet_interface_set_event_callback(
252            s->vmnet_if,
253            VMNET_INTERFACE_PACKETS_AVAILABLE,
254            s->if_queue,
255            ^(interface_event_t event_id, xpc_object_t event) {
256                assert(event_id == VMNET_INTERFACE_PACKETS_AVAILABLE);
257                /*
258                 * This function is being called from a non qemu thread, so
259                 * we only schedule a BH, and do the rest of the io completion
260                 * handling from vmnet_send_bh() which runs in a qemu context.
261                 */
262                qemu_bh_schedule(s->send_bh);
263            });
264    } else {
265        vmnet_interface_set_event_callback(
266            s->vmnet_if,
267            VMNET_INTERFACE_PACKETS_AVAILABLE,
268            NULL,
269            NULL);
270    }
271}
272
273int vmnet_if_create(NetClientState *nc,
274                    xpc_object_t if_desc,
275                    Error **errp)
276{
277    VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
278    dispatch_semaphore_t if_created_sem = dispatch_semaphore_create(0);
279    __block vmnet_return_t if_status;
280
281    s->if_queue = dispatch_queue_create(
282        "org.qemu.vmnet.if_queue",
283        DISPATCH_QUEUE_SERIAL
284    );
285
286    xpc_dictionary_set_bool(
287        if_desc,
288        vmnet_allocate_mac_address_key,
289        false
290    );
291
292#ifdef DEBUG
293    qemu_log("vmnet.start.interface_desc:\n");
294    xpc_dictionary_apply(if_desc,
295                         ^bool(const char *k, xpc_object_t v) {
296                             char *desc = xpc_copy_description(v);
297                             qemu_log("  %s=%s\n", k, desc);
298                             free(desc);
299                             return true;
300                         });
301#endif /* DEBUG */
302
303    s->vmnet_if = vmnet_start_interface(
304        if_desc,
305        s->if_queue,
306        ^(vmnet_return_t status, xpc_object_t interface_param) {
307            if_status = status;
308            if (status != VMNET_SUCCESS || !interface_param) {
309                dispatch_semaphore_signal(if_created_sem);
310                return;
311            }
312
313#ifdef DEBUG
314            qemu_log("vmnet.start.interface_param:\n");
315            xpc_dictionary_apply(interface_param,
316                                 ^bool(const char *k, xpc_object_t v) {
317                                     char *desc = xpc_copy_description(v);
318                                     qemu_log("  %s=%s\n", k, desc);
319                                     free(desc);
320                                     return true;
321                                 });
322#endif /* DEBUG */
323
324            s->mtu = xpc_dictionary_get_uint64(
325                interface_param,
326                vmnet_mtu_key);
327            s->max_packet_size = xpc_dictionary_get_uint64(
328                interface_param,
329                vmnet_max_packet_size_key);
330
331            dispatch_semaphore_signal(if_created_sem);
332        });
333
334    if (s->vmnet_if == NULL) {
335        dispatch_release(s->if_queue);
336        dispatch_release(if_created_sem);
337        error_setg(errp,
338                   "unable to create interface with requested params");
339        return -1;
340    }
341
342    dispatch_semaphore_wait(if_created_sem, DISPATCH_TIME_FOREVER);
343    dispatch_release(if_created_sem);
344
345    if (if_status != VMNET_SUCCESS) {
346        dispatch_release(s->if_queue);
347        error_setg(errp,
348                   "cannot create vmnet interface: %s",
349                   vmnet_status_map_str(if_status));
350        return -1;
351    }
352
353    s->send_bh = aio_bh_new(qemu_get_aio_context(), vmnet_send_bh, nc);
354    vmnet_bufs_init(s);
355
356    s->packets_send_current_pos = 0;
357    s->packets_send_end_pos = 0;
358
359    vmnet_vm_state_change_cb(s, 1, RUN_STATE_RUNNING);
360
361    s->change = qemu_add_vm_change_state_handler(vmnet_vm_state_change_cb, s);
362
363    return 0;
364}
365
366
367void vmnet_cleanup_common(NetClientState *nc)
368{
369    VmnetState *s = DO_UPCAST(VmnetState, nc, nc);
370    dispatch_semaphore_t if_stopped_sem;
371
372    if (s->vmnet_if == NULL) {
373        return;
374    }
375
376    vmnet_vm_state_change_cb(s, 0, RUN_STATE_SHUTDOWN);
377    qemu_del_vm_change_state_handler(s->change);
378    if_stopped_sem = dispatch_semaphore_create(0);
379    vmnet_stop_interface(
380        s->vmnet_if,
381        s->if_queue,
382        ^(vmnet_return_t status) {
383            assert(status == VMNET_SUCCESS);
384            dispatch_semaphore_signal(if_stopped_sem);
385        });
386    dispatch_semaphore_wait(if_stopped_sem, DISPATCH_TIME_FOREVER);
387
388    qemu_purge_queued_packets(nc);
389
390    qemu_bh_delete(s->send_bh);
391    dispatch_release(if_stopped_sem);
392    dispatch_release(s->if_queue);
393
394    for (int i = 0; i < VMNET_PACKETS_LIMIT; ++i) {
395        g_free(s->iov_buf[i].iov_base);
396    }
397}
398