Hi,

On Sun, 28 Aug 2016 14:28:31 +0200
David Craven <da...@craven.ch> wrote:

> Thanks for your suggestions. I pushed the packages.
> 
> Is there a git repo somewhere I can pull your uboot stuff from? I'd
> like to test it =) 

There's no such repo right now.

I'd prefer merging into master soon to creating a fork.

For what it's worth, the changes I sent to the mailing list were tested on my 
machines, including that the Grub support still works.

If merging is deemed too risky, would it be possible to create a "wip-u-boot" 
branch on Savannah instead? - it seems that is how these bigger changes are 
handled. Then I could push the U-Boot parts there.

I've attached the UNFINISHED u-boot-install program which installs the U-Boot 
bootloader (comparable to grub-install). I plan to upstream it into the U-Boot 
main repository eventually. The other parts required for u-boot support were 
all already posted to our mailing list here.

gnu/packages/u-boot.scm could be merged now - it shouldn't affect anything 
because it's unused by master.

Other than that, it's mostly renaming "grub" -> "bootloader" in the Guix main 
source code that it creating a massive amount of noise in the patches I sent 
before. The actual functional change is a small part.

> I'm interested in starting a riscv port of guixsd at some point.

Nice! I think RISC-V is quite important to have.
/** 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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <parted/parted.h>
#include <sys/types.h>
#include <sys/stat.h>

/** 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 <key>=<value> 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=<name>] [--platform=<platform>] <drive>\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, "<drive> 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;
}

Reply via email to