From eab28163024ae0f21e48103019b92bd16ee0f685 Mon Sep 17 00:00:00 2001 From: John Grossman Date: Thu, 18 Jun 2015 15:49:35 -0700 Subject: [PATCH] [lib][ptable] Modify ptable to respect erase geometries. Modify the existing ptable code to pay attention to bio device erase geometry if present. Significant changes include... + Partitions must be allocated on both program and erase block boundaries. + Partitions lengths must be multiples of both program and erase block sizes. + Partitions may not span non-homogeneous regions of erase geometry. + ptable_allocate as been made private. + Users may no longer explicitly select a position for partitions to be added, they may only ask for the partition to be allocated at the begining or the end of the block device. + A bio subdevice will be registered for each active partition in the system. Users are encouraged to add their partition using ptable_add, and then open a handle to the subdevice using bio_open. The bio subdevice will prevent accidental scribbling outside of the partition lines, and also advertise the partition erase size. Signed-off-by: John Grossman Change-Id: I09bf9038d210ff8be42d44166ab92c789872e036 --- app/lkboot/commands.c | 12 +- app/zynq-common/init.c | 14 +- include/lib/ptable.h | 21 +- lib/ptable/ptable.c | 896 ++++++++++++++++++++++++++--------------- 4 files changed, 597 insertions(+), 346 deletions(-) diff --git a/app/lkboot/commands.c b/app/lkboot/commands.c index d0a84fa2..c6c45f39 100644 --- a/app/lkboot/commands.c +++ b/app/lkboot/commands.c @@ -295,17 +295,7 @@ int lkb_handle_command(lkb_t *lkb, const char *cmd, const char *arg, size_t len, if (ptable_find(arg, &entry) < 0) { size_t plen = len; /* doesn't exist, make one */ -#if PLATFORM_ZYNQ - /* XXX not really the right place, should be in the ptable/bio layer */ - plen = ROUNDUP(plen, 256*1024); -#endif - off_t off = ptable_allocate(plen, 0); - if (off < 0) { - *result = "no space to allocate partition"; - return -1; - } - - if (ptable_add(arg, off, plen, 0) < 0) { + if (ptable_add(arg, plen, 0) < 0) { *result = "error creating partition"; return -1; } diff --git a/app/zynq-common/init.c b/app/zynq-common/init.c index e765723f..bec6ad8c 100644 --- a/app/zynq-common/init.c +++ b/app/zynq-common/init.c @@ -42,6 +42,8 @@ #include #endif +#define BLOCK_DEVICE_NAME "spi0" + static void zynq_common_target_init(uint level) { status_t err; @@ -49,11 +51,11 @@ static void zynq_common_target_init(uint level) /* zybo has a spiflash on qspi */ spiflash_detect(); - bdev_t *spi = bio_open("spi0"); + bdev_t *spi = bio_open(BLOCK_DEVICE_NAME); if (spi) { /* find or create a partition table at the start of flash */ - if (ptable_scan(spi, 0) < 0) { - ptable_create_default(spi, 0); + if (ptable_scan(BLOCK_DEVICE_NAME, 0) < 0) { + ptable_create_default(BLOCK_DEVICE_NAME, 0); } struct ptable_entry entry = { 0 }; @@ -61,7 +63,7 @@ static void zynq_common_target_init(uint level) /* find and recover sysparams */ if (ptable_find("sysparam", &entry) < 0) { /* didn't find sysparam partition, create it */ - ptable_add("sysparam", 0x1000, 0x1000, 0); + ptable_add("sysparam", 0x1000, 0); ptable_find("sysparam", &entry); } @@ -81,7 +83,7 @@ static void zynq_common_target_init(uint level) } /* create bootloader partition if it does not exist */ - ptable_add("bootloader", 0x20000, 0x40000, 0); + ptable_add("bootloader", 0x40000, 0); #if LK_DEBUGLEVEL > 1 printf("flash partition table:\n"); @@ -111,7 +113,7 @@ static void zynq_common_target_init(uint level) if (ptr) { bootimage_open(ptr, bootimage_size, &bi); } - } else if (!strcmp(device, "spi0")) { + } else if (!strcmp(device, BLOCK_DEVICE_NAME)) { /* we were loaded from spi flash, go look at it to see if we can find it */ if (spi) { void *ptr = 0; diff --git a/include/lib/ptable.h b/include/lib/ptable.h index 44ca14b2..79a4fd01 100644 --- a/include/lib/ptable.h +++ b/include/lib/ptable.h @@ -27,12 +27,8 @@ #include #include -status_t ptable_scan(bdev_t *bdev, uint64_t offset); - -bool ptable_found_valid(void); -bdev_t *ptable_get_device(void); - #define MAX_FLASH_PTABLE_NAME_LEN 12 +#define FLASH_PTABLE_ALLOC_END 0x1 struct ptable_entry { uint64_t offset; @@ -41,14 +37,11 @@ struct ptable_entry { uint8_t name[MAX_FLASH_PTABLE_NAME_LEN]; }; +bool ptable_found_valid(void); +bdev_t* ptable_get_device(void); +status_t ptable_scan(const char* bdev_name, uint64_t offset); status_t ptable_find(const char *name, struct ptable_entry *entry) __NONNULL((1)); - -status_t ptable_create_default(bdev_t *bdev, uint64_t offset) __NONNULL(); -status_t ptable_add(const char *name, uint64_t offset, uint64_t len, uint32_t flags) __NONNULL(); +status_t ptable_create_default(const char* bdev_name, uint64_t offset) __NONNULL(); +status_t ptable_add(const char *name, uint64_t min_len, uint32_t flags) __NONNULL(); status_t ptable_remove(const char *name) __NONNULL(); -void ptable_dump(void); - -#define FLASH_PTABLE_ALLOC_END 0x1 -off_t ptable_allocate(uint64_t length, uint flags); -off_t ptable_allocate_at(off_t offset, uint64_t length); - +void ptable_dump(void); diff --git a/lib/ptable/ptable.c b/lib/ptable/ptable.c index 11293613..428a53ec 100644 --- a/lib/ptable/ptable.c +++ b/lib/ptable/ptable.c @@ -37,6 +37,8 @@ #define LOCAL_TRACE 0 #define PTABLE_MAGIC '1BTP' +#define PTABLE_MIN_ENTRIES 16 +#define PTABLE_PART_NAME "ptable" struct ptable_header { uint32_t magic; @@ -45,28 +47,24 @@ struct ptable_header { uint32_t total_length; /* valid length of table, only covers entries that are used */ }; -static struct ptable_state { - bool valid; - bdev_t *bdev; - - uint64_t offset; - uint32_t gen; - - struct list_node list; -} ptable; - struct ptable_mem_entry { struct list_node node; struct ptable_entry entry; }; -#define PTABLE_HEADER_NUM_ENTRIES(header) (((header).total_length - sizeof(struct ptable_header)) / sizeof(struct ptable_entry)) -#define ENTRY_NUM_TO_OFFSET(num) (sizeof(struct ptable_header) + sizeof(struct ptable_entry) * (num)) -#define ENTRY_NUM_TO_ENTRY(header, num) ((struct ptable_entry *)(((uint8_t *)(header)) + ENTRY_NUM_TO_OFFSET(num))) +static struct ptable_state { + bdev_t *bdev; + uint32_t gen; + struct list_node list; +} ptable; -#define FOR_ALL_PTABLE_ENTRIES \ - struct ptable_entry *entry = (void *)(PTABLE_HEADER + 1); \ - for (uint i = 0; i < PTABLE_HEADER_NUM_ENTRIES(*PTABLE_HEADER); i++, entry++) +#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) { @@ -75,20 +73,29 @@ static status_t validate_entry(const struct ptable_entry *entry) if (entry->offset + entry->length > (uint64_t)ptable.bdev->total_size) return ERR_GENERIC; - bool nullterm = false; - for (uint i = 0; i < sizeof(entry->name); i++) { + uint i; + for (i = 0; i < sizeof(entry->name); i++) if (entry->name[i] == 0) - nullterm = true; - } - if (!nullterm) + break; + + if (!i || (i >= sizeof(entry->name))) return ERR_GENERIC; return NO_ERROR; } -static status_t write_ptable(void) +static status_t ptable_write(void) { - ptable.valid = false; + 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; @@ -99,11 +106,14 @@ static status_t write_ptable(void) 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 */ - uint8_t *buf = malloc(total_length); - if (!buf) { - return ERR_NO_MEMORY; - } + buf = malloc(total_length); + if (!buf) + BAIL(ERR_NO_MEMORY); /* fill in a default header */ struct ptable_header *header = (struct ptable_header *)buf; @@ -129,65 +139,435 @@ static status_t write_ptable(void) off += sizeof(struct ptable_entry); } - /* write it to the block device */ - ssize_t err = bio_erase(ptable.bdev, ptable.offset, 4096); // XXX get erase size from bdev - if (err < 4096) { - TRACEF("error %d erasing device\n", (int)err); - free(buf); - return ERR_IO; + /* 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(ptable.bdev, buf, ptable.offset, total_length); + err = bio_write(bdev, buf, 0, total_length); if (err < (ssize_t)total_length) { - TRACEF("error %d writing data to device\n", (int)err); - free(buf); - return ERR_IO; + 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); - ptable.valid = true; - - return NO_ERROR; -} - -static void clear_ptable_list(void) -{ - /* walk through the partition list, clearing all 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) { - list_delete(&mentry->node); - free(mentry); - } + return err; } static void ptable_init(uint level) { - ptable.valid = false; + memset(&ptable, 0, sizeof(ptable)); list_initialize(&ptable.list); } LK_INIT_HOOK(ptable, &ptable_init, LK_INIT_LEVEL_THREADING); -status_t ptable_scan(bdev_t *bdev, uint64_t offset) +static void ptable_unpublish(struct ptable_mem_entry* mentry) { - DEBUG_ASSERT(bdev); + if (mentry) { + bdev_t* bdev; - ptable.valid = false; - ptable.bdev = bdev; - ptable.offset = offset; + 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 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 { + list_add_tail(&ptable.list, &mentry->node); + } + + 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 : "", + 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, + ""); + 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; - ssize_t err = bio_read(bdev, &header, ptable.offset, sizeof(header)); + err = bio_read(ptable.bdev, &header, offset, sizeof(header)); if (err < (ssize_t)sizeof(header)) { - return err; + LTRACEF("failed to read partition table header @%llu (%ld)\n", offset, err); + goto bailout; } if (LOCAL_TRACE) @@ -195,19 +575,19 @@ status_t ptable_scan(bdev_t *bdev, uint64_t offset) if (header.magic != PTABLE_MAGIC) { LTRACEF("failed magic test\n"); - return ERR_NOT_FOUND; + BAIL(ERR_NOT_FOUND); } if (header.total_length < sizeof(struct ptable_header)) { LTRACEF("total length too short\n"); - return ERR_NOT_FOUND; + BAIL(ERR_NOT_FOUND); } if (header.total_length > ptable.bdev->block_size) { LTRACEF("total length too long\n"); - return ERR_NOT_FOUND; + 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"); - return ERR_NOT_FOUND; + BAIL(ERR_NOT_FOUND); } /* start a crc check by calculating the header */ @@ -216,74 +596,70 @@ status_t ptable_scan(bdev_t *bdev, uint64_t offset) header.crc32 = 0; crc = crc32(0, (void *)&header, sizeof(header)); header.crc32 = saved_crc; + bool found_ptable = false; /* read the entries into memory */ - bool seen_ptable_entry = false; - off_t off = ptable.offset + sizeof(struct ptable_header); + 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(bdev, &entry, off, sizeof(entry)); + err = bio_read(ptable.bdev, &entry, off, sizeof(entry)); if (err < 0) { LTRACEF("failed to read entry\n"); - return err; + goto bailout; } LTRACEF("looking at entry:\n"); if (LOCAL_TRACE) hexdump(&entry, sizeof(entry)); - status_t err = validate_entry(&entry); + /* Attempt to publish the entry */ + err = ptable_publish(&entry); if (err < 0) { - LTRACEF("entry failed valid check\n"); - return ERR_NOT_FOUND; + 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)); - /* if one of them is named "ptable", make sure it is in the right spot */ - if (!strcmp((char *)entry.name, "ptable")) { - if (entry.offset == ptable.offset && entry.length == ptable.bdev->block_size) { - seen_ptable_entry = true; - } - } - - /* create an in-memory copy */ - struct ptable_mem_entry *mentry; - mentry = malloc(sizeof(struct ptable_mem_entry)); - if (!mentry) { - clear_ptable_list(); - return ERR_NO_MEMORY; - } - - memcpy(&mentry->entry, &entry, sizeof(struct ptable_entry)); - - list_add_tail(&ptable.list, &mentry->node); - + /* Move on to the next entry */ off += sizeof(struct ptable_entry); } if (header.crc32 != crc) { - TRACEF("failed crc check at the end\n"); - clear_ptable_list(); - return ERR_NOT_FOUND; + LTRACEF("failed crc check at the end (0x%08x != 0x%08x)\n", header.crc32, crc); + BAIL(ERR_CRC_FAIL); } - if (!seen_ptable_entry) { - clear_ptable_list(); - return ERR_NOT_FOUND; + if (!found_ptable) { + LTRACEF("\"ptable\" partition not found\n"); + BAIL(ERR_NOT_FOUND); } - ptable.valid = true; + err = NO_ERROR; - return NO_ERROR; +bailout: + if (err < 0) + ptable_reset(); + + return (status_t)err; } bool ptable_found_valid(void) { - return ptable.valid; + return (NULL != ptable.bdev); } bdev_t *ptable_get_device(void) @@ -293,9 +669,6 @@ bdev_t *ptable_get_device(void) status_t ptable_find(const char *name, struct ptable_entry *_entry) { - if (!ptable.valid) - return ERR_NOT_FOUND; - struct ptable_mem_entry *mentry; list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) { const struct ptable_entry *entry = &mentry->entry; @@ -312,24 +685,53 @@ status_t ptable_find(const char *name, struct ptable_entry *_entry) return ERR_NOT_FOUND; } -status_t ptable_create_default(bdev_t *bdev, uint64_t offset) +status_t ptable_create_default(const char* bdev_name, uint64_t offset) { - ptable.bdev = bdev; - ptable.gen = 0; - ptable.offset = offset; + DEBUG_ASSERT(bdev_name); - /* clear the old entries */ - clear_ptable_list(); + /* 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; + } - /* mark the ptable valid so ptable_add will continue */ - ptable.valid = true; + /* 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; + } - /* create a new entry with a pointer to ourselves, and flush the table */ - status_t err = ptable_add("ptable", ptable.offset, ptable.bdev->block_size, 0); + /* Publish the ptable partition */ + struct ptable_entry ptable_entry; + ptable_entry.offset = offset; + ptable_entry.length = len; + ptable_entry.flags = 0; + strncpy((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; + } - /* if we failed, make sure we're properly marked invalid */ + /* 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.valid = false; + ptable_reset(); return err; } @@ -340,8 +742,9 @@ status_t ptable_remove(const char *name) LTRACEF("name %s\n", name); - if (!ptable.valid) - return ERR_INVALID_ARGS; + if (!ptable_found_valid()) + return ERR_NOT_MOUNTED; + if (!name) return ERR_INVALID_ARGS; @@ -353,7 +756,7 @@ status_t ptable_remove(const char *name) 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) { - list_delete(&mentry->node); + ptable_unpublish(mentry); found = true; break; } @@ -362,202 +765,52 @@ status_t ptable_remove(const char *name) if (!found) return ERR_NOT_FOUND; - /* delete the entry */ - DEBUG_ASSERT(mentry); - free(mentry); - /* rewrite the page table */ - status_t err = write_ptable(); + status_t err = ptable_write(); return err; } -status_t ptable_add(const char *name, uint64_t offset, uint64_t len, uint32_t flags) +status_t ptable_add(const char *name, uint64_t min_len, uint32_t flags) { - status_t err; - DEBUG_ASSERT(ptable.bdev); + LTRACEF("name %s min_len 0x%llx flags 0x%x\n", name, min_len, flags); - LTRACEF("name %s offset 0x%llx len 0x%llx flags 0x%x\n", name, offset, len, flags); - - if (!ptable.valid) - return ERR_INVALID_ARGS; + if (!ptable_found_valid()) + return ERR_NOT_MOUNTED; /* see if the name is valid */ - if (strlen(name) > MAX_FLASH_PTABLE_NAME_LEN - 1) - return ERR_INVALID_ARGS; - - /* see if it already exists */ - if (ptable_find(name, NULL) == NO_ERROR) - return ERR_ALREADY_EXISTS; - - /* see if the offset and length are aligned */ - if (!IS_ALIGNED(offset, ptable.bdev->block_size)) { - LTRACEF("unaligned offset\n"); + if (strlen(name) > MAX_FLASH_PTABLE_NAME_LEN - 1) { + LTRACEF("Name too long\n"); return ERR_INVALID_ARGS; } - /* check to see if it has a bogus size (0 len or wraparound) */ - if (offset >= offset + len) { - LTRACEF("bogus size\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; } - /* make sure its within the bounds of the device */ - if (offset + len > (uint64_t)ptable.bdev->total_size) { - LTRACEF("outside of device\n"); - return ERR_INVALID_ARGS; + /* Attempt to publish the partition */ + struct ptable_entry ptable_entry; + ptable_entry.offset = part_loc; + ptable_entry.length = min_len; + ptable_entry.flags = 0; + strncpy((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; } - len = ROUNDUP(len, ptable.bdev->block_size); - - /* try to find its slot */ - struct list_node *insert_before = NULL; - struct ptable_mem_entry *mentry; - list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) { - const struct ptable_entry *entry = &mentry->entry; - - /* check to see if we overlap */ - if (bio_does_overlap(offset, len, entry->offset, entry->length)) { - LTRACEF("overlaps with existing partition\n"); - return ERR_INVALID_ARGS; - } - - /* see if we fit before this entry */ - if (offset + len <= entry->offset) { - insert_before = &mentry->node; - break; - } + /* Commit the partition table */ + err = ptable_write(); + if (err < 0) { + LTRACEF("Failed to commit ptable\n"); } - /* create a struct */ - struct ptable_mem_entry *newentry = malloc(sizeof(struct ptable_mem_entry)); - if (!newentry) - return ERR_NO_MEMORY; - - newentry->entry.offset = offset; - newentry->entry.length = len; - newentry->entry.flags = flags; - memset(newentry->entry.name, 0, sizeof(newentry->entry.name)); - strlcpy((char *)newentry->entry.name, name, sizeof(newentry->entry.name)); - - LTRACEF("new entry at %p\n", newentry); - - /* validate it */ - if (validate_entry(&newentry->entry) < 0) { - free(newentry); - return ERR_INVALID_ARGS; - } - - /* add it to the list */ - if (insert_before) - list_add_before(insert_before, &newentry->node); - else - list_add_tail(&ptable.list, &newentry->node); - - /* recalc crc and write */ - err = write_ptable(); - return err; } -off_t ptable_allocate(uint64_t length, uint flags) -{ - DEBUG_ASSERT(ptable.bdev); - - LTRACEF("length 0x%llx, flags 0x%x\n", length, flags); - - if (!ptable.valid) - return ERR_INVALID_ARGS; - - length = ROUNDUP(length, ptable.bdev->block_size); - - /* walk through the existing table making sure it's not a duplicate and where we'll put it */ - off_t offset = ERR_NOT_FOUND; - -#define ALLOC_END (flags & FLASH_PTABLE_ALLOC_END) - - if (list_is_empty(&ptable.list)) { - /* if the ptable is empty, see if we can simply fit in flash and alloc at the start or end */ - LTRACEF("empty list\n"); - if (length <= (uint64_t)ptable.bdev->total_size) { - offset = ALLOC_END ? ptable.bdev->total_size - length : 0; - LTRACEF("spot at 0x%llx\n", offset); - } - } else { - const struct ptable_entry *lastentry = NULL; - struct ptable_mem_entry *mentry; - list_for_every_entry(&ptable.list, mentry, struct ptable_mem_entry, node) { - const struct ptable_entry *entry = &mentry->entry; - LTRACEF("looking at entry %p: offset 0x%llx, length 0x%llx\n", entry, entry->offset, entry->length); - if (!lastentry) { - /* can it fit before the first one? */ - LTRACEF("first entry\n"); - if (entry->offset >= length) { - offset = ALLOC_END ? entry->offset - length : 0; - if (!ALLOC_END) goto done; - } - } else { - LTRACEF("not first entry, lastentry %p: offset 0x%llx, length 0x%llx\n", - lastentry, lastentry->offset, lastentry->length); - - if (entry->offset - (lastentry->offset + lastentry->length) >= length) { - /* space between the last entry and this one */ - offset = ALLOC_END ? entry->offset - length : lastentry->offset + lastentry->length; - if (!ALLOC_END) goto done; - } - } - lastentry = entry; - } - - /* didn't find a slot */ - - /* see if we can fit off the end */ - DEBUG_ASSERT(lastentry); /* should always have a valid tail */ - - if (lastentry->offset + lastentry->length + length <= (uint64_t)ptable.bdev->total_size) - offset = ALLOC_END - ? (uint64_t)ptable.bdev->total_size - length - : lastentry->offset + lastentry->length; - } - -#undef ALLOC_END - -done: - LTRACEF("returning 0x%llx\n", offset); - return offset; -} - -off_t ptable_allocate_at(off_t _offset, uint64_t length) -{ - DEBUG_ASSERT(ptable.bdev); - - if (!ptable.valid) - return ERR_INVALID_ARGS; - - if (_offset < 0) - return ERR_INVALID_ARGS; - - /* to make life easier, get our offset into unsigned */ - uint64_t offset = (uint64_t)_offset; - - length = ROUNDUP(length, ptable.bdev->block_size); - - if (offset + length < offset || offset + length > (uint64_t)ptable.bdev->total_size) - return ERR_INVALID_ARGS; - - /* check all ptable entries for overlap with the requested spot */ - 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 (entry->offset < offset && entry->offset + entry->length > offset) - return ERR_NOT_FOUND; - - if (entry->offset >= offset && entry->offset < offset + length) - return ERR_NOT_FOUND; - } - - return offset; -} - void ptable_dump(void) { int i = 0; @@ -584,52 +837,65 @@ usage: printf("usage: %s scan \n", argv[0].str); printf("usage: %s default \n", argv[0].str); printf("usage: %s list\n", argv[0].str); - printf("usage: %s add \n", argv[0].str); + printf("usage: %s add \n", argv[0].str); printf("usage: %s remove \n", argv[0].str); printf("usage: %s alloc \n", argv[0].str); printf("usage: %s allocend \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; - bdev_t *bdev = bio_open(argv[2].str); - if (!bdev) { - printf("couldn't open bdev\n"); - return ERR_NOT_FOUND; - } - status_t err = ptable_scan(bdev, argv[3].u); + 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; - bdev_t *bdev = bio_open(argv[2].str); - if (!bdev) { - printf("couldn't open bdev\n"); - return ERR_NOT_FOUND; - } - status_t err = ptable_create_default(bdev, argv[3].u); + 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")) { - bio_erase(ptable.bdev, ptable.offset, ptable.bdev->block_size); + 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 < 6) goto notenoughargs; - err = ptable_add(argv[2].str, argv[3].u, argv[4].u, argv[5].u); + 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")) { + } else if (!strcmp(argv[1].str, "alloc") || + !strcmp(argv[1].str, "allocend")) { if (argc < 3) goto notenoughargs; - off_t off = ptable_allocate(argv[2].u, 0); - printf("off 0x%llx\n", off); - } else if (!strcmp(argv[1].str, "allocend")) { - if (argc < 3) goto notenoughargs; - off_t off = ptable_allocate(argv[2].u, FLASH_PTABLE_ALLOC_END); - printf("off 0x%llx\n", off); + + 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; }