From 529adafde26d951fa425029e0b56370c36d29081 Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Fri, 27 Aug 2010 14:21:17 +0200 Subject: Make U-Boot restartable and crash dump support * Added an u8500 board specific ld script. Reserve space for .data backup. * Make a backup copy of initialised data (.data section). If restarted, copy the backup data back to .data. * Create crashkernel env variable dynamically, since it depends on U-Boot start address. For dumping U-Boot itself is used as crashkernel. * Set preboot environment variable to checkcrash command, if data_init_flag > 0, i.e. we were restarted e.g. by kexec. * Added crashkernel parameter to common bootargs. Decreased environment and pool sizes. * Changed link address of U-Boot: moved it 32 K up for kdump info to 0x05608000 * Added "checkcrash" command This command checks if there is a pending crash dump and writes it to a file on SD/MMC. ST-Ericsson ID: WP264488 Change-Id: If545822e424b95532f1128afb0e762b6b73834e9 Signed-off-by: Michael Brandt Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/3011 --- board/st/u8500/Makefile | 2 +- board/st/u8500/cmd_cdump.c | 332 +++++++++++++++++++++++++++++++++++++++++++++ board/st/u8500/config.mk | 2 +- board/st/u8500/u-boot.lds | 70 ++++++++++ board/st/u8500/u8500.c | 50 +++++++ 5 files changed, 454 insertions(+), 2 deletions(-) create mode 100644 board/st/u8500/cmd_cdump.c create mode 100644 board/st/u8500/u-boot.lds (limited to 'board') diff --git a/board/st/u8500/Makefile b/board/st/u8500/Makefile index 3bd3a7a37..e0617f471 100644 --- a/board/st/u8500/Makefile +++ b/board/st/u8500/Makefile @@ -24,7 +24,7 @@ include $(TOPDIR)/config.mk LIB = $(obj)lib$(BOARD).a -COBJS := u8500.o flash.o gpio.o u8500_i2c.o mmc_host.o mmc_utils.o clock.o prcmu.o mcde_display.o mcde_hw.o ab8500vibra.o +COBJS := u8500.o flash.o gpio.o u8500_i2c.o mmc_host.o mmc_utils.o clock.o prcmu.o mcde_display.o mcde_hw.o ab8500vibra.o cmd_cdump.o SOBJS := mmc_fifo.o SRCS := $(SOBJS:.o=.S) $(COBJS:.o=.c) diff --git a/board/st/u8500/cmd_cdump.c b/board/st/u8500/cmd_cdump.c new file mode 100644 index 000000000..36bdfc4c8 --- /dev/null +++ b/board/st/u8500/cmd_cdump.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Spjalle Joelson for ST-Ericsson + * Licensed under GPLv2. + * + * Cleanups and comments: Michael Brandt + */ + +/* cmd_cdump.c - crash dump commands, require FAT write support */ + +#include +#include +#include "malloc.h" +#include + +#include +#include +#include + +/* + * Note: with the Rockbox FAT support, the file path must be an absolute path, + * i.e. with leading /. + */ +static char *crash_filename = "/cdump.elf"; + +/* + * Check ELF header + */ +static int check_elfhdr(u32 elfhdr_addr) +{ + unsigned char *elfhdr = (unsigned char *) elfhdr_addr; + + /* check ELF core image header MAGIC */ + if (memcmp(elfhdr, ELFMAG, SELFMAG) != 0) + return 1; + + /* check that this is ELF32 */ + if (elfhdr[EI_CLASS] == ELFCLASS32) + return 0; + + return 1; +} + +/* + * Write a chunk + */ +static int write_chunk(int fd, void *addr, size_t count) +{ + size_t bytes; + + bytes = write(fd, addr, count); + if (bytes != count) { + printf("write error\n"); + close(fd); + return -1; + } + return 0; +} + +/* + * Write a big chunk with 'progress' indicator '.' for every MiB + */ +static int write_big_chunk(int fd, void *addr, size_t count) +{ + unsigned char *a = addr; + size_t total = 0; + + if (count < 0x100000) + return write_chunk(fd, addr, count); + /* if large chunk then print dot to show progress */ + while (total < count) { + size_t bytes = count - total; + + if (bytes > 0x100000) + bytes = 0x100000; + if (write_chunk(fd, a, bytes)) + return -1; + putc('.'); + total += bytes; + a += bytes; + } + putc('\n'); + return 0; +} + +/* + * Open the dump file for writing. Create if it not exists. + * Note that with the Rockbox FAT support, the file path must be an absolute + * path, i.e. with leading /. + */ +static int open_create(const char *filename) +{ + int fd; + + fd = open(filename, O_WRONLY | O_CREAT); + if (fd < 0) + printf("%s open error\n", filename); + return fd; +} + +/* + * Check program header and segment + * Truncate note segments. + * Return segment size. + */ +static u32 check_proghdr(u32 *proghdr) +{ + u32 i; + u32 *note; + Elf32_Phdr *phdr = (Elf32_Phdr *) proghdr; + + if (phdr->p_type == PT_NOTE) { + /* see Linux kernel/kexec.c:append_elf_note() */ + note = (u32 *)(phdr->p_paddr); + for (i = 0; i < phdr->p_filesz/4;) { + if (note[i] == 0 && note[i+1] == 0 && note[i+2] == 0) + return i*4; + i += 3 + (note[i] + 3) / 4 + (note[i+1] + 3) / 4; + } + } + + return phdr->p_filesz; +} + +/* + * Dump crash to file + */ +static int write_elf(u32 elfhdr_addr, int fd) +{ + u32 i; + u32 len; + u32 offset; + Elf32_Ehdr *ehdr; + Elf32_Phdr *phdr; + + ehdr = (Elf32_Ehdr *)elfhdr_addr; + /* total hdr size = elf header + pgm header size * pgm header entries */ + offset = ehdr->e_ehsize + ehdr->e_phentsize * ehdr->e_phnum; + ehdr = (Elf32_Ehdr *)malloc(offset); + if (ehdr == NULL) { + printf("elf header alloc error\n"); + return -1; + } + memcpy(ehdr, (void *)elfhdr_addr, offset); + + /* check program headers and adjust length of segments */ + debug("elf hdr %lx\n", (ulong) ehdr); + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = (Elf32_Phdr *) + ((char *)ehdr + ehdr->e_ehsize + ehdr->e_phentsize * i); + + len = check_proghdr((u32 *)phdr); + debug("prog hdr %d: %lx at %x len %x adjusted to %x\n", + i, (ulong) phdr, phdr->p_paddr, + phdr->p_filesz, len); + phdr->p_filesz = len; + phdr->p_memsz = len; + /* set (file) offset to segment */ + phdr->p_offset = offset; + offset += len; + } + + /* write elf header and program headers */ + len = ehdr->e_ehsize + ehdr->e_phentsize * ehdr->e_phnum; + if (write_chunk(fd, ehdr, (size_t) len)) { + free(ehdr); + return -1; + } + + /* copy segment contents from SDRAM to file */ + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = (Elf32_Phdr *) + ((char *)ehdr + ehdr->e_ehsize + ehdr->e_phentsize * i); + if (phdr->p_filesz > 0) { + if (write_big_chunk(fd, (void *) phdr->p_paddr, + phdr->p_filesz)) { + free(ehdr); + return -1; + } + } + } + + free(ehdr); + return 0; +} + +/* + * Dump crash to file + */ +static int dump_elf(u32 elfhdr_addr, char *filename) +{ + int fd; + int rc; + + printf("Crash dump to %s\n", filename); + fd = open_create(filename); + if (fd < 0) + return 1; + rc = write_elf(elfhdr_addr, fd); + close(fd); + + return rc; +} + +/* + * Wait for MMC/SD card to be inserted + */ +static int wait_for_mmc(void) +{ + struct mmc *mmc; + + mmc = find_mmc_device(CONFIG_MMC_DEV_NUM); + while (mmc_init(mmc) != 0) { + printf("Insert MMC/SD card or press ctrl-c to abort\n"); + putc('.'); + udelay(500000); + /* check for ctrl-c to abort... */ + if (ctrlc()) { + puts("Abort\n"); + return -1; + } + } + return 0; +} + +/* + * Find kexec/kdump ATAG command line + */ +static char *get_atag_cmdline(void) +{ + ulong atag_offset = 0x1000; /* 4k offset from memory start */ + ulong offset = 0x8000; /* 32k offset from memory start */ + /* + * Get pointer to ATAG area, somewhere below U-boot image. + * Above values are hard coded in the kexec-tools. + */ + u32 * atags = (u32 *)(_armboot_start - offset + atag_offset); + u32 i = 0; + + /* + * ATAG command line: \0 terminated string. + * The list ends with an ATAG_NONE node. + */ + for (i = 0; (atags[i] != 0) && (atags[i+1] != ATAG_NONE); + i += atags[i]) { + + if (atags[i+1] == ATAG_CMDLINE) + return (char *) &atags[i+2]; + /* sanity check before checking next ATAG */ + if (atags[i] > (offset - atag_offset) / sizeof(u32) - i) + return NULL; + if ((atags[i] + i) < i) /* cannot step backwards */ + return NULL; + } + + return NULL; +} + +/* + * Find out where the kdump elf header is. + */ +static u32 get_elfhdr_addr(void) +{ + const char elfcorehdr[] = "elfcorehdr="; + char *cmd; + char *atag_cmdline = get_atag_cmdline(); + + if (atag_cmdline != NULL) { + cmd = strstr(atag_cmdline, elfcorehdr); + if (cmd != NULL) { + cmd += strlen(elfcorehdr); + return simple_strtoul(cmd, NULL, 16); + } + } + return 0; +} + +/* + * Dump crash to file (typically FAT file on SD/MMC). + */ +static int crashdump(u32 elfhdr_addr, char *filename) +{ + int rc; + block_dev_desc_t *dev_desc=NULL; + + rc = wait_for_mmc(); + if (rc == 0) { + dev_desc = get_dev("mmc", 1); /* mmc 1 */ + rc = fat_register_device(dev_desc, 1); /* part 1 */ + if (rc != 0) { + printf("crashdump: fat_register_device failed %d\n", + rc); + return -1; + } + rc = dump_elf(elfhdr_addr, filename); + } + + if (rc != 0) + printf("crashdump: error writing dump to %s\n", filename); + + return rc; +} + +/* + * Dump crash to file (typically FAT file on SD/MMC). + */ +static int do_checkcrash(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{ + u32 elfhdr_addr; + int rc = -1; + + elfhdr_addr = get_elfhdr_addr(); + if (elfhdr_addr != 0) + rc = check_elfhdr(elfhdr_addr); + if (rc == 0) { + printf("crash dump elf header found. Dumping to card...\n"); + /* stop autoboot in case we were called by preboot */ + setenv("bootdelay", "-1"); + rc = crashdump(elfhdr_addr, crash_filename); + if (rc != 0) + printf("checkcrash: error writing dump from %x to %s\n", + elfhdr_addr, crash_filename); + } + return rc; +} + +U_BOOT_CMD( + checkcrash, 1, 0, do_checkcrash, + "check ATAGS from crash and dump to file", + " - dump crash info to file and stop autoboot\n" +); diff --git a/board/st/u8500/config.mk b/board/st/u8500/config.mk index 4d007f96d..02f1643a4 100644 --- a/board/st/u8500/config.mk +++ b/board/st/u8500/config.mk @@ -12,7 +12,7 @@ sinclude $(OBJTREE)/board/$(BOARDDIR)/config.tmp ifndef TEXT_BASE -TEXT_BASE = 0x05600000 +TEXT_BASE = 0x05608000 endif PLATFORM_CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE) diff --git a/board/st/u8500/u-boot.lds b/board/st/u8500/u-boot.lds new file mode 100644 index 000000000..c2fe5ccec --- /dev/null +++ b/board/st/u8500/u-boot.lds @@ -0,0 +1,70 @@ +/* + * January 2004 - Changed to support H4 device + * Copyright (c) 2004-2008 Texas Instruments + * + * (C) Copyright 2002 + * Gary Jennejohn, DENX Software Engineering, + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") +OUTPUT_ARCH(arm) +ENTRY(_start) +SECTIONS +{ + . = 0x00000000; + + . = ALIGN(4); + .text : + { + cpu/arm_cortexa9/start.o (.text) + *(.text) + } + + . = ALIGN(4); + .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } + + . = ALIGN(4); + _data_start = . ; + .data : { *(.data) } + _data_end = . ; + + . = ALIGN(4); + .got : { *(.got) } + + __u_boot_cmd_start = .; + .u_boot_cmd : { *(.u_boot_cmd) } + __u_boot_cmd_end = .; + + /* + * To make U-Boot restartable, we need to save and copy the initialised + * data. Reserve a memory area idata and copy the .data section to it + * in u8500.c:board_init(). + */ + . = ALIGN(4); + _idata_start = .; + _idata_end = _idata_start + SIZEOF(.data); + . = _idata_end; + + . = ALIGN(4); + __bss_start = .; + .bss : { *(.bss) } + _end = .; +} diff --git a/board/st/u8500/u8500.c b/board/st/u8500/u8500.c index c02d7d9b0..debcc117c 100644 --- a/board/st/u8500/u8500.c +++ b/board/st/u8500/u8500.c @@ -69,6 +69,14 @@ int board_id; /* set in board_late_init() */ int errno; +/* + * Flag to indicate from where to where we have to copy the initialised data. + * In case we were loaded, its value is -1 and .data must be saved for an + * eventual restart. It is 1 if .data was restored, i.e. we were restarted, + * e.g. by kexec. + */ +static volatile int data_init_flag = -1; /* -1 to get it into .data section */ + /* PLLs for clock management registers */ enum { GATED = 0, @@ -194,6 +202,28 @@ int cpu_is_u8500v2(void) int board_init(void) { + extern char _idata_start[]; + extern char _data_start[]; + extern char _data_end[]; + unsigned long data_len; + + data_len = _data_end - _data_start; + if (++data_init_flag == 0) { + /* + * first init after reset/loading + * save .data section for restart + */ + memcpy(_idata_start, _data_start, data_len); + } else { + /* + * restart, e.g. by kexec + * copy back .data section + */ + memcpy(_data_start, _idata_start, data_len); + /* memcpy set data_init_flag back to zero */ + ++data_init_flag; + } + gd->bd->bi_arch_number = 0x1A4; gd->bd->bi_boot_params = 0x00000100; @@ -378,6 +408,7 @@ int board_late_init(void) #ifdef CONFIG_MMC uchar byte_array[] = {0x06, 0x06}; #endif + char strbuf[80]; /* * Determine and set board_id environment variable @@ -436,6 +467,25 @@ int board_late_init(void) setenv("mem", "512M"); } + /* + * Create crashkernel env dynamically since it depends on U-Boot start + * address. U-Boot itself is used for dumping. + * The 32K offset is hardcoded in the kexec-tools. + * Parsed by Linux setup.c:reserve_crashkernel() using + * lib/cmdline.c:memparse(). + * crashkernel=ramsize-range:size[,...][@offset] + */ + sprintf(strbuf, "crashkernel=1M@0x%lx", _armboot_start - 0x8000); + setenv("crashkernel", strbuf); + + /* + * Check for a crashdump, if data_init_flag > 0, i.e. we were + * restarted e.g. by kexec. Do not check for crashdump if we were just + * loaded from the x-loader. + */ + if (data_init_flag > 0) + setenv("preboot", "checkcrash"); + return (0); } #endif /* BOARD_LATE_INIT */ -- cgit v1.2.3