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

#include <abuf.h>
#include <cedit.h>
#include <command.h>
#include <dm.h>
#include <expo.h>
#include <fs.h>
#include <malloc.h>
#include <mapmem.h>
#include <dm/ofnode.h>
#include <linux/sizes.h>

struct expo *cur_exp;

static int check_cur_expo(void)
{
	if (!cur_exp) {
		printf("No expo loaded\n");
		return -ENOENT;
	}

	return 0;
}

static int do_cedit_load(struct cmd_tbl *cmdtp, int flag, int argc,
			 char *const argv[])
{
	const char *fname;
	struct expo *exp;
	oftree tree;
	ulong size;
	void *buf;
	int ret;

	if (argc < 4)
		return CMD_RET_USAGE;
	fname = argv[3];

	ret = fs_load_alloc(argv[1], argv[2], argv[3], SZ_1M, 0, &buf, &size);
	if (ret) {
		printf("File not found\n");
		return CMD_RET_FAILURE;
	}

	tree = oftree_from_fdt(buf);
	if (!oftree_valid(tree)) {
		printf("Cannot create oftree\n");
		return CMD_RET_FAILURE;
	}

	ret = expo_build(oftree_root(tree), &exp);
	oftree_dispose(tree);
	if (ret) {
		printf("Failed to build expo: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	cur_exp = exp;

	return 0;
}

#ifdef CONFIG_COREBOOT_SYSINFO
static int do_cedit_cb_load(struct cmd_tbl *cmdtp, int flag, int argc,
			    char *const argv[])
{
	struct expo *exp;
	int ret;

	if (argc > 1)
		return CMD_RET_USAGE;

	ret = cb_expo_build(&exp);
	if (ret) {
		printf("Failed to build expo: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	cur_exp = exp;

	return 0;
}
#endif /* CONFIG_COREBOOT_SYSINFO */

static int do_cedit_write_fdt(struct cmd_tbl *cmdtp, int flag, int argc,
			      char *const argv[])
{
	const char *fname;
	struct abuf buf;
	loff_t bytes;
	int ret;

	if (argc < 4)
		return CMD_RET_USAGE;
	fname = argv[3];

	if (check_cur_expo())
		return CMD_RET_FAILURE;

	ret = cedit_write_settings(cur_exp, &buf);
	if (ret) {
		printf("Failed to write settings: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	if (fs_set_blk_dev(argv[1], argv[2], FS_TYPE_ANY))
		return CMD_RET_FAILURE;

	ret = fs_write(fname, map_to_sysmem(abuf_data(&buf)), 0,
		       abuf_size(&buf), &bytes);
	if (ret)
		return CMD_RET_FAILURE;

	return 0;
}

static int do_cedit_read_fdt(struct cmd_tbl *cmdtp, int flag, int argc,
			     char *const argv[])
{
	const char *fname;
	void *buf;
	oftree tree;
	ulong size;
	int ret;

	if (argc < 4)
		return CMD_RET_USAGE;
	fname = argv[3];

	ret = fs_load_alloc(argv[1], argv[2], argv[3], SZ_1M, 0, &buf, &size);
	if (ret) {
		printf("File not found\n");
		return CMD_RET_FAILURE;
	}

	tree = oftree_from_fdt(buf);
	if (!oftree_valid(tree)) {
		free(buf);
		printf("Cannot create oftree\n");
		return CMD_RET_FAILURE;
	}

	ret = cedit_read_settings(cur_exp, tree);
	oftree_dispose(tree);
	free(buf);
	if (ret) {
		printf("Failed to read settings: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	return 0;
}

static int do_cedit_write_env(struct cmd_tbl *cmdtp, int flag, int argc,
			      char *const argv[])
{
	bool verbose;
	int ret;

	if (check_cur_expo())
		return CMD_RET_FAILURE;

	verbose = argc > 1 && !strcmp(argv[1], "-v");

	ret = cedit_write_settings_env(cur_exp, verbose);
	if (ret) {
		printf("Failed to write settings to environment: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	return 0;
}

static int do_cedit_read_env(struct cmd_tbl *cmdtp, int flag, int argc,
			     char *const argv[])
{
	bool verbose;
	int ret;

	if (check_cur_expo())
		return CMD_RET_FAILURE;

	verbose = argc > 1 && !strcmp(argv[1], "-v");

	ret = cedit_read_settings_env(cur_exp, verbose);
	if (ret) {
		printf("Failed to read settings from environment: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	return 0;
}

static int do_cedit_write_cmos(struct cmd_tbl *cmdtp, int flag, int argc,
			       char *const argv[])
{
	struct udevice *dev;
	bool verbose = false;
	int ret;

	if (check_cur_expo())
		return CMD_RET_FAILURE;

	if (argc > 1 && !strcmp(argv[1], "-v")) {
		verbose = true;
		argc--;
		argv++;
	}

	if (argc > 1)
		ret = uclass_get_device_by_name(UCLASS_RTC, argv[1], &dev);
	else
		ret = uclass_first_device_err(UCLASS_RTC, &dev);
	if (ret) {
		printf("Failed to get RTC device: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	if (cedit_write_settings_cmos(cur_exp, dev, verbose)) {
		printf("Failed to write settings to CMOS\n");
		return CMD_RET_FAILURE;
	}

	return 0;
}

static int do_cedit_read_cmos(struct cmd_tbl *cmdtp, int flag, int argc,
			      char *const argv[])
{
	struct udevice *dev;
	bool verbose = false;
	int ret;

	if (check_cur_expo())
		return CMD_RET_FAILURE;

	if (argc > 1 && !strcmp(argv[1], "-v")) {
		verbose = true;
		argc--;
		argv++;
	}

	if (argc > 1)
		ret = uclass_get_device_by_name(UCLASS_RTC, argv[1], &dev);
	else
		ret = uclass_first_device_err(UCLASS_RTC, &dev);
	if (ret) {
		printf("Failed to get RTC device: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	ret = cedit_read_settings_cmos(cur_exp, dev, verbose);
	if (ret) {
		printf("Failed to read settings from CMOS: %dE\n", ret);
		return CMD_RET_FAILURE;
	}

	return 0;
}

static int do_cedit_run(struct cmd_tbl *cmdtp, int flag, int argc,
			char *const argv[])
{
	ofnode node;
	int ret;

	if (check_cur_expo())
		return CMD_RET_FAILURE;

	node = ofnode_path("/bootstd/cedit-theme");
	if (ofnode_valid(node)) {
		ret = expo_apply_theme(cur_exp, node);
		if (ret)
			return CMD_RET_FAILURE;
	} else {
		log_warning("No theme found\n");
	}
	ret = cedit_run(cur_exp);
	if (ret) {
		log_err("Failed (err=%dE)\n", ret);
		return CMD_RET_FAILURE;
	}

	return 0;
}

U_BOOT_LONGHELP(cedit,
	"load <interface> <dev[:part]> <filename>   - load config editor\n"
#ifdef CONFIG_COREBOOT_SYSINFO
	"cb_load                                          - load coreboot CMOS editor\n"
#endif
	"cedit read_fdt <i/f> <dev[:part]> <filename>     - read settings\n"
	"cedit write_fdt <i/f> <dev[:part]> <filename>    - write settings\n"
	"cedit read_env [-v]                              - read settings from env vars\n"
	"cedit write_env [-v]                             - write settings to env vars\n"
	"cedit read_cmos [-v] [dev]                       - read settings from CMOS RAM\n"
	"cedit write_cmos [-v] [dev]                      - write settings to CMOS RAM\n"
	"cedit run                                        - run config editor");

U_BOOT_CMD_WITH_SUBCMDS(cedit, "Configuration editor", cedit_help_text,
	U_BOOT_SUBCMD_MKENT(load, 5, 1, do_cedit_load),
#ifdef CONFIG_COREBOOT_SYSINFO
	U_BOOT_SUBCMD_MKENT(cb_load, 5, 1, do_cedit_cb_load),
#endif
	U_BOOT_SUBCMD_MKENT(read_fdt, 5, 1, do_cedit_read_fdt),
	U_BOOT_SUBCMD_MKENT(write_fdt, 5, 1, do_cedit_write_fdt),
	U_BOOT_SUBCMD_MKENT(read_env, 2, 1, do_cedit_read_env),
	U_BOOT_SUBCMD_MKENT(write_env, 2, 1, do_cedit_write_env),
	U_BOOT_SUBCMD_MKENT(read_cmos, 2, 1, do_cedit_read_cmos),
	U_BOOT_SUBCMD_MKENT(write_cmos, 2, 1, do_cedit_write_cmos),
	U_BOOT_SUBCMD_MKENT(run, 1, 1, do_cedit_run),
);
