aboutsummaryrefslogtreecommitdiffstats
path: root/hw
diff options
context:
space:
mode:
authorOt ten Thije <ottenthije@google.com>2010-09-08 19:01:33 +0100
committerOt ten Thije <ottenthije@google.com>2010-10-13 15:37:13 +0100
commitc51182e909275b7d89c1ed847eae40fdd8b6a33f (patch)
treee93a8867c1e68755faa85aedbef58e27c79961b6 /hw
parent00fa926bdd4c7d169f1124b6029234dcc254bc1f (diff)
downloadexternal_qemu-c51182e909275b7d89c1ed847eae40fdd8b6a33f.zip
external_qemu-c51182e909275b7d89c1ed847eae40fdd8b6a33f.tar.gz
external_qemu-c51182e909275b7d89c1ed847eae40fdd8b6a33f.tar.bz2
Save NAND disk state in snapshots.
This patch fixes saving and restoring the state of the emulated NAND disks, by copying all contents of each disk into the snapshot file. During restore the disks are then entirely overwritten with the snapshot contents. The main issue with this approach is that the size of the disk snapshots is determined by the maximum capacity of the disk in question, rather than the actual size of the underlying file, which may be considerably smaller. The difference is filled with padding. Unfortunately, obvious approaches to avoid this padding result in incorrect state after restoring. Change-Id: I4abe329ff2cf2f159c3d6dfc206959614cc4ca35
Diffstat (limited to 'hw')
-rw-r--r--hw/goldfish_nand.c251
1 files changed, 206 insertions, 45 deletions
diff --git a/hw/goldfish_nand.c b/hw/goldfish_nand.c
index e222cd1..933edc9 100644
--- a/hw/goldfish_nand.c
+++ b/hw/goldfish_nand.c
@@ -49,16 +49,19 @@ xlog( const char* format, ... )
va_end(args);
}
+/* Information on a single device/nand image used by the emulator
+ */
typedef struct {
- char* devname;
+ char* devname; /* name for this device (not null-terminated, use len below) */
size_t devname_len;
- uint8_t* data;
+ uint8_t* data; /* buffer for read/write actions to underlying image */
int fd;
uint32_t flags;
uint32_t page_size;
uint32_t extra_size;
- uint32_t erase_size;
- uint64_t size;
+ uint32_t erase_size; /* size of the data buffer mentioned above */
+ uint64_t max_size; /* Capacity limit for the image. The actual underlying
+ * file may be smaller. */
} nand_dev;
nand_threshold android_nand_write_threshold;
@@ -110,23 +113,30 @@ nand_threshold_update( nand_threshold* t, uint32_t len )
static nand_dev *nand_devs = NULL;
static uint32_t nand_dev_count = 0;
+/* The controller is the single access point for all NAND images currently
+ * attached to the system.
+ */
typedef struct {
uint32_t base;
// register state
- uint32_t dev;
+ uint32_t dev; /* offset in nand_devs for the device that is
+ * currently being accessed */
uint32_t addr_low;
uint32_t addr_high;
uint32_t transfer_size;
uint32_t data;
uint32_t result;
-} nand_dev_state;
+} nand_dev_controller_state;
-/* update this everytime you change the nand_dev_state structure */
-#define NAND_DEV_STATE_SAVE_VERSION 1
+/* update this everytime you change the nand_dev_controller_state structure
+ * 1: initial version, saving only nand_dev_controller_state fields
+ * 2: saving actual disk contents as well
+ */
+#define NAND_DEV_STATE_SAVE_VERSION 2
-#define QFIELD_STRUCT nand_dev_state
-QFIELD_BEGIN(nand_dev_state_fields)
+#define QFIELD_STRUCT nand_dev_controller_state
+QFIELD_BEGIN(nand_dev_controller_state_fields)
QFIELD_INT32(dev),
QFIELD_INT32(addr_low),
QFIELD_INT32(addr_high),
@@ -135,23 +145,6 @@ QFIELD_BEGIN(nand_dev_state_fields)
QFIELD_INT32(result),
QFIELD_END
-static void nand_dev_state_save(QEMUFile* f, void* opaque)
-{
- nand_dev_state* s = opaque;
-
- qemu_put_struct(f, nand_dev_state_fields, s);
-}
-
-static int nand_dev_state_load(QEMUFile* f, void* opaque, int version_id)
-{
- nand_dev_state* s = opaque;
-
- if (version_id != NAND_DEV_STATE_SAVE_VERSION)
- return -1;
-
- return qemu_get_struct(f, nand_dev_state_fields, s);
-}
-
static int do_read(int fd, void* buf, size_t size)
{
@@ -173,6 +166,174 @@ static int do_write(int fd, const void* buf, size_t size)
return ret;
}
+#define NAND_DEV_SAVE_DISK_BUF_SIZE 2048
+
+
+/**
+ * Copies the current contents of a disk image into the snapshot file.
+ *
+ * TODO optimize this using some kind of copy-on-write mechanism for
+ * unchanged disk sections.
+ */
+static void nand_dev_save_disk_state(QEMUFile *f, nand_dev *dev)
+{
+ int buf_size = NAND_DEV_SAVE_DISK_BUF_SIZE;
+ uint8_t buffer[NAND_DEV_SAVE_DISK_BUF_SIZE] = {0};
+ int ret;
+ uint64_t total_copied = 0;
+
+ /* Put the size up front, since otherwise we don't know how much to read
+ * when restoring.
+ */
+ const uint64_t total_size = dev->max_size;
+ qemu_put_be64(f, total_size);
+
+ /* copy all data from the stream to the stored image */
+ ret = lseek(dev->fd, 0, SEEK_SET);
+ if (ret < 0) {
+ XLOG("%s seek failed: %s\n", __FUNCTION__, strerror(errno));
+ qemu_file_set_error(f);
+ return;
+ }
+ do {
+ ret = do_read(dev->fd, buffer, buf_size);
+ if (ret < 0) {
+ XLOG("%s read failed: %s\n", __FUNCTION__, strerror(errno));
+ qemu_file_set_error(f);
+ return;
+ }
+ qemu_put_buffer(f, buffer, ret);
+
+ total_copied += ret;
+ }
+ while (ret == buf_size && total_copied < total_size);
+
+ /* The file may be smaller than the device size. Pad with 0xff (pattern for
+ * erased data) until we have filled the snapshot buffer we declared earlier.
+ * TODO avoid padding. Unfortunately, attempts to use the actual size of the
+ * underlying image instead result in broken restores. This also happens
+ * when limited padding is inserted so the image size is a multiple of
+ * page_size or erase_size.
+ */
+ memset(buffer, 0xff, NAND_DEV_SAVE_DISK_BUF_SIZE);
+ while (total_copied < total_size) {
+ /* adjust buffer size for last part of the image */
+ if (total_size - total_copied < buf_size) {
+ buf_size = total_size - total_copied;
+ }
+ qemu_put_buffer(f, buffer, buf_size);
+ total_copied += buf_size;
+ }
+}
+
+
+/**
+ * Saves the state of all disks managed by this controller to a snapshot file.
+ */
+static void nand_dev_save_disks(QEMUFile *f)
+{
+ int i;
+ for (i = 0; i < nand_dev_count; i++) {
+ nand_dev_save_disk_state(f, nand_devs + i);
+ }
+}
+
+/**
+ * Overwrites the contents of the disk image managed by this device with the
+ * contents as they were at the point the snapshot was made.
+ */
+static int nand_dev_load_disk_state(QEMUFile *f, nand_dev *dev)
+{
+ int buf_size = NAND_DEV_SAVE_DISK_BUF_SIZE;
+ uint8_t buffer[NAND_DEV_SAVE_DISK_BUF_SIZE] = {0};
+ int ret;
+
+ /* number of bytes to restore */
+ uint64_t total_size = qemu_get_be64(f);
+ if (total_size > dev->max_size) {
+ XLOG("%s, restore failed: size required (%lld) exceeds device limit (%lld)\n",
+ __FUNCTION__, total_size, dev->max_size);
+ return -EIO;
+ }
+
+ /* overwrite disk contents with snapshot contents */
+ uint64_t next_offset = 0;
+ do {
+ ret = lseek(dev->fd, 0, SEEK_SET);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ XLOG("%s seek failed: %s\n", __FUNCTION__, strerror(errno));
+ return -EIO;
+ }
+ while (next_offset < total_size) {
+ /* snapshot buffer may not be an exact multiple of buf_size
+ * if necessary, adjust buffer size for last copy operation */
+ if (total_size - next_offset < buf_size) {
+ buf_size = total_size - next_offset;
+ }
+
+ ret = qemu_get_buffer(f, buffer, buf_size);
+ if (ret != buf_size) {
+ XLOG("%s read failed: expected %d bytes but got %d\n",
+ __FUNCTION__, buf_size, ret);
+ return -EIO;
+ }
+ ret = do_write(dev->fd, buffer, buf_size);
+ if (ret != buf_size) {
+ XLOG("%s, write failed: %s\n", __FUNCTION__, strerror(errno));
+ return -EIO;
+ }
+
+ next_offset += buf_size;
+ }
+
+ return 0;
+}
+
+/**
+ * Restores the state of all disks managed by this driver from a snapshot file.
+ */
+static int nand_dev_load_disks(QEMUFile *f)
+{
+ int i, ret;
+ for (i = 0; i < nand_dev_count; i++) {
+ ret = nand_dev_load_disk_state(f, nand_devs + i);
+ if (ret)
+ return ret; // abort on error
+ }
+
+ return 0;
+}
+
+static void nand_dev_controller_state_save(QEMUFile *f, void *opaque)
+{
+ nand_dev_controller_state* s = opaque;
+
+ qemu_put_struct(f, nand_dev_controller_state_fields, s);
+
+ /* The guest will continue writing to the disk image after the state has
+ * been saved. To guarantee that the state is identical after resume, save
+ * a copy of the current disk state in the snapshot.
+ */
+ nand_dev_save_disks(f);
+}
+
+static int nand_dev_controller_state_load(QEMUFile *f, void *opaque, int version_id)
+{
+ nand_dev_controller_state* s = opaque;
+ int ret;
+
+ if (version_id != NAND_DEV_STATE_SAVE_VERSION)
+ return -1;
+
+ if ((ret = qemu_get_struct(f, nand_dev_controller_state_fields, s)))
+ return ret;
+ if ((ret = nand_dev_load_disks(f)))
+ return ret;
+
+ return 0;
+}
+
static uint32_t nand_dev_read_file(nand_dev *dev, uint32_t data, uint64_t addr, uint32_t total_len)
{
uint32_t len = total_len;
@@ -258,7 +419,7 @@ static uint32_t nand_dev_erase_file(nand_dev *dev, uint64_t addr, uint32_t total
#if !(defined __APPLE__ && defined __powerpc__)
static
#endif
-uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd)
+uint32_t nand_dev_do_cmd(nand_dev_controller_state *s, uint32_t cmd)
{
uint32_t size;
uint64_t addr;
@@ -277,10 +438,10 @@ uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd)
cpu_memory_rw_debug(cpu_single_env, s->data, (uint8_t*)dev->devname, size, 1);
return size;
case NAND_CMD_READ:
- if(addr >= dev->size)
+ if(addr >= dev->max_size)
return 0;
- if(size + addr > dev->size)
- size = dev->size - addr;
+ if(size > dev->max_size - addr)
+ size = dev->max_size - addr;
if(dev->fd >= 0)
return nand_dev_read_file(dev, s->data, addr, size);
cpu_memory_rw_debug(cpu_single_env,s->data, &dev->data[addr], size, 1);
@@ -288,10 +449,10 @@ uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd)
case NAND_CMD_WRITE:
if(dev->flags & NAND_DEV_FLAG_READ_ONLY)
return 0;
- if(addr >= dev->size)
+ if(addr >= dev->max_size)
return 0;
- if(size + addr > dev->size)
- size = dev->size - addr;
+ if(size > dev->max_size - addr)
+ size = dev->max_size - addr;
if(dev->fd >= 0)
return nand_dev_write_file(dev, s->data, addr, size);
cpu_memory_rw_debug(cpu_single_env,s->data, &dev->data[addr], size, 0);
@@ -299,10 +460,10 @@ uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd)
case NAND_CMD_ERASE:
if(dev->flags & NAND_DEV_FLAG_READ_ONLY)
return 0;
- if(addr >= dev->size)
+ if(addr >= dev->max_size)
return 0;
- if(size + addr > dev->size)
- size = dev->size - addr;
+ if(size > dev->max_size - addr)
+ size = dev->max_size - addr;
if(dev->fd >= 0)
return nand_dev_erase_file(dev, addr, size);
memset(&dev->data[addr], 0xff, size);
@@ -322,7 +483,7 @@ uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd)
/* I/O write */
static void nand_dev_write(void *opaque, target_phys_addr_t offset, uint32_t value)
{
- nand_dev_state *s = (nand_dev_state *)opaque;
+ nand_dev_controller_state *s = (nand_dev_controller_state *)opaque;
switch (offset) {
case NAND_DEV:
@@ -355,7 +516,7 @@ static void nand_dev_write(void *opaque, target_phys_addr_t offset, uint32_t val
/* I/O read */
static uint32_t nand_dev_read(void *opaque, target_phys_addr_t offset)
{
- nand_dev_state *s = (nand_dev_state *)opaque;
+ nand_dev_controller_state *s = (nand_dev_controller_state *)opaque;
nand_dev *dev;
switch (offset) {
@@ -389,10 +550,10 @@ static uint32_t nand_dev_read(void *opaque, target_phys_addr_t offset)
return dev->erase_size;
case NAND_DEV_SIZE_LOW:
- return (uint32_t)dev->size;
+ return (uint32_t)dev->max_size;
case NAND_DEV_SIZE_HIGH:
- return (uint32_t)(dev->size >> 32);
+ return (uint32_t)(dev->max_size >> 32);
default:
cpu_abort(cpu_single_env, "nand_dev_read: Bad offset %x\n", offset);
@@ -417,15 +578,15 @@ void nand_dev_init(uint32_t base)
{
int iomemtype;
static int instance_id = 0;
- nand_dev_state *s;
+ nand_dev_controller_state *s;
- s = (nand_dev_state *)qemu_mallocz(sizeof(nand_dev_state));
+ s = (nand_dev_controller_state *)qemu_mallocz(sizeof(nand_dev_controller_state));
iomemtype = cpu_register_io_memory(nand_dev_readfn, nand_dev_writefn, s);
cpu_register_physical_memory(base, 0x00000fff, iomemtype);
s->base = base;
register_savevm( "nand_dev", instance_id++, NAND_DEV_STATE_SAVE_VERSION,
- nand_dev_state_save, nand_dev_state_load, s);
+ nand_dev_controller_state_save, nand_dev_controller_state_load, s);
}
static int arg_match(const char *a, const char *b, size_t b_len)
@@ -597,7 +758,7 @@ void nand_add_dev(const char *arg)
}
dev->devname = devname;
dev->devname_len = devname_len;
- dev->size = dev_size;
+ dev->max_size = dev_size;
dev->data = malloc(dev->erase_size);
if(dev->data == NULL)
goto out_of_memory;