aboot: Add support for "compressed ext4" sparse images

Compressed ext4 file system sparse image format is defined by
AOSP and generation of these images is done using ext4_utils
which is also part of Android Open Source Project.

In order to transfer and flash large, empty ext4 filesystems
(userdata) in a reasonable amount of time, we need to store
the image in a sparse format that contains information about
which areas of the filesystem can be left unwritten.

Compressed ext4 file format:

All fields are unsigned little-endian File contains a file header,
followed by a series of chunks File header, chunk header, and chunk
data are all multiples of 4 bytes long

Header:
32 bit magic: 0xed26ff3a
16 bit major version (0x1) - reject images with higher major versions
16 bit minor version (0x0) - allow images with higher minor versions
16 bit file header size in bytes (28 in v1.0)
16 bit chunk header size in bytes (12 in v1.0)
32 bit block size in bytes, must be multiple of 4
32 bit total blocks in output file
32 bit total chunks in input file

Chunk:
16 bit chunk type:
  0xCAC1 raw
  0xCAC2 fill
  0xCAC3 don't care
  0xCAC4 crc
16 bits reserved (write as 0, ignore on read)
32 bit chunk size in blocks in output image
32 bit total size in bytes of chunk input file including chunk header and data
Data:
  for raw, raw data, size in blocks * block size in bytes
  for fill, 4 bytes of fill data

Change-Id: I5ee5c288dae9a8ae364502ae8f32fcab65d2274b
diff --git a/app/aboot/aboot.c b/app/aboot/aboot.c
index e574fb4..392bdae 100644
--- a/app/aboot/aboot.c
+++ b/app/aboot/aboot.c
@@ -47,6 +47,7 @@
 #include "recovery.h"
 #include "bootimg.h"
 #include "fastboot.h"
+#include "sparse_format.h"
 
 #define EXPAND(NAME) #NAME
 #define TARGET(NAME) EXPAND(NAME)
@@ -556,7 +557,7 @@
 }
 
 
-void cmd_flash_mmc(const char *arg, void *data, unsigned sz)
+void cmd_flash_mmc_img(const char *arg, void *data, unsigned sz)
 {
 	unsigned long long ptn = 0;
 	unsigned long long size = 0;
@@ -588,6 +589,122 @@
 	return;
 }
 
+void cmd_flash_mmc_sparse_img(const char *arg, void *data, unsigned sz)
+{
+	unsigned int chunk;
+	unsigned int chunk_data_sz;
+	sparse_header_t *sparse_header;
+	chunk_header_t *chunk_header;
+	uint32_t crc32 = 0;
+	uint32_t total_blocks = 0;
+	unsigned long long ptn = 0;
+	unsigned long long size = 0;
+
+	ptn = mmc_ptn_offset(arg);
+	if(ptn == 0) {
+		fastboot_fail("partition table doesn't exist");
+		return;
+	}
+
+	/* Read and skip over sparse image header */
+	sparse_header = (sparse_header_t *) data;
+	data += sparse_header->file_hdr_sz;
+	if(sparse_header->file_hdr_sz > sizeof(sparse_header_t))
+	{
+		/* Skip the remaining bytes in a header that is longer than
+		 * we expected.
+		 */
+		data += (sparse_header->file_hdr_sz - sizeof(sparse_header_t));
+	}
+
+	dprintf (INFO, "=== Sparse Image Header ===\n");
+	dprintf (INFO, "magic: 0x%x\n", sparse_header->magic);
+	dprintf (INFO, "major_version: 0x%x\n", sparse_header->major_version);
+	dprintf (INFO, "minor_version: 0x%x\n", sparse_header->minor_version);
+	dprintf (INFO, "file_hdr_sz: %d\n", sparse_header->file_hdr_sz);
+	dprintf (INFO, "chunk_hdr_sz: %d\n", sparse_header->chunk_hdr_sz);
+	dprintf (INFO, "blk_sz: %d\n", sparse_header->blk_sz);
+	dprintf (INFO, "total_blks: %d\n", sparse_header->total_blks);
+	dprintf (INFO, "total_chunks: %d\n", sparse_header->total_chunks);
+
+	/* Start processing chunks */
+	for (chunk=0; chunk<sparse_header->total_chunks; chunk++)
+	{
+		/* Read and skip over chunk header */
+		chunk_header = (chunk_header_t *) data;
+		data += sizeof(chunk_header_t);
+
+		dprintf (SPEW, "=== Chunk Header ===\n");
+		dprintf (SPEW, "chunk_type: 0x%x\n", chunk_header->chunk_type);
+		dprintf (SPEW, "chunk_data_sz: 0x%x\n", chunk_header->chunk_sz);
+		dprintf (SPEW, "total_size: 0x%x\n", chunk_header->total_sz);
+
+		if(sparse_header->chunk_hdr_sz > sizeof(chunk_header_t))
+		{
+			/* Skip the remaining bytes in a header that is longer than
+			 * we expected.
+			 */
+			data += (sparse_header->chunk_hdr_sz - sizeof(chunk_header_t));
+		}
+
+		chunk_data_sz = sparse_header->blk_sz * chunk_header->chunk_sz;
+		switch (chunk_header->chunk_type)
+		{
+			case CHUNK_TYPE_RAW:
+			if(chunk_header->total_sz != (sparse_header->chunk_hdr_sz +
+											chunk_data_sz))
+			{
+				fastboot_fail("Bogus chunk size for chunk type Raw");
+				return;
+			}
+
+			if(mmc_write(ptn + (total_blocks*sparse_header->blk_sz),
+								chunk_data_sz,
+								(unsigned int*)data))
+			{
+				fastboot_fail("flash write failure");
+				return;
+			}
+			total_blocks += chunk_header->chunk_sz;
+			data += chunk_data_sz;
+			break;
+
+			case CHUNK_TYPE_DONT_CARE:
+			case CHUNK_TYPE_CRC:
+			if(chunk_header->total_sz != sparse_header->chunk_hdr_sz)
+			{
+				fastboot_fail("Bogus chunk size for chunk type Dont Care");
+				return;
+			}
+			total_blocks += chunk_header->chunk_sz;
+			data += chunk_data_sz;
+			break;
+
+			fastboot_fail("Unknown chunk type");
+			return;
+		}
+	}
+
+    dprintf(INFO, "Wrote %d blocks, expected to write %d blocks\n",
+             total_blocks, sparse_header->total_blks);
+
+	fastboot_okay("");
+	return;
+}
+
+void cmd_flash_mmc(const char *arg, void *data, unsigned sz)
+{
+	sparse_header_t *sparse_header;
+	sparse_header = (sparse_header_t *) data;
+
+	if (sparse_header->magic != SPARSE_HEADER_MAGIC)
+		cmd_flash_mmc_img(arg, data, sz);
+	else
+		cmd_flash_mmc_sparse_img(arg, data, sz);
+
+	return;
+}
+
 void cmd_flash(const char *arg, void *data, unsigned sz)
 {
 	struct ptentry *ptn;