Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/backup.c
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,7 @@ static int restore_backup(const char *arg, bool skip_env, bool force) {
mtd.env_dev = -1;
}
enum_mtd_info(&mtd, cb_mtd_restore);
mtd.totalsz = mtd_flash_size();
if (skip_env) {
if (mtd.env_dev != -1)
printf("U-Boot env detected at #%d/0x%x\n", mtd.env_dev,
Expand Down Expand Up @@ -1506,6 +1507,7 @@ static int do_upgrade(const char *filename, bool force, bool wipe_settings) {

mtd_restore_ctx_t mtd = {.env_dev = -1};
enum_mtd_info(&mtd, cb_mtd_restore);
mtd.totalsz = mtd_flash_size();
if (mtd.env_dev == -1) {
fprintf(stderr, "Cannot find env, internal error\n");
return 1;
Expand Down
19 changes: 16 additions & 3 deletions src/mtd.c
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,11 @@ bool mtd_flash_offset(int num, const char *name, uint64_t size,
return known;
}

// What the last enum_mtd_info() found the partitions to cover
static uint64_t flash_size;

uint64_t mtd_flash_size() { return flash_size; }

void enum_mtd_info(void *ctx, cb_mtd cb) {
FILE *fp;
char dev[80];
Expand Down Expand Up @@ -506,9 +511,17 @@ void enum_mtd_info(void *ctx, cb_mtd cb) {
idx[m++] = i;
}
mtd_mark_aliases(m, offs, has_off, sizes, alias);
for (int k = 0; k < m; k++)
if (alias[k])
int kept = 0;
for (int k = 0; k < m; k++) {
if (alias[k]) {
mtds[idx[k]].valid = false;
continue;
}
offs[kept] = offs[k];
has_off[kept] = has_off[k];
sizes[kept++] = sizes[k];
}
flash_size = mtd_covered_size(kept, offs, has_off, sizes);

for (int i = 0; i < n; i++) {
if (mtds[i].valid && !cb(mtds[i].i, mtds[i].name, &mtds[i].mtd, ctx))
Expand All @@ -533,7 +546,7 @@ cJSON *get_mtd_info() {
cJSON *j_inner = ctx.json;

if (ctx.totalsz)
ADD_PARAM_FMT("size", "%dM", ctx.totalsz / 1024 / 1024);
ADD_PARAM_FMT("size", "%dM", (int)(mtd_flash_size() / 1024 / 1024));

if (hal_fmc_mode) {
const char *fmc_mode = hal_fmc_mode();
Expand Down
3 changes: 3 additions & 0 deletions src/mtd.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ typedef bool (*cb_mtd)(int i, const char *name, struct mtd_info_user *mtd,
cJSON *get_mtd_info();
char *open_mtdblock(int i, int *fd, uint32_t size, int flags);
void enum_mtd_info(void *ctx, cb_mtd cb);
// How much flash the partitions of the last enum_mtd_info() cover, each byte
// once. Partitions can nest, so adding up what it hands out overstates it.
uint64_t mtd_flash_size();
// Where partition `num` starts on the flash: the kernel's word where it gives
// one, then the command line and the boot log, which a long uptime can lose
bool mtd_flash_offset(int num, const char *name, uint64_t size,
Expand Down
45 changes: 39 additions & 6 deletions src/mtd_alias.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ void mtd_mark_aliases(int n, const uint64_t *off, const bool *has_off,
for (int i = 0; i < n; i++) {
alias[i] = false;
if (has_off[i]) {
for (int j = 0; j < n; j++) {
if (j == i || !has_off[j] || size[j] >= size[i])
continue;
if (off[j] >= off[i] && off[j] + size[j] <= off[i] + size[i]) {
alias[i] = true;
break;
// How far from its start the partitions inside it reach
uint64_t end = off[i] + size[i], reached = off[i];
for (bool moved = true; moved && reached < end;) {
moved = false;
for (int j = 0; j < n; j++) {
if (j == i || !has_off[j] || size[j] >= size[i] ||
off[j] < off[i] || off[j] + size[j] > end)
continue;
if (off[j] <= reached && off[j] + size[j] > reached) {
reached = off[j] + size[j];
moved = true;
}
}
}
alias[i] = reached == end;
continue;
}
uint64_t others = 0;
Expand All @@ -27,6 +34,32 @@ void mtd_mark_aliases(int n, const uint64_t *off, const bool *has_off,
}
}

uint64_t mtd_covered_size(int n, const uint64_t *off, const bool *has_off,
const uint64_t *size) {
uint64_t total = 0, reached = 0;
for (int i = 0; i < n; i++)
if (!has_off[i])
total += size[i];
// The lowest start past what is counted, n times at most
for (;;) {
int next = -1;
uint64_t next_from = 0;
for (int i = 0; i < n; i++) {
if (!has_off[i] || off[i] + size[i] <= reached)
continue;
uint64_t from = off[i] < reached ? reached : off[i];
if (next < 0 || from < next_from) {
next = i;
next_from = from;
}
}
if (next < 0)
return total;
total += off[next] + size[next] - next_from;
reached = off[next] + size[next];
}
}

// The kernel's memparse(): strtoull with a size suffix
static uint64_t memparse(const char *s, const char **end) {
char *e;
Expand Down
14 changes: 11 additions & 3 deletions src/mtd_alias.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,20 @@

/* Marks alias[i] for each partition that is another name for flash the
* others already cover -- Novatek's "all", 0..chip size, after "loader" to
* "encrypt". Where the kernel gives offsets (has_off), a partition that
* contains another one whole is the alias. Without them, one whose size is
* the sum of all the others' is. Backing it up would put the chip in twice. */
* "encrypt". Where the kernel gives offsets (has_off), it is a partition the
* smaller ones inside it cover from end to end: holding one is not enough.
* OpenIPC's "firmware" is kernel + rootfs and goes, while "rootfs_data" is
* only the tail of "rootfs", which stays because the squashfs is nowhere else.
* Without offsets, an alias is one whose size is the sum of all the others'.
* Backing up an alias would put the chip in twice. */
void mtd_mark_aliases(int n, const uint64_t *off, const bool *has_off,
const uint64_t *size, bool *alias);

/* How much flash `n` partitions cover, each byte once: nested partitions are
* not a bigger chip. One with no offset can only be added on. */
uint64_t mtd_covered_size(int n, const uint64_t *off, const bool *has_off,
const uint64_t *size);

/*
* Where a partition starts, for kernels before 4.4: they have no
* /sys/class/mtd/mtdN/offset. A partition is found by name and size, and is
Expand Down
51 changes: 51 additions & 0 deletions src/mtd_alias_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,57 @@ int main(void) {
for (int i = 0; i < 4; i++)
CHECK(!nalias[i], "NOR partition %d called an alias", i);

// A parent partition with a split inside it (hi3516cv610, 8 MB NOR):
// firmware is kernel + rootfs, while rootfs_data is only rootfs's tail,
// and dropping rootfs for holding it lost the squashfs from backups.
const char *cv[6] = {"u-boot", "env", "firmware",
"kernel", "rootfs", "rootfs_data"};
uint64_t coff[6] = {0, 0x40000, 0x50000, 0x50000, 0x250000, 0x6d0000};
uint64_t csize[6] = {0x40000, 0x10000, 0x7b0000,
0x200000, 0x5b0000, 0x130000};
bool chas[6] = {true, true, true, true, true, true}, calias[6];
mtd_mark_aliases(6, coff, chas, csize, calias);
for (int i = 0; i < 6; i++)
CHECK(calias[i] == (i == 2), "%s alias=%d", cv[i], calias[i]);

// A parent of parents goes too; a parent with a hole in it stays
uint64_t goff[5] = {0, 0x100000, 0, 0x100000, 0x300000};
uint64_t gsize[5] = {0x800000, 0x700000, 0x100000, 0x200000, 0x500000};
bool ghas[5] = {true, true, true, true, true}, galias[5];
mtd_mark_aliases(5, goff, ghas, gsize, galias);
CHECK(galias[0] && galias[1] && !galias[2] && !galias[3] && !galias[4],
"parent of parents");
uint64_t hoff[3] = {0, 0, 0x200000};
uint64_t hsize[3] = {0x400000, 0x100000, 0x200000};
bool hhas[3] = {true, true, true}, halias[3];
mtd_mark_aliases(3, hoff, hhas, hsize, halias);
CHECK(!halias[0], "a parent with a hole called an alias");

// Listed in any order
uint64_t roff[3] = {0x200000, 0, 0};
uint64_t rsize[3] = {0x200000, 0x400000, 0x200000};
bool rhas[3] = {true, true, true}, ralias[3];
mtd_mark_aliases(3, roff, rhas, rsize, ralias);
CHECK(!ralias[0] && ralias[1] && !ralias[2], "out of order");

// What is left of that layout is 8 MB, not the 9.2 of its sizes
uint64_t kept_off[5] = {0, 0x40000, 0x50000, 0x250000, 0x6d0000};
uint64_t kept_size[5] = {0x40000, 0x10000, 0x200000, 0x5b0000, 0x130000};
bool kept_has[5] = {true, true, true, true, true};
uint64_t sz = mtd_covered_size(5, kept_off, kept_has, kept_size);
CHECK(sz == 0x800000, "covered %llx", (unsigned long long)sz);
// In any order, with a hole not counted, and a tail that outruns
uint64_t any_off[4] = {0x700000, 0, 0x600000, 0x100000};
uint64_t any_size[4] = {0x100000, 0x100000, 0x180000, 0x100000};
bool any_has[4] = {true, true, true, true};
sz = mtd_covered_size(4, any_off, any_has, any_size);
CHECK(sz == 0x400000, "covered %llx", (unsigned long long)sz);
// Unknown offsets are all that can be added up, and none at all is 0
kept_has[1] = false;
sz = mtd_covered_size(5, kept_off, kept_has, kept_size);
CHECK(sz == 0x800000, "partly known %llx", (unsigned long long)sz);
CHECK(!mtd_covered_size(0, NULL, NULL, NULL), "no partitions");

// One partition alone is never an alias.
uint64_t one = 0x1000000, zero = 0;
bool f = false, a;
Expand Down
10 changes: 8 additions & 2 deletions src/raw_image.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,21 @@ size_t raw_image_plan(const raw_part_t *parts, size_t nparts,
}
}

// By offset; few enough that this is all the sort it needs
// By offset, the larger first where two start together: a parent that
// nothing could drop (its children leave a hole) covers them, whereas
// picking a child first would make the parent look like an overlap.
// Few enough that this is all the sort it needs.
static raw_part_t sorted[64];
if (nparts > sizeof(sorted) / sizeof(sorted[0])) {
snprintf(why, whylen, "too many partitions");
return 0;
}
for (size_t k = 0; k < nparts; k++) {
size_t at = k;
for (; at && sorted[at - 1].offset > parts[k].offset; at--)
for (; at && (sorted[at - 1].offset > parts[k].offset ||
(sorted[at - 1].offset == parts[k].offset &&
sorted[at - 1].size < parts[k].size));
at--)
sorted[at] = sorted[at - 1];
sorted[at] = parts[k];
}
Expand Down
30 changes: 29 additions & 1 deletion src/raw_image_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,39 @@ int main(void) {
p[4].size = ES;
planned(p, 5, (int[]){0, 1, 2, 3}, 4);

// hi3516cv610 on a kernel with mtdsplit: what is left once firmware has
// gone as an alias. rootfs_data is the tail of rootfs and is not written
// twice.
layout(p);
p[3].size = 0x5b0000;
p[4] = p[3];
p[4].num = 4;
p[4].offset = 0x6d0000;
p[4].size = 0x130000;
planned(p, 5, (int[]){0, 1, 2, 3}, 4);

// A parent still standing is written through, in either listing order
layout(p);
p[2] = (raw_part_t){.num = 2,
.size = 0x7b0000,
.erasesize = ES,
.nor = true,
.has_offset = true,
.offset = 0x50000};
p[3] = p[2];
p[3].num = 3;
p[3].size = 0x200000;
planned(p, 4, (int[]){0, 1, 2}, 3);
raw_part_t parent = p[2];
p[2] = p[3];
p[3] = parent;
planned(p, 4, (int[]){0, 1, 2}, 3);

// The sizes add up, and only the offsets show these two share flash
layout(p);
p[2].offset -= ES;
p[3].offset += ES;
refused(p, 4, FLASH, "mtd2 overlaps");
refused(p, 4, FLASH, "no partition covers 0x240000-0x260000");
layout(p);
p[3].offset = p[2].offset;
p[3].offset += p[2].size - ES;
Expand Down
Loading