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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Anthony Liguori
						Anthony Liguori