root / hw / qdev-monitor.c @ 9c17d615
History | View | Annotate | Download (16.1 kB)
1 |
/*
|
---|---|
2 |
* Dynamic device configuration and creation.
|
3 |
*
|
4 |
* Copyright (c) 2009 CodeSourcery
|
5 |
*
|
6 |
* This library is free software; you can redistribute it and/or
|
7 |
* modify it under the terms of the GNU Lesser General Public
|
8 |
* License as published by the Free Software Foundation; either
|
9 |
* version 2 of the License, or (at your option) any later version.
|
10 |
*
|
11 |
* This library is distributed in the hope that it will be useful,
|
12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 |
* Lesser General Public License for more details.
|
15 |
*
|
16 |
* You should have received a copy of the GNU Lesser General Public
|
17 |
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
18 |
*/
|
19 |
|
20 |
#include "qdev.h" |
21 |
#include "monitor/monitor.h" |
22 |
#include "qmp-commands.h" |
23 |
#include "sysemu/arch_init.h" |
24 |
#include "qemu/config-file.h" |
25 |
|
26 |
/*
|
27 |
* Aliases were a bad idea from the start. Let's keep them
|
28 |
* from spreading further.
|
29 |
*/
|
30 |
typedef struct QDevAlias |
31 |
{ |
32 |
const char *typename; |
33 |
const char *alias; |
34 |
uint32_t arch_mask; |
35 |
} QDevAlias; |
36 |
|
37 |
static const QDevAlias qdev_alias_table[] = { |
38 |
{ "virtio-blk-pci", "virtio-blk", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, |
39 |
{ "virtio-net-pci", "virtio-net", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, |
40 |
{ "virtio-serial-pci", "virtio-serial", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, |
41 |
{ "virtio-balloon-pci", "virtio-balloon", |
42 |
QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, |
43 |
{ "virtio-blk-s390", "virtio-blk", QEMU_ARCH_S390X }, |
44 |
{ "virtio-net-s390", "virtio-net", QEMU_ARCH_S390X }, |
45 |
{ "virtio-serial-s390", "virtio-serial", QEMU_ARCH_S390X }, |
46 |
{ "lsi53c895a", "lsi" }, |
47 |
{ "ich9-ahci", "ahci" }, |
48 |
{ "kvm-pci-assign", "pci-assign" }, |
49 |
{ } |
50 |
}; |
51 |
|
52 |
static const char *qdev_class_get_alias(DeviceClass *dc) |
53 |
{ |
54 |
const char *typename = object_class_get_name(OBJECT_CLASS(dc)); |
55 |
int i;
|
56 |
|
57 |
for (i = 0; qdev_alias_table[i].typename; i++) { |
58 |
if (qdev_alias_table[i].arch_mask &&
|
59 |
!(qdev_alias_table[i].arch_mask & arch_type)) { |
60 |
continue;
|
61 |
} |
62 |
|
63 |
if (strcmp(qdev_alias_table[i].typename, typename) == 0) { |
64 |
return qdev_alias_table[i].alias;
|
65 |
} |
66 |
} |
67 |
|
68 |
return NULL; |
69 |
} |
70 |
|
71 |
static bool qdev_class_has_alias(DeviceClass *dc) |
72 |
{ |
73 |
return (qdev_class_get_alias(dc) != NULL); |
74 |
} |
75 |
|
76 |
static void qdev_print_devinfo(ObjectClass *klass, void *opaque) |
77 |
{ |
78 |
DeviceClass *dc; |
79 |
bool *show_no_user = opaque;
|
80 |
|
81 |
dc = (DeviceClass *)object_class_dynamic_cast(klass, TYPE_DEVICE); |
82 |
|
83 |
if (!dc || (show_no_user && !*show_no_user && dc->no_user)) {
|
84 |
return;
|
85 |
} |
86 |
|
87 |
error_printf("name \"%s\"", object_class_get_name(klass));
|
88 |
if (dc->bus_type) {
|
89 |
error_printf(", bus %s", dc->bus_type);
|
90 |
} |
91 |
if (qdev_class_has_alias(dc)) {
|
92 |
error_printf(", alias \"%s\"", qdev_class_get_alias(dc));
|
93 |
} |
94 |
if (dc->desc) {
|
95 |
error_printf(", desc \"%s\"", dc->desc);
|
96 |
} |
97 |
if (dc->no_user) {
|
98 |
error_printf(", no-user");
|
99 |
} |
100 |
error_printf("\n");
|
101 |
} |
102 |
|
103 |
static int set_property(const char *name, const char *value, void *opaque) |
104 |
{ |
105 |
DeviceState *dev = opaque; |
106 |
|
107 |
if (strcmp(name, "driver") == 0) |
108 |
return 0; |
109 |
if (strcmp(name, "bus") == 0) |
110 |
return 0; |
111 |
|
112 |
if (qdev_prop_parse(dev, name, value) == -1) { |
113 |
return -1; |
114 |
} |
115 |
return 0; |
116 |
} |
117 |
|
118 |
static const char *find_typename_by_alias(const char *alias) |
119 |
{ |
120 |
int i;
|
121 |
|
122 |
for (i = 0; qdev_alias_table[i].alias; i++) { |
123 |
if (qdev_alias_table[i].arch_mask &&
|
124 |
!(qdev_alias_table[i].arch_mask & arch_type)) { |
125 |
continue;
|
126 |
} |
127 |
|
128 |
if (strcmp(qdev_alias_table[i].alias, alias) == 0) { |
129 |
return qdev_alias_table[i].typename;
|
130 |
} |
131 |
} |
132 |
|
133 |
return NULL; |
134 |
} |
135 |
|
136 |
int qdev_device_help(QemuOpts *opts)
|
137 |
{ |
138 |
const char *driver; |
139 |
Property *prop; |
140 |
ObjectClass *klass; |
141 |
|
142 |
driver = qemu_opt_get(opts, "driver");
|
143 |
if (driver && is_help_option(driver)) {
|
144 |
bool show_no_user = false; |
145 |
object_class_foreach(qdev_print_devinfo, TYPE_DEVICE, false, &show_no_user);
|
146 |
return 1; |
147 |
} |
148 |
|
149 |
if (!driver || !qemu_opt_has_help_opt(opts)) {
|
150 |
return 0; |
151 |
} |
152 |
|
153 |
klass = object_class_by_name(driver); |
154 |
if (!klass) {
|
155 |
const char *typename = find_typename_by_alias(driver); |
156 |
|
157 |
if (typename) {
|
158 |
driver = typename; |
159 |
klass = object_class_by_name(driver); |
160 |
} |
161 |
} |
162 |
|
163 |
if (!klass) {
|
164 |
return 0; |
165 |
} |
166 |
do {
|
167 |
for (prop = DEVICE_CLASS(klass)->props; prop && prop->name; prop++) {
|
168 |
/*
|
169 |
* TODO Properties without a parser are just for dirty hacks.
|
170 |
* qdev_prop_ptr is the only such PropertyInfo. It's marked
|
171 |
* for removal. This conditional should be removed along with
|
172 |
* it.
|
173 |
*/
|
174 |
if (!prop->info->set) {
|
175 |
continue; /* no way to set it, don't show */ |
176 |
} |
177 |
error_printf("%s.%s=%s\n", driver, prop->name,
|
178 |
prop->info->legacy_name ?: prop->info->name); |
179 |
} |
180 |
klass = object_class_get_parent(klass); |
181 |
} while (klass != object_class_by_name(TYPE_DEVICE));
|
182 |
return 1; |
183 |
} |
184 |
|
185 |
static Object *qdev_get_peripheral(void) |
186 |
{ |
187 |
static Object *dev;
|
188 |
|
189 |
if (dev == NULL) { |
190 |
dev = container_get(qdev_get_machine(), "/peripheral");
|
191 |
} |
192 |
|
193 |
return dev;
|
194 |
} |
195 |
|
196 |
static Object *qdev_get_peripheral_anon(void) |
197 |
{ |
198 |
static Object *dev;
|
199 |
|
200 |
if (dev == NULL) { |
201 |
dev = container_get(qdev_get_machine(), "/peripheral-anon");
|
202 |
} |
203 |
|
204 |
return dev;
|
205 |
} |
206 |
|
207 |
static void qbus_list_bus(DeviceState *dev) |
208 |
{ |
209 |
BusState *child; |
210 |
const char *sep = " "; |
211 |
|
212 |
error_printf("child busses at \"%s\":",
|
213 |
dev->id ? dev->id : object_get_typename(OBJECT(dev))); |
214 |
QLIST_FOREACH(child, &dev->child_bus, sibling) { |
215 |
error_printf("%s\"%s\"", sep, child->name);
|
216 |
sep = ", ";
|
217 |
} |
218 |
error_printf("\n");
|
219 |
} |
220 |
|
221 |
static void qbus_list_dev(BusState *bus) |
222 |
{ |
223 |
BusChild *kid; |
224 |
const char *sep = " "; |
225 |
|
226 |
error_printf("devices at \"%s\":", bus->name);
|
227 |
QTAILQ_FOREACH(kid, &bus->children, sibling) { |
228 |
DeviceState *dev = kid->child; |
229 |
error_printf("%s\"%s\"", sep, object_get_typename(OBJECT(dev)));
|
230 |
if (dev->id)
|
231 |
error_printf("/\"%s\"", dev->id);
|
232 |
sep = ", ";
|
233 |
} |
234 |
error_printf("\n");
|
235 |
} |
236 |
|
237 |
static BusState *qbus_find_bus(DeviceState *dev, char *elem) |
238 |
{ |
239 |
BusState *child; |
240 |
|
241 |
QLIST_FOREACH(child, &dev->child_bus, sibling) { |
242 |
if (strcmp(child->name, elem) == 0) { |
243 |
return child;
|
244 |
} |
245 |
} |
246 |
return NULL; |
247 |
} |
248 |
|
249 |
static DeviceState *qbus_find_dev(BusState *bus, char *elem) |
250 |
{ |
251 |
BusChild *kid; |
252 |
|
253 |
/*
|
254 |
* try to match in order:
|
255 |
* (1) instance id, if present
|
256 |
* (2) driver name
|
257 |
* (3) driver alias, if present
|
258 |
*/
|
259 |
QTAILQ_FOREACH(kid, &bus->children, sibling) { |
260 |
DeviceState *dev = kid->child; |
261 |
if (dev->id && strcmp(dev->id, elem) == 0) { |
262 |
return dev;
|
263 |
} |
264 |
} |
265 |
QTAILQ_FOREACH(kid, &bus->children, sibling) { |
266 |
DeviceState *dev = kid->child; |
267 |
if (strcmp(object_get_typename(OBJECT(dev)), elem) == 0) { |
268 |
return dev;
|
269 |
} |
270 |
} |
271 |
QTAILQ_FOREACH(kid, &bus->children, sibling) { |
272 |
DeviceState *dev = kid->child; |
273 |
DeviceClass *dc = DEVICE_GET_CLASS(dev); |
274 |
|
275 |
if (qdev_class_has_alias(dc) &&
|
276 |
strcmp(qdev_class_get_alias(dc), elem) == 0) {
|
277 |
return dev;
|
278 |
} |
279 |
} |
280 |
return NULL; |
281 |
} |
282 |
|
283 |
static BusState *qbus_find_recursive(BusState *bus, const char *name, |
284 |
const char *bus_typename) |
285 |
{ |
286 |
BusChild *kid; |
287 |
BusState *child, *ret; |
288 |
int match = 1; |
289 |
|
290 |
if (name && (strcmp(bus->name, name) != 0)) { |
291 |
match = 0;
|
292 |
} |
293 |
if (bus_typename && !object_dynamic_cast(OBJECT(bus), bus_typename)) {
|
294 |
match = 0;
|
295 |
} |
296 |
if (match) {
|
297 |
return bus;
|
298 |
} |
299 |
|
300 |
QTAILQ_FOREACH(kid, &bus->children, sibling) { |
301 |
DeviceState *dev = kid->child; |
302 |
QLIST_FOREACH(child, &dev->child_bus, sibling) { |
303 |
ret = qbus_find_recursive(child, name, bus_typename); |
304 |
if (ret) {
|
305 |
return ret;
|
306 |
} |
307 |
} |
308 |
} |
309 |
return NULL; |
310 |
} |
311 |
|
312 |
static BusState *qbus_find(const char *path) |
313 |
{ |
314 |
DeviceState *dev; |
315 |
BusState *bus; |
316 |
char elem[128]; |
317 |
int pos, len;
|
318 |
|
319 |
/* find start element */
|
320 |
if (path[0] == '/') { |
321 |
bus = sysbus_get_default(); |
322 |
pos = 0;
|
323 |
} else {
|
324 |
if (sscanf(path, "%127[^/]%n", elem, &len) != 1) { |
325 |
assert(!path[0]);
|
326 |
elem[0] = len = 0; |
327 |
} |
328 |
bus = qbus_find_recursive(sysbus_get_default(), elem, NULL);
|
329 |
if (!bus) {
|
330 |
qerror_report(QERR_BUS_NOT_FOUND, elem); |
331 |
return NULL; |
332 |
} |
333 |
pos = len; |
334 |
} |
335 |
|
336 |
for (;;) {
|
337 |
assert(path[pos] == '/' || !path[pos]);
|
338 |
while (path[pos] == '/') { |
339 |
pos++; |
340 |
} |
341 |
if (path[pos] == '\0') { |
342 |
return bus;
|
343 |
} |
344 |
|
345 |
/* find device */
|
346 |
if (sscanf(path+pos, "%127[^/]%n", elem, &len) != 1) { |
347 |
assert(0);
|
348 |
elem[0] = len = 0; |
349 |
} |
350 |
pos += len; |
351 |
dev = qbus_find_dev(bus, elem); |
352 |
if (!dev) {
|
353 |
qerror_report(QERR_DEVICE_NOT_FOUND, elem); |
354 |
if (!monitor_cur_is_qmp()) {
|
355 |
qbus_list_dev(bus); |
356 |
} |
357 |
return NULL; |
358 |
} |
359 |
|
360 |
assert(path[pos] == '/' || !path[pos]);
|
361 |
while (path[pos] == '/') { |
362 |
pos++; |
363 |
} |
364 |
if (path[pos] == '\0') { |
365 |
/* last specified element is a device. If it has exactly
|
366 |
* one child bus accept it nevertheless */
|
367 |
switch (dev->num_child_bus) {
|
368 |
case 0: |
369 |
qerror_report(QERR_DEVICE_NO_BUS, elem); |
370 |
return NULL; |
371 |
case 1: |
372 |
return QLIST_FIRST(&dev->child_bus);
|
373 |
default:
|
374 |
qerror_report(QERR_DEVICE_MULTIPLE_BUSSES, elem); |
375 |
if (!monitor_cur_is_qmp()) {
|
376 |
qbus_list_bus(dev); |
377 |
} |
378 |
return NULL; |
379 |
} |
380 |
} |
381 |
|
382 |
/* find bus */
|
383 |
if (sscanf(path+pos, "%127[^/]%n", elem, &len) != 1) { |
384 |
assert(0);
|
385 |
elem[0] = len = 0; |
386 |
} |
387 |
pos += len; |
388 |
bus = qbus_find_bus(dev, elem); |
389 |
if (!bus) {
|
390 |
qerror_report(QERR_BUS_NOT_FOUND, elem); |
391 |
if (!monitor_cur_is_qmp()) {
|
392 |
qbus_list_bus(dev); |
393 |
} |
394 |
return NULL; |
395 |
} |
396 |
} |
397 |
} |
398 |
|
399 |
DeviceState *qdev_device_add(QemuOpts *opts) |
400 |
{ |
401 |
ObjectClass *obj; |
402 |
DeviceClass *k; |
403 |
const char *driver, *path, *id; |
404 |
DeviceState *qdev; |
405 |
BusState *bus; |
406 |
|
407 |
driver = qemu_opt_get(opts, "driver");
|
408 |
if (!driver) {
|
409 |
qerror_report(QERR_MISSING_PARAMETER, "driver");
|
410 |
return NULL; |
411 |
} |
412 |
|
413 |
/* find driver */
|
414 |
obj = object_class_by_name(driver); |
415 |
if (!obj) {
|
416 |
const char *typename = find_typename_by_alias(driver); |
417 |
|
418 |
if (typename) {
|
419 |
driver = typename; |
420 |
obj = object_class_by_name(driver); |
421 |
} |
422 |
} |
423 |
|
424 |
if (!obj) {
|
425 |
qerror_report(QERR_INVALID_PARAMETER_VALUE, "driver", "device type"); |
426 |
return NULL; |
427 |
} |
428 |
|
429 |
k = DEVICE_CLASS(obj); |
430 |
|
431 |
/* find bus */
|
432 |
path = qemu_opt_get(opts, "bus");
|
433 |
if (path != NULL) { |
434 |
bus = qbus_find(path); |
435 |
if (!bus) {
|
436 |
return NULL; |
437 |
} |
438 |
if (!object_dynamic_cast(OBJECT(bus), k->bus_type)) {
|
439 |
qerror_report(QERR_BAD_BUS_FOR_DEVICE, |
440 |
driver, object_get_typename(OBJECT(bus))); |
441 |
return NULL; |
442 |
} |
443 |
} else {
|
444 |
bus = qbus_find_recursive(sysbus_get_default(), NULL, k->bus_type);
|
445 |
if (!bus) {
|
446 |
qerror_report(QERR_NO_BUS_FOR_DEVICE, |
447 |
k->bus_type, driver); |
448 |
return NULL; |
449 |
} |
450 |
} |
451 |
if (qdev_hotplug && !bus->allow_hotplug) {
|
452 |
qerror_report(QERR_BUS_NO_HOTPLUG, bus->name); |
453 |
return NULL; |
454 |
} |
455 |
|
456 |
if (!bus) {
|
457 |
bus = sysbus_get_default(); |
458 |
} |
459 |
|
460 |
/* create device, set properties */
|
461 |
qdev = DEVICE(object_new(driver)); |
462 |
qdev_set_parent_bus(qdev, bus); |
463 |
|
464 |
id = qemu_opts_id(opts); |
465 |
if (id) {
|
466 |
qdev->id = id; |
467 |
} |
468 |
if (qemu_opt_foreach(opts, set_property, qdev, 1) != 0) { |
469 |
qdev_free(qdev); |
470 |
return NULL; |
471 |
} |
472 |
if (qdev->id) {
|
473 |
object_property_add_child(qdev_get_peripheral(), qdev->id, |
474 |
OBJECT(qdev), NULL);
|
475 |
} else {
|
476 |
static int anon_count; |
477 |
gchar *name = g_strdup_printf("device[%d]", anon_count++);
|
478 |
object_property_add_child(qdev_get_peripheral_anon(), name, |
479 |
OBJECT(qdev), NULL);
|
480 |
g_free(name); |
481 |
} |
482 |
if (qdev_init(qdev) < 0) { |
483 |
qerror_report(QERR_DEVICE_INIT_FAILED, driver); |
484 |
return NULL; |
485 |
} |
486 |
qdev->opts = opts; |
487 |
return qdev;
|
488 |
} |
489 |
|
490 |
|
491 |
#define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__) |
492 |
static void qbus_print(Monitor *mon, BusState *bus, int indent); |
493 |
|
494 |
static void qdev_print_props(Monitor *mon, DeviceState *dev, Property *props, |
495 |
int indent)
|
496 |
{ |
497 |
if (!props)
|
498 |
return;
|
499 |
for (; props->name; props++) {
|
500 |
Error *err = NULL;
|
501 |
char *value;
|
502 |
char *legacy_name = g_strdup_printf("legacy-%s", props->name); |
503 |
if (object_property_get_type(OBJECT(dev), legacy_name, NULL)) { |
504 |
value = object_property_get_str(OBJECT(dev), legacy_name, &err); |
505 |
} else {
|
506 |
value = object_property_print(OBJECT(dev), props->name, &err); |
507 |
} |
508 |
g_free(legacy_name); |
509 |
|
510 |
if (err) {
|
511 |
error_free(err); |
512 |
continue;
|
513 |
} |
514 |
qdev_printf("%s = %s\n", props->name,
|
515 |
value && *value ? value : "<null>");
|
516 |
g_free(value); |
517 |
} |
518 |
} |
519 |
|
520 |
static void bus_print_dev(BusState *bus, Monitor *mon, DeviceState *dev, int indent) |
521 |
{ |
522 |
BusClass *bc = BUS_GET_CLASS(bus); |
523 |
|
524 |
if (bc->print_dev) {
|
525 |
bc->print_dev(mon, dev, indent); |
526 |
} |
527 |
} |
528 |
|
529 |
static void qdev_print(Monitor *mon, DeviceState *dev, int indent) |
530 |
{ |
531 |
ObjectClass *class; |
532 |
BusState *child; |
533 |
qdev_printf("dev: %s, id \"%s\"\n", object_get_typename(OBJECT(dev)),
|
534 |
dev->id ? dev->id : "");
|
535 |
indent += 2;
|
536 |
if (dev->num_gpio_in) {
|
537 |
qdev_printf("gpio-in %d\n", dev->num_gpio_in);
|
538 |
} |
539 |
if (dev->num_gpio_out) {
|
540 |
qdev_printf("gpio-out %d\n", dev->num_gpio_out);
|
541 |
} |
542 |
class = object_get_class(OBJECT(dev)); |
543 |
do {
|
544 |
qdev_print_props(mon, dev, DEVICE_CLASS(class)->props, indent); |
545 |
class = object_class_get_parent(class); |
546 |
} while (class != object_class_by_name(TYPE_DEVICE));
|
547 |
bus_print_dev(dev->parent_bus, mon, dev, indent); |
548 |
QLIST_FOREACH(child, &dev->child_bus, sibling) { |
549 |
qbus_print(mon, child, indent); |
550 |
} |
551 |
} |
552 |
|
553 |
static void qbus_print(Monitor *mon, BusState *bus, int indent) |
554 |
{ |
555 |
BusChild *kid; |
556 |
|
557 |
qdev_printf("bus: %s\n", bus->name);
|
558 |
indent += 2;
|
559 |
qdev_printf("type %s\n", object_get_typename(OBJECT(bus)));
|
560 |
QTAILQ_FOREACH(kid, &bus->children, sibling) { |
561 |
DeviceState *dev = kid->child; |
562 |
qdev_print(mon, dev, indent); |
563 |
} |
564 |
} |
565 |
#undef qdev_printf
|
566 |
|
567 |
void do_info_qtree(Monitor *mon)
|
568 |
{ |
569 |
if (sysbus_get_default())
|
570 |
qbus_print(mon, sysbus_get_default(), 0);
|
571 |
} |
572 |
|
573 |
void do_info_qdm(Monitor *mon)
|
574 |
{ |
575 |
object_class_foreach(qdev_print_devinfo, TYPE_DEVICE, false, NULL); |
576 |
} |
577 |
|
578 |
int do_device_add(Monitor *mon, const QDict *qdict, QObject **ret_data) |
579 |
{ |
580 |
Error *local_err = NULL;
|
581 |
QemuOpts *opts; |
582 |
|
583 |
opts = qemu_opts_from_qdict(qemu_find_opts("device"), qdict, &local_err);
|
584 |
if (error_is_set(&local_err)) {
|
585 |
qerror_report_err(local_err); |
586 |
error_free(local_err); |
587 |
return -1; |
588 |
} |
589 |
if (!monitor_cur_is_qmp() && qdev_device_help(opts)) {
|
590 |
qemu_opts_del(opts); |
591 |
return 0; |
592 |
} |
593 |
if (!qdev_device_add(opts)) {
|
594 |
qemu_opts_del(opts); |
595 |
return -1; |
596 |
} |
597 |
return 0; |
598 |
} |
599 |
|
600 |
void qmp_device_del(const char *id, Error **errp) |
601 |
{ |
602 |
DeviceState *dev; |
603 |
|
604 |
dev = qdev_find_recursive(sysbus_get_default(), id); |
605 |
if (NULL == dev) { |
606 |
error_set(errp, QERR_DEVICE_NOT_FOUND, id); |
607 |
return;
|
608 |
} |
609 |
|
610 |
qdev_unplug(dev, errp); |
611 |
} |
612 |
|
613 |
void qdev_machine_init(void) |
614 |
{ |
615 |
qdev_get_peripheral_anon(); |
616 |
qdev_get_peripheral(); |
617 |
} |