@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 IBM Corporation
+ * Author: Nayna Jain <nayna@linux.ibm.com>
+ *
+ * secvar.c
+ * - wrappers to expose secure variables to userspace
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/kdebug.h>
+#include <linux/efi.h>
+#include <linux/module.h>
+#include <linux/ucs2_string.h>
+#include <asm/opal-secvar.h>
+#include <asm/opal.h>
+
+static struct efivars efivars;
+struct kobject *powerpc_kobj;
+
+efi_status_t opal_to_efi_status_log(int rc, const char *func_name)
+{
+ efi_status_t status;
+
+ switch (rc) {
+ case OPAL_EMPTY:
+ status = EFI_NOT_FOUND;
+ break;
+ case OPAL_HARDWARE:
+ status = EFI_DEVICE_ERROR;
+ break;
+ case OPAL_NO_MEM:
+ pr_err("%s: No space in the volatile storage\n", func_name);
+ status = EFI_OUT_OF_RESOURCES;
+ break;
+ case OPAL_PARAMETER:
+ status = EFI_INVALID_PARAMETER;
+ break;
+ case OPAL_PARTIAL:
+ status = EFI_BUFFER_TOO_SMALL;
+ break;
+ case OPAL_PERMISSION:
+ status = EFI_WRITE_PROTECTED;
+ break;
+ case OPAL_RESOURCE:
+ pr_err("%s: No space in the non-volatile storage\n", func_name);
+ status = EFI_OUT_OF_RESOURCES;
+ break;
+ case OPAL_SUCCESS:
+ status = EFI_SUCCESS;
+ break;
+ default:
+ pr_err("%s: Unknown OPAL error %d\n", func_name, rc);
+ status = EFI_DEVICE_ERROR;
+ break;
+ }
+
+ return status;
+}
+
+#define opal_to_efi_status(rc) opal_to_efi_status_log(rc, __func__)
+
+static void createkey(efi_char16_t *name, u8 **key, unsigned long *keylen)
+{
+ *keylen = ucs2_utf8size(name) + 1;
+
+ *key = kzalloc(*keylen, GFP_KERNEL);
+ if (!*key) {
+ *keylen = 0;
+ *key = NULL;
+ return;
+ }
+
+ ucs2_as_utf8(*key, name, *keylen);
+}
+
+static void createmetadata(efi_char16_t *name, efi_guid_t *vendor, u32 *attr,
+ u8 **mdata, unsigned long *mdsize)
+{
+ int size = 0;
+
+ *mdsize = ucs2_strsize(name, 1024) + sizeof(efi_guid_t) + sizeof(u32);
+ *mdata = kzalloc(*mdsize, GFP_KERNEL);
+
+ memcpy(*mdata, name, ucs2_strsize(name, 1024));
+ size = ucs2_strsize(name, 1024);
+
+ memcpy(*mdata + size, vendor, sizeof(efi_guid_t));
+ size += sizeof(efi_guid_t);
+
+ if (attr != NULL)
+ memcpy(*mdata + size, attr, sizeof(u32));
+ else
+ memset(*mdata + size, 0, sizeof(u32));
+}
+
+static int convert_buffer_to_efi_guid(u8 *buffer, efi_guid_t *guid)
+{
+ u32 *a1;
+ u16 *a2;
+ u16 *a3;
+
+ a1 = kzalloc(4, GFP_KERNEL);
+ memcpy(a1, buffer, 4);
+ *a1 = be32_to_cpu(*a1);
+
+ a2 = kzalloc(2, GFP_KERNEL);
+ memcpy(a2, buffer+4, 2);
+ *a2 = be16_to_cpu(*a2);
+
+ a3 = kzalloc(2, GFP_KERNEL);
+ memcpy(a3, buffer+6, 2);
+ *a3 = be16_to_cpu(*a3);
+
+ *guid = EFI_GUID(*a1, *a2, *a3, *(buffer + 8),
+ *(buffer + 9),
+ *(buffer + 10),
+ *(buffer + 11),
+ *(buffer + 12),
+ *(buffer + 13),
+ *(buffer + 14),
+ *(buffer + 15));
+
+ kfree(a1);
+ kfree(a2);
+ kfree(a3);
+ return 0;
+}
+
+static efi_status_t powerpc_get_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 *attr, unsigned long *data_size,
+ void *data)
+{
+ int rc;
+ u8 *key;
+ unsigned long keylen;
+ u8 *metadata;
+ unsigned long mdsize;
+ unsigned long dsize;
+ unsigned long namesize;
+
+ if (!name)
+ return EFI_INVALID_PARAMETER;
+
+ if (!vendor)
+ return EFI_INVALID_PARAMETER;
+
+ if (*data_size == 0) {
+ /* If *data_size is zero, it implies data size is being asked */
+ createkey(name, &key, &keylen);
+ rc = opal_get_variable_size(key, keylen, &mdsize, &dsize);
+ *data_size = dsize;
+ kfree(key);
+ return opal_to_efi_status(rc);
+ }
+
+ createkey(name, &key, &keylen);
+ createmetadata(name, vendor, attr, &metadata, &mdsize);
+
+ rc = opal_get_variable(key, keylen, metadata, &mdsize, data, data_size);
+
+ if (rc)
+ return opal_to_efi_status(rc);
+
+ if (mdsize > 0) {
+ namesize = mdsize - sizeof(efi_guid_t) - sizeof(u32);
+ if (!attr)
+ return opal_to_efi_status(rc);
+ memset(attr, 0, sizeof(u32));
+ memcpy(attr, metadata + namesize + sizeof(efi_guid_t),
+ sizeof(u32));
+ *attr = be32_to_cpu(*attr);
+ }
+
+ kfree(key);
+ kfree(metadata);
+
+ return opal_to_efi_status(rc);
+}
+
+
+static efi_status_t powerpc_get_next_variable(unsigned long *name_size,
+ efi_char16_t *name,
+ efi_guid_t *vendor)
+{
+ int rc;
+ u8 *key;
+ int namesize;
+ unsigned long keylen;
+ unsigned long keysize = 1024;
+ unsigned long *mdsize;
+ u8 *mdata = NULL;
+ efi_guid_t guid;
+
+ if (ucs2_strnlen(name, 1024) > 0) {
+ createkey(name, &key, &keylen);
+ } else {
+ keylen = 0;
+ key = kzalloc(1024, GFP_KERNEL);
+ }
+
+ pr_info("%s: powerpc get next variable, key is %s\n", __func__, key);
+
+ rc = opal_get_next_variable(key, &keylen, keysize);
+ if (rc) {
+ kfree(key);
+ return opal_to_efi_status(rc);
+ }
+
+ mdsize = kzalloc(sizeof(unsigned long), GFP_KERNEL);
+ rc = opal_get_variable_size(key, keylen, mdsize, NULL);
+ if (rc)
+ goto out;
+
+ if (*mdsize <= 0)
+ goto out;
+
+ mdata = kzalloc(*mdsize, GFP_KERNEL);
+
+ rc = opal_get_variable(key, keylen, mdata, mdsize, NULL, NULL);
+ if (rc)
+ goto out;
+
+ if (*mdsize > 0) {
+ namesize = *mdsize - sizeof(efi_guid_t) - sizeof(u32);
+ if (namesize > 0) {
+ memset(&guid, 0, sizeof(efi_guid_t));
+ convert_buffer_to_efi_guid(mdata + namesize, &guid);
+ memcpy(vendor, &guid, sizeof(efi_guid_t));
+ memset(name, 0, namesize + 2);
+ memcpy(name, mdata, namesize);
+ *name_size = namesize + 2;
+ name[namesize++] = 0;
+ name[namesize] = 0;
+ }
+ }
+
+out:
+ kfree(mdsize);
+ kfree(mdata);
+
+ return opal_to_efi_status(rc);
+}
+
+static efi_status_t powerpc_set_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size,
+ void *data)
+{
+ int rc;
+ u8 *key;
+ unsigned long keylen;
+ u8 *metadata;
+ unsigned long mdsize;
+
+ if (!name)
+ return EFI_INVALID_PARAMETER;
+
+ if (!vendor)
+ return EFI_INVALID_PARAMETER;
+
+ createkey(name, &key, &keylen);
+ pr_info("%s: nayna key is %s\n", __func__, key);
+
+ createmetadata(name, vendor, &attr, &metadata, &mdsize);
+
+ rc = opal_set_variable(key, keylen, metadata, mdsize, data, data_size);
+
+ return opal_to_efi_status(rc);
+}
+
+
+static const struct efivar_operations efivar_ops = {
+ .get_variable = powerpc_get_variable,
+ .set_variable = powerpc_set_variable,
+ .get_next_variable = powerpc_get_next_variable,
+};
+
+
+static __init int power_secvar_init(void)
+{
+ int rc = 0;
+ unsigned long ver = 0;
+
+ rc = opal_variable_version(&ver);
+ if (ver != BACKEND_TC_COMPAT_V1) {
+ pr_info("Compatible backend unsupported\n");
+ return -1;
+ }
+
+ powerpc_kobj = kobject_create_and_add("secvar", firmware_kobj);
+ if (!powerpc_kobj) {
+ pr_info("secvar: Failed to create firmware kobj\n");
+ goto out_err;
+ }
+
+ rc = efivars_register(&efivars, &efivar_ops, powerpc_kobj);
+ if (rc) {
+ pr_info("powerpc: Failed to register efivars\n");
+ return rc;
+ }
+
+ return 0;
+out_err:
+ kobject_put(powerpc_kobj);
+ pr_info("powerpc: failed to load: %d\n", rc);
+ return rc;
+}
+arch_initcall(power_secvar_init);
+
+static void __exit power_secvar_exit(void)
+{
+ efivars_unregister(&efivars);
+ kobject_put(powerpc_kobj);
+}
+module_exit(power_secvar_exit);
+
+MODULE_AUTHOR("Nayna Jain");
+MODULE_LICENSE("GPL");