From 3e92c2d49cb0e8752ce8c9a3c879c84ac3299061 Mon Sep 17 00:00:00 2001 From: David 'Digit' Turner Date: Tue, 11 Oct 2011 03:02:41 +0200 Subject: Fix snapshot crash - Add snapshot load/save support to QEMU Pipes This adds the ability to save and load QEMU Pipe connections with snapshots. Note that by default, all loaded pipe client connections are force-fully closed on load. We don't have a good way to save the state of network connections to persistent storage. Properly implements snapshot save / load for qemu pipe clients. Change-Id: Ie5767f8ce40c8341b958cc5844e724dd4fc1ed2b --- hw/goldfish_pipe.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++++---- hw/goldfish_pipe.h | 21 +++++ hw/hw.h | 5 ++ 3 files changed, 243 insertions(+), 15 deletions(-) (limited to 'hw') diff --git a/hw/goldfish_pipe.c b/hw/goldfish_pipe.c index b3c6975..3c076f1 100644 --- a/hw/goldfish_pipe.c +++ b/hw/goldfish_pipe.c @@ -52,6 +52,11 @@ /* Set to 1 to enable the 'throttle' pipe type, useful for debugging */ #define DEBUG_THROTTLE_PIPE 1 +/* Maximum length of pipe service name, in characters (excluding final 0) */ +#define MAX_PIPE_SERVICE_NAME_SIZE 255 + +#define GOLDFISH_PIPE_SAVE_VERSION 1 + /*********************************************************************** *********************************************************************** ***** @@ -85,6 +90,10 @@ goldfish_pipe_add_type(const char* pipeName, APANIC("Too many goldfish pipe services (%d)", count); } + if (strlen(pipeName) > MAX_PIPE_SERVICE_NAME_SIZE) { + APANIC("Pipe service name too long: '%s'", pipeName); + } + list->services[count].name = pipeName; list->services[count].opaque = pipeOpaque; list->services[count].funcs = pipeFuncs[0]; @@ -120,24 +129,33 @@ typedef struct PipeDevice PipeDevice; typedef struct Pipe { struct Pipe* next; struct Pipe* next_waked; - PipeDevice* device; - uint32_t channel; - void* opaque; - const GoldfishPipeFuncs* funcs; - unsigned char wanted; - char closed; + PipeDevice* device; + uint32_t channel; + void* opaque; + const GoldfishPipeFuncs* funcs; + const PipeService* service; + char* args; + unsigned char wanted; + char closed; } Pipe; /* Forward */ static void* pipeConnector_new(Pipe* pipe); -Pipe* -pipe_new(uint32_t channel, PipeDevice* dev) +static Pipe* +pipe_new0(PipeDevice* dev) { Pipe* pipe; ANEW0(pipe); + pipe->device = dev; + return pipe; +} + +static Pipe* +pipe_new(uint32_t channel, PipeDevice* dev) +{ + Pipe* pipe = pipe_new0(dev); pipe->channel = channel; - pipe->device = dev; pipe->opaque = pipeConnector_new(pipe); return pipe; } @@ -199,6 +217,97 @@ pipe_list_remove_waked( Pipe** list, Pipe* pipe ) } } +static void +pipe_save( Pipe* pipe, QEMUFile* file ) +{ + if (pipe->service == NULL) { + /* pipe->service == NULL means we're still using a PipeConnector */ + /* Write a zero to indicate this condition */ + qemu_put_byte(file, 0); + } else { + /* Otherwise, write a '1' then the service name */ + qemu_put_byte(file, 1); + qemu_put_string(file, pipe->service->name); + } + + /* Now save other common data */ + qemu_put_be32(file, (unsigned int)pipe->channel); + qemu_put_byte(file, (int)pipe->wanted); + qemu_put_byte(file, (int)pipe->closed); + + /* Write 1 + args, if any, or simply 0 otherwise */ + if (pipe->args != NULL) { + qemu_put_byte(file, 1); + qemu_put_string(file, pipe->args); + } else { + qemu_put_byte(file, 0); + } + + if (pipe->funcs->save) { + pipe->funcs->save(pipe->opaque, file); + } +} + +static Pipe* +pipe_load( PipeDevice* dev, QEMUFile* file ) +{ + Pipe* pipe; + const PipeService* service = NULL; + int state = qemu_get_byte(file); + uint32_t channel; + + if (state != 0) { + /* Pipe is associated with a service. */ + char* name = qemu_get_string(file); + if (name == NULL) + return NULL; + + service = goldfish_pipe_find_type(name); + if (service == NULL) { + D("No QEMU pipe service named '%s'", name); + AFREE(name); + return NULL; + } + } + + channel = qemu_get_be32(file); + pipe = pipe_new(channel, dev); + pipe->wanted = qemu_get_byte(file); + pipe->closed = qemu_get_byte(file); + if (qemu_get_byte(file) != 0) { + pipe->args = qemu_get_string(file); + } + + pipe->service = service; + if (service != NULL) { + pipe->funcs = &service->funcs; + } + + if (pipe->funcs->load) { + pipe->opaque = pipe->funcs->load(pipe, service->opaque, pipe->args, file); + if (pipe->opaque == NULL) { + AFREE(pipe); + return NULL; + } + } else { + /* Force-close the pipe on load */ + pipe->closed = 1; + } + return pipe; +} + +static void +pipe_free( Pipe* pipe ) +{ + /* Call close callback */ + if (pipe->funcs->close) { + pipe->funcs->close(pipe->opaque); + } + /* Free stuff */ + AFREE(pipe->args); + AFREE(pipe); +} + /*********************************************************************** *********************************************************************** ***** @@ -293,6 +402,8 @@ pipeConnector_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int if (pipeArgs != NULL) { *pipeArgs++ = '\0'; + if (!*pipeArgs) + pipeArgs = NULL; } Pipe* pipe = pcon->pipe; @@ -310,7 +421,9 @@ pipeConnector_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int /* Do the evil switch now */ pipe->opaque = peer; + pipe->service = svc; pipe->funcs = &svc->funcs; + pipe->args = ASTRDUP(pipeArgs); AFREE(pcon); } @@ -335,6 +448,32 @@ pipeConnector_wakeOn( void* opaque, int flags ) /* nothing, really should never happen */ } +static void +pipeConnector_save( void* pipe, QEMUFile* file ) +{ + PipeConnector* pcon = pipe; + qemu_put_sbe32(file, pcon->buffpos); + qemu_put_sbuffer(file, (const int8_t*)pcon->buffer, pcon->buffpos); +} + +static void* +pipeConnector_load( void* hwpipe, void* pipeOpaque, const char* args, QEMUFile* file ) +{ + PipeConnector* pcon; + + int len = qemu_get_sbe32(file); + if (len < 0 || len > sizeof(pcon->buffer)) { + return NULL; + } + pcon = pipeConnector_new(hwpipe); + pcon->buffpos = len; + if (qemu_get_buffer(file, (uint8_t*)pcon->buffer, pcon->buffpos) != pcon->buffpos) { + AFREE(pcon); + return NULL; + } + return pcon; +} + static const GoldfishPipeFuncs pipeConnector_funcs = { NULL, /* init */ pipeConnector_close, /* should rarely happen */ @@ -342,6 +481,8 @@ static const GoldfishPipeFuncs pipeConnector_funcs = { pipeConnector_recvBuffers, /* should not happen */ pipeConnector_poll, /* should not happen */ pipeConnector_wakeOn, /* should not happen */ + pipeConnector_save, + pipeConnector_load, }; /*********************************************************************** @@ -860,12 +1001,7 @@ pipeDevice_doCommand( PipeDevice* dev, uint32_t command ) *lookup = pipe->next; pipe->next = NULL; pipe_list_remove_waked(&dev->signaled_pipes, pipe); - /* Call close callback */ - if (pipe->funcs->close) { - pipe->funcs->close(pipe->opaque); - } - /* Free stuff */ - AFREE(pipe); + pipe_free(pipe); break; case PIPE_CMD_POLL: @@ -1018,6 +1154,69 @@ static CPUWriteMemoryFunc *pipe_dev_writefn[] = { pipe_dev_write }; +static void +goldfish_pipe_save( QEMUFile* file, void* opaque ) +{ + PipeDevice* dev = opaque; + Pipe* pipe; + + qemu_put_be32(file, dev->address); + qemu_put_be32(file, dev->size); + qemu_put_be32(file, dev->status); + qemu_put_be32(file, dev->channel); + qemu_put_be32(file, dev->wakes); + + /* Count the number of pipe connections */ + int count = 0; + for ( pipe = dev->pipes; pipe; pipe = pipe->next ) + count++; + + qemu_put_sbe32(file, count); + + /* Now save each pipe one after the other */ + for ( pipe = dev->pipes; pipe; pipe = pipe->next ) { + pipe_save(pipe, file); + } +} + +static int +goldfish_pipe_load( QEMUFile* file, void* opaque, int version_id ) +{ + PipeDevice* dev = opaque; + Pipe* pipe; + + if (version_id != GOLDFISH_PIPE_SAVE_VERSION) + return -EINVAL; + + dev->address = qemu_get_be32(file); + dev->size = qemu_get_be32(file); + dev->status = qemu_get_be32(file); + dev->channel = qemu_get_be32(file); + dev->wakes = qemu_get_be32(file); + + /* Count the number of pipe connections */ + int count = qemu_get_sbe32(file); + + /* Load all pipe connections */ + for ( ; count > 0; count-- ) { + pipe = pipe_load(dev, file); + if (pipe == NULL) { + return -EIO; + } + pipe->next = dev->pipes; + dev->pipes = pipe; + } + + /* Now we need to wake/close all relevant pipes */ + for ( pipe = dev->pipes; pipe; pipe = pipe->next ) { + if (pipe->wanted != 0) + goldfish_pipe_wake(pipe, pipe->wanted); + if (pipe->closed != 0) + goldfish_pipe_close(pipe); + } + return 0; +} + /* initialize the trace device */ void pipe_dev_init() { @@ -1034,6 +1233,9 @@ void pipe_dev_init() goldfish_device_add(&s->dev, pipe_dev_readfn, pipe_dev_writefn, s); + register_savevm( "goldfish_pipe", 0, GOLDFISH_PIPE_SAVE_VERSION, + goldfish_pipe_save, goldfish_pipe_load, s); + #if DEBUG_ZERO_PIPE goldfish_pipe_add_type("zero", NULL, &zeroPipe_funcs); #endif diff --git a/hw/goldfish_pipe.h b/hw/goldfish_pipe.h index 8074619..f08cef8 100644 --- a/hw/goldfish_pipe.h +++ b/hw/goldfish_pipe.h @@ -94,6 +94,27 @@ typedef struct { * then the pipe implementation shall call goldfish_pipe_wake(). */ void (*wakeOn)( void* opaque, int flags ); + + /* Called to save the pipe's state to a QEMUFile, i.e. when saving + * snapshots. This can be NULL to indicate that no state can be saved. + * In this case, when the pipe is loaded, the emulator will automatically + * force-close so the next operation the guest performs on it will return + * a PIPE_ERROR_IO error code. + */ + void (*save)( void* pipe, QEMUFile* file ); + + /* Called to load the sate of a pipe from a QEMUFile. This will always + * correspond to the state of the pipe as saved by a previous call to + * the 'save' method. Can be NULL to indicate that the pipe state cannot + * be loaded. In this case, the emulator will automatically force-close + * it. + * + * In case of success, this returns 0, and the new pipe object is returned + * in '*ppipe'. In case of errno code is returned to indicate a failure. + * 'hwpipe' and 'pipeOpaque' are the same arguments than those passed + * to 'init'. + */ + void* (*load)( void* hwpipe, void* pipeOpaque, const char* args, QEMUFile* file); } GoldfishPipeFuncs; /* Register a new pipe handler type. 'pipeOpaque' is passed directly diff --git a/hw/hw.h b/hw/hw.h index d230448..292d42e 100644 --- a/hw/hw.h +++ b/hw/hw.h @@ -218,6 +218,11 @@ static inline void qemu_get_sbe64s(QEMUFile *f, int64_t *pv) qemu_get_be64s(f, (uint64_t *)pv); } +#ifdef CONFIG_ANDROID +void qemu_put_string(QEMUFile *f, const char* str); +char* qemu_get_string(QEMUFile *f); +#endif + #ifdef NEED_CPU_H #if TARGET_LONG_BITS == 64 #define qemu_put_betl qemu_put_be64 -- cgit v1.1