// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: © 2013 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix

/* of_property.c - device tree property handling support */

#include <common.h>
#include <libfile.h>
#include <environment.h>
#include <fdt.h>
#include <of.h>
#include <command.h>
#include <fs.h>
#include <malloc.h>
#include <complete.h>
#include <linux/ctype.h>
#include <asm/byteorder.h>
#include <errno.h>
#include <getopt.h>
#include <init.h>
#include <xfuncs.h>

static int of_parse_prop_cells(struct device_node *root, char * const *newval, int count, char *data, int *len)
{
	char *cp;
	unsigned long tmp;	/* holds converted values */
	int stridx = 0;
	char *newp = newval[0];

	newp++;

	while (1) {
		if (*newp == '>')
			return 0;

		if (*newp == '\0') {
			newp = newval[++stridx];

			if (stridx == count) {
				printf("missing '>'\n");
				return -EINVAL;
			}

			continue;
		}

		cp = newp;

		if (isdigit(*cp)) {
			tmp = simple_strtoul(cp, &newp, 0);
		} else {
			struct device_node *n;
			char *str;
			int len = 0;

			str = cp;
			while (*str && *str != '>' && *str != ' ') {
				str++;
				len++;
			}

			str = xzalloc(len + 1);
			strncpy(str, cp, len);

			n = of_find_node_by_path_or_alias(root, str);
			if (!n)
				printf("Cannot find node '%s'\n", str);

			free(str);

			if (!n)
				return -EINVAL;

			tmp = of_node_create_phandle(n);
			newp += len;
		}

		*(__be32 *)data = __cpu_to_be32(tmp);
		data  += 4;
		*len += 4;

		/* If the ptr didn't advance, something went wrong */
		if ((newp - cp) <= 0) {
			printf("cannot convert \"%s\"\n", cp);
			return -EINVAL;
		}

		while (*newp == ' ')
			newp++;
	}
}

static int of_parse_prop_stream(char * const *newval, int count, char *data, int *len)
{
	char *cp;
	unsigned long tmp;	/* holds converted values */
	int stridx = 0;
	char *newp = newval[0];

	newp++;

	while (1) {
		if (*newp == ']')
			return 0;

		while (*newp == ' ')
			newp++;

		if (*newp == '\0') {
			newp = newval[++stridx];

			if (stridx == count) {
				printf("missing ']'\n");
				return -EINVAL;
			}

			continue;
		}

		cp = newp;
		tmp = simple_strtoul(newp, &newp, 16);
		*data++ = tmp & 0xff;
		*len    = *len + 1;

		/* If the ptr didn't advance, something went wrong */
		if ((newp - cp) <= 0) {
			printf("cannot convert \"%s\"\n", cp);
			return -EINVAL;
		}
	}
}

static int of_parse_prop_string(char * const *newval, int count, char *data, int *len)
{
	int stridx = 0;
	char *newp = newval[0];

	/*
	 * Assume it is one or more strings.  Copy it into our
	 * data area for convenience (including the
	 * terminating '\0's).
	 */
	while (stridx < count) {
		size_t length = strlen(newp) + 1;

		strcpy(data, newp);
		data += length;
		*len += length;
		newp = newval[++stridx];
	}

	return 0;
}

/*
 * Parse the user's input, partially heuristic.  Valid formats:
 * <0x00112233 4 05>	- an array of cells.  Numbers follow standard
 *			C conventions.
 * [00 11 22 .. nn] - byte stream
 * "string"	- If the the value doesn't start with "<" or "[", it is
 *			treated as a string.  Note that the quotes are
 *			stripped by the parser before we get the string.
 * newval: An array of strings containing the new property as specified
 *	on the command line
 * count: The number of strings in the array
 * data: A bytestream to be placed in the property
 * len: The length of the resulting bytestream
 */
static int of_parse_prop(struct device_node *root, char * const *newval, int count, char *data, int *len)
{
	char *newp;		/* temporary newval char pointer */

	*len = 0;

	if (!count)
		return 0;

	newp = newval[0];

	switch (*newp) {
	case '<':
		return of_parse_prop_cells(root, newval, count, data, len);
	case '[':
		return of_parse_prop_stream(newval, count, data, len);
	default:
		return of_parse_prop_string(newval, count, data, len);
	}
}

struct of_fixup_property_data {
	char *path;
	char *propname;
	void *data;
	int len;
};

static int do_of_property_delete_now(struct device_node *root, const char *path,
		const char *propname);
static int do_of_property_set_now(struct device_node *root, const char *path,
		const char *propname, void *data, int len);

static int of_fixup_property_set(struct device_node *root, void *context)
{
	struct of_fixup_property_data *fixup = context;

	return do_of_property_set_now(root, fixup->path, fixup->propname,
			fixup->data, fixup->len);
}

static int of_fixup_property_delete(struct device_node *root, void *context)
{
	struct of_fixup_property_data *fixup = context;

	return do_of_property_delete_now(root, fixup->path, fixup->propname);
}

static int do_of_property_set_fixup(const char *path, const char *propname,
		void *data, int len)
{
	struct of_fixup_property_data *fixup;

	fixup = xzalloc(sizeof(*fixup));
	fixup->path = xstrdup(path);
	fixup->propname = xstrdup(propname);
	fixup->data = data;
	fixup->len = len;

	return of_register_fixup(of_fixup_property_set, (void *)fixup);
}

static int do_of_property_delete_fixup(const char *path, const char *propname)
{
	struct of_fixup_property_data *fixup;

	fixup = xzalloc(sizeof(*fixup));
	fixup->path = xstrdup(path);
	fixup->propname = xstrdup(propname);
	fixup->data = NULL;
	fixup->len = 0;

	return of_register_fixup(of_fixup_property_delete, (void *)fixup);
}

static int do_of_property_set_now(struct device_node *root, const char *path,
		const char *propname, void *data, int len)
{
	struct device_node *node = of_find_node_by_path_or_alias(root, path);
	struct property *pp;

	if (!node) {
		printf("Cannot find nodepath %s\n", path);
		return -ENOENT;
	}

	pp = of_find_property(node, propname, NULL);

	if (pp) {
		free(pp->value);
		pp->value_const = NULL;

		if (len)
			pp->value = xmemdup(data, len);
		else
			pp->value = NULL;

		pp->length = len;
	} else {
		pp = of_new_property(node, propname, data, len);
		if (!pp) {
			printf("Cannot create property %s\n", propname);
			return -ENOMEM;
		}
	}

	return 0;
}

static int do_of_property_delete_now(struct device_node *root, const char *path,
		const char *propname)
{
	struct device_node *node = of_find_node_by_path_or_alias(root, path);
	struct property *pp;

	if (!node) {
		printf("Cannot find nodepath %s\n", path);
		return -ENOENT;
	}

	pp = of_find_property(node, propname, NULL);
	if (!pp) {
		printf("Cannot find property %s\n", propname);
		return -ENOENT;
	}

	of_delete_property(pp);

	return 0;
}

static int do_of_property(int argc, char *argv[])
{
	int opt;
	int delete = 0;
	int set = 0;
	int fixup = 0;
	char *path, *propname;
	char *dtbfile = NULL;
	int ret = 0;
	size_t size;
	struct fdt_header *fdt = NULL;
	struct device_node *root = NULL;

	while ((opt = getopt(argc, argv, "dsfe:")) > 0) {
		switch (opt) {
		case 'd':
			delete = 1;
			break;
		case 's':
			set = 1;
			break;
		case 'f':
			fixup = 1;
			break;
		case 'e':
			dtbfile = optarg;
			break;
		default:
			return COMMAND_ERROR_USAGE;
		}
	}

	if (argc - optind < 2)
		return COMMAND_ERROR_USAGE;

	path = argv[optind];
	propname = argv[optind + 1];

	debug("path: %s propname: %s\n", path, propname);

	if ( !(set || delete))
		return COMMAND_ERROR_USAGE;

	if (dtbfile) {
		fdt = read_file(dtbfile, &size);
		if (!fdt) {
			printf("unable to read %s: %m\n", dtbfile);
			return -errno;
		}

		root = of_unflatten_dtb(fdt, size);

		free(fdt);
	}

	if (set) {
		int len;
		void *data;

		/*
		 * standard console buffer size. The result won't be bigger than the
		 * string input.
		 */
		data = malloc(1024);
		if (!data)
			return -ENOMEM;

		ret = of_parse_prop(root, &argv[optind + 2], argc - optind - 2, data, &len);
		if (ret) {
			free(data);
			goto out;
		}

		if (fixup) {
			ret = do_of_property_set_fixup(path, propname, data, len);
			if (ret)
				free(data);
		} else {
			ret = do_of_property_set_now(root, path, propname, data, len);
			free(data);
		}

		if (ret)
			goto out;

	} else if (delete) {
		if (fixup)
			ret = do_of_property_delete_fixup(path, propname);
		else
			ret = do_of_property_delete_now(root, path, propname);

		if (ret)
			goto out;
	}

	if (root && !fixup) {
		fdt = of_flatten_dtb(root);

		ret = write_file(dtbfile, fdt, fdt32_to_cpu(fdt->totalsize));

		free(fdt);
	}

out:

	if (root)
		of_delete_node(root);

	return ret;
}

BAREBOX_CMD_HELP_START(of_property)
BAREBOX_CMD_HELP_TEXT("Options:")
BAREBOX_CMD_HELP_OPT ("-s",  "set property to value")
BAREBOX_CMD_HELP_OPT ("-d",  "delete property")
BAREBOX_CMD_HELP_OPT ("-f",  "set/delete as a fixup (defer the action until booting)")
BAREBOX_CMD_HELP_OPT ("-e dtb",  "set/delete/fixup from external dtb")
BAREBOX_CMD_HELP_TEXT("")
BAREBOX_CMD_HELP_TEXT("Valid formats for values:")
BAREBOX_CMD_HELP_TEXT("<0x00112233 4 05> - an array of cells. cells not beginning with a digit are")
BAREBOX_CMD_HELP_TEXT("                    interpreted as node paths and converted to phandles")
BAREBOX_CMD_HELP_TEXT("[00 11 22 .. nn]  - byte stream")
BAREBOX_CMD_HELP_TEXT("If the value does not start with '<' or '[' it is interpreted as string")
BAREBOX_CMD_HELP_END

BAREBOX_CMD_START(of_property)
	.cmd		= do_of_property,
	BAREBOX_CMD_DESC("handle device tree properties")
	BAREBOX_CMD_OPTS("[-sd] [-e] [-f] NODE [PROPERTY] [VALUES]")
	BAREBOX_CMD_GROUP(CMD_GRP_MISC)
	BAREBOX_CMD_COMPLETE(devicetree_file_complete)
	BAREBOX_CMD_HELP(cmd_of_property_help)
BAREBOX_CMD_END
