/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * Copyright 2021 Google LLC
 * Written by Simon Glass <sjg@chromium.org>
 */

#ifndef __bootdev_h
#define __bootdev_h

#include <dm/uclass-id.h>
#include <linux/list.h>

struct bootflow;
struct bootflow_iter;
struct bootstd_priv;
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
 *
 * BOOTDEVP_0_NONE: Invalid value, do not use
 * @BOOTDEVP_6_PRE_SCAN: Scan bootdevs with this priority always, before
 * starting any bootflow scan
 * @BOOTDEVP_2_INTERNAL_FAST: Internal devices which don't need scanning and
 * generally very quick to access, e.g. less than 100ms
 * @BOOTDEVP_3_INTERNAL_SLOW: Internal devices which don't need scanning but
 * take a significant fraction of a second to access
 * @BOOTDEVP_4_SCAN_FAST: External devices which need scanning or bus
 * enumeration to find, but this enumeration happens quickly, typically under
 * 100ms
 * @BOOTDEVP_5_SCAN_SLOW: External devices which need scanning or bus
 * enumeration to find. The enumeration takes significant fraction of a second
 * to complete
 * @BOOTDEVP_6_NET_BASE: Basic network devices which are quickly and easily
 * available. Typically used for an internal Ethernet device
 * @BOOTDEVP_7_NET_FALLBACK: Secondary network devices which require extra time
 * to start up, or are less desirable. Typically used for secondary Ethernet
 * devices. Note that USB ethernet devices are found during USB enumeration,
 * so do not use this priority
 */
enum bootdev_prio_t {
	BOOTDEVP_0_NONE,
	BOOTDEVP_1_PRE_SCAN,
	BOOTDEVP_2_INTERNAL_FAST,
	BOOTDEVP_3_INTERNAL_SLOW,
	BOOTDEVP_4_SCAN_FAST,
	BOOTDEVP_5_SCAN_SLOW,
	BOOTDEVP_6_NET_BASE,
	BOOTDEVP_7_NET_FALLBACK,

	BOOTDEVP_COUNT,
};

struct bootdev_hunter;

/**
 * bootdev_hunter_func - function to probe for bootdevs of a given type
 *
 * This should hunt around for bootdevs of the given type, binding them as it
 * finds them. This may involve bus enumeration, etc.
 *
 * @info: Info structure describing this hunter
 * @show: true to show information from the hunter
 * Returns: 0 if OK, -ENOENT on device not found, otherwise -ve on error
 */
typedef int (*bootdev_hunter_func)(struct bootdev_hunter *info, bool show);

/**
 * struct bootdev_hunter - information about how to hunt for bootdevs
 *
 * @prio: Scanning priority of this hunter
 * @uclass: Uclass ID for the media associated with this bootdev
 * @drv: bootdev driver for the things found by this hunter
 * @hunt: Function to call to hunt for bootdevs of this type (NULL if none)
 *
 * Some bootdevs are not visible until other devices are enumerated. For
 * example, USB bootdevs only appear when the USB bus is enumerated.
 *
 * On the other hand, we don't always want to enumerate all the buses just to
 * find the first valid bootdev. Ideally we want to work through them in
 * priority order, so that the fastest bootdevs are discovered first.
 *
 * This struct holds information about the bootdev so we can determine the probe
 * order and how to hunt for bootdevs of this type
 */
struct bootdev_hunter {
	enum bootdev_prio_t prio;
	enum uclass_id uclass;
	struct driver *drv;
	bootdev_hunter_func hunt;
};

/* declare a new bootdev hunter */
#define BOOTDEV_HUNTER(__name)						\
	ll_entry_declare(struct bootdev_hunter, __name, bootdev_hunter)

/* access a bootdev hunter by name */
#define BOOTDEV_HUNTER_GET(__name)						\
	ll_entry_get(struct bootdev_hunter, __name, bootdev_hunter)

/**
 * 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)
 *
 * @piro: Priority of this bootdev
 */
struct bootdev_uc_plat {
	enum bootdev_prio_t prio;
};

/** struct bootdev_ops - Operations for the bootdev uclass */
struct bootdev_ops {
	/**
	 * get_bootflow() - get a bootflow (optional)
	 *
	 * If this is NULL then the default implementaton is used, which is
	 * default_get_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_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)
 * @method_flagsp: If non-NULL, returns any flags implied by the label
 * (enum bootflow_meth_flags_t), 0 if none. Unset if function fails
 * 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,
			  int *method_flagsp);

/**
 * 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
 * @method_flagsp: If non-NULL, returns any flags implied by the label
 * (enum bootflow_meth_flags_t), 0 if none. Unset if function fails
 * Return: 0 if OK, -EPFNOSUPPORT if the uclass is not supported by this board,
 * -ENOENT if there is no device with that number
 */
int bootdev_find_by_any(const char *name, struct udevice **devp,
			int *method_flagsp);

/**
 * bootdev_setup_iter() - Set up iteration through bootdevs
 *
 * This sets up the an interation, based on the provided device or label. If
 * neither is provided, the iteration is based on the priority of each bootdev,
 * the * bootdev-order property in the bootstd node (or the boot_targets env
 * var).
 *
 * @iter: Iterator to update with the order
 * @label: label to scan, or NULL to scan all
 * @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
 * @method_flagsp: If non-NULL, returns any flags implied by the label
 * (enum bootflow_meth_flags_t), 0 if none
 * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve
 *	on other error
 */
int bootdev_setup_iter(struct bootflow_iter *iter, const char *label,
		       struct udevice **devp, int *method_flagsp);

/**
 * bootdev_list_hunters() - List the available bootdev hunters
 *
 * These provide a way to find new bootdevs by enumerating buses, etc. This
 * function lists the available hunters
 *
 * @std: Pointer to bootstd private info
 */
void bootdev_list_hunters(struct bootstd_priv *std);

/**
 * bootdev_hunt() - Hunt for bootdevs matching a particular spec
 *
 * This runs the selected hunter (or all if @spec is NULL) to try to find new
 * bootdevs.
 *
 * @spec: Spec to match, e.g. "mmc0", or NULL for any. If provided, this must
 * match a uclass name so that the hunter can be determined. Any trailing number
 * is ignored
 * @show: true to show each hunter before using it
 * Returns: 0 if OK, -ve on error
 */
int bootdev_hunt(const char *spec, bool show);

/**
 * bootdev_hunt_prio() - Hunt for bootdevs of a particular priority
 *
 * This runs all hunters which can find bootdevs of the given priority.
 *
 * @prio: Priority to use
 * @show: true to show each hunter as it is used
 * Returns: 0 if OK, -ve on error
 */
int bootdev_hunt_prio(enum bootdev_prio_t prio, bool show);

/**
 * bootdev_unhunt() - Mark a device as needing to be hunted again
 *
 * @id: uclass ID to update
 * Return: 0 if done, -EALREADY if already in this state, -ENOENT if no hunter
 * found for that uclass
 */
int bootdev_unhunt(enum uclass_id id);

/**
 * bootdev_hunt_and_find_by_label() - Hunt for bootdevs by label
 *
 * Runs the hunter for the label, then tries to find the bootdev, possible
 * created by the hunter
 *
 * @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)
 * @method_flagsp: If non-NULL, returns any flags implied by the label
 * (enum bootflow_meth_flags_t), 0 if none. Unset if function fails
 * 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_hunt_and_find_by_label(const char *label, struct udevice **devp,
				   int *method_flagsp);

/**
 * bootdev_next_label() - Move to the next bootdev in the label sequence
 *
 * Looks through the remaining labels until it finds one that matches a bootdev.
 * Bootdev scanners are used as needed. For example a label "mmc1" results in
 * running the "mmc" bootdrv.
 *
 * @iter: Iteration info, containing iter->cur_label
 * @devp: New bootdev found, if any was found
 * @method_flagsp: If non-NULL, returns any flags implied by the label
 * (enum bootflow_meth_flags_t), 0 if none
 * Returns 0 if OK, -ENODEV if no bootdev was found
 */
int bootdev_next_label(struct bootflow_iter *iter, struct udevice **devp,
		       int *method_flagsp);

/**
 * bootdev_next_prio() - Find the next bootdev in priority order
 *
 * This moves @devp to the next bootdev with the current priority. If there is
 * none, then it moves to the next priority and scans for new bootdevs there.
 *
 * @iter: Iteration info, containing iter->cur_prio
 * @devp: On entry this is the previous bootdev that was considered. On exit
 *	this is the new bootdev, if any was found
 * Returns 0 on success (*devp is updated), -ENODEV if there are no more
 * bootdevs at any priority
 */
int bootdev_next_prio(struct bootflow_iter *iter, struct udevice **devp);

#if CONFIG_IS_ENABLED(BOOTSTD)
/**
 * bootdev_setup_for_dev() - Bind a new bootdev device (deprecated)
 *
 * Please use bootdev_setup_for_sibling_blk() instead since it supports multiple
 * (child) block devices for each media 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);

#if CONFIG_IS_ENABLED(BOOTSTD)
/**
 * bootdev_setup_for_sibling_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_for_sibling_blk(struct udevice *blk, const char *drv_name);
#else
static int bootdev_setup_for_sibling_blk(struct udevice *blk,
					 const char *drv_name)
{
	return 0;
}
#endif

/**
 * 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_get_from_blk() - Get the bootdev given a block device
 *
 * @blk: Block device to check
 * @bootdebp: Returns the bootdev found, if any
 * Return 0 if OK, -ve on error
 */
int bootdev_get_from_blk(struct udevice *blk, struct udevice **bootdevp);

/**
 * 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_for_sibling_blk(struct udevice *blk,
						const char *drv_name)
{
	return 0;
}

static inline int bootdev_unbind_dev(struct udevice *parent)
{
	return 0;
}
#endif

#endif
