Skip to content

Commit

Permalink
Support bigger firmwares
Browse files Browse the repository at this point in the history
  • Loading branch information
Koromix committed Dec 31, 2021
1 parent 2d95684 commit 33c904c
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 64 deletions.
116 changes: 85 additions & 31 deletions src/libty/firmware.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
See the LICENSE file for more details. */

#include "common.h"
#include "../libhs/array.h"
#include "class_priv.h"
#include "firmware.h"
#include "system.h"
Expand All @@ -20,6 +19,19 @@ const ty_firmware_format ty_firmware_formats[] = {
};
const unsigned int ty_firmware_formats_count = _HS_COUNTOF(ty_firmware_formats);

struct read_file_context {
const char *filename;
FILE *fp;
int64_t offset;
};

struct read_memory_context {
const char *filename;
const uint8_t *mem;
size_t len;
int64_t offset;
};

static const char *get_basename(const char *filename)
{
const char *basename;
Expand Down Expand Up @@ -106,15 +118,68 @@ static int find_format(const char *filename, const char *format_name,
return 0;
}

static ssize_t read_file(int64_t offset, uint8_t *buf, size_t len, void *udata)
{
struct read_file_context *ctx = (struct read_file_context *)udata;
ssize_t r;

if (offset < 0)
offset = ctx->offset;
if (offset != ctx->offset) {
#ifdef _WIN32
r = _fseeki64(ctx->fp, offset, SEEK_SET);
#else
r = fseeko(ctx->fp, (off_t)offset, SEEK_SET);
#endif
if (r < 0) {
if (errno == ESPIPE) {
return ty_error(TY_ERROR_IO, "Trying to seek in non-seekable file '%s'", ctx->filename);
} else if (errno == EINVAL) {
return ty_error(TY_ERROR_RANGE, "Cannot seek beyond end of file '%s'", ctx->filename);
} else {
return ty_error(TY_ERROR_SYSTEM, "fseek('%s') failed: %s", ctx->filename, strerror(errno));
}
}
}

r = (ssize_t)fread(buf, 1, len, ctx->fp);
if (ferror(ctx->fp)) {
if (errno == EIO) {
return ty_error(TY_ERROR_IO, "I/O error while reading from '%s'", ctx->filename);
} else {
return ty_error(TY_ERROR_SYSTEM, "fread('%s') failed: %s", ctx->filename, strerror(errno));
}
}
ctx->offset += (int64_t)r;

return r;
}

static ssize_t read_memory(int64_t offset, uint8_t *buf, size_t len, void *udata)
{
struct read_memory_context *ctx = (struct read_memory_context *)udata;

if (offset < 0)
offset = ctx->offset;
if (offset > ctx->len)
return ty_error(TY_ERROR_RANGE, "Cannot seek beyond end of file '%s'", ctx->filename);

size_t copy_len = _HS_MIN(ctx->len - offset, len);
memcpy(buf, ctx->mem + offset, copy_len);
ctx->offset += (int64_t)copy_len;

return (ssize_t)copy_len;
}

int ty_firmware_load_file(const char *filename, FILE *fp, const char *format_name,
ty_firmware **rfw)
{
assert(filename);
assert(rfw);

const ty_firmware_format *format;
struct read_file_context ctx = {0};
bool close_fp = false;
_HS_ARRAY(uint8_t) buf = {0};
ty_firmware *fw = NULL;
int r;

Expand Down Expand Up @@ -156,33 +221,15 @@ int ty_firmware_load_file(const char *filename, FILE *fp, const char *format_nam
close_fp = true;
}

// Load file to memory
while (!feof(fp)) {
r = _hs_array_grow(&buf, 128 * 1024);
if (r < 0)
goto cleanup;

buf.count += fread(buf.values + buf.count, 1, 131072, fp);
if (ferror(fp)) {
if (errno == EIO) {
r = ty_error(TY_ERROR_IO, "I/O error while reading from '%s'", filename);
} else {
r = ty_error(TY_ERROR_SYSTEM, "fread('%s') failed: %s", filename, strerror(errno));
}
goto cleanup;
}
if (buf.count > 8 * 1024 * 1024) {
r = ty_error(TY_ERROR_RANGE, "Firmware '%s' is too big to load", filename);
goto cleanup;
}
}
_hs_array_shrink(&buf);

r = ty_firmware_new(filename, &fw);
if (r < 0)
goto cleanup;

r = (*format->load)(fw, buf.values, buf.count);
ctx.filename = filename;
ctx.fp = fp;
ctx.offset = 0;

r = (*format->load)(fw, read_file, &ctx);
if (r < 0)
goto cleanup;

Expand All @@ -193,7 +240,6 @@ int ty_firmware_load_file(const char *filename, FILE *fp, const char *format_nam
ty_firmware_unref(fw);
if (close_fp)
fclose(fp);
_hs_array_release(&buf);
return r;
}

Expand All @@ -205,6 +251,7 @@ int ty_firmware_load_mem(const char *filename, const uint8_t *mem, size_t len,
assert(rfw);

const ty_firmware_format *format;
struct read_memory_context ctx = {0};
ty_firmware *fw = NULL;
int r;

Expand All @@ -216,7 +263,11 @@ int ty_firmware_load_mem(const char *filename, const uint8_t *mem, size_t len,
if (r < 0)
goto cleanup;

r = (*format->load)(fw, mem, len);
ctx.filename = filename;
ctx.mem = mem;
ctx.len = len;

r = (*format->load)(fw, read_memory, &ctx);
if (r < 0)
goto cleanup;

Expand Down Expand Up @@ -319,15 +370,16 @@ int ty_firmware_add_segment(ty_firmware *fw, uint32_t address, size_t size,
int ty_firmware_expand_segment(ty_firmware *fw, ty_firmware_segment *segment, size_t size)
{
const size_t step_size = 65536;
size_t total_size = fw->total_size - segment->size + size;

if (total_size > TY_FIRMWARE_MAX_SIZE)
return ty_error(TY_ERROR_RANGE, "Firmware '%s' has excessive size (max %u bytes)",
fw->filename, TY_FIRMWARE_MAX_SIZE);

if (size > segment->alloc_size) {
uint8_t *tmp;
size_t alloc_size;

if (size > TY_FIRMWARE_MAX_SEGMENT_SIZE)
return ty_error(TY_ERROR_RANGE, "Firmware '%s' has excessive segment size (max %u bytes)",
fw->filename, TY_FIRMWARE_MAX_SEGMENT_SIZE);

alloc_size = (size + (step_size - 1)) / step_size * step_size;
tmp = realloc(segment->data, alloc_size);
if (!tmp)
Expand All @@ -336,7 +388,9 @@ int ty_firmware_expand_segment(ty_firmware *fw, ty_firmware_segment *segment, si
segment->data = tmp;
segment->alloc_size = alloc_size;
}

segment->size = size;
fw->total_size = total_size;

return 0;
}
Expand Down
10 changes: 6 additions & 4 deletions src/libty/firmware.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
_HS_BEGIN_C

#define TY_FIRMWARE_MAX_SEGMENTS 16
#define TY_FIRMWARE_MAX_SEGMENT_SIZE (2 * 1024 * 1024)
#define TY_FIRMWARE_MAX_SIZE (32 * 1024 * 1024)

typedef struct ty_firmware_segment {
uint8_t *data;
Expand All @@ -39,11 +39,13 @@ typedef struct ty_firmware {
size_t total_size;
} ty_firmware;

typedef ssize_t ty_firmware_read_func(int64_t offset, uint8_t *buf, size_t len, void *udata);

typedef struct ty_firmware_format {
const char *name;
const char *ext;

int (*load)(ty_firmware *fw, const uint8_t *mem, size_t len);
int (*load)(ty_firmware *fw, ty_firmware_read_func *func, void *udata);
} ty_firmware_format;

extern const ty_firmware_format ty_firmware_formats[];
Expand All @@ -55,8 +57,8 @@ int ty_firmware_load_file(const char *filename, FILE *fp, const char *format_nam
int ty_firmware_load_mem(const char *filename, const uint8_t *mem, size_t len,
const char *format_name, ty_firmware **rfw);

int ty_firmware_load_elf(ty_firmware *fw, const uint8_t *mem, size_t len);
int ty_firmware_load_ihex(ty_firmware *fw, const uint8_t *mem, size_t len);
int ty_firmware_load_elf(ty_firmware *fw, ty_firmware_read_func *func, void *udata);
int ty_firmware_load_ihex(ty_firmware *fw, ty_firmware_read_func *func, void *udata);

ty_firmware *ty_firmware_ref(ty_firmware *fw);
void ty_firmware_unref(ty_firmware *fw);
Expand Down
38 changes: 23 additions & 15 deletions src/libty/firmware_elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ typedef struct Elf32_Phdr {
struct loader_context {
ty_firmware *fw;

const uint8_t *mem;
size_t len;
ty_firmware_read_func *func;
void *udata;

Elf32_Ehdr ehdr;
};
Expand All @@ -86,21 +86,31 @@ static inline void reverse_uint32(uint32_t *u)
| ((*u & 0xFF0000) >> 8) | ((*u & 0xFF000000) >> 24);
}

static int read_chunk(struct loader_context *ctx, off_t offset, size_t size, void *buf)
static int read_chunk(struct loader_context *ctx, int64_t offset, void *buf, size_t size)
{
if (offset < 0 || (size_t)offset > ctx->len - size)
return ty_error(TY_ERROR_PARSE, "ELF file '%s' is malformed or truncated",
ctx->fw->filename);
ssize_t r;

if (offset < 0)
goto truncated;

r = (*ctx->func)(offset, (uint8_t *)buf, size, ctx->udata);
if (r < 0)
return (int)r;
if (r < size)
goto truncated;

memcpy(buf, ctx->mem + offset, size);
return 0;

truncated:
return ty_error(TY_ERROR_PARSE, "ELF file '%s' is malformed or truncated",
ctx->fw->filename);
}

static int load_program_header(struct loader_context *ctx, unsigned int i, Elf32_Phdr *rphdr)
{
int r;

r = read_chunk(ctx, (off_t)(ctx->ehdr.e_phoff + i * ctx->ehdr.e_phentsize), sizeof(*rphdr), rphdr);
r = read_chunk(ctx, (off_t)(ctx->ehdr.e_phoff + i * ctx->ehdr.e_phentsize), rphdr, sizeof(*rphdr));
if (r < 0)
return r;

Expand Down Expand Up @@ -134,27 +144,26 @@ static int load_segment(struct loader_context *ctx, unsigned int i)
r = ty_firmware_add_segment(ctx->fw, phdr.p_paddr, phdr.p_filesz, &segment);
if (r < 0)
return r;
r = read_chunk(ctx, phdr.p_offset, phdr.p_filesz, segment->data);
r = read_chunk(ctx, phdr.p_offset, segment->data, phdr.p_filesz);
if (r < 0)
return r;

return 1;
}

int ty_firmware_load_elf(ty_firmware *fw, const uint8_t *mem, size_t len)
int ty_firmware_load_elf(ty_firmware *fw, ty_firmware_read_func *func, void *udata)
{
assert(fw);
assert(!fw->segments_count && !fw->total_size);
assert(mem || !len);

struct loader_context ctx = {0};
int r;

ctx.fw = fw;
ctx.mem = mem;
ctx.len = len;
ctx.func = func;
ctx.udata = udata;

r = read_chunk(&ctx, 0, sizeof(ctx.ehdr), &ctx.ehdr);
r = read_chunk(&ctx, 0, &ctx.ehdr, sizeof(ctx.ehdr));
if (r < 0)
return r;

Expand Down Expand Up @@ -191,7 +200,6 @@ int ty_firmware_load_elf(ty_firmware *fw, const uint8_t *mem, size_t len)

for (unsigned int i = 0; i < fw->segments_count; i++) {
const ty_firmware_segment *segment = &fw->segments[i];
fw->total_size += segment->size;
fw->max_address = _HS_MAX(fw->max_address, segment->address + segment->size);
}

Expand Down
Loading

0 comments on commit 33c904c

Please sign in to comment.