Hi list, this is just a request for comments, because some things in this patch are more a hack than a valid solution. 1) building the tool Why do I need the "always := $(hostprogs-y)" line in the Makefile? If I omit this line, the buildsystem does not build the tool????? 2) any idea how to add some paths to the u-boot-v2 source tree itself. Currently I'm using "HOST_EXTRACFLAGS=-I$(srctree)" and these ugly lines in the source: #include "include/linux/utsrelease.h" #include "arch/x86/include/asm/u-boot.lds.h" to get the correct header info. I tried with the full paths to these directories, but it fails badly, because it interferes with the regular header files with the same names I need from "/usr/include/" to build it as a host tool. Any better idea how to not include the full paths in the "#include"-statements? -----8<-----8<-----8<-----8<-----8<-----8<-----8<-----8<-----8<-----8<-----8< From: Juergen Beisert <j.beisert@xxxxxxxxxxxxxx> Subject: Add a tool to activate u-boot-v2 as a boot loader on x86 architectures To use u-boot-v2 as a BIOS based bootloader for x86 architectures, the binary must be patched to get it bootstrapped at runtime. The 'setupmbr' tool installs the u-boot-binary to the given device node or image file and patch it in accordance to the needed sector information at runtime. Signed-off by: Juergen Beisert <j.beisert@xxxxxxxxxxxxxx> --- scripts/Makefile | 2 scripts/setupmbr/Makefile | 4 scripts/setupmbr/arch.h | 55 ++++ scripts/setupmbr/setupmbr.c | 558 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 619 insertions(+) Index: scripts/Makefile =================================================================== --- scripts/Makefile.orig +++ scripts/Makefile @@ -24,5 +24,7 @@ hostprogs-y += unifdef #subdir-$(CONFIG_MODVERSIONS) += genksyms subdir-y += mod +subdir-$(CONFIG_X86) += setupmbr + # Let clean descend into subdirs subdir- += basic kconfig Index: scripts/setupmbr/Makefile =================================================================== --- /dev/null +++ scripts/setupmbr/Makefile @@ -0,0 +1,4 @@ +HOST_EXTRACFLAGS=-I$(srctree) + +hostprogs-y := setupmbr +always := $(hostprogs-y) Index: scripts/setupmbr/arch.h =================================================================== --- /dev/null +++ scripts/setupmbr/arch.h @@ -0,0 +1,55 @@ + +/* we need the one from the host */ +#include <endian.h> +#include <stdint.h> + +/* Byte-orders. */ +#define swap16(x) \ +({ \ + uint16_t _x = (x); \ + (uint16_t) ((_x << 8) | (_x >> 8)); \ +}) + +#define swap32(x) \ +({ \ + uint32_t _x = (x); \ + (uint32_t) ((_x << 24) \ + | ((_x & (uint32_t) 0xFF00UL) << 8) \ + | ((_x & (uint32_t) 0xFF0000UL) >> 8) \ + | (_x >> 24)); \ +}) + +#define swap64(x) \ +({ \ + uint64_t _x = (x); \ + (uint64_t) ((_x << 56) \ + | ((_x & (uint64_t) 0xFF00ULL) << 40) \ + | ((_x & (uint64_t) 0xFF0000ULL) << 24) \ + | ((_x & (uint64_t) 0xFF000000ULL) << 8) \ + | ((_x & (uint64_t) 0xFF00000000ULL) >> 8) \ + | ((_x & (uint64_t) 0xFF0000000000ULL) >> 24) \ + | ((_x & (uint64_t) 0xFF000000000000ULL) >> 40) \ + | (_x >> 56)); \ +}) + +#if __BYTE_ORDER == __BIG_ENDIAN + +/* Our target is a ia32 machine, always little endian */ + +# define host2target_16(x) swap16(x) +# define host2target_32(x) swap32(x) +# define host2target_64(x) swap64(x) +# define target2host_16(x) swap16(x) +# define target2host_32(x) swap32(x) +# define target2host_64(x) swap64(x) + +#else + +# define host2target_16(x) (x) +# define host2target_32(x) (x) +# define host2target_64(x) (x) +# define target2host_16(x) (x) +# define target2host_32(x) (x) +# define target2host_64(x) (x) + +#endif Index: scripts/setupmbr/setupmbr.c =================================================================== --- /dev/null +++ scripts/setupmbr/setupmbr.c @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2009 Juergen Beisert, Pengutronix + * + * 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 + * + */ + +/** + * @brief Write the u-boot-binary to the MBR and the following disk sectors + * Also patch dedicated locations in the image to make it work at runtime + * + * Current restrictions are: + * - only installs into MBR and the sectors after it + * - tested only with QEMU + * - and maybe some others + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <assert.h> + +/* include the info from this u-boot-v2 release */ +#include "include/linux/utsrelease.h" +#include "arch/x86/include/asm/u-boot.lds.h" + +/** define to disable integrity tests and debug messages */ +#define NDEBUG + +/* some infos about our target architecture */ +#include "arch.h" + +/** + * "Disk Address Packet Structure" to be used when calling int13, + * function 0x42 + * Note: all entries are in target endianess + */ +struct DAPS +{ + uint8_t size; /* 0 marks it as invalid */ + uint8_t res1; /* always 0 */ + int8_t count; /* number of sectors 0...127 */ + uint8_t res2; /* always 0 */ + uint16_t offset; /* store address: offset */ + uint16_t segment; /* store address: segment */ + uint64_t lba; /* LBA */ +} __attribute__ ((packed)); + +/** + * Description of one partition table entry (D*S type) + * Note: all entries are in target endianess + */ +struct partition_entry { + uint8_t boot_indicator; + uint8_t chs_begin[3]; + uint8_t type; + uint8_t chs_end[3]; + uint32_t partition_start; /* LE */ + uint32_t partition_size; /* LE */ +} __attribute__ ((packed)); + +#ifndef NDEBUG +static void debugout(const struct DAPS *entry, int supress_entry) +{ + if (supress_entry) + printf("DAPS entry: "); + else + printf("DAPS entry % 3u: ", ((unsigned)entry & ( SECTOR_SIZE - 1)) / sizeof(struct DAPS)); + + printf("Size: % 2u, Count: % 3d, Offset: 0x%04hX, Segment: 0x%04hX, LBA: %llu\n", + entry->size, entry->count, + target2host_16(entry->offset), target2host_16(entry->segment), + target2host_64(entry->lba)); +} +#else +# define debugout(x,y) (__ASSERT_VOID_CAST(0)) +#endif + +/** + * Fill *one* DAPS + * @param buffer the DAPS to fill + * @param count sector count + * @param offset realmode offset in the segment + * @param segment real mode segment + * @param lba LBA of the first sector to read + * @return 0 on success + */ +static int fill_daps(struct DAPS *sector, unsigned count, unsigned offset, unsigned segment, uint64_t lba) +{ + assert(sector != NULL); + assert(count < 128); + assert(offset < 0x10000); + assert(segment < 0x10000); + + sector->size = sizeof(struct DAPS); + sector->res1 = 0; + sector->count = (int8_t)count; + sector->res2 = 0; + sector->offset = host2target_16(offset); + sector->segment = host2target_16(segment); + sector->lba = host2target_64(lba); + + return 0; +} + +/** + * Mark a DAPS invalid to let the boot loader code stop at this entry. + * @param buffer The DAPS to mark as invalid + * + * Marking as invalid must be done in accordance to the detection method + * the assembler routine in the boot loader uses: + * The current code tests for zero in the first two bytes of the DAPS. + */ +static void invalidate_daps(struct DAPS *sector) +{ + sector->size = MARK_DAPS_INVALID; + sector->res1 = 0; +} + +/** + * Create the indirect sector with the DAPS entries + * @param daps_table Where to store the entries + * @param size Size of the whole image in bytes + * @param pers_sector_count Count of sectors to skip after MBR for the persistant environment storage + * @return 0 on success + * + * This routine calculates the DAPS entries for the case the whole + * u-boot-v2 image fits into the MBR itself and the sectors after it. + * This means the start of the first partition must keep enough sectors + * unused. + * It also skips 'pers_sector_count' sectors after the MBR for special + * usage if given. + */ +static int uboot_linear_image(struct DAPS *daps_table, off_t size, long pers_sector_count) +{ + unsigned offset = LOAD_AREA, next_offset; + unsigned segment = LOAD_SEGMENT; + unsigned chunk_size, i = 0; + uint64_t lba = 2 + pers_sector_count; + int rc; + + /* + * We can load up to 127 sectors in one chunk. What a bad number... + * So, we will load in chunks of 32 kiB. + */ + + /* at runtime two sectors from the image are already loaded: MBR and indirect */ + size -= 2 * SECTOR_SIZE; + /* and now round up to multiple of sector size */ + size = (size + SECTOR_SIZE - 1) & ~(SECTOR_SIZE - 1); + + /* + * The largest image we can load with this method is: + * (SECTOR_SIZE / sizeof(DAPS) - 1) * 32 kiB + * For a 512 byte sector and a 16 byte DAPS: + * (512 / 16 - 1) * 32 kiB = 992 kiB + * Note: '- 1' to consider one entry is required to pad to a 32 kiB boundary + */ + + if (size >= (SECTOR_SIZE / sizeof(struct DAPS) - 1) * 32 * 1024) { + fprintf(stderr, "Image too large to boot. Max size is %u kiB, image size is %lu kiB\n", + (SECTOR_SIZE / sizeof(struct DAPS) - 1) * 32, size / 1024); + return -1; + } + + if (size > 32 * 1024) { + /* first fill up until the next 32 k boundary */ + next_offset = (offset + 32 * 1024 -1) & ~0x7fff; + chunk_size = next_offset - offset; + if (chunk_size & (SECTOR_SIZE-1)) { + fprintf(stderr, "Unable to pad from %X to %X in multiple of sectors\n", offset, next_offset); + return -1; + } + + rc = fill_daps(&daps_table[i], chunk_size / SECTOR_SIZE, offset, segment, lba); + if (rc != 0) + return -1; + debugout(&daps_table[i], 0); + + /* calculate values to enter the loop for the other entries */ + size -= chunk_size; + i++; + lba += chunk_size / SECTOR_SIZE; + offset += chunk_size; + if (offset >= 0x10000) { + segment += 4096; + offset = 0; + } + + /* + * Now load the remaining image part in 32 kiB chunks + */ + while (size) { + if (size >= 32 * 1024 ) { + if (i >= (SECTOR_SIZE / sizeof(struct DAPS))) { + fprintf(stderr, "Internal tool error: Too many DAPS entries!\n"); + return -1; + } + rc = fill_daps(&daps_table[i], 64, offset, segment, lba); + if (rc != 0) + return -1; + debugout(&daps_table[i], 0); + + size -= 32 * 1024; + lba += 64; + offset += 32 * 1024; + if (offset >= 0x10000) { + segment += 4096; + offset = 0; + } + i++; + } else { + if (i >= (SECTOR_SIZE / sizeof(struct DAPS))) { + fprintf(stderr, "Internal tool error: Too many DAPS entries!\n"); + return -1; + } + rc = fill_daps(&daps_table[i], size / SECTOR_SIZE, offset, segment, lba); + if (rc != 0) + return -1; + debugout(&daps_table[i], 0); + size = 0; /* finished */ + i++; + } + }; + } else { + /* less than 32 kiB. Very small image... */ + rc = fill_daps(&daps_table[i], size / SECTOR_SIZE, offset, segment, lba); + if (rc != 0) + return -1; + debugout(&daps_table[i], 0); + i++; + } + + /* + * Do not mark an entry as invalid if the buffer is full. The + * boot code stops if all entries of a buffer are read. + */ + if (i >= (SECTOR_SIZE / sizeof(struct DAPS))) + return 0; + + /* mark the last DAPS invalid */ + invalidate_daps(&daps_table[i]); + debugout(&daps_table[i], 0); + + return 0; +} + +/** + * Do some simple sanity checks if this sector could be an MBR + * @param sector Sector with data to check + * @param size Size of the buffer + * @return 0 if successfull + */ +static int check_for_valid_mbr(const uint8_t *sector, off_t size) +{ + if (size < SECTOR_SIZE) { + fprintf(stderr, "MBR too small to be valid\n"); + return -1; + } + + if ((sector[OFFSET_OF_SIGNATURE] != 0x55) || + (sector[OFFSET_OF_SIGNATURE + 1] != 0xAA)) { + fprintf(stderr, "No MBR signature found\n"); + return -1; + } + + /* FIXME: try to check if there is a valid partition table */ + return 0; +} + +/** + * Check space between start of the image and the start of the first partition + * @param hd_image HD image to examine + * @param size Size of the u-boot-v2 image + * @return 0 on success, -1 if the u-boot-v2 image is too large + */ +static int check_for_space(const void *hd_image, off_t size) +{ + struct partition_entry *partition; + uint8_t *mbr_disk_sector = (uint8_t*)hd_image; + off_t spare_sector_count; + + assert(hd_image != NULL); + assert(size > 0); + + if (check_for_valid_mbr(hd_image, size) != 0) + return -1; + + /* where to read */ + partition = (struct partition_entry*) &mbr_disk_sector[OFFSET_OF_PARTITION_TABLE]; + + /* TODO describes the first entry always the first partition? */ + spare_sector_count = target2host_32(partition->partition_start); + +#ifdef DEBUG + printf("Debug: Required free sectors for u-boot-v2 prior first partition: %lu, hd image provides: %lu\n", + (size + SECTOR_SIZE - 1) / SECTOR_SIZE, spare_sector_count); +#endif + spare_sector_count *= SECTOR_SIZE; + if (spare_sector_count < size) { + fprintf(stderr, "Not enough space after MBR to store u-boot-v2\n"); + fprintf(stderr, "Move begin of the first partition beyond sector %lu\n", (size + SECTOR_SIZE - 1) / SECTOR_SIZE); + return -1; + } + + return 0; +} + +/** + * Setup the persistant environment storage information + * @param patch_area Where to patch + * @param pers_sector_start Start sector of the persistant environment storage + * @param pers_sector_count Count of sectors for the persistant environment storage + * @return 0 on success + */ +static int store_pers_env_info(void *patch_area, uint64_t pers_sector_start, long pers_sector_count) +{ + uint64_t *start_lba = (uint64_t*)(patch_area + PATCH_AREA_PERS_START); + uint16_t *count_lba = (uint16_t*)(patch_area + PATCH_AREA_PERS_SIZE); + + assert(patch_area != NULL); + assert(pers_sector_count >= 0); + + if (pers_sector_count == 0) { + *count_lba = host2target_16(PATCH_AREA_PERS_SIZE_UNUSED); + return 0; + } + + *start_lba = host2target_64(pers_sector_start); + *count_lba = host2target_16(pers_sector_count); + + return 0; +} + +/** + * Prepare the MBR and indirect sector for runtime + * @param fd_uboot u-boot image to use + * @param fd_hd Hard disk image to prepare + * @param pers_sector_count Count of sectors to skip after MBR for the persistant environment storage + * @return 0 on success + * + * This routine expects a prepared hard disk image file with a partition table + * in its first sector. This method only is currently supported. + */ +static int uboot_overlay_mbr(int fd_uboot, int fd_hd, long pers_sector_count) +{ + const void *uboot_image; + void *hd_image; + int rc; + struct stat sb; + struct DAPS *embed; /* part of the MBR */ + struct DAPS *indirect; /* sector with indirect DAPS */ + off_t required_size; + + if (fstat(fd_uboot, &sb) == -1) { + perror("fstat"); + return -1; + } + + /* the u-boot image won't be touched */ + uboot_image = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd_uboot, 0); + if (uboot_image == MAP_FAILED) { + perror("mmap"); + return -1; + } + + rc = check_for_valid_mbr(uboot_image, sb.st_size); + if (rc != 0) { + fprintf(stderr, "u-boot-v2 image seems not valid: Bad MBR signature\n"); + goto on_error_hd; + } + + /* + * the persistant environment storage is in front of the main + * u-boot-v2 image. To handle both, we need more space in front of the + * the first partition. + */ + required_size = sb.st_size + pers_sector_count * SECTOR_SIZE; + + /* the hd image will be modified */ + hd_image = mmap(NULL, required_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd_hd, 0); + if (hd_image == MAP_FAILED) { + perror("mmap"); + rc = -1; + goto on_error_hd; + } + + /* check for space */ + rc = check_for_space(hd_image, required_size); + if (rc != 0) + goto on_error_space; + + /* embed u-boot-v2's boot code into the disk drive image */ + memcpy(hd_image, uboot_image, OFFSET_OF_PARTITION_TABLE); + + /* + * embed the u-boot-v2 main image into the disk drive image, + * but keep the persistant environment storage untouched + * (if defined), e.g. store the main image behind this special area. + */ + memcpy(hd_image + ((pers_sector_count + 1) * SECTOR_SIZE), + uboot_image + SECTOR_SIZE, sb.st_size - SECTOR_SIZE); + + /* now, prepare this hard disk image for BIOS based booting */ + embed = hd_image + PATCH_AREA; + indirect = hd_image + ((pers_sector_count + 1) * SECTOR_SIZE); + + /* + * Fill the embedded DAPS to let the basic boot code find the + * indirect sector at runtime + */ +#ifdef DEBUG + printf("Debug: Fill in embedded DAPS\n"); +#endif + rc = fill_daps(embed, 1, INDIRECT_AREA, INDIRECT_SEGMENT, + 1 + pers_sector_count); + if (rc != 0) + goto on_error_space; + debugout(embed, 1); + +#ifdef DEBUG + printf("Debug: Fill in indirect sector\n"); +#endif + /* + * fill the indirect sector with the remaining DAPS to load the + * whole u-boot-v2 image at runtime + */ + rc = uboot_linear_image(indirect, sb.st_size, pers_sector_count); + if (rc != 0) + goto on_error_space; + + /* + * TODO: Replace the fixed LBA starting number by a calculated one, + * to support u-boot-v2 as a chained loader in a different start + * sector than the MBR + */ + rc = store_pers_env_info(embed, 1, pers_sector_count); + if (rc != 0) + goto on_error_space; + +on_error_space: + munmap(hd_image, required_size); + +on_error_hd: + munmap((void*)uboot_image, sb.st_size); + + return rc; +} + +static void print_usage(const char *pname) +{ + printf("%s: Preparing a hard disk image for boot with u-boot-v2 on x86.\n", pname); + printf("Usage is\n %s [options] -m <u-boot image> -d <hd image>\n", pname); + printf(" [options] are:\n -s <count> sector count of the persistant environment storage\n"); + printf(" <u-boot image> u-boot-v2's boot image file\n"); + printf(" <hd image> HD image to store the u-boot image\n"); + printf(" If no '-s <x>' was given, u-boot-v2 occupies sectors 0 to n, else sector 0 and x+1 to n\n"); +} + +int main(int argc, char *argv[]) +{ + int rc = 0, c; + char *uboot_image_filename = NULL, *hd_image_filename = NULL; + int fd_uboot_image = 0, fd_hd_image = 0; + long uboot_pers_size = -1; + + if (argc == 1) { + print_usage(argv[0]); + exit(0); + } + + /* handle command line options first */ + while (1) { + c = getopt(argc, argv, "m:d:s:hv"); + if (c == -1) + break; + + switch (c) { + case 's': + uboot_pers_size = strtol(optarg, NULL, 0); + break; + case 'm': + uboot_image_filename = strdup(optarg); + break; + case 'd': + hd_image_filename = strdup(optarg); + break; + case 'h': + print_usage(argv[0]); + rc = 0; + goto on_error; + case 'v': + printf("setupmbr for u-boot-v%s\n", UTS_RELEASE); + printf("Send bug reports to 'bugs@xxxxxxxxxxxxxx'\n"); + rc = 0; + goto on_error; + } + } + + if (uboot_image_filename == NULL) { + print_usage(argv[0]); + rc = -1; + goto on_error; + } + + fd_uboot_image = open(uboot_image_filename, O_RDONLY); + if (fd_uboot_image == -1) { + fprintf(stderr, "Cannot open '%s' for reading\n", + uboot_image_filename); + rc = -1; + goto on_error; + } + + fd_hd_image = open(hd_image_filename, O_RDWR); + if (fd_hd_image == -1) { + fprintf(stderr, "Cannot open '%s'\n", hd_image_filename); + rc = -1; + goto on_error; + } + + if (uboot_pers_size < 0) + uboot_pers_size = 0; + + rc = uboot_overlay_mbr(fd_uboot_image, fd_hd_image, uboot_pers_size); + +on_error: + if (fd_uboot_image != -1) + close(fd_uboot_image); + if (fd_hd_image != -1) + close(fd_hd_image); + + if (uboot_image_filename != NULL) + free(uboot_image_filename); + if (hd_image_filename != NULL) + free(hd_image_filename); + + return rc; +} -- Pengutronix e.K. | Juergen Beisert | Linux Solutions for Science and Industry | Phone: +49-8766-939 228 | Vertretung Sued/Muenchen, Germany | Fax: +49-5121-206917-5555 | Amtsgericht Hildesheim, HRA 2686 | http://www.pengutronix.de/ | _______________________________________________ u-boot-v2 mailing list u-boot-v2@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/u-boot-v2