8 #include <sys/select.h>
14 #include <spice/enums.h>
15 #include <spice/macros.h>
16 #include <spice/qxl_dev.h>
18 #include "test_display_base.h"
20 #define MEM_SLOT_GROUP_ID 0
22 #define NOTIFY_DISPLAY_BATCH (SINGLE_PART/2)
23 #define NOTIFY_CURSOR_BATCH 10
25 /* Parts cribbed from spice-display.h/.c/qxl.c */
27 typedef struct SimpleSpiceUpdate
{
28 QXLCommandExt ext
; // first
34 static void test_spice_destroy_update(SimpleSpiceUpdate
*update
)
39 if (update
->drawable
.clip
.type
!= SPICE_CLIP_TYPE_NONE
) {
40 uint8_t *ptr
= (uint8_t*)update
->drawable
.clip
.data
;
47 #define DEFAULT_WIDTH 640
48 #define DEFAULT_HEIGHT 320
51 static const int angle_parts
= 64 / SINGLE_PART
;
52 static int unique
= 1;
53 static int color
= -1;
56 __attribute__((noreturn
))
57 static void sigchld_handler(int signal_num
) // wait for the child process and exit
64 static void set_cmd(QXLCommandExt
*ext
, uint32_t type
, QXLPHYSICAL data
)
69 ext
->group_id
= MEM_SLOT_GROUP_ID
;
73 static void simple_set_release_info(QXLReleaseInfo
*info
, intptr_t ptr
)
76 //info->group_id = MEM_SLOT_GROUP_ID;
79 /* bitmap and rects are freed, so they must be allocated with malloc */
80 SimpleSpiceUpdate
*test_spice_create_update_from_bitmap(uint32_t surface_id
,
84 SimpleSpiceUpdate
*update
;
85 QXLDrawable
*drawable
;
89 bh
= bbox
.bottom
- bbox
.top
;
90 bw
= bbox
.right
- bbox
.left
;
92 update
= calloc(sizeof(*update
), 1);
93 update
->bitmap
= bitmap
;
94 drawable
= &update
->drawable
;
95 image
= &update
->image
;
97 drawable
->surface_id
= surface_id
;
99 drawable
->bbox
= bbox
;
100 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
101 drawable
->effect
= QXL_EFFECT_OPAQUE
;
102 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
103 drawable
->type
= QXL_DRAW_COPY
;
104 drawable
->surfaces_dest
[0] = -1;
105 drawable
->surfaces_dest
[1] = -1;
106 drawable
->surfaces_dest
[2] = -1;
108 drawable
->u
.copy
.rop_descriptor
= SPICE_ROPD_OP_PUT
;
109 drawable
->u
.copy
.src_bitmap
= (intptr_t)image
;
110 drawable
->u
.copy
.src_area
.right
= bw
;
111 drawable
->u
.copy
.src_area
.bottom
= bh
;
113 QXL_SET_IMAGE_ID(image
, QXL_IMAGE_GROUP_DEVICE
, unique
);
114 image
->descriptor
.type
= SPICE_IMAGE_TYPE_BITMAP
;
115 image
->bitmap
.flags
= QXL_BITMAP_DIRECT
| QXL_BITMAP_TOP_DOWN
;
116 image
->bitmap
.stride
= bw
* 4;
117 image
->descriptor
.width
= image
->bitmap
.x
= bw
;
118 image
->descriptor
.height
= image
->bitmap
.y
= bh
;
119 image
->bitmap
.data
= (intptr_t)bitmap
;
120 image
->bitmap
.palette
= 0;
121 image
->bitmap
.format
= SPICE_BITMAP_FMT_32BIT
;
123 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
128 static SimpleSpiceUpdate
*test_spice_create_update_draw(Test
*test
, uint32_t surface_id
)
145 bitmap
= dst
= malloc(bw
* bh
* 4);
146 //printf("allocated %p\n", dst);
149 unsigned char *data
= vt_font_data
+ c
*16;
150 unsigned char d
= *data
;
152 for (j
= 0; j
< 16; j
++) {
153 for (i
= 0; i
< 8; i
++) {
169 bbox
.left
= left
; bbox
.top
= top
;
170 bbox
.right
= left
+ bw
; bbox
.bottom
= top
+ bh
;
172 return test_spice_create_update_from_bitmap(surface_id
, bbox
, bitmap
);
176 static void create_primary_surface(Test
*test
, uint32_t width
,
179 QXLWorker
*qxl_worker
= test
->qxl_worker
;
180 QXLDevSurfaceCreate surface
= { 0, };
182 g_assert(height
<= MAX_HEIGHT
);
183 g_assert(width
<= MAX_WIDTH
);
184 g_assert(height
> 0);
187 surface
.format
= SPICE_SURFACE_FMT_32_xRGB
;
188 surface
.width
= test
->primary_width
= width
;
189 surface
.height
= test
->primary_height
= height
;
190 surface
.stride
= -width
* 4; /* negative? */
191 surface
.mouse_mode
= TRUE
; /* unused by red_worker */
193 surface
.type
= 0; /* unused by red_worker */
194 surface
.position
= 0; /* unused by red_worker */
195 surface
.mem
= (uint64_t)&test
->primary_surface
;
196 surface
.group_id
= MEM_SLOT_GROUP_ID
;
199 test
->height
= height
;
201 qxl_worker
->create_primary_surface(qxl_worker
, 0, &surface
);
204 QXLDevMemSlot slot
= {
205 .slot_group_id
= MEM_SLOT_GROUP_ID
,
214 static void attache_worker(QXLInstance
*qin
, QXLWorker
*_qxl_worker
)
216 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
218 if (test
->qxl_worker
) {
219 if (test
->qxl_worker
!= _qxl_worker
)
220 printf("%s ignored, %p is set, ignoring new %p\n", __func__
,
221 test
->qxl_worker
, _qxl_worker
);
223 printf("%s ignored, redundant\n", __func__
);
226 printf("%s\n", __func__
);
227 test
->qxl_worker
= _qxl_worker
;
228 test
->qxl_worker
->add_memslot(test
->qxl_worker
, &slot
);
229 create_primary_surface(test
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
230 test
->qxl_worker
->start(test
->qxl_worker
);
233 static void set_compression_level(QXLInstance
*qin
, int level
)
235 printf("%s\n", __func__
);
238 static void set_mm_time(QXLInstance
*qin
, uint32_t mm_time
)
241 static void get_init_info(QXLInstance
*qin
, QXLDevInitInfo
*info
)
243 memset(info
, 0, sizeof(*info
));
244 info
->num_memslots
= 1;
245 info
->num_memslots_groups
= 1;
246 info
->memslot_id_bits
= 1;
247 info
->memslot_gen_bits
= 1;
248 info
->n_surfaces
= 1;
252 // We shall now have a ring of commands, so that we can update
253 // it from a separate thread - since get_command is called from
254 // the worker thread, and we need to sometimes do an update_area,
255 // which cannot be done from red_worker context (not via dispatcher,
256 // since you get a deadlock, and it isn't designed to be done
257 // any other way, so no point testing that).
258 int commands_end
= 0;
259 int commands_start
= 0;
260 struct QXLCommandExt
* commands
[1024];
262 #define COMMANDS_SIZE COUNT(commands)
264 static void push_command(QXLCommandExt
*ext
)
266 g_assert(commands_end
- commands_start
< COMMANDS_SIZE
);
267 commands
[commands_end
% COMMANDS_SIZE
] = ext
;
271 static struct QXLCommandExt
*get_simple_command(void)
273 struct QXLCommandExt
*ret
= commands
[commands_start
% COMMANDS_SIZE
];
274 g_assert(commands_start
< commands_end
);
279 static int get_num_commands(void)
281 return commands_end
- commands_start
;
284 // called from spice_server thread (i.e. red_worker thread)
285 static int get_command(QXLInstance
*qin
, struct QXLCommandExt
*ext
)
287 if (get_num_commands() == 0) {
290 *ext
= *get_simple_command();
294 static void produce_command(Test
*test
)
297 QXLWorker
*qxl_worker
= test
->qxl_worker
;
299 g_assert(qxl_worker
);
301 if (!test
->num_commands
) {
306 command
= &test
->commands
[test
->cmd_index
];
308 command
->cb(test
, command
);
310 switch (command
->command
) {
311 /* Drawing commands, they all push a command to the command ring */
313 SimpleSpiceUpdate
*update
;
314 update
= test_spice_create_update_draw(test
, 0);
315 push_command(&update
->ext
);
319 test
->cmd_index
= (test
->cmd_index
+ 1) % test
->num_commands
;
322 static int req_cmd_notification(QXLInstance
*qin
)
324 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
326 test
->core
->timer_start(test
->wakeup_timer
, test
->wakeup_ms
);
330 static void do_wakeup(void *opaque
)
335 test
->cursor_notify
= NOTIFY_CURSOR_BATCH
;
336 for (notify
= NOTIFY_DISPLAY_BATCH
; notify
> 0;--notify
) {
337 produce_command(test
);
340 test
->core
->timer_start(test
->wakeup_timer
, test
->wakeup_ms
);
341 test
->qxl_worker
->wakeup(test
->qxl_worker
);
344 static void release_resource(QXLInstance
*qin
, struct QXLReleaseInfoExt release_info
)
346 QXLCommandExt
*ext
= (QXLCommandExt
*)(unsigned long)release_info
.info
->id
;
347 //printf("%s\n", __func__);
348 g_assert(release_info
.group_id
== MEM_SLOT_GROUP_ID
);
349 switch (ext
->cmd
.type
) {
351 test_spice_destroy_update((void*)ext
);
353 case QXL_CMD_SURFACE
:
356 case QXL_CMD_CURSOR
: {
357 QXLCursorCmd
*cmd
= (QXLCursorCmd
*)(unsigned long)ext
->cmd
.data
;
358 if (cmd
->type
== QXL_CURSOR_SET
) {
369 #define CURSOR_WIDTH 32
370 #define CURSOR_HEIGHT 32
374 uint8_t data
[CURSOR_WIDTH
* CURSOR_HEIGHT
* 4]; // 32bit per pixel
377 static void cursor_init()
379 cursor
.cursor
.header
.unique
= 0;
380 cursor
.cursor
.header
.type
= SPICE_CURSOR_TYPE_COLOR32
;
381 cursor
.cursor
.header
.width
= CURSOR_WIDTH
;
382 cursor
.cursor
.header
.height
= CURSOR_HEIGHT
;
383 cursor
.cursor
.header
.hot_spot_x
= 0;
384 cursor
.cursor
.header
.hot_spot_y
= 0;
385 cursor
.cursor
.data_size
= CURSOR_WIDTH
* CURSOR_HEIGHT
* 4;
387 // X drivers addes it to the cursor size because it could be
388 // cursor data information or another cursor related stuffs.
389 // Otherwise, the code will break in client/cursor.cpp side,
390 // that expect the data_size plus cursor information.
391 // Blame cursor protocol for this. :-)
392 cursor
.cursor
.data_size
+= 128;
393 cursor
.cursor
.chunk
.data_size
= cursor
.cursor
.data_size
;
394 cursor
.cursor
.chunk
.prev_chunk
= cursor
.cursor
.chunk
.next_chunk
= 0;
397 static int get_cursor_command(QXLInstance
*qin
, struct QXLCommandExt
*ext
)
399 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
400 static int color
= 0;
402 static int x
= 0, y
= 0;
403 QXLCursorCmd
*cursor_cmd
;
406 if (!test
->cursor_notify
) {
410 test
->cursor_notify
--;
411 cmd
= calloc(sizeof(QXLCommandExt
), 1);
412 cursor_cmd
= calloc(sizeof(QXLCursorCmd
), 1);
414 cursor_cmd
->release_info
.id
= (unsigned long)cmd
;
417 cursor_cmd
->type
= QXL_CURSOR_SET
;
418 cursor_cmd
->u
.set
.position
.x
= 0;
419 cursor_cmd
->u
.set
.position
.y
= 0;
420 cursor_cmd
->u
.set
.visible
= TRUE
;
421 cursor_cmd
->u
.set
.shape
= (unsigned long)&cursor
;
422 // Only a white rect (32x32) as cursor
423 memset(cursor
.data
, 255, sizeof(cursor
.data
));
426 cursor_cmd
->type
= QXL_CURSOR_MOVE
;
427 cursor_cmd
->u
.position
.x
= x
++ % test
->primary_width
;
428 cursor_cmd
->u
.position
.y
= y
++ % test
->primary_height
;
431 cmd
->cmd
.data
= (unsigned long)cursor_cmd
;
432 cmd
->cmd
.type
= QXL_CMD_CURSOR
;
433 cmd
->group_id
= MEM_SLOT_GROUP_ID
;
436 //printf("%s\n", __func__);
440 static int req_cursor_notification(QXLInstance
*qin
)
442 printf("%s\n", __func__
);
446 static void notify_update(QXLInstance
*qin
, uint32_t update_id
)
448 printf("%s\n", __func__
);
451 static int flush_resources(QXLInstance
*qin
)
453 printf("%s\n", __func__
);
457 static int client_monitors_config(QXLInstance
*qin
,
458 VDAgentMonitorsConfig
*monitors_config
)
460 if (!monitors_config
) {
461 printf("%s: NULL monitors_config\n", __func__
);
463 printf("%s: %d\n", __func__
, monitors_config
->num_of_monitors
);
468 static void set_client_capabilities(QXLInstance
*qin
,
469 uint8_t client_present
,
472 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
474 printf("%s: present %d caps %d\n", __func__
, client_present
, caps
[0]);
475 if (test
->on_client_connected
&& client_present
) {
476 test
->on_client_connected(test
);
478 if (test
->on_client_disconnected
&& !client_present
) {
479 test
->on_client_disconnected(test
);
483 static int client_count
= 0;
485 static void client_connected(Test
*test
)
487 printf("Client connected\n");
491 static void client_disconnected(Test
*test
)
494 if (client_count
> 0) {
496 printf("Client disconnected\n");
497 exit(0); // fixme: cleanup?
501 static void do_conn_timeout(void *opaque
)
505 if (client_count
<= 0) {
506 printf("do_conn_timeout\n");
507 exit (0); // fixme: cleanup?
512 QXLInterface display_sif
= {
514 .type
= SPICE_INTERFACE_QXL
,
515 .description
= "test",
516 .major_version
= SPICE_INTERFACE_QXL_MAJOR
,
517 .minor_version
= SPICE_INTERFACE_QXL_MINOR
519 .attache_worker
= attache_worker
,
520 .set_compression_level
= set_compression_level
,
521 .set_mm_time
= set_mm_time
,
522 .get_init_info
= get_init_info
,
524 /* the callbacks below are called from spice server thread context */
525 .get_command
= get_command
,
526 .req_cmd_notification
= req_cmd_notification
,
527 .release_resource
= release_resource
,
528 .get_cursor_command
= get_cursor_command
,
529 .req_cursor_notification
= req_cursor_notification
,
530 .notify_update
= notify_update
,
531 .flush_resources
= flush_resources
,
532 .client_monitors_config
= client_monitors_config
,
533 .set_client_capabilities
= set_client_capabilities
,
536 /* interface for tests */
537 void test_add_display_interface(Test
* test
)
539 spice_server_add_interface(test
->server
, &test
->qxl_instance
.base
);
542 static int vmc_write(SpiceCharDeviceInstance
*sin
, const uint8_t *buf
, int len
)
544 // printf("%s: %d\n", __func__, len);
548 static int vmc_read(SpiceCharDeviceInstance
*sin
, uint8_t *buf
, int len
)
550 // printf("%s: %d\n", __func__, len);
554 static void vmc_state(SpiceCharDeviceInstance
*sin
, int connected
)
556 // printf("%s: %d\n", __func__, connected);
559 static SpiceCharDeviceInterface vdagent_sif
= {
560 .base
.type
= SPICE_INTERFACE_CHAR_DEVICE
,
561 .base
.description
= "test spice virtual channel char device",
562 .base
.major_version
= SPICE_INTERFACE_CHAR_DEVICE_MAJOR
,
563 .base
.minor_version
= SPICE_INTERFACE_CHAR_DEVICE_MINOR
,
569 SpiceCharDeviceInstance vdagent_sin
= {
571 .sif
= &vdagent_sif
.base
,
573 .subtype
= "vdagent",
576 void test_add_agent_interface(SpiceServer
*server
)
578 spice_server_add_interface(server
, &vdagent_sin
.base
);
581 static void kbd_push_key(SpiceKbdInstance
*sin
, uint8_t frag
)
583 printf("KEYCODE %u\n", frag
);
586 static uint8_t kbd_get_leds(SpiceKbdInstance
*sin
)
591 static SpiceKbdInterface keyboard_sif
= {
592 .base
.type
= SPICE_INTERFACE_KEYBOARD
,
593 .base
.description
= "spiceterm keyboard device",
594 .base
.major_version
= SPICE_INTERFACE_KEYBOARD_MAJOR
,
595 .base
.minor_version
= SPICE_INTERFACE_KEYBOARD_MINOR
,
596 .push_scan_freg
= kbd_push_key
,
597 .get_leds
= kbd_get_leds
,
600 SpiceKbdInstance keyboard_sin
= {
602 .sif
= &keyboard_sif
.base
,
606 void test_add_keyboard_interface(SpiceServer
*server
)
608 spice_server_add_interface(server
, &keyboard_sin
.base
);
611 void test_set_simple_command_list(Test
*test
, int *simple_commands
, int num_commands
)
616 test
->commands
= malloc(sizeof(*test
->commands
) * num_commands
);
617 memset(test
->commands
, 0, sizeof(*test
->commands
) * num_commands
);
618 test
->num_commands
= num_commands
;
619 for (i
= 0 ; i
< num_commands
; ++i
) {
620 test
->commands
[i
].command
= simple_commands
[i
];
624 void test_set_command_list(Test
*test
, Command
*commands
, int num_commands
)
626 test
->commands
= commands
;
627 test
->num_commands
= num_commands
;
631 Test
*test_new(SpiceCoreInterface
*core
)
634 Test
*test
= g_new0(Test
, 1);
635 SpiceServer
* server
= spice_server_new();
637 test
->on_client_connected
= client_connected
,
638 test
->on_client_disconnected
= client_disconnected
,
640 test
->qxl_instance
.base
.sif
= &display_sif
.base
;
641 test
->qxl_instance
.id
= 0;
644 test
->server
= server
;
645 test
->wakeup_ms
= 50;
646 test
->cursor_notify
= NOTIFY_CURSOR_BATCH
;
647 // some common initialization for all display tests
648 printf("TESTER: listening on port %d (unsecure)\n", port
);
649 spice_server_set_port(server
, port
);
650 spice_server_set_noauth(server
);
651 int res
= spice_server_init(server
, core
);
653 g_error("spice_server_init failed, res = %d\n", res
);
657 test
->has_secondary
= 0;
658 test
->wakeup_timer
= core
->timer_add(do_wakeup
, test
);
660 int timeout
= 10; // max time to wait for client connection
661 test
->conn_timeout_timer
= core
->timer_add(do_conn_timeout
, test
);
662 test
->core
->timer_start(test
->conn_timeout_timer
, timeout
*1000);
668 __attribute__((noreturn
))
669 void usage(const char *argv0
, const int exitcode
)
672 printf("usage: %s\n", argv0
);
676 void spice_test_config_parse_args(int argc
, char **argv
)
678 struct option options
[] = {
679 // {"automated-tests", no_argument, &has_automated_tests, 1},
685 while ((val
= getopt_long(argc
, argv
, "", options
, &option_index
)) != -1) {
688 printf("unrecognized option '%s'\n", argv
[optind
- 1]);
689 usage(argv
[0], EXIT_FAILURE
);
696 printf("unknown argument '%s'\n", argv
[optind
]);
697 usage(argv
[0], EXIT_FAILURE
);