Merge remote-tracking branch 'luiz/queue/qmp' into staging
# By Lei Li (3) and others # Via Luiz Capitulino * luiz/queue/qmp: QAPI: Introduce memchar-read QMP command QAPI: Introduce memchar-write QMP command qemu-char: Add new char backend CirMemCharDriver docs: document virtio-balloon stats balloon: re-enable balloon stats balloon: drop old stats code & API block: Monitor command commit neglects to report some errors
This commit is contained in:
commit
6cebf7afac
14
blockdev.c
14
blockdev.c
@ -642,21 +642,17 @@ void do_commit(Monitor *mon, const QDict *qdict)
|
||||
|
||||
if (!strcmp(device, "all")) {
|
||||
ret = bdrv_commit_all();
|
||||
if (ret == -EBUSY) {
|
||||
qerror_report(QERR_DEVICE_IN_USE, device);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
bs = bdrv_find(device);
|
||||
if (!bs) {
|
||||
qerror_report(QERR_DEVICE_NOT_FOUND, device);
|
||||
monitor_printf(mon, "Device '%s' not found\n", device);
|
||||
return;
|
||||
}
|
||||
ret = bdrv_commit(bs);
|
||||
if (ret == -EBUSY) {
|
||||
qerror_report(QERR_DEVICE_IN_USE, device);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
monitor_printf(mon, "'commit' error for '%s': %s\n", device,
|
||||
strerror(-ret));
|
||||
}
|
||||
}
|
||||
|
||||
|
104
docs/virtio-balloon-stats.txt
Normal file
104
docs/virtio-balloon-stats.txt
Normal file
@ -0,0 +1,104 @@
|
||||
virtio balloon memory statistics
|
||||
================================
|
||||
|
||||
The virtio balloon driver supports guest memory statistics reporting. These
|
||||
statistics are available to QEMU users as QOM (QEMU Object Model) device
|
||||
properties via a polling mechanism.
|
||||
|
||||
Before querying the available stats, clients first have to enable polling.
|
||||
This is done by writing a time interval value (in seconds) to the
|
||||
guest-stats-polling-interval property. This value can be:
|
||||
|
||||
> 0 enables polling in the specified interval. If polling is already
|
||||
enabled, the polling time interval is changed to the new value
|
||||
|
||||
0 disables polling. Previous polled statistics are still valid and
|
||||
can be queried.
|
||||
|
||||
Once polling is enabled, the virtio-balloon device in QEMU will start
|
||||
polling the guest's balloon driver for new stats in the specified time
|
||||
interval.
|
||||
|
||||
To retrieve those stats, clients have to query the guest-stats property,
|
||||
which will return a dictionary containing:
|
||||
|
||||
o A key named 'stats', containing all available stats. If the guest
|
||||
doesn't support a particular stat, or if it couldn't be retrieved,
|
||||
its value will be -1. Currently, the following stats are supported:
|
||||
|
||||
- stat-swap-in
|
||||
- stat-swap-out
|
||||
- stat-major-faults
|
||||
- stat-minor-faults
|
||||
- stat-free-memory
|
||||
- stat-total-memory
|
||||
|
||||
o A key named last-update, which contains the last stats update
|
||||
timestamp in seconds. Since this timestamp is generated by the host,
|
||||
a buggy guest can't influence its value
|
||||
|
||||
It's also important to note the following:
|
||||
|
||||
- Previously polled statistics remain available even if the polling is
|
||||
later disabled
|
||||
|
||||
- As noted above, if a guest doesn't support a particular stat its value
|
||||
will always be -1. However, it's also possible that a guest temporarily
|
||||
couldn't update one or even all stats. If this happens, just wait for
|
||||
the next update
|
||||
|
||||
- Polling can be enabled even if the guest doesn't have stats support
|
||||
or the balloon driver wasn't loaded in the guest. If this is the case
|
||||
and stats are queried, an error will be returned
|
||||
|
||||
- The polling timer is only re-armed when the guest responds to the
|
||||
statistics request. This means that if a (buggy) guest doesn't ever
|
||||
respond to the request the timer will never be re-armed, which has
|
||||
the same effect as disabling polling
|
||||
|
||||
Here are a few examples. QEMU is started with '-balloon virtio', which
|
||||
generates '/machine/peripheral-anon/device[1]' as the QOM path for the
|
||||
balloon device.
|
||||
|
||||
Enable polling with 2 seconds interval:
|
||||
|
||||
{ "execute": "qom-set",
|
||||
"arguments": { "path": "/machine/peripheral-anon/device[1]",
|
||||
"property": "guest-stats-polling-interval", "value": 2 } }
|
||||
|
||||
{ "return": {} }
|
||||
|
||||
Change polling to 10 seconds:
|
||||
|
||||
{ "execute": "qom-set",
|
||||
"arguments": { "path": "/machine/peripheral-anon/device[1]",
|
||||
"property": "guest-stats-polling-interval", "value": 10 } }
|
||||
|
||||
{ "return": {} }
|
||||
|
||||
Get stats:
|
||||
|
||||
{ "execute": "qom-get",
|
||||
"arguments": { "path": "/machine/peripheral-anon/device[1]",
|
||||
"property": "guest-stats" } }
|
||||
{
|
||||
"return": {
|
||||
"stats": {
|
||||
"stat-swap-out": 0,
|
||||
"stat-free-memory": 844943360,
|
||||
"stat-minor-faults": 219028,
|
||||
"stat-major-faults": 235,
|
||||
"stat-total-memory": 1044406272,
|
||||
"stat-swap-in": 0
|
||||
},
|
||||
"last-update": 1358529861
|
||||
}
|
||||
}
|
||||
|
||||
Disable polling:
|
||||
|
||||
{ "execute": "qom-set",
|
||||
"arguments": { "path": "/machine/peripheral-anon/device[1]",
|
||||
"property": "stats-polling-interval", "value": 0 } }
|
||||
|
||||
{ "return": {} }
|
@ -837,6 +837,45 @@ STEXI
|
||||
@item nmi @var{cpu}
|
||||
@findex nmi
|
||||
Inject an NMI on the given CPU (x86 only).
|
||||
|
||||
ETEXI
|
||||
|
||||
{
|
||||
.name = "memchar_write",
|
||||
.args_type = "device:s,data:s",
|
||||
.params = "device data",
|
||||
.help = "Provide writing interface for CirMemCharDriver. Write"
|
||||
"'data' to it.",
|
||||
.mhandler.cmd = hmp_memchar_write,
|
||||
},
|
||||
|
||||
STEXI
|
||||
@item memchar_write @var{device} @var{data}
|
||||
@findex memchar_write
|
||||
Provide writing interface for CirMemCharDriver. Write @var{data}
|
||||
to char device 'memory'.
|
||||
|
||||
ETEXI
|
||||
|
||||
{
|
||||
.name = "memchar_read",
|
||||
.args_type = "device:s,size:i",
|
||||
.params = "device size",
|
||||
.help = "Provide read interface for CirMemCharDriver. Read from"
|
||||
"it and return the data with size.",
|
||||
.mhandler.cmd = hmp_memchar_read,
|
||||
},
|
||||
|
||||
STEXI
|
||||
@item memchar_read @var{device}
|
||||
@findex memchar_read
|
||||
Provide read interface for CirMemCharDriver. Read from char device
|
||||
'memory' and return the data.
|
||||
|
||||
@var{size} is the size of data want to read from. Refer to unencoded
|
||||
size of the raw data, would adjust to the init size of the memchar
|
||||
if the requested size is larger than it.
|
||||
|
||||
ETEXI
|
||||
|
||||
{
|
||||
|
58
hmp.c
58
hmp.c
@ -465,29 +465,7 @@ void hmp_info_balloon(Monitor *mon, const QDict *qdict)
|
||||
return;
|
||||
}
|
||||
|
||||
monitor_printf(mon, "balloon: actual=%" PRId64, info->actual >> 20);
|
||||
if (info->has_mem_swapped_in) {
|
||||
monitor_printf(mon, " mem_swapped_in=%" PRId64, info->mem_swapped_in);
|
||||
}
|
||||
if (info->has_mem_swapped_out) {
|
||||
monitor_printf(mon, " mem_swapped_out=%" PRId64, info->mem_swapped_out);
|
||||
}
|
||||
if (info->has_major_page_faults) {
|
||||
monitor_printf(mon, " major_page_faults=%" PRId64,
|
||||
info->major_page_faults);
|
||||
}
|
||||
if (info->has_minor_page_faults) {
|
||||
monitor_printf(mon, " minor_page_faults=%" PRId64,
|
||||
info->minor_page_faults);
|
||||
}
|
||||
if (info->has_free_mem) {
|
||||
monitor_printf(mon, " free_mem=%" PRId64, info->free_mem);
|
||||
}
|
||||
if (info->has_total_mem) {
|
||||
monitor_printf(mon, " total_mem=%" PRId64, info->total_mem);
|
||||
}
|
||||
|
||||
monitor_printf(mon, "\n");
|
||||
monitor_printf(mon, "balloon: actual=%" PRId64 "\n", info->actual >> 20);
|
||||
|
||||
qapi_free_BalloonInfo(info);
|
||||
}
|
||||
@ -684,6 +662,40 @@ void hmp_pmemsave(Monitor *mon, const QDict *qdict)
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
void hmp_memchar_write(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
uint32_t size;
|
||||
const char *chardev = qdict_get_str(qdict, "device");
|
||||
const char *data = qdict_get_str(qdict, "data");
|
||||
Error *errp = NULL;
|
||||
|
||||
size = strlen(data);
|
||||
qmp_memchar_write(chardev, size, data, false, 0, &errp);
|
||||
|
||||
hmp_handle_error(mon, &errp);
|
||||
}
|
||||
|
||||
void hmp_memchar_read(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
uint32_t size = qdict_get_int(qdict, "size");
|
||||
const char *chardev = qdict_get_str(qdict, "device");
|
||||
MemCharRead *meminfo;
|
||||
Error *errp = NULL;
|
||||
|
||||
meminfo = qmp_memchar_read(chardev, size, false, 0, &errp);
|
||||
if (errp) {
|
||||
monitor_printf(mon, "%s\n", error_get_pretty(errp));
|
||||
error_free(errp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (meminfo->count > 0) {
|
||||
monitor_printf(mon, "%s\n", meminfo->data);
|
||||
}
|
||||
|
||||
qapi_free_MemCharRead(meminfo);
|
||||
}
|
||||
|
||||
static void hmp_cont_cb(void *opaque, int err)
|
||||
{
|
||||
if (!err) {
|
||||
|
2
hmp.h
2
hmp.h
@ -43,6 +43,8 @@ void hmp_system_powerdown(Monitor *mon, const QDict *qdict);
|
||||
void hmp_cpu(Monitor *mon, const QDict *qdict);
|
||||
void hmp_memsave(Monitor *mon, const QDict *qdict);
|
||||
void hmp_pmemsave(Monitor *mon, const QDict *qdict);
|
||||
void hmp_memchar_write(Monitor *mon, const QDict *qdict);
|
||||
void hmp_memchar_read(Monitor *mon, const QDict *qdict);
|
||||
void hmp_cont(Monitor *mon, const QDict *qdict);
|
||||
void hmp_system_wakeup(Monitor *mon, const QDict *qdict);
|
||||
void hmp_inject_nmi(Monitor *mon, const QDict *qdict);
|
||||
|
@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
#include "qemu/iov.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu-common.h"
|
||||
#include "virtio.h"
|
||||
#include "pc.h"
|
||||
@ -22,6 +23,7 @@
|
||||
#include "virtio-balloon.h"
|
||||
#include "sysemu/kvm.h"
|
||||
#include "exec/address-spaces.h"
|
||||
#include "qapi/visitor.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <sys/mman.h>
|
||||
@ -36,6 +38,9 @@ typedef struct VirtIOBalloon
|
||||
uint64_t stats[VIRTIO_BALLOON_S_NR];
|
||||
VirtQueueElement stats_vq_elem;
|
||||
size_t stats_vq_offset;
|
||||
QEMUTimer *stats_timer;
|
||||
int64_t stats_last_update;
|
||||
int64_t stats_poll_interval;
|
||||
DeviceState *qdev;
|
||||
} VirtIOBalloon;
|
||||
|
||||
@ -53,6 +58,16 @@ static void balloon_page(void *addr, int deflate)
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char *balloon_stat_names[] = {
|
||||
[VIRTIO_BALLOON_S_SWAP_IN] = "stat-swap-in",
|
||||
[VIRTIO_BALLOON_S_SWAP_OUT] = "stat-swap-out",
|
||||
[VIRTIO_BALLOON_S_MAJFLT] = "stat-major-faults",
|
||||
[VIRTIO_BALLOON_S_MINFLT] = "stat-minor-faults",
|
||||
[VIRTIO_BALLOON_S_MEMFREE] = "stat-free-memory",
|
||||
[VIRTIO_BALLOON_S_MEMTOT] = "stat-total-memory",
|
||||
[VIRTIO_BALLOON_S_NR] = NULL
|
||||
};
|
||||
|
||||
/*
|
||||
* reset_stats - Mark all items in the stats array as unset
|
||||
*
|
||||
@ -67,6 +82,118 @@ static inline void reset_stats(VirtIOBalloon *dev)
|
||||
for (i = 0; i < VIRTIO_BALLOON_S_NR; dev->stats[i++] = -1);
|
||||
}
|
||||
|
||||
static bool balloon_stats_supported(const VirtIOBalloon *s)
|
||||
{
|
||||
return s->vdev.guest_features & (1 << VIRTIO_BALLOON_F_STATS_VQ);
|
||||
}
|
||||
|
||||
static bool balloon_stats_enabled(const VirtIOBalloon *s)
|
||||
{
|
||||
return s->stats_poll_interval > 0;
|
||||
}
|
||||
|
||||
static void balloon_stats_destroy_timer(VirtIOBalloon *s)
|
||||
{
|
||||
if (balloon_stats_enabled(s)) {
|
||||
qemu_del_timer(s->stats_timer);
|
||||
qemu_free_timer(s->stats_timer);
|
||||
s->stats_timer = NULL;
|
||||
s->stats_poll_interval = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void balloon_stats_change_timer(VirtIOBalloon *s, int secs)
|
||||
{
|
||||
qemu_mod_timer(s->stats_timer, qemu_get_clock_ms(vm_clock) + secs * 1000);
|
||||
}
|
||||
|
||||
static void balloon_stats_poll_cb(void *opaque)
|
||||
{
|
||||
VirtIOBalloon *s = opaque;
|
||||
|
||||
if (!balloon_stats_supported(s)) {
|
||||
/* re-schedule */
|
||||
balloon_stats_change_timer(s, s->stats_poll_interval);
|
||||
return;
|
||||
}
|
||||
|
||||
virtqueue_push(s->svq, &s->stats_vq_elem, s->stats_vq_offset);
|
||||
virtio_notify(&s->vdev, s->svq);
|
||||
}
|
||||
|
||||
static void balloon_stats_get_all(Object *obj, struct Visitor *v,
|
||||
void *opaque, const char *name, Error **errp)
|
||||
{
|
||||
VirtIOBalloon *s = opaque;
|
||||
int i;
|
||||
|
||||
if (!s->stats_last_update) {
|
||||
error_setg(errp, "guest hasn't updated any stats yet");
|
||||
return;
|
||||
}
|
||||
|
||||
visit_start_struct(v, NULL, "guest-stats", name, 0, errp);
|
||||
visit_type_int(v, &s->stats_last_update, "last-update", errp);
|
||||
|
||||
visit_start_struct(v, NULL, NULL, "stats", 0, errp);
|
||||
for (i = 0; i < VIRTIO_BALLOON_S_NR; i++) {
|
||||
visit_type_int64(v, (int64_t *) &s->stats[i], balloon_stat_names[i],
|
||||
errp);
|
||||
}
|
||||
visit_end_struct(v, errp);
|
||||
|
||||
visit_end_struct(v, errp);
|
||||
}
|
||||
|
||||
static void balloon_stats_get_poll_interval(Object *obj, struct Visitor *v,
|
||||
void *opaque, const char *name,
|
||||
Error **errp)
|
||||
{
|
||||
VirtIOBalloon *s = opaque;
|
||||
visit_type_int(v, &s->stats_poll_interval, name, errp);
|
||||
}
|
||||
|
||||
static void balloon_stats_set_poll_interval(Object *obj, struct Visitor *v,
|
||||
void *opaque, const char *name,
|
||||
Error **errp)
|
||||
{
|
||||
VirtIOBalloon *s = opaque;
|
||||
int64_t value;
|
||||
|
||||
visit_type_int(v, &value, name, errp);
|
||||
if (error_is_set(errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value < 0) {
|
||||
error_setg(errp, "timer value must be greater than zero");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == s->stats_poll_interval) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == 0) {
|
||||
/* timer=0 disables the timer */
|
||||
balloon_stats_destroy_timer(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (balloon_stats_enabled(s)) {
|
||||
/* timer interval change */
|
||||
s->stats_poll_interval = value;
|
||||
balloon_stats_change_timer(s, value);
|
||||
return;
|
||||
}
|
||||
|
||||
/* create a new timer */
|
||||
g_assert(s->stats_timer == NULL);
|
||||
s->stats_timer = qemu_new_timer_ms(vm_clock, balloon_stats_poll_cb, s);
|
||||
s->stats_poll_interval = value;
|
||||
balloon_stats_change_timer(s, 0);
|
||||
}
|
||||
|
||||
static void virtio_balloon_handle_output(VirtIODevice *vdev, VirtQueue *vq)
|
||||
{
|
||||
VirtIOBalloon *s = to_virtio_balloon(vdev);
|
||||
@ -107,9 +234,10 @@ static void virtio_balloon_receive_stats(VirtIODevice *vdev, VirtQueue *vq)
|
||||
VirtQueueElement *elem = &s->stats_vq_elem;
|
||||
VirtIOBalloonStat stat;
|
||||
size_t offset = 0;
|
||||
qemu_timeval tv;
|
||||
|
||||
if (!virtqueue_pop(vq, elem)) {
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Initialize the stats to get rid of any stale values. This is only
|
||||
@ -128,6 +256,18 @@ static void virtio_balloon_receive_stats(VirtIODevice *vdev, VirtQueue *vq)
|
||||
s->stats[tag] = val;
|
||||
}
|
||||
s->stats_vq_offset = offset;
|
||||
|
||||
if (qemu_gettimeofday(&tv) < 0) {
|
||||
fprintf(stderr, "warning: %s: failed to get time of day\n", __func__);
|
||||
goto out;
|
||||
}
|
||||
|
||||
s->stats_last_update = tv.tv_sec;
|
||||
|
||||
out:
|
||||
if (balloon_stats_enabled(s)) {
|
||||
balloon_stats_change_timer(s, s->stats_poll_interval);
|
||||
}
|
||||
}
|
||||
|
||||
static void virtio_balloon_get_config(VirtIODevice *vdev, uint8_t *config_data)
|
||||
@ -164,28 +304,6 @@ static uint32_t virtio_balloon_get_features(VirtIODevice *vdev, uint32_t f)
|
||||
static void virtio_balloon_stat(void *opaque, BalloonInfo *info)
|
||||
{
|
||||
VirtIOBalloon *dev = opaque;
|
||||
|
||||
#if 0
|
||||
/* Disable guest-provided stats for now. For more details please check:
|
||||
* https://bugzilla.redhat.com/show_bug.cgi?id=623903
|
||||
*
|
||||
* If you do enable it (which is probably not going to happen as we
|
||||
* need a new command for it), remember that you also need to fill the
|
||||
* appropriate members of the BalloonInfo structure so that the stats
|
||||
* are returned to the client.
|
||||
*/
|
||||
if (dev->vdev.guest_features & (1 << VIRTIO_BALLOON_F_STATS_VQ)) {
|
||||
virtqueue_push(dev->svq, &dev->stats_vq_elem, dev->stats_vq_offset);
|
||||
virtio_notify(&dev->vdev, dev->svq);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Stats are not supported. Clear out any stale values that might
|
||||
* have been set by a more featureful guest kernel.
|
||||
*/
|
||||
reset_stats(dev);
|
||||
|
||||
info->actual = ram_size - ((uint64_t) dev->actual <<
|
||||
VIRTIO_BALLOON_PFN_SHIFT);
|
||||
}
|
||||
@ -255,12 +373,18 @@ VirtIODevice *virtio_balloon_init(DeviceState *dev)
|
||||
s->dvq = virtio_add_queue(&s->vdev, 128, virtio_balloon_handle_output);
|
||||
s->svq = virtio_add_queue(&s->vdev, 128, virtio_balloon_receive_stats);
|
||||
|
||||
reset_stats(s);
|
||||
|
||||
s->qdev = dev;
|
||||
register_savevm(dev, "virtio-balloon", -1, 1,
|
||||
virtio_balloon_save, virtio_balloon_load, s);
|
||||
|
||||
object_property_add(OBJECT(dev), "guest-stats", "guest statistics",
|
||||
balloon_stats_get_all, NULL, NULL, s, NULL);
|
||||
|
||||
object_property_add(OBJECT(dev), "guest-stats-polling-interval", "int",
|
||||
balloon_stats_get_poll_interval,
|
||||
balloon_stats_set_poll_interval,
|
||||
NULL, s, NULL);
|
||||
|
||||
return &s->vdev;
|
||||
}
|
||||
|
||||
@ -268,6 +392,7 @@ void virtio_balloon_exit(VirtIODevice *vdev)
|
||||
{
|
||||
VirtIOBalloon *s = DO_UPCAST(VirtIOBalloon, vdev, vdev);
|
||||
|
||||
balloon_stats_destroy_timer(s);
|
||||
qemu_remove_balloon_handler(s);
|
||||
unregister_savevm(s->qdev, "virtio-balloon", s);
|
||||
virtio_cleanup(vdev);
|
||||
|
@ -324,6 +324,80 @@
|
||||
##
|
||||
{ 'command': 'query-chardev', 'returns': ['ChardevInfo'] }
|
||||
|
||||
##
|
||||
# @DataFormat:
|
||||
#
|
||||
# An enumeration of data format.
|
||||
#
|
||||
# @utf8: The data format is 'utf8'.
|
||||
#
|
||||
# @base64: The data format is 'base64'.
|
||||
#
|
||||
# Since: 1.4
|
||||
##
|
||||
{ 'enum': 'DataFormat'
|
||||
'data': [ 'utf8', 'base64' ] }
|
||||
|
||||
##
|
||||
# @memchar-write:
|
||||
#
|
||||
# Provide writing interface for memchardev. Write data to char
|
||||
# device 'memory'.
|
||||
#
|
||||
# @device: the name of the memory char device.
|
||||
#
|
||||
# @size: the size to write in bytes.
|
||||
#
|
||||
# @data: the source data write to memchar.
|
||||
#
|
||||
# @format: #optional the format of the data write to chardev 'memory',
|
||||
# by default is 'utf8'.
|
||||
#
|
||||
# Returns: Nothing on success
|
||||
# If @device is not a valid char device, DeviceNotFound
|
||||
#
|
||||
# Since: 1.4
|
||||
##
|
||||
{ 'command': 'memchar-write',
|
||||
'data': {'device': 'str', 'size': 'int', 'data': 'str',
|
||||
'*format': 'DataFormat'} }
|
||||
|
||||
##
|
||||
# @MemCharRead
|
||||
#
|
||||
# Result of QMP command memchar-read.
|
||||
#
|
||||
# @data: The data read from memchar as string.
|
||||
#
|
||||
# @count: The numbers of bytes read from.
|
||||
#
|
||||
# Since: 1.4
|
||||
##
|
||||
{ 'type': 'MemCharRead',
|
||||
'data': { 'data': 'str', 'count': 'int' } }
|
||||
|
||||
##
|
||||
# @memchar-read:
|
||||
#
|
||||
# Provide read interface for memchardev. Read from the char
|
||||
# device 'memory' and return the data.
|
||||
#
|
||||
# @device: the name of the memory char device.
|
||||
#
|
||||
# @size: the size to read in bytes.
|
||||
#
|
||||
# @format: #optional the format of the data want to read from
|
||||
# memchardev, by default is 'utf8'.
|
||||
#
|
||||
# Returns: @MemCharRead
|
||||
# If @device is not a valid memchr device, DeviceNotFound
|
||||
#
|
||||
# Since: 1.4
|
||||
##
|
||||
{ 'command': 'memchar-read',
|
||||
'data': {'device': 'str', 'size': 'int', '*format': 'DataFormat'},
|
||||
'returns': 'MemCharRead' }
|
||||
|
||||
##
|
||||
# @CommandInfo:
|
||||
#
|
||||
@ -977,28 +1051,10 @@
|
||||
#
|
||||
# @actual: the number of bytes the balloon currently contains
|
||||
#
|
||||
# @mem_swapped_in: #optional number of pages swapped in within the guest
|
||||
#
|
||||
# @mem_swapped_out: #optional number of pages swapped out within the guest
|
||||
#
|
||||
# @major_page_faults: #optional number of major page faults within the guest
|
||||
#
|
||||
# @minor_page_faults: #optional number of minor page faults within the guest
|
||||
#
|
||||
# @free_mem: #optional amount of memory (in bytes) free in the guest
|
||||
#
|
||||
# @total_mem: #optional amount of memory (in bytes) visible to the guest
|
||||
#
|
||||
# Since: 0.14.0
|
||||
#
|
||||
# Notes: all current versions of QEMU do not fill out optional information in
|
||||
# this structure.
|
||||
##
|
||||
{ 'type': 'BalloonInfo',
|
||||
'data': {'actual': 'int', '*mem_swapped_in': 'int',
|
||||
'*mem_swapped_out': 'int', '*major_page_faults': 'int',
|
||||
'*minor_page_faults': 'int', '*free_mem': 'int',
|
||||
'*total_mem': 'int'} }
|
||||
{ 'type': 'BalloonInfo', 'data': {'actual': 'int' } }
|
||||
|
||||
##
|
||||
# @query-balloon:
|
||||
|
203
qemu-char.c
203
qemu-char.c
@ -98,6 +98,7 @@
|
||||
#include "ui/qemu-spice.h"
|
||||
|
||||
#define READ_BUF_LEN 4096
|
||||
#define CBUFF_SIZE 65536
|
||||
|
||||
/***********************************************************/
|
||||
/* character device */
|
||||
@ -2643,6 +2644,199 @@ size_t qemu_chr_mem_osize(const CharDriverState *chr)
|
||||
return d->outbuf_size;
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/*CircularMemory chardev*/
|
||||
|
||||
typedef struct {
|
||||
size_t size;
|
||||
size_t prod;
|
||||
size_t cons;
|
||||
uint8_t *cbuf;
|
||||
} CirMemCharDriver;
|
||||
|
||||
static bool cirmem_chr_is_empty(const CharDriverState *chr)
|
||||
{
|
||||
const CirMemCharDriver *d = chr->opaque;
|
||||
|
||||
return d->cons == d->prod;
|
||||
}
|
||||
|
||||
static size_t qemu_chr_cirmem_count(const CharDriverState *chr)
|
||||
{
|
||||
const CirMemCharDriver *d = chr->opaque;
|
||||
|
||||
return (d->prod - d->cons);
|
||||
}
|
||||
|
||||
static int cirmem_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
|
||||
{
|
||||
CirMemCharDriver *d = chr->opaque;
|
||||
int i;
|
||||
|
||||
if (!buf || (len < 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++ ) {
|
||||
/* Avoid writing the IAC information to the queue. */
|
||||
if ((unsigned char)buf[i] == IAC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
d->cbuf[d->prod++ % d->size] = buf[i];
|
||||
if ((d->prod - d->cons) > d->size) {
|
||||
d->cons = d->prod - d->size;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cirmem_chr_read(CharDriverState *chr, uint8_t *buf, int len)
|
||||
{
|
||||
CirMemCharDriver *d = chr->opaque;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len && !cirmem_chr_is_empty(chr); i++) {
|
||||
buf[i] = d->cbuf[d->cons++ % d->size];
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void cirmem_chr_close(struct CharDriverState *chr)
|
||||
{
|
||||
CirMemCharDriver *d = chr->opaque;
|
||||
|
||||
g_free(d->cbuf);
|
||||
g_free(d);
|
||||
chr->opaque = NULL;
|
||||
}
|
||||
|
||||
static CharDriverState *qemu_chr_open_cirmemchr(QemuOpts *opts)
|
||||
{
|
||||
CharDriverState *chr;
|
||||
CirMemCharDriver *d;
|
||||
|
||||
chr = g_malloc0(sizeof(CharDriverState));
|
||||
d = g_malloc(sizeof(*d));
|
||||
|
||||
d->size = qemu_opt_get_number(opts, "maxcapacity", 0);
|
||||
if (d->size == 0) {
|
||||
d->size = CBUFF_SIZE;
|
||||
}
|
||||
|
||||
/* The size must be power of 2 */
|
||||
if (d->size & (d->size - 1)) {
|
||||
fprintf(stderr, "chardev: size of memory device must be power of 2\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
d->prod = 0;
|
||||
d->cons = 0;
|
||||
d->cbuf = g_malloc0(d->size);
|
||||
|
||||
chr->opaque = d;
|
||||
chr->chr_write = cirmem_chr_write;
|
||||
chr->chr_close = cirmem_chr_close;
|
||||
|
||||
return chr;
|
||||
|
||||
fail:
|
||||
g_free(d);
|
||||
g_free(chr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool qemu_is_chr(const CharDriverState *chr, const char *filename)
|
||||
{
|
||||
return strcmp(chr->filename, filename);
|
||||
}
|
||||
|
||||
void qmp_memchar_write(const char *device, int64_t size,
|
||||
const char *data, bool has_format,
|
||||
enum DataFormat format,
|
||||
Error **errp)
|
||||
{
|
||||
CharDriverState *chr;
|
||||
guchar *write_data;
|
||||
int ret;
|
||||
gsize write_count;
|
||||
|
||||
chr = qemu_chr_find(device);
|
||||
if (!chr) {
|
||||
error_set(errp, QERR_DEVICE_NOT_FOUND, device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (qemu_is_chr(chr, "memory")) {
|
||||
error_setg(errp,"%s is not memory char device", device);
|
||||
return;
|
||||
}
|
||||
|
||||
write_count = (gsize)size;
|
||||
|
||||
if (has_format && (format == DATA_FORMAT_BASE64)) {
|
||||
write_data = g_base64_decode(data, &write_count);
|
||||
} else {
|
||||
write_data = (uint8_t *)data;
|
||||
}
|
||||
|
||||
ret = cirmem_chr_write(chr, write_data, write_count);
|
||||
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Failed to write to device %s", device);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MemCharRead *qmp_memchar_read(const char *device, int64_t size,
|
||||
bool has_format, enum DataFormat format,
|
||||
Error **errp)
|
||||
{
|
||||
CharDriverState *chr;
|
||||
guchar *read_data;
|
||||
MemCharRead *meminfo;
|
||||
size_t count;
|
||||
|
||||
chr = qemu_chr_find(device);
|
||||
if (!chr) {
|
||||
error_set(errp, QERR_DEVICE_NOT_FOUND, device);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (qemu_is_chr(chr, "memory")) {
|
||||
error_setg(errp,"%s is not memory char device", device);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (size <= 0) {
|
||||
error_setg(errp, "size must be greater than zero");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
meminfo = g_malloc0(sizeof(MemCharRead));
|
||||
|
||||
count = qemu_chr_cirmem_count(chr);
|
||||
if (count == 0) {
|
||||
meminfo->data = g_strdup("");
|
||||
return meminfo;
|
||||
}
|
||||
|
||||
size = size > count ? count : size;
|
||||
read_data = g_malloc0(size + 1);
|
||||
|
||||
meminfo->count = cirmem_chr_read(chr, read_data, size);
|
||||
|
||||
if (has_format && (format == DATA_FORMAT_BASE64)) {
|
||||
meminfo->data = g_base64_encode(read_data, (size_t)meminfo->count);
|
||||
} else {
|
||||
meminfo->data = (char *)read_data;
|
||||
}
|
||||
|
||||
return meminfo;
|
||||
}
|
||||
|
||||
QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename)
|
||||
{
|
||||
char host[65], port[33], width[8], height[8];
|
||||
@ -2697,6 +2891,11 @@ QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename)
|
||||
qemu_opt_set(opts, "path", filename);
|
||||
return opts;
|
||||
}
|
||||
if (strstart(filename, "memory", &p)) {
|
||||
qemu_opt_set(opts, "backend", "memory");
|
||||
qemu_opt_set(opts, "maxcapacity", p);
|
||||
return opts;
|
||||
}
|
||||
if (strstart(filename, "file:", &p)) {
|
||||
qemu_opt_set(opts, "backend", "file");
|
||||
qemu_opt_set(opts, "path", p);
|
||||
@ -2796,6 +2995,7 @@ static const struct {
|
||||
{ .name = "udp", .open = qemu_chr_open_udp },
|
||||
{ .name = "msmouse", .open = qemu_chr_open_msmouse },
|
||||
{ .name = "vc", .open = text_console_init },
|
||||
{ .name = "memory", .open = qemu_chr_open_cirmemchr },
|
||||
#ifdef _WIN32
|
||||
{ .name = "file", .open = qemu_chr_open_win_file_out },
|
||||
{ .name = "pipe", .open = qemu_chr_open_win_pipe },
|
||||
@ -3055,6 +3255,9 @@ QemuOptsList qemu_chardev_opts = {
|
||||
},{
|
||||
.name = "debug",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
},{
|
||||
.name = "maxcapacity",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
|
@ -1736,6 +1736,7 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
|
||||
"-chardev msmouse,id=id[,mux=on|off]\n"
|
||||
"-chardev vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]\n"
|
||||
" [,mux=on|off]\n"
|
||||
"-chardev memory,id=id,maxcapacity=maxcapacity\n"
|
||||
"-chardev file,id=id,path=path[,mux=on|off]\n"
|
||||
"-chardev pipe,id=id,path=path[,mux=on|off]\n"
|
||||
#ifdef _WIN32
|
||||
@ -1777,6 +1778,7 @@ Backend is one of:
|
||||
@option{udp},
|
||||
@option{msmouse},
|
||||
@option{vc},
|
||||
@option{memory},
|
||||
@option{file},
|
||||
@option{pipe},
|
||||
@option{console},
|
||||
@ -1885,6 +1887,14 @@ the console, in pixels.
|
||||
@option{cols} and @option{rows} specify that the console be sized to fit a text
|
||||
console with the given dimensions.
|
||||
|
||||
@item -chardev memory ,id=@var{id} ,maxcapacity=@var{maxcapacity}
|
||||
|
||||
Create a circular buffer with fixed size indicated by optionally @option{maxcapacity}
|
||||
which will be default 64K if it is not given.
|
||||
|
||||
@option{maxcapacity} specifies the max capacity of the size of circular buffer
|
||||
to create. Should be power of 2.
|
||||
|
||||
@item -chardev file ,id=@var{id} ,path=@var{path}
|
||||
|
||||
Log all traffic received from the guest to a file.
|
||||
|
@ -463,6 +463,72 @@ Example:
|
||||
Note: inject-nmi fails when the guest doesn't support injecting.
|
||||
Currently, only x86 guests do.
|
||||
|
||||
EQMP
|
||||
|
||||
{
|
||||
.name = "memchar-write",
|
||||
.args_type = "device:s,size:i,data:s,format:s?",
|
||||
.mhandler.cmd_new = qmp_marshal_input_memchar_write,
|
||||
},
|
||||
|
||||
SQMP
|
||||
memchar-write
|
||||
-------------
|
||||
|
||||
Provide writing interface for CirMemCharDriver. Write data to memory
|
||||
char device.
|
||||
|
||||
Arguments:
|
||||
|
||||
- "device": the name of the char device, must be unique (json-string)
|
||||
- "size": the memory size, in bytes, should be power of 2 (json-int)
|
||||
- "data": the source data write to memory (json-string)
|
||||
- "format": the data format write to memory, default is
|
||||
utf8. (json-string, optional)
|
||||
- Possible values: "utf8", "base64"
|
||||
|
||||
Example:
|
||||
|
||||
-> { "execute": "memchar-write",
|
||||
"arguments": { "device": foo,
|
||||
"size": 8,
|
||||
"data": "abcdefgh",
|
||||
"format": "utf8" } }
|
||||
<- { "return": {} }
|
||||
|
||||
EQMP
|
||||
|
||||
{
|
||||
.name = "memchar-read",
|
||||
.args_type = "device:s,size:i,format:s?",
|
||||
.mhandler.cmd_new = qmp_marshal_input_memchar_read,
|
||||
},
|
||||
|
||||
SQMP
|
||||
memchar-read
|
||||
-------------
|
||||
|
||||
Provide read interface for CirMemCharDriver. Read from the char
|
||||
device memory and return the data with size.
|
||||
|
||||
Arguments:
|
||||
|
||||
- "device": the name of the char device, must be unique (json-string)
|
||||
- "size": the memory size wanted to read in bytes (refer to unencoded
|
||||
size of the raw data), would adjust to the init size of the
|
||||
memchar if the requested size is larger than it. (json-int)
|
||||
- "format": the data format write to memchardev, default is
|
||||
utf8. (json-string, optional)
|
||||
- Possible values: "utf8", "base64"
|
||||
|
||||
Example:
|
||||
|
||||
-> { "execute": "memchar-read",
|
||||
"arguments": { "device": foo,
|
||||
"size": 1000,
|
||||
"format": "utf8" } }
|
||||
<- { "return": { "data": "data string...", "count": 1000 } }
|
||||
|
||||
EQMP
|
||||
|
||||
{
|
||||
@ -2549,13 +2615,6 @@ Make an asynchronous request for balloon info. When the request completes a
|
||||
json-object will be returned containing the following data:
|
||||
|
||||
- "actual": current balloon value in bytes (json-int)
|
||||
- "mem_swapped_in": Amount of memory swapped in bytes (json-int, optional)
|
||||
- "mem_swapped_out": Amount of memory swapped out in bytes (json-int, optional)
|
||||
- "major_page_faults": Number of major faults (json-int, optional)
|
||||
- "minor_page_faults": Number of minor faults (json-int, optional)
|
||||
- "free_mem": Total amount of free and unused memory in
|
||||
bytes (json-int, optional)
|
||||
- "total_mem": Total amount of available memory in bytes (json-int, optional)
|
||||
|
||||
Example:
|
||||
|
||||
@ -2563,12 +2622,6 @@ Example:
|
||||
<- {
|
||||
"return":{
|
||||
"actual":1073741824,
|
||||
"mem_swapped_in":0,
|
||||
"mem_swapped_out":0,
|
||||
"major_page_faults":142,
|
||||
"minor_page_faults":239245,
|
||||
"free_mem":1014185984,
|
||||
"total_mem":1044668416
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user