599 lines
13 KiB
C
599 lines
13 KiB
C
#include <bootm.h>
|
|
#include <boot.h>
|
|
#include <common.h>
|
|
#include <command.h>
|
|
#include <driver.h>
|
|
#include <environment.h>
|
|
#include <image.h>
|
|
#include <init.h>
|
|
#include <fs.h>
|
|
#include <libfile.h>
|
|
#include <linux/list.h>
|
|
#include <xfuncs.h>
|
|
#include <malloc.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <linux/sizes.h>
|
|
#include <libbb.h>
|
|
#include <magicvar.h>
|
|
#include <binfmt.h>
|
|
#include <restart.h>
|
|
|
|
#include <asm/byteorder.h>
|
|
#include <asm/setup.h>
|
|
#include <asm/barebox-arm.h>
|
|
#include <asm/armlinux.h>
|
|
#include <asm/system.h>
|
|
|
|
/*
|
|
* sdram_start_and_size() - determine place for putting the kernel/oftree/initrd
|
|
*
|
|
* @start: returns the start address of the first RAM bank
|
|
* @size: returns the usable space at the beginning of the first RAM bank
|
|
*
|
|
* This function returns the base address of the first RAM bank and the free
|
|
* space found there.
|
|
*
|
|
* return: 0 for success, negative error code otherwise
|
|
*/
|
|
static int sdram_start_and_size(unsigned long *start, unsigned long *size)
|
|
{
|
|
struct memory_bank *bank;
|
|
struct resource *res;
|
|
|
|
/*
|
|
* We use the first memory bank for the kernel and other resources
|
|
*/
|
|
bank = list_first_entry_or_null(&memory_banks, struct memory_bank,
|
|
list);
|
|
if (!bank) {
|
|
printf("cannot find first memory bank\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* If the first memory bank has child resources we can use the bank up
|
|
* to the beginning of the first child resource, otherwise we can use
|
|
* the whole bank.
|
|
*/
|
|
res = list_first_entry_or_null(&bank->res->children, struct resource,
|
|
sibling);
|
|
if (res)
|
|
*size = res->start - bank->start;
|
|
else
|
|
*size = bank->size;
|
|
|
|
*start = bank->start;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_kernel_addresses(size_t image_size,
|
|
int verbose, unsigned long *load_address,
|
|
unsigned long *mem_free)
|
|
{
|
|
unsigned long mem_start, mem_size;
|
|
int ret;
|
|
size_t image_decomp_size;
|
|
unsigned long spacing;
|
|
|
|
ret = sdram_start_and_size(&mem_start, &mem_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* The kernel documentation "Documentation/arm/Booting" advises
|
|
* to place the compressed image outside of the lowest 32 MiB to
|
|
* avoid relocation. We should do this if we have at least 64 MiB
|
|
* of ram. If we have less space, we assume a maximum
|
|
* compression factor of 5.
|
|
*/
|
|
image_decomp_size = PAGE_ALIGN(image_size * 5);
|
|
if (mem_size >= SZ_64M)
|
|
image_decomp_size = max_t(size_t, image_decomp_size, SZ_32M);
|
|
|
|
/*
|
|
* By default put oftree/initrd close behind compressed kernel image to
|
|
* avoid placing it outside of the kernels lowmem region.
|
|
*/
|
|
spacing = SZ_1M;
|
|
|
|
if (*load_address == UIMAGE_INVALID_ADDRESS) {
|
|
/*
|
|
* Place the kernel at an address where it does not need to
|
|
* relocate itself before decompression.
|
|
*/
|
|
*load_address = mem_start + image_decomp_size;
|
|
if (verbose)
|
|
printf("no OS load address, defaulting to 0x%08lx\n",
|
|
*load_address);
|
|
} else if (*load_address <= mem_start + image_decomp_size) {
|
|
/*
|
|
* If the user/image specified an address where the kernel needs
|
|
* to relocate itself before decompression we need to extend the
|
|
* spacing to allow this relocation to happen without
|
|
* overwriting anything placed behind the kernel.
|
|
*/
|
|
spacing += image_decomp_size;
|
|
}
|
|
|
|
*mem_free = PAGE_ALIGN(*load_address + image_size + spacing);
|
|
|
|
/*
|
|
* Place oftree/initrd outside of the first 128 MiB, if we have space
|
|
* for it. This avoids potential conflicts with the kernel decompressor.
|
|
*/
|
|
if (mem_size > SZ_256M)
|
|
*mem_free = max(*mem_free, mem_start + SZ_128M);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __do_bootm_linux(struct image_data *data, unsigned long free_mem, int swap)
|
|
{
|
|
unsigned long kernel;
|
|
unsigned long initrd_start = 0, initrd_size = 0, initrd_end = 0;
|
|
int ret;
|
|
|
|
kernel = data->os_res->start + data->os_entry;
|
|
|
|
initrd_start = data->initrd_address;
|
|
|
|
if (initrd_start == UIMAGE_INVALID_ADDRESS) {
|
|
initrd_start = PAGE_ALIGN(free_mem);
|
|
|
|
if (bootm_verbose(data)) {
|
|
printf("no initrd load address, defaulting to 0x%08lx\n",
|
|
initrd_start);
|
|
}
|
|
}
|
|
|
|
if (bootm_has_initrd(data)) {
|
|
ret = bootm_load_initrd(data, initrd_start);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (data->initrd_res) {
|
|
initrd_start = data->initrd_res->start;
|
|
initrd_end = data->initrd_res->end;
|
|
initrd_size = resource_size(data->initrd_res);
|
|
free_mem = PAGE_ALIGN(initrd_end);
|
|
}
|
|
|
|
ret = bootm_load_devicetree(data, free_mem);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (bootm_verbose(data)) {
|
|
printf("\nStarting kernel at 0x%08lx", kernel);
|
|
if (initrd_size)
|
|
printf(", initrd at 0x%08lx", initrd_start);
|
|
if (data->oftree)
|
|
printf(", oftree at 0x%p", data->oftree);
|
|
printf("...\n");
|
|
}
|
|
|
|
if (data->dryrun)
|
|
return 0;
|
|
|
|
start_linux((void *)kernel, swap, initrd_start, initrd_size, data->oftree);
|
|
|
|
restart_machine();
|
|
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
static int do_bootm_linux(struct image_data *data)
|
|
{
|
|
unsigned long load_address, mem_free;
|
|
int ret;
|
|
|
|
load_address = data->os_address;
|
|
|
|
ret = get_kernel_addresses(bootm_get_os_size(data),
|
|
bootm_verbose(data), &load_address, &mem_free);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = bootm_load_os(data, load_address);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return __do_bootm_linux(data, mem_free, 0);
|
|
}
|
|
|
|
static struct image_handler uimage_handler = {
|
|
.name = "ARM Linux uImage",
|
|
.bootm = do_bootm_linux,
|
|
.filetype = filetype_uimage,
|
|
.ih_os = IH_OS_LINUX,
|
|
};
|
|
|
|
static struct image_handler rawimage_handler = {
|
|
.name = "ARM raw image",
|
|
.bootm = do_bootm_linux,
|
|
.filetype = filetype_unknown,
|
|
};
|
|
|
|
struct zimage_header {
|
|
u32 unused[9];
|
|
u32 magic;
|
|
u32 start;
|
|
u32 end;
|
|
};
|
|
|
|
#define ZIMAGE_MAGIC 0x016F2818
|
|
|
|
static int do_bootz_linux_fdt(int fd, struct image_data *data)
|
|
{
|
|
struct fdt_header __header, *header;
|
|
void *oftree;
|
|
int ret;
|
|
|
|
u32 end;
|
|
|
|
if (data->oftree)
|
|
return -ENXIO;
|
|
|
|
header = &__header;
|
|
ret = read(fd, header, sizeof(*header));
|
|
if (ret < sizeof(*header))
|
|
return ret;
|
|
|
|
if (file_detect_type(header, sizeof(*header)) != filetype_oftree)
|
|
return -ENXIO;
|
|
|
|
end = be32_to_cpu(header->totalsize);
|
|
|
|
oftree = malloc(end + 0x8000);
|
|
if (!oftree) {
|
|
perror("zImage: oftree malloc");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memcpy(oftree, header, sizeof(*header));
|
|
|
|
end -= sizeof(*header);
|
|
|
|
ret = read_full(fd, oftree + sizeof(*header), end);
|
|
if (ret < 0)
|
|
goto err_free;
|
|
if (ret < end) {
|
|
printf("premature end of image\n");
|
|
ret = -EIO;
|
|
goto err_free;
|
|
}
|
|
|
|
if (IS_BUILTIN(CONFIG_OFTREE)) {
|
|
data->of_root_node = of_unflatten_dtb(oftree);
|
|
if (!data->of_root_node) {
|
|
pr_err("unable to unflatten devicetree\n");
|
|
ret = -EINVAL;
|
|
goto err_free;
|
|
}
|
|
free(oftree);
|
|
} else {
|
|
data->oftree = oftree;
|
|
}
|
|
|
|
pr_info("zImage: concatenated oftree detected\n");
|
|
|
|
return 0;
|
|
|
|
err_free:
|
|
free(oftree);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int do_bootz_linux(struct image_data *data)
|
|
{
|
|
int fd, ret, swap = 0;
|
|
struct zimage_header __header, *header;
|
|
void *zimage;
|
|
u32 end, start;
|
|
size_t image_size;
|
|
unsigned long load_address = data->os_address;
|
|
unsigned long mem_free;
|
|
|
|
fd = open(data->os_file, O_RDONLY);
|
|
if (fd < 0) {
|
|
perror("open");
|
|
return 1;
|
|
}
|
|
|
|
header = &__header;
|
|
ret = read(fd, header, sizeof(*header));
|
|
if (ret < sizeof(*header)) {
|
|
printf("could not read %s\n", data->os_file);
|
|
goto err_out;
|
|
}
|
|
|
|
switch (header->magic) {
|
|
case swab32(ZIMAGE_MAGIC):
|
|
swap = 1;
|
|
/* fall through */
|
|
case ZIMAGE_MAGIC:
|
|
break;
|
|
default:
|
|
printf("invalid magic 0x%08x\n", header->magic);
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
end = header->end;
|
|
start = header->start;
|
|
|
|
if (swap) {
|
|
end = swab32(end);
|
|
start = swab32(start);
|
|
}
|
|
|
|
image_size = end - start;
|
|
load_address = data->os_address;
|
|
|
|
ret = get_kernel_addresses(image_size, bootm_verbose(data),
|
|
&load_address, &mem_free);
|
|
if (ret)
|
|
return ret;
|
|
|
|
data->os_res = request_sdram_region("zimage", load_address, image_size);
|
|
if (!data->os_res) {
|
|
pr_err("bootm/zImage: failed to request memory at 0x%lx to 0x%lx (%d).\n",
|
|
load_address, load_address + image_size, image_size);
|
|
ret = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
zimage = (void *)data->os_res->start;
|
|
|
|
memcpy(zimage, header, sizeof(*header));
|
|
|
|
ret = read_full(fd, zimage + sizeof(*header),
|
|
image_size - sizeof(*header));
|
|
if (ret < 0)
|
|
goto err_out;
|
|
if (ret < image_size - sizeof(*header)) {
|
|
printf("premature end of image\n");
|
|
ret = -EIO;
|
|
goto err_out;
|
|
}
|
|
|
|
if (swap) {
|
|
void *ptr;
|
|
for (ptr = zimage; ptr < zimage + end; ptr += 4)
|
|
*(u32 *)ptr = swab32(*(u32 *)ptr);
|
|
}
|
|
|
|
ret = do_bootz_linux_fdt(fd, data);
|
|
if (ret && ret != -ENXIO)
|
|
goto err_out;
|
|
|
|
close(fd);
|
|
|
|
return __do_bootm_linux(data, mem_free, swap);
|
|
|
|
err_out:
|
|
close(fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct image_handler zimage_handler = {
|
|
.name = "ARM zImage",
|
|
.bootm = do_bootz_linux,
|
|
.filetype = filetype_arm_zimage,
|
|
};
|
|
|
|
static struct image_handler barebox_handler = {
|
|
.name = "ARM barebox",
|
|
.bootm = do_bootm_linux,
|
|
.filetype = filetype_arm_barebox,
|
|
};
|
|
|
|
#include <aimage.h>
|
|
|
|
static int aimage_load_resource(int fd, struct resource *r, void* buf, int ps)
|
|
{
|
|
int ret;
|
|
void *image = (void *)r->start;
|
|
unsigned to_read = ps - resource_size(r) % ps;
|
|
|
|
ret = read_full(fd, image, resource_size(r));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = read_full(fd, buf, to_read);
|
|
if (ret < 0)
|
|
printf("could not read dummy %u\n", to_read);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int do_bootm_aimage(struct image_data *data)
|
|
{
|
|
struct resource *snd_stage_res;
|
|
int fd, ret;
|
|
struct android_header __header, *header;
|
|
void *buf;
|
|
int to_read;
|
|
struct android_header_comp *cmp;
|
|
unsigned long mem_free;
|
|
unsigned long mem_start, mem_size;
|
|
|
|
ret = sdram_start_and_size(&mem_start, &mem_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
fd = open(data->os_file, O_RDONLY);
|
|
if (fd < 0) {
|
|
perror("open");
|
|
return 1;
|
|
}
|
|
|
|
header = &__header;
|
|
ret = read(fd, header, sizeof(*header));
|
|
if (ret < sizeof(*header)) {
|
|
printf("could not read %s\n", data->os_file);
|
|
goto err_out;
|
|
}
|
|
|
|
printf("Android Image for '%s'\n", header->name);
|
|
|
|
/*
|
|
* As on tftp we do not support lseek and we will just have to seek
|
|
* for the size of a page - 1 max just buffer instead to read to dummy
|
|
* data
|
|
*/
|
|
buf = xmalloc(header->page_size);
|
|
|
|
to_read = header->page_size - sizeof(*header);
|
|
ret = read_full(fd, buf, to_read);
|
|
if (ret < 0) {
|
|
printf("could not read dummy %d from %s\n", to_read, data->os_file);
|
|
goto err_out;
|
|
}
|
|
|
|
cmp = &header->kernel;
|
|
data->os_res = request_sdram_region("akernel", cmp->load_addr, cmp->size);
|
|
if (!data->os_res) {
|
|
pr_err("Cannot request region 0x%08x - 0x%08x, using default load address\n",
|
|
cmp->load_addr, cmp->size);
|
|
|
|
data->os_address = mem_start + PAGE_ALIGN(cmp->size * 4);
|
|
data->os_res = request_sdram_region("akernel", data->os_address, cmp->size);
|
|
if (!data->os_res) {
|
|
pr_err("Cannot request region 0x%08x - 0x%08x\n",
|
|
cmp->load_addr, cmp->size);
|
|
ret = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
ret = aimage_load_resource(fd, data->os_res, buf, header->page_size);
|
|
if (ret < 0) {
|
|
perror("could not read kernel");
|
|
goto err_out;
|
|
}
|
|
|
|
/*
|
|
* fastboot always expect a ramdisk
|
|
* in barebox we can be less restrictive
|
|
*/
|
|
cmp = &header->ramdisk;
|
|
if (cmp->size) {
|
|
data->initrd_res = request_sdram_region("ainitrd", cmp->load_addr, cmp->size);
|
|
if (!data->initrd_res) {
|
|
ret = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
ret = aimage_load_resource(fd, data->initrd_res, buf, header->page_size);
|
|
if (ret < 0) {
|
|
perror("could not read initrd");
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
if (!getenv("aimage_noverwrite_bootargs"))
|
|
linux_bootargs_overwrite(header->cmdline);
|
|
|
|
if (!getenv("aimage_noverwrite_tags"))
|
|
armlinux_set_bootparams((void*)header->tags_addr);
|
|
|
|
cmp = &header->second_stage;
|
|
if (cmp->size) {
|
|
void (*second)(void);
|
|
|
|
snd_stage_res = request_sdram_region("asecond", cmp->load_addr, cmp->size);
|
|
if (!snd_stage_res) {
|
|
ret = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
ret = aimage_load_resource(fd, snd_stage_res, buf, header->page_size);
|
|
if (ret < 0) {
|
|
perror("could not read initrd");
|
|
goto err_out;
|
|
}
|
|
|
|
second = (void*)snd_stage_res->start;
|
|
shutdown_barebox();
|
|
|
|
second();
|
|
|
|
restart_machine();
|
|
}
|
|
|
|
close(fd);
|
|
|
|
/*
|
|
* Put devicetree right after initrd if present or after the kernel
|
|
* if not.
|
|
*/
|
|
if (data->initrd_res)
|
|
mem_free = PAGE_ALIGN(data->initrd_res->end);
|
|
else
|
|
mem_free = PAGE_ALIGN(data->os_res->end + SZ_1M);
|
|
|
|
return __do_bootm_linux(data, mem_free, 0);
|
|
|
|
err_out:
|
|
linux_bootargs_overwrite(NULL);
|
|
close(fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct image_handler aimage_handler = {
|
|
.name = "ARM Android Image",
|
|
.bootm = do_bootm_aimage,
|
|
.filetype = filetype_aimage,
|
|
};
|
|
|
|
#ifdef CONFIG_BOOTM_AIMAGE
|
|
BAREBOX_MAGICVAR(aimage_noverwrite_bootargs, "Disable overwrite of the bootargs with the one present in aimage");
|
|
BAREBOX_MAGICVAR(aimage_noverwrite_tags, "Disable overwrite of the tags addr with the one present in aimage");
|
|
#endif
|
|
|
|
static struct image_handler arm_fit_handler = {
|
|
.name = "FIT image",
|
|
.bootm = do_bootm_linux,
|
|
.filetype = filetype_oftree,
|
|
};
|
|
|
|
static struct binfmt_hook binfmt_aimage_hook = {
|
|
.type = filetype_aimage,
|
|
.exec = "bootm",
|
|
};
|
|
|
|
static struct binfmt_hook binfmt_arm_zimage_hook = {
|
|
.type = filetype_arm_zimage,
|
|
.exec = "bootm",
|
|
};
|
|
|
|
static struct binfmt_hook binfmt_barebox_hook = {
|
|
.type = filetype_arm_barebox,
|
|
.exec = "bootm",
|
|
};
|
|
|
|
static int armlinux_register_image_handler(void)
|
|
{
|
|
register_image_handler(&barebox_handler);
|
|
register_image_handler(&uimage_handler);
|
|
register_image_handler(&rawimage_handler);
|
|
register_image_handler(&zimage_handler);
|
|
if (IS_BUILTIN(CONFIG_BOOTM_AIMAGE)) {
|
|
register_image_handler(&aimage_handler);
|
|
binfmt_register(&binfmt_aimage_hook);
|
|
}
|
|
if (IS_BUILTIN(CONFIG_FITIMAGE))
|
|
register_image_handler(&arm_fit_handler);
|
|
binfmt_register(&binfmt_arm_zimage_hook);
|
|
binfmt_register(&binfmt_barebox_hook);
|
|
|
|
return 0;
|
|
}
|
|
late_initcall(armlinux_register_image_handler);
|