/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* Copyright (C) 2009 Red Hat, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #define SPICE_LOG_DOMAIN "SpiceWorker" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/lz.h" #include "common/rect.h" #include "common/region.h" #include "common/ring.h" #include "display-channel.h" #include "stream.h" #include "spice.h" #include "red-worker.h" #include "cursor-channel.h" #include "tree.h" #define CMD_RING_POLL_TIMEOUT 10 //milli #define CMD_RING_POLL_RETRIES 1 #define INF_EVENT_WAIT ~0 struct RedWorker { pthread_t thread; QXLInstance *qxl; SpiceWatch *dispatch_watch; int running; SpiceCoreInterfaceInternal core; unsigned int event_timeout; DisplayChannel *display_channel; uint32_t display_poll_tries; gboolean was_blocked; CursorChannel *cursor_channel; uint32_t cursor_poll_tries; RedMemSlotInfo mem_slots; SpiceImageCompression image_compression; spice_wan_compression_t jpeg_state; spice_wan_compression_t zlib_glz_state; uint32_t process_display_generation; #ifdef RED_STATISTICS StatNodeRef stat; uint64_t *wakeup_counter; uint64_t *command_counter; #endif int driver_cap_monitors_config; FILE *record_fd; }; static RedsState* red_worker_get_server(RedWorker *worker); static int display_is_connected(RedWorker *worker) { return (worker->display_channel && red_channel_is_connected( &worker->display_channel->common.base)); } static uint8_t *common_alloc_recv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size) { CommonGraphicsChannel *common = SPICE_CONTAINEROF(rcc->channel, CommonGraphicsChannel, base); /* SPICE_MSGC_MIGRATE_DATA is the only client message whose size is dynamic */ if (type == SPICE_MSGC_MIGRATE_DATA) { return spice_malloc(size); } if (size > CHANNEL_RECEIVE_BUF_SIZE) { spice_critical("unexpected message size %u (max is %d)", size, CHANNEL_RECEIVE_BUF_SIZE); return NULL; } return common->recv_buf; } static void common_release_recv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size, uint8_t* msg) { if (type == SPICE_MSGC_MIGRATE_DATA) { free(msg); } } void red_drawable_unref(RedDrawable *red_drawable) { if (--red_drawable->refs) { return; } red_qxl_release_resource(red_drawable->qxl, red_drawable->release_info_ext); red_put_drawable(red_drawable); free(red_drawable); } static int red_process_cursor(RedWorker *worker, int *ring_is_empty) { QXLCommandExt ext_cmd; int n = 0; if (!worker->running) { *ring_is_empty = TRUE; return n; } *ring_is_empty = FALSE; while (red_channel_max_pipe_size(RED_CHANNEL(worker->cursor_channel)) <= MAX_PIPE_SIZE) { if (!red_qxl_get_cursor_command(worker->qxl, &ext_cmd)) { *ring_is_empty = TRUE; if (worker->cursor_poll_tries < CMD_RING_POLL_RETRIES) { worker->event_timeout = MIN(worker->event_timeout, CMD_RING_POLL_TIMEOUT); } else if (worker->cursor_poll_tries == CMD_RING_POLL_RETRIES && !red_qxl_req_cursor_notification(worker->qxl)) { continue; } worker->cursor_poll_tries++; return n; } worker->cursor_poll_tries = 0; switch (ext_cmd.cmd.type) { case QXL_CMD_CURSOR: { RedCursorCmd *cursor = spice_new0(RedCursorCmd, 1); if (red_get_cursor_cmd(&worker->mem_slots, ext_cmd.group_id, cursor, ext_cmd.cmd.data)) { free(cursor); break; } cursor_channel_process_cmd(worker->cursor_channel, cursor); break; } default: spice_warning("bad command type"); } n++; } worker->was_blocked = TRUE; return n; } static RedDrawable *red_drawable_new(QXLInstance *qxl) { RedDrawable * red = spice_new0(RedDrawable, 1); red->refs = 1; red->qxl = qxl; return red; } static int red_process_display(RedWorker *worker, int *ring_is_empty) { QXLCommandExt ext_cmd; int n = 0; uint64_t start = spice_get_monotonic_time_ns(); if (!worker->running) { *ring_is_empty = TRUE; return n; } worker->process_display_generation++; *ring_is_empty = FALSE; while (red_channel_max_pipe_size(RED_CHANNEL(worker->display_channel)) <= MAX_PIPE_SIZE) { if (!red_qxl_get_command(worker->qxl, &ext_cmd)) { *ring_is_empty = TRUE; if (worker->display_poll_tries < CMD_RING_POLL_RETRIES) { worker->event_timeout = MIN(worker->event_timeout, CMD_RING_POLL_TIMEOUT); } else if (worker->display_poll_tries == CMD_RING_POLL_RETRIES && !red_qxl_req_cmd_notification(worker->qxl)) { continue; } worker->display_poll_tries++; return n; } if (worker->record_fd) red_record_qxl_command(worker->record_fd, &worker->mem_slots, ext_cmd, stat_now(CLOCK_MONOTONIC)); stat_inc_counter(reds, worker->command_counter, 1); worker->display_poll_tries = 0; switch (ext_cmd.cmd.type) { case QXL_CMD_DRAW: { RedDrawable *red_drawable = red_drawable_new(worker->qxl); // returns with 1 ref if (!red_get_drawable(&worker->mem_slots, ext_cmd.group_id, red_drawable, ext_cmd.cmd.data, ext_cmd.flags)) { display_channel_process_draw(worker->display_channel, red_drawable, worker->process_display_generation); } // release the red_drawable red_drawable_unref(red_drawable); break; } case QXL_CMD_UPDATE: { RedUpdateCmd update; if (red_get_update_cmd(&worker->mem_slots, ext_cmd.group_id, &update, ext_cmd.cmd.data)) { break; } if (!validate_surface(worker->display_channel, update.surface_id)) { spice_warning("Invalid surface in QXL_CMD_UPDATE"); } else { display_channel_draw(worker->display_channel, &update.area, update.surface_id); red_qxl_notify_update(worker->qxl, update.update_id); } red_qxl_release_resource(worker->qxl, update.release_info_ext); red_put_update_cmd(&update); break; } case QXL_CMD_MESSAGE: { RedMessage message; if (red_get_message(&worker->mem_slots, ext_cmd.group_id, &message, ext_cmd.cmd.data)) { break; } #ifdef DEBUG /* alert: accessing message.data is insecure */ spice_warning("MESSAGE: %s", message.data); #endif red_qxl_release_resource(worker->qxl, message.release_info_ext); red_put_message(&message); break; } case QXL_CMD_SURFACE: { RedSurfaceCmd surface; if (red_get_surface_cmd(&worker->mem_slots, ext_cmd.group_id, &surface, ext_cmd.cmd.data)) { break; } display_channel_process_surface_cmd(worker->display_channel, &surface, FALSE); // do not release resource as is released inside display_channel_process_surface_cmd red_put_surface_cmd(&surface); break; } default: spice_error("bad command type"); } n++; if (red_channel_all_blocked(&worker->display_channel->common.base) || spice_get_monotonic_time_ns() - start > NSEC_PER_SEC / 100) { worker->event_timeout = 0; return n; } } worker->was_blocked = TRUE; return n; } static bool red_process_is_blocked(RedWorker *worker) { return red_channel_max_pipe_size(RED_CHANNEL(worker->cursor_channel)) > MAX_PIPE_SIZE || red_channel_max_pipe_size(RED_CHANNEL(worker->display_channel)) > MAX_PIPE_SIZE; } static void red_disconnect_display(RedWorker *worker) { spice_warning("update timeout"); // TODO: we need to record the client that actually causes the timeout. So // we need to check the locations of the various pipe heads when counting, // and disconnect only those/that. red_channel_apply_clients(RED_CHANNEL(worker->display_channel), red_channel_client_disconnect); } static void red_migrate_display(DisplayChannel *display, RedChannelClient *rcc) { /* We need to stop the streams, and to send upgrade_items to the client. * Otherwise, (1) the client might display lossy regions that we don't track * (streams are not part of the migration data) (2) streams_timeout may occur * after the MIGRATE message has been sent. This can result in messages * being sent to the client after MSG_MIGRATE and before MSG_MIGRATE_DATA (e.g., * STREAM_CLIP, STREAM_DESTROY, DRAW_COPY) * No message besides MSG_MIGRATE_DATA should be sent after MSG_MIGRATE. * Notice that detach_and_stop_streams won't lead to any dev ram changes, since * handle_dev_stop already took care of releasing all the dev ram resources. */ stream_detach_and_stop(display); if (red_channel_client_is_connected(rcc)) { red_channel_client_default_migrate(rcc); } } typedef int (*red_process_t)(RedWorker *worker, int *ring_is_empty); typedef void (*red_disconnect_t)(RedWorker *worker); static void flush_commands(RedWorker *worker, RedChannel *red_channel, red_process_t process, red_disconnect_t disconnect) { for (;;) { uint64_t end_time; int ring_is_empty; process(worker, &ring_is_empty); if (ring_is_empty) { break; } while (process(worker, &ring_is_empty)) { red_channel_push(red_channel); } if (ring_is_empty) { break; } end_time = spice_get_monotonic_time_ns() + COMMON_CLIENT_TIMEOUT; for (;;) { red_channel_push(red_channel); if (red_channel_max_pipe_size(red_channel) <= MAX_PIPE_SIZE) { break; } red_channel_receive(red_channel); red_channel_send(red_channel); // TODO: MC: the whole timeout will break since it takes lowest timeout, should // do it client by client. if (spice_get_monotonic_time_ns() >= end_time) { disconnect(worker); } else { usleep(DISPLAY_CLIENT_RETRY_INTERVAL); } } } } static void flush_display_commands(RedWorker *worker) { flush_commands(worker, RED_CHANNEL(worker->display_channel), red_process_display, red_disconnect_display); } static void red_disconnect_cursor(RedWorker *worker) { spice_warning("flush cursor timeout"); cursor_channel_disconnect(worker->cursor_channel); } static void flush_cursor_commands(RedWorker *worker) { flush_commands(worker, RED_CHANNEL(worker->cursor_channel), red_process_cursor, red_disconnect_cursor); } // TODO: on timeout, don't disconnect all channels immediatly - try to disconnect the slowest ones // first and maybe turn timeouts to several timeouts in order to disconnect channels gradually. // Should use disconnect or shutdown? static void flush_all_qxl_commands(RedWorker *worker) { flush_display_commands(worker); flush_cursor_commands(worker); } static int common_channel_config_socket(RedChannelClient *rcc) { RedClient *client = red_channel_client_get_client(rcc); MainChannelClient *mcc = red_client_get_main(client); RedsStream *stream = red_channel_client_get_stream(rcc); CommonGraphicsChannelClient *ccc = COMMON_GRAPHICS_CHANNEL_CLIENT(rcc); int flags; int delay_val; if ((flags = fcntl(stream->socket, F_GETFL)) == -1) { spice_warning("accept failed, %s", strerror(errno)); return FALSE; } if (fcntl(stream->socket, F_SETFL, flags | O_NONBLOCK) == -1) { spice_warning("accept failed, %s", strerror(errno)); return FALSE; } // TODO - this should be dynamic, not one time at channel creation ccc->is_low_bandwidth = main_channel_client_is_low_bandwidth(mcc); delay_val = ccc->is_low_bandwidth ? 0 : 1; /* FIXME: Using Nagle's Algorithm can lead to apparent delays, depending * on the delayed ack timeout on the other side. * Instead of using Nagle's, we need to implement message buffering on * the application level. * see: http://www.stuartcheshire.org/papers/NagleDelayedAck/ */ if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) { if (errno != ENOTSUP) { spice_warning("setsockopt failed, %s", strerror(errno)); } } return TRUE; } CommonGraphicsChannelClient *common_graphics_channel_new_client(CommonGraphicsChannel *common, int size, RedClient *client, RedsStream *stream, int mig_target, int monitor_latency, uint32_t *common_caps, int num_common_caps, uint32_t *caps, int num_caps) { RedChannelClient *rcc = red_channel_client_create(size, &common->base, client, stream, monitor_latency, num_common_caps, common_caps, num_caps, caps); if (!rcc) { return NULL; } CommonGraphicsChannelClient *common_cc = (CommonGraphicsChannelClient*)rcc; common->during_target_migrate = mig_target; // TODO: move wide/narrow ack setting to red_channel. red_channel_client_ack_set_client_window(rcc, common_cc->is_low_bandwidth ? WIDE_CLIENT_ACK_WINDOW : NARROW_CLIENT_ACK_WINDOW); return common_cc; } CommonGraphicsChannel *red_worker_new_channel(RedWorker *worker, int size, const char *name, uint32_t channel_type, int migration_flags, ChannelCbs *channel_cbs, channel_handle_parsed_proc handle_parsed) { RedChannel *channel = NULL; CommonGraphicsChannel *common; spice_return_val_if_fail(worker, NULL); spice_return_val_if_fail(channel_cbs, NULL); spice_return_val_if_fail(!channel_cbs->config_socket, NULL); spice_return_val_if_fail(!channel_cbs->alloc_recv_buf, NULL); spice_return_val_if_fail(!channel_cbs->release_recv_buf, NULL); channel_cbs->config_socket = common_channel_config_socket; channel_cbs->alloc_recv_buf = common_alloc_recv_buf; channel_cbs->release_recv_buf = common_release_recv_buf; channel = red_channel_create_parser(size, red_worker_get_server(worker), &worker->core, channel_type, worker->qxl->id, TRUE /* handle_acks */, spice_get_client_channel_parser(channel_type, NULL), handle_parsed, channel_cbs, migration_flags); spice_return_val_if_fail(channel, NULL); red_channel_set_stat_node(channel, stat_add_node(red_worker_get_server(worker), worker->stat, name, TRUE)); common = (CommonGraphicsChannel *)channel; common->qxl = worker->qxl; return common; } static void guest_set_client_capabilities(RedWorker *worker) { int i; DisplayChannelClient *dcc; RedChannelClient *rcc; RingItem *link, *next; uint8_t caps[SPICE_CAPABILITIES_SIZE] = { 0 }; int caps_available[] = { SPICE_DISPLAY_CAP_SIZED_STREAM, SPICE_DISPLAY_CAP_MONITORS_CONFIG, SPICE_DISPLAY_CAP_COMPOSITE, SPICE_DISPLAY_CAP_A8_SURFACE, }; QXLInterface *qif = qxl_get_interface(worker->qxl); if (!red_qxl_check_qxl_version(worker->qxl, 3, 2)) { return; } if (!qif->set_client_capabilities) { return; } #define SET_CAP(a,c) \ ((a)[(c) / 8] |= (1 << ((c) % 8))) #define CLEAR_CAP(a,c) \ ((a)[(c) / 8] &= ~(1 << ((c) % 8))) if (!worker->running) { return; } if ((worker->display_channel == NULL) || (RED_CHANNEL(worker->display_channel)->clients_num == 0)) { red_qxl_set_client_capabilities(worker->qxl, FALSE, caps); } else { // Take least common denominator for (i = 0 ; i < sizeof(caps_available) / sizeof(caps_available[0]); ++i) { SET_CAP(caps, caps_available[i]); } DCC_FOREACH_SAFE(link, next, dcc, RED_CHANNEL(worker->display_channel)) { rcc = (RedChannelClient *)dcc; for (i = 0 ; i < sizeof(caps_available) / sizeof(caps_available[0]); ++i) { if (!red_channel_client_test_remote_cap(rcc, caps_available[i])) CLEAR_CAP(caps, caps_available[i]); } } red_qxl_set_client_capabilities(worker->qxl, TRUE, caps); } } static void cursor_connect(RedWorker *worker, RedClient *client, RedsStream *stream, int migrate, uint32_t *common_caps, int num_common_caps, uint32_t *caps, int num_caps) { CursorChannel *channel = worker->cursor_channel; CursorChannelClient *ccc; spice_return_if_fail(channel != NULL); spice_info("add cursor channel client"); ccc = cursor_channel_client_new(channel, client, stream, migrate, common_caps, num_common_caps, caps, num_caps); spice_return_if_fail(ccc != NULL); RedChannelClient *rcc = RED_CHANNEL_CLIENT(ccc); red_channel_client_ack_zero_messages_window(rcc); red_channel_client_push_set_ack(rcc); // TODO: why do we check for context.canvas? defer this to after display cc is connected // and test it's canvas? this is just a test to see if there is an active renderer? if (display_channel_surface_has_canvas(worker->display_channel, 0)) cursor_channel_init(channel, ccc); } static void handle_dev_update_async(void *opaque, void *payload) { RedWorker *worker = opaque; RedWorkerMessageUpdateAsync *msg = payload; QXLRect *qxl_dirty_rects = NULL; uint32_t num_dirty_rects = 0; spice_return_if_fail(worker->running); spice_return_if_fail(qxl_get_interface(worker->qxl)->update_area_complete); flush_display_commands(worker); display_channel_update(worker->display_channel, msg->surface_id, &msg->qxl_area, msg->clear_dirty_region, &qxl_dirty_rects, &num_dirty_rects); red_qxl_update_area_complete(worker->qxl, msg->surface_id, qxl_dirty_rects, num_dirty_rects); free(qxl_dirty_rects); } static void handle_dev_update(void *opaque, void *payload) { RedWorker *worker = opaque; RedWorkerMessageUpdate *msg = payload; spice_return_if_fail(worker->running); flush_display_commands(worker); display_channel_update(worker->display_channel, msg->surface_id, msg->qxl_area, msg->clear_dirty_region, &msg->qxl_dirty_rects, &msg->num_dirty_rects); } static void handle_dev_del_memslot(void *opaque, void *payload) { RedWorker *worker = opaque; RedWorkerMessageDelMemslot *msg = payload; uint32_t slot_id = msg->slot_id; uint32_t slot_group_id = msg->slot_group_id; memslot_info_del_slot(&worker->mem_slots, slot_group_id, slot_id); } static void handle_dev_destroy_surface_wait(void *opaque, void *payload) { RedWorkerMessageDestroySurfaceWait *msg = payload; RedWorker *worker = opaque; spice_return_if_fail(msg->surface_id == 0); flush_all_qxl_commands(worker); display_channel_destroy_surface_wait(worker->display_channel, msg->surface_id); } static void handle_dev_destroy_surfaces(void *opaque, void *payload) { RedWorker *worker = opaque; flush_all_qxl_commands(worker); display_channel_destroy_surfaces(worker->display_channel); cursor_channel_reset(worker->cursor_channel); } static void display_update_monitors_config(DisplayChannel *display, QXLMonitorsConfig *config, uint16_t count, uint16_t max_allowed) { if (display->monitors_config) monitors_config_unref(display->monitors_config); display->monitors_config = monitors_config_new(config->heads, count, max_allowed); } static void red_worker_push_monitors_config(RedWorker *worker) { DisplayChannelClient *dcc; RingItem *item, *next; FOREACH_DCC(worker->display_channel, item, next, dcc) { dcc_push_monitors_config(dcc); } } static void set_monitors_config_to_primary(DisplayChannel *display) { DrawContext *context = &display->surfaces[0].context; QXLHead head = { 0, }; spice_return_if_fail(display->surfaces[0].context.canvas); if (display->monitors_config) monitors_config_unref(display->monitors_config); head.width = context->width; head.height = context->height; display->monitors_config = monitors_config_new(&head, 1, 1); } static void dev_create_primary_surface(RedWorker *worker, uint32_t surface_id, QXLDevSurfaceCreate surface) { DisplayChannel *display = worker->display_channel; uint8_t *line_0; int error; spice_debug(NULL); spice_warn_if_fail(surface_id == 0); spice_warn_if_fail(surface.height != 0); spice_warn_if_fail(((uint64_t)abs(surface.stride) * (uint64_t)surface.height) == abs(surface.stride) * surface.height); line_0 = (uint8_t*)memslot_get_virt(&worker->mem_slots, surface.mem, surface.height * abs(surface.stride), surface.group_id, &error); if (error) { return; } if (worker->record_fd) { red_record_dev_input_primary_surface_create(worker->record_fd, &surface, line_0); } if (surface.stride < 0) { line_0 -= (int32_t)(surface.stride * (surface.height -1)); } display_channel_create_surface(display, 0, surface.width, surface.height, surface.stride, surface.format, line_0, surface.flags & QXL_SURF_FLAG_KEEP_DATA, TRUE); set_monitors_config_to_primary(display); if (display_is_connected(worker) && !worker->display_channel->common.during_target_migrate) { /* guest created primary, so it will (hopefully) send a monitors_config * now, don't send our own temporary one */ if (!worker->driver_cap_monitors_config) { red_worker_push_monitors_config(worker); } red_pipes_add_verb(&worker->display_channel->common.base, SPICE_MSG_DISPLAY_MARK); red_channel_push(&worker->display_channel->common.base); } cursor_channel_init(worker->cursor_channel, NULL); } static void handle_dev_create_primary_surface(void *opaque, void *payload) { RedWorkerMessageCreatePrimarySurface *msg = payload; RedWorker *worker = opaque; dev_create_primary_surface(worker, msg->surface_id, msg->surface); } static void destroy_primary_surface(RedWorker *worker, uint32_t surface_id) { DisplayChannel *display = worker->display_channel; if (!validate_surface(display, surface_id)) return; spice_warn_if_fail(surface_id == 0); spice_debug(NULL); if (!display->surfaces[surface_id].context.canvas) { spice_warning("double destroy of primary surface"); return; } flush_all_qxl_commands(worker); display_channel_destroy_surface_wait(display, 0); display_channel_surface_unref(display, 0); spice_warn_if_fail(ring_is_empty(&display->streams)); spice_warn_if_fail(!display->surfaces[surface_id].context.canvas); cursor_channel_reset(worker->cursor_channel); } static void handle_dev_destroy_primary_surface(void *opaque, void *payload) { RedWorkerMessageDestroyPrimarySurface *msg = payload; RedWorker *worker = opaque; uint32_t surface_id = msg->surface_id; destroy_primary_surface(worker, surface_id); } static void handle_dev_destroy_primary_surface_async(void *opaque, void *payload) { RedWorkerMessageDestroyPrimarySurfaceAsync *msg = payload; RedWorker *worker = opaque; uint32_t surface_id = msg->surface_id; destroy_primary_surface(worker, surface_id); } static void handle_dev_flush_surfaces_async(void *opaque, void *payload) { RedWorker *worker = opaque; flush_all_qxl_commands(worker); display_channel_flush_all_surfaces(worker->display_channel); } static void handle_dev_stop(void *opaque, void *payload) { RedWorker *worker = opaque; spice_info("stop"); spice_assert(worker->running); worker->running = FALSE; display_channel_free_glz_drawables(worker->display_channel); display_channel_flush_all_surfaces(worker->display_channel); /* todo: when the waiting is expected to take long (slow connection and * overloaded pipe), don't wait, and in case of migration, * purge the pipe, send destroy_all_surfaces * to the client (there is no such message right now), and start * from scratch on the destination side */ if (!red_channel_wait_all_sent(RED_CHANNEL(worker->display_channel), COMMON_CLIENT_TIMEOUT)) { red_channel_apply_clients(RED_CHANNEL(worker->display_channel), red_channel_client_disconnect_if_pending_send); } if (!red_channel_wait_all_sent(RED_CHANNEL(worker->cursor_channel), COMMON_CLIENT_TIMEOUT)) { red_channel_apply_clients(RED_CHANNEL(worker->cursor_channel), red_channel_client_disconnect_if_pending_send); } } static void handle_dev_start(void *opaque, void *payload) { RedWorker *worker = opaque; spice_assert(!worker->running); if (worker->cursor_channel) { COMMON_GRAPHICS_CHANNEL(worker->cursor_channel)->during_target_migrate = FALSE; } if (worker->display_channel) { worker->display_channel->common.during_target_migrate = FALSE; display_channel_wait_for_migrate_data(worker->display_channel); } worker->running = TRUE; guest_set_client_capabilities(worker); } static void handle_dev_wakeup(void *opaque, void *payload) { RedWorker *worker = opaque; stat_inc_counter(reds, worker->wakeup_counter, 1); red_qxl_clear_pending(worker->qxl->st, RED_DISPATCHER_PENDING_WAKEUP); } static void handle_dev_oom(void *opaque, void *payload) { RedWorker *worker = opaque; DisplayChannel *display = worker->display_channel; RedChannel *display_red_channel = &display->common.base; int ring_is_empty; spice_return_if_fail(worker->running); // streams? but without streams also leak spice_debug("OOM1 #draw=%u, #glz_draw=%u current %u pipes %u", display->drawable_count, display->glz_drawable_count, display->current_size, red_channel_sum_pipes_size(display_red_channel)); while (red_process_display(worker, &ring_is_empty)) { red_channel_push(display_red_channel); } if (red_qxl_flush_resources(worker->qxl) == 0) { display_channel_free_some(worker->display_channel); red_qxl_flush_resources(worker->qxl); } spice_debug("OOM2 #draw=%u, #glz_draw=%u current %u pipes %u", display->drawable_count, display->glz_drawable_count, display->current_size, red_channel_sum_pipes_size(display_red_channel)); red_qxl_clear_pending(worker->qxl->st, RED_DISPATCHER_PENDING_OOM); } static void handle_dev_reset_cursor(void *opaque, void *payload) { RedWorker *worker = opaque; cursor_channel_reset(worker->cursor_channel); } static void handle_dev_reset_image_cache(void *opaque, void *payload) { RedWorker *worker = opaque; DisplayChannel *display = worker->display_channel; image_cache_reset(&display->image_cache); } static void handle_dev_destroy_surface_wait_async(void *opaque, void *payload) { RedWorkerMessageDestroySurfaceWaitAsync *msg = payload; RedWorker *worker = opaque; display_channel_destroy_surface_wait(worker->display_channel, msg->surface_id); } static void handle_dev_destroy_surfaces_async(void *opaque, void *payload) { RedWorker *worker = opaque; flush_all_qxl_commands(worker); display_channel_destroy_surfaces(worker->display_channel); cursor_channel_reset(worker->cursor_channel); } static void handle_dev_create_primary_surface_async(void *opaque, void *payload) { RedWorkerMessageCreatePrimarySurfaceAsync *msg = payload; RedWorker *worker = opaque; dev_create_primary_surface(worker, msg->surface_id, msg->surface); } static void handle_dev_display_connect(void *opaque, void *payload) { RedWorkerMessageDisplayConnect *msg = payload; RedWorker *worker = opaque; DisplayChannel *display = worker->display_channel; DisplayChannelClient *dcc; spice_info("connect new client"); spice_return_if_fail(display); dcc = dcc_new(display, msg->client, msg->stream, msg->migration, msg->common_caps, msg->num_common_caps, msg->caps, msg->num_caps, worker->image_compression, worker->jpeg_state, worker->zlib_glz_state); if (!dcc) { return; } display_channel_update_compression(display, dcc); guest_set_client_capabilities(worker); dcc_start(dcc); free(msg->caps); free(msg->common_caps); } static void handle_dev_display_disconnect(void *opaque, void *payload) { RedWorkerMessageDisplayDisconnect *msg = payload; RedChannelClient *rcc = msg->rcc; RedWorker *worker = opaque; spice_info("disconnect display client"); spice_assert(rcc); guest_set_client_capabilities(worker); red_channel_client_disconnect(rcc); } static void handle_dev_display_migrate(void *opaque, void *payload) { RedWorkerMessageDisplayMigrate *msg = payload; RedWorker *worker = opaque; RedChannelClient *rcc = msg->rcc; spice_info("migrate display client"); spice_assert(rcc); red_migrate_display(worker->display_channel, rcc); } static inline uint32_t qxl_monitors_config_size(uint32_t heads) { return sizeof(QXLMonitorsConfig) + sizeof(QXLHead) * heads; } static void handle_dev_monitors_config_async(void *opaque, void *payload) { RedWorkerMessageMonitorsConfigAsync *msg = payload; RedWorker *worker = opaque; int error; uint16_t count, max_allowed; QXLMonitorsConfig *dev_monitors_config = (QXLMonitorsConfig*)memslot_get_virt(&worker->mem_slots, msg->monitors_config, qxl_monitors_config_size(1), msg->group_id, &error); if (error) { /* TODO: raise guest bug (requires added QXL interface) */ return; } worker->driver_cap_monitors_config = 1; count = dev_monitors_config->count; max_allowed = dev_monitors_config->max_allowed; if (count == 0) { spice_warning("ignoring an empty monitors config message from driver"); return; } if (count > max_allowed) { spice_warning("ignoring malformed monitors_config from driver, " "count > max_allowed %d > %d", count, max_allowed); return; } /* get pointer again to check virtual size */ dev_monitors_config = (QXLMonitorsConfig*)memslot_get_virt(&worker->mem_slots, msg->monitors_config, qxl_monitors_config_size(count), msg->group_id, &error); if (error) { /* TODO: raise guest bug (requires added QXL interface) */ return; } display_update_monitors_config(worker->display_channel, dev_monitors_config, MIN(count, msg->max_monitors), MIN(max_allowed, msg->max_monitors)); red_worker_push_monitors_config(worker); } /* TODO: special, perhaps use another dispatcher? */ static void handle_dev_cursor_connect(void *opaque, void *payload) { RedWorkerMessageCursorConnect *msg = payload; RedWorker *worker = opaque; spice_info("cursor connect"); cursor_connect(worker, msg->client, msg->stream, msg->migration, msg->common_caps, msg->num_common_caps, msg->caps, msg->num_caps); free(msg->caps); free(msg->common_caps); } static void handle_dev_cursor_disconnect(void *opaque, void *payload) { RedWorkerMessageCursorDisconnect *msg = payload; RedChannelClient *rcc = msg->rcc; spice_info("disconnect cursor client"); spice_return_if_fail(rcc); red_channel_client_disconnect(rcc); } static void handle_dev_cursor_migrate(void *opaque, void *payload) { RedWorkerMessageCursorMigrate *msg = payload; RedChannelClient *rcc = msg->rcc; spice_info("migrate cursor client"); cursor_channel_client_migrate(CURSOR_CHANNEL_CLIENT(rcc)); } static void handle_dev_set_compression(void *opaque, void *payload) { RedWorkerMessageSetCompression *msg = payload; RedWorker *worker = opaque; worker->image_compression = msg->image_compression; switch (worker->image_compression) { case SPICE_IMAGE_COMPRESSION_AUTO_LZ: spice_info("ic auto_lz"); break; case SPICE_IMAGE_COMPRESSION_AUTO_GLZ: spice_info("ic auto_glz"); break; case SPICE_IMAGE_COMPRESSION_QUIC: spice_info("ic quic"); break; #ifdef USE_LZ4 case SPICE_IMAGE_COMPRESSION_LZ4: spice_info("ic lz4"); break; #endif case SPICE_IMAGE_COMPRESSION_LZ: spice_info("ic lz"); break; case SPICE_IMAGE_COMPRESSION_GLZ: spice_info("ic glz"); break; case SPICE_IMAGE_COMPRESSION_OFF: spice_info("ic off"); break; default: spice_warning("ic invalid"); } display_channel_compress_stats_print(worker->display_channel); display_channel_compress_stats_reset(worker->display_channel); } static void handle_dev_set_streaming_video(void *opaque, void *payload) { RedWorkerMessageSetStreamingVideo *msg = payload; RedWorker *worker = opaque; display_channel_set_stream_video(worker->display_channel, msg->streaming_video); } static void handle_dev_set_mouse_mode(void *opaque, void *payload) { RedWorkerMessageSetMouseMode *msg = payload; RedWorker *worker = opaque; spice_info("mouse mode %u", msg->mode); cursor_channel_set_mouse_mode(worker->cursor_channel, msg->mode); } static void dev_add_memslot(RedWorker *worker, QXLDevMemSlot mem_slot) { memslot_info_add_slot(&worker->mem_slots, mem_slot.slot_group_id, mem_slot.slot_id, mem_slot.addr_delta, mem_slot.virt_start, mem_slot.virt_end, mem_slot.generation); } static void handle_dev_add_memslot(void *opaque, void *payload) { RedWorker *worker = opaque; RedWorkerMessageAddMemslot *msg = payload; QXLDevMemSlot mem_slot = msg->mem_slot; memslot_info_add_slot(&worker->mem_slots, mem_slot.slot_group_id, mem_slot.slot_id, mem_slot.addr_delta, mem_slot.virt_start, mem_slot.virt_end, mem_slot.generation); } static void handle_dev_add_memslot_async(void *opaque, void *payload) { RedWorkerMessageAddMemslotAsync *msg = payload; RedWorker *worker = opaque; dev_add_memslot(worker, msg->mem_slot); } static void handle_dev_reset_memslots(void *opaque, void *payload) { RedWorker *worker = opaque; memslot_info_reset(&worker->mem_slots); } static void handle_dev_driver_unload(void *opaque, void *payload) { RedWorker *worker = opaque; worker->driver_cap_monitors_config = 0; } static void handle_dev_gl_scanout(void *opaque, void *payload) { RedWorker *worker = opaque; display_channel_gl_scanout(worker->display_channel); } static void handle_dev_gl_draw_async(void *opaque, void *payload) { RedWorker *worker = opaque; SpiceMsgDisplayGlDraw *draw = payload; display_channel_gl_draw(worker->display_channel, draw); } static int loadvm_command(RedWorker *worker, QXLCommandExt *ext) { RedCursorCmd *cursor_cmd; RedSurfaceCmd *surface_cmd; switch (ext->cmd.type) { case QXL_CMD_CURSOR: cursor_cmd = spice_new0(RedCursorCmd, 1); if (red_get_cursor_cmd(&worker->mem_slots, ext->group_id, cursor_cmd, ext->cmd.data)) { free(cursor_cmd); return FALSE; } cursor_channel_process_cmd(worker->cursor_channel, cursor_cmd); break; case QXL_CMD_SURFACE: surface_cmd = spice_new0(RedSurfaceCmd, 1); if (red_get_surface_cmd(&worker->mem_slots, ext->group_id, surface_cmd, ext->cmd.data)) { free(surface_cmd); return FALSE; } display_channel_process_surface_cmd(worker->display_channel, surface_cmd, TRUE); break; default: spice_warning("unhandled loadvm command type (%d)", ext->cmd.type); } return TRUE; } static void handle_dev_loadvm_commands(void *opaque, void *payload) { RedWorkerMessageLoadvmCommands *msg = payload; RedWorker *worker = opaque; uint32_t i; uint32_t count = msg->count; QXLCommandExt *ext = msg->ext; spice_info("loadvm_commands"); for (i = 0 ; i < count ; ++i) { if (!loadvm_command(worker, &ext[i])) { /* XXX allow failure in loadvm? */ spice_warning("failed loadvm command type (%d)", ext[i].cmd.type); } } } static void worker_handle_dispatcher_async_done(void *opaque, uint32_t message_type, void *payload) { RedWorker *worker = opaque; RedWorkerMessageAsync *msg_async = payload; spice_debug(NULL); red_qxl_async_complete(worker->qxl, msg_async->cmd); } static void worker_dispatcher_record(void *opaque, uint32_t message_type, void *payload) { RedWorker *worker = opaque; red_record_event(worker->record_fd, 1, message_type, stat_now(CLOCK_MONOTONIC)); } static void register_callbacks(Dispatcher *dispatcher) { dispatcher_register_async_done_callback( dispatcher, worker_handle_dispatcher_async_done); /* TODO: register cursor & display specific msg in respective channel files */ dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DISPLAY_CONNECT, handle_dev_display_connect, sizeof(RedWorkerMessageDisplayConnect), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DISPLAY_DISCONNECT, handle_dev_display_disconnect, sizeof(RedWorkerMessageDisplayDisconnect), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DISPLAY_MIGRATE, handle_dev_display_migrate, sizeof(RedWorkerMessageDisplayMigrate), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CURSOR_CONNECT, handle_dev_cursor_connect, sizeof(RedWorkerMessageCursorConnect), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CURSOR_DISCONNECT, handle_dev_cursor_disconnect, sizeof(RedWorkerMessageCursorDisconnect), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CURSOR_MIGRATE, handle_dev_cursor_migrate, sizeof(RedWorkerMessageCursorMigrate), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_UPDATE, handle_dev_update, sizeof(RedWorkerMessageUpdate), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_UPDATE_ASYNC, handle_dev_update_async, sizeof(RedWorkerMessageUpdateAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_ADD_MEMSLOT, handle_dev_add_memslot, sizeof(RedWorkerMessageAddMemslot), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_ADD_MEMSLOT_ASYNC, handle_dev_add_memslot_async, sizeof(RedWorkerMessageAddMemslotAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DEL_MEMSLOT, handle_dev_del_memslot, sizeof(RedWorkerMessageDelMemslot), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_SURFACES, handle_dev_destroy_surfaces, sizeof(RedWorkerMessageDestroySurfaces), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_SURFACES_ASYNC, handle_dev_destroy_surfaces_async, sizeof(RedWorkerMessageDestroySurfacesAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE, handle_dev_destroy_primary_surface, sizeof(RedWorkerMessageDestroyPrimarySurface), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE_ASYNC, handle_dev_destroy_primary_surface_async, sizeof(RedWorkerMessageDestroyPrimarySurfaceAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE_ASYNC, handle_dev_create_primary_surface_async, sizeof(RedWorkerMessageCreatePrimarySurfaceAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE, handle_dev_create_primary_surface, sizeof(RedWorkerMessageCreatePrimarySurface), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_RESET_IMAGE_CACHE, handle_dev_reset_image_cache, sizeof(RedWorkerMessageResetImageCache), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_RESET_CURSOR, handle_dev_reset_cursor, sizeof(RedWorkerMessageResetCursor), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_WAKEUP, handle_dev_wakeup, sizeof(RedWorkerMessageWakeup), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_OOM, handle_dev_oom, sizeof(RedWorkerMessageOom), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_START, handle_dev_start, sizeof(RedWorkerMessageStart), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_FLUSH_SURFACES_ASYNC, handle_dev_flush_surfaces_async, sizeof(RedWorkerMessageFlushSurfacesAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_STOP, handle_dev_stop, sizeof(RedWorkerMessageStop), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_LOADVM_COMMANDS, handle_dev_loadvm_commands, sizeof(RedWorkerMessageLoadvmCommands), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_SET_COMPRESSION, handle_dev_set_compression, sizeof(RedWorkerMessageSetCompression), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_SET_STREAMING_VIDEO, handle_dev_set_streaming_video, sizeof(RedWorkerMessageSetStreamingVideo), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_SET_MOUSE_MODE, handle_dev_set_mouse_mode, sizeof(RedWorkerMessageSetMouseMode), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_SURFACE_WAIT, handle_dev_destroy_surface_wait, sizeof(RedWorkerMessageDestroySurfaceWait), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_SURFACE_WAIT_ASYNC, handle_dev_destroy_surface_wait_async, sizeof(RedWorkerMessageDestroySurfaceWaitAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_RESET_MEMSLOTS, handle_dev_reset_memslots, sizeof(RedWorkerMessageResetMemslots), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC, handle_dev_monitors_config_async, sizeof(RedWorkerMessageMonitorsConfigAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DRIVER_UNLOAD, handle_dev_driver_unload, sizeof(RedWorkerMessageDriverUnload), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_GL_SCANOUT, handle_dev_gl_scanout, sizeof(RedWorkerMessageGlScanout), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_GL_DRAW_ASYNC, handle_dev_gl_draw_async, sizeof(SpiceMsgDisplayGlDraw), DISPATCHER_NONE); } static void handle_dev_input(int fd, int event, void *opaque) { Dispatcher *dispatcher = opaque; dispatcher_handle_recv_read(dispatcher); } typedef struct RedWorkerSource { GSource source; RedWorker *worker; } RedWorkerSource; static gboolean worker_source_prepare(GSource *source, gint *p_timeout) { RedWorkerSource *wsource = SPICE_CONTAINEROF(source, RedWorkerSource, source); RedWorker *worker = wsource->worker; unsigned int timeout; timeout = MIN(worker->event_timeout, display_channel_get_streams_timeout(worker->display_channel)); *p_timeout = (timeout == INF_EVENT_WAIT) ? -1 : timeout; if (*p_timeout == 0) return TRUE; if (worker->was_blocked && !red_process_is_blocked(worker)) { return TRUE; } return FALSE; } static gboolean worker_source_check(GSource *source) { RedWorkerSource *wsource = SPICE_CONTAINEROF(source, RedWorkerSource, source); RedWorker *worker = wsource->worker; return worker->running /* TODO && worker->pending_process */; } static gboolean worker_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { RedWorkerSource *wsource = SPICE_CONTAINEROF(source, RedWorkerSource, source); RedWorker *worker = wsource->worker; DisplayChannel *display = worker->display_channel; int ring_is_empty; /* during migration, in the dest, the display channel can be initialized while the global lz data not since migrate data msg hasn't been received yet */ /* TODO: why is this here, and not in display_channel_create */ display_channel_free_glz_drawables_to_free(display); /* TODO: could use its own source */ stream_timeout(display); worker->event_timeout = INF_EVENT_WAIT; worker->was_blocked = FALSE; red_process_cursor(worker, &ring_is_empty); red_process_display(worker, &ring_is_empty); return TRUE; } /* cannot be const */ static GSourceFuncs worker_source_funcs = { .prepare = worker_source_prepare, .check = worker_source_check, .dispatch = worker_source_dispatch, }; RedWorker* red_worker_new(QXLInstance *qxl, const ClientCbs *client_cursor_cbs, const ClientCbs *client_display_cbs) { QXLDevInitInfo init_info; RedWorker *worker; Dispatcher *dispatcher; const char *record_filename; RedsState *reds = red_qxl_get_server(qxl->st); RedChannel *channel; red_qxl_get_init_info(qxl, &init_info); worker = spice_new0(RedWorker, 1); worker->core = event_loop_core; worker->core.main_context = g_main_context_new(); record_filename = getenv("SPICE_WORKER_RECORD_FILENAME"); if (record_filename) { static const char header[] = "SPICE_REPLAY 1\n"; worker->record_fd = fopen(record_filename, "w+"); if (worker->record_fd == NULL) { spice_error("failed to open recording file %s\n", record_filename); } if (fwrite(header, sizeof(header)-1, 1, worker->record_fd) != 1) { spice_error("failed to write replay header"); } } dispatcher = red_qxl_get_dispatcher(qxl); dispatcher_set_opaque(dispatcher, worker); worker->qxl = qxl; register_callbacks(dispatcher); if (worker->record_fd) { dispatcher_register_universal_handler(dispatcher, worker_dispatcher_record); } worker->image_compression = spice_server_get_image_compression(reds); worker->jpeg_state = reds_get_jpeg_state(reds); worker->zlib_glz_state = reds_get_zlib_glz_state(reds); worker->driver_cap_monitors_config = 0; #ifdef RED_STATISTICS char worker_str[20]; sprintf(worker_str, "display[%d]", worker->qxl->id); worker->stat = stat_add_node(reds, INVALID_STAT_REF, worker_str, TRUE); worker->wakeup_counter = stat_add_counter(reds, worker->stat, "wakeups", TRUE); worker->command_counter = stat_add_counter(reds, worker->stat, "commands", TRUE); #endif worker->dispatch_watch = worker->core.watch_add(&worker->core, dispatcher_get_recv_fd(dispatcher), SPICE_WATCH_EVENT_READ, handle_dev_input, dispatcher); spice_assert(worker->dispatch_watch != NULL); GSource *source = g_source_new(&worker_source_funcs, sizeof(RedWorkerSource)); SPICE_CONTAINEROF(source, RedWorkerSource, source)->worker = worker; g_source_attach(source, worker->core.main_context); g_source_unref(source); memslot_info_init(&worker->mem_slots, init_info.num_memslots_groups, init_info.num_memslots, init_info.memslot_gen_bits, init_info.memslot_id_bits, init_info.internal_groupslot_id); spice_warn_if_fail(init_info.n_surfaces <= NUM_SURFACES); worker->event_timeout = INF_EVENT_WAIT; worker->cursor_channel = cursor_channel_new(worker); channel = RED_CHANNEL(worker->cursor_channel); red_channel_register_client_cbs(channel, client_cursor_cbs, dispatcher); reds_register_channel(reds, channel); // TODO: handle seemless migration. Temp, setting migrate to FALSE worker->display_channel = display_channel_new(reds, worker, FALSE, reds_get_streaming_video(reds), init_info.n_surfaces); channel = RED_CHANNEL(worker->display_channel); red_channel_register_client_cbs(channel, client_display_cbs, dispatcher); red_channel_set_cap(channel, SPICE_DISPLAY_CAP_MONITORS_CONFIG); red_channel_set_cap(channel, SPICE_DISPLAY_CAP_PREF_COMPRESSION); red_channel_set_cap(channel, SPICE_DISPLAY_CAP_STREAM_REPORT); reds_register_channel(reds, channel); return worker; } SPICE_GNUC_NORETURN static void *red_worker_main(void *arg) { RedWorker *worker = arg; spice_info("begin"); spice_assert(MAX_PIPE_SIZE > WIDE_CLIENT_ACK_WINDOW && MAX_PIPE_SIZE > NARROW_CLIENT_ACK_WINDOW); //ensure wakeup by ack message RED_CHANNEL(worker->cursor_channel)->thread_id = pthread_self(); RED_CHANNEL(worker->display_channel)->thread_id = pthread_self(); GMainLoop *loop = g_main_loop_new(worker->core.main_context, FALSE); g_main_loop_run(loop); g_main_loop_unref(loop); /* FIXME: free worker, and join threads */ exit(0); } bool red_worker_run(RedWorker *worker) { sigset_t thread_sig_mask; sigset_t curr_sig_mask; int r; spice_return_val_if_fail(worker, FALSE); spice_return_val_if_fail(!worker->thread, FALSE); sigfillset(&thread_sig_mask); sigdelset(&thread_sig_mask, SIGILL); sigdelset(&thread_sig_mask, SIGFPE); sigdelset(&thread_sig_mask, SIGSEGV); pthread_sigmask(SIG_SETMASK, &thread_sig_mask, &curr_sig_mask); if ((r = pthread_create(&worker->thread, NULL, red_worker_main, worker))) { spice_error("create thread failed %d", r); } pthread_sigmask(SIG_SETMASK, &curr_sig_mask, NULL); return r == 0; } static RedsState* red_worker_get_server(RedWorker *worker) { return red_qxl_get_server(worker->qxl->st); }