/** u-boot-install. SPDX GPLv3+ Installation methods are: A20: /proc/cpuinfo: Processor : ARMv7 Processor rev 4 (v7l) Hardware : sun7i "platform"; check the U-Boot config for this. U-Boot [def]config: CONFIG_ARCH_SUNXI=y CONFIG_MACH_SUN7I=y CONFIG_DEFAULT_DEVICE_TREE="sun7i-a20-olinuxino-lime2" Installer for Allwinner: dd if=u-boot-sunxi-with-spl.bin of=/dev/sdX bs=1024 seek=8 ; can be done by parted. ped_device_write, ped_device_sync, ... Novena: "We ship a simple script called novena-install-spl. This simply does a dd of the file to the specified disk." disk=/dev/disk/by-path/platform-2198000.usdhc file=/boot/u-boot.spl if ! dd if="${file}" of="${disk}" bs=1024 seek=1 conv=notrunc 2> /dev/null Raspberry Pi: mount /dev/sdb1 /mnt/tmp cp u-boot.bin /mnt/tmp/kernel.img umount /mnt/tmp STMicroelectronics: http://www.stlinux.com/u-boot/install [from the U-Boot prompt] Sheevaplug: http://www.cyrius.com/debian/kirkwood/sheevaplug/uboot-upgrade/ Xilinx: Downloading U-Boot to MicroBlaze http://www.wiki.xilinx.com/Build+U-Boot */ #include #include #include #include #include #include #include #include #include /** skip_overlay_long_line: given a FILE, skips forward until the next '\n'. It also skips the '\n'. * @param f File to read */ static void skip_overly_long_line(FILE* f) { char buf[200]; /* skip overly long lines */ while (fgets(buf, sizeof(buf), f) != NULL && strchr(buf, '\n') == NULL) ; } /** read_value_of_key_value_file: Read the value of a = file with the given key and return it. * @param separator separator between key and value * @param filename of file to read. File will be opened and closed automatically. * @param key key to search for. Whitespace after key in file is ignored. * @param value buffer to store value in. Will be NUL terminated if result != NULL. * @param valuesize size of buffer, including the NUL terminator. * @return value if successful, NULL otherwise. * * Cannot return values from lines which are longer than 399 Byte. */ static const char* read_value_of_key_value_file(char separator, const char* filename, const char* key, char* value, size_t valuesize) { char buf[400]; char* result = NULL; FILE* f = fopen(filename, "r"); if (f == NULL) return NULL; while (fgets(buf, sizeof(buf), f) != NULL) { if (strchr(buf, '\n') == NULL) { fprintf(stderr, "Warning: skipping overly long line that starts with \"%s\".\n", buf); skip_overly_long_line(f); } else { const char* xkey = buf; char* sep = strchr(buf, separator); if (sep != NULL) { /* we have key and value */ *sep = 0; /* just in case separator is ' ' */ const char* xvalue = sep + 1; /* strip leading whitespace from xvalue */ while (*xvalue == ' ' || *xvalue == '\t') ++xvalue; /* strip trailing whitespace from key */ for (--sep; sep >= xkey; --sep) { if (*sep != ' ' && *sep != '\t') break; *sep = 0; } if (strcmp(key, xkey) == 0 && valuesize >= 1) { /* Note: be careful about '\n' in value */ strncpy(value, xvalue, valuesize); if (value[valuesize - 1] == '\n') value[valuesize - 1] = 0; if (value[valuesize - 1] != 0) { fprintf(stderr, "Warning: skipping overly long value for entry that starts with %s=\"%s\".\n", xkey, xvalue); } else { result = strchr(value, '\n'); if (result != NULL) *result = 0; result = value; break; } } } } } (void) fclose(f); return result; } static const char* read_proc_cpuinfo_hardware_platform(char* value, size_t valuesize) { return read_value_of_key_value_file(':', "/proc/cpuinfo", "Hardware", value, valuesize); } /** get_first_commandline_option_value: gets the value of the first (long) option with the specified name on the commandline and returns it. * @param argv The argument "vector" from main() * @param key The name of the option. Includes the "--" prefix * @return Value of the option. NULL if it was not found */ static const char* get_first_commandline_option_value(char* argv[], const char* key) { size_t keylen = strlen(key); for (++argv; *argv != NULL; ++argv) { const char* arg = *argv; if (strncmp(arg, "--", strlen("--")) != 0) break; if (strncmp(arg, key, keylen) == 0) { const char* value = &arg[keylen]; if (key[0] && key[strlen(key) - 1] == '=') { return value; } else if (*value == 0) return value; } if (strcmp(arg, "--") == 0) /* end of options */ break; } return NULL; } /** get_first_commandline_argument: gets the value of the first commandline argument (i.e. the first non-option). * @param argv The argument "vector" from main() * @return Value of the argument. NULL if none is there. */ static const char* get_first_commandline_argument(char* argv[]) { for (++argv; *argv != NULL; ++argv) { const char* arg = *argv; if (strncmp(arg, "--", strlen("--")) != 0) break; if (strcmp(arg, "--") == 0) /* end of options */ { ++argv; break; } } return *argv; } /** print_usage: Prints usage information to stderr. * @param argv Argument "vector" from main() */ static void print_usage(char* argv[]) { fprintf(stderr, "Usage: %s [--source-image=] [--platform=] \n", argv[0] ?: "u-boot-install"); fprintf(stderr, "Usage: %s --list-platforms\n", argv[0] ?: "u-boot-install"); fprintf(stderr, "Usage: %s --list-drives\n", argv[0] ?: "u-boot-install"); fprintf(stderr, "If platform is not specified, it's autodetected by reading /proc/cpuinfo .\n"); fprintf(stderr, " is usually a block device of the drive.\n"); } /** ensure_nonclobbering_slot: Ensures a free slot at start_Sectors, size size_Sectors which doesn't clobber anything else (or fails) * @param drive Drive to use * @param start_Sectors The start (in Sectors) * @param size_Sectors The size (in Sectors) */ static void ensure_nonclobbering_slot(PedDevice* drive, PedSector start_Sectors, PedSector size_Sectors) { /* TODO print drive model just to make sure */ /* TODO print char* size = ped_unit_format_byte (device, device->length * device->sector_size); */ const char* drivename = drive->path; //long long sectorsize_Bytes = drive->sector_size; PedDisk* disk = ped_disk_new(drive); if (disk == NULL) { fprintf(stderr, "Error: could not read partition table from \"%s\".\n", drivename); exit(3); } for (PedPartition* part = ped_disk_next_partition(disk, NULL); part != NULL; part = ped_disk_next_partition(disk, part)) { if (part->num < 0) continue; /* example: 1 2048 3563667087 ext4 2 3563669504 6291456 3 3569960960 143360000 4 3713320960 193708175 ext4 */ /* FIXME check unit */ printf("%d\t%lld\t%lld\t%s\n", part->num, part->geom.start, part->geom.length, part->fs_type ? part->fs_type->name : ""); } { int max_partition_count; /* FIXME what's the difference to ped_disk_get_max_primary_partition_count ? */ if (!ped_disk_get_max_supported_partition_count(disk, &max_partition_count)) { /* FIXME print error message */ abort(); } if (max_partition_count > 56) { /* FIXME for disk type GPT; static PedDisk * gpt_duplicate (const PedDisk *disk); see _generate_header which sets FirstUsableLBA = gpt_disk_data->data_area.start */ /* TODO if GPT is too large, reduce size of GPT to max. 56 entries. If it even is GPT to begin with (see disk->type). That might entail copying all the things over to a new GPT. */ /* FirstUsableLBA is the first logical block that is used for contents */ abort(); } /* FIXME gpt_partition_align (PedPartition *part, const PedConstraint *constraint) -> boooool */ } /* FIXME check active gpt_partition_set_flag (PedPartition *part, PedPartitionFlag flag, int state) PED_PARTITION_BOOT FIXME LEGACY_BOOT ?? */ ped_disk_destroy(disk); } /** write_block_safely: first makes sure there's nothing at the specified location, then writes there. * @param drive Drive to write to * @param buffer Buffer to read * @param start_Bytes Location where to write to, in LBA bytes from the beginning of the disk * @param size_Bytes Size of buffer and also number of bytes to write */ static void write_block_safely(PedDevice* drive, const void* buffer, off_t start_Bytes, off_t size_Bytes) { const char* drivename = drive->path; long long sectorsize_Bytes = drive->sector_size; if (start_Bytes % sectorsize_Bytes != 0) { fprintf(stderr, "Error: %jd Bytes from the start of drive %s is not the start of a sector. Cannot install there.\n", (intmax_t) start_Bytes, drivename); exit(3); } PedSector start_Sectors = start_Bytes / sectorsize_Bytes; PedSector size_Sectors = size_Bytes / sectorsize_Bytes; // FIXME round up. ensure_nonclobbering_slot(drive, start_Sectors, size_Sectors); if (ped_device_write(drive, buffer, start_Sectors, size_Sectors) == 0) { /* FIXME print error message */ exit(4); } if (ped_device_sync(drive) == 0) { /* FIXME print error message */ exit(5); } } /** install_on_raw_drive: Installs U-Boot on Novena (i.MX6) devices * @param imagename Name of the image to read * @param drivename Name of the drive's block device (not partition) * @param position_Bytes position to write the image to */ static void install_on_raw_drive(const char* imagename, const char* drivename, off_t position_Bytes) { PedDevice* drive = ped_device_get(drivename); if (drive == NULL) { fprintf(stderr, "Error: could not open drive \"%s\".\n", drivename); exit(2); } FILE* f = fopen(imagename, "rb"); long size_Bytes; if (f == NULL || fseek(f, 0, SEEK_END) == -1 || (size_Bytes = ftell(f)) == -1 || fseek(f, 0, SEEK_SET) == -1) { /* FIXME ferror, strerror */ exit(1); } if (size_Bytes > 8000000U) { /* 8 MB */ /* FIXME print Too big instead of allocating a huge buffer. */ abort(); } void* buffer = malloc(size_Bytes); if (buffer == NULL) { fprintf(stderr, "ERROR: memory allocation of %ju bytes failed.\n", (uintmax_t) size_Bytes); /* FIXME print error */ abort(); } clearerr(f); if (fread(buffer, size_Bytes, 1U, f) != 1) { /* FIXME ferror, strerror. fread() does not distinguish between end-of-file and error, and callers must use feof(3) and ferror(3) to determine which occurred. */ abort(); } write_block_safely(drive, buffer, position_Bytes, size_Bytes); free(buffer); (void) fclose(f); ped_device_destroy(drive); } /** install_on_sunxi: Installs U-Boot on Allwinner (sunxi) devices * @param argv Argument "vector" from main() * @param drivename Name of the drive */ static void install_on_sunxi(char* argv[], const char* drivename) { const char* imagename = get_first_commandline_option_value(argv, "--source-image=") ?: "u-boot-sunxi-with-spl.bin"; install_on_raw_drive(imagename, drivename, 8192ULL); } /** install_on_novena: Installs U-Boot on Novena (Freescale i.MX6) devices * @param argv Argument "vector" from main() * @param drivename Name of the drive */ static void install_on_novena(char* argv[], const char* drivename) { const char* imagename = get_first_commandline_option_value(argv, "--source-image=") ?: "u-boot.spl"; install_on_raw_drive(imagename, drivename, 1024ULL); } /** struct installer: Represents an installer for a platform */ struct installer { const char* platform; void (*install)(char* argv[], const char* drive); }; /** installers: Registry for known installers */ static struct installer installers[] = { {.platform = "sun7i", install_on_sunxi}, {.platform = "mxc" /* imx6 */, install_on_novena}, // TODO {.platform = "uefi", install_on_uefi}, {}, /* sentinel */ }; /** install: Given a platform and drive, uses the platform-specific installer to install U-Boot on the drive. * @param argv Argument "vector" from main() * @param platform Name of the platform * @param drive Name of the drive */ static void install(char* argv[], const char* platform, const char* drive) { for (const struct installer* xinstallers = installers; xinstallers->platform != NULL; ++xinstallers) { const struct installer* xinstaller = xinstallers; const char* xplatform = xinstaller->platform; if (strcmp(xplatform, platform) == 0) { (*xinstaller->install)(argv, drive); return; } } fprintf(stderr, "Error: don't know how to install U-Boot on platform \"%s\".\n", platform); exit(1); } /** list_platforms: Lists supported platforms */ static void list_platforms(void) { for (const struct installer* xinstallers = installers; xinstallers->platform != NULL; ++xinstallers) { const struct installer* xinstaller = xinstallers; const char* xplatform = xinstaller->platform; if (puts(xplatform) == EOF) { /* FIXME print error message */ exit(1); } } } /** list_drives: Lists available drives */ static void list_drives(void) { ped_device_probe_all(); for (PedDevice* device = ped_device_get_next(NULL); device != NULL; device = ped_device_get_next(device)) { /* FIXME handle overflow. FIXME round properly. */ if (printf("%s\t%'lld MB\n", device->path, device->length * device->sector_size / 1000000) == -1) { /* FIXME print error message */ exit(1); } } } static const char* get_uefi_platform(void) { DIR* dir = opendir("/sys/firmware/efi/vars"); if (dir != NULL) { closedir(dir); return "uefi"; } else { if (errno != ENOENT) { perror("ERROR: /sys/firmware/efi/vars"); exit(9); } return NULL; } } int main(int argc, char* argv[]) { /* FIXME maybe make the user specify a --remote for remote installation and a --local for local installation - both mandatory */ char buf[200]; if (get_first_commandline_option_value(argv, "--help") != NULL) { print_usage(argv); return 0; } const char* oplist_platforms = get_first_commandline_option_value(argv, "--list-platforms"); if (oplist_platforms != NULL) { list_platforms(); return 0; } else { const char* oplist_drives = get_first_commandline_option_value(argv, "--list-drives"); if (oplist_drives != NULL) { list_drives(); return 0; } } const char* platform = get_first_commandline_option_value(argv, "--platform=") ?: get_uefi_platform() ?: read_proc_cpuinfo_hardware_platform(buf, sizeof(buf)); const char* drive = get_first_commandline_argument(argv); if (platform != NULL && drive != NULL) { printf("Platform %s\n", platform); printf("Drive %s\n", drive); //printf("Hardware %s\n", read_proc_cpuinfo_hardware_platform(buf, sizeof(buf))); install(argv, platform, drive); } else { print_usage(argv); return 1; } return 0; }