Revision 055b889f hw/virtio-serial-bus.c
b/hw/virtio-serial-bus.c | ||
---|---|---|
41 | 41 |
VirtIOSerialBus *bus; |
42 | 42 |
|
43 | 43 |
QTAILQ_HEAD(, VirtIOSerialPort) ports; |
44 |
|
|
45 |
/* bitmap for identifying active ports */ |
|
46 |
uint32_t *ports_map; |
|
47 |
|
|
44 | 48 |
struct virtio_console_config config; |
45 | 49 |
}; |
46 | 50 |
|
... | ... | |
48 | 52 |
{ |
49 | 53 |
VirtIOSerialPort *port; |
50 | 54 |
|
55 |
if (id == VIRTIO_CONSOLE_BAD_ID) { |
|
56 |
return NULL; |
|
57 |
} |
|
58 |
|
|
51 | 59 |
QTAILQ_FOREACH(port, &vser->ports, next) { |
52 | 60 |
if (port->id == id) |
53 | 61 |
return port; |
... | ... | |
208 | 216 |
size_t buffer_len; |
209 | 217 |
|
210 | 218 |
gcpkt = buf; |
211 |
port = find_port_by_id(vser, ldl_p(&gcpkt->id)); |
|
212 |
if (!port) |
|
213 |
return; |
|
214 | 219 |
|
215 | 220 |
cpkt.event = lduw_p(&gcpkt->event); |
216 | 221 |
cpkt.value = lduw_p(&gcpkt->value); |
217 | 222 |
|
223 |
port = find_port_by_id(vser, ldl_p(&gcpkt->id)); |
|
224 |
if (!port && cpkt.event != VIRTIO_CONSOLE_DEVICE_READY) |
|
225 |
return; |
|
226 |
|
|
218 | 227 |
switch(cpkt.event) { |
228 |
case VIRTIO_CONSOLE_DEVICE_READY: |
|
229 |
/* |
|
230 |
* The device is up, we can now tell the device about all the |
|
231 |
* ports we have here. |
|
232 |
*/ |
|
233 |
QTAILQ_FOREACH(port, &vser->ports, next) { |
|
234 |
send_control_event(port, VIRTIO_CONSOLE_PORT_ADD, 1); |
|
235 |
} |
|
236 |
break; |
|
237 |
|
|
219 | 238 |
case VIRTIO_CONSOLE_PORT_READY: |
220 | 239 |
/* |
221 | 240 |
* Now that we know the guest asked for the port name, we're |
... | ... | |
363 | 382 |
VirtIOSerial *s = opaque; |
364 | 383 |
VirtIOSerialPort *port; |
365 | 384 |
uint32_t nr_active_ports; |
385 |
unsigned int i; |
|
366 | 386 |
|
367 | 387 |
/* The virtio device */ |
368 | 388 |
virtio_save(&s->vdev, f); |
... | ... | |
370 | 390 |
/* The config space */ |
371 | 391 |
qemu_put_be16s(f, &s->config.cols); |
372 | 392 |
qemu_put_be16s(f, &s->config.rows); |
373 |
qemu_put_be32s(f, &s->config.nr_ports); |
|
374 | 393 |
|
375 |
/* Items in struct VirtIOSerial */ |
|
394 |
qemu_put_be32s(f, &s->config.max_nr_ports); |
|
395 |
|
|
396 |
/* The ports map */ |
|
397 |
|
|
398 |
for (i = 0; i < (s->config.max_nr_ports + 31) / 32; i++) { |
|
399 |
qemu_put_be32s(f, &s->ports_map[i]); |
|
400 |
} |
|
376 | 401 |
|
377 |
qemu_put_be32s(f, &s->bus->max_nr_ports);
|
|
402 |
/* Ports */
|
|
378 | 403 |
|
379 |
/* Do this because we might have hot-unplugged some ports */ |
|
380 | 404 |
nr_active_ports = 0; |
381 | 405 |
QTAILQ_FOREACH(port, &s->ports, next) { |
382 | 406 |
nr_active_ports++; |
... | ... | |
388 | 412 |
* Items in struct VirtIOSerialPort. |
389 | 413 |
*/ |
390 | 414 |
QTAILQ_FOREACH(port, &s->ports, next) { |
391 |
/* |
|
392 |
* We put the port number because we may not have an active |
|
393 |
* port at id 0 that's reserved for a console port, or in case |
|
394 |
* of ports that might have gotten unplugged |
|
395 |
*/ |
|
396 | 415 |
qemu_put_be32s(f, &port->id); |
397 | 416 |
qemu_put_byte(f, port->guest_connected); |
398 | 417 |
qemu_put_byte(f, port->host_connected); |
... | ... | |
403 | 422 |
{ |
404 | 423 |
VirtIOSerial *s = opaque; |
405 | 424 |
VirtIOSerialPort *port; |
406 |
uint32_t max_nr_ports, nr_active_ports, nr_ports; |
|
425 |
size_t ports_map_size; |
|
426 |
uint32_t max_nr_ports, nr_active_ports, *ports_map; |
|
407 | 427 |
unsigned int i; |
408 | 428 |
|
409 | 429 |
if (version_id > 2) { |
... | ... | |
420 | 440 |
/* The config space */ |
421 | 441 |
qemu_get_be16s(f, &s->config.cols); |
422 | 442 |
qemu_get_be16s(f, &s->config.rows); |
423 |
nr_ports = qemu_get_be32(f); |
|
424 | 443 |
|
425 |
if (nr_ports != s->config.nr_ports) { |
|
426 |
/* |
|
427 |
* Source hot-plugged/unplugged ports and we don't have all of |
|
428 |
* them here. |
|
429 |
* |
|
430 |
* Note: This condition cannot check for all hotplug/unplug |
|
431 |
* events: eg, if one port was hot-plugged and one was |
|
432 |
* unplugged, the nr_ports remains the same but the port id's |
|
433 |
* would have changed and we won't catch it here. A later |
|
434 |
* check for !find_port_by_id() will confirm if this happened. |
|
435 |
*/ |
|
444 |
qemu_get_be32s(f, &max_nr_ports); |
|
445 |
if (max_nr_ports > s->config.max_nr_ports) { |
|
446 |
/* Source could have had more ports than us. Fail migration. */ |
|
436 | 447 |
return -EINVAL; |
437 | 448 |
} |
438 | 449 |
|
439 |
/* Items in struct VirtIOSerial */ |
|
450 |
ports_map_size = sizeof(uint32_t) * (max_nr_ports + 31) / 32; |
|
451 |
ports_map = qemu_malloc(ports_map_size); |
|
440 | 452 |
|
441 |
qemu_get_be32s(f, &max_nr_ports); |
|
442 |
if (max_nr_ports > s->bus->max_nr_ports) { |
|
443 |
/* Source could have more ports than us. Fail migration. */ |
|
444 |
return -EINVAL; |
|
453 |
for (i = 0; i < (max_nr_ports + 31) / 32; i++) { |
|
454 |
qemu_get_be32s(f, &ports_map[i]); |
|
455 |
|
|
456 |
if (ports_map[i] != s->ports_map[i]) { |
|
457 |
/* |
|
458 |
* Ports active on source and destination don't |
|
459 |
* match. Fail migration. |
|
460 |
*/ |
|
461 |
qemu_free(ports_map); |
|
462 |
return -EINVAL; |
|
463 |
} |
|
445 | 464 |
} |
465 |
qemu_free(ports_map); |
|
446 | 466 |
|
447 | 467 |
qemu_get_be32s(f, &nr_active_ports); |
448 | 468 |
|
... | ... | |
453 | 473 |
|
454 | 474 |
id = qemu_get_be32(f); |
455 | 475 |
port = find_port_by_id(s, id); |
456 |
if (!port) { |
|
457 |
/* |
|
458 |
* The requested port was hot-plugged on the source but we |
|
459 |
* don't have it |
|
460 |
*/ |
|
461 |
return -EINVAL; |
|
462 |
} |
|
463 | 476 |
|
464 | 477 |
port->guest_connected = qemu_get_byte(f); |
465 | 478 |
host_connected = qemu_get_byte(f); |
... | ... | |
472 | 485 |
port->host_connected); |
473 | 486 |
} |
474 | 487 |
} |
475 |
|
|
476 | 488 |
return 0; |
477 | 489 |
} |
478 | 490 |
|
... | ... | |
507 | 519 |
indent, "", port->host_connected); |
508 | 520 |
} |
509 | 521 |
|
522 |
/* This function is only used if a port id is not provided by the user */ |
|
523 |
static uint32_t find_free_port_id(VirtIOSerial *vser) |
|
524 |
{ |
|
525 |
unsigned int i; |
|
526 |
|
|
527 |
for (i = 0; i < (vser->config.max_nr_ports + 31) / 32; i++) { |
|
528 |
uint32_t map, bit; |
|
529 |
|
|
530 |
map = vser->ports_map[i]; |
|
531 |
bit = ffs(~map); |
|
532 |
if (bit) { |
|
533 |
return (bit - 1) + i * 32; |
|
534 |
} |
|
535 |
} |
|
536 |
return VIRTIO_CONSOLE_BAD_ID; |
|
537 |
} |
|
538 |
|
|
539 |
static void mark_port_added(VirtIOSerial *vser, uint32_t port_id) |
|
540 |
{ |
|
541 |
unsigned int i; |
|
542 |
|
|
543 |
i = port_id / 32; |
|
544 |
vser->ports_map[i] |= 1U << (port_id % 32); |
|
545 |
} |
|
546 |
|
|
547 |
static void add_port(VirtIOSerial *vser, uint32_t port_id) |
|
548 |
{ |
|
549 |
mark_port_added(vser, port_id); |
|
550 |
|
|
551 |
send_control_event(find_port_by_id(vser, port_id), |
|
552 |
VIRTIO_CONSOLE_PORT_ADD, 1); |
|
553 |
} |
|
554 |
|
|
555 |
static void remove_port(VirtIOSerial *vser, uint32_t port_id) |
|
556 |
{ |
|
557 |
unsigned int i; |
|
558 |
|
|
559 |
i = port_id / 32; |
|
560 |
vser->ports_map[i] &= ~(1U << (port_id % 32)); |
|
561 |
|
|
562 |
send_control_event(find_port_by_id(vser, port_id), |
|
563 |
VIRTIO_CONSOLE_PORT_REMOVE, 1); |
|
564 |
} |
|
565 |
|
|
510 | 566 |
static int virtser_port_qdev_init(DeviceState *qdev, DeviceInfo *base) |
511 | 567 |
{ |
512 | 568 |
VirtIOSerialDevice *dev = DO_UPCAST(VirtIOSerialDevice, qdev, qdev); |
... | ... | |
525 | 581 |
*/ |
526 | 582 |
plugging_port0 = port->is_console && !find_port_by_id(port->vser, 0); |
527 | 583 |
|
528 |
if (port->vser->config.nr_ports == bus->max_nr_ports && !plugging_port0) { |
|
529 |
error_report("virtio-serial-bus: Maximum device limit reached"); |
|
584 |
if (find_port_by_id(port->vser, port->id)) { |
|
585 |
error_report("virtio-serial-bus: A port already exists at id %u\n", |
|
586 |
port->id); |
|
530 | 587 |
return -1; |
531 | 588 |
} |
532 |
dev->info = info; |
|
533 | 589 |
|
590 |
if (port->id == VIRTIO_CONSOLE_BAD_ID) { |
|
591 |
if (plugging_port0) { |
|
592 |
port->id = 0; |
|
593 |
} else { |
|
594 |
port->id = find_free_port_id(port->vser); |
|
595 |
if (port->id == VIRTIO_CONSOLE_BAD_ID) { |
|
596 |
error_report("virtio-serial-bus: Maximum port limit for this device reached\n"); |
|
597 |
return -1; |
|
598 |
} |
|
599 |
} |
|
600 |
} |
|
601 |
|
|
602 |
if (port->id >= port->vser->config.max_nr_ports) { |
|
603 |
error_report("virtio-serial-bus: Out-of-range port id specified, max. allowed: %u\n", |
|
604 |
port->vser->config.max_nr_ports - 1); |
|
605 |
return -1; |
|
606 |
} |
|
607 |
|
|
608 |
dev->info = info; |
|
534 | 609 |
ret = info->init(dev); |
535 | 610 |
if (ret) { |
536 | 611 |
return ret; |
537 | 612 |
} |
538 | 613 |
|
539 |
port->id = plugging_port0 ? 0 : port->vser->config.nr_ports++; |
|
540 |
|
|
541 | 614 |
if (!use_multiport(port->vser)) { |
542 | 615 |
/* |
543 | 616 |
* Allow writes to guest in this case; we have no way of |
... | ... | |
550 | 623 |
port->ivq = port->vser->ivqs[port->id]; |
551 | 624 |
port->ovq = port->vser->ovqs[port->id]; |
552 | 625 |
|
626 |
add_port(port->vser, port->id); |
|
627 |
|
|
553 | 628 |
/* Send an update to the guest about this new port added */ |
554 | 629 |
virtio_notify_config(&port->vser->vdev); |
555 | 630 |
|
... | ... | |
562 | 637 |
VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); |
563 | 638 |
VirtIOSerial *vser = port->vser; |
564 | 639 |
|
565 |
send_control_event(port, VIRTIO_CONSOLE_PORT_REMOVE, 1);
|
|
640 |
remove_port(port->vser, port->id);
|
|
566 | 641 |
|
567 |
/* |
|
568 |
* Don't decrement nr_ports here; thus we keep a linearly |
|
569 |
* increasing port id. Not utilising an id again saves us a couple |
|
570 |
* of complications: |
|
571 |
* |
|
572 |
* - Not having to bother about sending the port id to the guest |
|
573 |
* kernel on hotplug or on addition of new ports; the guest can |
|
574 |
* also linearly increment the port number. This is preferable |
|
575 |
* because the config space won't have the need to store a |
|
576 |
* ports_map. |
|
577 |
* |
|
578 |
* - Extra state to be stored for all the "holes" that got created |
|
579 |
* so that we keep filling in the ids from the least available |
|
580 |
* index. |
|
581 |
* |
|
582 |
* When such a functionality is desired, a control message to add |
|
583 |
* a port can be introduced. |
|
584 |
*/ |
|
585 | 642 |
QTAILQ_REMOVE(&vser->ports, port, next); |
586 | 643 |
|
587 | 644 |
if (port->info->exit) |
... | ... | |
641 | 698 |
} |
642 | 699 |
|
643 | 700 |
vser->config.max_nr_ports = max_nr_ports; |
701 |
vser->ports_map = qemu_mallocz((max_nr_ports + 31) / 32); |
|
644 | 702 |
/* |
645 | 703 |
* Reserve location 0 for a console port for backward compat |
646 | 704 |
* (old kernel, new qemu) |
647 | 705 |
*/ |
648 |
vser->config.nr_ports = 1;
|
|
706 |
mark_port_added(vser, 0);
|
|
649 | 707 |
|
650 | 708 |
vser->vdev.get_features = get_features; |
651 | 709 |
vser->vdev.get_config = get_config; |
Also available in: Unified diff