qemu-char: Saner naming of memchar stuff & doc fixes

New device, has never been released, so we can still improve things
without worrying about compatibility.

Naming is a mess.  The code calls the device driver CirMemCharDriver,
the public API calls it "memory", "memchardev", or "memchar", and the
special commands are named like "memchar-FOO".  "memory" is a
particularly unfortunate choice, because there's another character
device driver called MemoryDriver.  Moreover, the device's distinctive
property is that it's a ring buffer, not that's in memory.  Therefore:

* Rename CirMemCharDriver to RingBufCharDriver, and call the thing a
  "ringbuf" in the API.

* Rename QMP and HMP commands from memchar-FOO to ringbuf-FOO.

* Rename device parameter from maxcapacity to size (simple words are
  good for you).

* Clearly mark the parameter as optional in documentation.

* Fix error reporting so that chardev-add reports to current monitor,
  not stderr.

* Replace cirmem in C identifiers by ringbuf.

* Rework documentation.  Document the impact of our crappy UTF-8
  handling on reading.

* QMP examples that even work.

I could split this up into multiple commits, but they'd change the
same documentation lines multiple times.  Not worth it.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
This commit is contained in:
Markus Armbruster 2013-02-06 21:27:24 +01:00 committed by Anthony Liguori
parent 5c230105cd
commit 3949e59414
7 changed files with 122 additions and 107 deletions

View File

@ -841,40 +841,37 @@ Inject an NMI on the given CPU (x86 only).
ETEXI ETEXI
{ {
.name = "memchar_write", .name = "ringbuf_write",
.args_type = "device:s,data:s", .args_type = "device:s,data:s",
.params = "device data", .params = "device data",
.help = "Provide writing interface for CirMemCharDriver. Write" .help = "Write to a ring buffer character device",
"'data' to it.", .mhandler.cmd = hmp_ringbuf_write,
.mhandler.cmd = hmp_memchar_write,
}, },
STEXI STEXI
@item memchar_write @var{device} @var{data} @item ringbuf_write @var{device} @var{data}
@findex memchar_write @findex ringbuf_write
Provide writing interface for CirMemCharDriver. Write @var{data} Write @var{data} to ring buffer character device @var{device}.
to char device 'memory'. @var{data} must be a UTF-8 string.
ETEXI ETEXI
{ {
.name = "memchar_read", .name = "ringbuf_read",
.args_type = "device:s,size:i", .args_type = "device:s,size:i",
.params = "device size", .params = "device size",
.help = "Provide read interface for CirMemCharDriver. Read from" .help = "Read from a ring buffer character device",
"it and return the data with size.", .mhandler.cmd = hmp_ringbuf_read,
.mhandler.cmd = hmp_memchar_read,
}, },
STEXI STEXI
@item memchar_read @var{device} @item ringbuf_read @var{device}
@findex memchar_read @findex ringbuf_read
Provide read interface for CirMemCharDriver. Read from char device Read and print up to @var{size} bytes from ring buffer character
'memory' and return the data. device @var{device}.
Bug: can screw up when the buffer contains invalid UTF-8 sequences,
@var{size} is the size of data want to read from. Refer to unencoded NUL characters, after the ring buffer lost data, and when reading
size of the raw data, would adjust to the init size of the memchar stops because the size limit is reached.
if the requested size is larger than it.
ETEXI ETEXI

8
hmp.c
View File

@ -662,25 +662,25 @@ void hmp_pmemsave(Monitor *mon, const QDict *qdict)
hmp_handle_error(mon, &errp); hmp_handle_error(mon, &errp);
} }
void hmp_memchar_write(Monitor *mon, const QDict *qdict) void hmp_ringbuf_write(Monitor *mon, const QDict *qdict)
{ {
const char *chardev = qdict_get_str(qdict, "device"); const char *chardev = qdict_get_str(qdict, "device");
const char *data = qdict_get_str(qdict, "data"); const char *data = qdict_get_str(qdict, "data");
Error *errp = NULL; Error *errp = NULL;
qmp_memchar_write(chardev, data, false, 0, &errp); qmp_ringbuf_write(chardev, data, false, 0, &errp);
hmp_handle_error(mon, &errp); hmp_handle_error(mon, &errp);
} }
void hmp_memchar_read(Monitor *mon, const QDict *qdict) void hmp_ringbuf_read(Monitor *mon, const QDict *qdict)
{ {
uint32_t size = qdict_get_int(qdict, "size"); uint32_t size = qdict_get_int(qdict, "size");
const char *chardev = qdict_get_str(qdict, "device"); const char *chardev = qdict_get_str(qdict, "device");
char *data; char *data;
Error *errp = NULL; Error *errp = NULL;
data = qmp_memchar_read(chardev, size, false, 0, &errp); data = qmp_ringbuf_read(chardev, size, false, 0, &errp);
if (errp) { if (errp) {
monitor_printf(mon, "%s\n", error_get_pretty(errp)); monitor_printf(mon, "%s\n", error_get_pretty(errp));
error_free(errp); error_free(errp);

4
hmp.h
View File

@ -43,8 +43,8 @@ void hmp_system_powerdown(Monitor *mon, const QDict *qdict);
void hmp_cpu(Monitor *mon, const QDict *qdict); void hmp_cpu(Monitor *mon, const QDict *qdict);
void hmp_memsave(Monitor *mon, const QDict *qdict); void hmp_memsave(Monitor *mon, const QDict *qdict);
void hmp_pmemsave(Monitor *mon, const QDict *qdict); void hmp_pmemsave(Monitor *mon, const QDict *qdict);
void hmp_memchar_write(Monitor *mon, const QDict *qdict); void hmp_ringbuf_write(Monitor *mon, const QDict *qdict);
void hmp_memchar_read(Monitor *mon, const QDict *qdict); void hmp_ringbuf_read(Monitor *mon, const QDict *qdict);
void hmp_cont(Monitor *mon, const QDict *qdict); void hmp_cont(Monitor *mon, const QDict *qdict);
void hmp_system_wakeup(Monitor *mon, const QDict *qdict); void hmp_system_wakeup(Monitor *mon, const QDict *qdict);
void hmp_inject_nmi(Monitor *mon, const QDict *qdict); void hmp_inject_nmi(Monitor *mon, const QDict *qdict);

View File

@ -329,9 +329,9 @@
# #
# An enumeration of data format. # An enumeration of data format.
# #
# @utf8: The data format is 'utf8'. # @utf8: Data is a UTF-8 string (RFC 3629)
# #
# @base64: The data format is 'base64'. # @base64: Data is Base64 encoded binary (RFC 3548)
# #
# Since: 1.4 # Since: 1.4
## ##
@ -339,44 +339,55 @@
'data': [ 'utf8', 'base64' ] } 'data': [ 'utf8', 'base64' ] }
## ##
# @memchar-write: # @ringbuf-write:
# #
# Provide writing interface for memchardev. Write data to char # Write to a ring buffer character device.
# device 'memory'.
# #
# @device: the name of the memory char device. # @device: the ring buffer character device name
# #
# @data: the source data write to memchar. # @data: data to write
# #
# @format: #optional the format of the data write to chardev 'memory', # @format: #optional data encoding (default 'utf8').
# by default is 'utf8'. # - base64: data must be base64 encoded text. Its binary
# decoding gets written.
# Bug: invalid base64 is currently not rejected.
# Whitespace *is* invalid.
# - utf8: data's UTF-8 encoding is written
# - data itself is always Unicode regardless of format, like
# any other string.
# #
# Returns: Nothing on success # Returns: Nothing on success
# #
# Since: 1.4 # Since: 1.4
## ##
{ 'command': 'memchar-write', { 'command': 'ringbuf-write',
'data': {'device': 'str', 'data': 'str', 'data': {'device': 'str', 'data': 'str',
'*format': 'DataFormat'} } '*format': 'DataFormat'} }
## ##
# @memchar-read: # @ringbuf-read:
# #
# Provide read interface for memchardev. Read from the char # Read from a ring buffer character device.
# device 'memory' and return the data.
# #
# @device: the name of the memory char device. # @device: the ring buffer character device name
# #
# @size: the size to read in bytes. # @size: how many bytes to read at most
# #
# @format: #optional the format of the data want to read from # @format: #optional data encoding (default 'utf8').
# memchardev, by default is 'utf8'. # - base64: the data read is returned in base64 encoding.
# - utf8: the data read is interpreted as UTF-8.
# Bug: can screw up when the buffer contains invalid UTF-8
# sequences, NUL characters, after the ring buffer lost
# data, and when reading stops because the size limit is
# reached.
# - The return value is always Unicode regardless of format,
# like any other string.
# #
# Returns: data read from the device # Returns: data read from the device
# #
# Since: 1.4 # Since: 1.4
## ##
{ 'command': 'memchar-read', { 'command': 'ringbuf-read',
'data': {'device': 'str', 'size': 'int', '*format': 'DataFormat'}, 'data': {'device': 'str', 'size': 'int', '*format': 'DataFormat'},
'returns': 'str' } 'returns': 'str' }

View File

@ -2645,25 +2645,25 @@ size_t qemu_chr_mem_osize(const CharDriverState *chr)
} }
/*********************************************************/ /*********************************************************/
/* CircularMemory chardev */ /* Ring buffer chardev */
typedef struct { typedef struct {
size_t size; size_t size;
size_t prod; size_t prod;
size_t cons; size_t cons;
uint8_t *cbuf; uint8_t *cbuf;
} CirMemCharDriver; } RingBufCharDriver;
static size_t cirmem_count(const CharDriverState *chr) static size_t ringbuf_count(const CharDriverState *chr)
{ {
const CirMemCharDriver *d = chr->opaque; const RingBufCharDriver *d = chr->opaque;
return d->prod - d->cons; return d->prod - d->cons;
} }
static int cirmem_chr_write(CharDriverState *chr, const uint8_t *buf, int len) static int ringbuf_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
{ {
CirMemCharDriver *d = chr->opaque; RingBufCharDriver *d = chr->opaque;
int i; int i;
if (!buf || (len < 0)) { if (!buf || (len < 0)) {
@ -2680,9 +2680,9 @@ static int cirmem_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
return 0; return 0;
} }
static int cirmem_chr_read(CharDriverState *chr, uint8_t *buf, int len) static int ringbuf_chr_read(CharDriverState *chr, uint8_t *buf, int len)
{ {
CirMemCharDriver *d = chr->opaque; RingBufCharDriver *d = chr->opaque;
int i; int i;
for (i = 0; i < len && d->cons != d->prod; i++) { for (i = 0; i < len && d->cons != d->prod; i++) {
@ -2692,31 +2692,31 @@ static int cirmem_chr_read(CharDriverState *chr, uint8_t *buf, int len)
return i; return i;
} }
static void cirmem_chr_close(struct CharDriverState *chr) static void ringbuf_chr_close(struct CharDriverState *chr)
{ {
CirMemCharDriver *d = chr->opaque; RingBufCharDriver *d = chr->opaque;
g_free(d->cbuf); g_free(d->cbuf);
g_free(d); g_free(d);
chr->opaque = NULL; chr->opaque = NULL;
} }
static CharDriverState *qemu_chr_open_cirmemchr(QemuOpts *opts) static CharDriverState *qemu_chr_open_ringbuf(QemuOpts *opts)
{ {
CharDriverState *chr; CharDriverState *chr;
CirMemCharDriver *d; RingBufCharDriver *d;
chr = g_malloc0(sizeof(CharDriverState)); chr = g_malloc0(sizeof(CharDriverState));
d = g_malloc(sizeof(*d)); d = g_malloc(sizeof(*d));
d->size = qemu_opt_get_number(opts, "maxcapacity", 0); d->size = qemu_opt_get_number(opts, "size", 0);
if (d->size == 0) { if (d->size == 0) {
d->size = CBUFF_SIZE; d->size = CBUFF_SIZE;
} }
/* The size must be power of 2 */ /* The size must be power of 2 */
if (d->size & (d->size - 1)) { if (d->size & (d->size - 1)) {
fprintf(stderr, "chardev: size of memory device must be power of 2\n"); error_report("size of ringbuf device must be power of two");
goto fail; goto fail;
} }
@ -2725,8 +2725,8 @@ static CharDriverState *qemu_chr_open_cirmemchr(QemuOpts *opts)
d->cbuf = g_malloc0(d->size); d->cbuf = g_malloc0(d->size);
chr->opaque = d; chr->opaque = d;
chr->chr_write = cirmem_chr_write; chr->chr_write = ringbuf_chr_write;
chr->chr_close = cirmem_chr_close; chr->chr_close = ringbuf_chr_close;
return chr; return chr;
@ -2736,12 +2736,12 @@ fail:
return NULL; return NULL;
} }
static bool chr_is_cirmem(const CharDriverState *chr) static bool chr_is_ringbuf(const CharDriverState *chr)
{ {
return chr->chr_write == cirmem_chr_write; return chr->chr_write == ringbuf_chr_write;
} }
void qmp_memchar_write(const char *device, const char *data, void qmp_ringbuf_write(const char *device, const char *data,
bool has_format, enum DataFormat format, bool has_format, enum DataFormat format,
Error **errp) Error **errp)
{ {
@ -2756,8 +2756,8 @@ void qmp_memchar_write(const char *device, const char *data,
return; return;
} }
if (!chr_is_cirmem(chr)) { if (!chr_is_ringbuf(chr)) {
error_setg(errp,"%s is not memory char device", device); error_setg(errp,"%s is not a ringbuf device", device);
return; return;
} }
@ -2768,7 +2768,7 @@ void qmp_memchar_write(const char *device, const char *data,
write_count = strlen(data); write_count = strlen(data);
} }
ret = cirmem_chr_write(chr, write_data, write_count); ret = ringbuf_chr_write(chr, write_data, write_count);
if (write_data != (uint8_t *)data) { if (write_data != (uint8_t *)data) {
g_free((void *)write_data); g_free((void *)write_data);
@ -2780,7 +2780,7 @@ void qmp_memchar_write(const char *device, const char *data,
} }
} }
char *qmp_memchar_read(const char *device, int64_t size, char *qmp_ringbuf_read(const char *device, int64_t size,
bool has_format, enum DataFormat format, bool has_format, enum DataFormat format,
Error **errp) Error **errp)
{ {
@ -2795,8 +2795,8 @@ char *qmp_memchar_read(const char *device, int64_t size,
return NULL; return NULL;
} }
if (!chr_is_cirmem(chr)) { if (!chr_is_ringbuf(chr)) {
error_setg(errp,"%s is not memory char device", device); error_setg(errp,"%s is not a ringbuf device", device);
return NULL; return NULL;
} }
@ -2805,16 +2805,23 @@ char *qmp_memchar_read(const char *device, int64_t size,
return NULL; return NULL;
} }
count = cirmem_count(chr); count = ringbuf_count(chr);
size = size > count ? count : size; size = size > count ? count : size;
read_data = g_malloc(size + 1); read_data = g_malloc(size + 1);
cirmem_chr_read(chr, read_data, size); ringbuf_chr_read(chr, read_data, size);
if (has_format && (format == DATA_FORMAT_BASE64)) { if (has_format && (format == DATA_FORMAT_BASE64)) {
data = g_base64_encode(read_data, size); data = g_base64_encode(read_data, size);
g_free(read_data); g_free(read_data);
} else { } else {
/*
* FIXME should read only complete, valid UTF-8 characters up
* to @size bytes. Invalid sequences should be replaced by a
* suitable replacement character. Except when (and only
* when) ring buffer lost characters since last read, initial
* continuation characters should be dropped.
*/
read_data[size] = 0; read_data[size] = 0;
data = (char *)read_data; data = (char *)read_data;
} }
@ -2975,7 +2982,7 @@ static const struct {
{ .name = "udp", .open = qemu_chr_open_udp }, { .name = "udp", .open = qemu_chr_open_udp },
{ .name = "msmouse", .open = qemu_chr_open_msmouse }, { .name = "msmouse", .open = qemu_chr_open_msmouse },
{ .name = "vc", .open = text_console_init }, { .name = "vc", .open = text_console_init },
{ .name = "memory", .open = qemu_chr_open_cirmemchr }, { .name = "memory", .open = qemu_chr_open_ringbuf },
#ifdef _WIN32 #ifdef _WIN32
{ .name = "file", .open = qemu_chr_open_win_file_out }, { .name = "file", .open = qemu_chr_open_win_file_out },
{ .name = "pipe", .open = qemu_chr_open_win_pipe }, { .name = "pipe", .open = qemu_chr_open_win_pipe },
@ -3236,7 +3243,7 @@ QemuOptsList qemu_chardev_opts = {
.name = "debug", .name = "debug",
.type = QEMU_OPT_NUMBER, .type = QEMU_OPT_NUMBER,
},{ },{
.name = "maxcapacity", .name = "size",
.type = QEMU_OPT_NUMBER, .type = QEMU_OPT_NUMBER,
}, },
{ /* end of list */ } { /* end of list */ }

View File

@ -1736,7 +1736,7 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
"-chardev msmouse,id=id[,mux=on|off]\n" "-chardev msmouse,id=id[,mux=on|off]\n"
"-chardev vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]\n" "-chardev vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]\n"
" [,mux=on|off]\n" " [,mux=on|off]\n"
"-chardev memory,id=id,maxcapacity=maxcapacity\n" "-chardev ringbuf,id=id[,size=size]\n"
"-chardev file,id=id,path=path[,mux=on|off]\n" "-chardev file,id=id,path=path[,mux=on|off]\n"
"-chardev pipe,id=id,path=path[,mux=on|off]\n" "-chardev pipe,id=id,path=path[,mux=on|off]\n"
#ifdef _WIN32 #ifdef _WIN32
@ -1778,7 +1778,7 @@ Backend is one of:
@option{udp}, @option{udp},
@option{msmouse}, @option{msmouse},
@option{vc}, @option{vc},
@option{memory}, @option{ringbuf},
@option{file}, @option{file},
@option{pipe}, @option{pipe},
@option{console}, @option{console},
@ -1887,13 +1887,10 @@ the console, in pixels.
@option{cols} and @option{rows} specify that the console be sized to fit a text @option{cols} and @option{rows} specify that the console be sized to fit a text
console with the given dimensions. console with the given dimensions.
@item -chardev memory ,id=@var{id} ,maxcapacity=@var{maxcapacity} @item -chardev ringbuf ,id=@var{id} [,size=@var{size}]
Create a circular buffer with fixed size indicated by optionally @option{maxcapacity} Create a ring buffer with fixed size @option{size}.
which will be default 64K if it is not given. @var{size} must be a power of two, and defaults to @code{64K}).
@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} @item -chardev file ,id=@var{id} ,path=@var{path}

View File

@ -466,30 +466,30 @@ Note: inject-nmi fails when the guest doesn't support injecting.
EQMP EQMP
{ {
.name = "memchar-write", .name = "ringbuf-write",
.args_type = "device:s,data:s,format:s?", .args_type = "device:s,data:s,format:s?",
.mhandler.cmd_new = qmp_marshal_input_memchar_write, .mhandler.cmd_new = qmp_marshal_input_ringbuf_write,
}, },
SQMP SQMP
memchar-write ringbuf-write
------------- -------------
Provide writing interface for CirMemCharDriver. Write data to memory Write to a ring buffer character device.
char device.
Arguments: Arguments:
- "device": the name of the char device, must be unique (json-string) - "device": ring buffer character device name (json-string)
- "data": the source data write to memory (json-string) - "data": data to write (json-string)
- "format": the data format write to memory, default is - "format": data format (json-string, optional)
utf8. (json-string, optional) - Possible values: "utf8" (default), "base64"
- Possible values: "utf8", "base64" Bug: invalid base64 is currently not rejected.
Whitespace *is* invalid.
Example: Example:
-> { "execute": "memchar-write", -> { "execute": "ringbuf-write",
"arguments": { "device": foo, "arguments": { "device": "foo",
"data": "abcdefgh", "data": "abcdefgh",
"format": "utf8" } } "format": "utf8" } }
<- { "return": {} } <- { "return": {} }
@ -497,32 +497,35 @@ Example:
EQMP EQMP
{ {
.name = "memchar-read", .name = "ringbuf-read",
.args_type = "device:s,size:i,format:s?", .args_type = "device:s,size:i,format:s?",
.mhandler.cmd_new = qmp_marshal_input_memchar_read, .mhandler.cmd_new = qmp_marshal_input_ringbuf_read,
}, },
SQMP SQMP
memchar-read ringbuf-read
------------- -------------
Provide read interface for CirMemCharDriver. Read from the char Read from a ring buffer character device.
device memory and return the data with size.
Arguments: Arguments:
- "device": the name of the char device, must be unique (json-string) - "device": ring buffer character device name (json-string)
- "size": the memory size wanted to read in bytes (refer to unencoded - "size": how many bytes to read at most (json-int)
size of the raw data), would adjust to the init size of the - Number of data bytes, not number of characters in encoded data
memchar if the requested size is larger than it. (json-int) - "format": data format (json-string, optional)
- "format": the data format write to memchardev, default is - Possible values: "utf8" (default), "base64"
utf8. (json-string, optional) - Naturally, format "utf8" works only when the ring buffer
- Possible values: "utf8", "base64" contains valid UTF-8 text. Invalid UTF-8 sequences get
replaced. Bug: replacement doesn't work. Bug: can screw
up on encountering NUL characters, after the ring buffer
lost data, and when reading stops because the size limit
is reached.
Example: Example:
-> { "execute": "memchar-read", -> { "execute": "ringbuf-read",
"arguments": { "device": foo, "arguments": { "device": "foo",
"size": 1000, "size": 1000,
"format": "utf8" } } "format": "utf8" } }
<- {"return": "abcdefgh"} <- {"return": "abcdefgh"}