~ahs3/+junk/cq-qemu

« back to all changes in this revision

Viewing changes to ui/vnc-jobs-async.c

  • Committer: Al Stone
  • Date: 2012-02-09 01:17:20 UTC
  • Revision ID: albert.stone@canonical.com-20120209011720-tztl7ik3qayz80p4
first commit to bzr for qemu

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * QEMU VNC display driver
 
3
 *
 
4
 * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
 
5
 * Copyright (C) 2006 Fabrice Bellard
 
6
 * Copyright (C) 2009 Red Hat, Inc
 
7
 * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
 
8
 *
 
9
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 
10
 * of this software and associated documentation files (the "Software"), to deal
 
11
 * in the Software without restriction, including without limitation the rights
 
12
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
13
 * copies of the Software, and to permit persons to whom the Software is
 
14
 * furnished to do so, subject to the following conditions:
 
15
 *
 
16
 * The above copyright notice and this permission notice shall be included in
 
17
 * all copies or substantial portions of the Software.
 
18
 *
 
19
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
20
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
21
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 
22
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
23
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
24
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
25
 * THE SOFTWARE.
 
26
 */
 
27
 
 
28
 
 
29
#include "vnc.h"
 
30
#include "vnc-jobs.h"
 
31
 
 
32
/*
 
33
 * Locking:
 
34
 *
 
35
 * There is three levels of locking:
 
36
 * - jobs queue lock: for each operation on the queue (push, pop, isEmpty?)
 
37
 * - VncDisplay global lock: mainly used for framebuffer updates to avoid
 
38
 *                      screen corruption if the framebuffer is updated
 
39
 *                      while the worker is doing something.
 
40
 * - VncState::output lock: used to make sure the output buffer is not corrupted
 
41
 *                       if two threads try to write on it at the same time
 
42
 *
 
43
 * While the VNC worker thread is working, the VncDisplay global lock is hold
 
44
 * to avoid screen corruptions (this does not block vnc_refresh() because it
 
45
 * uses trylock()) but the output lock is not hold because the thread work on
 
46
 * its own output buffer.
 
47
 * When the encoding job is done, the worker thread will hold the output lock
 
48
 * and copy its output buffer in vs->output.
 
49
*/
 
50
 
 
51
struct VncJobQueue {
 
52
    QemuCond cond;
 
53
    QemuMutex mutex;
 
54
    QemuThread thread;
 
55
    Buffer buffer;
 
56
    bool exit;
 
57
    QTAILQ_HEAD(, VncJob) jobs;
 
58
};
 
59
 
 
60
typedef struct VncJobQueue VncJobQueue;
 
61
 
 
62
/*
 
63
 * We use a single global queue, but most of the functions are
 
64
 * already reetrant, so we can easilly add more than one encoding thread
 
65
 */
 
66
static VncJobQueue *queue;
 
67
 
 
68
static void vnc_lock_queue(VncJobQueue *queue)
 
69
{
 
70
    qemu_mutex_lock(&queue->mutex);
 
71
}
 
72
 
 
73
static void vnc_unlock_queue(VncJobQueue *queue)
 
74
{
 
75
    qemu_mutex_unlock(&queue->mutex);
 
76
}
 
77
 
 
78
VncJob *vnc_job_new(VncState *vs)
 
79
{
 
80
    VncJob *job = g_malloc0(sizeof(VncJob));
 
81
 
 
82
    job->vs = vs;
 
83
    vnc_lock_queue(queue);
 
84
    QLIST_INIT(&job->rectangles);
 
85
    vnc_unlock_queue(queue);
 
86
    return job;
 
87
}
 
88
 
 
89
int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h)
 
90
{
 
91
    VncRectEntry *entry = g_malloc0(sizeof(VncRectEntry));
 
92
 
 
93
    entry->rect.x = x;
 
94
    entry->rect.y = y;
 
95
    entry->rect.w = w;
 
96
    entry->rect.h = h;
 
97
 
 
98
    vnc_lock_queue(queue);
 
99
    QLIST_INSERT_HEAD(&job->rectangles, entry, next);
 
100
    vnc_unlock_queue(queue);
 
101
    return 1;
 
102
}
 
103
 
 
104
void vnc_job_push(VncJob *job)
 
105
{
 
106
    vnc_lock_queue(queue);
 
107
    if (queue->exit || QLIST_EMPTY(&job->rectangles)) {
 
108
        g_free(job);
 
109
    } else {
 
110
        QTAILQ_INSERT_TAIL(&queue->jobs, job, next);
 
111
        qemu_cond_broadcast(&queue->cond);
 
112
    }
 
113
    vnc_unlock_queue(queue);
 
114
}
 
115
 
 
116
static bool vnc_has_job_locked(VncState *vs)
 
117
{
 
118
    VncJob *job;
 
119
 
 
120
    QTAILQ_FOREACH(job, &queue->jobs, next) {
 
121
        if (job->vs == vs || !vs) {
 
122
            return true;
 
123
        }
 
124
    }
 
125
    return false;
 
126
}
 
127
 
 
128
bool vnc_has_job(VncState *vs)
 
129
{
 
130
    bool ret;
 
131
 
 
132
    vnc_lock_queue(queue);
 
133
    ret = vnc_has_job_locked(vs);
 
134
    vnc_unlock_queue(queue);
 
135
    return ret;
 
136
}
 
137
 
 
138
void vnc_jobs_clear(VncState *vs)
 
139
{
 
140
    VncJob *job, *tmp;
 
141
 
 
142
    vnc_lock_queue(queue);
 
143
    QTAILQ_FOREACH_SAFE(job, &queue->jobs, next, tmp) {
 
144
        if (job->vs == vs || !vs) {
 
145
            QTAILQ_REMOVE(&queue->jobs, job, next);
 
146
        }
 
147
    }
 
148
    vnc_unlock_queue(queue);
 
149
}
 
150
 
 
151
void vnc_jobs_join(VncState *vs)
 
152
{
 
153
    vnc_lock_queue(queue);
 
154
    while (vnc_has_job_locked(vs)) {
 
155
        qemu_cond_wait(&queue->cond, &queue->mutex);
 
156
    }
 
157
    vnc_unlock_queue(queue);
 
158
}
 
159
 
 
160
/*
 
161
 * Copy data for local use
 
162
 */
 
163
static void vnc_async_encoding_start(VncState *orig, VncState *local)
 
164
{
 
165
    local->vnc_encoding = orig->vnc_encoding;
 
166
    local->features = orig->features;
 
167
    local->ds = orig->ds;
 
168
    local->vd = orig->vd;
 
169
    local->lossy_rect = orig->lossy_rect;
 
170
    local->write_pixels = orig->write_pixels;
 
171
    local->clientds = orig->clientds;
 
172
    local->tight = orig->tight;
 
173
    local->zlib = orig->zlib;
 
174
    local->hextile = orig->hextile;
 
175
    local->zrle = orig->zrle;
 
176
    local->output =  queue->buffer;
 
177
    local->csock = -1; /* Don't do any network work on this thread */
 
178
 
 
179
    buffer_reset(&local->output);
 
180
}
 
181
 
 
182
static void vnc_async_encoding_end(VncState *orig, VncState *local)
 
183
{
 
184
    orig->tight = local->tight;
 
185
    orig->zlib = local->zlib;
 
186
    orig->hextile = local->hextile;
 
187
    orig->zrle = local->zrle;
 
188
    orig->lossy_rect = local->lossy_rect;
 
189
 
 
190
    queue->buffer = local->output;
 
191
}
 
192
 
 
193
static int vnc_worker_thread_loop(VncJobQueue *queue)
 
194
{
 
195
    VncJob *job;
 
196
    VncRectEntry *entry, *tmp;
 
197
    VncState vs;
 
198
    int n_rectangles;
 
199
    int saved_offset;
 
200
    bool flush;
 
201
 
 
202
    vnc_lock_queue(queue);
 
203
    while (QTAILQ_EMPTY(&queue->jobs) && !queue->exit) {
 
204
        qemu_cond_wait(&queue->cond, &queue->mutex);
 
205
    }
 
206
    /* Here job can only be NULL if queue->exit is true */
 
207
    job = QTAILQ_FIRST(&queue->jobs);
 
208
    vnc_unlock_queue(queue);
 
209
 
 
210
    if (queue->exit) {
 
211
        return -1;
 
212
    }
 
213
 
 
214
    vnc_lock_output(job->vs);
 
215
    if (job->vs->csock == -1 || job->vs->abort == true) {
 
216
        goto disconnected;
 
217
    }
 
218
    vnc_unlock_output(job->vs);
 
219
 
 
220
    /* Make a local copy of vs and switch output buffers */
 
221
    vnc_async_encoding_start(job->vs, &vs);
 
222
 
 
223
    /* Start sending rectangles */
 
224
    n_rectangles = 0;
 
225
    vnc_write_u8(&vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
 
226
    vnc_write_u8(&vs, 0);
 
227
    saved_offset = vs.output.offset;
 
228
    vnc_write_u16(&vs, 0);
 
229
 
 
230
    vnc_lock_display(job->vs->vd);
 
231
    QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
 
232
        int n;
 
233
 
 
234
        if (job->vs->csock == -1) {
 
235
            vnc_unlock_display(job->vs->vd);
 
236
            /* output mutex must be locked before going to
 
237
             * disconnected:
 
238
             */
 
239
            vnc_lock_output(job->vs);
 
240
            goto disconnected;
 
241
        }
 
242
 
 
243
        n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y,
 
244
                                        entry->rect.w, entry->rect.h);
 
245
 
 
246
        if (n >= 0) {
 
247
            n_rectangles += n;
 
248
        }
 
249
        g_free(entry);
 
250
    }
 
251
    vnc_unlock_display(job->vs->vd);
 
252
 
 
253
    /* Put n_rectangles at the beginning of the message */
 
254
    vs.output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF;
 
255
    vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
 
256
 
 
257
    /* Switch back buffers */
 
258
    vnc_lock_output(job->vs);
 
259
    if (job->vs->csock == -1) {
 
260
        goto disconnected;
 
261
    }
 
262
 
 
263
    vnc_write(job->vs, vs.output.buffer, vs.output.offset);
 
264
 
 
265
disconnected:
 
266
    /* Copy persistent encoding data */
 
267
    vnc_async_encoding_end(job->vs, &vs);
 
268
    flush = (job->vs->csock != -1 && job->vs->abort != true);
 
269
    vnc_unlock_output(job->vs);
 
270
 
 
271
    if (flush) {
 
272
        vnc_flush(job->vs);
 
273
    }
 
274
 
 
275
    vnc_lock_queue(queue);
 
276
    QTAILQ_REMOVE(&queue->jobs, job, next);
 
277
    vnc_unlock_queue(queue);
 
278
    qemu_cond_broadcast(&queue->cond);
 
279
    g_free(job);
 
280
    return 0;
 
281
}
 
282
 
 
283
static VncJobQueue *vnc_queue_init(void)
 
284
{
 
285
    VncJobQueue *queue = g_malloc0(sizeof(VncJobQueue));
 
286
 
 
287
    qemu_cond_init(&queue->cond);
 
288
    qemu_mutex_init(&queue->mutex);
 
289
    QTAILQ_INIT(&queue->jobs);
 
290
    return queue;
 
291
}
 
292
 
 
293
static void vnc_queue_clear(VncJobQueue *q)
 
294
{
 
295
    qemu_cond_destroy(&queue->cond);
 
296
    qemu_mutex_destroy(&queue->mutex);
 
297
    buffer_free(&queue->buffer);
 
298
    g_free(q);
 
299
    queue = NULL; /* Unset global queue */
 
300
}
 
301
 
 
302
static void *vnc_worker_thread(void *arg)
 
303
{
 
304
    VncJobQueue *queue = arg;
 
305
 
 
306
    qemu_thread_get_self(&queue->thread);
 
307
 
 
308
    while (!vnc_worker_thread_loop(queue)) ;
 
309
    vnc_queue_clear(queue);
 
310
    return NULL;
 
311
}
 
312
 
 
313
void vnc_start_worker_thread(void)
 
314
{
 
315
    VncJobQueue *q;
 
316
 
 
317
    if (vnc_worker_thread_running())
 
318
        return ;
 
319
 
 
320
    q = vnc_queue_init();
 
321
    qemu_thread_create(&q->thread, vnc_worker_thread, q);
 
322
    queue = q; /* Set global queue */
 
323
}
 
324
 
 
325
bool vnc_worker_thread_running(void)
 
326
{
 
327
    return queue; /* Check global queue */
 
328
}
 
329
 
 
330
void vnc_stop_worker_thread(void)
 
331
{
 
332
    if (!vnc_worker_thread_running())
 
333
        return ;
 
334
 
 
335
    /* Remove all jobs and wake up the thread */
 
336
    vnc_lock_queue(queue);
 
337
    queue->exit = true;
 
338
    vnc_unlock_queue(queue);
 
339
    vnc_jobs_clear(NULL);
 
340
    qemu_cond_broadcast(&queue->cond);
 
341
}