aboutsummaryrefslogtreecommitdiffstats
path: root/hw
diff options
context:
space:
mode:
authorOt ten Thije <ottenthije@google.com>2010-10-14 02:09:00 -0700
committerAndroid Code Review <code-review@android.com>2010-10-14 02:09:00 -0700
commit4820ac8ec0fa1a4124d364608568c1a3f5bdc9bb (patch)
tree3666aa0ed5136bc27cf93893400948bad8423ebd /hw
parent93be8ce1ebb3663bd389ae42e0c2f26b8277e21f (diff)
parentc51182e909275b7d89c1ed847eae40fdd8b6a33f (diff)
downloadexternal_qemu-4820ac8ec0fa1a4124d364608568c1a3f5bdc9bb.zip
external_qemu-4820ac8ec0fa1a4124d364608568c1a3f5bdc9bb.tar.gz
external_qemu-4820ac8ec0fa1a4124d364608568c1a3f5bdc9bb.tar.bz2
Merge "Save NAND disk state in snapshots."
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;