// SPDX-License-Identifier: GPL-2.0
/*
 * PowerNV code for secure variables
 *
 * Copyright (C) 2019 IBM Corporation
 * Author: Claudio Carvalho
 *         Nayna Jain
 *
 * APIs to access secure variables managed by OPAL.
 */

#define pr_fmt(fmt) "secvar: "fmt

#include <linux/types.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <asm/opal.h>
#include <asm/secvar.h>
#include <asm/secure_boot.h>

static int opal_status_to_err(int rc)
{
	int err;

	switch (rc) {
	case OPAL_SUCCESS:
		err = 0;
		break;
	case OPAL_UNSUPPORTED:
		err = -ENXIO;
		break;
	case OPAL_PARAMETER:
		err = -EINVAL;
		break;
	case OPAL_RESOURCE:
		err = -ENOSPC;
		break;
	case OPAL_HARDWARE:
		err = -EIO;
		break;
	case OPAL_NO_MEM:
		err = -ENOMEM;
		break;
	case OPAL_EMPTY:
		err = -ENOENT;
		break;
	case OPAL_PARTIAL:
		err = -EFBIG;
		break;
	default:
		err = -EINVAL;
	}

	return err;
}

static int opal_get_variable(const char *key, u64 ksize, u8 *data, u64 *dsize)
{
	int rc;

	if (!key || !dsize)
		return -EINVAL;

	*dsize = cpu_to_be64(*dsize);

	rc = opal_secvar_get(key, ksize, data, dsize);

	*dsize = be64_to_cpu(*dsize);

	return opal_status_to_err(rc);
}

static int opal_get_next_variable(const char *key, u64 *keylen, u64 keybufsize)
{
	int rc;

	if (!key || !keylen)
		return -EINVAL;

	*keylen = cpu_to_be64(*keylen);

	rc = opal_secvar_get_next(key, keylen, keybufsize);

	*keylen = be64_to_cpu(*keylen);

	return opal_status_to_err(rc);
}

static int opal_set_variable(const char *key, u64 ksize, u8 *data, u64 dsize)
{
	int rc;

	if (!key || !data)
		return -EINVAL;

	rc = opal_secvar_enqueue_update(key, ksize, data, dsize);

	return opal_status_to_err(rc);
}

static ssize_t opal_secvar_format(char *buf, size_t bufsize)
{
	ssize_t rc = 0;
	struct device_node *node;
	const char *format;

	node = of_find_compatible_node(NULL, NULL, "ibm,secvar-backend");
	if (!of_device_is_available(node)) {
		rc = -ENODEV;
		goto out;
	}

	rc = of_property_read_string(node, "format", &format);
	if (rc)
		goto out;

	rc = snprintf(buf, bufsize, "%s", format);

out:
	of_node_put(node);

	return rc;
}

static int opal_secvar_max_size(u64 *max_size)
{
	int rc;
	struct device_node *node;

	node = of_find_compatible_node(NULL, NULL, "ibm,secvar-backend");
	if (!node)
		return -ENODEV;

	if (!of_device_is_available(node)) {
		rc = -ENODEV;
		goto out;
	}

	rc = of_property_read_u64(node, "max-var-size", max_size);

out:
	of_node_put(node);
	return rc;
}

static const struct secvar_operations opal_secvar_ops = {
	.get = opal_get_variable,
	.get_next = opal_get_next_variable,
	.set = opal_set_variable,
	.format = opal_secvar_format,
	.max_size = opal_secvar_max_size,
};

static int opal_secvar_probe(struct platform_device *pdev)
{
	if (!opal_check_token(OPAL_SECVAR_GET)
			|| !opal_check_token(OPAL_SECVAR_GET_NEXT)
			|| !opal_check_token(OPAL_SECVAR_ENQUEUE_UPDATE)) {
		pr_err("OPAL doesn't support secure variables\n");
		return -ENODEV;
	}

	return set_secvar_ops(&opal_secvar_ops);
}

static const struct of_device_id opal_secvar_match[] = {
	{ .compatible = "ibm,secvar-backend",},
	{},
};

static struct platform_driver opal_secvar_driver = {
	.driver = {
		.name = "secvar",
		.of_match_table = opal_secvar_match,
	},
};

static int __init opal_secvar_init(void)
{
	return platform_driver_probe(&opal_secvar_driver, opal_secvar_probe);
}
device_initcall(opal_secvar_init);