bootstd: Add the bootdev uclass
A 'bootdev' is a device which can be used to boot an operating system. It is a child of the media device (e.g. MMC) which handles reading files from that device, such as a bootflow file. Add a uclass for bootdev and the various helpers needed to make it work. Also add a binding file, empty for now. Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
		
							parent
							
								
									ef5e3891f5
								
							
						
					
					
						commit
						201417d700
					
				|  | @ -696,9 +696,11 @@ F:	tools/binman/ | ||||||
| BOOTDEVICE | BOOTDEVICE | ||||||
| M:	Simon Glass <sjg@chromium.org> | M:	Simon Glass <sjg@chromium.org> | ||||||
| S:	Maintained | S:	Maintained | ||||||
|  | F:	boot/bootdev*.c | ||||||
| F:	boot/bootstd.c | F:	boot/bootstd.c | ||||||
| F:	include/bootstd.h | F:	include/bootdev*.h | ||||||
| F:	include/bootflow.h | F:	include/bootflow.h | ||||||
|  | F:	include/bootstd.h | ||||||
| 
 | 
 | ||||||
| BTRFS | BTRFS | ||||||
| M:	Marek Behun <marek.behun@nic.cz> | M:	Marek Behun <marek.behun@nic.cz> | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ obj-y += image.o image-board.o | ||||||
| obj-$(CONFIG_ANDROID_AB) += android_ab.o | obj-$(CONFIG_ANDROID_AB) += android_ab.o | ||||||
| obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o | obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o | ||||||
| 
 | 
 | ||||||
|  | obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootdev-uclass.o | ||||||
| obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o | obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o | ||||||
| 
 | 
 | ||||||
| obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o | obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o | ||||||
|  |  | ||||||
|  | @ -0,0 +1,640 @@ | ||||||
|  | // SPDX-License-Identifier: GPL-2.0+
 | ||||||
|  | /*
 | ||||||
|  |  * Copyright 2021 Google LLC | ||||||
|  |  * Written by Simon Glass <sjg@chromium.org> | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #define LOG_CATEGORY UCLASS_BOOTSTD | ||||||
|  | 
 | ||||||
|  | #include <common.h> | ||||||
|  | #include <dm.h> | ||||||
|  | #include <bootdev.h> | ||||||
|  | #include <bootflow.h> | ||||||
|  | #include <bootstd.h> | ||||||
|  | #include <env.h> | ||||||
|  | #include <fs.h> | ||||||
|  | #include <log.h> | ||||||
|  | #include <malloc.h> | ||||||
|  | #include <part.h> | ||||||
|  | #include <sort.h> | ||||||
|  | #include <dm/device-internal.h> | ||||||
|  | #include <dm/lists.h> | ||||||
|  | #include <dm/uclass-internal.h> | ||||||
|  | 
 | ||||||
|  | enum { | ||||||
|  | 	/*
 | ||||||
|  | 	 * Set some sort of limit on the number of partitions a bootdev can | ||||||
|  | 	 * have. Note that for disks this limits the partitions numbers that | ||||||
|  | 	 * are scanned to 1..MAX_BOOTFLOWS_PER_BOOTDEV | ||||||
|  | 	 */ | ||||||
|  | 	MAX_PART_PER_BOOTDEV	= 30, | ||||||
|  | 
 | ||||||
|  | 	/* Maximum supported length of the "boot_targets" env string */ | ||||||
|  | 	BOOT_TARGETS_MAX_LEN	= 100, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | int bootdev_add_bootflow(struct bootflow *bflow) | ||||||
|  | { | ||||||
|  | 	struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); | ||||||
|  | 	struct bootstd_priv *std; | ||||||
|  | 	struct bootflow *new; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	assert(bflow->dev); | ||||||
|  | 	ret = bootstd_get_priv(&std); | ||||||
|  | 	if (ret) | ||||||
|  | 		return ret; | ||||||
|  | 
 | ||||||
|  | 	new = malloc(sizeof(*bflow)); | ||||||
|  | 	if (!new) | ||||||
|  | 		return log_msg_ret("bflow", -ENOMEM); | ||||||
|  | 	memcpy(new, bflow, sizeof(*bflow)); | ||||||
|  | 
 | ||||||
|  | 	list_add_tail(&new->glob_node, &std->glob_head); | ||||||
|  | 	list_add_tail(&new->bm_node, &ucp->bootflow_head); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp) | ||||||
|  | { | ||||||
|  | 	struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); | ||||||
|  | 
 | ||||||
|  | 	if (list_empty(&ucp->bootflow_head)) | ||||||
|  | 		return -ENOENT; | ||||||
|  | 
 | ||||||
|  | 	*bflowp = list_first_entry(&ucp->bootflow_head, struct bootflow, | ||||||
|  | 				   bm_node); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_next_bootflow(struct bootflow **bflowp) | ||||||
|  | { | ||||||
|  | 	struct bootflow *bflow = *bflowp; | ||||||
|  | 	struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); | ||||||
|  | 
 | ||||||
|  | 	*bflowp = NULL; | ||||||
|  | 
 | ||||||
|  | 	if (list_is_last(&bflow->bm_node, &ucp->bootflow_head)) | ||||||
|  | 		return -ENOENT; | ||||||
|  | 
 | ||||||
|  | 	*bflowp = list_entry(bflow->bm_node.next, struct bootflow, bm_node); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name, | ||||||
|  | 		 struct udevice **devp) | ||||||
|  | { | ||||||
|  | 	struct udevice *dev; | ||||||
|  | 	char dev_name[30]; | ||||||
|  | 	char *str; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	snprintf(dev_name, sizeof(dev_name), "%s.%s", parent->name, name); | ||||||
|  | 	str = strdup(dev_name); | ||||||
|  | 	if (!str) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 	ret = device_bind_driver(parent, drv_name, str, &dev); | ||||||
|  | 	if (ret) | ||||||
|  | 		return ret; | ||||||
|  | 	device_set_name_alloced(dev); | ||||||
|  | 	*devp = dev; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, | ||||||
|  | 			struct bootflow_iter *iter, struct bootflow *bflow) | ||||||
|  | { | ||||||
|  | 	struct blk_desc *desc = dev_get_uclass_plat(blk); | ||||||
|  | 	struct disk_partition info; | ||||||
|  | 	char partstr[20]; | ||||||
|  | 	char name[60]; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	/* Sanity check */ | ||||||
|  | 	if (iter->part >= MAX_PART_PER_BOOTDEV) | ||||||
|  | 		return log_msg_ret("max", -ESHUTDOWN); | ||||||
|  | 
 | ||||||
|  | 	bflow->blk = blk; | ||||||
|  | 	if (iter->part) | ||||||
|  | 		snprintf(partstr, sizeof(partstr), "part_%x", iter->part); | ||||||
|  | 	else | ||||||
|  | 		strcpy(partstr, "whole"); | ||||||
|  | 	snprintf(name, sizeof(name), "%s.%s", dev->name, partstr); | ||||||
|  | 	bflow->name = strdup(name); | ||||||
|  | 	if (!bflow->name) | ||||||
|  | 		return log_msg_ret("name", -ENOMEM); | ||||||
|  | 
 | ||||||
|  | 	bflow->part = iter->part; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * partition numbers start at 0 so this cannot succeed, but it can tell | ||||||
|  | 	 * us whether there is valid media there | ||||||
|  | 	 */ | ||||||
|  | 	ret = part_get_info(desc, iter->part, &info); | ||||||
|  | 	if (!iter->part && ret == -ENOENT) | ||||||
|  | 		ret = 0; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * This error indicates the media is not present. Otherwise we just | ||||||
|  | 	 * blindly scan the next partition. We could be more intelligent here | ||||||
|  | 	 * and check which partition numbers actually exist. | ||||||
|  | 	 */ | ||||||
|  | 	if (ret == -EOPNOTSUPP) | ||||||
|  | 		ret = -ESHUTDOWN; | ||||||
|  | 	else | ||||||
|  | 		bflow->state = BOOTFLOWST_MEDIA; | ||||||
|  | 	if (ret) | ||||||
|  | 		return log_msg_ret("part", ret); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * Currently we don't get the number of partitions, so just | ||||||
|  | 	 * assume a large number | ||||||
|  | 	 */ | ||||||
|  | 	iter->max_part = MAX_PART_PER_BOOTDEV; | ||||||
|  | 
 | ||||||
|  | 	if (iter->part) { | ||||||
|  | 		ret = fs_set_blk_dev_with_part(desc, bflow->part); | ||||||
|  | 		bflow->state = BOOTFLOWST_PART; | ||||||
|  | 
 | ||||||
|  | 		/* Use an #ifdef due to info.sys_ind */ | ||||||
|  | #ifdef CONFIG_DOS_PARTITION | ||||||
|  | 		log_debug("%s: Found partition %x type %x fstype %d\n", | ||||||
|  | 			  blk->name, bflow->part, info.sys_ind, | ||||||
|  | 			  ret ? -1 : fs_get_type()); | ||||||
|  | #endif | ||||||
|  | 		if (ret) | ||||||
|  | 			return log_msg_ret("fs", ret); | ||||||
|  | 		bflow->state = BOOTFLOWST_FS; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bootdev_list(bool probe) | ||||||
|  | { | ||||||
|  | 	struct udevice *dev; | ||||||
|  | 	int ret; | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	printf("Seq  Probed  Status  Uclass    Name\n"); | ||||||
|  | 	printf("---  ------  ------  --------  ------------------\n"); | ||||||
|  | 	if (probe) | ||||||
|  | 		ret = uclass_first_device_err(UCLASS_BOOTDEV, &dev); | ||||||
|  | 	else | ||||||
|  | 		ret = uclass_find_first_device(UCLASS_BOOTDEV, &dev); | ||||||
|  | 	for (i = 0; dev; i++) { | ||||||
|  | 		printf("%3x   [ %c ]  %6s  %-9.9s %s\n", dev_seq(dev), | ||||||
|  | 		       device_active(dev) ? '+' : ' ', | ||||||
|  | 		       ret ? simple_itoa(ret) : "OK", | ||||||
|  | 		       dev_get_uclass_name(dev_get_parent(dev)), dev->name); | ||||||
|  | 		if (probe) | ||||||
|  | 			ret = uclass_next_device_err(&dev); | ||||||
|  | 		else | ||||||
|  | 			ret = uclass_find_next_device(&dev); | ||||||
|  | 	} | ||||||
|  | 	printf("---  ------  ------  --------  ------------------\n"); | ||||||
|  | 	printf("(%d bootdev%s)\n", i, i != 1 ? "s" : ""); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name) | ||||||
|  | { | ||||||
|  | 	struct udevice *bdev; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, | ||||||
|  | 						&bdev); | ||||||
|  | 	if (ret) { | ||||||
|  | 		if (ret != -ENODEV) { | ||||||
|  | 			log_debug("Cannot access bootdev device\n"); | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		ret = bootdev_bind(parent, drv_name, "bootdev", &bdev); | ||||||
|  | 		if (ret) { | ||||||
|  | 			log_debug("Cannot create bootdev device\n"); | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_setup_sibling_blk(struct udevice *blk, const char *drv_name) | ||||||
|  | { | ||||||
|  | 	struct udevice *parent, *dev; | ||||||
|  | 	char dev_name[50]; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	snprintf(dev_name, sizeof(dev_name), "%s.%s", blk->name, "bootdev"); | ||||||
|  | 
 | ||||||
|  | 	parent = dev_get_parent(blk); | ||||||
|  | 	ret = device_find_child_by_name(parent, dev_name, &dev); | ||||||
|  | 	if (ret) { | ||||||
|  | 		char *str; | ||||||
|  | 
 | ||||||
|  | 		if (ret != -ENODEV) { | ||||||
|  | 			log_debug("Cannot access bootdev device\n"); | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
|  | 		str = strdup(dev_name); | ||||||
|  | 		if (!str) | ||||||
|  | 			return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 		ret = device_bind_driver(parent, drv_name, str, &dev); | ||||||
|  | 		if (ret) { | ||||||
|  | 			log_debug("Cannot create bootdev device\n"); | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
|  | 		device_set_name_alloced(dev); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp) | ||||||
|  | { | ||||||
|  | 	struct udevice *parent = dev_get_parent(dev); | ||||||
|  | 	struct udevice *blk; | ||||||
|  | 	int ret, len; | ||||||
|  | 	char *p; | ||||||
|  | 
 | ||||||
|  | 	if (device_get_uclass_id(dev) != UCLASS_BOOTDEV) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	/* This should always work if bootdev_setup_sibling_blk() was used */ | ||||||
|  | 	p = strstr(dev->name, ".bootdev"); | ||||||
|  | 	if (!p) | ||||||
|  | 		return log_msg_ret("str", -EINVAL); | ||||||
|  | 
 | ||||||
|  | 	len = p - dev->name; | ||||||
|  | 	ret = device_find_child_by_namelen(parent, dev->name, len, &blk); | ||||||
|  | 	if (ret) | ||||||
|  | 		return log_msg_ret("find", ret); | ||||||
|  | 	*blkp = blk; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bootdev_get_from_blk(struct udevice *blk, struct udevice **bootdevp) | ||||||
|  | { | ||||||
|  | 	struct udevice *parent = dev_get_parent(blk); | ||||||
|  | 	struct udevice *bootdev; | ||||||
|  | 	char dev_name[50]; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	if (device_get_uclass_id(blk) != UCLASS_BLK) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	/* This should always work if bootdev_setup_sibling_blk() was used */ | ||||||
|  | 	snprintf(dev_name, sizeof(dev_name), "%s.%s", blk->name, "bootdev"); | ||||||
|  | 	ret = device_find_child_by_name(parent, dev_name, &bootdev); | ||||||
|  | 	if (ret) | ||||||
|  | 		return log_msg_ret("find", ret); | ||||||
|  | 	*bootdevp = bootdev; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_unbind_dev(struct udevice *parent) | ||||||
|  | { | ||||||
|  | 	struct udevice *dev; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, &dev); | ||||||
|  | 	if (!ret) { | ||||||
|  | 		ret = device_remove(dev, DM_REMOVE_NORMAL); | ||||||
|  | 		if (ret) | ||||||
|  | 			return log_msg_ret("rem", ret); | ||||||
|  | 		ret = device_unbind(dev); | ||||||
|  | 		if (ret) | ||||||
|  | 			return log_msg_ret("unb", ret); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_find_by_label() - Convert a label string to a bootdev device | ||||||
|  |  * | ||||||
|  |  * Looks up a label name to find the associated bootdev. For example, if the | ||||||
|  |  * label name is "mmc2", this will find a bootdev for an mmc device whose | ||||||
|  |  * sequence number is 2. | ||||||
|  |  * | ||||||
|  |  * @label: Label string to convert, e.g. "mmc2" | ||||||
|  |  * @devp: Returns bootdev device corresponding to that boot label | ||||||
|  |  * Return: 0 if OK, -EINVAL if the label name (e.g. "mmc") does not refer to a | ||||||
|  |  *	uclass, -ENOENT if no bootdev for that media has the sequence number | ||||||
|  |  *	(e.g. 2) | ||||||
|  |  */ | ||||||
|  | int bootdev_find_by_label(const char *label, struct udevice **devp) | ||||||
|  | { | ||||||
|  | 	struct udevice *media; | ||||||
|  | 	struct uclass *uc; | ||||||
|  | 	enum uclass_id id; | ||||||
|  | 	const char *end; | ||||||
|  | 	int seq; | ||||||
|  | 
 | ||||||
|  | 	seq = trailing_strtoln_end(label, NULL, &end); | ||||||
|  | 	id = uclass_get_by_namelen(label, end - label); | ||||||
|  | 	log_debug("find %s: seq=%d, id=%d/%s\n", label, seq, id, | ||||||
|  | 		  uclass_get_name(id)); | ||||||
|  | 	if (id == UCLASS_INVALID) { | ||||||
|  | 		log_warning("Unknown uclass '%s' in label\n", label); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 	if (id == UCLASS_USB) | ||||||
|  | 		id = UCLASS_MASS_STORAGE; | ||||||
|  | 
 | ||||||
|  | 	/* Iterate through devices in the media uclass (e.g. UCLASS_MMC) */ | ||||||
|  | 	uclass_id_foreach_dev(id, media, uc) { | ||||||
|  | 		struct udevice *bdev, *blk; | ||||||
|  | 		int ret; | ||||||
|  | 
 | ||||||
|  | 		/* if there is no seq, match anything */ | ||||||
|  | 		if (seq != -1 && dev_seq(media) != seq) { | ||||||
|  | 			log_debug("- skip, media seq=%d\n", dev_seq(media)); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		ret = device_find_first_child_by_uclass(media, UCLASS_BOOTDEV, | ||||||
|  | 							&bdev); | ||||||
|  | 		if (ret) { | ||||||
|  | 			log_debug("- looking via blk, seq=%d, id=%d\n", seq, | ||||||
|  | 				  id); | ||||||
|  | 			ret = blk_find_device(id, seq, &blk); | ||||||
|  | 			if (!ret) { | ||||||
|  | 				log_debug("- get from blk %s\n", blk->name); | ||||||
|  | 				ret = bootdev_get_from_blk(blk, &bdev); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if (!ret) { | ||||||
|  | 			log_debug("- found %s\n", bdev->name); | ||||||
|  | 			*devp = bdev; | ||||||
|  | 			return 0; | ||||||
|  | 		} | ||||||
|  | 		log_debug("- no device in %s\n", media->name); | ||||||
|  | 	} | ||||||
|  | 	log_warning("Unknown seq %d for label '%s'\n", seq, label); | ||||||
|  | 
 | ||||||
|  | 	return -ENOENT; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_find_by_any(const char *name, struct udevice **devp) | ||||||
|  | { | ||||||
|  | 	struct udevice *dev; | ||||||
|  | 	int ret, seq; | ||||||
|  | 	char *endp; | ||||||
|  | 
 | ||||||
|  | 	seq = simple_strtol(name, &endp, 16); | ||||||
|  | 
 | ||||||
|  | 	/* Select by name, label or number */ | ||||||
|  | 	if (*endp) { | ||||||
|  | 		ret = uclass_get_device_by_name(UCLASS_BOOTDEV, name, &dev); | ||||||
|  | 		if (ret == -ENODEV) { | ||||||
|  | 			ret = bootdev_find_by_label(name, &dev); | ||||||
|  | 			if (ret) { | ||||||
|  | 				printf("Cannot find bootdev '%s' (err=%d)\n", | ||||||
|  | 				       name, ret); | ||||||
|  | 				return ret; | ||||||
|  | 			} | ||||||
|  | 			ret = device_probe(dev); | ||||||
|  | 		} | ||||||
|  | 		if (ret) { | ||||||
|  | 			printf("Cannot probe bootdev '%s' (err=%d)\n", name, | ||||||
|  | 			       ret); | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		ret = uclass_get_device_by_seq(UCLASS_BOOTDEV, seq, &dev); | ||||||
|  | 	} | ||||||
|  | 	if (ret) { | ||||||
|  | 		printf("Cannot find '%s' (err=%d)\n", name, ret); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	*devp = dev; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, | ||||||
|  | 			 struct bootflow *bflow) | ||||||
|  | { | ||||||
|  | 	const struct bootdev_ops *ops = bootdev_get_ops(dev); | ||||||
|  | 
 | ||||||
|  | 	if (!ops->get_bootflow) | ||||||
|  | 		return -ENOSYS; | ||||||
|  | 	memset(bflow, '\0', sizeof(*bflow)); | ||||||
|  | 	bflow->dev = dev; | ||||||
|  | 	bflow->method = iter->method; | ||||||
|  | 	bflow->state = BOOTFLOWST_BASE; | ||||||
|  | 
 | ||||||
|  | 	return ops->get_bootflow(dev, iter, bflow); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bootdev_clear_bootflows(struct udevice *dev) | ||||||
|  | { | ||||||
|  | 	struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); | ||||||
|  | 
 | ||||||
|  | 	while (!list_empty(&ucp->bootflow_head)) { | ||||||
|  | 		struct bootflow *bflow; | ||||||
|  | 
 | ||||||
|  | 		bflow = list_first_entry(&ucp->bootflow_head, struct bootflow, | ||||||
|  | 					 bm_node); | ||||||
|  | 		/* later bootflow_remove(bflow); */ | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * h_cmp_bootdev() - Compare two bootdevs to find out which should go first | ||||||
|  |  * | ||||||
|  |  * @v1: struct udevice * of first bootdev device | ||||||
|  |  * @v2: struct udevice * of second bootdev device | ||||||
|  |  * Return: sort order (<0 if dev1 < dev2, ==0 if equal, >0 if dev1 > dev2) | ||||||
|  |  */ | ||||||
|  | static int h_cmp_bootdev(const void *v1, const void *v2) | ||||||
|  | { | ||||||
|  | 	const struct udevice *dev1 = *(struct udevice **)v1; | ||||||
|  | 	const struct udevice *dev2 = *(struct udevice **)v2; | ||||||
|  | 	const struct bootdev_uc_plat *ucp1 = dev_get_uclass_plat(dev1); | ||||||
|  | 	const struct bootdev_uc_plat *ucp2 = dev_get_uclass_plat(dev2); | ||||||
|  | 	int diff; | ||||||
|  | 
 | ||||||
|  | 	/* Use priority first */ | ||||||
|  | 	diff = ucp1->prio - ucp2->prio; | ||||||
|  | 	if (diff) | ||||||
|  | 		return diff; | ||||||
|  | 
 | ||||||
|  | 	/* Fall back to seq for devices of the same priority */ | ||||||
|  | 	diff = dev_seq(dev1) - dev_seq(dev2); | ||||||
|  | 
 | ||||||
|  | 	return diff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * build_order() - Build the ordered list of bootdevs to use | ||||||
|  |  * | ||||||
|  |  * This builds an ordered list of devices by one of three methods: | ||||||
|  |  * - using the boot_targets environment variable, if non-empty | ||||||
|  |  * - using the bootdev-order devicetree property, if present | ||||||
|  |  * - sorted by priority and sequence number | ||||||
|  |  * | ||||||
|  |  * @bootstd: BOOTSTD device to use | ||||||
|  |  * @order: Bootdevs listed in default order | ||||||
|  |  * @max_count: Number of entries in @order | ||||||
|  |  * Return: number of bootdevs found in the ordering, or -E2BIG if the | ||||||
|  |  * boot_targets string is too long, or -EXDEV if the ordering produced 0 results | ||||||
|  |  */ | ||||||
|  | static int build_order(struct udevice *bootstd, struct udevice **order, | ||||||
|  | 		       int max_count) | ||||||
|  | { | ||||||
|  | 	const char *overflow_target = NULL; | ||||||
|  | 	const char *const *labels; | ||||||
|  | 	struct udevice *dev; | ||||||
|  | 	const char *targets; | ||||||
|  | 	int i, ret, count; | ||||||
|  | 
 | ||||||
|  | 	targets = env_get("boot_targets"); | ||||||
|  | 	labels = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? | ||||||
|  | 		bootstd_get_bootdev_order(bootstd) : NULL; | ||||||
|  | 	if (targets) { | ||||||
|  | 		char str[BOOT_TARGETS_MAX_LEN]; | ||||||
|  | 		char *target; | ||||||
|  | 
 | ||||||
|  | 		if (strlen(targets) >= BOOT_TARGETS_MAX_LEN) | ||||||
|  | 			return log_msg_ret("len", -E2BIG); | ||||||
|  | 
 | ||||||
|  | 		/* make a copy of the string, since strok() will change it */ | ||||||
|  | 		strcpy(str, targets); | ||||||
|  | 		for (i = 0, target = strtok(str, " "); target; | ||||||
|  | 		     target = strtok(NULL, " ")) { | ||||||
|  | 			ret = bootdev_find_by_label(target, &dev); | ||||||
|  | 			if (!ret) { | ||||||
|  | 				if (i == max_count) { | ||||||
|  | 					overflow_target = target; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				order[i++] = dev; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		count = i; | ||||||
|  | 	} else if (labels) { | ||||||
|  | 		int upto; | ||||||
|  | 
 | ||||||
|  | 		upto = 0; | ||||||
|  | 		for (i = 0; labels[i]; i++) { | ||||||
|  | 			ret = bootdev_find_by_label(labels[i], &dev); | ||||||
|  | 			if (!ret) { | ||||||
|  | 				if (upto == max_count) { | ||||||
|  | 					overflow_target = labels[i]; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				order[upto++] = dev; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		count = upto; | ||||||
|  | 	} else { | ||||||
|  | 		/* sort them into priority order */ | ||||||
|  | 		count = max_count; | ||||||
|  | 		qsort(order, count, sizeof(struct udevice *), h_cmp_bootdev); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (overflow_target) { | ||||||
|  | 		log_warning("Expected at most %d bootdevs, but overflowed with boot_target '%s'\n", | ||||||
|  | 			    max_count, overflow_target); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!count) | ||||||
|  | 		return log_msg_ret("targ", -EXDEV); | ||||||
|  | 
 | ||||||
|  | 	return count; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp) | ||||||
|  | { | ||||||
|  | 	struct udevice *bootstd, *dev = *devp, **order; | ||||||
|  | 	int upto, i; | ||||||
|  | 	int count; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); | ||||||
|  | 	if (ret) { | ||||||
|  | 		log_err("Missing bootstd device\n"); | ||||||
|  | 		return log_msg_ret("std", ret); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Handle scanning a single device */ | ||||||
|  | 	if (dev) { | ||||||
|  | 		iter->flags |= BOOTFLOWF_SINGLE_DEV; | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	count = uclass_id_count(UCLASS_BOOTDEV); | ||||||
|  | 	if (!count) | ||||||
|  | 		return log_msg_ret("count", -ENOENT); | ||||||
|  | 
 | ||||||
|  | 	order = calloc(count, sizeof(struct udevice *)); | ||||||
|  | 	if (!order) | ||||||
|  | 		return log_msg_ret("order", -ENOMEM); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * Get a list of bootdevs, in seq order (i.e. using aliases). There may | ||||||
|  | 	 * be gaps so try to count up high enough to find them all. | ||||||
|  | 	 */ | ||||||
|  | 	for (i = 0, upto = 0; upto < count && i < 20 + count * 2; i++) { | ||||||
|  | 		ret = uclass_find_device_by_seq(UCLASS_BOOTDEV, i, &dev); | ||||||
|  | 		if (!ret) | ||||||
|  | 			order[upto++] = dev; | ||||||
|  | 	} | ||||||
|  | 	log_debug("Found %d bootdevs\n", count); | ||||||
|  | 	if (upto != count) | ||||||
|  | 		log_debug("Expected %d bootdevs, found %d using aliases\n", | ||||||
|  | 			  count, upto); | ||||||
|  | 
 | ||||||
|  | 	count = build_order(bootstd, order, upto); | ||||||
|  | 	if (count < 0) { | ||||||
|  | 		free(order); | ||||||
|  | 		return log_msg_ret("build", count); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	iter->dev_order = order; | ||||||
|  | 	iter->num_devs = count; | ||||||
|  | 	iter->cur_dev = 0; | ||||||
|  | 
 | ||||||
|  | 	dev = *order; | ||||||
|  | 	ret = device_probe(dev); | ||||||
|  | 	if (ret) | ||||||
|  | 		return log_msg_ret("probe", ret); | ||||||
|  | 	*devp = dev; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bootdev_post_bind(struct udevice *dev) | ||||||
|  | { | ||||||
|  | 	struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); | ||||||
|  | 
 | ||||||
|  | 	INIT_LIST_HEAD(&ucp->bootflow_head); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int bootdev_pre_unbind(struct udevice *dev) | ||||||
|  | { | ||||||
|  | 	bootdev_clear_bootflows(dev); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | UCLASS_DRIVER(bootdev) = { | ||||||
|  | 	.id		= UCLASS_BOOTDEV, | ||||||
|  | 	.name		= "bootdev", | ||||||
|  | 	.flags		= DM_UC_FLAG_SEQ_ALIAS, | ||||||
|  | 	.per_device_plat_auto	= sizeof(struct bootdev_uc_plat), | ||||||
|  | 	.post_bind	= bootdev_post_bind, | ||||||
|  | 	.pre_unbind	= bootdev_pre_unbind, | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | U-Boot boot device (bootdev) | ||||||
|  | ============================ | ||||||
|  | 
 | ||||||
|  | A bootdev provides a way to obtain a bootflow file from a device. It is a | ||||||
|  | child of the media device (UCLASS_MMC, UCLASS_SPI_FLASH, etc.) | ||||||
|  | 
 | ||||||
|  | The bootdev driver is provided by the media devices. The bindings for each | ||||||
|  | are described in this file (to come). | ||||||
|  | @ -0,0 +1,275 @@ | ||||||
|  | /* SPDX-License-Identifier: GPL-2.0+ */ | ||||||
|  | /*
 | ||||||
|  |  * Copyright 2021 Google LLC | ||||||
|  |  * Written by Simon Glass <sjg@chromium.org> | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #ifndef __bootdev_h | ||||||
|  | #define __bootdev_h | ||||||
|  | 
 | ||||||
|  | #include <linux/list.h> | ||||||
|  | 
 | ||||||
|  | struct bootflow; | ||||||
|  | struct bootflow_iter; | ||||||
|  | struct udevice; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * enum bootdev_prio_t - priority of each bootdev | ||||||
|  |  * | ||||||
|  |  * These values are associated with each bootdev and set up by the driver. | ||||||
|  |  * | ||||||
|  |  * Smallest value is the highest priority. By default, bootdevs are scanned from | ||||||
|  |  * highest to lowest priority | ||||||
|  |  */ | ||||||
|  | enum bootdev_prio_t { | ||||||
|  | 	BOOTDEVP_0_INTERNAL_FAST	= 10, | ||||||
|  | 	BOOTDEVP_1_INTERNAL_SLOW	= 20, | ||||||
|  | 	BOOTDEVP_2_SCAN_FAST		= 30, | ||||||
|  | 	BOOTDEVP_3_SCAN_SLOW		= 40, | ||||||
|  | 	BOOTDEVP_4_NET_BASE		= 50, | ||||||
|  | 	BOOTDEVP_5_NET_FALLBACK		= 60, | ||||||
|  | 	BOOTDEVP_6_SYSTEM		= 70, | ||||||
|  | 
 | ||||||
|  | 	BOOTDEVP_COUNT, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct bootdev_uc_plat - uclass information about a bootdev | ||||||
|  |  * | ||||||
|  |  * This is attached to each device in the bootdev uclass and accessible via | ||||||
|  |  * dev_get_uclass_plat(dev) | ||||||
|  |  * | ||||||
|  |  * @bootflows: List of available bootflows for this bootdev | ||||||
|  |  * @piro: Priority of this bootdev | ||||||
|  |  */ | ||||||
|  | struct bootdev_uc_plat { | ||||||
|  | 	struct list_head bootflow_head; | ||||||
|  | 	enum bootdev_prio_t prio; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** struct bootdev_ops - Operations for the bootdev uclass */ | ||||||
|  | struct bootdev_ops { | ||||||
|  | 	/**
 | ||||||
|  | 	 * get_bootflow() - get a bootflow | ||||||
|  | 	 * | ||||||
|  | 	 * @dev:	Bootflow device to check | ||||||
|  | 	 * @iter:	Provides current dev, part, method to get. Should update | ||||||
|  | 	 *	max_part if there is a partition table. Should update state, | ||||||
|  | 	 *	subdir, fname, buf, size according to progress | ||||||
|  | 	 * @bflow:	Updated bootflow if found | ||||||
|  | 	 * Return: 0 if OK, -ESHUTDOWN if there are no more bootflows on this | ||||||
|  | 	 *	device, -ENOSYS if this device doesn't support bootflows, | ||||||
|  | 	 *	other -ve value on other error | ||||||
|  | 	 */ | ||||||
|  | 	int (*get_bootflow)(struct udevice *dev, struct bootflow_iter *iter, | ||||||
|  | 			    struct bootflow *bflow); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #define bootdev_get_ops(dev)  ((struct bootdev_ops *)(dev)->driver->ops) | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_get_bootflow() - get a bootflow | ||||||
|  |  * | ||||||
|  |  * @dev:	Bootflow device to check | ||||||
|  |  * @iter:	Provides current  part, method to get | ||||||
|  |  * @bflow:	Returns bootflow if found | ||||||
|  |  * Return: 0 if OK, -ESHUTDOWN if there are no more bootflows on this device, | ||||||
|  |  *	-ENOSYS if this device doesn't support bootflows, other -ve value on | ||||||
|  |  *	other error | ||||||
|  |  */ | ||||||
|  | int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, | ||||||
|  | 			 struct bootflow *bflow); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_bind() - Bind a new named bootdev device | ||||||
|  |  * | ||||||
|  |  * @parent:	Parent of the new device | ||||||
|  |  * @drv_name:	Driver name to use for the bootdev device | ||||||
|  |  * @name:	Name for the device (parent name is prepended) | ||||||
|  |  * @devp:	the new device (which has not been probed) | ||||||
|  |  */ | ||||||
|  | int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name, | ||||||
|  | 		 struct udevice **devp); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_find_in_blk() - Find a bootdev in a block device | ||||||
|  |  * | ||||||
|  |  * @dev: Bootflow device associated with this block device | ||||||
|  |  * @blk: Block device to search | ||||||
|  |  * @iter:	Provides current dev, part, method to get. Should update | ||||||
|  |  *	max_part if there is a partition table | ||||||
|  |  * @bflow: On entry, provides information about the partition and device to | ||||||
|  |  *	check. On exit, returns bootflow if found | ||||||
|  |  * Return: 0 if found, -ESHUTDOWN if no more bootflows, other -ve on error | ||||||
|  |  */ | ||||||
|  | int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, | ||||||
|  | 			struct bootflow_iter *iter, struct bootflow *bflow); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_list() - List all available bootdevs | ||||||
|  |  * | ||||||
|  |  * @probe: true to probe devices, false to leave them as is | ||||||
|  |  */ | ||||||
|  | void bootdev_list(bool probe); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_clear_bootflows() - Clear bootflows from a bootdev | ||||||
|  |  * | ||||||
|  |  * Each bootdev maintains a list of discovered bootflows. This provides a | ||||||
|  |  * way to clear it. These bootflows are removed from the global list too. | ||||||
|  |  * | ||||||
|  |  * @dev: bootdev device to update | ||||||
|  |  */ | ||||||
|  | void bootdev_clear_bootflows(struct udevice *dev); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_add_bootflow() - Add a bootflow to the bootdev's list | ||||||
|  |  * | ||||||
|  |  * All fields in @bflow must be set up. Note that @bflow->dev is used to add the | ||||||
|  |  * bootflow to that device. | ||||||
|  |  * | ||||||
|  |  * @dev: Bootdevice device to add to | ||||||
|  |  * @bflow: Bootflow to add. Note that fields within bflow must be allocated | ||||||
|  |  *	since this function takes over ownership of these. This functions makes | ||||||
|  |  *	a copy of @bflow itself (without allocating its fields again), so the | ||||||
|  |  *	caller must dispose of the memory used by the @bflow pointer itself | ||||||
|  |  * Return: 0 if OK, -ENOMEM if out of memory | ||||||
|  |  */ | ||||||
|  | int bootdev_add_bootflow(struct bootflow *bflow); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_first_bootflow() - Get the first bootflow from a bootdev | ||||||
|  |  * | ||||||
|  |  * Returns the first bootflow attached to a bootdev | ||||||
|  |  * | ||||||
|  |  * @dev: bootdev device | ||||||
|  |  * @bflowp: Returns a pointer to the bootflow | ||||||
|  |  * Return: 0 if found, -ENOENT if there are no bootflows | ||||||
|  |  */ | ||||||
|  | int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_next_bootflow() - Get the next bootflow from a bootdev | ||||||
|  |  * | ||||||
|  |  * Returns the next bootflow attached to a bootdev | ||||||
|  |  * | ||||||
|  |  * @bflowp: On entry, the last bootflow returned , e.g. from | ||||||
|  |  *	bootdev_first_bootflow() | ||||||
|  |  * Return: 0 if found, -ENOENT if there are no more bootflows | ||||||
|  |  */ | ||||||
|  | int bootdev_next_bootflow(struct bootflow **bflowp); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_find_by_label() - Look up a bootdev by label | ||||||
|  |  * | ||||||
|  |  * Each bootdev has a label which contains the media-uclass name and a number, | ||||||
|  |  * e.g. 'mmc2'. This looks up the label and returns the associated bootdev | ||||||
|  |  * | ||||||
|  |  * The lookup is performed based on the media device's sequence number. So for | ||||||
|  |  * 'mmc2' this looks for a device in UCLASS_MMC with a dev_seq() of 2. | ||||||
|  |  * | ||||||
|  |  * @label: Label to look up (e.g. "mmc1" or "mmc0") | ||||||
|  |  * @devp: Returns the bootdev device found, or NULL if none (note it does not | ||||||
|  |  *	return the media device, but its bootdev child) | ||||||
|  |  * Return: 0 if OK, -EINVAL if the uclass is not supported by this board, | ||||||
|  |  *	-ENOENT if there is no device with that number | ||||||
|  |  */ | ||||||
|  | int bootdev_find_by_label(const char *label, struct udevice **devp); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_find_by_any() - Find a bootdev by name, label or sequence | ||||||
|  |  * | ||||||
|  |  * @name: name (e.g. "mmc2.bootdev"), label ("mmc2"), or sequence ("2") to find | ||||||
|  |  * @devp: returns the device found, on success | ||||||
|  |  * Return: 0 if OK, -ve on error | ||||||
|  |  */ | ||||||
|  | int bootdev_find_by_any(const char *name, struct udevice **devp); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_setup_iter_order() - Set up the ordering of bootdevs to scan | ||||||
|  |  * | ||||||
|  |  * This sets up the ordering information in @iter, based on the priority of each | ||||||
|  |  * bootdev and the bootdev-order property in the bootstd node | ||||||
|  |  * | ||||||
|  |  * If a single device is requested, no ordering is needed | ||||||
|  |  * | ||||||
|  |  * @iter: Iterator to update with the order | ||||||
|  |  * @devp: On entry, *devp is NULL to scan all, otherwise this is the (single) | ||||||
|  |  *	device to scan. Returns the first device to use, which is the passed-in | ||||||
|  |  *	@devp if it was non-NULL | ||||||
|  |  * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve | ||||||
|  |  *	on other error | ||||||
|  |  */ | ||||||
|  | int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp); | ||||||
|  | 
 | ||||||
|  | #if CONFIG_IS_ENABLED(BOOTSTD) | ||||||
|  | /**
 | ||||||
|  |  * bootdev_setup_for_dev() - Bind a new bootdev device | ||||||
|  |  * | ||||||
|  |  * Creates a bootdev device as a child of @parent. This should be called from | ||||||
|  |  * the driver's bind() method or its uclass' post_bind() method. | ||||||
|  |  * | ||||||
|  |  * If a child bootdev already exists, this function does nothing | ||||||
|  |  * | ||||||
|  |  * @parent: Parent device (e.g. MMC or Ethernet) | ||||||
|  |  * @drv_name: Name of bootdev driver to bind | ||||||
|  |  * Return: 0 if OK, -ve on error | ||||||
|  |  */ | ||||||
|  | int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_setup_for_blk() - Bind a new bootdev device for a blk device | ||||||
|  |  * | ||||||
|  |  * Creates a bootdev device as a sibling of @blk. This should be called from | ||||||
|  |  * the driver's bind() method or its uclass' post_bind() method, at the same | ||||||
|  |  * time as the bould device is bound | ||||||
|  |  * | ||||||
|  |  * If a device of the same name already exists, this function does nothing | ||||||
|  |  * | ||||||
|  |  * @parent: Parent device (e.g. MMC or Ethernet) | ||||||
|  |  * @drv_name: Name of bootdev driver to bind | ||||||
|  |  * Return: 0 if OK, -ve on error | ||||||
|  |  */ | ||||||
|  | int bootdev_setup_sibling_blk(struct udevice *blk, const char *drv_name); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_get_sibling_blk() - Locate the block device for a bootdev | ||||||
|  |  * | ||||||
|  |  * @dev: bootdev to check | ||||||
|  |  * @blkp: returns associated block device | ||||||
|  |  * Return: 0 if OK, -EINVAL if @dev is not a bootdev device, other -ve on other | ||||||
|  |  *	error | ||||||
|  |  */ | ||||||
|  | int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * bootdev_unbind_dev() - Unbind a bootdev device | ||||||
|  |  * | ||||||
|  |  * Remove and unbind a bootdev device which is a child of @parent. This should | ||||||
|  |  * be called from the driver's unbind() method or its uclass' post_bind() | ||||||
|  |  * method. | ||||||
|  |  * | ||||||
|  |  * @parent: Parent device (e.g. MMC or Ethernet) | ||||||
|  |  * Return: 0 if OK, -ve on error | ||||||
|  |  */ | ||||||
|  | int bootdev_unbind_dev(struct udevice *parent); | ||||||
|  | #else | ||||||
|  | static inline int bootdev_setup_for_dev(struct udevice *parent, | ||||||
|  | 					const char *drv_name) | ||||||
|  | { | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline int bootdev_setup_sibling_blk(struct udevice *blk, | ||||||
|  | 					    const char *drv_name) | ||||||
|  | { | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline int bootdev_unbind_dev(struct udevice *parent) | ||||||
|  | { | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
|  | @ -38,6 +38,7 @@ enum uclass_id { | ||||||
| 	UCLASS_AXI,		/* AXI bus */ | 	UCLASS_AXI,		/* AXI bus */ | ||||||
| 	UCLASS_BLK,		/* Block device */ | 	UCLASS_BLK,		/* Block device */ | ||||||
| 	UCLASS_BOOTCOUNT,       /* Bootcount backing store */ | 	UCLASS_BOOTCOUNT,       /* Bootcount backing store */ | ||||||
|  | 	UCLASS_BOOTDEV,		/* Boot device for locating an OS to boot */ | ||||||
| 	UCLASS_BOOTSTD,		/* Standard boot driver */ | 	UCLASS_BOOTSTD,		/* Standard boot driver */ | ||||||
| 	UCLASS_BUTTON,		/* Button */ | 	UCLASS_BUTTON,		/* Button */ | ||||||
| 	UCLASS_CACHE,		/* Cache controller */ | 	UCLASS_CACHE,		/* Cache controller */ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue