[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/lib/ptable/ptable.c b/src/bsp/lk/lib/ptable/ptable.c
new file mode 100644
index 0000000..13fe935
--- /dev/null
+++ b/src/bsp/lk/lib/ptable/ptable.c
@@ -0,0 +1,937 @@
+/*
+ * Copyright (c) 2013, Google, Inc. All rights reserved
+ * Copyright (c) 2014, Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <lib/ptable.h>
+#include <debug.h>
+#include <trace.h>
+#include <assert.h>
+#include <err.h>
+#include <string.h>
+#include <malloc.h>
+#include <stdlib.h>
+#include <list.h>
+#include <lib/bio.h>
+#include <lib/cksum.h>
+#include <lk/init.h>
+
+#define LOCAL_TRACE 0
+
+#define PTABLE_MAGIC '1BTP'
+#define PTABLE_MIN_ENTRIES 16
+#define PTABLE_PART_NAME "ptable"
+
+struct ptable_header {
+    uint32_t magic;
+    uint32_t crc32;         /* (total ptable according to total_length, 0 where crc field is) */
+    uint32_t generation;    /* incremented by one every time its saved */
+    uint32_t total_length;  /* valid length of table, only covers entries that are used */
+};
+
+struct ptable_mem_entry {
+    struct list_node node;
+    struct ptable_entry entry;
+};
+
+static struct ptable_state {
+    bdev_t *bdev;
+    uint32_t gen;
+    struct list_node list;
+} ptable;
+
+#define PTABLE_HEADER_NUM_ENTRIES(header) (((header).total_length - sizeof(struct ptable_header)) / sizeof(struct ptable_entry))
+#define BAIL(__err) do { err = __err; goto bailout; } while (0)
+
+static inline size_t ptable_length(size_t entry_cnt)
+{
+    return sizeof(struct ptable_header) + (sizeof(struct ptable_entry) * entry_cnt);
+}
+
+static status_t validate_entry(const struct ptable_entry *entry)
+{
+    if (entry->offset > entry->offset + entry->length)
+        return ERR_GENERIC;
+    if (entry->offset + entry->length > (uint64_t)ptable.bdev->total_size)
+        return ERR_GENERIC;
+
+    uint i;
+    for (i = 0; i < sizeof(entry->name); i++)
+        if (entry->name[i] == 0)
+            break;
+
+    if (!i || (i >= sizeof(entry->name)))
+        return ERR_GENERIC;
+
+    return NO_ERROR;
+}
+
+static status_t ptable_write(void)
+{
+    uint8_t* buf = NULL;
+    bdev_t* bdev = NULL;
+    ssize_t err = ERR_GENERIC;
+
+    if (!ptable_found_valid())
+        return ERR_NOT_MOUNTED;
+
+    bdev = bio_open(PTABLE_PART_NAME);
+    if (!bdev)
+        return ERR_BAD_STATE;
+
+    /* count the number of entries in the list and calculate the total size */
+    size_t count = 0;
+    struct list_node *node;
+    list_for_every(&ptable.list, node) {
+        count++;
+    }
+    LTRACEF("%u entries\n", count);
+    size_t total_length = sizeof(struct ptable_header) + sizeof(struct ptable_entry) * count;
+
+    /* can we fit our partition table in our ptable subdevice? */
+    if (total_length > bdev->total_size)
+        BAIL(ERR_TOO_BIG);
+
+    /* allocate a buffer to hold it */
+    buf = malloc(total_length);
+    if (!buf)
+        BAIL(ERR_NO_MEMORY);
+
+    /* fill in a default header */
+    struct ptable_header *header = (struct ptable_header *)buf;
+    header->magic = PTABLE_MAGIC;
+    header->crc32 = 0;
+    header->generation = ptable.gen++;
+    header->total_length = total_length;
+
+    /* start the crc calculation */
+    header->crc32 = crc32(0, (void *)header, sizeof(*header));
+
+    /* start by writing the entries */
+    size_t off = sizeof(struct ptable_header);
+    struct ptable_mem_entry *mentry;
+    list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) {
+        const struct ptable_entry *entry = &mentry->entry;
+
+        memcpy(buf + off, entry, sizeof(struct ptable_entry));
+
+        /* update the header */
+        header->crc32 = crc32(header->crc32, (void *)entry, sizeof(struct ptable_entry));
+
+        off += sizeof(struct ptable_entry);
+    }
+
+    /* write it to the block device.  If the device has an erase geometry, start
+     * by erasing the partition.
+     */
+    if (bdev->geometry_count && bdev->geometry) {
+        /* This is a subdevice, it should have a homogeneous erase geometry */
+        DEBUG_ASSERT(1 == bdev->geometry_count);
+
+        ssize_t err = bio_erase(bdev, 0, bdev->total_size);
+        if (err != (ssize_t)bdev->total_size) {
+            LTRACEF("error %d erasing device\n", (int)err);
+            BAIL(ERR_IO);
+        }
+    }
+
+    err = bio_write(bdev, buf, 0, total_length);
+    if (err < (ssize_t)total_length) {
+        LTRACEF("error %d writing data to device\n", (int)err);
+        BAIL(ERR_IO);
+    }
+
+    LTRACEF("wrote ptable:\n");
+    if (LOCAL_TRACE)
+        hexdump(buf, total_length);
+
+    err = NO_ERROR;
+
+bailout:
+    if (bdev)
+        bio_close(bdev);
+
+    free(buf);
+
+    return err;
+}
+
+static void ptable_init(uint level)
+{
+    memset(&ptable, 0, sizeof(ptable));
+    list_initialize(&ptable.list);
+}
+
+LK_INIT_HOOK(ptable, &ptable_init, LK_INIT_LEVEL_THREADING);
+
+static void ptable_unpublish(struct ptable_mem_entry* mentry)
+{
+    if (mentry) {
+        bdev_t* bdev;
+
+        bdev = bio_open((char*)mentry->entry.name);
+        if (bdev) {
+            bio_unregister_device(bdev);
+            bio_close(bdev);
+        }
+
+        if (list_in_list(&mentry->node))
+            list_delete(&mentry->node);
+
+        free(mentry);
+    }
+}
+
+static void ptable_reset(void)
+{
+    /* walk through the partition list, clearing any entries */
+    struct ptable_mem_entry *mentry;
+    struct ptable_mem_entry *temp;
+    list_for_every_entry_safe(&ptable.list, mentry, temp, struct ptable_mem_entry, node) {
+        ptable_unpublish(mentry);
+    }
+
+    /* release our reference to our primary device */
+    if (NULL != ptable.bdev)
+        bio_close(ptable.bdev);
+
+    /* Reset initialize our bookkeeping */
+    ptable_init(LK_INIT_LEVEL_THREADING);
+}
+
+static void ptable_push_entry (struct ptable_mem_entry *mentry)
+{
+    DEBUG_ASSERT (mentry);
+
+    // iterator for the list
+    struct ptable_mem_entry *it_mentry;
+
+    // The ptable list must be ordered by offset, so let's find the correct
+    // spot for this entry
+    list_for_every_entry(&ptable.list, it_mentry, struct ptable_mem_entry, node) {
+        if (it_mentry->entry.offset > mentry->entry.offset) {
+            // push the entry and we are done !
+            list_add_before(&it_mentry->node, &mentry->node);
+            // All done
+            return;
+        }
+    }
+
+    // if we exist the loop, that means that the
+    // entry has not been added, let add it at the tail
+    list_add_tail(&ptable.list, &mentry->node);
+}
+
+static status_t ptable_publish(const struct ptable_entry* entry) {
+    status_t err;
+    struct ptable_mem_entry *mentry = NULL;
+
+    DEBUG_ASSERT(entry && ptable.bdev);
+    size_t block_mask = ((size_t)0x01 << ptable.bdev->block_shift) - 1;
+
+    err = validate_entry(entry);
+    if (err < 0) {
+        LTRACEF("entry failed valid check\n");
+        BAIL(ERR_NOT_FOUND);
+    }
+
+    // Make sure the partition does not already exist.
+    const char* part_name = (const char*)entry->name;
+    err = ptable_find(part_name, 0);
+    if (err >= 0) {
+        LTRACEF("entry \"%s\" already exists\n", part_name);
+        BAIL(ERR_ALREADY_EXISTS);
+    }
+
+    // make sure that the partition is aligned properly
+    if ((entry->offset & block_mask) || (entry->length & block_mask)) {
+        LTRACEF("Entry in parition (\"%s\") is misaligned "
+                "(off 0x%llx len 0x%llx blockmask 0x%zx\n",
+                part_name, entry->offset, entry->length, block_mask);
+        BAIL(ERR_BAD_STATE);
+    }
+
+    // make sure that length is non-zero and does not wrap
+    if ((entry->offset + entry->length) <= entry->offset) {
+        LTRACEF("Bad offset/length 0x%llx/0x%llx\n", entry->offset, entry->length);
+        BAIL(ERR_INVALID_ARGS);
+    }
+
+    // make sure entry can fit in the device
+    if ((entry->offset + entry->length) > (uint64_t)ptable.bdev->total_size) {
+        LTRACEF("outside of device\n");
+        BAIL(ERR_INVALID_ARGS);
+    }
+
+    /* create an in-memory copy and attempt to publish a subdevice for the
+     * partition
+     */
+    mentry = calloc(1, sizeof(struct ptable_mem_entry));
+    if (!mentry) {
+        LTRACEF("Out of memory\n");
+        BAIL(ERR_NO_MEMORY);
+    }
+
+    memcpy(&mentry->entry, entry, sizeof(struct ptable_entry));
+    err = bio_publish_subdevice(ptable.bdev->name, part_name,
+                                entry->offset >> ptable.bdev->block_shift,
+                                entry->length >> ptable.bdev->block_shift);
+    if (err < 0) {
+        LTRACEF("Failed to publish subdevice for \"%s\"\n", part_name);
+        goto bailout;
+    }
+
+    err = NO_ERROR;
+
+bailout:
+    /* If we failed to publish, clean up whatever we may have allocated.
+     * Otherwise, put our new entry on the in-memory list.
+     */
+    if (err < 0) {
+        ptable_unpublish(mentry);
+    } else {
+        ptable_push_entry (mentry);
+    }
+
+    return err;
+}
+
+static off_t ptable_adjust_request_for_erase_geometry(uint64_t  region_start,
+                                                      uint64_t  region_len,
+                                                      uint64_t* plength,
+                                                      bool      alloc_end)
+{
+    DEBUG_ASSERT(plength && ptable.bdev);
+
+    LTRACEF("[0x%llx, 0x%llx) len 0x%llx%s\n",
+            region_start, region_start + region_len, *plength, alloc_end ? " (alloc end)" : "");
+
+    uint64_t block_mask = ((uint64_t)0x1 << ptable.bdev->block_shift) - 1;
+    DEBUG_ASSERT(!(*plength & block_mask));
+    DEBUG_ASSERT(!(region_start & block_mask));
+    DEBUG_ASSERT(!(region_len & block_mask));
+
+    uint64_t region_end = region_start + region_len;
+    DEBUG_ASSERT(region_end >= region_start);
+
+    // Can we fit in the region at all?
+    if (*plength > region_len) {
+        LTRACEF("Request too large for region (0x%llx > 0x%llx)\n", *plength, region_len);
+        return ERR_TOO_BIG;
+    }
+
+    // If our block device does not have an erase geometry to obey, then great!
+    // No special modifications to the request are needed.  Just determine the
+    // offset based on if we are allocating from the start or the end.
+    if (!ptable.bdev->geometry_count || !ptable.bdev->geometry) {
+        off_t ret = alloc_end ? (region_start + region_len - *plength) : region_start;
+        LTRACEF("No geometry; allocating at [0x%llx, 0x%llx)\n", ret, ret + *plength);
+        return ret;
+    }
+
+    // Intersect each of the erase regions with the region being proposed and
+    // see if we can fit the allocation request in the intersection, after
+    // adjusting the intersection and requested length to multiples of and
+    // alligned to the erase block size.  Test the geometries back-to-front
+    // instead of front-to-back if alloc_end has been reqeusted.
+    for (size_t i = 0; i < ptable.bdev->geometry_count; ++i) {
+        size_t geo_index = alloc_end ?  (ptable.bdev->geometry_count - i - 1) : i;
+        const bio_erase_geometry_info_t* geo = ptable.bdev->geometry + geo_index;
+        uint64_t erase_mask = ((uint64_t)0x1 << geo->erase_shift) - 1;
+
+        LTRACEF("Considering erase region [0x%llx, 0x%llx) (erase size 0x%zx)\n",
+                geo->start, geo->start + geo->size, geo->erase_size);
+
+        // If the erase region and the allocation region do not intersect at
+        // all, just move on to the next region.
+        if (!bio_does_overlap(region_start, region_len, geo->start, geo->size)) {
+            LTRACEF("No overlap...\n");
+            continue;
+        }
+
+        // Compute the intersection of the request region with the erase region.
+        uint64_t erase_end = geo->start + geo->size;
+        uint64_t rstart    = MAX(region_start, (uint64_t)geo->start);
+        uint64_t rend      = MIN(region_end, erase_end);
+
+        // Align to erase unit boundaries.  Move the start of the intersected
+        // region up and the end of the intersected region down.
+        rstart = (rstart + erase_mask) & ~erase_mask;
+        rend = rend & ~erase_mask;
+
+        // Round the requested length up to a multiple of the erase unit.
+        uint64_t length = (*plength + erase_mask) & ~erase_mask;
+
+        LTRACEF("Trimmed and aligned request [0x%llx, 0x%llx) len 0x%llx%s\n",
+                rstart, rend, length, alloc_end ? " (alloc end)" : "");
+
+        // Is there enough space in the aligned intersection to hold the
+        // request?
+        uint64_t tmp = rstart + length;
+        if ((tmp < rstart) || (rend < tmp)) {
+            LTRACEF("Not enough space\n");
+            continue;
+        }
+
+        // Yay!  We found space for this allocation!  Adjust the requested
+        // length and return the approprate offset based on whether we want to
+        // allocate from the start or the end.
+        off_t ret;
+        *plength = length;
+        ret      = alloc_end ? (rend - length) : rstart;
+        LTRACEF("Allocating at [0x%llx, 0x%llx) (erase_size 0x%zx)\n",
+                ret, ret + *plength, geo->erase_size);
+        return ret;
+    }
+
+    // Looks like we didn't find a place to put this allocation.
+    LTRACEF("No location found!\n");
+    return ERR_INVALID_ARGS;
+}
+
+static off_t ptable_allocate(uint64_t* plength, uint flags)
+{
+    DEBUG_ASSERT(plength);
+
+    if (!ptable.bdev)
+        return ERR_BAD_STATE;
+
+    LTRACEF("length 0x%llx, flags 0x%x\n", *plength, flags);
+
+    uint64_t block_mask = ((uint64_t)0x1 << ptable.bdev->block_shift) - 1;
+    uint64_t length = (*plength + block_mask) & ~block_mask;
+    off_t offset = ERR_NOT_FOUND;
+    bool alloc_end = 0 != (flags & FLASH_PTABLE_ALLOC_END);
+
+    if (list_is_empty(&ptable.list)) {
+        /* If the ptable is empty, then we have the entire device to use for
+         * allocation.  Apply the erase geometry and return the result.
+         */
+        offset = ptable_adjust_request_for_erase_geometry(0,
+                                                          ptable.bdev->total_size,
+                                                          &length,
+                                                          alloc_end);
+        goto done;
+    }
+
+    const struct ptable_entry *lastentry = NULL;
+    struct ptable_mem_entry *mentry;
+    uint64_t region_start;
+    uint64_t region_len;
+    uint64_t test_len;
+    off_t    test_offset;
+
+    list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) {
+        const struct ptable_entry *entry = &mentry->entry;
+
+        // Figure out the region we are testing, then adjust the request
+        // based on the device erase geometry.
+        region_start = lastentry ? (lastentry->offset + lastentry->length): 0;
+        region_len   = entry->offset - region_start;
+        DEBUG_ASSERT((int64_t)region_len >= 0);
+
+        LTRACEF("Considering region [0x%llx, 0x%llx) between \"%s\" and \"%s\"\n",
+                region_start,
+                region_start + region_len,
+                lastentry ? (char*)lastentry->name : "<device start>",
+                entry->name);
+        lastentry = entry;
+
+        // Don't bother with the region if it is of zero length
+        if (!region_len)
+            continue;
+
+        test_len = length;
+        test_offset = ptable_adjust_request_for_erase_geometry(region_start,
+                                                               region_len,
+                                                               &test_len,
+                                                               alloc_end);
+
+        // If this region was no good, move onto the next one.
+        if (test_offset < 0)
+            continue;
+
+        // We found a possible answer, go ahead and record it.  If we are
+        // allocating from the front, then we are finished.  If we are
+        // attempting to allocate from the back, keep looking to see if
+        // there are other (better) answers.
+        offset = test_offset;
+        length = test_len;
+        if (!alloc_end)
+            goto done;
+    }
+
+    /* still looking... the final region to test goes from the end of the previous
+     * region to the end of the device.
+     */
+    DEBUG_ASSERT(lastentry); /* should always have a valid tail */
+
+    region_start = lastentry->offset + lastentry->length;
+    region_len   = ptable.bdev->total_size - region_start;
+    DEBUG_ASSERT((int64_t)region_len >= 0);
+
+    if (region_len) {
+        LTRACEF("Considering region [0x%llx, 0x%llx) between \"%s\" and \"%s\"\n",
+                region_start,
+                region_start + region_len,
+                lastentry->name,
+                "<device end>");
+        test_len = length;
+        test_offset = ptable_adjust_request_for_erase_geometry(region_start,
+                                                               region_len,
+                                                               &test_len,
+                                                               alloc_end);
+        if (test_offset >= 0) {
+            offset = test_offset;
+            length = test_len;
+        }
+    }
+
+done:
+    if (offset < 0) {
+        LTRACEF("Failed to find a suitable region of at least length %llu (err %lld)\n",
+                *plength, offset);
+    } else {
+        LTRACEF("Found region for %lld byte request @[%lld, %lld)\n",
+                *plength, offset, offset + length);
+        *plength = length;
+    }
+
+    return offset;
+}
+
+static status_t ptable_allocate_at(off_t _offset, uint64_t* plength)
+{
+    if (!ptable.bdev)
+        return ERR_BAD_STATE;
+
+    if ((_offset < 0) || !plength)
+        return ERR_INVALID_ARGS;
+
+    /* to make life easier, get our offset into unsigned */
+    uint64_t offset = (uint64_t)_offset;
+
+    /* Make certain the request was aligned to a program block boundary, and
+     * adjust the length to be a multiple of program blocks in size.
+     */
+    uint64_t block_mask = ((uint64_t)0x1 << ptable.bdev->block_shift) - 1;
+    if (offset & block_mask)
+        return ERR_INVALID_ARGS;
+
+    *plength = (*plength + block_mask) & ~block_mask;
+
+    /* Make sure the request is contained within the extent of the device
+     * itself.
+     */
+    if (!bio_contains_range(0, ptable.bdev->total_size, offset, *plength))
+        return ERR_INVALID_ARGS;
+
+    /* Adjust the request base on the erase geometry.  If the offset needs to
+     * move to accomadate the erase geometry, we cannot satisfy this request.
+     */
+    uint64_t new_offset = ptable_adjust_request_for_erase_geometry(offset,
+                                                                   ptable.bdev->total_size - offset,
+                                                                   plength,
+                                                                   false);
+    if (new_offset != offset)
+        return ERR_INVALID_ARGS;
+
+    /* Finally, check the adjusted request against all of the existing
+     * partitions.  The final region may not overlap an of the existing
+     * partitions.
+     */
+    struct ptable_mem_entry *mentry;
+    list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) {
+        const struct ptable_entry *entry = &mentry->entry;
+
+        if (bio_does_overlap(offset, *plength, entry->offset, entry->length))
+            return ERR_NOT_FOUND;
+    }
+
+    // Success.
+    return NO_ERROR;
+}
+
+status_t ptable_scan(const char* bdev_name, uint64_t offset)
+{
+    ssize_t err;
+    DEBUG_ASSERT(bdev_name);
+
+    ptable_reset();
+
+    /* Open a reference to the main block device */
+    ptable.bdev = bio_open(bdev_name);
+    if (NULL == ptable.bdev) {
+        LTRACEF("Failed to find device \"%s\"", bdev_name);
+        BAIL(ERR_NOT_FOUND);
+    }
+
+    /* validate the header */
+    struct ptable_header header;
+
+    err = bio_read(ptable.bdev, &header, offset, sizeof(header));
+    if (err < (ssize_t)sizeof(header)) {
+        LTRACEF("failed to read partition table header @%llu (%ld)\n", offset, err);
+        goto bailout;
+    }
+
+    if (LOCAL_TRACE)
+        hexdump(&header, sizeof(struct ptable_header));
+
+    if (header.magic != PTABLE_MAGIC) {
+        LTRACEF("failed magic test\n");
+        BAIL(ERR_NOT_FOUND);
+    }
+    if (header.total_length < sizeof(struct ptable_header)) {
+        LTRACEF("total length too short\n");
+        BAIL(ERR_NOT_FOUND);
+    }
+    if (header.total_length > ptable.bdev->block_size) {
+        LTRACEF("total length too long\n");
+        BAIL(ERR_NOT_FOUND);
+    }
+    if (((header.total_length - sizeof(struct ptable_header)) % sizeof(struct ptable_entry)) != 0) {
+        LTRACEF("total length not multiple of header + multiple of entry size\n");
+        BAIL(ERR_NOT_FOUND);
+    }
+
+    /* start a crc check by calculating the header */
+    uint32_t crc;
+    uint32_t saved_crc = header.crc32;
+    header.crc32 = 0;
+    crc = crc32(0, (void *)&header, sizeof(header));
+    header.crc32 = saved_crc;
+    bool found_ptable = false;
+
+    /* read the entries into memory */
+    off_t off = offset + sizeof(struct ptable_header);
+    for (uint i = 0; i < PTABLE_HEADER_NUM_ENTRIES(header); i++) {
+        struct ptable_entry entry;
+
+        /* read the next entry off the device */
+        err = bio_read(ptable.bdev, &entry, off, sizeof(entry));
+        if (err < 0) {
+            LTRACEF("failed to read entry\n");
+            goto bailout;
+        }
+
+        LTRACEF("looking at entry:\n");
+        if (LOCAL_TRACE)
+            hexdump(&entry, sizeof(entry));
+
+        /* Attempt to publish the entry */
+        err = ptable_publish(&entry);
+        if (err < 0) {
+            goto bailout;
+        }
+
+        /* If this was the "ptable" entry, was it in the right place? */
+        if (!strncmp((char*)entry.name, PTABLE_PART_NAME, sizeof(entry.name))) {
+            found_ptable = true;
+
+            if (entry.offset != offset) {
+                LTRACEF("\"ptable\" in the wrong location! (expected %lld got %lld)\n",
+                        offset, entry.offset);
+                BAIL(ERR_BAD_STATE);
+            }
+        }
+
+        /* append the crc */
+        crc = crc32(crc, (void *)&entry, sizeof(entry));
+
+        /* Move on to the next entry */
+        off += sizeof(struct ptable_entry);
+    }
+
+    if (header.crc32 != crc) {
+        LTRACEF("failed crc check at the end (0x%08x != 0x%08x)\n", header.crc32, crc);
+        BAIL(ERR_CRC_FAIL);
+    }
+
+    if (!found_ptable) {
+        LTRACEF("\"ptable\" partition not found\n");
+        BAIL(ERR_NOT_FOUND);
+    }
+
+    err = NO_ERROR;
+
+bailout:
+    if (err < 0)
+        ptable_reset();
+
+    return (status_t)err;
+}
+
+bool ptable_found_valid(void)
+{
+    return (NULL != ptable.bdev);
+}
+
+bdev_t *ptable_get_device(void)
+{
+    return ptable.bdev;
+}
+
+status_t ptable_find(const char *name, struct ptable_entry *_entry)
+{
+    struct ptable_mem_entry *mentry;
+    list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) {
+        const struct ptable_entry *entry = &mentry->entry;
+        if (strcmp(name, (void *)entry->name) == 0) {
+            /* copy the entry to the passed in pointer */
+            if (_entry) {
+                memcpy(_entry, entry, sizeof(struct ptable_entry));
+            }
+
+            return NO_ERROR;
+        }
+    }
+
+    return ERR_NOT_FOUND;
+}
+
+status_t ptable_create_default(const char* bdev_name, uint64_t offset)
+{
+    DEBUG_ASSERT(bdev_name);
+
+    /* Reset the system */
+    ptable_reset();
+    ptable.bdev = bio_open(bdev_name);
+    if (!ptable.bdev) {
+        LTRACEF("Failed to open \"%s\"\n", bdev_name);
+        return ERR_NOT_FOUND;
+    }
+
+    /* See if we can put the partition table partition at the requested
+     * location, and determine the size needed based on program block size and
+     * erase block geometry.
+     */
+    uint64_t len = ptable_length(PTABLE_MIN_ENTRIES);
+    status_t err = ptable_allocate_at(offset, &len);
+    if (err < 0) {
+        LTRACEF("Failed to allocate partition of len 0x%llx @ 0x%llx (err %d)\n",
+                len, offset, err);
+        goto bailout;
+    }
+
+    /* Publish the ptable partition */
+    struct ptable_entry ptable_entry;
+    memset(&ptable_entry, 0, sizeof(ptable_entry));
+    ptable_entry.offset = offset;
+    ptable_entry.length = len;
+    ptable_entry.flags  = 0;
+
+    strlcpy((char *)ptable_entry.name, PTABLE_PART_NAME, sizeof(ptable_entry.name));
+    err = ptable_publish(&ptable_entry);
+    if (err < 0) {
+        LTRACEF("Failed to publish ptable partition\n");
+        goto bailout;
+    }
+
+    /* Commit the partition table to storage */
+    err = ptable_write();
+    if (err < 0) {
+        LTRACEF("Failed to commit ptable\n");
+        goto bailout;
+    }
+
+bailout:
+    /* if we failed, reset the system. */
+    if (err < 0)
+        ptable_reset();
+
+    return err;
+}
+
+status_t ptable_remove(const char *name)
+{
+    DEBUG_ASSERT(ptable.bdev);
+
+    LTRACEF("name %s\n", name);
+
+    if (!ptable_found_valid())
+        return ERR_NOT_MOUNTED;
+
+    if (!name)
+        return ERR_INVALID_ARGS;
+
+    if (!strcmp(name, "ptable"))
+        return ERR_NOT_ALLOWED;
+
+    bool found = false;
+    struct ptable_mem_entry *mentry;
+    list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) {
+        const struct ptable_entry *entry = &mentry->entry;
+        if (strcmp(name, (void *)entry->name) == 0) {
+            ptable_unpublish(mentry);
+            found = true;
+            break;
+        }
+    }
+
+    if (!found)
+        return ERR_NOT_FOUND;
+
+    /* rewrite the page table */
+    status_t err = ptable_write();
+    return err;
+}
+
+status_t ptable_add(const char *name, uint64_t min_len, uint32_t flags)
+{
+    LTRACEF("name %s min_len 0x%llx flags 0x%x\n", name, min_len, flags);
+
+    if (!ptable_found_valid())
+        return ERR_NOT_MOUNTED;
+
+    /* see if the name is valid */
+    if (strlen(name) > MAX_FLASH_PTABLE_NAME_LEN - 1) {
+        LTRACEF("Name too long\n");
+        return ERR_INVALID_ARGS;
+    }
+
+    // Find a place for the requested partition, adjust the length as needed
+    off_t part_loc = ptable_allocate(&min_len, flags);
+    if (part_loc < 0) {
+        LTRACEF("Failed to usable find location.\n");
+        return (status_t)part_loc;
+    }
+
+    /* Attempt to publish the partition */
+    struct ptable_entry ptable_entry;
+    memset(&ptable_entry, 0, sizeof(ptable_entry));
+    ptable_entry.offset = part_loc;
+    ptable_entry.length = min_len;
+    ptable_entry.flags  = 0;
+    strlcpy((char *)ptable_entry.name, name, sizeof(ptable_entry.name));
+    status_t err = ptable_publish(&ptable_entry);
+    if (err < 0) {
+        LTRACEF("Failed to publish\n");
+        return err;
+    }
+
+    /* Commit the partition table */
+    err = ptable_write();
+    if (err < 0) {
+        LTRACEF("Failed to commit ptable\n");
+    }
+
+    return err;
+}
+
+void ptable_dump(void)
+{
+    int i = 0;
+    struct ptable_mem_entry *mentry;
+    list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) {
+        const struct ptable_entry *entry = &mentry->entry;
+
+        printf("%d: %16s off 0x%016llx len 0x%016llx flags 0x%08x\n",
+               i, entry->name, entry->offset, entry->length, entry->flags);
+        i++;
+    }
+}
+
+#if WITH_LIB_CONSOLE
+
+#include <lib/console.h>
+
+static int cmd_ptable(int argc, const cmd_args *argv)
+{
+    if (argc < 2) {
+notenoughargs:
+        printf("not enough arguments\n");
+usage:
+        printf("usage: %s scan <bio_device> <offset>\n", argv[0].str);
+        printf("usage: %s default <bio_device> <offset>\n", argv[0].str);
+        printf("usage: %s list\n", argv[0].str);
+        printf("usage: %s add <name> <length> <flags>\n", argv[0].str);
+        printf("usage: %s remove <name>\n", argv[0].str);
+        printf("usage: %s alloc <len>\n", argv[0].str);
+        printf("usage: %s allocend <len>\n", argv[0].str);
+        printf("usage: %s write\n", argv[0].str);
+        return -1;
+    }
+
+    status_t err;
+    if (!strcmp(argv[1].str, "scan")) {
+        if (argc < 4) goto notenoughargs;
+        status_t err = ptable_scan(argv[2].str, argv[3].u);
+        printf("ptable_scan returns %d\n", err);
+    } else if (!strcmp(argv[1].str, "default")) {
+        if (argc < 4) goto notenoughargs;
+        status_t err = ptable_create_default(argv[2].str, argv[3].u);
+        printf("ptable_create_default returns %d\n", err);
+    } else if (!strcmp(argv[1].str, "list")) {
+        ptable_dump();
+    } else if (!strcmp(argv[1].str, "nuke")) {
+        bdev_t* ptable_dev = bio_open(PTABLE_PART_NAME);
+
+        if (ptable_dev) {
+            status_t err;
+            err = bio_erase(ptable_dev, 0, ptable_dev->total_size);
+            if (err < 0) {
+                printf("ptable nuke failed (err %d)\n", err);
+            } else {
+                printf("ptable nuke OK\n");
+            }
+            bio_close(ptable_dev);
+        } else {
+            printf("Failed to find ptable device\n");
+        }
+    } else if (!strcmp(argv[1].str, "add")) {
+        if (argc < 5) goto notenoughargs;
+        err = ptable_add(argv[2].str, argv[3].u, argv[4].u);
+        if (err < NO_ERROR)
+            printf("ptable_add returns err %d\n", err);
+    } else if (!strcmp(argv[1].str, "remove")) {
+        if (argc < 3) goto notenoughargs;
+        ptable_remove(argv[2].str);
+    } else if (!strcmp(argv[1].str, "alloc") ||
+               !strcmp(argv[1].str, "allocend")) {
+        if (argc < 3) goto notenoughargs;
+
+        uint     flags = !strcmp(argv[1].str, "allocend") ? FLASH_PTABLE_ALLOC_END : 0;
+        uint64_t len   = argv[2].u;
+        off_t    off   = ptable_allocate(&len, flags);
+
+        if (off < 0) {
+            printf("%s of 0x%lx failed (err %lld)\n",
+                   argv[1].str, argv[2].u, off);
+        } else {
+            printf("%s of 0x%lx gives [0x%llx, 0x%llx)\n",
+                    argv[1].str, argv[2].u, off, off + len);
+        }
+    } else if (!strcmp(argv[1].str, "write")) {
+        printf("ptable_write result %d\n", ptable_write());
+    } else {
+        goto usage;
+    }
+
+    return 0;
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("ptable", "commands for manipulating the flash partition table", &cmd_ptable)
+STATIC_COMMAND_END(ptable);
+
+#endif // WITH_LIB_CONSOLE
+