blob: 2315ce60345ed962c1244e35c6d00bd4fcb729b3 [file] [log] [blame]
Lyudec99f8b72016-10-18 14:12:09 -04001/*
2 * Copyright © 2016 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 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * 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 NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 *
23 * Authors:
24 * Lyude Paul <lyude@redhat.com>
25 */
26
27#include "config.h"
28
29#include <string.h>
30#include <errno.h>
31#include <xmlrpc-c/base.h>
32#include <xmlrpc-c/client.h>
33#include <pthread.h>
34#include <glib.h>
35#include <pixman.h>
36#include <cairo.h>
37
38#include "igt.h"
39
40/**
41 * SECTION:igt_chamelium
42 * @short_description: Library for using the Chamelium into igt tests
43 * @title: Chamelium
44 * @include: igt_chamelium.h
45 *
46 * This library contains helpers for using Chameliums in IGT tests. This allows
47 * for tests to simulate more difficult tasks to automate such as display
48 * hotplugging, faulty display behaviors, etc.
49 *
50 * More information on the Chamelium can be found
51 * [on the ChromeOS project page](https://www.chromium.org/chromium-os/testing/chamelium).
52 *
53 * In order to run tests using the Chamelium, a valid configuration file must be
54 * present. The configuration file is a normal Glib keyfile (similar to Windows
55 * INI) structured like so:
56 *
57 * |[<!-- language="plain" -->
58 * [Chamelium]
59 * URL=http://chameleon:9992 # The URL used for connecting to the Chamelium's RPC server
60 *
61 * # The rest of the sections are used for defining connector mappings.
62 * # This is required so any tests using the Chamelium know which connector
63 * # on the test machine should be connected to each Chamelium port.
64 * #
65 * # In the event that any of these mappings are specified incorrectly,
66 * # any hotplugging tests for the incorrect connector mapping will fail.
67 *
68 * [Chamelium:DP-1] # The name of the DRM connector
69 * ChameliumPortID=1 # The ID of the port on the Chamelium this connector is attached to
70 *
71 * [Chamelium:HDMI-A-1]
72 * ChameliumPortID=3
73 * ]|
74 *
75 * By default, this file is expected to exist in ~/.igtrc . The directory for
76 * this can be overriden by setting the environment variable %IGT_CONFIG_PATH.
77 */
78
79struct chamelium_edid {
80 int id;
81 struct igt_list link;
82};
83
84struct chamelium_port {
85 unsigned int type;
86 int id;
87 int connector_id;
88 char *name;
89};
90
91struct chamelium_frame_dump {
92 unsigned char *bgr;
93 size_t size;
94 int width;
95 int height;
96 struct chamelium_port *port;
97};
98
99struct chamelium {
100 xmlrpc_env env;
101 xmlrpc_client *client;
102 char *url;
103
104 /* Indicates the last port to have been used for capturing video */
105 struct chamelium_port *capturing_port;
106
107 int drm_fd;
108
109 struct chamelium_edid *edids;
110 struct chamelium_port *ports;
111 int port_count;
112};
113
114static struct chamelium *cleanup_instance;
115
116/**
117 * chamelium_get_ports:
118 * @chamelium: The Chamelium instance to use
119 * @count: Where to store the number of ports
120 *
121 * Retrieves all of the ports currently configured for use with this chamelium
122 *
123 * Returns: an array containing a pointer to each configured chamelium port
124 */
125struct chamelium_port **chamelium_get_ports(struct chamelium *chamelium,
126 int *count)
127{
128 int i;
129 struct chamelium_port **ret =
130 calloc(sizeof(void*), chamelium->port_count);
131
132 *count = chamelium->port_count;
133 for (i = 0; i < chamelium->port_count; i++)
134 ret[i] = &chamelium->ports[i];
135
136 return ret;
137}
138
139/**
140 * chamelium_port_get_type:
141 * @port: The chamelium port to retrieve the type from
142 *
143 * Retrieves the DRM connector type of the physical port on the Chamelium. It
144 * should be noted that this type may differ from the type provided by the
145 * driver.
146 *
147 * Returns: the DRM connector type of the physical Chamelium port
148 */
149unsigned int chamelium_port_get_type(const struct chamelium_port *port) {
150 return port->type;
151}
152
153/**
154 * chamelium_port_get_connector:
155 * @chamelium: The Chamelium instance to use
156 * @port: The chamelium port to retrieve the DRM connector for
157 * @reprobe: Whether or not to reprobe the DRM connector
158 *
159 * Get a drmModeConnector object for the given Chamelium port, and optionally
160 * reprobe the port in the process
161 *
162 * Returns: a drmModeConnector object corresponding to the given port
163 */
164drmModeConnector *chamelium_port_get_connector(struct chamelium *chamelium,
165 struct chamelium_port *port,
166 bool reprobe)
167{
168 drmModeConnector *connector;
169
170 if (reprobe)
171 connector = drmModeGetConnector(chamelium->drm_fd,
172 port->connector_id);
173 else
174 connector = drmModeGetConnectorCurrent(
175 chamelium->drm_fd, port->connector_id);
176
177 return connector;
178}
179
180/**
181 * chamelium_port_get_name:
182 * @port: The chamelium port to retrieve the name of
183 *
184 * Gets the name of the DRM connector corresponding to the given Chamelium
185 * port.
186 *
187 * Returns: the name of the DRM connector
188 */
189const char *chamelium_port_get_name(struct chamelium_port *port)
190{
191 return port->name;
192}
193
194/**
195 * chamelium_destroy_frame_dump:
196 * @dump: The frame dump to destroy
197 *
198 * Destroys the given frame dump and frees all of the resources associated with
199 * it.
200 */
201void chamelium_destroy_frame_dump(struct chamelium_frame_dump *dump)
202{
203 free(dump->bgr);
204 free(dump);
205}
206
207struct fsm_monitor_args {
208 struct chamelium *chamelium;
209 struct chamelium_port *port;
210 struct udev_monitor *mon;
211};
212
213/*
214 * Whenever resolutions or other factors change with the display output, the
215 * Chamelium's display receivers need to be fully reset in order to perform any
216 * frame-capturing related tasks. This requires cutting off the display then
217 * turning it back on, and is indicated by the Chamelium sending hotplug events
218 */
219static void *chamelium_fsm_mon(void *data)
220{
221 struct fsm_monitor_args *args = data;
222 drmModeConnector *connector;
223 int drm_fd = args->chamelium->drm_fd;
224
225 /*
226 * Wait for the chamelium to try unplugging the connector, otherwise
227 * the thread calling chamelium_rpc will kill us
228 */
229 igt_hotplug_detected(args->mon, 60);
230
231 /*
232 * Just in case the RPC call being executed returns before we complete
233 * the FSM modesetting sequence, so we don't leave the display in a bad
234 * state.
235 */
236 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
237
238 igt_debug("Chamelium needs FSM, handling\n");
239 connector = chamelium_port_get_connector(args->chamelium, args->port,
240 false);
241 kmstest_set_connector_dpms(drm_fd, connector, DRM_MODE_DPMS_OFF);
242 kmstest_set_connector_dpms(drm_fd, connector, DRM_MODE_DPMS_ON);
243
244 drmModeFreeConnector(connector);
245 return NULL;
246}
247
248static xmlrpc_value *chamelium_rpc(struct chamelium *chamelium,
249 struct chamelium_port *fsm_port,
250 const char *method_name,
251 const char *format_str,
252 ...)
253{
254 xmlrpc_value *res;
255 va_list va_args;
256 struct fsm_monitor_args monitor_args;
257 pthread_t fsm_thread_id;
258
259 /* Cleanup the last error, if any */
260 if (chamelium->env.fault_occurred) {
261 xmlrpc_env_clean(&chamelium->env);
262 xmlrpc_env_init(&chamelium->env);
263 }
264
265 /* Unfortunately xmlrpc_client's event loop helpers are rather useless
266 * for implementing any sort of event loop, since they provide no way
267 * to poll for events other then the RPC response. This means in order
268 * to handle the chamelium attempting FSM, we have to fork into another
269 * thread and have that handle hotplugging displays
270 */
271 if (fsm_port) {
272 monitor_args.chamelium = chamelium;
273 monitor_args.port = fsm_port;
274 monitor_args.mon = igt_watch_hotplug();
275 pthread_create(&fsm_thread_id, NULL, chamelium_fsm_mon,
276 &monitor_args);
277 }
278
279 va_start(va_args, format_str);
280 xmlrpc_client_call2f_va(&chamelium->env, chamelium->client,
281 chamelium->url, method_name, format_str, &res,
282 va_args);
283 va_end(va_args);
284
285 if (fsm_port) {
286 pthread_cancel(fsm_thread_id);
287 igt_cleanup_hotplug(monitor_args.mon);
288 }
289
290 igt_assert_f(!chamelium->env.fault_occurred,
291 "Chamelium RPC call failed: %s\n",
292 chamelium->env.fault_string);
293
294 return res;
295}
296
297/**
298 * chamelium_plug:
299 * @chamelium: The Chamelium instance to use
300 * @port: The port on the chamelium to plug
301 *
302 * Simulate a display connector being plugged into the system using the
303 * chamelium.
304 */
305void chamelium_plug(struct chamelium *chamelium, struct chamelium_port *port)
306{
307 igt_debug("Plugging %s\n", port->name);
308 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Plug", "(i)", port->id));
309}
310
311/**
312 * chamelium_unplug:
313 * @chamelium: The Chamelium instance to use
314 * @port: The port on the chamelium to unplug
315 *
316 * Simulate a display connector being unplugged from the system using the
317 * chamelium.
318 */
319void chamelium_unplug(struct chamelium *chamelium, struct chamelium_port *port)
320{
321 igt_debug("Unplugging port %s\n", port->name);
322 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Unplug", "(i)",
323 port->id));
324}
325
326/**
327 * chamelium_is_plugged:
328 * @chamelium: The Chamelium instance to use
329 * @port: The port on the Chamelium to check the status of
330 *
331 * Check whether or not the given port has been plugged into the system using
332 * #chamelium_plug.
333 *
334 * Returns: %true if the connector is set to plugged in, %false otherwise.
335 */
336bool chamelium_is_plugged(struct chamelium *chamelium,
337 struct chamelium_port *port)
338{
339 xmlrpc_value *res;
340 xmlrpc_bool is_plugged;
341
342 res = chamelium_rpc(chamelium, NULL, "IsPlugged", "(i)", port->id);
343
344 xmlrpc_read_bool(&chamelium->env, res, &is_plugged);
345 xmlrpc_DECREF(res);
346
347 return is_plugged;
348}
349
350/**
351 * chamelium_port_wait_video_input_stable:
352 * @chamelium: The Chamelium instance to use
353 * @port: The port on the Chamelium to check the status of
354 * @timeout_secs: How long to wait for a video signal to appear before timing
355 * out
356 *
357 * Waits for a video signal to appear on the given port. This is useful for
358 * checking whether or not we've setup a monitor correctly.
359 *
360 * Returns: %true if a video signal was detected, %false if we timed out
361 */
362bool chamelium_port_wait_video_input_stable(struct chamelium *chamelium,
363 struct chamelium_port *port,
364 int timeout_secs)
365{
366 xmlrpc_value *res;
367 xmlrpc_bool is_on;
368
369 igt_debug("Waiting for video input to stabalize on %s\n", port->name);
370
371 res = chamelium_rpc(chamelium, port, "WaitVideoInputStable", "(ii)",
372 port->id, timeout_secs);
373
374 xmlrpc_read_bool(&chamelium->env, res, &is_on);
375 xmlrpc_DECREF(res);
376
377 return is_on;
378}
379
380/**
381 * chamelium_fire_hpd_pulses:
382 * @chamelium: The Chamelium instance to use
383 * @port: The port to fire the HPD pulses on
384 * @width_msec: How long each pulse should last
385 * @count: The number of pulses to send
386 *
387 * A convienence function for sending multiple hotplug pulses to the system.
388 * The pulses start at low (e.g. connector is disconnected), and then alternate
389 * from high (e.g. connector is plugged in) to low. This is the equivalent of
390 * repeatedly calling #chamelium_plug and #chamelium_unplug, waiting
391 * @width_msec between each call.
392 *
393 * If @count is even, the last pulse sent will be high, and if it's odd then it
394 * will be low. Resetting the HPD line back to it's previous state, if desired,
395 * is the responsibility of the caller.
396 */
397void chamelium_fire_hpd_pulses(struct chamelium *chamelium,
398 struct chamelium_port *port,
399 int width_msec, int count)
400{
401 xmlrpc_value *pulse_widths = xmlrpc_array_new(&chamelium->env);
402 xmlrpc_value *width = xmlrpc_int_new(&chamelium->env, width_msec);
403 int i;
404
405 igt_debug("Firing %d HPD pulses with width of %d msec on %s\n",
406 count, width_msec, port->name);
407
408 for (i = 0; i < count; i++)
409 xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
410
411 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "FireMixedHpdPulses",
412 "(iA)", port->id, pulse_widths));
413
414 xmlrpc_DECREF(width);
415 xmlrpc_DECREF(pulse_widths);
416}
417
418/**
419 * chamelium_fire_mixed_hpd_pulses:
420 * @chamelium: The Chamelium instance to use
421 * @port: The port to fire the HPD pulses on
422 * @...: The length of each pulse in milliseconds, terminated with a %0
423 *
424 * Does the same thing as #chamelium_fire_hpd_pulses, but allows the caller to
425 * specify the length of each individual pulse.
426 */
427void chamelium_fire_mixed_hpd_pulses(struct chamelium *chamelium,
428 struct chamelium_port *port, ...)
429{
430 va_list args;
431 xmlrpc_value *pulse_widths = xmlrpc_array_new(&chamelium->env), *width;
432 int arg;
433
434 igt_debug("Firing mixed HPD pulses on %s\n", port->name);
435
436 va_start(args, port);
437 for (arg = va_arg(args, int); arg; arg = va_arg(args, int)) {
438 width = xmlrpc_int_new(&chamelium->env, arg);
439 xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
440 xmlrpc_DECREF(width);
441 }
442 va_end(args);
443
444 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "FireMixedHpdPulses",
445 "(iA)", port->id, pulse_widths));
446
447 xmlrpc_DECREF(pulse_widths);
448}
449
450static void async_rpc_handler(const char *server_url, const char *method_name,
451 xmlrpc_value *param_array, void *user_data,
452 xmlrpc_env *fault, xmlrpc_value *result)
453{
454 /* We don't care about the responses */
455}
456
457/**
458 * chamelium_async_hpd_pulse_start:
459 * @chamelium: The Chamelium instance to use
460 * @port: The port to fire the HPD pulses on
461 * @high: Whether to fire a high pulse (e.g. simulate a connect), or a low
462 * pulse (e.g. simulate a disconnect)
463 * @delay_secs: How long to wait before sending the HPD pulse.
464 *
465 * Instructs the chamelium to send an hpd pulse after @delay_secs seconds have
466 * passed, without waiting for the chamelium to finish. This is useful for
467 * testing things such as hpd after a suspend/resume cycle, since we can't tell
468 * the chamelium to send a hotplug at the same time that our system is
469 * suspended.
470 *
471 * It is required that the user eventually call
472 * #chamelium_async_hpd_pulse_finish, to clean up the leftover XML-RPC
473 * responses from the chamelium.
474 */
475void chamelium_async_hpd_pulse_start(struct chamelium *chamelium,
476 struct chamelium_port *port,
477 bool high, int delay_secs)
478{
479 xmlrpc_value *pulse_widths = xmlrpc_array_new(&chamelium->env), *width;
480
481 /* TODO: Actually implement something in the chameleon server to allow
482 * for delayed actions such as hotplugs. This would work a bit better
483 * and allow us to test suspend/resume on ports without hpd like VGA
484 */
485
486 igt_debug("Sending HPD pulse (%s) on %s with %d second delay\n",
487 high ? "high->low" : "low->high", port->name, delay_secs);
488
489 /* If we're starting at high, make the first pulse width 0 so we keep
490 * the port connected */
491 if (high) {
492 width = xmlrpc_int_new(&chamelium->env, 0);
493 xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
494 xmlrpc_DECREF(width);
495 }
496
497 width = xmlrpc_int_new(&chamelium->env, delay_secs * 1000);
498 xmlrpc_array_append_item(&chamelium->env, pulse_widths, width);
499 xmlrpc_DECREF(width);
500
501 xmlrpc_client_start_rpcf(&chamelium->env, chamelium->client,
502 chamelium->url,
503 "FireMixedHpdPulses", async_rpc_handler, NULL,
504 "(iA)", port->id, pulse_widths);
505 xmlrpc_DECREF(pulse_widths);
506}
507
508/**
509 * chamelium_async_hpd_pulse_finish:
510 * @chamelium: The Chamelium instance to use
511 *
512 * Waits for any asynchronous RPC started by #chamelium_async_hpd_pulse_start
513 * to complete, and then cleans up any leftover responses from the chamelium.
514 * If all of the RPC calls have already completed, this function returns
515 * immediately.
516 */
517void chamelium_async_hpd_pulse_finish(struct chamelium *chamelium)
518{
519 xmlrpc_client_event_loop_finish(chamelium->client);
520}
521
522/**
523 * chamelium_new_edid:
524 * @chamelium: The Chamelium instance to use
525 * @edid: The edid blob to upload to the chamelium
526 *
527 * Uploads and registers a new EDID with the chamelium. The EDID will be
528 * destroyed automatically when #chamelium_deinit is called.
529 *
530 * Returns: The ID of the EDID uploaded to the chamelium.
531 */
532int chamelium_new_edid(struct chamelium *chamelium, const unsigned char *edid)
533{
534 xmlrpc_value *res;
535 struct chamelium_edid *allocated_edid;
536 int edid_id;
537
538 res = chamelium_rpc(chamelium, NULL, "CreateEdid", "(6)",
539 edid, EDID_LENGTH);
540
541 xmlrpc_read_int(&chamelium->env, res, &edid_id);
542 xmlrpc_DECREF(res);
543
544 allocated_edid = malloc(sizeof(struct chamelium_edid));
545 memset(allocated_edid, 0, sizeof(*allocated_edid));
546
547 allocated_edid->id = edid_id;
548 igt_list_init(&allocated_edid->link);
549
550 if (chamelium->edids)
551 igt_list_add(&chamelium->edids->link, &allocated_edid->link);
552 else
553 chamelium->edids = allocated_edid;
554
555 return edid_id;
556}
557
558static void chamelium_destroy_edid(struct chamelium *chamelium, int edid_id)
559{
560 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "DestroyEdid", "(i)",
561 edid_id));
562}
563
564/**
565 * chamelium_port_set_edid:
566 * @chamelium: The Chamelium instance to use
567 * @port: The port on the Chamelium to set the EDID on
568 * @edid_id: The ID of an EDID on the chamelium created with
569 * #chamelium_new_edid, or 0 to disable the EDID on the port
570 *
571 * Sets a port on the chamelium to use the specified EDID. This does not fire a
572 * hotplug pulse on it's own, and merely changes what EDID the chamelium port
573 * will report to us the next time we probe it. Users will need to reprobe the
574 * connectors themselves if they want to see the EDID reported by the port
575 * change.
576 */
577void chamelium_port_set_edid(struct chamelium *chamelium,
578 struct chamelium_port *port, int edid_id)
579{
580 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "ApplyEdid", "(ii)",
581 port->id, edid_id));
582}
583
584/**
585 * chamelium_port_set_ddc_state:
586 * @chamelium: The Chamelium instance to use
587 * @port: The port to change the DDC state on
588 * @enabled: Whether or not to enable the DDC bus
589 *
590 * This disables the DDC bus (e.g. the i2c line on the connector that gives us
591 * an EDID) of the specified port on the chamelium. This is useful for testing
592 * behavior on legacy connectors such as VGA, where the presence of a DDC bus
593 * is not always guaranteed.
594 */
595void chamelium_port_set_ddc_state(struct chamelium *chamelium,
596 struct chamelium_port *port,
597 bool enabled)
598{
599 igt_debug("%sabling DDC bus on %s\n",
600 enabled ? "En" : "Dis", port->name);
601
602 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "SetDdcState", "(ib)",
603 port->id, enabled));
604}
605
606/**
607 * chamelium_port_get_ddc_state:
608 * @chamelium: The Chamelium instance to use
609 * @port: The port on the Chamelium to check the status of
610 *
611 * Check whether or not the DDC bus on the specified chamelium port is enabled
612 * or not.
613 *
614 * Returns: %true if the DDC bus is enabled, %false otherwise.
615 */
616bool chamelium_port_get_ddc_state(struct chamelium *chamelium,
617 struct chamelium_port *port)
618{
619 xmlrpc_value *res;
620 xmlrpc_bool enabled;
621
622 res = chamelium_rpc(chamelium, NULL, "IsDdcEnabled", "(i)", port->id);
623 xmlrpc_read_bool(&chamelium->env, res, &enabled);
624
625 xmlrpc_DECREF(res);
626 return enabled;
627}
628
629/**
630 * chamelium_port_get_resolution:
631 * @chamelium: The Chamelium instance to use
632 * @port: The port on the Chamelium to check
633 * @x: Where to store the horizontal resolution of the port
634 * @y: Where to store the verical resolution of the port
635 *
636 * Check the current reported display resolution of the specified port on the
637 * chamelium. This information is provided by the chamelium itself, not DRM.
638 * Useful for verifying that we really are scanning out at the resolution we
639 * think we are.
640 */
641void chamelium_port_get_resolution(struct chamelium *chamelium,
642 struct chamelium_port *port,
643 int *x, int *y)
644{
645 xmlrpc_value *res, *res_x, *res_y;
646
647 res = chamelium_rpc(chamelium, port, "DetectResolution", "(i)",
648 port->id);
649
650 xmlrpc_array_read_item(&chamelium->env, res, 0, &res_x);
651 xmlrpc_array_read_item(&chamelium->env, res, 1, &res_y);
652 xmlrpc_read_int(&chamelium->env, res_x, x);
653 xmlrpc_read_int(&chamelium->env, res_y, y);
654
655 xmlrpc_DECREF(res_x);
656 xmlrpc_DECREF(res_y);
657 xmlrpc_DECREF(res);
658}
659
660static void chamelium_get_captured_resolution(struct chamelium *chamelium,
661 int *w, int *h)
662{
663 xmlrpc_value *res, *res_w, *res_h;
664
665 res = chamelium_rpc(chamelium, NULL, "GetCapturedResolution", "()");
666
667 xmlrpc_array_read_item(&chamelium->env, res, 0, &res_w);
668 xmlrpc_array_read_item(&chamelium->env, res, 1, &res_h);
669 xmlrpc_read_int(&chamelium->env, res_w, w);
670 xmlrpc_read_int(&chamelium->env, res_h, h);
671
672 xmlrpc_DECREF(res_w);
673 xmlrpc_DECREF(res_h);
674 xmlrpc_DECREF(res);
675}
676
677static struct chamelium_frame_dump *frame_from_xml(struct chamelium *chamelium,
678 xmlrpc_value *frame_xml)
679{
680 struct chamelium_frame_dump *ret = malloc(sizeof(*ret));
681
682 chamelium_get_captured_resolution(chamelium, &ret->width, &ret->height);
683 ret->port = chamelium->capturing_port;
684 xmlrpc_read_base64(&chamelium->env, frame_xml, &ret->size,
685 (void*)&ret->bgr);
686
687 return ret;
688}
689
690/**
691 * chamelium_port_dump_pixels:
692 * @chamelium: The Chamelium instance to use
693 * @port: The port to perform the video capture on
694 * @x: The X coordinate to crop the screen capture to
695 * @y: The Y coordinate to crop the screen capture to
696 * @w: The width of the area to crop the screen capture to, or 0 for the whole
697 * screen
698 * @h: The height of the area to crop the screen capture to, or 0 for the whole
699 * screen
700 *
701 * Captures the currently displayed image on the given chamelium port,
702 * optionally cropped to a given region. In situations where pre-calculating
703 * CRCs may not be reliable, this can be used as an alternative for figuring
704 * out whether or not the correct images are being displayed on the screen.
705 *
706 * The frame dump data returned by this function should be freed when the
707 * caller is done with it using #chamelium_destroy_frame_dump.
708 *
709 * As an important note: some of the EDIDs provided by the Chamelium cause
710 * certain GPU drivers to default to using limited color ranges. This can cause
711 * video captures from the Chamelium to provide different images then expected
712 * due to the difference in color ranges (framebuffer uses full color range,
713 * but the video output doesn't), and as a result lead to CRC mismatches. To
714 * workaround this, the caller should force the connector to use full color
715 * ranges by using #kmstest_set_connector_broadcast_rgb before setting up the
716 * display.
717 *
718 * Returns: a chamelium_frame_dump struct
719 */
720struct chamelium_frame_dump *chamelium_port_dump_pixels(struct chamelium *chamelium,
721 struct chamelium_port *port,
722 int x, int y,
723 int w, int h)
724{
725 xmlrpc_value *res;
726 struct chamelium_frame_dump *frame;
727
728 res = chamelium_rpc(chamelium, port, "DumpPixels",
729 (w && h) ? "(iiiii)" : "(innnn)",
730 port->id, x, y, w, h);
731 chamelium->capturing_port = port;
732
733 frame = frame_from_xml(chamelium, res);
734 xmlrpc_DECREF(res);
735
736 return frame;
737}
738
739static void crc_from_xml(struct chamelium *chamelium,
740 xmlrpc_value *xml_crc, igt_crc_t *out)
741{
742 xmlrpc_value *res;
743 int i;
744
745 out->n_words = xmlrpc_array_size(&chamelium->env, xml_crc);
746 for (i = 0; i < out->n_words; i++) {
747 xmlrpc_array_read_item(&chamelium->env, xml_crc, i, &res);
748 xmlrpc_read_int(&chamelium->env, res, (int*)&out->crc[i]);
749 xmlrpc_DECREF(res);
750 }
751}
752
753/**
754 * chamelium_get_crc_for_area:
755 * @chamelium: The Chamelium instance to use
756 * @port: The port to perform the CRC checking on
757 * @x: The X coordinate on the emulated display to start calculating the CRC
758 * from
759 * @y: The Y coordinate on the emulated display to start calculating the CRC
760 * from
761 * @w: The width of the area to fetch the CRC from, or %0 for the whole display
762 * @h: The height of the area to fetch the CRC from, or %0 for the whole display
763 *
764 * Reads back the pixel CRC for an area on the specified chamelium port. This
765 * is the same as using the CRC readback from a GPU, the main difference being
766 * the data is provided by the chamelium and also allows us to specify a region
767 * of the screen to use as opposed to the entire thing.
768 *
769 * As an important note: some of the EDIDs provided by the Chamelium cause
770 * certain GPU drivers to default to using limited color ranges. This can cause
771 * video captures from the Chamelium to provide different images then expected
772 * due to the difference in color ranges (framebuffer uses full color range,
773 * but the video output doesn't), and as a result lead to CRC mismatches. To
774 * workaround this, the caller should force the connector to use full color
775 * ranges by using #kmstest_set_connector_broadcast_rgb before setting up the
776 * display.
777 *
778 * After the caller is finished with the EDID returned by this function, the
779 * caller should manually free the resources associated with it.
780 *
781 * Returns: The CRC read back from the chamelium
782 */
783igt_crc_t *chamelium_get_crc_for_area(struct chamelium *chamelium,
784 struct chamelium_port *port,
785 int x, int y, int w, int h)
786{
787 xmlrpc_value *res;
788 igt_crc_t *ret = malloc(sizeof(igt_crc_t));
789
790 res = chamelium_rpc(chamelium, port, "ComputePixelChecksum",
791 (w && h) ? "(iiiii)" : "(innnn)",
792 port->id, x, y, w, h);
793 chamelium->capturing_port = port;
794
795 crc_from_xml(chamelium, res, ret);
796 xmlrpc_DECREF(res);
797
798 return ret;
799}
800
801/**
802 * chamelium_start_capture:
803 * @chamelium: The Chamelium instance to use
804 * @port: The port to perform the video capture on
805 * @x: The X coordinate to crop the video to
806 * @y: The Y coordinate to crop the video to
807 * @w: The width of the cropped video, or %0 for the whole display
808 * @h: The height of the cropped video, or %0 for the whole display
809 *
810 * Starts capturing video frames on the given Chamelium port. Once the user is
811 * finished capturing frames, they should call #chamelium_stop_capture.
812 *
813 * A blocking, one-shot version of this function is available: see
814 * #chamelium_capture
815 *
816 * As an important note: some of the EDIDs provided by the Chamelium cause
817 * certain GPU drivers to default to using limited color ranges. This can cause
818 * video captures from the Chamelium to provide different images then expected
819 * due to the difference in color ranges (framebuffer uses full color range,
820 * but the video output doesn't), and as a result lead to CRC and frame dump
821 * comparison mismatches. To workaround this, the caller should force the
822 * connector to use full color ranges by using
823 * #kmstest_set_connector_broadcast_rgb before setting up the display.
824 */
825void chamelium_start_capture(struct chamelium *chamelium,
826 struct chamelium_port *port, int x, int y, int w, int h)
827{
828 xmlrpc_DECREF(chamelium_rpc(chamelium, port, "StartCapturingVideo",
829 (w && h) ? "(iiiii)" : "(innnn)",
830 port->id, x, y, w, h));
831 chamelium->capturing_port = port;
832}
833
834/**
835 * chamelium_stop_capture:
836 * @chamelium: The Chamelium instance to use
837 * @frame_count: The number of frames to wait to capture, or %0 to stop
838 * immediately
839 *
840 * Finishes capturing video frames on the given Chamelium port. If @frame_count
841 * is specified, this call will block until the given number of frames have been
842 * captured.
843 */
844void chamelium_stop_capture(struct chamelium *chamelium, int frame_count)
845{
846 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "StopCapturingVideo",
847 "(i)", frame_count));
848}
849
850/**
851 * chamelium_capture:
852 * @chamelium: The Chamelium instance to use
853 * @port: The port to perform the video capture on
854 * @x: The X coordinate to crop the video to
855 * @y: The Y coordinate to crop the video to
856 * @w: The width of the cropped video, or %0 for the whole display
857 * @h: The height of the cropped video, or %0 for the whole display
858 * @frame_count: The number of frames to capture
859 *
860 * Captures the given number of frames on the chamelium. This is equivalent to
861 * calling #chamelium_start_capture immediately followed by
862 * #chamelium_stop_capture. The caller is blocked until all of the frames have
863 * been captured.
864 *
865 * As an important note: some of the EDIDs provided by the Chamelium cause
866 * certain GPU drivers to default to using limited color ranges. This can cause
867 * video captures from the Chamelium to provide different images then expected
868 * due to the difference in color ranges (framebuffer uses full color range,
869 * but the video output doesn't), and as a result lead to CRC and frame dump
870 * comparison mismatches. To workaround this, the caller should force the
871 * connector to use full color ranges by using
872 * #kmstest_set_connector_broadcast_rgb before setting up the display.
873 */
874void chamelium_capture(struct chamelium *chamelium, struct chamelium_port *port,
875 int x, int y, int w, int h, int frame_count)
876{
877 xmlrpc_DECREF(chamelium_rpc(chamelium, port, "CaptureVideo",
878 (w && h) ? "(iiiiii)" : "(iinnnn)",
879 port->id, frame_count, x, y, w, h));
880 chamelium->capturing_port = port;
881}
882
883/**
884 * chamelium_read_captured_crcs:
885 * @chamelium: The Chamelium instance to use
886 * @frame_count: Where to store the number of CRCs we read in
887 *
888 * Reads all of the CRCs that have been captured thus far from the Chamelium.
889 *
890 * Returns: An array of @frame_count length containing all of the CRCs we read
891 */
892igt_crc_t *chamelium_read_captured_crcs(struct chamelium *chamelium,
893 int *frame_count)
894{
895 igt_crc_t *ret;
896 xmlrpc_value *res, *elem;
897 int i;
898
899 res = chamelium_rpc(chamelium, NULL, "GetCapturedChecksums", "(in)", 0);
900
901 *frame_count = xmlrpc_array_size(&chamelium->env, res);
902 ret = calloc(sizeof(igt_crc_t), *frame_count);
903
904 for (i = 0; i < *frame_count; i++) {
905 xmlrpc_array_read_item(&chamelium->env, res, i, &elem);
906
907 crc_from_xml(chamelium, elem, &ret[i]);
908 ret[i].frame = i;
909
910 xmlrpc_DECREF(elem);
911 }
912
913 xmlrpc_DECREF(res);
914
915 return ret;
916}
917
918/**
919 * chamelium_port_read_captured_frame:
920 *
921 * @chamelium: The Chamelium instance to use
922 * @index: The index of the captured frame we want to get
923 *
924 * Retrieves a single video frame captured during the last video capture on the
925 * Chamelium. This data should be freed using #chamelium_destroy_frame_data
926 *
927 * Returns: a chamelium_frame_dump struct
928 */
929struct chamelium_frame_dump *chamelium_read_captured_frame(struct chamelium *chamelium,
930 unsigned int index)
931{
932 xmlrpc_value *res;
933 struct chamelium_frame_dump *frame;
934
935 res = chamelium_rpc(chamelium, NULL, "ReadCapturedFrame", "(i)", index);
936 frame = frame_from_xml(chamelium, res);
937 xmlrpc_DECREF(res);
938
939 return frame;
940}
941
942/**
943 * chamelium_get_captured_frame_count:
944 * @chamelium: The Chamelium instance to use
945 *
946 * Gets the number of frames that were captured during the last video capture.
947 *
948 * Returns: the number of frames the Chamelium captured during the last video
949 * capture.
950 */
951int chamelium_get_captured_frame_count(struct chamelium *chamelium)
952{
953 xmlrpc_value *res;
954 int ret;
955
956 res = chamelium_rpc(chamelium, NULL, "GetCapturedFrameCount", "()");
957 xmlrpc_read_int(&chamelium->env, res, &ret);
958
959 xmlrpc_DECREF(res);
960 return ret;
961}
962
963static pixman_image_t *convert_frame_format(pixman_image_t *src,
964 int format)
965{
966 pixman_image_t *converted;
967 int w = pixman_image_get_width(src), h = pixman_image_get_height(src);
968
969 converted = pixman_image_create_bits(format, w, h, NULL,
970 PIXMAN_FORMAT_BPP(format) * w);
971 pixman_image_composite(PIXMAN_OP_ADD, src, NULL, converted,
972 0, 0, 0, 0, 0, 0, w, h);
973
974 return converted;
975}
976
977/**
978 * chamelium_assert_frame_eq:
979 * @chamelium: The chamelium instance the frame dump belongs to
980 * @dump: The chamelium frame dump to check
981 * @fb: The framebuffer to check against
982 *
983 * Asserts that the image contained in the chamelium frame dump is identical to
984 * the given framebuffer. Useful for scenarios where pre-calculating CRCs might
985 * not be ideal.
986 */
987void chamelium_assert_frame_eq(const struct chamelium *chamelium,
988 const struct chamelium_frame_dump *dump,
989 struct igt_fb *fb)
990{
991 cairo_t *cr;
992 cairo_surface_t *fb_surface;
993 pixman_image_t *reference_src, *reference_bgr;
994 int w = dump->width, h = dump->height;
995 bool eq;
996
997 /* Get the cairo surface for the framebuffer */
998 cr = igt_get_cairo_ctx(chamelium->drm_fd, fb);
999 fb_surface = cairo_get_target(cr);
1000 cairo_surface_reference(fb_surface);
1001 cairo_destroy(cr);
1002
1003 /*
1004 * Convert the reference image into the same format as the chamelium
1005 * image
1006 */
1007 reference_src = pixman_image_create_bits(
1008 PIXMAN_x8r8g8b8, w, h,
1009 (void*)cairo_image_surface_get_data(fb_surface),
1010 cairo_image_surface_get_stride(fb_surface));
1011 reference_bgr = convert_frame_format(reference_src, PIXMAN_b8g8r8);
1012 pixman_image_unref(reference_src);
1013
1014 /* Now do the actual comparison */
1015 eq = memcmp(dump->bgr, pixman_image_get_data(reference_bgr),
1016 dump->size) == 0;
1017
1018 pixman_image_unref(reference_bgr);
1019 cairo_surface_destroy(fb_surface);
1020
1021 igt_fail_on_f(!eq,
1022 "Chamelium frame dump didn't match reference image\n");
1023}
1024
1025/**
1026 * chamelium_get_frame_limit:
1027 * @chamelium: The Chamelium instance to use
1028 * @port: The port to check the frame limit on
1029 * @w: The width of the area to get the capture frame limit for, or %0 for the
1030 * whole display
1031 * @h: The height of the area to get the capture frame limit for, or %0 for the
1032 * whole display
1033 *
1034 * Gets the max number of frames we can capture with the Chamelium for the given
1035 * resolution.
1036 *
1037 * Returns: The number of the max number of frames we can capture
1038 */
1039int chamelium_get_frame_limit(struct chamelium *chamelium,
1040 struct chamelium_port *port,
1041 int w, int h)
1042{
1043 xmlrpc_value *res;
1044 int ret;
1045
1046 if (!w && !h)
1047 chamelium_port_get_resolution(chamelium, port, &w, &h);
1048
1049 res = chamelium_rpc(chamelium, port, "GetMaxFrameLimit", "(iii)",
1050 port->id, w, h);
1051
1052 xmlrpc_read_int(&chamelium->env, res, &ret);
1053 xmlrpc_DECREF(res);
1054
1055 return ret;
1056}
1057
1058static unsigned int chamelium_get_port_type(struct chamelium *chamelium,
1059 struct chamelium_port *port)
1060{
1061 xmlrpc_value *res;
1062 const char *port_type_str;
1063 unsigned int port_type;
1064
1065 res = chamelium_rpc(chamelium, NULL, "GetConnectorType",
1066 "(i)", port->id);
1067
1068 xmlrpc_read_string(&chamelium->env, res, &port_type_str);
1069 igt_debug("Port %d is of type '%s'\n", port->id, port_type_str);
1070
1071 if (strcmp(port_type_str, "DP") == 0)
1072 port_type = DRM_MODE_CONNECTOR_DisplayPort;
1073 else if (strcmp(port_type_str, "HDMI") == 0)
1074 port_type = DRM_MODE_CONNECTOR_HDMIA;
1075 else if (strcmp(port_type_str, "VGA") == 0)
1076 port_type = DRM_MODE_CONNECTOR_VGA;
1077 else
1078 port_type = DRM_MODE_CONNECTOR_Unknown;
1079
1080 free((void*)port_type_str);
1081 xmlrpc_DECREF(res);
1082
1083 return port_type;
1084}
1085
1086static bool chamelium_read_port_mappings(struct chamelium *chamelium,
1087 int drm_fd, GKeyFile *key_file)
1088{
1089 drmModeRes *res;
1090 drmModeConnector *connector;
1091 struct chamelium_port *port;
1092 GError *error = NULL;
1093 char **group_list;
1094 char *group, *map_name;
1095 int port_i, i, j;
1096 bool ret = true;
1097
1098 group_list = g_key_file_get_groups(key_file, NULL);
1099
1100 /* Count how many connector mappings are specified in the config */
1101 for (i = 0; group_list[i] != NULL; i++) {
1102 if (strstr(group_list[i], "Chamelium:"))
1103 chamelium->port_count++;
1104 }
1105
1106 chamelium->ports = calloc(sizeof(struct chamelium_port),
1107 chamelium->port_count);
1108 port_i = 0;
1109 res = drmModeGetResources(drm_fd);
1110
1111 for (i = 0; group_list[i] != NULL; i++) {
1112 group = group_list[i];
1113
1114 if (!strstr(group, "Chamelium:"))
1115 continue;
1116
1117 map_name = group + (sizeof("Chamelium:") - 1);
1118
1119 port = &chamelium->ports[port_i++];
1120 port->name = strdup(map_name);
1121 port->id = g_key_file_get_integer(key_file, group,
1122 "ChameliumPortID",
1123 &error);
1124 if (!port->id) {
1125 igt_warn("Failed to read chamelium port ID for %s: %s\n",
1126 map_name, error->message);
1127 ret = false;
1128 goto out;
1129 }
1130
1131 port->type = chamelium_get_port_type(chamelium, port);
1132 if (port->type == DRM_MODE_CONNECTOR_Unknown) {
1133 igt_warn("Unable to retrieve the physical port type from the Chamelium for '%s'\n",
1134 map_name);
1135 ret = false;
1136 goto out;
1137 }
1138
1139 for (j = 0;
1140 j < res->count_connectors && !port->connector_id;
1141 j++) {
1142 char name[50];
1143
1144 connector = drmModeGetConnectorCurrent(
1145 drm_fd, res->connectors[j]);
1146
1147 /* We have to generate the connector name on our own */
1148 snprintf(name, 50, "%s-%u",
1149 kmstest_connector_type_str(connector->connector_type),
1150 connector->connector_type_id);
1151
1152 if (strcmp(name, map_name) == 0)
1153 port->connector_id = connector->connector_id;
1154
1155 drmModeFreeConnector(connector);
1156 }
1157 if (!port->connector_id) {
1158 igt_warn("No connector found with name '%s'\n",
1159 map_name);
1160 ret = false;
1161 goto out;
1162 }
1163
1164 igt_debug("Port '%s' with physical type '%s' mapped to Chamelium port %d\n",
1165 map_name, kmstest_connector_type_str(port->type),
1166 port->id);
1167 }
1168
1169out:
1170 drmModeFreeResources(res);
1171 g_strfreev(group_list);
1172
1173 return ret;
1174}
1175
1176static bool chamelium_read_config(struct chamelium *chamelium, int drm_fd)
1177{
1178 GKeyFile *key_file = g_key_file_new();
1179 GError *error = NULL;
1180 char *key_file_loc, *key_file_env;
1181 int rc;
1182 bool ret = true;
1183
1184 key_file_env = getenv("IGT_CONFIG_PATH");
1185 if (key_file_env) {
1186 key_file_loc = key_file_env;
1187 } else {
1188 key_file_loc = malloc(100);
1189 snprintf(key_file_loc, 100, "%s/.igtrc", g_get_home_dir());
1190 }
1191
1192 rc = g_key_file_load_from_file(key_file, key_file_loc,
1193 G_KEY_FILE_NONE, &error);
1194 if (!rc) {
1195 igt_warn("Failed to read chamelium configuration file: %s\n",
1196 error->message);
1197 ret = false;
1198 goto out;
1199 }
1200
1201 chamelium->url = g_key_file_get_string(key_file, "Chamelium", "URL",
1202 &error);
1203 if (!chamelium->url) {
1204 igt_warn("Couldn't read chamelium URL from config file: %s\n",
1205 error->message);
1206 ret = false;
1207 goto out;
1208 }
1209
1210 ret = chamelium_read_port_mappings(chamelium, drm_fd, key_file);
1211
1212out:
1213 g_key_file_free(key_file);
1214
1215 if (!key_file_env)
1216 free(key_file_loc);
1217
1218 return ret;
1219}
1220
1221/**
1222 * chamelium_reset:
1223 * @chamelium: The Chamelium instance to use
1224 *
1225 * Resets the chamelium's IO board. As well, this also has the effect of
1226 * causing all of the chamelium ports to get set to unplugged
1227 */
1228void chamelium_reset(struct chamelium *chamelium)
1229{
1230 igt_debug("Resetting the chamelium\n");
1231 xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Reset", "()"));
1232}
1233
1234static void chamelium_exit_handler(int sig)
1235{
1236 igt_debug("Deinitializing Chamelium\n");
1237
1238 if (cleanup_instance)
1239 chamelium_deinit(cleanup_instance);
1240}
1241
1242/**
1243 * chamelium_init:
1244 * @chamelium: The Chamelium instance to use
1245 * @drm_fd: a display initialized with #igt_display_init
1246 *
1247 * Sets up a connection with a chamelium, using the URL specified in the
1248 * Chamelium configuration. This must be called first before trying to use the
1249 * chamelium.
1250 *
1251 * If we fail to establish a connection with the chamelium, fail to find a
1252 * configured connector, etc. we fail the current test.
1253 *
1254 * Returns: A newly initialized chamelium struct, or NULL on error
1255 */
1256struct chamelium *chamelium_init(int drm_fd)
1257{
1258 struct chamelium *chamelium = malloc(sizeof(struct chamelium));
1259
1260 if (!chamelium)
1261 return NULL;
1262
1263 /* A chamelium instance was set up previously, so clean it up before
1264 * starting a new one
1265 */
1266 if (cleanup_instance)
1267 chamelium_deinit(cleanup_instance);
1268
1269 memset(chamelium, 0, sizeof(*chamelium));
1270 chamelium->drm_fd = drm_fd;
1271
1272 /* Setup the libxmlrpc context */
1273 xmlrpc_env_init(&chamelium->env);
1274 xmlrpc_client_setup_global_const(&chamelium->env);
1275 xmlrpc_client_create(&chamelium->env, XMLRPC_CLIENT_NO_FLAGS, PACKAGE,
1276 PACKAGE_VERSION, NULL, 0, &chamelium->client);
1277 if (chamelium->env.fault_occurred) {
1278 igt_debug("Failed to init xmlrpc: %s\n",
1279 chamelium->env.fault_string);
1280 goto error;
1281 }
1282
1283 if (!chamelium_read_config(chamelium, drm_fd))
1284 goto error;
1285
1286 chamelium_reset(chamelium);
1287
1288 cleanup_instance = chamelium;
1289 igt_install_exit_handler(chamelium_exit_handler);
1290
1291 return chamelium;
1292
1293error:
1294 xmlrpc_env_clean(&chamelium->env);
1295 free(chamelium);
1296
1297 return NULL;
1298}
1299
1300/**
1301 * chamelium_deinit:
1302 * @chamelium: The Chamelium instance to use
1303 *
1304 * Frees the resources used by a connection to the chamelium that was set up
1305 * with #chamelium_init. As well, this function restores the state of the
1306 * chamelium like it was before calling #chamelium_init. This function is also
1307 * called as an exit handler, so users only need to call manually if they don't
1308 * want the chamelium interfering with other tests in the same file.
1309 */
1310void chamelium_deinit(struct chamelium *chamelium)
1311{
1312 int i;
1313 struct chamelium_edid *pos, *tmp;
1314
1315 /* We want to make sure we leave all of the ports plugged in, since
1316 * testing setups requiring multiple monitors are probably using the
1317 * chamelium to provide said monitors
1318 */
1319 chamelium_reset(chamelium);
1320 for (i = 0; i < chamelium->port_count; i++)
1321 chamelium_plug(chamelium, &chamelium->ports[i]);
1322
1323 /* Destroy any EDIDs we created to make sure we don't leak them */
1324 igt_list_for_each_safe(pos, tmp, &chamelium->edids->link, link) {
1325 chamelium_destroy_edid(chamelium, pos->id);
1326 free(pos);
1327 }
1328
1329 xmlrpc_client_destroy(chamelium->client);
1330 xmlrpc_env_clean(&chamelium->env);
1331
1332 for (i = 0; i < chamelium->port_count; i++)
1333 free(chamelium->ports[i].name);
1334
1335 free(chamelium->ports);
1336 free(chamelium);
1337}
1338
1339igt_constructor {
1340 /* Frame dumps can be large, so we need to be able to handle very large
1341 * responses
1342 *
1343 * Limit here is 10MB
1344 */
1345 xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, 10485760);
1346}