Loading...
// SPDX-License-Identifier: GPL-2.0+
/*
 * Common functions for extlinux and PXE
 *
 * Copyright 2024 Collabora
 * Written by Martyn Welch <martyn.welch@collabora.com>
 */

#define LOG_CATEGORY UCLASS_BOOTSTD

#include <dm.h>
#include <extlinux.h>
#include <mapmem.h>
#include <linux/string.h>

enum extlinux_option_type {
	EO_FALLBACK,
	EO_INVALID
};

struct extlinux_option {
	char *name;
	enum extlinux_option_type option;
};

static const struct extlinux_option options[] = {
	{"fallback", EO_FALLBACK},
	{NULL, EO_INVALID}
};

static enum extlinux_option_type extlinux_get_option(const char *option)
{
	int i = 0;

	while (options[i].name) {
		if (!strcmp(options[i].name, option))
			return options[i].option;

		i++;
	}

	return EO_INVALID;
};

int extlinux_set_property(struct udevice *dev, const char *property,
			  const char *value)
{
	struct extlinux_plat *plat;
	static enum extlinux_option_type option;

	plat = dev_get_plat(dev);

	option = extlinux_get_option(property);
	if (option == EO_INVALID) {
		printf("Invalid option\n");
		return -EINVAL;
	}

	switch (option) {
	case EO_FALLBACK:
		if (!strcmp(value, "1")) {
			plat->use_fallback = true;
		} else if (!strcmp(value, "0")) {
			plat->use_fallback = false;
		} else {
			printf("Unexpected value '%s'\n", value);
			return -EINVAL;
		}
		break;
	default:
		printf("Unrecognised property '%s'\n", property);
		return -EINVAL;
	}

	return 0;
}

struct pxe_context *extlinux_get_ctx(struct extlinux_priv *priv,
				     struct bootflow *bflow)
{
	struct pxe_context *ctx;

	/* Return existing context if one was already allocated */
	if (bflow->bootmeth_id >= 0) {
		ctx = alist_getw(&priv->ctxs, bflow->bootmeth_id,
				 struct pxe_context);
		if (ctx)
			return ctx;
	}

	/* Allocate a new one */
	ctx = alist_add_placeholder(&priv->ctxs);
	if (!ctx)
		return NULL;
	bflow->bootmeth_id = priv->ctxs.count - 1;

	return ctx;
}

static int extlinux_setup(struct udevice *dev, struct bootflow *bflow,
			  pxe_getfile_func getfile, bool allow_abs_path,
			  const char *bootfile, struct pxe_context *ctx)
{
	struct extlinux_plat *plat = dev_get_plat(dev);
	int ret;

	plat->info.dev = dev;
	plat->info.bflow = bflow;

	ret = pxe_setup_ctx(ctx, getfile, &plat->info, allow_abs_path,
			    bootfile, false, plat->use_fallback, bflow);
	if (ret)
		return log_msg_ret("ctx", ret);
	log_debug("bootfl flags %x\n", bflow->flags);
	if (bflow->flags & BOOTFLOWF_FAKE_GO)
		ctx->fake_go = true;

	return 0;
}

int extlinux_boot(struct udevice *dev, struct bootflow *bflow,
		  struct pxe_context *ctx, pxe_getfile_func getfile,
		  bool allow_abs_path, const char *bootfile, bool restart)
{
	ulong addr;
	int ret;

	/* if we have already selected a label, just boot it */
	if (ctx->label) {
		ctx->fake_go = bflow->flags & BOOTFLOWF_FAKE_GO;
		ret = pxe_boot(ctx);
		if (ret)
			return log_msg_ret("pxb", -EFAULT);
		return 0;
	}

	/*
	 * If the config was cached during scanning, update the callback fields
	 * for file loading. Otherwise set up the context from scratch.
	 */
	if (ctx->cfg) {
		struct extlinux_plat *plat = dev_get_plat(dev);

		plat->info.dev = dev;
		plat->info.bflow = bflow;
		ctx->getfile = getfile;
		ctx->userdata = &plat->info;
		ctx->bflow = bflow;
	} else {
		ret = extlinux_setup(dev, bflow, getfile, allow_abs_path,
				     bootfile, ctx);
		if (ret)
			return log_msg_ret("elb", ret);
	}
	ctx->restart = restart;
	ctx->fake_go = bflow->flags & BOOTFLOWF_FAKE_GO;
	addr = map_to_sysmem(bflow->buf);

	ret = pxe_boot_entry(ctx, addr, bflow->entry);
	if (ret)
		return log_msg_ret("ent", -EFAULT);

	return 0;
}

int extlinux_read_all(struct udevice *dev, struct bootflow *bflow,
		      struct pxe_context *ctx, pxe_getfile_func getfile,
		      bool allow_abs_path, const char *bootfile)
{
	ulong addr;
	int ret;

	ret = extlinux_setup(dev, bflow, getfile, allow_abs_path, bootfile,
			     ctx);
	if (ret)
		return log_msg_ret("era", ret);
	addr = map_to_sysmem(bflow->buf);
	ctx->pxe_file_size = bflow->size;
	ret = pxe_probe(ctx, addr, false);
	if (ret)
		return log_msg_ret("elb", -EFAULT);

	return 0;
}