1/*
2 * Copyright 2011 Red Hat, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * on the rights to use, copy, modify, merge, publish, distribute, sub
8 * license, and/or sell copies of the Software, and to permit persons to whom
9 * the Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
25
26#include <spice.h>
27
28#include "qxl.h"
29#include "spiceqxl_display.h"
30
31#ifndef container_of
32#define container_of(ptr, type, member) ({                      \
33        const typeof(((type *) 0)->member) *__mptr = (ptr);     \
34        (type *) ((char *) __mptr - offsetof(type, member));})
35#endif
36
37/* TODO: these is copied from qemu/hw/qxl.c . It shouldn't be there
38 * either, these ugly undef just remove the definitions from spice-protocol/spice/ipc_ring.h
39 * What should happen is using one definition, or a rename, and both in spice-protocol (because
40 * all the others are there).
41 * Practically speaking the only difference between the two is extra checking in this version,
42 * and usage (this one takes an extra parameter, the previous is meant to be used by assignment) */
43#undef SPICE_RING_PROD_ITEM
44#define SPICE_RING_PROD_ITEM(r, ret) {                                  \
45        typeof(r) start = r;                                            \
46        typeof(r) end = r + 1;                                          \
47        uint32_t prod = (r)->prod & SPICE_RING_INDEX_MASK(r);           \
48        typeof(&(r)->items[prod]) m_item = &(r)->items[prod];           \
49        if (!((uint8_t*)m_item >= (uint8_t*)(start) && (uint8_t*)(m_item + 1) <= (uint8_t*)(end))) { \
50            abort();                                                    \
51        }                                                               \
52        ret = &m_item->el;                                              \
53    }
54
55#undef SPICE_RING_CONS_ITEM
56#define SPICE_RING_CONS_ITEM(r, ret) {                                  \
57        typeof(r) start = r;                                            \
58        typeof(r) end = r + 1;                                          \
59        uint32_t cons = (r)->cons & SPICE_RING_INDEX_MASK(r);           \
60        typeof(&(r)->items[cons]) m_item = &(r)->items[cons];           \
61        if (!((uint8_t*)m_item >= (uint8_t*)(start) && (uint8_t*)(m_item + 1) <= (uint8_t*)(end))) { \
62            abort();                                                    \
63        }                                                               \
64        ret = &m_item->el;                                              \
65    }
66
67
68
69/* XSpice:
70 * We only need a single static identity slot.
71 * We actually need no slots, but less changes if we use one.
72 * We currently add it during attache_worker - should not be called more
73 * then once during lifetime (but we don't check)
74 */
75static QXLDevMemSlot slot = {
76.slot_group_id = MEMSLOT_GROUP,
77.slot_id = 0,
78.generation = 0,
79.virt_start = 0,
80.virt_end = ~0,
81.addr_delta = 0,
82.qxl_ram_size = ~0,
83};
84
85// TODO - real dprint, this is just to get it compiling
86#define dprint(qxl, lvl, fmt, ...) printf(fmt, __VA_ARGS__)
87
88static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker)
89{
90    static int count = 0;
91    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
92
93    if (++count > 1) {
94        dprint(qxl, 0, "%s ignored\n", __FUNCTION__);
95        return;
96    }
97    dprint(qxl, 1, "%s:\n", __FUNCTION__);
98    spice_qxl_add_memslot(sin, &slot);
99    qxl->worker = qxl_worker;
100}
101
102static void interface_set_compression_level(QXLInstance *sin, int level)
103{
104    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
105
106    dprint(qxl, 1, "%s: %d\n", __FUNCTION__, level);
107    qxl->shadow_rom.compression_level = level;
108    qxl->rom->compression_level = level;
109}
110
111static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time)
112{
113    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
114
115    qxl->shadow_rom.mm_clock = mm_time;
116    qxl->rom->mm_clock = mm_time;
117}
118
119static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
120{
121    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
122
123    dprint(qxl, 1, "%s:\n", __FUNCTION__);
124    info->memslot_gen_bits = MEMSLOT_GENERATION_BITS;
125    info->memslot_id_bits = MEMSLOT_SLOT_BITS;
126    info->num_memslots = NUM_MEMSLOTS;
127    info->num_memslots_groups = NUM_MEMSLOTS_GROUPS;
128    info->internal_groupslot_id = 0;
129    info->qxl_ram_size = qxl->shadow_rom.num_pages << TARGET_PAGE_BITS;
130    info->n_surfaces = NUM_SURFACES;
131}
132
133void qxl_send_events(qxl_screen_t *qxl, int events)
134{
135#if 0
136    ErrorF("qxl_send_events %d\n", events);
137    qxl_garbage_collect(qxl);
138#endif
139    /* we should trigger a garbage collection, but via a pipe. TODO */
140}
141
142/* called from spice server thread context only */
143static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
144{
145    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
146    QXLRam *ram = get_ram_header(qxl);
147    QXLCommandRing *ring;
148    QXLCommand *cmd;
149    int notify;
150
151    dprint(qxl, 2, "%s: %s\n", __FUNCTION__,
152           qxl->cmdflags ? "compat" : "native");
153    ring = &ram->cmd_ring;
154    if (SPICE_RING_IS_EMPTY(ring)) {
155        return FALSE;
156    }
157    SPICE_RING_CONS_ITEM(ring, cmd);
158    ext->cmd      = *cmd;
159    ext->group_id = MEMSLOT_GROUP;
160    ext->flags    = qxl->cmdflags;
161    SPICE_RING_POP(ring, notify);
162    if (notify) {
163        qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY);
164    }
165    qxl->guest_primary.commands++;
166    // TODO: re-enable, useful
167    //qxl_track_command(qxl, ext);
168    //qxl_log_command(qxl, "cmd", ext);
169    return TRUE;
170}
171
172/* called from spice server thread context only */
173static int interface_req_cmd_notification(QXLInstance *sin)
174{
175    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
176    QXLRam *header = get_ram_header(qxl);
177    int wait = 1;
178
179    SPICE_RING_CONS_WAIT(&header->cmd_ring, wait);
180    return wait;
181}
182
183/* called from spice server thread context only */
184static inline void qxl_push_free_res(qxl_screen_t *qxl, int flush)
185{
186    QXLRam *header = get_ram_header(qxl);
187    QXLReleaseRing *ring = &header->release_ring;
188    uint64_t *item;
189    int notify;
190
191#define QXL_FREE_BUNCH_SIZE 32
192
193    if (ring->prod - ring->cons + 1 == ring->num_items) {
194        /* ring full -- can't push */
195        return;
196    }
197    if (!flush && qxl->oom_running) {
198        /* collect everything from oom handler before pushing */
199        return;
200    }
201    if (!flush && qxl->num_free_res < QXL_FREE_BUNCH_SIZE) {
202        /* collect a bit more before pushing */
203        return;
204    }
205
206    SPICE_RING_PUSH(ring, notify);
207    dprint(qxl, 2, "free: push %d items, notify %s, ring %d/%d [%d,%d]\n",
208           qxl->num_free_res, notify ? "yes" : "no",
209           ring->prod - ring->cons, ring->num_items,
210           ring->prod, ring->cons);
211    if (notify) {
212        qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY);
213    }
214    SPICE_RING_PROD_ITEM(ring, item);
215    *item = 0;
216    qxl->num_free_res = 0;
217    qxl->last_release = NULL;
218}
219
220/* called from spice server thread context only */
221static void interface_release_resource(QXLInstance *sin,
222                                       struct QXLReleaseInfoExt ext)
223{
224    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
225    QXLRam *ram = get_ram_header(qxl);
226    QXLReleaseRing *ring;
227    uint64_t *item, id;
228
229    /*
230     * ext->info points into guest-visible memory
231     * pci bar 0, $command.release_info
232     */
233    ring = &ram->release_ring;
234    SPICE_RING_PROD_ITEM(ring, item);
235    if (*item == 0) {
236        /* stick head into the ring */
237        id = ext.info->id;
238        ext.info->next = 0;
239        *item = id;
240    } else {
241        /* append item to the list */
242        qxl->last_release->next = ext.info->id;
243        ext.info->next = 0;
244    }
245    qxl->last_release = ext.info;
246    qxl->num_free_res++;
247    dprint(qxl, 3, "%4d\r", qxl->num_free_res);
248    qxl_push_free_res(qxl, 0);
249}
250
251/* called from spice server thread context only */
252static int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext)
253{
254    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
255    QXLCursorRing *ring;
256    QXLCommand *cmd;
257    QXLRam *ram = get_ram_header(qxl);
258    int notify;
259
260    ring = &ram->cursor_ring;
261    if (SPICE_RING_IS_EMPTY(ring)) {
262        return FALSE;
263    }
264    SPICE_RING_CONS_ITEM(ring, cmd);
265    ext->cmd      = *cmd;
266    ext->group_id = MEMSLOT_GROUP;
267    ext->flags    = qxl->cmdflags;
268    SPICE_RING_POP(ring, notify);
269    if (notify) {
270        qxl_send_events(qxl, QXL_INTERRUPT_CURSOR);
271    }
272    qxl->guest_primary.commands++;
273    //qxl_track_command(qxl, ext); // TODO - copy me
274    //qxl_log_command(qxl, "csr", ext); // TODO - copy me
275    return TRUE;
276}
277
278/* called from spice server thread context only */
279static int interface_req_cursor_notification(QXLInstance *sin)
280{
281    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
282    QXLRam *ram = get_ram_header(qxl);
283    int wait = 1;
284
285    SPICE_RING_CONS_WAIT(&ram->cursor_ring, wait);
286    return wait;
287}
288
289/* called from spice server thread context */
290static void __attribute__ ((__noreturn__))
291    interface_notify_update(QXLInstance *sin, uint32_t update_id)
292{
293    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
294    abort();
295}
296
297/* called from spice server thread context only */
298static int interface_flush_resources(QXLInstance *sin)
299{
300    qxl_screen_t *qxl = container_of(sin, qxl_screen_t, display_sin);
301    int ret;
302
303    dprint(qxl, 1, "free: guest flush (have %d)\n", qxl->num_free_res);
304    ret = qxl->num_free_res;
305    if (ret) {
306        qxl_push_free_res(qxl, 1);
307    }
308    return ret;
309}
310
311static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token)
312{
313}
314
315static const QXLInterface qxl_interface = {
316    .base.type               = SPICE_INTERFACE_QXL,
317    .base.description        = "qxl gpu",
318    .base.major_version      = SPICE_INTERFACE_QXL_MAJOR,
319    .base.minor_version      = SPICE_INTERFACE_QXL_MINOR,
320
321    .attache_worker          = interface_attach_worker,
322    .set_compression_level   = interface_set_compression_level,
323    .set_mm_time             = interface_set_mm_time,
324    .get_init_info           = interface_get_init_info,
325
326    /* the callbacks below are called from spice server thread context */
327    .get_command             = interface_get_command,
328    .req_cmd_notification    = interface_req_cmd_notification,
329    .release_resource        = interface_release_resource,
330    .get_cursor_command      = interface_get_cursor_command,
331    .req_cursor_notification = interface_req_cursor_notification,
332    .notify_update           = interface_notify_update,
333    .flush_resources         = interface_flush_resources,
334    .async_complete          = interface_async_complete,
335};
336
337void qxl_add_spice_display_interface(qxl_screen_t *qxl)
338{
339    /* use this function to initialize the parts of qxl_screen_t
340     * that were added directly from qemu/hw/qxl.c */
341    qxl->cmdflags = 0;
342    qxl->oom_running = 0;
343    qxl->num_free_res = 0;
344
345    qxl->display_sin.base.sif = &qxl_interface.base;
346    qxl->display_sin.id = 0;
347    qxl->display_sin.st = (struct QXLState*)qxl;
348    spice_server_add_interface(qxl->spice_server, &qxl->display_sin.base);
349}
350
351void spiceqxl_display_monitors_config(qxl_screen_t *qxl)
352{
353    spice_qxl_monitors_config_async(&qxl->display_sin, physical_address(qxl, qxl->monitors_config, 0),
354                                    MEMSLOT_GROUP, 0);
355}
356