blob: b21405796c8b4c725814b3a14cdb16c558722417 [file] [log] [blame]
/*
* Copyright (c) 2013, Google, Inc. All rights reserved
* Copyright (c) 2014, Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#include <lib/ptable.h>
#include <lk/debug.h>
#include <lk/trace.h>
#include <assert.h>
#include <lk/err.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <lk/list.h>
#include <lib/bio.h>
#include <lib/cksum.h>
#include <lk/init.h>
#include <lk/console_cmd.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);
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
// aligned 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++;
}
}
static int cmd_ptable(int argc, const console_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;
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;
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) {
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);